diff --git a/include/zephyr/net/net_context.h b/include/zephyr/net/net_context.h index 430c1164f4047..6b7e2a7e233e8 100644 --- a/include/zephyr/net/net_context.h +++ b/include/zephyr/net/net_context.h @@ -1292,6 +1292,7 @@ enum net_context_option { NET_OPT_TTL = 16, /**< IPv4 unicast TTL */ NET_OPT_ADDR_PREFERENCES = 17, /**< IPv6 address preference */ NET_OPT_TIMESTAMPING = 18, /**< Packet timestamping */ + NET_OPT_MTU = 20, /**< IPv4 socket path MTU */ }; /** diff --git a/include/zephyr/net/net_event.h b/include/zephyr/net/net_event.h index 9bcba06be76eb..f0f8545a713a7 100644 --- a/include/zephyr/net/net_event.h +++ b/include/zephyr/net/net_event.h @@ -74,6 +74,7 @@ enum net_event_ipv6_cmd { NET_EVENT_IPV6_CMD_PE_DISABLED, NET_EVENT_IPV6_CMD_PE_FILTER_ADD, NET_EVENT_IPV6_CMD_PE_FILTER_DEL, + NET_EVENT_IPV6_CMD_PMTU_CHANGED, }; /* IPv4 Events*/ @@ -99,6 +100,7 @@ enum net_event_ipv4_cmd { NET_EVENT_IPV4_CMD_ACD_SUCCEED, NET_EVENT_IPV4_CMD_ACD_FAILED, NET_EVENT_IPV4_CMD_ACD_CONFLICT, + NET_EVENT_IPV4_CMD_PMTU_CHANGED, }; /* L4 network events */ @@ -237,6 +239,10 @@ enum net_event_l4_cmd { #define NET_EVENT_IPV6_PE_FILTER_DEL \ (_NET_EVENT_IPV6_BASE | NET_EVENT_IPV6_CMD_PE_FILTER_DEL) +/** IPv6 Path MTU is changed. */ +#define NET_EVENT_IPV6_PMTU_CHANGED \ + (_NET_EVENT_IPV6_BASE | NET_EVENT_IPV6_CMD_PMTU_CHANGED) + /** Event emitted when an IPv4 address is added to the system. */ #define NET_EVENT_IPV4_ADDR_ADD \ (_NET_EVENT_IPV4_BASE | NET_EVENT_IPV4_CMD_ADDR_ADD) @@ -296,6 +302,10 @@ enum net_event_l4_cmd { #define NET_EVENT_IPV4_ACD_CONFLICT \ (_NET_EVENT_IPV4_BASE | NET_EVENT_IPV4_CMD_ACD_CONFLICT) +/** IPv4 Path MTU is changed. */ +#define NET_EVENT_IPV4_PMTU_CHANGED \ + (_NET_EVENT_IPV4_BASE | NET_EVENT_IPV4_CMD_PMTU_CHANGED) + /** Event emitted when the system is considered to be connected. * The connected in this context means that the network interface is up, * and the interface has either IPv4 or IPv6 address assigned to it. @@ -441,6 +451,34 @@ struct net_event_ipv6_pe_filter { bool is_deny_list; }; +/** + * @brief Network Management event information structure + * Used to pass information on network event + * NET_EVENT_IPV4_PMTU_CHANGED + * when CONFIG_NET_MGMT_EVENT_INFO enabled and event generator pass the + * information. + */ +struct net_event_ipv4_pmtu_info { + /** IPv4 address */ + struct in_addr dst; + /** New MTU */ + uint16_t mtu; +}; + +/** + * @brief Network Management event information structure + * Used to pass information on network event + * NET_EVENT_IPV6_PMTU_CHANGED + * when CONFIG_NET_MGMT_EVENT_INFO enabled and event generator pass the + * information. + */ +struct net_event_ipv6_pmtu_info { + /** IPv6 address */ + struct in6_addr dst; + /** New MTU */ + uint32_t mtu; +}; + #ifdef __cplusplus } #endif diff --git a/include/zephyr/net/net_pkt.h b/include/zephyr/net/net_pkt.h index 7693f9a79734b..1a3b4a042aeec 100644 --- a/include/zephyr/net/net_pkt.h +++ b/include/zephyr/net/net_pkt.h @@ -342,6 +342,11 @@ struct net_pkt { uint8_t cooked_mode_pkt : 1; #endif /* CONFIG_NET_CAPTURE_COOKED_MODE */ +#if defined(CONFIG_NET_IPV4_PMTU) + /* Path MTU needed for this destination address */ + uint8_t ipv4_pmtu : 1; +#endif /* CONFIG_NET_IPV4_PMTU */ + /* @endcond */ }; @@ -783,6 +788,31 @@ static inline uint16_t net_pkt_ip_opts_len(struct net_pkt *pkt) #endif } +#if defined(CONFIG_NET_IPV4_PMTU) +static inline bool net_pkt_ipv4_pmtu(struct net_pkt *pkt) +{ + return !!pkt->ipv4_pmtu; +} + +static inline void net_pkt_set_ipv4_pmtu(struct net_pkt *pkt, bool value) +{ + pkt->ipv4_pmtu = value; +} +#else +static inline bool net_pkt_ipv4_pmtu(struct net_pkt *pkt) +{ + ARG_UNUSED(pkt); + + return false; +} + +static inline void net_pkt_set_ipv4_pmtu(struct net_pkt *pkt, bool value) +{ + ARG_UNUSED(pkt); + ARG_UNUSED(value); +} +#endif /* CONFIG_NET_IPV4_PMTU */ + #if defined(CONFIG_NET_IPV4_FRAGMENT) static inline uint16_t net_pkt_ipv4_fragment_offset(struct net_pkt *pkt) { diff --git a/include/zephyr/net/net_stats.h b/include/zephyr/net/net_stats.h index f41bf204e9682..2fb28b6104c71 100644 --- a/include/zephyr/net/net_stats.h +++ b/include/zephyr/net/net_stats.h @@ -198,6 +198,34 @@ struct net_stats_ipv6_nd { net_stats_t sent; }; +/** + * @brief IPv6 Path MTU Discovery statistics + */ +struct net_stats_ipv6_pmtu { + /** Number of dropped IPv6 PMTU packets. */ + net_stats_t drop; + + /** Number of received IPv6 PMTU packets. */ + net_stats_t recv; + + /** Number of sent IPv6 PMTU packets. */ + net_stats_t sent; +}; + +/** + * @brief IPv4 Path MTU Discovery statistics + */ +struct net_stats_ipv4_pmtu { + /** Number of dropped IPv4 PMTU packets. */ + net_stats_t drop; + + /** Number of received IPv4 PMTU packets. */ + net_stats_t recv; + + /** Number of sent IPv4 PMTU packets. */ + net_stats_t sent; +}; + /** * @brief IPv6 multicast listener daemon statistics */ @@ -379,6 +407,16 @@ struct net_stats { struct net_stats_ipv6_nd ipv6_nd; #endif +#if defined(CONFIG_NET_STATISTICS_IPV6_PMTU) + /** IPv6 Path MTU Discovery statistics */ + struct net_stats_ipv6_pmtu ipv6_pmtu; +#endif + +#if defined(CONFIG_NET_STATISTICS_IPV4_PMTU) + /** IPv4 Path MTU Discovery statistics */ + struct net_stats_ipv4_pmtu ipv4_pmtu; +#endif + #if defined(CONFIG_NET_STATISTICS_MLD) /** IPv6 MLD statistics */ struct net_stats_ipv6_mld ipv6_mld; @@ -665,6 +703,8 @@ enum net_request_stats_cmd { NET_REQUEST_STATS_CMD_GET_IPV4, NET_REQUEST_STATS_CMD_GET_IPV6, NET_REQUEST_STATS_CMD_GET_IPV6_ND, + NET_REQUEST_STATS_CMD_GET_IPV6_PMTU, + NET_REQUEST_STATS_CMD_GET_IPV4_PMTU, NET_REQUEST_STATS_CMD_GET_ICMP, NET_REQUEST_STATS_CMD_GET_UDP, NET_REQUEST_STATS_CMD_GET_TCP, @@ -732,6 +772,26 @@ NET_MGMT_DEFINE_REQUEST_HANDLER(NET_REQUEST_STATS_GET_IPV6_ND); /** @endcond */ #endif /* CONFIG_NET_STATISTICS_IPV6_ND */ +#if defined(CONFIG_NET_STATISTICS_IPV6_PMTU) +/** Request IPv6 Path MTU Discovery statistics */ +#define NET_REQUEST_STATS_GET_IPV6_PMTU \ + (_NET_STATS_BASE | NET_REQUEST_STATS_CMD_GET_IPV6_PMTU) + +/** @cond INTERNAL_HIDDEN */ +NET_MGMT_DEFINE_REQUEST_HANDLER(NET_REQUEST_STATS_GET_IPV6_PMTU); +/** @endcond */ +#endif /* CONFIG_NET_STATISTICS_IPV6_PMTU */ + +#if defined(CONFIG_NET_STATISTICS_IPV4_PMTU) +/** Request IPv4 Path MTU Discovery statistics */ +#define NET_REQUEST_STATS_GET_IPV4_PMTU \ + (_NET_STATS_BASE | NET_REQUEST_STATS_CMD_GET_IPV4_PMTU) + +/** @cond INTERNAL_HIDDEN */ +NET_MGMT_DEFINE_REQUEST_HANDLER(NET_REQUEST_STATS_GET_IPV4_PMTU); +/** @endcond */ +#endif /* CONFIG_NET_STATISTICS_IPV4_PMTU */ + #if defined(CONFIG_NET_STATISTICS_ICMP) /** Request ICMPv4 and ICMPv6 statistics */ #define NET_REQUEST_STATS_GET_ICMP \ diff --git a/include/zephyr/net/socket.h b/include/zephyr/net/socket.h index f1a937d42294d..2519a186614d4 100644 --- a/include/zephyr/net/socket.h +++ b/include/zephyr/net/socket.h @@ -1190,6 +1190,12 @@ struct in_pktinfo { struct in_addr ipi_addr; /**< Header Destination address */ }; +/** Retrieve the current known path MTU of the current socket. Returns an + * integer. IP_MTU is valid only for getsockopt and can be employed only when + * the socket has been connected. + */ +#define IP_MTU 14 + /** Set IPv4 multicast TTL value. */ #define IP_MULTICAST_TTL 33 /** Join IPv4 multicast group. */ @@ -1236,6 +1242,13 @@ struct ipv6_mreq { int ipv6mr_ifindex; }; +/** For getsockopt(), retrieve the current known IPv6 path MTU of the given socket. + * Valid only when the socket has been connected. + * For setsockopt(), set the MTU to be used for the socket. The MTU is limited by + * the device MTU or the path MTU when path MTU discovery is enabled. + */ +#define IPV6_MTU 24 + /** Don't support IPv4 access */ #define IPV6_V6ONLY 26 diff --git a/samples/net/stats/src/main.c b/samples/net/stats/src/main.c index 171757cac853c..fdfb7e4339c18 100644 --- a/samples/net/stats/src/main.c +++ b/samples/net/stats/src/main.c @@ -43,6 +43,12 @@ static void print_stats(struct net_if *iface, struct net_stats *data) GET_STAT(iface, ipv6_nd.sent), GET_STAT(iface, ipv6_nd.drop)); #endif /* CONFIG_NET_IPV6_ND */ +#if defined(CONFIG_NET_IPV6_PMTU) + printk("IPv6 PMTU recv %d\tsent\t%d\tdrop\t%d\n", + GET_STAT(iface, ipv6_pmtu.recv), + GET_STAT(iface, ipv6_pmtu.sent), + GET_STAT(iface, ipv6_pmtu.drop)); +#endif /* CONFIG_NET_IPV6_PMTU */ #if defined(CONFIG_NET_STATISTICS_MLD) printk("IPv6 MLD recv %d\tsent\t%d\tdrop\t%d\n", GET_STAT(iface, ipv6_mld.recv), @@ -68,6 +74,13 @@ static void print_stats(struct net_if *iface, struct net_stats *data) GET_STAT(iface, ip_errors.chkerr), GET_STAT(iface, ip_errors.protoerr)); +#if defined(CONFIG_NET_IPV4_PMTU) + printk("IPv4 PMTU recv %d\tsent\t%d\tdrop\t%d\n", + GET_STAT(iface, ipv4_pmtu.recv), + GET_STAT(iface, ipv4_pmtu.sent), + GET_STAT(iface, ipv4_pmtu.drop)); +#endif /* CONFIG_NET_IPV4_PMTU */ + printk("ICMP recv %d\tsent\t%d\tdrop\t%d\n", GET_STAT(iface, icmp.recv), GET_STAT(iface, icmp.sent), diff --git a/subsys/net/ip/CMakeLists.txt b/subsys/net/ip/CMakeLists.txt index 23d433930b1f7..961a0c036d65e 100644 --- a/subsys/net/ip/CMakeLists.txt +++ b/subsys/net/ip/CMakeLists.txt @@ -42,6 +42,7 @@ zephyr_library_sources_ifdef(CONFIG_NET_IPV6_PE ipv6_pe.c) zephyr_library_sources_ifdef(CONFIG_NET_IPV6_FRAGMENT ipv6_fragment.c) zephyr_library_sources_ifdef(CONFIG_NET_IPV4_FRAGMENT ipv4_fragment.c) zephyr_library_sources_ifdef(CONFIG_NET_MGMT_EVENT net_mgmt.c) +zephyr_library_sources_ifdef(CONFIG_NET_PMTU pmtu.c) zephyr_library_sources_ifdef(CONFIG_NET_ROUTE route.c) zephyr_library_sources_ifdef(CONFIG_NET_STATISTICS net_stats.c) zephyr_library_sources_ifdef(CONFIG_NET_TCP tcp.c) diff --git a/subsys/net/ip/Kconfig b/subsys/net/ip/Kconfig index b61e2dd74c3cc..f123854a7a32c 100644 --- a/subsys/net/ip/Kconfig +++ b/subsys/net/ip/Kconfig @@ -48,6 +48,22 @@ config NET_NATIVE_IPV4 depends on NET_NATIVE default y if NET_IPV4 +config NET_PMTU + bool + select NET_MGMT + select NET_MGMT_EVENT + select NET_MGMT_EVENT_INFO + default y + depends on NET_IPV6_PMTU || NET_IPV4_PMTU + +if NET_PMTU +module = NET_PMTU +module-dep = NET_LOG +module-str = Log level for PMTU +module-help = Enables PMTU to output debug messages. +source "subsys/net/Kconfig.template.log_config.net" +endif # NET_PMTU + config NET_NATIVE_TCP bool depends on NET_NATIVE @@ -252,6 +268,11 @@ config NET_SHELL_PKT_ALLOC_SUPPORTED default y depends on NET_SHELL_SHOW_DISABLED_COMMANDS || NET_DEBUG_NET_PKT_ALLOC +config NET_SHELL_PMTU_SUPPORTED + bool "PMTU config" + default y + depends on NET_SHELL_SHOW_DISABLED_COMMANDS || NET_PMTU + config NET_SHELL_PPP_SUPPORTED bool "PPP config" default y diff --git a/subsys/net/ip/Kconfig.ipv4 b/subsys/net/ip/Kconfig.ipv4 index 77c29c8966ced..e2456557caab3 100644 --- a/subsys/net/ip/Kconfig.ipv4 +++ b/subsys/net/ip/Kconfig.ipv4 @@ -151,6 +151,18 @@ config NET_IPV4_FRAGMENT_TIMEOUT How long to wait for IPv4 fragment to arrive before the reassembly will timeout. This value is in seconds. +config NET_IPV4_PMTU + bool "IPv4 Path MTU Discovery" + help + Enables IPv4 Path MTU Discovery (see RFC 1191) + +config NET_IPV4_PMTU_DESTINATION_CACHE_ENTRIES + int "Number of IPv4 PMTU destination cache entries" + default 3 + depends on NET_IPV4_PMTU + help + How many PMTU entries we can track for each destination address. + module = NET_IPV4 module-dep = NET_LOG module-str = Log level for core IPv4 diff --git a/subsys/net/ip/Kconfig.ipv6 b/subsys/net/ip/Kconfig.ipv6 index 33e493d2a237d..2c818bdfb1235 100644 --- a/subsys/net/ip/Kconfig.ipv6 +++ b/subsys/net/ip/Kconfig.ipv6 @@ -46,6 +46,18 @@ config NET_IPV6_MTU The value should normally be 1280 which is the minimum IPv6 packet size that implementations need to support without fragmentation. +config NET_IPV6_PMTU + bool "IPv6 Path MTU Discovery" + help + Enables IPv6 Path MTU Discovery (see RFC 8201) + +config NET_IPV6_PMTU_DESTINATION_CACHE_ENTRIES + int "Number of IPv6 PMTU destination cache entries" + default 3 + depends on NET_IPV6_PMTU + help + How many PMTU entries we can track for each destination address. + config NET_INITIAL_HOP_LIMIT int "Initial IPv6 hop limit value for unicast packets" default 64 diff --git a/subsys/net/ip/Kconfig.stats b/subsys/net/ip/Kconfig.stats index ae0d6e95e46b0..cdd59c11685f1 100644 --- a/subsys/net/ip/Kconfig.stats +++ b/subsys/net/ip/Kconfig.stats @@ -57,6 +57,20 @@ config NET_STATISTICS_IPV6_ND help Keep track of IPv6 Neighbor Discovery related statistics +config NET_STATISTICS_IPV6_PMTU + bool "IPv6 PMTU statistics" + depends on NET_IPV6_PMTU + default y + help + Keep track of IPv6 Path MTU Discovery related statistics + +config NET_STATISTICS_IPV4_PMTU + bool "IPv4 PMTU statistics" + depends on NET_IPV4_PMTU + default y + help + Keep track of IPv4 Path MTU Discovery related statistics + config NET_STATISTICS_ICMP bool "ICMP statistics" depends on NET_IPV6 || NET_IPV4 diff --git a/subsys/net/ip/icmpv4.c b/subsys/net/ip/icmpv4.c index 5e1cb35566c57..cc25e572dd4cf 100644 --- a/subsys/net/ip/icmpv4.c +++ b/subsys/net/ip/icmpv4.c @@ -21,6 +21,7 @@ LOG_MODULE_REGISTER(net_icmpv4, CONFIG_NET_ICMPV4_LOG_LEVEL); #include "ipv4.h" #include "icmpv4.h" #include "net_stats.h" +#include "pmtu.h" #define PKT_WAIT_TIME K_SECONDS(1) @@ -654,6 +655,108 @@ enum net_verdict net_icmpv4_input(struct net_pkt *pkt, return NET_DROP; } +#if defined(CONFIG_NET_IPV4_PMTU) +/* The RFC 1191 chapter 3 says the minimum MTU size is 68 octets. + * This is way too small in modern world, so make the minimum 576 octets. + */ +#define MIN_IPV4_MTU NET_IPV4_MTU + +static int icmpv4_handle_dst_unreach(struct net_icmp_ctx *ctx, + struct net_pkt *pkt, + struct net_icmp_ip_hdr *hdr, + struct net_icmp_hdr *icmp_hdr, + void *user_data) +{ + NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(dst_unreach_access, + struct net_icmpv4_dest_unreach); + struct net_icmpv4_dest_unreach *dest_unreach_hdr; + struct net_ipv4_hdr *ip_hdr = hdr->ipv4; + uint16_t length = net_pkt_get_len(pkt); + struct net_pmtu_entry *entry; + struct sockaddr_in sockaddr_src = { + .sin_family = AF_INET, + }; + uint16_t mtu; + int ret; + + ARG_UNUSED(user_data); + + dest_unreach_hdr = (struct net_icmpv4_dest_unreach *) + net_pkt_get_data(pkt, &dst_unreach_access); + if (dest_unreach_hdr == NULL) { + NET_DBG("DROP: NULL ICMPv4 Destination Unreachable header"); + goto drop; + } + + net_stats_update_ipv4_pmtu_recv(net_pkt_iface(pkt)); + + NET_DBG("Received Destination Unreachable from %s to %s", + net_sprint_ipv4_addr(&ip_hdr->src), + net_sprint_ipv4_addr(&ip_hdr->dst)); + + if (length < (sizeof(struct net_ipv4_hdr) + + sizeof(struct net_icmp_hdr) + + sizeof(struct net_icmpv4_dest_unreach))) { + NET_DBG("DROP: length %d too big %zd", + length, sizeof(struct net_ipv4_hdr) + + sizeof(struct net_icmp_hdr) + + sizeof(struct net_icmpv4_dest_unreach)); + goto drop; + } + + net_pkt_acknowledge_data(pkt, &dst_unreach_access); + + mtu = ntohs(dest_unreach_hdr->mtu); + + if (mtu < MIN_IPV4_MTU) { + NET_DBG("DROP: Unsupported MTU %u, min is %u", + mtu, MIN_IPV4_MTU); + goto drop; + } + + net_ipaddr_copy(&sockaddr_src.sin_addr, (struct in_addr *)&ip_hdr->src); + + entry = net_pmtu_get_entry((struct sockaddr *)&sockaddr_src); + if (entry == NULL) { + NET_DBG("DROP: Cannot find PMTU entry for %s", + net_sprint_ipv4_addr(&ip_hdr->src)); + goto silent_drop; + } + + /* We must not accept larger PMTU value than what we already know. + * RFC 1191 chapter 3 page 5. + */ + if (entry->mtu > 0 && entry->mtu < mtu) { + NET_DBG("DROP: PMTU for %s %u larger than %u", + net_sprint_ipv4_addr(&ip_hdr->src), mtu, + entry->mtu); + goto silent_drop; + } + + ret = net_pmtu_update_entry(entry, mtu); + if (ret > 0) { + NET_DBG("PMTU for %s changed from %u to %u", + net_sprint_ipv4_addr(&ip_hdr->src), ret, mtu); + } + + return 0; +drop: + net_stats_update_ipv4_pmtu_drop(net_pkt_iface(pkt)); + + return -EIO; + +silent_drop: + /* If the event is not really an error then just ignore it and + * return 0 so that icmpv4 module will not complain about it. + */ + net_stats_update_ipv4_pmtu_drop(net_pkt_iface(pkt)); + + return 0; +} + +static struct net_icmp_ctx dst_unreach_ctx; +#endif /* CONFIG_NET_IPV4_PMTU */ + void net_icmpv4_init(void) { static struct net_icmp_ctx ctx; @@ -664,4 +767,13 @@ void net_icmpv4_init(void) NET_ERR("Cannot register %s handler (%d)", STRINGIFY(NET_ICMPV4_ECHO_REQUEST), ret); } + +#if defined(CONFIG_NET_IPV4_PMTU) + ret = net_icmp_init_ctx(&dst_unreach_ctx, NET_ICMPV4_DST_UNREACH, 0, + icmpv4_handle_dst_unreach); + if (ret < 0) { + NET_ERR("Cannot register %s handler (%d)", STRINGIFY(NET_ICMPV4_DST_UNREACH), + ret); + } +#endif } diff --git a/subsys/net/ip/icmpv4.h b/subsys/net/ip/icmpv4.h index ae848c9d96415..88f13971c9ca2 100644 --- a/subsys/net/ip/icmpv4.h +++ b/subsys/net/ip/icmpv4.h @@ -34,6 +34,11 @@ struct net_icmpv4_echo_req { uint16_t sequence; } __packed; +struct net_icmpv4_dest_unreach { + uint16_t unused; + uint16_t mtu; +} __packed; + /** * @brief Send ICMPv4 error message. * @param pkt Network packet that this error is related to. diff --git a/subsys/net/ip/icmpv6.h b/subsys/net/ip/icmpv6.h index 64ee1dfaa7bcb..2b68ae8bfaeda 100644 --- a/subsys/net/ip/icmpv6.h +++ b/subsys/net/ip/icmpv6.h @@ -117,6 +117,9 @@ struct net_icmpv6_mld_mcast_record { uint8_t mcast_address[NET_IPV6_ADDR_SIZE]; } __packed; +struct net_icmpv6_ptb { + uint32_t mtu; +} __packed; #define NET_ICMPV6_ND_O_FLAG(flag) ((flag) & 0x40) #define NET_ICMPV6_ND_M_FLAG(flag) ((flag) & 0x80) diff --git a/subsys/net/ip/ipv4.c b/subsys/net/ip/ipv4.c index a7d74d74a2fd4..7178ba1d1dbb4 100644 --- a/subsys/net/ip/ipv4.c +++ b/subsys/net/ip/ipv4.c @@ -25,6 +25,7 @@ LOG_MODULE_REGISTER(net_ipv4, CONFIG_NET_IPV4_LOG_LEVEL); #include "tcp_internal.h" #include "dhcpv4/dhcpv4_internal.h" #include "ipv4.h" +#include "pmtu.h" BUILD_ASSERT(sizeof(struct in_addr) == NET_IPV4_ADDR_SIZE); @@ -90,13 +91,18 @@ int net_ipv4_create(struct net_pkt *pkt, const struct in_addr *dst) { uint8_t tos = 0; + uint8_t flags = 0U; if (IS_ENABLED(CONFIG_NET_IP_DSCP_ECN)) { net_ipv4_set_dscp(&tos, net_pkt_ip_dscp(pkt)); net_ipv4_set_ecn(&tos, net_pkt_ip_ecn(pkt)); } - return net_ipv4_create_full(pkt, src, dst, tos, 0U, 0U, 0U); + if (IS_ENABLED(CONFIG_NET_IPV4_PMTU) && net_pkt_ipv4_pmtu(pkt)) { + flags = NET_IPV4_DF; + } + + return net_ipv4_create_full(pkt, src, dst, tos, 0U, flags, 0U); } int net_ipv4_finalize(struct net_pkt *pkt, uint8_t next_header_proto) @@ -444,6 +450,36 @@ enum net_verdict net_ipv4_input(struct net_pkt *pkt, bool is_loopback) return NET_DROP; } +enum net_verdict net_ipv4_prepare_for_send(struct net_pkt *pkt) +{ + if (IS_ENABLED(CONFIG_NET_IPV4_PMTU)) { + struct net_pmtu_entry *entry; + struct sockaddr_in dst = { + .sin_family = AF_INET, + }; + int ret; + + net_ipv4_addr_copy_raw((uint8_t *)&dst.sin_addr, + NET_IPV4_HDR(pkt)->dst); + entry = net_pmtu_get_entry((struct sockaddr *)&dst); + if (entry == NULL) { + ret = net_pmtu_update_mtu((struct sockaddr *)&dst, + net_if_get_mtu(net_pkt_iface(pkt))); + if (ret < 0) { + NET_DBG("Cannot update PMTU for %s (%d)", + net_sprint_ipv4_addr(&dst.sin_addr), + ret); + } + } + } + +#if defined(CONFIG_NET_IPV4_FRAGMENT) + return net_ipv4_prepare_for_send_fragment(pkt); +#else + return NET_OK; +#endif +} + void net_ipv4_init(void) { if (IS_ENABLED(CONFIG_NET_IPV4_FRAGMENT)) { diff --git a/subsys/net/ip/ipv4.h b/subsys/net/ip/ipv4.h index 8c063952e9fff..e70fae3ff1f7c 100644 --- a/subsys/net/ip/ipv4.h +++ b/subsys/net/ip/ipv4.h @@ -357,6 +357,17 @@ typedef void (*net_ipv4_frag_cb_t)(struct net_ipv4_reassembly *reass, void *user */ void net_ipv4_frag_foreach(net_ipv4_frag_cb_t cb, void *user_data); +/** + * @brief Prepare packet for sending, this will split up a packet that is too large to send into + * multiple fragments so that it can be sent. It will also update PMTU destination cache if it + * is enabled. + * + * @param pkt Network packet + * + * @return Return verdict about the packet. + */ +enum net_verdict net_ipv4_prepare_for_send(struct net_pkt *pkt); + #if defined(CONFIG_NET_NATIVE_IPV4) /** * @brief Initialises IPv4 @@ -384,22 +395,9 @@ static inline enum net_verdict net_ipv4_handle_fragment_hdr(struct net_pkt *pkt, } #endif /* CONFIG_NET_IPV4_FRAGMENT */ -/** - * @brief Prepare packet for sending, this will split up a packet that is too large to send into - * multiple fragments so that it can be sent. - * - * @param pkt Network packet - * - * @return Return verdict about the packet. - */ #if defined(CONFIG_NET_IPV4_FRAGMENT) -enum net_verdict net_ipv4_prepare_for_send(struct net_pkt *pkt); -#else -static inline enum net_verdict net_ipv4_prepare_for_send(struct net_pkt *pkt) -{ - return NET_OK; -} -#endif /* CONFIG_NET_IPV4_FRAGMENT */ +enum net_verdict net_ipv4_prepare_for_send_fragment(struct net_pkt *pkt); +#endif /** * @brief Sets up fragment buffers for usage, should only be called by the SYS_INIT() handler in diff --git a/subsys/net/ip/ipv4_fragment.c b/subsys/net/ip/ipv4_fragment.c index 2fe6fb2033dd3..69fe952215989 100644 --- a/subsys/net/ip/ipv4_fragment.c +++ b/subsys/net/ip/ipv4_fragment.c @@ -23,6 +23,7 @@ LOG_MODULE_DECLARE(net_ipv4, CONFIG_NET_IPV4_LOG_LEVEL); #include "ipv4.h" #include "route.h" #include "net_stats.h" +#include "pmtu.h" /* Timeout for various buffer allocations in this file. */ #define NET_BUF_TIMEOUT K_MSEC(100) @@ -607,7 +608,7 @@ int net_ipv4_send_fragmented_pkt(struct net_if *iface, struct net_pkt *pkt, return 0; } -enum net_verdict net_ipv4_prepare_for_send(struct net_pkt *pkt) +enum net_verdict net_ipv4_prepare_for_send_fragment(struct net_pkt *pkt) { NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv4_access, struct net_ipv4_hdr); struct net_ipv4_hdr *ip_hdr; @@ -624,10 +625,26 @@ enum net_verdict net_ipv4_prepare_for_send(struct net_pkt *pkt) * and we can skip other checks. */ if (ip_hdr->id[0] == 0 && ip_hdr->id[1] == 0) { - uint16_t mtu = net_if_get_mtu(net_pkt_iface(pkt)); size_t pkt_len = net_pkt_get_len(pkt); + uint16_t mtu; - mtu = MAX(NET_IPV4_MTU, mtu); + if (IS_ENABLED(CONFIG_NET_IPV4_PMTU)) { + struct sockaddr_in dst = { + .sin_family = AF_INET, + .sin_addr = *((struct in_addr *)ip_hdr->dst), + }; + + ret = net_pmtu_get_mtu((struct sockaddr *)&dst); + if (ret <= 0) { + goto use_interface_mtu; + } + + mtu = ret; + } else { +use_interface_mtu: + mtu = net_if_get_mtu(net_pkt_iface(pkt)); + mtu = MAX(NET_IPV4_MTU, mtu); + } if (pkt_len > mtu) { ret = net_ipv4_send_fragmented_pkt(net_pkt_iface(pkt), pkt, pkt_len, mtu); diff --git a/subsys/net/ip/ipv6.c b/subsys/net/ip/ipv6.c index 032794a76680d..54a884dc2ae8a 100644 --- a/subsys/net/ip/ipv6.c +++ b/subsys/net/ip/ipv6.c @@ -574,6 +574,7 @@ enum net_verdict net_ipv6_input(struct net_pkt *pkt, bool is_loopback) if (!net_pkt_filter_ip_recv_ok(pkt)) { /* drop the packet */ + NET_DBG("DROP: pkt filter"); return NET_DROP; } @@ -587,6 +588,7 @@ enum net_verdict net_ipv6_input(struct net_pkt *pkt, bool is_loopback) * layer. */ if (ipv6_forward_mcast_packet(pkt, hdr) == NET_DROP) { + NET_DBG("DROP: forward mcast"); goto drop; } } @@ -597,6 +599,9 @@ enum net_verdict net_ipv6_input(struct net_pkt *pkt, bool is_loopback) return NET_OK; } + NET_DBG("DROP: no such address %s in iface %d", + net_sprint_ipv6_addr((struct in6_addr *)hdr->dst), + net_if_get_by_iface(pkt_iface)); goto drop; } @@ -611,6 +616,7 @@ enum net_verdict net_ipv6_input(struct net_pkt *pkt, bool is_loopback) pkt_iface, (struct in6_addr *)hdr->dst)) { ipv6_no_route_info(pkt, (struct in6_addr *)hdr->src, (struct in6_addr *)hdr->dst); + NET_DBG("DROP: cross interface boundary"); goto drop; } } @@ -664,6 +670,7 @@ enum net_verdict net_ipv6_input(struct net_pkt *pkt, bool is_loopback) * This is not an error case so do not update drop * statistics. */ + NET_DBG("DROP: none nexthdr"); return NET_DROP; } @@ -671,6 +678,7 @@ enum net_verdict net_ipv6_input(struct net_pkt *pkt, bool is_loopback) prev_hdr_offset = net_pkt_get_current_offset(pkt); if (net_pkt_read_u8(pkt, &nexthdr)) { + NET_DBG("DROP: pkt invalid read"); goto drop; } @@ -731,6 +739,7 @@ enum net_verdict net_ipv6_input(struct net_pkt *pkt, bool is_loopback) exthdr_len = ipv6_handle_ext_hdr_options(pkt, hdr, pkt_len); if (exthdr_len < 0) { + NET_DBG("DROP: extension hdr len (%d)", exthdr_len); goto drop; } @@ -753,12 +762,16 @@ enum net_verdict net_ipv6_input(struct net_pkt *pkt, bool is_loopback) if (proto_hdr.tcp) { verdict = NET_OK; } + + NET_DBG("%s verdict %s", "TCP", net_verdict2str(verdict)); break; case IPPROTO_UDP: proto_hdr.udp = net_udp_input(pkt, &udp_access); if (proto_hdr.udp) { verdict = NET_OK; } + + NET_DBG("%s verdict %s", "UDP", net_verdict2str(verdict)); break; #if defined(CONFIG_NET_L2_IPIP) @@ -787,14 +800,19 @@ enum net_verdict net_ipv6_input(struct net_pkt *pkt, bool is_loopback) } if (verdict == NET_DROP) { + NET_DBG("DROP: because verdict"); goto drop; } else if (current_hdr == IPPROTO_ICMPV6) { + NET_DBG("%s verdict %s", "ICMPv6", net_verdict2str(verdict)); return verdict; } ip.ipv6 = hdr; verdict = net_conn_input(pkt, &ip, current_hdr, &proto_hdr); + + NET_DBG("%s verdict %s", "Connection", net_verdict2str(verdict)); + if (verdict != NET_DROP) { return verdict; } diff --git a/subsys/net/ip/ipv6_fragment.c b/subsys/net/ip/ipv6_fragment.c index fda00c59f0390..f50e5f9e4e21b 100644 --- a/subsys/net/ip/ipv6_fragment.c +++ b/subsys/net/ip/ipv6_fragment.c @@ -686,7 +686,7 @@ static int send_ipv6_fragment(struct net_pkt *pkt, } int net_ipv6_send_fragmented_pkt(struct net_if *iface, struct net_pkt *pkt, - uint16_t pkt_len) + uint16_t pkt_len, uint16_t mtu) { uint16_t next_hdr_off; uint16_t last_hdr_off; @@ -713,12 +713,16 @@ int net_ipv6_send_fragmented_pkt(struct net_if *iface, struct net_pkt *pkt, /* The Maximum payload can fit into each packet after IPv6 header, * Extension headers and Fragmentation header. */ - fit_len = NET_IPV6_MTU - NET_IPV6_FRAGH_LEN - + fit_len = (int)mtu - NET_IPV6_FRAGH_LEN - (net_pkt_ip_hdr_len(pkt) + net_pkt_ipv6_ext_len(pkt)); + + /* The data we want to sent in one fragment must be multiple of 8 */ + fit_len = ROUND_DOWN(fit_len, 8); + if (fit_len <= 0) { /* Must be invalid extension headers length */ NET_DBG("No room for IPv6 payload MTU %d hdrs_len %d", - NET_IPV6_MTU, NET_IPV6_FRAGH_LEN + + mtu, NET_IPV6_FRAGH_LEN + net_pkt_ip_hdr_len(pkt) + net_pkt_ipv6_ext_len(pkt)); return -EINVAL; } diff --git a/subsys/net/ip/ipv6_nbr.c b/subsys/net/ip/ipv6_nbr.c index 86e6b1f2173b1..fa78f4221dcca 100644 --- a/subsys/net/ip/ipv6_nbr.c +++ b/subsys/net/ip/ipv6_nbr.c @@ -35,6 +35,7 @@ LOG_MODULE_REGISTER(net_ipv6_nd, CONFIG_NET_IPV6_ND_LOG_LEVEL); #include "6lo.h" #include "route.h" #include "net_stats.h" +#include "pmtu.h" /* Timeout value to be used when allocating net buffer during various * neighbor discovery procedures. @@ -809,13 +810,31 @@ enum net_verdict net_ipv6_prepare_for_send(struct net_pkt *pkt) * contain a proper value and we can skip other checks. */ if (net_pkt_ipv6_fragment_id(pkt) == 0U) { - uint16_t mtu = net_if_get_mtu(net_pkt_iface(pkt)); size_t pkt_len = net_pkt_get_len(pkt); + uint16_t mtu; + + if (IS_ENABLED(CONFIG_NET_IPV6_PMTU)) { + struct sockaddr_in6 dst = { + .sin6_family = AF_INET6, + }; + + net_ipv6_addr_copy_raw((uint8_t *)&dst.sin6_addr, ip_hdr->dst); + + ret = net_pmtu_get_mtu((struct sockaddr *)&dst); + if (ret <= 0) { + goto use_interface_mtu; + } + + mtu = ret; + } else { +use_interface_mtu: + mtu = net_if_get_mtu(net_pkt_iface(pkt)); + mtu = MAX(NET_IPV6_MTU, mtu); + } - mtu = MAX(NET_IPV6_MTU, mtu); if (mtu < pkt_len) { ret = net_ipv6_send_fragmented_pkt(net_pkt_iface(pkt), - pkt, pkt_len); + pkt, pkt_len, mtu); if (ret < 0) { NET_DBG("Cannot fragment IPv6 pkt (%d)", ret); return NET_DROP; @@ -903,6 +922,26 @@ enum net_verdict net_ipv6_prepare_for_send(struct net_pkt *pkt) } try_send: + if (IS_ENABLED(CONFIG_NET_IPV6_PMTU)) { + struct net_pmtu_entry *entry; + struct sockaddr_in6 dst = { + .sin6_family = AF_INET6, + }; + + net_ipaddr_copy(&dst.sin6_addr, (struct in6_addr *)ip_hdr->dst); + + entry = net_pmtu_get_entry((struct sockaddr *)&dst); + if (entry == NULL) { + ret = net_pmtu_update_mtu((struct sockaddr *)&dst, + net_if_get_mtu(iface)); + if (ret < 0) { + NET_DBG("Cannot update PMTU for %s (%d)", + net_sprint_ipv6_addr(&dst.sin6_addr), + ret); + } + } + } + net_ipv6_nbr_lock(); nbr = nbr_lookup(&net_neighbor.table, iface, nexthop); @@ -2716,6 +2755,98 @@ static int handle_ra_input(struct net_icmp_ctx *ctx, } #endif /* CONFIG_NET_IPV6_ND */ +#if defined(CONFIG_NET_IPV6_PMTU) +/* Packet format described in RFC 4443 ch 3.2. Packet Too Big Message */ +static int handle_ptb_input(struct net_icmp_ctx *ctx, + struct net_pkt *pkt, + struct net_icmp_ip_hdr *hdr, + struct net_icmp_hdr *icmp_hdr, + void *user_data) +{ + NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ptb_access, struct net_icmpv6_ptb); + struct net_ipv6_hdr *ip_hdr = hdr->ipv6; + uint16_t length = net_pkt_get_len(pkt); + struct net_icmpv6_ptb *ptb_hdr; + struct sockaddr_in6 sockaddr_src = { + .sin6_family = AF_INET6, + }; + struct net_pmtu_entry *entry; + uint32_t mtu; + int ret; + + ARG_UNUSED(user_data); + + ptb_hdr = (struct net_icmpv6_ptb *)net_pkt_get_data(pkt, &ptb_access); + if (!ptb_hdr) { + NET_DBG("DROP: NULL PTB header"); + goto drop; + } + + dbg_addr_recv("Packet Too Big", &ip_hdr->src, &ip_hdr->dst, pkt); + + net_stats_update_ipv6_pmtu_recv(net_pkt_iface(pkt)); + + if (length < (sizeof(struct net_ipv6_hdr) + + sizeof(struct net_icmp_hdr) + + sizeof(struct net_icmpv6_ptb))) { + NET_DBG("DROP: length %d too big %zd", + length, sizeof(struct net_ipv6_hdr) + + sizeof(struct net_icmp_hdr) + + sizeof(struct net_icmpv6_ptb)); + goto drop; + } + + net_pkt_acknowledge_data(pkt, &ptb_access); + + mtu = ntohl(ptb_hdr->mtu); + + if (mtu < MIN_IPV6_MTU || mtu > MAX_IPV6_MTU) { + NET_DBG("DROP: Unsupported MTU %u, min is %u, max is %u", + mtu, MIN_IPV6_MTU, MAX_IPV6_MTU); + goto drop; + } + + net_ipaddr_copy(&sockaddr_src.sin6_addr, (struct in6_addr *)&ip_hdr->src); + + entry = net_pmtu_get_entry((struct sockaddr *)&sockaddr_src); + if (entry == NULL) { + NET_DBG("DROP: Cannot find PMTU entry for %s", + net_sprint_ipv6_addr(&ip_hdr->src)); + goto silent_drop; + } + + /* We must not accept larger PMTU value than what we already know. + * RFC 8201 chapter 4 page 8. + */ + if (entry->mtu > 0 && entry->mtu < mtu) { + NET_DBG("DROP: PMTU for %s %u larger than %u", + net_sprint_ipv6_addr(&ip_hdr->src), mtu, + entry->mtu); + goto silent_drop; + } + + ret = net_pmtu_update_entry(entry, mtu); + if (ret > 0) { + NET_DBG("PMTU for %s changed from %u to %u", + net_sprint_ipv6_addr(&ip_hdr->src), ret, mtu); + } + + return 0; +drop: + net_stats_update_ipv6_pmtu_drop(net_pkt_iface(pkt)); + + return -EIO; + +silent_drop: + /* If the event is not really an error then just ignore it and + * return 0 so that icmpv6 module will not complain about it. + */ + net_stats_update_ipv6_pmtu_drop(net_pkt_iface(pkt)); + + return 0; +} +#endif /* CONFIG_NET_IPV6_PMTU */ + #if defined(CONFIG_NET_IPV6_NBR_CACHE) static struct net_icmp_ctx ns_ctx; static struct net_icmp_ctx na_ctx; @@ -2725,6 +2856,10 @@ static struct net_icmp_ctx na_ctx; static struct net_icmp_ctx ra_ctx; #endif /* CONFIG_NET_IPV6_ND */ +#if defined(CONFIG_NET_IPV6_PMTU) +static struct net_icmp_ctx ptb_ctx; +#endif /* CONFIG_NET_IPV6_PMTU */ + void net_ipv6_nbr_init(void) { int ret; @@ -2755,5 +2890,13 @@ void net_ipv6_nbr_init(void) ipv6_nd_reachable_timeout); #endif +#if defined(CONFIG_NET_IPV6_PMTU) + ret = net_icmp_init_ctx(&ptb_ctx, NET_ICMPV6_PACKET_TOO_BIG, 0, handle_ptb_input); + if (ret < 0) { + NET_ERR("Cannot register %s handler (%d)", STRINGIFY(NET_ICMPV6_PACKET_TOO_BIG), + ret); + } +#endif + ARG_UNUSED(ret); } diff --git a/subsys/net/ip/net_context.c b/subsys/net/ip/net_context.c index e6a13874bdef4..0d304e624eb1c 100644 --- a/subsys/net/ip/net_context.c +++ b/subsys/net/ip/net_context.c @@ -37,6 +37,7 @@ LOG_MODULE_REGISTER(net_ctx, CONFIG_NET_CONTEXT_LOG_LEVEL); #include "udp_internal.h" #include "tcp_internal.h" #include "net_stats.h" +#include "pmtu.h" #if defined(CONFIG_NET_TCP) #include "tcp.h" @@ -1139,6 +1140,22 @@ int net_context_create_ipv4_new(struct net_context *context, } #endif + if (IS_ENABLED(CONFIG_NET_IPV4_PMTU)) { + struct net_pmtu_entry *entry; + struct sockaddr_in dst_addr = { + .sin_family = AF_INET, + .sin_addr = *dst, + }; + + entry = net_pmtu_get_entry((struct sockaddr *)&dst_addr); + if (entry == NULL) { + /* Try to figure out the MTU of the path */ + net_pkt_set_ipv4_pmtu(pkt, true); + } else { + net_pkt_set_ipv4_pmtu(pkt, false); + } + } + return net_ipv4_create(pkt, src, dst); } #endif /* CONFIG_NET_IPV4 */ @@ -1771,6 +1788,48 @@ static int get_context_timestamping(struct net_context *context, #endif } +static int get_context_mtu(struct net_context *context, + void *value, size_t *len) +{ + sa_family_t family = net_context_get_family(context); + struct net_if *iface = NULL; + int mtu; + + if (IS_ENABLED(CONFIG_NET_PMTU)) { + mtu = net_pmtu_get_mtu(&context->remote); + if (mtu > 0) { + goto out; + } + } + + if (net_context_is_bound_to_iface(context)) { + iface = net_context_get_iface(context); + + mtu = net_if_get_mtu(iface); + } else { + if (IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) { + iface = net_if_ipv6_select_src_iface( + &net_sin6(&context->remote)->sin6_addr); + } else if (IS_ENABLED(CONFIG_NET_IPV4) && family == AF_INET) { + iface = net_if_ipv4_select_src_iface( + &net_sin(&context->remote)->sin_addr); + } else { + return -EAFNOSUPPORT; + } + + mtu = net_if_get_mtu(iface); + } + +out: + *((int *)value) = mtu; + + if (len) { + *len = sizeof(int); + } + + return 0; +} + /* If buf is not NULL, then use it. Otherwise read the data to be written * to net_pkt from msghdr. */ @@ -3043,6 +3102,55 @@ static int set_context_reuseport(struct net_context *context, #endif } +static int set_context_ipv6_mtu(struct net_context *context, + const void *value, size_t len) +{ +#if defined(CONFIG_NET_IPV6) + struct net_if *iface; + uint16_t mtu; + + if (len != sizeof(int)) { + return -EINVAL; + } + + mtu = *((int *)value); + + if (IS_ENABLED(CONFIG_NET_IPV6_PMTU)) { + int ret; + + ret = net_pmtu_update_mtu(&context->remote, mtu); + if (ret < 0) { + return ret; + } + + return 0; + } + + if (net_context_is_bound_to_iface(context)) { + iface = net_context_get_iface(context); + } else { + sa_family_t family = net_context_get_family(context); + + if (IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) { + iface = net_if_ipv6_select_src_iface( + &net_sin6(&context->remote)->sin6_addr); + } else { + return -EAFNOSUPPORT; + } + } + + net_if_set_mtu(iface, (uint16_t)mtu); + + return 0; +#else + ARG_UNUSED(context); + ARG_UNUSED(value); + ARG_UNUSED(len); + + return -ENOTSUP; +#endif +} + static int set_context_ipv6_v6only(struct net_context *context, const void *value, size_t len) { @@ -3171,6 +3279,17 @@ int net_context_set_option(struct net_context *context, break; case NET_OPT_TIMESTAMPING: ret = set_context_timestamping(context, value, len); + break; + case NET_OPT_MTU: + /* IPv4 only supports getting the MTU */ + if (IS_ENABLED(CONFIG_NET_IPV4) && + net_context_get_family(context) == AF_INET) { + ret = -EOPNOTSUPP; + } else if (IS_ENABLED(CONFIG_NET_IPV6) && + net_context_get_family(context) == AF_INET6) { + ret = set_context_ipv6_mtu(context, value, len); + } + break; } @@ -3248,6 +3367,9 @@ int net_context_get_option(struct net_context *context, case NET_OPT_TIMESTAMPING: ret = get_context_timestamping(context, value, len); break; + case NET_OPT_MTU: + ret = get_context_mtu(context, value, len); + break; } k_mutex_unlock(&context->lock); diff --git a/subsys/net/ip/net_core.c b/subsys/net/ip/net_core.c index 5259f9c6e6d70..facc4c8ecc7d1 100644 --- a/subsys/net/ip/net_core.c +++ b/subsys/net/ip/net_core.c @@ -40,6 +40,8 @@ LOG_MODULE_REGISTER(net_core, CONFIG_NET_CORE_LOG_LEVEL); #include "net_private.h" #include "shell/net_shell.h" +#include "pmtu.h" + #include "icmpv6.h" #include "ipv6.h" @@ -536,6 +538,7 @@ int net_recv_data(struct net_if *iface, struct net_pkt *pkt) static inline void l3_init(void) { + net_pmtu_init(); net_icmpv4_init(); net_icmpv6_init(); net_ipv4_init(); diff --git a/subsys/net/ip/net_if.c b/subsys/net/ip/net_if.c index f9f7f68b41428..fa4cb4b29ea62 100644 --- a/subsys/net/ip/net_if.c +++ b/subsys/net/ip/net_if.c @@ -520,11 +520,9 @@ enum net_verdict net_if_send_data(struct net_if *iface, struct net_pkt *pkt) verdict = net_ipv6_prepare_for_send(pkt); } -#if defined(CONFIG_NET_IPV4_FRAGMENT) - if (net_pkt_family(pkt) == AF_INET) { + if (IS_ENABLED(CONFIG_NET_IPV4) && net_pkt_family(pkt) == AF_INET) { verdict = net_ipv4_prepare_for_send(pkt); } -#endif done: /* NET_OK in which case packet has checked successfully. In this case diff --git a/subsys/net/ip/net_private.h b/subsys/net/ip/net_private.h index 04c3205f5aece..9e3878c1d8e24 100644 --- a/subsys/net/ip/net_private.h +++ b/subsys/net/ip/net_private.h @@ -245,7 +245,7 @@ int net_ipv4_send_fragmented_pkt(struct net_if *iface, struct net_pkt *pkt, #if defined(CONFIG_NET_IPV6_FRAGMENT) int net_ipv6_send_fragmented_pkt(struct net_if *iface, struct net_pkt *pkt, - uint16_t pkt_len); + uint16_t pkt_len, uint16_t mtu); #endif extern const char *net_verdict2str(enum net_verdict verdict); diff --git a/subsys/net/ip/net_stats.c b/subsys/net/ip/net_stats.c index 4fe68933a061b..69579b9ec6fb5 100644 --- a/subsys/net/ip/net_stats.c +++ b/subsys/net/ip/net_stats.c @@ -91,6 +91,12 @@ static inline void stats(struct net_if *iface) GET_STAT(iface, ipv6_nd.sent), GET_STAT(iface, ipv6_nd.drop)); #endif /* CONFIG_NET_STATISTICS_IPV6_ND */ +#if defined(CONFIG_NET_STATISTICS_IPV6_PMTU) + NET_INFO("IPv6 PMTU recv %d\tsent\t%d\tdrop\t%d", + GET_STAT(iface, ipv6_pmtu.recv), + GET_STAT(iface, ipv6_pmtu.sent), + GET_STAT(iface, ipv6_pmtu.drop)); +#endif /* CONFIG_NET_STATISTICS_IPV6_PMTU */ #if defined(CONFIG_NET_STATISTICS_MLD) NET_INFO("IPv6 MLD recv %d\tsent\t%d\tdrop\t%d", GET_STAT(iface, ipv6_mld.recv), @@ -116,6 +122,13 @@ static inline void stats(struct net_if *iface) GET_STAT(iface, ip_errors.chkerr), GET_STAT(iface, ip_errors.protoerr)); +#if defined(CONFIG_NET_STATISTICS_IPV4_PMTU) + NET_INFO("IPv4 PMTU recv %d\tsent\t%d\tdrop\t%d", + GET_STAT(iface, ipv4_pmtu.recv), + GET_STAT(iface, ipv4_pmtu.sent), + GET_STAT(iface, ipv4_pmtu.drop)); +#endif /* CONFIG_NET_STATISTICS_IPV4_PMTU */ + NET_INFO("ICMP recv %d\tsent\t%d\tdrop\t%d", GET_STAT(iface, icmp.recv), GET_STAT(iface, icmp.sent), @@ -279,6 +292,18 @@ static int net_stats_get(uint32_t mgmt_request, struct net_if *iface, src = GET_STAT_ADDR(iface, ipv6_nd); break; #endif +#if defined(CONFIG_NET_STATISTICS_IPV6_PMTU) + case NET_REQUEST_STATS_CMD_GET_IPV6_PMTU: + len_chk = sizeof(struct net_stats_ipv6_pmtu); + src = GET_STAT_ADDR(iface, ipv6_pmtu); + break; +#endif +#if defined(CONFIG_NET_STATISTICS_IPV4_PMTU) + case NET_REQUEST_STATS_CMD_GET_IPV4_PMTU: + len_chk = sizeof(struct net_stats_ipv4_pmtu); + src = GET_STAT_ADDR(iface, ipv4_pmtu); + break; +#endif #if defined(CONFIG_NET_STATISTICS_ICMP) case NET_REQUEST_STATS_CMD_GET_ICMP: len_chk = sizeof(struct net_stats_icmp); @@ -341,6 +366,16 @@ NET_MGMT_REGISTER_REQUEST_HANDLER(NET_REQUEST_STATS_GET_IPV6_ND, net_stats_get); #endif +#if defined(CONFIG_NET_STATISTICS_IPV6_PMTU) +NET_MGMT_REGISTER_REQUEST_HANDLER(NET_REQUEST_STATS_GET_IPV6_PMTU, + net_stats_get); +#endif + +#if defined(CONFIG_NET_STATISTICS_IPV4_PMTU) +NET_MGMT_REGISTER_REQUEST_HANDLER(NET_REQUEST_STATS_GET_IPV4_PMTU, + net_stats_get); +#endif + #if defined(CONFIG_NET_STATISTICS_ICMP) NET_MGMT_REGISTER_REQUEST_HANDLER(NET_REQUEST_STATS_GET_ICMP, net_stats_get); diff --git a/subsys/net/ip/net_stats.h b/subsys/net/ip/net_stats.h index 431461b497eee..60777d06f1e4f 100644 --- a/subsys/net/ip/net_stats.h +++ b/subsys/net/ip/net_stats.h @@ -113,6 +113,52 @@ static inline void net_stats_update_ipv6_nd_drop(struct net_if *iface) #define net_stats_update_ipv6_nd_drop(iface) #endif /* CONFIG_NET_STATISTICS_IPV6_ND */ +#if defined(CONFIG_NET_STATISTICS_IPV6_PMTU) && defined(CONFIG_NET_NATIVE_IPV6) +/* IPv6 Path MTU Discovery stats */ + +static inline void net_stats_update_ipv6_pmtu_sent(struct net_if *iface) +{ + UPDATE_STAT(iface, stats.ipv6_pmtu.sent++); +} + +static inline void net_stats_update_ipv6_pmtu_recv(struct net_if *iface) +{ + UPDATE_STAT(iface, stats.ipv6_pmtu.recv++); +} + +static inline void net_stats_update_ipv6_pmtu_drop(struct net_if *iface) +{ + UPDATE_STAT(iface, stats.ipv6_pmtu.drop++); +} +#else +#define net_stats_update_ipv6_pmtu_sent(iface) +#define net_stats_update_ipv6_pmtu_recv(iface) +#define net_stats_update_ipv6_pmtu_drop(iface) +#endif /* CONFIG_NET_STATISTICS_IPV6_PMTU */ + +#if defined(CONFIG_NET_STATISTICS_IPV4_PMTU) && defined(CONFIG_NET_NATIVE_IPV4) +/* IPv4 Path MTU Discovery stats */ + +static inline void net_stats_update_ipv4_pmtu_sent(struct net_if *iface) +{ + UPDATE_STAT(iface, stats.ipv4_pmtu.sent++); +} + +static inline void net_stats_update_ipv4_pmtu_recv(struct net_if *iface) +{ + UPDATE_STAT(iface, stats.ipv4_pmtu.recv++); +} + +static inline void net_stats_update_ipv4_pmtu_drop(struct net_if *iface) +{ + UPDATE_STAT(iface, stats.ipv4_pmtu.drop++); +} +#else +#define net_stats_update_ipv4_pmtu_sent(iface) +#define net_stats_update_ipv4_pmtu_recv(iface) +#define net_stats_update_ipv4_pmtu_drop(iface) +#endif /* CONFIG_NET_STATISTICS_IPV4_PMTU */ + #if defined(CONFIG_NET_STATISTICS_IPV4) && defined(CONFIG_NET_NATIVE_IPV4) /* IPv4 stats */ diff --git a/subsys/net/ip/pmtu.c b/subsys/net/ip/pmtu.c new file mode 100644 index 0000000000000..be2c3355a6b40 --- /dev/null +++ b/subsys/net/ip/pmtu.c @@ -0,0 +1,288 @@ +/** @file + * @brief IPv4/6 PMTU related functions + */ + +/* + * Copyright (c) 2024 Nordic Semiconductor + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(net_pmtu, CONFIG_NET_PMTU_LOG_LEVEL); + +#include +#include +#include +#include +#include "pmtu.h" + +#if defined(CONFIG_NET_IPV4_PMTU) +#define NET_IPV4_PMTU_ENTRIES CONFIG_NET_IPV4_PMTU_DESTINATION_CACHE_ENTRIES +#else +#define NET_IPV4_PMTU_ENTRIES 0 +#endif + +#if defined(CONFIG_NET_IPV6_PMTU) +#define NET_IPV6_PMTU_ENTRIES CONFIG_NET_IPV6_PMTU_DESTINATION_CACHE_ENTRIES +#else +#define NET_IPV6_PMTU_ENTRIES 0 +#endif + +#define NET_PMTU_MAX_ENTRIES (NET_IPV4_PMTU_ENTRIES + NET_IPV6_PMTU_ENTRIES) + +static struct net_pmtu_entry pmtu_entries[NET_PMTU_MAX_ENTRIES]; + +static K_MUTEX_DEFINE(lock); + +static struct net_pmtu_entry *get_pmtu_entry(const struct sockaddr *dst) +{ + struct net_pmtu_entry *entry = NULL; + int i; + + k_mutex_lock(&lock, K_FOREVER); + + for (i = 0; i < ARRAY_SIZE(pmtu_entries); i++) { + switch (dst->sa_family) { + case AF_INET: + if (IS_ENABLED(CONFIG_NET_IPV4_PMTU) && + pmtu_entries[i].dst.family == AF_INET && + net_ipv4_addr_cmp(&pmtu_entries[i].dst.in_addr, + &net_sin(dst)->sin_addr)) { + entry = &pmtu_entries[i]; + goto out; + } + break; + + case AF_INET6: + if (IS_ENABLED(CONFIG_NET_IPV6_PMTU) && + pmtu_entries[i].dst.family == AF_INET6 && + net_ipv6_addr_cmp(&pmtu_entries[i].dst.in6_addr, + &net_sin6(dst)->sin6_addr)) { + entry = &pmtu_entries[i]; + goto out; + } + break; + + default: + break; + } + } + +out: + k_mutex_unlock(&lock); + + return entry; +} + +static struct net_pmtu_entry *get_free_pmtu_entry(void) +{ + struct net_pmtu_entry *entry = NULL; + uint32_t oldest = 0U; + int i; + + k_mutex_lock(&lock, K_FOREVER); + + for (i = 0; i < ARRAY_SIZE(pmtu_entries); i++) { + if (!pmtu_entries[i].in_use) { + pmtu_entries[i].in_use = true; + pmtu_entries[i].last_update = k_uptime_get_32(); + + entry = &pmtu_entries[i]; + goto out; + } + + if (oldest == 0U || pmtu_entries[i].last_update < oldest) { + oldest = pmtu_entries[i].last_update; + entry = &pmtu_entries[i]; + } + } + +out: + k_mutex_unlock(&lock); + + return entry; +} + +static void update_pmtu_entry(struct net_pmtu_entry *entry, uint16_t mtu) +{ + bool changed = false; + + if (entry->mtu != mtu) { + changed = true; + entry->mtu = mtu; + } + + entry->last_update = k_uptime_get_32(); + + if (changed) { + struct net_if *iface; + + if (IS_ENABLED(CONFIG_NET_IPV4_PMTU) && entry->dst.family == AF_INET) { + struct net_event_ipv4_pmtu_info info; + + net_ipaddr_copy(&info.dst, &entry->dst.in_addr); + info.mtu = mtu; + + iface = net_if_ipv4_select_src_iface(&info.dst); + + net_mgmt_event_notify_with_info(NET_EVENT_IPV4_PMTU_CHANGED, + iface, + (const void *)&info, + sizeof(struct net_event_ipv4_pmtu_info)); + + } else if (IS_ENABLED(CONFIG_NET_IPV6_PMTU) && entry->dst.family == AF_INET6) { + struct net_event_ipv6_pmtu_info info; + + net_ipaddr_copy(&info.dst, &entry->dst.in6_addr); + info.mtu = mtu; + + iface = net_if_ipv6_select_src_iface(&info.dst); + + net_mgmt_event_notify_with_info(NET_EVENT_IPV6_PMTU_CHANGED, + iface, + (const void *)&info, + sizeof(struct net_event_ipv6_pmtu_info)); + } + } +} + +struct net_pmtu_entry *net_pmtu_get_entry(const struct sockaddr *dst) +{ + struct net_pmtu_entry *entry; + + entry = get_pmtu_entry(dst); + + return entry; +} + +int net_pmtu_get_mtu(const struct sockaddr *dst) +{ + struct net_pmtu_entry *entry; + + entry = get_pmtu_entry(dst); + if (entry == NULL) { + return -ENOENT; + } + + return entry->mtu; +} + +static struct net_pmtu_entry *add_entry(const struct sockaddr *dst, bool *old_entry) +{ + struct net_pmtu_entry *entry; + + entry = get_pmtu_entry(dst); + if (entry != NULL) { + *old_entry = true; + return entry; + } + + entry = get_free_pmtu_entry(); + if (entry == NULL) { + return NULL; + } + + k_mutex_lock(&lock, K_FOREVER); + + switch (dst->sa_family) { + case AF_INET: + if (IS_ENABLED(CONFIG_NET_IPV4_PMTU)) { + entry->dst.family = AF_INET; + net_ipaddr_copy(&entry->dst.in_addr, &net_sin(dst)->sin_addr); + } else { + entry->in_use = false; + goto unlock_fail; + } + break; + + case AF_INET6: + if (IS_ENABLED(CONFIG_NET_IPV6_PMTU)) { + entry->dst.family = AF_INET6; + net_ipaddr_copy(&entry->dst.in6_addr, &net_sin6(dst)->sin6_addr); + } else { + entry->in_use = false; + goto unlock_fail; + } + break; + + default: + entry->in_use = false; + goto unlock_fail; + } + + k_mutex_unlock(&lock); + return entry; + +unlock_fail: + *old_entry = false; + + k_mutex_unlock(&lock); + return NULL; +} + +int net_pmtu_update_mtu(const struct sockaddr *dst, uint16_t mtu) +{ + struct net_pmtu_entry *entry; + uint16_t old_mtu = 0U; + bool updated = false; + + entry = add_entry(dst, &updated); + if (entry == NULL) { + return -ENOMEM; + } + + if (updated) { + old_mtu = entry->mtu; + } + + update_pmtu_entry(entry, mtu); + + return (int)old_mtu; +} + +int net_pmtu_update_entry(struct net_pmtu_entry *entry, uint16_t mtu) +{ + uint16_t old_mtu; + + if (entry == NULL) { + return -EINVAL; + } + + if (entry->mtu == mtu) { + return -EALREADY; + } + + old_mtu = entry->mtu; + + update_pmtu_entry(entry, mtu); + + return (int)old_mtu; +} + +int net_pmtu_foreach(net_pmtu_cb_t cb, void *user_data) +{ + int ret = 0; + + k_mutex_lock(&lock, K_FOREVER); + + ARRAY_FOR_EACH(pmtu_entries, i) { + ret++; + cb(&pmtu_entries[i], user_data); + } + + k_mutex_unlock(&lock); + + return ret; +} + +void net_pmtu_init(void) +{ + k_mutex_lock(&lock, K_FOREVER); + + ARRAY_FOR_EACH(pmtu_entries, i) { + pmtu_entries[i].in_use = false; + } + + k_mutex_unlock(&lock); +} diff --git a/subsys/net/ip/pmtu.h b/subsys/net/ip/pmtu.h new file mode 100644 index 0000000000000..bce20a7762c70 --- /dev/null +++ b/subsys/net/ip/pmtu.h @@ -0,0 +1,144 @@ +/** @file + * @brief IPv4/6 PMTU related functions + */ + +/* + * Copyright (c) 2024 Nordic Semiconductor + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __NET_PMTU_H +#define __NET_PMTU_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** PTMU destination cache entry */ +struct net_pmtu_entry { + /** Destination address */ + struct net_addr dst; + /** Last time the PMTU was updated */ + uint32_t last_update; + /** MTU for this destination address */ + uint16_t mtu; + /** In use flag */ + bool in_use : 1; +}; + +/** Get PMTU entry for the given destination address + * + * @param dst Destination address + * + * @return PMTU entry if found, NULL otherwise + */ +#if defined(CONFIG_NET_PMTU) +struct net_pmtu_entry *net_pmtu_get_entry(const struct sockaddr *dst); +#else +static inline struct net_pmtu_entry *net_pmtu_get_entry(const struct sockaddr *dst) +{ + ARG_UNUSED(dst); + + return NULL; +} +#endif /* CONFIG_NET_PMTU */ + +/** Get MTU value for the given destination address + * + * @param dst Destination address + * + * @return MTU value (> 0) if found, <0 otherwise + */ +#if defined(CONFIG_NET_PMTU) +int net_pmtu_get_mtu(const struct sockaddr *dst); +#else +static inline int net_pmtu_get_mtu(const struct sockaddr *dst) +{ + ARG_UNUSED(dst); + + return -ENOTSUP; +} +#endif /* CONFIG_NET_PMTU */ + +/** Update PMTU value for the given destination address + * + * @param dst Destination address + * @param mtu New MTU value + * + * @return >0 previous MTU, <0 if error + */ +#if defined(CONFIG_NET_PMTU) +int net_pmtu_update_mtu(const struct sockaddr *dst, uint16_t mtu); +#else +static inline int net_pmtu_update_mtu(const struct sockaddr *dst, uint16_t mtu) +{ + ARG_UNUSED(dst); + ARG_UNUSED(mtu); + + return -ENOTSUP; +} +#endif /* CONFIG_NET_PMTU */ + +/** Update PMTU entry for the given destination address + * + * @param entry PMTU entry + * @param mtu New MTU value + * + * @return >0 previous MTU, <0 if error + */ +#if defined(CONFIG_NET_PMTU) +int net_pmtu_update_entry(struct net_pmtu_entry *entry, uint16_t mtu); +#else +static inline int net_pmtu_update_entry(struct net_pmtu_entry *entry, + uint16_t mtu) +{ + ARG_UNUSED(entry); + ARG_UNUSED(mtu); + + return -ENOTSUP; +} +#endif /* CONFIG_NET_PMTU */ + +/** + * @typedef net_pmtu_cb_t + * @brief Callback used when traversing PMTU destination cache. + * + * @param entry PMTU entry + * @param user_data User specified data + */ +typedef void (*net_pmtu_cb_t)(struct net_pmtu_entry *entry, + void *user_data); + +/** Get PMTU destination cache contents + * + * @param cb PMTU callback to be called for each cache entry. + * @param user_data User specific data. + * + * @return >=0 number of entries in the PMTU destination cache, <0 if error + */ +#if defined(CONFIG_NET_PMTU) +int net_pmtu_foreach(net_pmtu_cb_t cb, void *user_data); +#else +static inline int net_pmtu_foreach(net_pmtu_cb_t cb, void *user_data) +{ + ARG_UNUSED(cb); + ARG_UNUSED(user_data); + + return -ENOTSUP; +} +#endif /* CONFIG_NET_PMTU */ + +/** Initialize PMTU module */ +#if defined(CONFIG_NET_PMTU) +void net_pmtu_init(void); +#else +#define net_pmtu_init(...) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __NET_PMTU_H */ diff --git a/subsys/net/ip/tcp.c b/subsys/net/ip/tcp.c index b7dd20d3ffdf8..14da3f0cc349b 100644 --- a/subsys/net/ip/tcp.c +++ b/subsys/net/ip/tcp.c @@ -25,6 +25,7 @@ LOG_MODULE_REGISTER(net_tcp, CONFIG_NET_TCP_LOG_LEVEL); #include "net_stats.h" #include "net_private.h" #include "tcp_internal.h" +#include "pmtu.h" #define ACK_TIMEOUT_MS tcp_max_timeout_ms #define ACK_TIMEOUT K_MSEC(ACK_TIMEOUT_MS) @@ -4392,53 +4393,135 @@ void net_tcp_foreach(net_tcp_cb_t cb, void *user_data) k_mutex_unlock(&tcp_lock); } -uint16_t net_tcp_get_supported_mss(const struct tcp *conn) +static uint16_t get_ipv6_destination_mtu(struct net_if *iface, + const struct in6_addr *dest) { - sa_family_t family = net_context_get_family(conn->context); +#if defined(CONFIG_NET_IPV6_PMTU) + int mtu = net_pmtu_get_mtu((struct sockaddr *)&(struct sockaddr_in6){ + .sin6_family = AF_INET6, + .sin6_addr = *dest }); + + if (mtu < 0) { + if (iface != NULL) { + return net_if_get_mtu(iface); + } - if (family == AF_INET) { -#if defined(CONFIG_NET_IPV4) - struct net_if *iface = net_context_get_iface(conn->context); - int mss = 0; + return NET_IPV6_MTU; + } - if (iface && net_if_get_mtu(iface) >= NET_IPV4TCPH_LEN) { - /* Detect MSS based on interface MTU minus "TCP,IP - * header size" - */ - mss = net_if_get_mtu(iface) - NET_IPV4TCPH_LEN; - } + return (uint16_t)mtu; +#else + if (iface != NULL) { + return net_if_get_mtu(iface); + } - if (mss == 0) { - mss = NET_IPV4_MTU - NET_IPV4TCPH_LEN; + return NET_IPV6_MTU; +#endif /* CONFIG_NET_IPV6_PMTU */ +} + +static uint16_t get_ipv4_destination_mtu(struct net_if *iface, + const struct in_addr *dest) +{ +#if defined(CONFIG_NET_IPV4_PMTU) + int mtu = net_pmtu_get_mtu((struct sockaddr *)&(struct sockaddr_in){ + .sin_family = AF_INET, + .sin_addr = *dest }); + + if (mtu < 0) { + if (iface != NULL) { + return net_if_get_mtu(iface); } - return mss; + return NET_IPV4_MTU; + } + + return (uint16_t)mtu; #else - return 0; -#endif /* CONFIG_NET_IPV4 */ + if (iface != NULL) { + return net_if_get_mtu(iface); } -#if defined(CONFIG_NET_IPV6) - else if (family == AF_INET6) { + + return NET_IPV4_MTU; +#endif /* CONFIG_NET_IPV4_PMTU */ +} + +uint16_t net_tcp_get_supported_mss(const struct tcp *conn) +{ + sa_family_t family = net_context_get_family(conn->context); + + if (IS_ENABLED(CONFIG_NET_IPV4) && family == AF_INET) { struct net_if *iface = net_context_get_iface(conn->context); - int mss = 0; + uint16_t dest_mtu; - if (iface && net_if_get_mtu(iface) >= NET_IPV6TCPH_LEN) { - /* Detect MSS based on interface MTU minus "TCP,IP - * header size" - */ - mss = net_if_get_mtu(iface) - NET_IPV6TCPH_LEN; + dest_mtu = get_ipv4_destination_mtu(iface, &conn->dst.sin.sin_addr); + + /* Detect MSS based on interface MTU minus "TCP,IP header size" */ + return dest_mtu - NET_IPV4TCPH_LEN; + + } else if (IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) { + struct net_if *iface = net_context_get_iface(conn->context); + uint16_t dest_mtu; + + dest_mtu = get_ipv6_destination_mtu(iface, &conn->dst.sin6.sin6_addr); + + /* Detect MSS based on interface MTU minus "TCP,IP header size" */ + return dest_mtu - NET_IPV6TCPH_LEN; + } + + return 0; +} + +#if defined(CONFIG_NET_TEST) +struct testing_user_data { + struct sockaddr remote; + uint16_t mtu; +}; + +static void testing_find_conn(struct tcp *conn, void *user_data) +{ + struct testing_user_data *data = user_data; + + if (IS_ENABLED(CONFIG_NET_IPV6) && data->remote.sa_family == AF_INET6 && + net_ipv6_addr_cmp(&conn->dst.sin6.sin6_addr, + &net_sin6(&data->remote)->sin6_addr)) { + if (data->mtu > 0) { + /* Set it only once */ + return; } - if (mss == 0) { - mss = NET_IPV6_MTU - NET_IPV6TCPH_LEN; + NET_DBG("Found connection %p mtu %u", conn, + net_tcp_get_supported_mss(conn) + NET_IPV6TCPH_LEN); + data->mtu = net_tcp_get_supported_mss(conn) + NET_IPV6TCPH_LEN; + return; + } + + if (IS_ENABLED(CONFIG_NET_IPV4) && data->remote.sa_family == AF_INET && + net_ipv4_addr_cmp(&conn->dst.sin.sin_addr, + &net_sin(&data->remote)->sin_addr)) { + if (data->mtu > 0) { + /* Set it only once */ + return; } - return mss; + NET_DBG("Found connection %p mtu %u", conn, + net_tcp_get_supported_mss(conn) + NET_IPV4TCPH_LEN); + data->mtu = net_tcp_get_supported_mss(conn) + NET_IPV4TCPH_LEN; + return; } -#endif /* CONFIG_NET_IPV6 */ +} - return 0; +uint16_t net_tcp_get_mtu(struct sockaddr *dst) +{ + struct testing_user_data data = { + .remote = *dst, + .mtu = 0, + }; + + net_tcp_foreach(testing_find_conn, &data); + + return data.mtu; } +#endif /* CONFIG_NET_TEST */ int net_tcp_set_option(struct net_context *context, enum tcp_conn_option option, diff --git a/subsys/net/lib/shell/CMakeLists.txt b/subsys/net/lib/shell/CMakeLists.txt index 05c92897403e0..02e9dde5e283c 100644 --- a/subsys/net/lib/shell/CMakeLists.txt +++ b/subsys/net/lib/shell/CMakeLists.txt @@ -24,6 +24,7 @@ zephyr_library_sources(mem.c) zephyr_library_sources_ifdef(CONFIG_NET_SHELL_IPV6_SUPPORTED nbr.c) zephyr_library_sources_ifdef(CONFIG_NET_SHELL_IP_SUPPORTED ping.c) zephyr_library_sources(pkt.c) +zephyr_library_sources_ifdef(CONFIG_NET_SHELL_PMTU_SUPPORTED pmtu.c) zephyr_library_sources_ifdef(CONFIG_NET_SHELL_PPP_SUPPORTED ppp.c) zephyr_library_sources_ifdef(CONFIG_NET_SHELL_POWER_MANAGEMENT_SUPPORTED resume.c) zephyr_library_sources_ifdef(CONFIG_NET_SHELL_ROUTE_SUPPORTED route.c) diff --git a/subsys/net/lib/shell/events.c b/subsys/net/lib/shell/events.c index 687e136e7cb33..699db20ef534b 100644 --- a/subsys/net/lib/shell/events.c +++ b/subsys/net/lib/shell/events.c @@ -20,9 +20,9 @@ LOG_MODULE_DECLARE(net_shell); #define THREAD_PRIORITY K_PRIO_COOP(2) #define MAX_EVENT_INFO_SIZE NET_EVENT_INFO_MAX_SIZE #define MONITOR_L2_MASK (_NET_EVENT_IF_BASE) -#define MONITOR_L3_IPV4_MASK (_NET_EVENT_IPV4_BASE) -#define MONITOR_L3_IPV6_MASK (_NET_EVENT_IPV6_BASE) -#define MONITOR_L4_MASK (_NET_EVENT_L4_BASE) +#define MONITOR_L3_IPV4_MASK (_NET_EVENT_IPV4_BASE | NET_MGMT_COMMAND_MASK) +#define MONITOR_L3_IPV6_MASK (_NET_EVENT_IPV6_BASE | NET_MGMT_COMMAND_MASK) +#define MONITOR_L4_MASK (_NET_EVENT_L4_BASE | NET_MGMT_COMMAND_MASK) static bool net_event_monitoring; static bool net_event_shutting_down; @@ -88,6 +88,11 @@ static char *get_l3_desc(struct event_msg *msg, static const char *desc_unknown = ""; char *info = NULL; +#if defined(CONFIG_NET_PMTU) +#define MAX_PMTU_INFO_STR_LEN sizeof("changed MTU xxxxx for") + static char pmtu_buf[MAX_PMTU_INFO_STR_LEN + 1]; +#endif + *desc = desc_unknown; switch (msg->event) { @@ -267,6 +272,34 @@ static char *get_l3_desc(struct event_msg *msg, info = net_addr_ntop(AF_INET, msg->data, extra_info, extra_info_len); break; + case NET_EVENT_IPV4_PMTU_CHANGED: { +#if defined(CONFIG_NET_IPV4_PMTU) + struct net_event_ipv4_pmtu_info *pmtu_info = + (struct net_event_ipv4_pmtu_info *)msg->data; + + *desc = "IPV4 PMTU"; + *desc2 = pmtu_buf; + snprintk(pmtu_buf, MAX_PMTU_INFO_STR_LEN, + "changed MTU %u for", (uint16_t)pmtu_info->mtu); + info = net_addr_ntop(AF_INET, &pmtu_info->dst, extra_info, + extra_info_len); +#endif + break; + } + case NET_EVENT_IPV6_PMTU_CHANGED: { +#if defined(CONFIG_NET_IPV6_PMTU) + struct net_event_ipv6_pmtu_info *pmtu_info = + (struct net_event_ipv6_pmtu_info *)msg->data; + + *desc = "IPV6 PMTU"; + *desc2 = pmtu_buf; + snprintk(pmtu_buf, MAX_PMTU_INFO_STR_LEN, + "changed MTU %u for", (uint16_t)pmtu_info->mtu); + info = net_addr_ntop(AF_INET6, &pmtu_info->dst, extra_info, + extra_info_len); +#endif + break; + } } return info; diff --git a/subsys/net/lib/shell/ipv4.c b/subsys/net/lib/shell/ipv4.c index d70dc786828be..966783182a657 100644 --- a/subsys/net/lib/shell/ipv4.c +++ b/subsys/net/lib/shell/ipv4.c @@ -68,6 +68,8 @@ static int cmd_net_ipv4(const struct shell *sh, size_t argc, char *argv[]) PR("IPv4 conflict detection support : %s\n", IS_ENABLED(CONFIG_NET_IPV4_ACD) ? "enabled" : "disabled"); + PR("Path MTU Discovery (PMTU) : %s\n", + IS_ENABLED(CONFIG_NET_IPV4_PMTU) ? "enabled" : "disabled"); #endif /* CONFIG_NET_NATIVE_IPV4 */ #if defined(CONFIG_NET_IPV4) diff --git a/subsys/net/lib/shell/ipv6.c b/subsys/net/lib/shell/ipv6.c index eb65185c03ca1..55b100e34c71b 100644 --- a/subsys/net/lib/shell/ipv6.c +++ b/subsys/net/lib/shell/ipv6.c @@ -194,6 +194,10 @@ static int cmd_net_ipv6(const struct shell *sh, size_t argc, char *argv[]) " : %d\n", CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT); #endif /* CONFIG_NET_IPV6_PE */ + + PR("Path MTU Discovery (PMTU) : %s\n", + IS_ENABLED(CONFIG_NET_IPV6_PMTU) ? "enabled" : "disabled"); + #endif /* CONFIG_NET_NATIVE_IPV6 */ #if defined(CONFIG_NET_IPV6) diff --git a/subsys/net/lib/shell/pmtu.c b/subsys/net/lib/shell/pmtu.c new file mode 100644 index 0000000000000..bf8eca403ce84 --- /dev/null +++ b/subsys/net/lib/shell/pmtu.c @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_DECLARE(net_shell); + +#include "net_shell_private.h" + +#include "pmtu.h" + +#if !defined(CONFIG_NET_PMTU) +static void print_pmtu_error(const struct shell *sh) +{ + PR_INFO("Set %s to enable %s support.\n", + "CONFIG_NET_IPV6_PMTU or CONFIG_NET_IPV4_PMTU", "PMTU"); +} +#endif + +#if defined(CONFIG_NET_PMTU) +static void pmtu_cb(struct net_pmtu_entry *entry, void *user_data) +{ + struct net_shell_user_data *data = user_data; + const struct shell *sh = data->sh; + int *count = data->user_data; + +#if defined(CONFIG_NET_IPV4) && defined(CONFIG_NET_IPV6) +/* Use the value of NET_IPV6_ADDR_LEN */ +#define ADDR_STR_LEN 40 +#elif defined(CONFIG_NET_IPV4) +#define ADDR_STR_LEN INET_ADDRSTRLEN +#elif defined(CONFIG_NET_IPV6) +#define ADDR_STR_LEN 40 +#else +#define ADDR_STR_LEN INET_ADDRSTRLEN +#endif + + if (!entry->in_use) { + return; + } + + if (*count == 0) { + PR(" %" STRINGIFY(ADDR_STR_LEN) "s MTU Age (sec)\n", + "Destination Address"); + } + + PR("[%2d] %" STRINGIFY(ADDR_STR_LEN) "s %5d %d\n", *count + 1, + net_sprint_addr(entry->dst.family, (void *)&entry->dst.in_addr), + entry->mtu, + (k_uptime_get_32() - entry->last_update) / 1000U); + + (*count)++; +} +#endif /* CONFIG_NET_PMTU */ + +static int cmd_net_pmtu(const struct shell *sh, size_t argc, char *argv[]) +{ +#if defined(CONFIG_NET_PMTU) + struct net_shell_user_data user_data; + int arg = 1; +#endif + + ARG_UNUSED(argc); + +#if defined(CONFIG_NET_PMTU) + if (!argv[arg]) { + /* PMTU destination cache content */ + int count = 0; + + user_data.sh = sh; + user_data.user_data = &count; + + (void)net_pmtu_foreach(pmtu_cb, &user_data); + + if (count == 0) { + PR("PMTU destination cache is empty.\n"); + } + } +#else + print_pmtu_error(sh); +#endif + + return 0; +} + +static int cmd_net_pmtu_flush(const struct shell *sh, size_t argc, char *argv[]) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + +#if defined(CONFIG_NET_PMTU) + PR("Flushing PMTU destination cache.\n"); + net_pmtu_init(); +#else + print_pmtu_error(sh); +#endif + + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_pmtu, + SHELL_CMD(flush, NULL, + "Remove all entries from PMTU destination cache.", + cmd_net_pmtu_flush), + SHELL_SUBCMD_SET_END +); + +SHELL_SUBCMD_ADD((net), pmtu, &net_cmd_pmtu, + "Show PMTU information.", + cmd_net_pmtu, 1, 0); diff --git a/subsys/net/lib/shell/stats.c b/subsys/net/lib/shell/stats.c index 0896add25f997..3556ec0269a4e 100644 --- a/subsys/net/lib/shell/stats.c +++ b/subsys/net/lib/shell/stats.c @@ -467,6 +467,12 @@ static void net_shell_print_statistics(struct net_if *iface, void *user_data) GET_STAT(iface, ipv6_nd.sent), GET_STAT(iface, ipv6_nd.drop)); #endif /* CONFIG_NET_STATISTICS_IPV6_ND */ +#if defined(CONFIG_NET_STATISTICS_IPV6_PMTU) + PR("IPv6 PMTU recv %d\tsent\t%d\tdrop\t%d\n", + GET_STAT(iface, ipv6_pmtu.recv), + GET_STAT(iface, ipv6_pmtu.sent), + GET_STAT(iface, ipv6_pmtu.drop)); +#endif /* CONFIG_NET_STATISTICS_IPV6_PMTU */ #if defined(CONFIG_NET_STATISTICS_MLD) PR("IPv6 MLD recv %d\tsent\t%d\tdrop\t%d\n", GET_STAT(iface, ipv6_mld.recv), @@ -492,6 +498,13 @@ static void net_shell_print_statistics(struct net_if *iface, void *user_data) GET_STAT(iface, ip_errors.chkerr), GET_STAT(iface, ip_errors.protoerr)); +#if defined(CONFIG_NET_STATISTICS_IPV4_PMTU) + PR("IPv4 PMTU recv %d\tsent\t%d\tdrop\t%d\n", + GET_STAT(iface, ipv4_pmtu.recv), + GET_STAT(iface, ipv4_pmtu.sent), + GET_STAT(iface, ipv4_pmtu.drop)); +#endif /* CONFIG_NET_STATISTICS_IPV4_PMTU */ + #if defined(CONFIG_NET_STATISTICS_ICMP) && defined(CONFIG_NET_NATIVE_IPV4) PR("ICMP recv %d\tsent\t%d\tdrop\t%d\n", GET_STAT(iface, icmp.recv), diff --git a/subsys/net/lib/sockets/sockets_inet.c b/subsys/net/lib/sockets/sockets_inet.c index acb9d470a5ea9..8e5673208aa3e 100644 --- a/subsys/net/lib/sockets/sockets_inet.c +++ b/subsys/net/lib/sockets/sockets_inet.c @@ -1840,12 +1840,38 @@ int zsock_getsockopt_ctx(struct net_context *ctx, int level, int optname, } return 0; + + case IP_MTU: + if (IS_ENABLED(CONFIG_NET_IPV4)) { + ret = net_context_get_option(ctx, NET_OPT_MTU, + optval, optlen); + if (ret < 0) { + errno = -ret; + return -1; + } + + return 0; + } } break; case IPPROTO_IPV6: switch (optname) { + case IPV6_MTU: + if (IS_ENABLED(CONFIG_NET_IPV6)) { + ret = net_context_get_option(ctx, NET_OPT_MTU, + optval, optlen); + if (ret < 0) { + errno = -ret; + return -1; + } + + return 0; + } + + break; + case IPV6_V6ONLY: if (IS_ENABLED(CONFIG_NET_IPV4_MAPPING_TO_IPV6)) { ret = net_context_get_option(ctx, @@ -2410,6 +2436,20 @@ int zsock_setsockopt_ctx(struct net_context *ctx, int level, int optname, case IPPROTO_IPV6: switch (optname) { + case IPV6_MTU: + if (IS_ENABLED(CONFIG_NET_IPV6)) { + ret = net_context_set_option(ctx, NET_OPT_MTU, + optval, optlen); + if (ret < 0) { + errno = -ret; + return -1; + } + + return 0; + } + + break; + case IPV6_V6ONLY: if (IS_ENABLED(CONFIG_NET_IPV4_MAPPING_TO_IPV6)) { ret = net_context_set_option(ctx, diff --git a/tests/net/ipv4_fragment/prj.conf b/tests/net/ipv4_fragment/prj.conf index d8c4d1ff5348e..2cd57fc788160 100644 --- a/tests/net/ipv4_fragment/prj.conf +++ b/tests/net/ipv4_fragment/prj.conf @@ -26,3 +26,5 @@ CONFIG_ZTEST_STACK_SIZE=2048 CONFIG_INIT_STACKS=y CONFIG_NET_STATISTICS=n + +CONFIG_NET_IPV4_FRAGMENT_TIMEOUT=1 diff --git a/tests/net/ipv4_fragment/src/main.c b/tests/net/ipv4_fragment/src/main.c index 9379af361a18c..8e496e8097d1e 100644 --- a/tests/net/ipv4_fragment/src/main.c +++ b/tests/net/ipv4_fragment/src/main.c @@ -31,7 +31,7 @@ LOG_MODULE_REGISTER(net_ipv4_test, CONFIG_NET_IPV4_LOG_LEVEL); #define IPV4_TEST_PACKET_SIZE 2048 /* Wait times for semaphores and buffers */ -#define WAIT_TIME K_SECONDS(2) +#define WAIT_TIME K_MSEC(1100) #define ALLOC_TIMEOUT K_MSEC(500) /* Dummy network addresses, 192.168.8.1 and 192.168.8.2 */ @@ -776,7 +776,7 @@ ZTEST(net_ipv4_fragment, test_fragment_timeout) zassert_equal(packets, 1, "Expected fragment to be present in buffer"); /* Delay briefly and re-check number of pending reassembly packets */ - k_sleep(K_SECONDS(6)); + k_sleep(K_MSEC(1100)); packets = 0; net_ipv4_frag_foreach(reassembly_foreach_cb, &packets); zassert_equal(packets, 0, "Expected fragment to be dropped after timeout"); @@ -790,7 +790,7 @@ ZTEST(net_ipv4_fragment, test_fragment_timeout) zassert_equal(sem_count, 0, "Expected no complete upper-layer packets"); /* Check packet counts are valid */ - k_sleep(K_SECONDS(1)); + k_sleep(K_MSEC(500)); zassert_equal(lower_layer_packet_count, 1, "Expected 1 packet at lower layers"); zassert_equal(upper_layer_packet_count, 0, "Expected no packets at upper layers"); zassert_equal(last_packet_received, 1, "Expected last packet"); @@ -862,7 +862,7 @@ ZTEST(net_ipv4_fragment, test_do_not_fragment) "Expected timeout waiting for packet to be received"); /* Check packet counts are valid */ - k_sleep(K_SECONDS(1)); + k_sleep(K_MSEC(100)); zassert_equal(lower_layer_packet_count, 0, "Expected no packets at lower layers"); zassert_equal(upper_layer_packet_count, 0, "Expected no packets at upper layers"); zassert_equal(last_packet_received, 0, "Did not expect last packet"); diff --git a/tests/net/ipv4_fragment/testcase.yaml b/tests/net/ipv4_fragment/testcase.yaml index 30e18231e4941..34822465bef22 100644 --- a/tests/net/ipv4_fragment/testcase.yaml +++ b/tests/net/ipv4_fragment/testcase.yaml @@ -5,9 +5,15 @@ # common: depends_on: netif + tags: + - net + - ipv4 + - fragment + platform_allow: native_sim tests: net.ipv4.fragment: - tags: - - net - - ipv4 - - fragment + extra_configs: + - CONFIG_NET_IPV4_PMTU=n + net.ipv4.fragment.with_pmtu: + extra_configs: + - CONFIG_NET_IPV4_PMTU=y diff --git a/tests/net/ipv6_fragment/testcase.yaml b/tests/net/ipv6_fragment/testcase.yaml index c5cfd98ada2d5..6095af5f02dd5 100644 --- a/tests/net/ipv6_fragment/testcase.yaml +++ b/tests/net/ipv6_fragment/testcase.yaml @@ -1,8 +1,13 @@ common: depends_on: netif + tags: + - net + - ipv6 + - fragment tests: net.ipv6.fragment: - tags: - - net - - ipv6 - - fragment + extra_configs: + - CONFIG_NET_IPV6_PMTU=n + net.ipv6.fragment.with_pmtu: + extra_configs: + - CONFIG_NET_IPV6_PMTU=y diff --git a/tests/net/pmtu/CMakeLists.txt b/tests/net/pmtu/CMakeLists.txt new file mode 100644 index 0000000000000..44c347478ef73 --- /dev/null +++ b/tests/net/pmtu/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(mld) + +target_include_directories(app PRIVATE ${ZEPHYR_BASE}/subsys/net/ip) +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/net/pmtu/prj.conf b/tests/net/pmtu/prj.conf new file mode 100644 index 0000000000000..1253c5c7c1c85 --- /dev/null +++ b/tests/net/pmtu/prj.conf @@ -0,0 +1,26 @@ +CONFIG_ZTEST=y +CONFIG_MAIN_STACK_SIZE=2048 +CONFIG_NET_TEST=y +CONFIG_NETWORKING=y +CONFIG_NET_UDP=n +CONFIG_NET_TCP=y +CONFIG_NET_IPV6=y +CONFIG_NET_IPV6_PMTU=y +CONFIG_NET_IPV4=y +CONFIG_NET_IPV4_PMTU=y +CONFIG_NET_L2_DUMMY=y +CONFIG_NET_LOOPBACK=y +CONFIG_NET_DRIVERS=y +CONFIG_NET_LOG=y +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_NET_PKT_TX_COUNT=20 +CONFIG_NET_PKT_RX_COUNT=20 +CONFIG_NET_BUF_RX_COUNT=20 +CONFIG_NET_BUF_TX_COUNT=20 +CONFIG_NET_MGMT=y +CONFIG_NET_MGMT_EVENT=y +CONFIG_NET_SOCKETS=y +CONFIG_ZVFS_OPEN_MAX=32 +CONFIG_NET_MAX_CONTEXTS=32 +CONFIG_NET_MAX_CONN=32 diff --git a/tests/net/pmtu/src/main.c b/tests/net/pmtu/src/main.c new file mode 100644 index 0000000000000..54d225a259e25 --- /dev/null +++ b/tests/net/pmtu/src/main.c @@ -0,0 +1,866 @@ +/* main.c - Application main entry point */ + +/* + * Copyright (c) 2024 Nordic Semiconductor + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(net_test, CONFIG_NET_PMTU_LOG_LEVEL); + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../../socket/socket_helpers.h" + +#include "route.h" +#include "icmpv6.h" +#include "icmpv4.h" +#include "ipv6.h" +#include "ipv4.h" +#include "pmtu.h" + +#define NET_LOG_ENABLED 1 +#include "net_private.h" + +#if defined(CONFIG_BOARD_NATIVE_SIM) || defined(CONFIG_BOARD_NATIVE_SIM_NATIVE_64) +#define WAIT_PROPERLY 0 +#else +#define WAIT_PROPERLY 1 +#endif + +#if defined(CONFIG_NET_PMTU_LOG_LEVEL_DBG) +#define DBG(fmt, ...) printk(fmt, ##__VA_ARGS__) +#else +#define DBG(fmt, ...) +#endif + +/* This is a helper function to get the MTU value for the given destination. + * It is implemented in tcp.c file. + */ +extern uint16_t net_tcp_get_mtu(struct sockaddr *dst); + +/* Small sleep between tests makes sure that the PMTU destination + * cache entries are separated from each other. + */ +#define SMALL_SLEEP K_MSEC(5) + +static struct in_addr dest_ipv4_addr1 = { { { 198, 51, 100, 1 } } }; +static struct in_addr dest_ipv4_addr2 = { { { 198, 51, 100, 2 } } }; +static struct in_addr dest_ipv4_addr3 = { { { 198, 51, 100, 3 } } }; +static struct in_addr dest_ipv4_addr4 = { { { 198, 51, 100, 4 } } }; +static struct in_addr dest_ipv4_addr_not_found = { { { 1, 2, 3, 4 } } }; + +static struct in6_addr dest_ipv6_addr1 = { { { 0x20, 0x01, 0x0d, 0xb8, 0x01, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0x1 } } }; +static struct in6_addr dest_ipv6_addr2 = { { { 0x20, 0x01, 0x0d, 0xb8, 0x01, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0x2 } } }; +static struct in6_addr dest_ipv6_addr3 = { { { 0x20, 0x01, 0x0d, 0xb8, 0x01, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0x3 } } }; +static struct in6_addr dest_ipv6_addr4 = { { { 0x20, 0x01, 0x0d, 0xb8, 0x01, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0x4 } } }; +static struct in6_addr dest_ipv6_addr_not_found = { { { 0x20, 0x01, 0x0d, 0xb8, 0xde, + 0xad, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x4 } } }; + +static struct net_if *target_iface; +static char target_iface_name[CONFIG_NET_INTERFACE_NAME_LEN + 1]; + +K_SEM_DEFINE(wait_data, 0, UINT_MAX); + +#define PKT_WAIT_TIME K_MSEC(500) +#define WAIT_TIME 500 +#define WAIT_TIME_LONG MSEC_PER_SEC +#define MY_PORT 1969 +#define PEER_PORT 2024 +#define PEER_IPV6_ADDR "::1" +#define MY_IPV6_ADDR "::1" +#define MY_IPV4_ADDR "127.0.0.1" +#define PEER_IPV4_ADDR "127.0.0.1" + +#define THREAD_SLEEP 50 /* ms */ + +static K_SEM_DEFINE(wait_pmtu_changed, 0, UINT_MAX); +static bool is_pmtu_changed; + +static void ipv6_pmtu_changed(struct net_mgmt_event_callback *cb, + uint32_t mgmt_event, + struct net_if *iface) +{ + ARG_UNUSED(cb); + ARG_UNUSED(iface); + + if (mgmt_event != NET_EVENT_IPV6_PMTU_CHANGED) { + return; + } + + NET_DBG("IPv6 PMTU changed event received"); + + k_sem_give(&wait_pmtu_changed); + is_pmtu_changed = true; + + /* Let the network stack to proceed */ + k_msleep(THREAD_SLEEP); +} + +static void ipv4_pmtu_changed(struct net_mgmt_event_callback *cb, + uint32_t mgmt_event, + struct net_if *iface) +{ + ARG_UNUSED(cb); + ARG_UNUSED(iface); + + if (mgmt_event != NET_EVENT_IPV4_PMTU_CHANGED) { + return; + } + + NET_DBG("IPv4 PMTU changed event received"); + + k_sem_give(&wait_pmtu_changed); + is_pmtu_changed = true; + + /* Let the network stack to proceed */ + k_msleep(THREAD_SLEEP); +} + +static struct mgmt_events { + uint32_t event; + net_mgmt_event_handler_t handler; + struct net_mgmt_event_callback cb; +} mgmt_events[] = { + { .event = NET_EVENT_IPV6_PMTU_CHANGED, .handler = ipv6_pmtu_changed }, + { .event = NET_EVENT_IPV4_PMTU_CHANGED, .handler = ipv4_pmtu_changed }, +}; + +static const char *iface2str(struct net_if *iface) +{ + if (net_if_l2(iface) == &NET_L2_GET_NAME(DUMMY)) { + return "No L2"; + } + + return ""; +} + +static void iface_cb(struct net_if *iface, void *user_data) +{ + static int if_count; + + NET_DBG("Interface %p (%s) [%d]", iface, iface2str(iface), + net_if_get_by_iface(iface)); + + switch (if_count) { + case 0: + target_iface = iface; + (void)net_if_get_name(iface, target_iface_name, + CONFIG_NET_INTERFACE_NAME_LEN); + break; + } + + if_count++; +} + +static void setup_mgmt_events(void) +{ + static bool setup_done; + + if (setup_done) { + return; + } + + setup_done = true; + + ARRAY_FOR_EACH(mgmt_events, i) { + net_mgmt_init_event_callback(&mgmt_events[i].cb, + mgmt_events[i].handler, + mgmt_events[i].event); + + net_mgmt_add_event_callback(&mgmt_events[i].cb); + } +} + +static void *test_setup(void) +{ + net_if_foreach(iface_cb, NULL); + + zassert_not_null(target_iface, "Interface is NULL"); + + return NULL; +} + +ZTEST(net_pmtu_test_suite, test_pmtu_01_ipv4_get_entry) +{ +#if defined(CONFIG_NET_IPV4_PMTU) + struct net_pmtu_entry *entry; + struct sockaddr_in dest_ipv4; + + net_ipaddr_copy(&dest_ipv4.sin_addr, &dest_ipv4_addr1); + dest_ipv4.sin_family = AF_INET; + + entry = net_pmtu_get_entry((struct sockaddr *)&dest_ipv4); + zassert_is_null(entry, "PMTU IPv4 entry is not NULL"); + + k_sleep(SMALL_SLEEP); +#else + ztest_test_skip(); +#endif +} + +ZTEST(net_pmtu_test_suite, test_pmtu_01_ipv6_get_entry) +{ +#if defined(CONFIG_NET_IPV6_PMTU) + struct net_pmtu_entry *entry; + struct sockaddr_in6 dest_ipv6; + + net_ipaddr_copy(&dest_ipv6.sin6_addr, &dest_ipv6_addr1); + dest_ipv6.sin6_family = AF_INET6; + + entry = net_pmtu_get_entry((struct sockaddr *)&dest_ipv6); + zassert_is_null(entry, "PMTU IPv6 entry is not NULL"); + + k_sleep(SMALL_SLEEP); +#else + ztest_test_skip(); +#endif +} + +ZTEST(net_pmtu_test_suite, test_pmtu_02_ipv4_update_entry) +{ +#if defined(CONFIG_NET_IPV4_PMTU) + struct sockaddr_in dest_ipv4; + int ret; + + net_ipaddr_copy(&dest_ipv4.sin_addr, &dest_ipv4_addr1); + dest_ipv4.sin_family = AF_INET; + + ret = net_pmtu_update_mtu((struct sockaddr *)&dest_ipv4, 1300); + zassert_equal(ret, 0, "PMTU IPv4 MTU update failed (%d)", ret); + + k_sleep(SMALL_SLEEP); +#else + ztest_test_skip(); +#endif +} + +ZTEST(net_pmtu_test_suite, test_pmtu_02_ipv6_update_entry) +{ +#if defined(CONFIG_NET_IPV6_PMTU) + struct sockaddr_in6 dest_ipv6; + int ret; + + net_ipaddr_copy(&dest_ipv6.sin6_addr, &dest_ipv6_addr1); + dest_ipv6.sin6_family = AF_INET6; + + ret = net_pmtu_update_mtu((struct sockaddr *)&dest_ipv6, 1600); + zassert_equal(ret, 0, "PMTU IPv6 MTU update failed (%d)", ret); + + k_sleep(SMALL_SLEEP); +#else + ztest_test_skip(); +#endif +} + +ZTEST(net_pmtu_test_suite, test_pmtu_03_ipv4_create_more_entries) +{ +#if defined(CONFIG_NET_IPV4_PMTU) + struct sockaddr_in dest_ipv4; + struct net_pmtu_entry *entry; + uint16_t mtu; + int ret; + + dest_ipv4.sin_family = AF_INET; + + net_ipaddr_copy(&dest_ipv4.sin_addr, &dest_ipv4_addr1); + ret = net_pmtu_update_mtu((struct sockaddr *)&dest_ipv4, 1300); + zassert_equal(ret, 1300, "PMTU IPv4 MTU update failed (%d)", ret); + entry = net_pmtu_get_entry((struct sockaddr *)&dest_ipv4); + zassert_equal(entry->mtu, 1300, "PMTU IPv4 MTU is not correct (%d)", + entry->mtu); + + k_sleep(SMALL_SLEEP); + + net_ipaddr_copy(&dest_ipv4.sin_addr, &dest_ipv4_addr2); + ret = net_pmtu_update_mtu((struct sockaddr *)&dest_ipv4, 1400); + zassert_equal(ret, 0, "PMTU IPv4 MTU update failed (%d)", ret); + mtu = net_pmtu_get_mtu((struct sockaddr *)&dest_ipv4); + zassert_equal(mtu, 1400, "PMTU IPv4 MTU is not correct (%d)", mtu); + + k_sleep(SMALL_SLEEP); + + net_ipaddr_copy(&dest_ipv4.sin_addr, &dest_ipv4_addr3); + ret = net_pmtu_update_mtu((struct sockaddr *)&dest_ipv4, 1500); + zassert_equal(ret, 0, "PMTU IPv4 MTU update failed (%d)", ret); + entry = net_pmtu_get_entry((struct sockaddr *)&dest_ipv4); + zassert_equal(entry->mtu, 1500, "PMTU IPv4 MTU is not correct (%d)", + entry->mtu); + + net_ipaddr_copy(&dest_ipv4.sin_addr, &dest_ipv4_addr_not_found); + ret = net_pmtu_get_mtu((struct sockaddr *)&dest_ipv4); + zassert_equal(ret, -ENOENT, "PMTU IPv4 MTU update succeed (%d)", ret); + entry = net_pmtu_get_entry((struct sockaddr *)&dest_ipv4); + zassert_equal(entry, NULL, "PMTU IPv4 MTU update succeed"); +#else + ztest_test_skip(); +#endif +} + +ZTEST(net_pmtu_test_suite, test_pmtu_03_ipv6_create_more_entries) +{ +#if defined(CONFIG_NET_IPV6_PMTU) + struct sockaddr_in6 dest_ipv6; + struct net_pmtu_entry *entry; + uint16_t mtu; + int ret; + + dest_ipv6.sin6_family = AF_INET6; + + net_ipaddr_copy(&dest_ipv6.sin6_addr, &dest_ipv6_addr1); + ret = net_pmtu_update_mtu((struct sockaddr *)&dest_ipv6, 1600); + zassert_equal(ret, 1600, "PMTU IPv6 MTU update failed (%d)", ret); + entry = net_pmtu_get_entry((struct sockaddr *)&dest_ipv6); + zassert_equal(entry->mtu, 1600, "PMTU IPv6 MTU is not correct (%d)", + entry->mtu); + + k_sleep(SMALL_SLEEP); + + net_ipaddr_copy(&dest_ipv6.sin6_addr, &dest_ipv6_addr2); + ret = net_pmtu_update_mtu((struct sockaddr *)&dest_ipv6, 1700); + zassert_equal(ret, 0, "PMTU IPv6 MTU update failed (%d)", ret); + mtu = net_pmtu_get_mtu((struct sockaddr *)&dest_ipv6); + zassert_equal(mtu, 1700, "PMTU IPv6 MTU is not correct (%d)", mtu); + + k_sleep(SMALL_SLEEP); + + net_ipaddr_copy(&dest_ipv6.sin6_addr, &dest_ipv6_addr3); + ret = net_pmtu_update_mtu((struct sockaddr *)&dest_ipv6, 1800); + zassert_equal(ret, 0, "PMTU IPv6 MTU update failed (%d)", ret); + entry = net_pmtu_get_entry((struct sockaddr *)&dest_ipv6); + zassert_equal(entry->mtu, 1800, "PMTU IPv6 MTU is not correct (%d)", + entry->mtu); + + net_ipaddr_copy(&dest_ipv6.sin6_addr, &dest_ipv6_addr_not_found); + ret = net_pmtu_get_mtu((struct sockaddr *)&dest_ipv6); + zassert_equal(ret, -ENOENT, "PMTU IPv6 MTU update succeed (%d)", ret); + entry = net_pmtu_get_entry((struct sockaddr *)&dest_ipv6); + zassert_equal(entry, NULL, "PMTU IPv6 MTU update succeed"); +#else + ztest_test_skip(); +#endif +} + +ZTEST(net_pmtu_test_suite, test_pmtu_04_ipv4_overflow) +{ +#if defined(CONFIG_NET_IPV4_PMTU) + struct sockaddr_in dest_ipv4; + struct net_pmtu_entry *entry; + int ret; + + dest_ipv4.sin_family = AF_INET; + + /* Create more entries than we have space */ + net_ipaddr_copy(&dest_ipv4.sin_addr, &dest_ipv4_addr4); + ret = net_pmtu_update_mtu((struct sockaddr *)&dest_ipv4, 1450); + zassert_equal(ret, 0, "PMTU IPv4 MTU update failed (%d)", ret); + + entry = net_pmtu_get_entry((struct sockaddr *)&dest_ipv4); + zassert_equal(entry->mtu, 1450, "PMTU IPv4 MTU is not correct (%d)", + entry->mtu); + + k_sleep(SMALL_SLEEP); + + net_ipaddr_copy(&dest_ipv4.sin_addr, &dest_ipv4_addr1); + entry = net_pmtu_get_entry((struct sockaddr *)&dest_ipv4); + zassert_is_null(entry, "PMTU IPv4 MTU found when it should not be"); +#else + ztest_test_skip(); +#endif +} + +ZTEST(net_pmtu_test_suite, test_pmtu_04_ipv6_overflow) +{ +#if defined(CONFIG_NET_IPV6_PMTU) + struct sockaddr_in6 dest_ipv6; + struct net_pmtu_entry *entry; + int ret; + + dest_ipv6.sin6_family = AF_INET6; + + /* Create more entries than we have space */ + net_ipaddr_copy(&dest_ipv6.sin6_addr, &dest_ipv6_addr4); + ret = net_pmtu_update_mtu((struct sockaddr *)&dest_ipv6, 1650); + zassert_equal(ret, 0, "PMTU IPv6 MTU update failed (%d)", ret); + + entry = net_pmtu_get_entry((struct sockaddr *)&dest_ipv6); + zassert_equal(entry->mtu, 1650, "PMTU IPv6 MTU is not correct (%d)", + entry->mtu); + + k_sleep(SMALL_SLEEP); + + /* If we have IPv4 PMTU enabled, then the oldest one is an IPv4 entry. + */ + if (IS_ENABLED(CONFIG_NET_IPV4_PMTU)) { + struct sockaddr_in dest_ipv4; + + dest_ipv4.sin_family = AF_INET; + + net_ipaddr_copy(&dest_ipv4.sin_addr, &dest_ipv4_addr2); + entry = net_pmtu_get_entry((struct sockaddr *)&dest_ipv4); + } else { + net_ipaddr_copy(&dest_ipv6.sin6_addr, &dest_ipv6_addr1); + entry = net_pmtu_get_entry((struct sockaddr *)&dest_ipv6); + } + + zassert_is_null(entry, "PMTU IPv6 MTU found when it should not be"); +#else + ztest_test_skip(); +#endif +} + +static void test_bind(int sock, struct sockaddr *addr, socklen_t addrlen) +{ + int ret; + + ret = zsock_bind(sock, addr, addrlen); + zassert_equal(ret, 0, "bind failed with error %d", errno); +} + +static void test_listen(int sock) +{ + zassert_equal(zsock_listen(sock, 1), + 0, + "listen failed with error %d", errno); +} + +static void test_connect(int sock, struct sockaddr *addr, socklen_t addrlen) +{ + zassert_equal(zsock_connect(sock, addr, addrlen), + 0, + "connect failed with error %d", errno); + + if (IS_ENABLED(CONFIG_NET_TC_THREAD_PREEMPTIVE)) { + /* Let the connection proceed */ + k_msleep(THREAD_SLEEP); + } +} + +static void test_accept(int sock, int *new_sock, struct sockaddr *addr, + socklen_t *addrlen) +{ + zassert_not_null(new_sock, "null newsock"); + + *new_sock = zsock_accept(sock, addr, addrlen); + zassert_true(*new_sock >= 0, "accept failed"); +} + +#if defined(CONFIG_NET_IPV6_PMTU) +static int get_v6_send_recv_sock(int *srv_sock, + struct sockaddr_in6 *my_saddr, + struct sockaddr_in6 *peer_saddr, + uint16_t my_port, + uint16_t peer_port) +{ + struct sockaddr addr; + socklen_t addrlen = sizeof(addr); + int new_sock; + int c_sock; + int s_sock; + + prepare_sock_tcp_v6(PEER_IPV6_ADDR, peer_port, &s_sock, peer_saddr); + test_bind(s_sock, (struct sockaddr *)peer_saddr, sizeof(*peer_saddr)); + test_listen(s_sock); + + prepare_sock_tcp_v6(MY_IPV6_ADDR, my_port, &c_sock, my_saddr); + test_bind(c_sock, (struct sockaddr *)my_saddr, sizeof(*my_saddr)); + test_connect(c_sock, (struct sockaddr *)peer_saddr, sizeof(*peer_saddr)); + + test_accept(s_sock, &new_sock, &addr, &addrlen); + zassert_equal(addrlen, sizeof(struct sockaddr_in6), "wrong addrlen"); + + *srv_sock = new_sock; + + return c_sock; +} + +static int create_icmpv6_ptb(struct net_if *iface, + struct sockaddr_in6 *src, + struct sockaddr_in6 *dst, + uint32_t mtu, + struct net_pkt **pkt) +{ + struct net_icmpv6_ptb ptb_hdr; + struct net_pkt *ptb_pkt; + struct in6_addr *dest6; + struct in6_addr *src6; + int ret; + + ptb_pkt = net_pkt_alloc_with_buffer(iface, sizeof(struct net_ipv6_hdr) + + sizeof(struct net_icmp_hdr) + + sizeof(struct net_icmpv6_ptb), + AF_INET6, IPPROTO_ICMPV6, + PKT_WAIT_TIME); + if (ptb_pkt == NULL) { + NET_DBG("No buffer"); + return -ENOMEM; + } + + dest6 = &dst->sin6_addr; + src6 = &src->sin6_addr; + + ret = net_ipv6_create(ptb_pkt, src6, dest6); + if (ret < 0) { + LOG_ERR("Cannot create IPv6 pkt (%d)", ret); + return ret; + } + + ret = net_icmpv6_create(ptb_pkt, NET_ICMPV6_PACKET_TOO_BIG, 0); + if (ret < 0) { + LOG_ERR("Cannot create ICMPv6 pkt (%d)", ret); + return ret; + } + + ptb_hdr.mtu = htonl(mtu); + + ret = net_pkt_write(ptb_pkt, &ptb_hdr, sizeof(ptb_hdr)); + if (ret < 0) { + LOG_ERR("Cannot write payload (%d)", ret); + return ret; + } + + net_pkt_cursor_init(ptb_pkt); + net_ipv6_finalize(ptb_pkt, IPPROTO_ICMPV6); + + net_pkt_set_iface(ptb_pkt, iface); + + *pkt = ptb_pkt; + + return 0; +} +#endif + +ZTEST(net_pmtu_test_suite, test_pmtu_05_ipv6_tcp) +{ +#if defined(CONFIG_NET_IPV6_PMTU) + struct sockaddr_in6 dest_ipv6; + struct sockaddr_in6 s_saddr = { 0 }; /* peer */ + struct sockaddr_in6 c_saddr = { 0 }; /* this host */ + struct net_pkt *pkt = NULL; + int client_sock, server_sock; + uint16_t mtu; + int ret; + + dest_ipv6.sin6_family = AF_INET6; + + client_sock = get_v6_send_recv_sock(&server_sock, &c_saddr, &s_saddr, + MY_PORT, PEER_PORT); + zassert_true(client_sock >= 0, "Failed to create client socket"); + + /* Set initial MTU for the destination */ + ret = net_pmtu_update_mtu((struct sockaddr *)&c_saddr, 4096); + zassert_true(ret >= 0, "PMTU IPv6 MTU update failed (%d)", ret); + + /* Send an ICMPv6 "Packet too big" message from server to client which + * will update the PMTU entry. + */ + ret = create_icmpv6_ptb(target_iface, &s_saddr, &c_saddr, 2048, &pkt); + zassert_equal(ret, 0, "Failed to create ICMPv6 PTB message"); + + ret = net_send_data(pkt); + zassert_equal(ret, 0, "Failed to send PTB message"); + + /* Check that the PMTU entry has been updated */ + mtu = net_tcp_get_mtu((struct sockaddr *)&s_saddr); + zassert_equal(mtu, 2048, "PMTU IPv6 MTU is not correct (%d)", mtu); + + (void)zsock_close(client_sock); + (void)zsock_close(server_sock); +#else + ztest_test_skip(); +#endif /* CONFIG_NET_IPV6_PMTU */ +} + +#if defined(CONFIG_NET_IPV4_PMTU) +static int get_v4_send_recv_sock(int *srv_sock, + struct sockaddr_in *my_saddr, + struct sockaddr_in *peer_saddr, + uint16_t my_port, + uint16_t peer_port) +{ + struct sockaddr addr; + socklen_t addrlen = sizeof(addr); + int new_sock; + int c_sock; + int s_sock; + + prepare_sock_tcp_v4(PEER_IPV4_ADDR, peer_port, &s_sock, peer_saddr); + test_bind(s_sock, (struct sockaddr *)peer_saddr, sizeof(*peer_saddr)); + test_listen(s_sock); + + prepare_sock_tcp_v4(MY_IPV4_ADDR, my_port, &c_sock, my_saddr); + test_bind(c_sock, (struct sockaddr *)my_saddr, sizeof(*my_saddr)); + test_connect(c_sock, (struct sockaddr *)peer_saddr, sizeof(*peer_saddr)); + + test_accept(s_sock, &new_sock, &addr, &addrlen); + zassert_equal(addrlen, sizeof(struct sockaddr_in), "wrong addrlen"); + + *srv_sock = new_sock; + + return c_sock; +} + +static int create_icmpv4_dest_unreach(struct net_if *iface, + struct sockaddr_in *src, + struct sockaddr_in *dst, + uint32_t mtu, + struct net_pkt **pkt) +{ + struct net_icmpv4_dest_unreach du_hdr; + struct net_pkt *du_pkt; + struct in_addr *dest4; + struct in_addr *src4; + int ret; + + du_pkt = net_pkt_alloc_with_buffer(iface, sizeof(struct net_ipv4_hdr) + + sizeof(struct net_icmp_hdr) + + sizeof(struct net_icmpv4_dest_unreach), + AF_INET, IPPROTO_ICMP, + PKT_WAIT_TIME); + if (du_pkt == NULL) { + NET_DBG("No buffer"); + return -ENOMEM; + } + + dest4 = &dst->sin_addr; + src4 = &src->sin_addr; + + ret = net_ipv4_create(du_pkt, src4, dest4); + if (ret < 0) { + LOG_ERR("Cannot create IPv4 pkt (%d)", ret); + return ret; + } + + ret = net_icmpv4_create(du_pkt, NET_ICMPV4_DST_UNREACH, 0); + if (ret < 0) { + LOG_ERR("Cannot create ICMPv4 pkt (%d)", ret); + return ret; + } + + du_hdr.mtu = htons(mtu); + + ret = net_pkt_write(du_pkt, &du_hdr, sizeof(du_hdr)); + if (ret < 0) { + LOG_ERR("Cannot write payload (%d)", ret); + return ret; + } + + net_pkt_cursor_init(du_pkt); + net_ipv4_finalize(du_pkt, IPPROTO_ICMP); + + net_pkt_set_iface(du_pkt, iface); + + *pkt = du_pkt; + + return 0; +} +#endif + +ZTEST(net_pmtu_test_suite, test_pmtu_05_ipv4_tcp) +{ +#if defined(CONFIG_NET_IPV4_PMTU) + struct sockaddr_in dest_ipv4; + struct sockaddr_in s_saddr = { 0 }; /* peer */ + struct sockaddr_in c_saddr = { 0 }; /* this host */ + struct net_pkt *pkt = NULL; + int client_sock, server_sock; + uint16_t mtu; + int ret; + + dest_ipv4.sin_family = AF_INET; + + client_sock = get_v4_send_recv_sock(&server_sock, &c_saddr, &s_saddr, + MY_PORT, PEER_PORT); + zassert_true(client_sock >= 0, "Failed to create client socket"); + + /* Set initial MTU for the destination */ + ret = net_pmtu_update_mtu((struct sockaddr *)&c_saddr, 4096); + zassert_true(ret >= 0, "PMTU IPv6 MTU update failed (%d)", ret); + + /* Send an ICMPv4 "Destination Unreachable" message from server to client which + * will update the PMTU entry. + */ + ret = create_icmpv4_dest_unreach(target_iface, &s_saddr, &c_saddr, 2048, &pkt); + zassert_equal(ret, 0, "Failed to create ICMPv4 Destination Unrechable message"); + + ret = net_send_data(pkt); + zassert_equal(ret, 0, "Failed to send Destination Unreachable message"); + + /* Check that the PMTU entry has been updated */ + mtu = net_tcp_get_mtu((struct sockaddr *)&s_saddr); + zassert_equal(mtu, 2048, "PMTU IPv4 MTU is not correct (%d)", mtu); + + (void)zsock_close(client_sock); + (void)zsock_close(server_sock); +#else + ztest_test_skip(); +#endif /* CONFIG_NET_IPV4_PMTU */ +} + +ZTEST(net_pmtu_test_suite, test_pmtu_06_ipv4_event) +{ +#if defined(CONFIG_NET_IPV4_PMTU) && WAIT_PROPERLY + struct sockaddr_in dest_ipv4; + int ret; + + setup_mgmt_events(); + + is_pmtu_changed = false; + + net_ipaddr_copy(&dest_ipv4.sin_addr, &dest_ipv4_addr1); + dest_ipv4.sin_family = AF_INET; + + ret = net_pmtu_update_mtu((struct sockaddr *)&dest_ipv4, 1200); + zassert_equal(ret, 0, "PMTU IPv4 MTU update failed (%d)", ret); + + if (k_sem_take(&wait_pmtu_changed, K_MSEC(WAIT_TIME))) { + zassert_true(0, "Timeout while waiting pmtu changed event"); + } + + zassert_true(is_pmtu_changed, "Did not catch pmtu changed event"); + + is_pmtu_changed = false; +#else + ztest_test_skip(); +#endif /* CONFIG_NET_IPV4_PMTU */ +} + +ZTEST(net_pmtu_test_suite, test_pmtu_06_ipv6_event) +{ +#if defined(CONFIG_NET_IPV6_PMTU) && WAIT_PROPERLY + struct sockaddr_in6 dest_ipv6; + int ret; + + setup_mgmt_events(); + + is_pmtu_changed = false; + + net_ipaddr_copy(&dest_ipv6.sin6_addr, &dest_ipv6_addr1); + dest_ipv6.sin6_family = AF_INET6; + + ret = net_pmtu_update_mtu((struct sockaddr *)&dest_ipv6, 1500); + zassert_equal(ret, 0, "PMTU IPv6 MTU update failed (%d)", ret); + + if (k_sem_take(&wait_pmtu_changed, K_MSEC(WAIT_TIME))) { + zassert_true(0, "Timeout while waiting pmtu changed event"); + } + + zassert_true(is_pmtu_changed, "Did not catch pmtu changed event"); + + is_pmtu_changed = false; +#else + ztest_test_skip(); +#endif /* CONFIG_NET_IPV6_PMTU */ +} + +ZTEST(net_pmtu_test_suite, test_pmtu_07_socket_api_ipv4) +{ +#if defined(CONFIG_NET_IPV4_PMTU) + struct sockaddr_in s_saddr = { 0 }; /* peer */ + struct sockaddr_in c_saddr = { 0 }; /* this host */ + int ret, client_sock, server_sock; + size_t optlen; + int optval; + int err; + + client_sock = get_v4_send_recv_sock(&server_sock, &c_saddr, &s_saddr, + MY_PORT + 1, PEER_PORT + 1); + zassert_true(client_sock >= 0, "Failed to create client socket"); + + /* Set initial MTU for the destination */ + ret = net_pmtu_update_mtu((struct sockaddr *)&c_saddr, 4096); + zassert_true(ret >= 0, "PMTU IPv4 MTU update failed (%d)", ret); + + optval = 0; optlen = sizeof(int); + ret = zsock_getsockopt(client_sock, IPPROTO_IP, IP_MTU, &optval, &optlen); + err = -errno; + zexpect_equal(ret, 0, "setsockopt failed (%d)", err); + zexpect_equal(optlen, sizeof(int), "setsockopt optlen (%d)", optlen); + zexpect_equal(optval, 4096, "setsockopt mtu (%d)", optval); + + optval = 0; optlen = sizeof(int); + ret = zsock_setsockopt(client_sock, IPPROTO_IP, IP_MTU, &optval, optlen); + err = -errno; + zexpect_equal(ret, -1, "setsockopt failed (%d)", err); + zexpect_equal(optlen, sizeof(int), "setsockopt optlen (%d)", optlen); + zexpect_equal(optval, 0, "setsockopt mtu (%d)", optval); + + (void)zsock_close(client_sock); + (void)zsock_close(server_sock); +#else + ztest_test_skip(); +#endif /* CONFIG_NET_IPV4_PMTU */ +} + +ZTEST(net_pmtu_test_suite, test_pmtu_08_socket_api_ipv6) +{ +#if defined(CONFIG_NET_IPV6_PMTU) + struct sockaddr_in6 s_saddr = { 0 }; /* peer */ + struct sockaddr_in6 c_saddr = { 0 }; /* this host */ + int ret, client_sock, server_sock; + size_t optlen; + int optval; + int err; + + client_sock = get_v6_send_recv_sock(&server_sock, &c_saddr, &s_saddr, + MY_PORT + 2, PEER_PORT + 2); + zassert_true(client_sock >= 0, "Failed to create client socket"); + + /* Set initial MTU for the destination */ + ret = net_pmtu_update_mtu((struct sockaddr *)&c_saddr, 2048); + zassert_true(ret >= 0, "PMTU IPv6 MTU update failed (%d)", ret); + + optval = 0; optlen = sizeof(int); + ret = zsock_getsockopt(client_sock, IPPROTO_IPV6, IPV6_MTU, &optval, &optlen); + err = -errno; + zexpect_equal(ret, 0, "getsockopt failed (%d)", err); + zexpect_equal(optlen, sizeof(int), "getsockopt optlen (%d)", optlen); + zexpect_equal(optval, 2048, "getsockopt mtu (%d)", optval); + + optval = 1500; optlen = sizeof(int); + ret = zsock_setsockopt(client_sock, IPPROTO_IPV6, IPV6_MTU, &optval, optlen); + err = -errno; + zexpect_equal(ret, 0, "setsockopt failed (%d)", err); + zexpect_equal(optlen, sizeof(int), "setsockopt optlen (%d)", optlen); + zexpect_equal(optval, 1500, "setsockopt mtu (%d)", optval); + + optval = 0; optlen = sizeof(int); + ret = zsock_getsockopt(client_sock, IPPROTO_IPV6, IPV6_MTU, &optval, &optlen); + err = -errno; + zexpect_equal(ret, 0, "getsockopt failed (%d)", err); + zexpect_equal(optlen, sizeof(int), "getsockopt optlen (%d)", optlen); + zexpect_equal(optval, 1500, "getsockopt mtu (%d)", optval); + + (void)zsock_close(client_sock); + (void)zsock_close(server_sock); +#else + ztest_test_skip(); +#endif /* CONFIG_NET_IPV6_PMTU */ +} + +ZTEST_SUITE(net_pmtu_test_suite, NULL, test_setup, NULL, NULL, NULL); diff --git a/tests/net/pmtu/testcase.yaml b/tests/net/pmtu/testcase.yaml new file mode 100644 index 0000000000000..6b0da0a2dfdbd --- /dev/null +++ b/tests/net/pmtu/testcase.yaml @@ -0,0 +1,17 @@ +common: + tags: + - net + - pmtu + depends_on: netif + platform_allow: qemu_x86 +tests: + net.pmtu.ipv4: + extra_configs: + - CONFIG_NET_IPV4_PMTU=y + net.pmtu.ipv6: + extra_configs: + - CONFIG_NET_IPV6_PMTU=y + net.pmtu.all: + extra_configs: + - CONFIG_NET_IPV4_PMTU=y + - CONFIG_NET_IPV6_PMTU=y