commit 06e36f6722a48e245c5348f8cbb5994bed7fee48 Author: Andrey V. Elsukov Date: Tue Sep 5 15:13:53 2017 +0300 Add L2 PFIL decapsulation support to if_gif. diff --git a/sys/net/if_gif.c b/sys/net/if_gif.c index 051934eb9c9..203e8886b5b 100644 --- a/sys/net/if_gif.c +++ b/sys/net/if_gif.c @@ -65,6 +65,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include @@ -166,6 +167,171 @@ SYSCTL_INT(_net_link_gif, OID_AUTO, parallel_tunnels, CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(parallel_tunnels), 0, "Allow parallel tunnels?"); +#ifdef INET6 +static VNET_DEFINE(struct ifnet *, pfil_ifp) = NULL; +#define V_pfil_ifp VNET(pfil_ifp) +#define GIF_PFIL_HOOKED 0x0001 + +/* + * PULLUP_TO(len, p, T) makes sure that len + sizeof(T) is contiguous, + * then it sets p to point at the offset "len" in the mbuf. WARNING: the + * pointer might become stale after other pullups (but we never use it + * this way). + */ +#define PULLUP_TO(_len, p, T) PULLUP_LEN(_len, p, sizeof(T)) +#define PULLUP_LEN(_len, p, T) \ +do { \ + int x = (_len) + T; \ + if ((m)->m_pkthdr.len < x) { \ + goto drop; \ + } \ + if ((m)->m_len < x) { \ + *m0 = m = m_pullup(m, x); \ + if (m == NULL) \ + goto pullup_failed; \ + } \ + p = mtodo(m, (_len)); \ +} while (0) + +static int +gif_pfil_hook(void *arg, struct mbuf **m0, struct ifnet *dst, int dir, + struct inpcb *inp) +{ + struct ether_header *eh; + struct gif_softc *sc; + struct ifnet *ifp; + struct mbuf *m; + struct ip6_hdr *ip6; + struct ip *ip; + uint32_t af; + int hlen; + uint8_t proto, ecn; + + ifp = V_pfil_ifp; + if (ifp == NULL || (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + return (0); + + sc = ifp->if_softc; + if (sc->gif_family != AF_INET6) + return (0); + + m = *m0; + eh = mtod(m, struct ether_header *); + if (ntohs(eh->ether_type) != ETHERTYPE_IPV6) + return (0); + + hlen = ETHER_HDR_LEN; + PULLUP_TO(hlen, ip6, struct ip6_hdr); + ecn = (ntohl(ip6->ip6_flow) >> 20) & 0xff; + proto = ip6->ip6_nxt; + hlen += sizeof(*ip6); + if (proto != IPPROTO_IPV4 || + !IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, &sc->gif_ip6hdr->ip6_src)) + return (0); + + PULLUP_TO(hlen, ip, struct ip); + if (ip_ecn_egress((ifp->if_flags & IFF_LINK1) ? ECN_ALLOWED: + ECN_NOCARE, &ecn, &ip->ip_tos) == 0) + goto drop; + + m_adj(m, hlen); /* Strip outer ethernet and IP headers */ + m->m_flags &= ~(M_BCAST | M_MCAST); + m->m_flags |= M_SKIP_FIREWALL; + M_SETFIB(m, sc->gif_fibnum); + + af = AF_INET; + BPF_MTAP2(ifp, &af, sizeof(af), m); + + /* Account input statistics */ + if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); + if_inc_counter(ifp, IFCOUNTER_IBYTES, m->m_pkthdr.len); + if ((ifp->if_flags & IFF_MONITOR) == 0) + netisr_dispatch(NETISR_IP, m); + else + m_freem(m); + /* Mark mbuf as consumed for ether_demux() */ + *m0 = NULL; + return (0); +drop: + m_freem(*m0); + *m0 = NULL; +pullup_failed: + if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); + return (EACCES); +} + +static int +gif_add_hook(void) +{ + struct gif_softc *sc; + struct pfil_head *ph; + int ret; + + if (V_pfil_ifp == NULL || (sc = V_pfil_ifp->if_softc) == NULL) + return (ENXIO); + + if (sc->gif_flags & GIF_PFIL_HOOKED) + return (0); + + ph = pfil_head_get(PFIL_TYPE_AF, AF_LINK); + if (ph == NULL) + return (ENOENT); + + ret = pfil_add_hook(gif_pfil_hook, NULL, PFIL_IN | PFIL_WAITOK, ph); + if (ret == 0) + sc->gif_flags |= GIF_PFIL_HOOKED; + return (ret); +} + +static void +gif_remove_hook(void) +{ + struct gif_softc *sc; + struct pfil_head *ph; + + if (V_pfil_ifp == NULL || (sc = V_pfil_ifp->if_softc) == NULL) + return; + + if ((sc->gif_flags & GIF_PFIL_HOOKED) == 0) + return; + + ph = pfil_head_get(PFIL_TYPE_AF, AF_LINK); + if (ph == NULL) + return; + pfil_remove_hook(gif_pfil_hook, NULL, PFIL_IN | PFIL_WAITOK, ph); + sc->gif_flags &= ~GIF_PFIL_HOOKED; + V_pfil_ifp = NULL; +} + +static int +sysctl_use_pfil(SYSCTL_HANDLER_ARGS) +{ + char tmp[IFNAMSIZ] = "none"; + struct ifnet *ifp; + int error; + + if (V_pfil_ifp != NULL) + memcpy(tmp, V_pfil_ifp->if_xname, sizeof(tmp)); + + error = sysctl_handle_string(oidp, tmp, sizeof(tmp), req); + if (error == 0 && req->newptr != NULL) { + if (strcmp("none", tmp) == 0) + gif_remove_hook(); + else { + ifp = ifunit(tmp); + if (ifp == NULL || ifp->if_alloctype != IFT_GIF) + return (EINVAL); + V_pfil_ifp = ifp; + error = gif_add_hook(); + } + } + return (error); +} +SYSCTL_PROC(_net_link_gif, OID_AUTO, use_pfil, CTLTYPE_STRING | CTLFLAG_VNET | + CTLFLAG_RW, 0, 0, sysctl_use_pfil, "A", + "Interface where PFIL hook will be used for decapsulation"); +#endif /* INET6 */ + static int gif_clone_create(struct if_clone *ifc, int unit, caddr_t params) { @@ -209,6 +375,10 @@ gif_clone_destroy(struct ifnet *ifp) sx_xlock(&gif_ioctl_sx); sc = ifp->if_softc; +#ifdef INET6 + if (sc->gif_flags & GIF_PFIL_HOOKED) + gif_remove_hook(); +#endif gif_delete_tunnel(ifp); GIF_LIST_LOCK(); LIST_REMOVE(sc, gif_list); @@ -1035,6 +1205,28 @@ bad: if (error == 0 && sc->gif_family != 0) { ifp->if_drv_flags |= IFF_DRV_RUNNING; if_link_state_change(ifp, LINK_STATE_UP); +#ifdef INET6 + /* + * Handle the fact that interface can be specified at + * boot time, but it will not exist at this time. If we have + * tunable, try to use it. + */ + if (V_pfil_ifp == NULL && + testenv("net.link.gif.use_pfil") != 0) { + char *name; + + name = kern_getenv("net.link.gif.use_pfil"); + if (name != NULL) { + ifp = ifunit(name); + if (ifp != NULL && + ifp->if_alloctype == IFT_GIF) { + V_pfil_ifp = ifp; + error = gif_add_hook(); + } + freeenv(name); + } + } +#endif } else { ifp->if_drv_flags &= ~IFF_DRV_RUNNING; if_link_state_change(ifp, LINK_STATE_DOWN);