From e7e3afcd0102ecf2c58c061bc33a2bc4e12331cf Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Tue, 5 Nov 2024 10:38:26 +0200 Subject: [PATCH] net: ipv6: Add PMTU support Catch "Packet Too Big" ICMPv6 messages and update PMTU for a given destination IPv6 address. Use that PMTU when sending data to the destination. Signed-off-by: Jukka Rissanen --- subsys/net/ip/icmpv6.h | 3 + subsys/net/ip/ipv6_nbr.c | 125 +++++++++++++++++++++++++++++++++++++++ subsys/net/ip/tcp.c | 49 ++++++++++----- 3 files changed, 161 insertions(+), 16 deletions(-) diff --git a/subsys/net/ip/icmpv6.h b/subsys/net/ip/icmpv6.h index 64ee1dfaa7b..2b68ae8bfae 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/ipv6_nbr.c b/subsys/net/ip/ipv6_nbr.c index 8102da13c2f..5ad332c3dec 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. @@ -903,6 +904,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); @@ -2727,6 +2748,98 @@ drop: } #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; @@ -2736,6 +2849,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; @@ -2766,5 +2883,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/tcp.c b/subsys/net/ip/tcp.c index b7dd20d3ffd..094990109c2 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,6 +4393,32 @@ void net_tcp_foreach(net_tcp_cb_t cb, void *user_data) k_mutex_unlock(&tcp_lock); } +static uint16_t get_ipv6_destination_mtu(struct net_if *iface, + const struct in6_addr *dest) +{ +#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); + } + + return NET_IPV6_MTU; + } + + return (uint16_t)mtu; +#else + if (iface != NULL) { + return net_if_get_mtu(iface); + } + + return NET_IPV6_MTU; +#endif /* CONFIG_NET_IPV6_PMTU */ +} + uint16_t net_tcp_get_supported_mss(const struct tcp *conn) { sa_family_t family = net_context_get_family(conn->context); @@ -4416,26 +4443,16 @@ uint16_t net_tcp_get_supported_mss(const struct tcp *conn) #else return 0; #endif /* CONFIG_NET_IPV4 */ - } -#if defined(CONFIG_NET_IPV6) - else if (family == AF_INET6) { + + } else if (IS_ENABLED(CONFIG_NET_IPV6) && family == AF_INET6) { 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_ipv6_destination_mtu(iface, &conn->dst.sin6.sin6_addr); - if (mss == 0) { - mss = NET_IPV6_MTU - NET_IPV6TCPH_LEN; - } - - return mss; + /* Detect MSS based on interface MTU minus "TCP,IP header size" */ + return dest_mtu - NET_IPV6TCPH_LEN; } -#endif /* CONFIG_NET_IPV6 */ return 0; }