Index: head/sys/net/if_gre.h =================================================================== --- head/sys/net/if_gre.h +++ head/sys/net/if_gre.h @@ -82,6 +82,7 @@ } gre_uhdr; CK_LIST_ENTRY(gre_softc) chain; + CK_LIST_ENTRY(gre_softc) srchash; }; CK_LIST_HEAD(gre_list, gre_softc); MALLOC_DECLARE(M_GRE); @@ -91,7 +92,8 @@ #endif #define GRE2IFP(sc) ((sc)->gre_ifp) -#define GRE_RLOCK() struct epoch_tracker gre_et; epoch_enter_preempt(net_epoch_preempt, &gre_et) +#define GRE_RLOCK_TRACKER struct epoch_tracker gre_et +#define GRE_RLOCK() epoch_enter_preempt(net_epoch_preempt, &gre_et) #define GRE_RUNLOCK() epoch_exit_preempt(net_epoch_preempt, &gre_et) #define GRE_WAIT() epoch_wait_preempt(net_epoch_preempt) Index: head/sys/net/if_gre.c =================================================================== --- head/sys/net/if_gre.c +++ head/sys/net/if_gre.c @@ -326,7 +326,6 @@ cmd == SIOCSIFPHYADDR_IN6 || #endif 0) { - ifp->if_drv_flags |= IFF_DRV_RUNNING; if_link_state_change(ifp, LINK_STATE_UP); } } @@ -342,6 +341,7 @@ sx_assert(&gre_ioctl_sx, SA_XLOCKED); if (sc->gre_family != 0) { CK_LIST_REMOVE(sc, chain); + CK_LIST_REMOVE(sc, srchash); GRE_WAIT(); free(sc->gre_hdr, M_GRE); sc->gre_family = 0; @@ -543,6 +543,7 @@ static int gre_transmit(struct ifnet *ifp, struct mbuf *m) { + GRE_RLOCK_TRACKER; struct gre_softc *sc; struct grehdr *gh; uint32_t af; @@ -562,6 +563,7 @@ sc = ifp->if_softc; if ((ifp->if_flags & IFF_MONITOR) != 0 || (ifp->if_flags & IFF_UP) == 0 || + (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0 || sc->gre_family == 0 || (error = if_tunnel_check_nesting(ifp, m, MTAG_GRE, V_max_gre_nesting)) != 0) { Index: head/sys/netinet/ip_gre.c =================================================================== --- head/sys/netinet/ip_gre.c +++ head/sys/netinet/ip_gre.c @@ -75,9 +75,13 @@ &VNET_NAME(ip_gre_ttl), 0, "Default TTL value for encapsulated packets"); VNET_DEFINE_STATIC(struct gre_list *, ipv4_hashtbl) = NULL; +VNET_DEFINE_STATIC(struct gre_list *, ipv4_srchashtbl) = NULL; #define V_ipv4_hashtbl VNET(ipv4_hashtbl) +#define V_ipv4_srchashtbl VNET(ipv4_srchashtbl) #define GRE_HASH(src, dst) (V_ipv4_hashtbl[\ in_gre_hashval((src), (dst)) & (GRE_HASH_SIZE - 1)]) +#define GRE_SRCHASH(src) (V_ipv4_srchashtbl[\ + fnv_32_buf(&(src), sizeof(src), FNV1_32_INIT) & (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) @@ -138,7 +142,44 @@ return (0); } +/* + * Check that ingress address belongs to local host. + */ static void +in_gre_set_running(struct gre_softc *sc) +{ + + if (in_localip(sc->gre_oip.ip_src)) + GRE2IFP(sc)->if_drv_flags |= IFF_DRV_RUNNING; + else + GRE2IFP(sc)->if_drv_flags &= ~IFF_DRV_RUNNING; +} + +/* + * ifaddr_event handler. + * Clear IFF_DRV_RUNNING flag when ingress address disappears to prevent + * source address spoofing. + */ +static void +in_gre_srcaddr(void *arg __unused, const struct sockaddr *sa, + int event __unused) +{ + const struct sockaddr_in *sin; + struct gre_softc *sc; + + if (V_ipv4_srchashtbl == NULL) + return; + + MPASS(in_epoch(net_epoch_preempt)); + sin = (const struct sockaddr_in *)sa; + CK_LIST_FOREACH(sc, &GRE_SRCHASH(sin->sin_addr.s_addr), srchash) { + if (sc->gre_oip.ip_src.s_addr != sin->sin_addr.s_addr) + continue; + in_gre_set_running(sc); + } +} + +static void in_gre_attach(struct gre_softc *sc) { @@ -148,6 +189,8 @@ 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); + CK_LIST_INSERT_HEAD(&GRE_SRCHASH(sc->gre_oip.ip_src.s_addr), + sc, srchash); } void @@ -159,6 +202,7 @@ /* NOTE: we are protected with gre_ioctl_sx lock */ MPASS(sc->gre_family == AF_INET); CK_LIST_REMOVE(sc, chain); + CK_LIST_REMOVE(sc, srchash); GRE_WAIT(); if (cmd == GRESKEY) sc->gre_key = value; @@ -193,8 +237,10 @@ error = EADDRNOTAVAIL; break; } - if (V_ipv4_hashtbl == NULL) + if (V_ipv4_hashtbl == NULL) { V_ipv4_hashtbl = gre_hashinit(); + V_ipv4_srchashtbl = gre_hashinit(); + } error = in_gre_checkdup(sc, src->sin_addr.s_addr, dst->sin_addr.s_addr); if (error == EADDRNOTAVAIL) @@ -211,6 +257,7 @@ if (sc->gre_family != 0) { /* Detach existing tunnel first */ CK_LIST_REMOVE(sc, chain); + CK_LIST_REMOVE(sc, srchash); GRE_WAIT(); free(sc->gre_hdr, M_GRE); /* XXX: should we notify about link state change? */ @@ -220,6 +267,7 @@ sc->gre_oseq = 0; sc->gre_iseq = UINT32_MAX; in_gre_attach(sc); + in_gre_set_running(sc); break; case SIOCGIFPSRCADDR: case SIOCGIFPDSTADDR: @@ -271,6 +319,7 @@ return (ip_output(m, NULL, NULL, IP_FORWARDING, NULL, NULL)); } +static const struct srcaddrtab *ipv4_srcaddrtab = NULL; static const struct encaptab *ecookie = NULL; static const struct encap_config ipv4_encap_cfg = { .proto = IPPROTO_GRE, @@ -286,6 +335,8 @@ if (!IS_DEFAULT_VNET(curvnet)) return; + ipv4_srcaddrtab = ip_encap_register_srcaddr(in_gre_srcaddr, + NULL, M_WAITOK); ecookie = ip_encap_attach(&ipv4_encap_cfg, NULL, M_WAITOK); } @@ -293,8 +344,12 @@ in_gre_uninit(void) { - if (IS_DEFAULT_VNET(curvnet)) + if (IS_DEFAULT_VNET(curvnet)) { ip_encap_detach(ecookie); - if (V_ipv4_hashtbl != NULL) + ip_encap_unregister_srcaddr(ipv4_srcaddrtab); + } + if (V_ipv4_hashtbl != NULL) { gre_hashdestroy(V_ipv4_hashtbl); + gre_hashdestroy(V_ipv4_srchashtbl); + } } Index: head/sys/netinet6/ip6_gre.c =================================================================== --- head/sys/netinet6/ip6_gre.c +++ head/sys/netinet6/ip6_gre.c @@ -66,9 +66,13 @@ &VNET_NAME(ip6_gre_hlim), 0, "Default hop limit for encapsulated packets"); VNET_DEFINE_STATIC(struct gre_list *, ipv6_hashtbl) = NULL; +VNET_DEFINE_STATIC(struct gre_list *, ipv6_srchashtbl) = NULL; #define V_ipv6_hashtbl VNET(ipv6_hashtbl) +#define V_ipv6_srchashtbl VNET(ipv6_srchashtbl) #define GRE_HASH(src, dst) (V_ipv6_hashtbl[\ in6_gre_hashval((src), (dst)) & (GRE_HASH_SIZE - 1)]) +#define GRE_SRCHASH(src) (V_ipv6_srchashtbl[\ + fnv_32_buf((src), sizeof(*src), FNV1_32_INIT) & (GRE_HASH_SIZE - 1)]) #define GRE_HASH_SC(sc) GRE_HASH(&(sc)->gre_oip6.ip6_src,\ &(sc)->gre_oip6.ip6_dst) @@ -131,7 +135,45 @@ return (0); } +/* + * Check that ingress address belongs to local host. + */ static void +in6_gre_set_running(struct gre_softc *sc) +{ + + if (in6_localip(&sc->gre_oip6.ip6_src)) + GRE2IFP(sc)->if_drv_flags |= IFF_DRV_RUNNING; + else + GRE2IFP(sc)->if_drv_flags &= ~IFF_DRV_RUNNING; +} + +/* + * ifaddr_event handler. + * Clear IFF_DRV_RUNNING flag when ingress address disappears to prevent + * source address spoofing. + */ +static void +in6_gre_srcaddr(void *arg __unused, const struct sockaddr *sa, + int event __unused) +{ + const struct sockaddr_in6 *sin; + struct gre_softc *sc; + + if (V_ipv6_srchashtbl == NULL) + return; + + MPASS(in_epoch(net_epoch_preempt)); + sin = (const struct sockaddr_in6 *)sa; + CK_LIST_FOREACH(sc, &GRE_SRCHASH(&sin->sin6_addr), srchash) { + if (IN6_ARE_ADDR_EQUAL(&sc->gre_oip6.ip6_src, + &sin->sin6_addr) == 0) + continue; + in6_gre_set_running(sc); + } +} + +static void in6_gre_attach(struct gre_softc *sc) { @@ -140,6 +182,7 @@ sc->gre_oip6.ip6_nxt = IPPROTO_GRE; gre_updatehdr(sc, &sc->gre_gi6hdr->gi6_gre); CK_LIST_INSERT_HEAD(&GRE_HASH_SC(sc), sc, chain); + CK_LIST_INSERT_HEAD(&GRE_SRCHASH(&sc->gre_oip6.ip6_src), sc, srchash); } void @@ -151,6 +194,7 @@ /* NOTE: we are protected with gre_ioctl_sx lock */ MPASS(sc->gre_family == AF_INET6); CK_LIST_REMOVE(sc, chain); + CK_LIST_REMOVE(sc, srchash); GRE_WAIT(); if (cmd == GRESKEY) sc->gre_key = value; @@ -194,8 +238,10 @@ (error = sa6_embedscope(dst, 0)) != 0) break; - if (V_ipv6_hashtbl == NULL) + if (V_ipv6_hashtbl == NULL) { V_ipv6_hashtbl = gre_hashinit(); + V_ipv6_srchashtbl = gre_hashinit(); + } error = in6_gre_checkdup(sc, &src->sin6_addr, &dst->sin6_addr); if (error == EADDRNOTAVAIL) @@ -212,6 +258,7 @@ if (sc->gre_family != 0) { /* Detach existing tunnel first */ CK_LIST_REMOVE(sc, chain); + CK_LIST_REMOVE(sc, srchash); GRE_WAIT(); free(sc->gre_hdr, M_GRE); /* XXX: should we notify about link state change? */ @@ -221,6 +268,7 @@ sc->gre_oseq = 0; sc->gre_iseq = UINT32_MAX; in6_gre_attach(sc); + in6_gre_set_running(sc); break; case SIOCGIFPSRCADDR_IN6: case SIOCGIFPDSTADDR_IN6: @@ -254,6 +302,7 @@ return (ip6_output(m, NULL, NULL, IPV6_MINMTU, NULL, NULL, NULL)); } +static const struct srcaddrtab *ipv6_srcaddrtab = NULL; static const struct encaptab *ecookie = NULL; static const struct encap_config ipv6_encap_cfg = { .proto = IPPROTO_GRE, @@ -274,6 +323,8 @@ if (!IS_DEFAULT_VNET(curvnet)) return; + ipv6_srcaddrtab = ip6_encap_register_srcaddr(in6_gre_srcaddr, + NULL, M_WAITOK); ecookie = ip6_encap_attach(&ipv6_encap_cfg, NULL, M_WAITOK); } @@ -281,8 +332,12 @@ in6_gre_uninit(void) { - if (IS_DEFAULT_VNET(curvnet)) + if (IS_DEFAULT_VNET(curvnet)) { ip6_encap_detach(ecookie); - if (V_ipv6_hashtbl != NULL) + ip6_encap_unregister_srcaddr(ipv6_srcaddrtab); + } + if (V_ipv6_hashtbl != NULL) { gre_hashdestroy(V_ipv6_hashtbl); + gre_hashdestroy(V_ipv6_srchashtbl); + } }