From 3d3101d2b9e4ce45cf9b56a9ba6359a740b34337 Mon Sep 17 00:00:00 2001 From: dlg Date: Tue, 23 Feb 2021 11:40:28 +0000 Subject: [PATCH] make a start on transparent ipsec interception, based on bridge(4). i found the Transparent Network Security Policy Enforcement paper by angelos and jason was useful for understanding the background and why you'd want to do this. the implementation is a little bit different to the bridge one because i've tweaked the order that pf and ipsec processing happens, depending on which direction the packet is going over the bridge. bridge always runs ipsec processing before pf, no matter which direction the packet is going. packets going into veb, pf runs first and then ipsec input processing is allowed to happen. in the outgoing direction ipsec happens first and then pf. pf runs before ipsec in the inbound direction so pf can apply policy to ipsec encapsulated packets before they hit pf. this allows you to apply policy to both the encrypted and unencrypted packets in both directions. the code is disabled for now. this is mostly because i want veb(4) to have a good chance at operating outside the netlock, and i'm pretty sure the ipsec stack isn't ready for that yet. the other reason why it's disabled is getting a test setup is effort, but i want to sleep. --- sys/net/if_veb.c | 288 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 287 insertions(+), 1 deletion(-) diff --git a/sys/net/if_veb.c b/sys/net/if_veb.c index 7214df9d16e..4df59eeca5a 100644 --- a/sys/net/if_veb.c +++ b/sys/net/if_veb.c @@ -1,4 +1,4 @@ -/* $OpenBSD: if_veb.c,v 1.5 2021/02/23 07:29:07 dlg Exp $ */ +/* $OpenBSD: if_veb.c,v 1.6 2021/02/23 11:40:28 dlg Exp $ */ /* * Copyright (c) 2021 David Gwynne @@ -40,8 +40,23 @@ #include #include +#include #include +#ifdef INET6 +#include +#include +#include +#endif + +#if 0 && defined(IPSEC) +/* + * IPsec handling is disabled in veb until getting and using tdbs is mpsafe. + */ +#include +#include +#endif + #include #include @@ -542,6 +557,258 @@ veb_pf(struct ifnet *ifp0, int dir, struct mbuf *m) } #endif /* NPF > 0 */ +#if 0 && defined(IPSEC) +static struct mbuf * +veb_ipsec_proto_in(struct ifnet *ifp0, struct mbuf *m, int iphlen, + /* const */ union sockaddr_union *dst, int poff) +{ + struct tdb *tdb; + uint16_t cpi; + uint32_t spi; + uint8_t proto; + + /* ipsec_common_input checks for 8 bytes of input, so we do too */ + if (m->m_pkthdr.len < iphlen + 2 * sizeof(u_int32_t)) + return (m); /* decline */ + + proto = *(mtod(m, uint8_t *) + poff); + /* i'm not a huge fan of how these headers get picked at */ + switch (proto) { + case IPPROTO_ESP: + m_copydata(m, iphlen, sizeof(spi), &spi); + break; + case IPPROTO_AH: + m_copydata(m, iphlen + sizeof(uint32_t), sizeof(spi), &spi); + break; + case IPPROTO_IPCOMP: + m_copydata(m, iphlen + sizeof(uint16_t), sizeof(cpi), &cpi); + spi = htonl(ntohs(cpi)); + break; + default: + return (m); /* decline */ + } + + tdb = gettdb(m->m_pkthdr.ph_rtableid, spi, dst, proto); + if (tdb != NULL && !ISSET(tdb->tdb_flags, TDBF_INVALID) && + tdb->tdb_xform != NULL) { + if (tdb->tdb_first_use == 0) { + tdb->tdb_first_use = gettime(); + if (ISSET(tdb->tdb_flags, TDBF_FIRSTUSE)) { + timeout_add_sec(&tdb->tdb_first_tmo, + tdb->tdb_exp_first_use); + } + if (ISSET(tdb->tdb_flags, TDBF_SOFT_FIRSTUSE)) { + timeout_add_sec(&tdb->tdb_sfirst_tmo, + tdb->tdb_soft_first_use); + } + } + + (*(tdb->tdb_xform->xf_input))(m, tdb, iphlen, poff); + return (NULL); + } + + return (m); +} + +static struct mbuf * +veb_ipsec_ipv4_in(struct ifnet *ifp0, struct mbuf *m) +{ + union sockaddr_union su = { + .sin.sin_len = sizeof(su.sin), + .sin.sin_family = AF_INET, + }; + struct ip *ip; + int iphlen; + + if (m->m_len < sizeof(*ip)) { + m = m_pullup(m, sizeof(*ip)); + if (m == NULL) + return (NULL); + } + + ip = mtod(m, struct ip *); + iphlen = ip->ip_hl << 2; + if (iphlen < sizeof(*ip)) { + /* this is a weird packet, decline */ + return (m); + } + + su.sin.sin_addr = ip->ip_dst; + + return (veb_ipsec_proto_in(ifp0, m, iphlen, &su, + offsetof(struct ip, ip_p))); +} + +#ifdef INET6 +static struct mbuf * +veb_ipsec_ipv6_in(struct ifnet *ifp0, struct mbuf *m) +{ + union sockaddr_union su = { + .sin6.sin6_len = sizeof(su.sin6), + .sin6.sin6_family = AF_INET6, + }; + struct ip6_hdr *ip6; + + if (m->m_len < sizeof(*ip6)) { + m = m_pullup(m, sizeof(*ip6)); + if (m == NULL) + return (NULL); + } + + ip6 = mtod(m, struct ip6_hdr *); + + su.sin6.sin6_addr = ip6->ip6_dst; + + /* XXX scope? */ + + return (veb_ipsec_proto_in(ifp0, m, sizeof(*ip6), &su, + offsetof(struct ip6_hdr, ip6_nxt))); +} +#endif /* INET6 */ + +static struct mbuf * +veb_ipsec_in(struct ifnet *ifp0, struct mbuf *m) +{ + struct mbuf *(*ipsec_ip_in)(struct ifnet *, struct mbuf *); + struct ether_header *eh, copy; + + if (ifp0->if_enqueue == vport_enqueue) + return (m); + + eh = mtod(m, struct ether_header *); + switch (ntohs(eh->ether_type)) { + case ETHERTYPE_IP: + ipsec_ip_in = veb_ipsec_ipv4_in; + break; +#ifdef INET6 + case ETHERTYPE_IPV6: + ipsec_ip_in = veb_ipsec_ipv6_in; + break; +#endif /* INET6 */ + default: + return (m); + } + + copy = *eh; + m_adj(m, sizeof(*eh)); + + m = (*ipsec_ip_in)(ifp0, m); + if (m == NULL) + return (NULL); + + m = m_prepend(m, sizeof(*eh), M_DONTWAIT); + if (m == NULL) + return (NULL); + + eh = mtod(m, struct ether_header *); + *eh = copy; + + return (m); +} + +static struct mbuf * +veb_ipsec_proto_out(struct mbuf *m, sa_family_t af, int iphlen) +{ + struct tdb *tdb; + int error; +#if NPF > 0 + struct ifnet *encifp; +#endif + + tdb = ipsp_spd_lookup(m, af, iphlen, &error, IPSP_DIRECTION_OUT, + NULL, NULL, 0); + if (tdb == NULL) + return (m); + +#if NPF > 0 + encifp = enc_getif(tdb->tdb_rdomain, tdb->tdb_tap); + if (encifp != NULL) { + if (pf_test(af, PF_OUT, encifp, &m) != PF_PASS) { + m_freem(m); + return (NULL); + } + if (m == NULL) + return (NULL); + } +#endif /* NPF > 0 */ + + /* XXX mtu checks */ + + (void)ipsp_process_packet(m, tdb, af, 0); + return (NULL); +} + +static struct mbuf * +veb_ipsec_ipv4_out(struct mbuf *m) +{ + struct ip *ip; + int iphlen; + + if (m->m_len < sizeof(*ip)) { + m = m_pullup(m, sizeof(*ip)); + if (m == NULL) + return (NULL); + } + + ip = mtod(m, struct ip *); + iphlen = ip->ip_hl << 2; + if (iphlen < sizeof(*ip)) { + /* this is a weird packet, decline */ + return (m); + } + + return (veb_ipsec_proto_out(m, AF_INET, iphlen)); +} + +#ifdef INET6 +static struct mbuf * +veb_ipsec_ipv6_out(struct mbuf *m) +{ + return (veb_ipsec_proto_out(m, AF_INET6, sizeof(struct ip6_hdr))); +} +#endif /* INET6 */ + +static struct mbuf * +veb_ipsec_out(struct ifnet *ifp0, struct mbuf *m) +{ + struct mbuf *(*ipsec_ip_out)(struct mbuf *); + struct ether_header *eh, copy; + + if (ifp0->if_enqueue == vport_enqueue) + return (m); + + eh = mtod(m, struct ether_header *); + switch (ntohs(eh->ether_type)) { + case ETHERTYPE_IP: + ipsec_ip_out = veb_ipsec_ipv4_out; + break; +#ifdef INET6 + case ETHERTYPE_IPV6: + ipsec_ip_out = veb_ipsec_ipv6_out; + break; +#endif /* INET6 */ + default: + return (m); + } + + copy = *eh; + m_adj(m, sizeof(*eh)); + + m = (*ipsec_ip_out)(m); + if (m == NULL) + return (NULL); + + m = m_prepend(m, sizeof(*eh), M_DONTWAIT); + if (m == NULL) + return (NULL); + + eh = mtod(m, struct ether_header *); + *eh = copy; + + return (m); +} +#endif /* IPSEC */ + static void veb_broadcast(struct veb_softc *sc, struct veb_port *rp, struct mbuf *m0) { @@ -561,6 +828,13 @@ veb_broadcast(struct veb_softc *sc, struct veb_port *rp, struct mbuf *m0) return; #endif +#if 0 && defined(IPSEC) + /* same goes for ipsec */ + if (ISSET(ifp->if_flags, IFF_LINK2) && + (m = veb_ipsec_out(ifp, m0)) == NULL) + return; +#endif + counters_pkt(ifp->if_counters, ifc_opackets, ifc_obytes, m0->m_pkthdr.len); @@ -626,6 +900,12 @@ veb_transmit(struct veb_softc *sc, struct veb_port *rp, struct veb_port *tp, ifp0 = tp->p_ifp0; +#if 0 && defined(IPSEC) + if (ISSET(ifp->if_flags, IFF_LINK2) && + (m = veb_ipsec_out(ifp0, m0)) == NULL) + return; +#endif + #if NPF > 0 if (ISSET(ifp->if_flags, IFF_LINK1) && (m = veb_pf(ifp0, PF_OUT, m)) == NULL) @@ -717,6 +997,12 @@ veb_port_input(struct ifnet *ifp0, struct mbuf *m, void *brport) return (NULL); #endif +#if 0 && defined(IPSEC) + if (ISSET(ifp->if_flags, IFF_LINK2) && + (m = veb_ipsec_in(ifp0, m)) == NULL) + return (NULL); +#endif + eh = mtod(m, struct ether_header *); if (ISSET(p->p_bif_flags, IFBIF_LEARNING)) { -- 2.20.1