ipsec: Check PMTU before sending a frame.

If an encapsulated frame is going to have DF bit set check its desitnitions'
PMTU and if it won't fit drop it and:

Generate ICMP 3/4 message if the packet was to be forwarded.
Return EMSGSIZE error otherwise.

Obtained from:		Semihalf
Sponsored by:		Stormshield
Differential revision:	https://reviews.freebsd.org/D30993
This commit is contained in:
Kornel Duleba 2021-08-13 09:20:46 +02:00 committed by Wojciech Macek
parent 06afb53bcd
commit 6b66194bcb
3 changed files with 90 additions and 1 deletions

View File

@ -1093,7 +1093,7 @@ ipsec_in_reject(struct secpolicy *sp, struct inpcb *inp, const struct mbuf *m)
* Compute the byte size to be occupied by IPsec header.
* In case it is tunnelled, it includes the size of outer IP header.
*/
static size_t
size_t
ipsec_hdrsiz_internal(struct secpolicy *sp)
{
size_t size;

View File

@ -332,6 +332,7 @@ int ipsec_chkreplay(uint32_t, uint32_t *, struct secasvar *);
int ipsec_updatereplay(uint32_t, struct secasvar *);
int ipsec_updateid(struct secasvar *, crypto_session_t *, crypto_session_t *);
int ipsec_initialized(void);
size_t ipsec_hdrsiz_internal(struct secpolicy *);
void ipsec_setspidx_inpcb(struct inpcb *, struct secpolicyindex *, u_int);
@ -345,6 +346,7 @@ int ipsec4_output(struct mbuf *, struct inpcb *);
int ipsec4_capability(struct mbuf *, u_int);
int ipsec4_common_input_cb(struct mbuf *, struct secasvar *, int, int);
int ipsec4_ctlinput(int, struct sockaddr *, void *);
int ipsec4_check_pmtu(struct mbuf *, struct secpolicy *, int);
int ipsec4_process_packet(struct mbuf *, struct secpolicy *, struct inpcb *);
int ipsec_process_done(struct mbuf *, struct secpolicy *, struct secasvar *,
u_int);

View File

@ -61,6 +61,8 @@
#ifdef INET6
#include <netinet6/ip6_ecn.h>
#endif
#include <netinet/ip_icmp.h>
#include <netinet/tcp_var.h>
#include <netinet/ip6.h>
#ifdef INET6
@ -292,6 +294,83 @@ ipsec4_process_packet(struct mbuf *m, struct secpolicy *sp,
return (ipsec4_perform_request(m, sp, inp, 0));
}
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;
uint32_t idx;
int error;
/* Don't check PMTU if the frame won't have DF bit set. */
if (!V_ip4_ipsec_dfbit)
return (0);
if (V_ip4_ipsec_dfbit == 1)
goto setdf;
/* V_ip4_ipsec_dfbit > 1 - we will copy it from inner header. */
ip = mtod(m, struct ip *);
if (!(ip->ip_off & htons(IP_DF)))
return (0);
setdf:
idx = sp->tcount - 1;
sav = ipsec4_allocsa(m, sp, &idx, &error);
if (sav == NULL) {
key_freesp(&sp);
if (error != EJUSTRETURN)
m_freem(m);
return (error);
}
dst = &sav->sah->saidx.dst;
/* Final header is not ipv4. */
if (dst->sa.sa_family != AF_INET) {
key_freesav(&sav);
return (0);
}
memset(&inc, 0, sizeof(inc));
inc.inc_faddr = satosin(&dst->sa)->sin_addr;
key_freesav(&sav);
pmtu = tcp_hc_getmtu(&inc);
/* No entry in hostcache. */
if (pmtu == 0)
return (0);
hlen = ipsec_hdrsiz_internal(sp);
if (m_length(m, NULL) + hlen > pmtu) {
/*
* If we're forwarding generate ICMP message here,
* so that it contains pmtu and not link mtu.
* Set error to EINPROGRESS, in order for the frame
* to be dropped silently.
*/
if (forwarding) {
if (pmtu > hlen)
icmp_error(m, ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG,
0, pmtu - hlen);
else
m_freem(m);
key_freesp(&sp);
return (EINPROGRESS); /* Pretend that we consumed it. */
} else {
m_freem(m);
key_freesp(&sp);
return (EMSGSIZE);
}
}
return (0);
}
static int
ipsec4_common_output(struct mbuf *m, struct inpcb *inp, int forwarding)
{
@ -349,6 +428,14 @@ ipsec4_common_output(struct mbuf *m, struct inpcb *inp, int forwarding)
#endif
}
/* NB: callee frees mbuf and releases reference to SP */
error = ipsec4_check_pmtu(m, sp, forwarding);
if (error != 0) {
if (error == EJUSTRETURN)
return (0);
return (error);
}
error = ipsec4_process_packet(m, sp, inp);
if (error == EJUSTRETURN) {
/*