diff options
Diffstat (limited to 'freebsd/sys/netinet/ip_gre.c')
-rw-r--r-- | freebsd/sys/netinet/ip_gre.c | 243 |
1 files changed, 187 insertions, 56 deletions
diff --git a/freebsd/sys/netinet/ip_gre.c b/freebsd/sys/netinet/ip_gre.c index 673e23d5..65ab0ab9 100644 --- a/freebsd/sys/netinet/ip_gre.c +++ b/freebsd/sys/netinet/ip_gre.c @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause-NetBSD * * Copyright (c) 1998 The NetBSD Foundation, Inc. - * Copyright (c) 2014 Andrey V. Elsukov <ae@FreeBSD.org> + * Copyright (c) 2014, 2018 Andrey V. Elsukov <ae@FreeBSD.org> * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation @@ -43,18 +43,17 @@ __FBSDID("$FreeBSD$"); #include <rtems/bsd/local/opt_inet6.h> #include <sys/param.h> +#include <sys/jail.h> #include <sys/systm.h> -#include <sys/mbuf.h> #include <sys/socket.h> -#include <sys/socketvar.h> -#include <sys/protosw.h> +#include <sys/sockio.h> +#include <sys/mbuf.h> #include <sys/errno.h> -#include <sys/time.h> #include <sys/kernel.h> -#include <sys/lock.h> -#include <sys/rmlock.h> #include <sys/sysctl.h> -#include <net/ethernet.h> +#include <sys/malloc.h> +#include <sys/proc.h> + #include <net/if.h> #include <net/if_var.h> #include <net/vnet.h> @@ -71,61 +70,177 @@ __FBSDID("$FreeBSD$"); #include <net/if_gre.h> -extern struct domain inetdomain; -static const struct protosw in_gre_protosw = { - .pr_type = SOCK_RAW, - .pr_domain = &inetdomain, - .pr_protocol = IPPROTO_GRE, - .pr_flags = PR_ATOMIC|PR_ADDR, - .pr_input = gre_input, - .pr_output = rip_output, - .pr_ctlinput = rip_ctlinput, - .pr_ctloutput = rip_ctloutput, - .pr_usrreqs = &rip_usrreqs -}; - #define GRE_TTL 30 VNET_DEFINE(int, ip_gre_ttl) = GRE_TTL; #define V_ip_gre_ttl VNET(ip_gre_ttl) SYSCTL_INT(_net_inet_ip, OID_AUTO, grettl, CTLFLAG_VNET | CTLFLAG_RW, - &VNET_NAME(ip_gre_ttl), 0, ""); + &VNET_NAME(ip_gre_ttl), 0, "Default TTL value for encapsulated packets"); + +VNET_DEFINE_STATIC(struct gre_list *, ipv4_hashtbl) = NULL; +#define V_ipv4_hashtbl VNET(ipv4_hashtbl) +#define GRE_HASH(src, dst) (V_ipv4_hashtbl[\ + in_gre_hashval((src), (dst)) & (GRE_HASH_SIZE - 1)]) +#define GRE_HASH_SC(sc) GRE_HASH((sc)->gre_oip.ip_src.s_addr,\ + (sc)->gre_oip.ip_dst.s_addr) + +static uint32_t +in_gre_hashval(in_addr_t src, in_addr_t dst) +{ + uint32_t ret; + + ret = fnv_32_buf(&src, sizeof(src), FNV1_32_INIT); + return (fnv_32_buf(&dst, sizeof(dst), ret)); +} + +static int +in_gre_checkdup(const struct gre_softc *sc, in_addr_t src, in_addr_t dst) +{ + struct gre_softc *tmp; + + if (sc->gre_family == AF_INET && + sc->gre_oip.ip_src.s_addr == src && + sc->gre_oip.ip_dst.s_addr == dst) + return (EEXIST); + + CK_LIST_FOREACH(tmp, &GRE_HASH(src, dst), chain) { + if (tmp == sc) + continue; + if (tmp->gre_oip.ip_src.s_addr == src && + tmp->gre_oip.ip_dst.s_addr == dst) + return (EADDRNOTAVAIL); + } + return (0); +} static int -in_gre_encapcheck(const struct mbuf *m, int off, int proto, void *arg) +in_gre_lookup(const struct mbuf *m, int off, int proto, void **arg) { - GRE_RLOCK_TRACKER; + const struct ip *ip; struct gre_softc *sc; - struct ip *ip; - sc = (struct gre_softc *)arg; - if ((GRE2IFP(sc)->if_flags & IFF_UP) == 0) + if (V_ipv4_hashtbl == NULL) return (0); - M_ASSERTPKTHDR(m); - /* - * We expect that payload contains at least IPv4 - * or IPv6 packet. - */ - if (m->m_pkthdr.len < sizeof(struct greip) + sizeof(struct ip)) - return (0); + MPASS(in_epoch(net_epoch_preempt)); + ip = mtod(m, const struct ip *); + CK_LIST_FOREACH(sc, &GRE_HASH(ip->ip_dst.s_addr, + ip->ip_src.s_addr), chain) { + /* + * This is an inbound packet, its ip_dst is source address + * in softc. + */ + if (sc->gre_oip.ip_src.s_addr == ip->ip_dst.s_addr && + sc->gre_oip.ip_dst.s_addr == ip->ip_src.s_addr) { + if ((GRE2IFP(sc)->if_flags & IFF_UP) == 0) + return (0); + *arg = sc; + return (ENCAP_DRV_LOOKUP); + } + } + return (0); +} - GRE_RLOCK(sc); - if (sc->gre_family == 0) - goto bad; +static void +in_gre_attach(struct gre_softc *sc) +{ - KASSERT(sc->gre_family == AF_INET, - ("wrong gre_family: %d", sc->gre_family)); + sc->gre_hlen = sizeof(struct greip); + sc->gre_oip.ip_v = IPVERSION; + sc->gre_oip.ip_hl = sizeof(struct ip) >> 2; + sc->gre_oip.ip_p = IPPROTO_GRE; + gre_updatehdr(sc, &sc->gre_gihdr->gi_gre); + CK_LIST_INSERT_HEAD(&GRE_HASH_SC(sc), sc, chain); +} - ip = mtod(m, struct ip *); - if (sc->gre_oip.ip_src.s_addr != ip->ip_dst.s_addr || - sc->gre_oip.ip_dst.s_addr != ip->ip_src.s_addr) - goto bad; +void +in_gre_setopts(struct gre_softc *sc, u_long cmd, uint32_t value) +{ - GRE_RUNLOCK(sc); - return (32 * 2); -bad: - GRE_RUNLOCK(sc); - return (0); + MPASS(cmd == GRESKEY || cmd == GRESOPTS); + + /* NOTE: we are protected with gre_ioctl_sx lock */ + MPASS(sc->gre_family == AF_INET); + CK_LIST_REMOVE(sc, chain); + GRE_WAIT(); + if (cmd == GRESKEY) + sc->gre_key = value; + else + sc->gre_options = value; + in_gre_attach(sc); +} + +int +in_gre_ioctl(struct gre_softc *sc, u_long cmd, caddr_t data) +{ + struct ifreq *ifr = (struct ifreq *)data; + struct sockaddr_in *dst, *src; + struct ip *ip; + int error; + + /* NOTE: we are protected with gre_ioctl_sx lock */ + error = EINVAL; + switch (cmd) { + case SIOCSIFPHYADDR: + src = &((struct in_aliasreq *)data)->ifra_addr; + dst = &((struct in_aliasreq *)data)->ifra_dstaddr; + + /* sanity checks */ + if (src->sin_family != dst->sin_family || + src->sin_family != AF_INET || + src->sin_len != dst->sin_len || + src->sin_len != sizeof(*src)) + break; + if (src->sin_addr.s_addr == INADDR_ANY || + dst->sin_addr.s_addr == INADDR_ANY) { + error = EADDRNOTAVAIL; + break; + } + if (V_ipv4_hashtbl == NULL) + V_ipv4_hashtbl = gre_hashinit(); + error = in_gre_checkdup(sc, src->sin_addr.s_addr, + dst->sin_addr.s_addr); + if (error == EADDRNOTAVAIL) + break; + if (error == EEXIST) { + /* Addresses are the same. Just return. */ + error = 0; + break; + } + ip = malloc(sizeof(struct greip) + 3 * sizeof(uint32_t), + M_GRE, M_WAITOK | M_ZERO); + ip->ip_src.s_addr = src->sin_addr.s_addr; + ip->ip_dst.s_addr = dst->sin_addr.s_addr; + if (sc->gre_family != 0) { + /* Detach existing tunnel first */ + CK_LIST_REMOVE(sc, chain); + GRE_WAIT(); + free(sc->gre_hdr, M_GRE); + /* XXX: should we notify about link state change? */ + } + sc->gre_family = AF_INET; + sc->gre_hdr = ip; + sc->gre_oseq = 0; + sc->gre_iseq = UINT32_MAX; + in_gre_attach(sc); + break; + case SIOCGIFPSRCADDR: + case SIOCGIFPDSTADDR: + if (sc->gre_family != AF_INET) { + error = EADDRNOTAVAIL; + break; + } + src = (struct sockaddr_in *)&ifr->ifr_addr; + memset(src, 0, sizeof(*src)); + src->sin_family = AF_INET; + src->sin_len = sizeof(*src); + src->sin_addr = (cmd == SIOCGIFPSRCADDR) ? + sc->gre_oip.ip_src: sc->gre_oip.ip_dst; + error = prison_if(curthread->td_ucred, (struct sockaddr *)src); + if (error != 0) + memset(src, 0, sizeof(*src)); + break; + } + return (error); } int @@ -158,14 +273,30 @@ in_gre_output(struct mbuf *m, int af, int hlen) return (ip_output(m, NULL, NULL, IP_FORWARDING, NULL, NULL)); } -int -in_gre_attach(struct gre_softc *sc) +static const struct encaptab *ecookie = NULL; +static const struct encap_config ipv4_encap_cfg = { + .proto = IPPROTO_GRE, + .min_length = sizeof(struct greip) + sizeof(struct ip), + .exact_match = ENCAP_DRV_LOOKUP, + .lookup = in_gre_lookup, + .input = gre_input +}; + +void +in_gre_init(void) { - KASSERT(sc->gre_ecookie == NULL, ("gre_ecookie isn't NULL")); - sc->gre_ecookie = encap_attach_func(AF_INET, IPPROTO_GRE, - in_gre_encapcheck, &in_gre_protosw, sc); - if (sc->gre_ecookie == NULL) - return (EEXIST); - return (0); + if (!IS_DEFAULT_VNET(curvnet)) + return; + ecookie = ip_encap_attach(&ipv4_encap_cfg, NULL, M_WAITOK); +} + +void +in_gre_uninit(void) +{ + + if (IS_DEFAULT_VNET(curvnet)) + ip_encap_detach(ecookie); + if (V_ipv4_hashtbl != NULL) + gre_hashdestroy(V_ipv4_hashtbl); } |