ipsec: Add support for PMTUD for IPv6 tunnels

Discard and send ICMPv6 Packet Too Big to sender when we try to encapsulate
and forward a packet which total length exceeds the PMTU.
Logic is based on the IPv4 implementation.
Common code was moved to a separate function.

Differential revision:	https://reviews.freebsd.org/D31771
Obtained from:		Semihalf
Sponsored by:		Stormshield
This commit is contained in:
Bartlomiej Grzesik 2021-09-24 10:27:21 +02:00 committed by Wojciech Macek
parent b4220bf387
commit 9dfc8606eb
2 changed files with 134 additions and 35 deletions

View File

@ -74,6 +74,7 @@ int ipsec6_output(struct mbuf *, struct inpcb *);
int ipsec6_capability(struct mbuf *, u_int);
int ipsec6_common_input_cb(struct mbuf *, struct secasvar *, int, int);
int ipsec6_ctlinput(int, struct sockaddr *, void *);
int ipsec6_check_pmtu(struct mbuf *, struct secpolicy *, int);
int ipsec6_process_packet(struct mbuf *, struct secpolicy *, struct inpcb *);
int ip6_ipsec_filtertunnel(struct mbuf *);

View File

@ -106,6 +106,7 @@
} while (0)
static int ipsec_encap(struct mbuf **mp, struct secasindex *saidx);
static size_t ipsec_get_pmtu(struct secasvar *sav);
#ifdef INET
static struct secasvar *
@ -297,8 +298,6 @@ ipsec4_process_packet(struct mbuf *m, struct secpolicy *sp,
int
ipsec4_check_pmtu(struct mbuf *m, struct secpolicy *sp, int forwarding)
{
union sockaddr_union *dst;
struct in_conninfo inc;
struct secasvar *sav;
struct ip *ip;
size_t hlen, pmtu;
@ -333,44 +332,15 @@ ipsec4_check_pmtu(struct mbuf *m, struct secpolicy *sp, int forwarding)
return (error);
}
dst = &sav->sah->saidx.dst;
memset(&inc, 0, sizeof(inc));
switch (dst->sa.sa_family) {
case AF_INET:
inc.inc_faddr = satosin(&dst->sa)->sin_addr;
break;
#ifdef INET6
case AF_INET6:
inc.inc6_faddr = satosin6(&dst->sa)->sin6_addr;
inc.inc_flags |= INC_ISIPV6;
break;
#endif
default:
pmtu = ipsec_get_pmtu(sav);
if (pmtu == 0) {
key_freesav(&sav);
return (0);
}
key_freesav(&sav);
pmtu = tcp_hc_getmtu(&inc);
/* No entry in hostcache. Use link MTU instead. */
if (pmtu == 0) {
switch (dst->sa.sa_family) {
case AF_INET:
pmtu = tcp_maxmtu(&inc, NULL);
break;
#ifdef INET6
case AF_INET6:
pmtu = tcp_maxmtu6(&inc, NULL);
break;
#endif
}
if (pmtu == 0)
return (0);
tcp_hc_updatemtu(&inc, pmtu);
}
hlen = ipsec_hdrsiz_internal(sp);
key_freesav(&sav);
if (m_length(m, NULL) + hlen > pmtu) {
/*
* If we're forwarding generate ICMP message here,
@ -720,6 +690,72 @@ ipsec6_process_packet(struct mbuf *m, struct secpolicy *sp,
return (ipsec6_perform_request(m, sp, inp, 0));
}
/*
* IPv6 implementation is based on IPv4 implementation.
*/
int
ipsec6_check_pmtu(struct mbuf *m, struct secpolicy *sp, int forwarding)
{
struct secasvar *sav;
size_t hlen, pmtu;
uint32_t idx;
int error;
/*
* According to RFC8200 L3 fragmentation is supposed to be done only on
* locally generated packets. During L3 forwarding packets that are too
* big are always supposed to be dropped, with an ICMPv6 packet being
* sent back.
*/
if (!forwarding)
return (0);
idx = sp->tcount - 1;
sav = ipsec6_allocsa(m, sp, &idx, &error);
if (sav == NULL) {
key_freesp(&sp);
/*
* No matching SA was found and SADB_ACQUIRE message was generated.
* Since we have matched a SP to this packet drop it silently.
*/
if (error == 0)
error = EINPROGRESS;
if (error != EJUSTRETURN)
m_freem(m);
return (error);
}
pmtu = ipsec_get_pmtu(sav);
if (pmtu == 0) {
key_freesav(&sav);
return (0);
}
hlen = ipsec_hdrsiz_internal(sp);
key_freesav(&sav);
if (m_length(m, NULL) + hlen > pmtu) {
/*
* If we're forwarding generate ICMPv6 message here,
* so that it contains pmtu substracted by header size.
* Set error to EINPROGRESS, in order for the frame
* to be dropped silently.
*/
if (forwarding) {
if (pmtu > hlen)
icmp6_error(m, ICMP6_PACKET_TOO_BIG, 0, pmtu - hlen);
else
m_freem(m);
key_freesp(&sp);
return (EINPROGRESS); /* Pretend that we consumed it. */
}
}
return (0);
}
static int
ipsec6_common_output(struct mbuf *m, struct inpcb *inp, int forwarding)
{
@ -766,6 +802,15 @@ ipsec6_common_output(struct mbuf *m, struct inpcb *inp, int forwarding)
}
#endif
}
error = ipsec6_check_pmtu(m, sp, forwarding);
if (error != 0) {
if (error == EJUSTRETURN)
return (0);
return (error);
}
/* NB: callee frees mbuf and releases reference to SP */
error = ipsec6_process_packet(m, sp, inp);
if (error == EJUSTRETURN) {
@ -1001,6 +1046,59 @@ ipsec_prepend(struct mbuf *m, int len, int how)
return (n);
}
static size_t
ipsec_get_pmtu(struct secasvar *sav)
{
union sockaddr_union *dst;
struct in_conninfo inc;
size_t pmtu;
dst = &sav->sah->saidx.dst;
memset(&inc, 0, sizeof(inc));
switch (dst->sa.sa_family) {
#ifdef INET
case AF_INET:
inc.inc_faddr = satosin(&dst->sa)->sin_addr;
break;
#endif
#ifdef INET6
case AF_INET6:
inc.inc6_faddr = satosin6(&dst->sa)->sin6_addr;
inc.inc_flags |= INC_ISIPV6;
break;
#endif
default:
return (0);
}
pmtu = tcp_hc_getmtu(&inc);
if (pmtu != 0)
return (pmtu);
/* No entry in hostcache. Assume that PMTU is equal to link's MTU */
switch (dst->sa.sa_family) {
#ifdef INET
case AF_INET:
pmtu = tcp_maxmtu(&inc, NULL);
break;
#endif
#ifdef INET6
case AF_INET6:
pmtu = tcp_maxmtu6(&inc, NULL);
break;
#endif
default:
return (0);
}
if (pmtu == 0)
return (0);
tcp_hc_updatemtu(&inc, pmtu);
return (pmtu);
}
static int
ipsec_encap(struct mbuf **mp, struct secasindex *saidx)
{