diff --git a/sbin/ifconfig/ifpfsync.c b/sbin/ifconfig/ifpfsync.c --- a/sbin/ifconfig/ifpfsync.c +++ b/sbin/ifconfig/ifpfsync.c @@ -234,12 +234,18 @@ struct sockaddr_in *sin = (struct sockaddr_in *) peerres->ai_addr; - if (IN_MULTICAST(ntohl(sin->sin_addr.s_addr))) - errx(1, "syncpeer address cannot be multicast"); - memcpy(&addr, sin, sizeof(*sin)); break; } +#endif +#ifdef INET6 + case AF_INET6: { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) + peerres->ai_addr; + + memcpy(&addr, sin6, sizeof(*sin6)); + break; + } #endif default: errx(1, "syncpeer address %s not supported", val); @@ -363,9 +369,9 @@ if (syncdev[0] != '\0') printf("syncdev: %s ", syncdev); - if (syncpeer.ss_family == AF_INET && + if ((syncpeer.ss_family == AF_INET && ((struct sockaddr_in *)&syncpeer)->sin_addr.s_addr != - htonl(INADDR_PFSYNC_GROUP)) { + htonl(INADDR_PFSYNC_GROUP)) || syncpeer.ss_family == AF_INET6) { struct sockaddr *syncpeer_sa = (struct sockaddr *)&syncpeer; diff --git a/sys/netinet6/in6.h b/sys/netinet6/in6.h --- a/sys/netinet6/in6.h +++ b/sys/netinet6/in6.h @@ -206,6 +206,9 @@ #define IN6ADDR_LINKLOCAL_ALLV2ROUTERS_INIT \ {{{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16 }}} +#define IN6ADDR_LINKLOCAL_PFSYNC_GROUP_INIT \ + {{{ 0xff, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0 }}} #endif extern const struct in6_addr in6addr_any; @@ -215,6 +218,7 @@ extern const struct in6_addr in6addr_linklocal_allnodes; extern const struct in6_addr in6addr_linklocal_allrouters; extern const struct in6_addr in6addr_linklocal_allv2routers; +extern const struct in6_addr in6addr_linklocal_pfsync_group; #endif /* diff --git a/sys/netinet6/in6.c b/sys/netinet6/in6.c --- a/sys/netinet6/in6.c +++ b/sys/netinet6/in6.c @@ -139,6 +139,8 @@ IN6ADDR_LINKLOCAL_ALLROUTERS_INIT; const struct in6_addr in6addr_linklocal_allv2routers = IN6ADDR_LINKLOCAL_ALLV2ROUTERS_INIT; +const struct in6_addr in6addr_linklocal_pfsync_group = + IN6ADDR_LINKLOCAL_PFSYNC_GROUP_INIT; const struct in6_addr in6mask0 = IN6MASK0; const struct in6_addr in6mask32 = IN6MASK32; diff --git a/sys/netpfil/pf/if_pfsync.c b/sys/netpfil/pf/if_pfsync.c --- a/sys/netpfil/pf/if_pfsync.c +++ b/sys/netpfil/pf/if_pfsync.c @@ -91,12 +91,15 @@ #include #include #include +#include #include #include #include #include +#include #include +#include #include #include #include @@ -105,6 +108,7 @@ #include #include +#include #include @@ -112,7 +116,8 @@ struct pfsync_softc; union inet_template { - struct ip ipv4; + struct ip ipv4; + struct ip6_hdr ipv6; }; #define PFSYNC_MINPKT ( \ @@ -215,6 +220,7 @@ struct ifnet *sc_ifp; struct ifnet *sc_sync_if; struct ip_moptions sc_imo; + struct ip6_moptions sc_im6o; struct sockaddr_storage sc_sync_peer; uint32_t sc_flags; uint8_t sc_maxupdates; @@ -265,8 +271,7 @@ static void pfsync_push(struct pfsync_bucket *); static void pfsync_push_all(struct pfsync_softc *); static void pfsyncintr(void *); -static int pfsync_multicast_setup(struct pfsync_softc *, struct ifnet *, - struct in_mfilter *imf); +static int pfsync_multicast_setup(struct pfsync_softc *, struct ifnet *); static void pfsync_multicast_cleanup(struct pfsync_softc *); static void pfsync_pointers_init(void); static void pfsync_pointers_uninit(void); @@ -761,6 +766,109 @@ } #endif +#ifdef INET6 +static int +pfsync6_input(struct mbuf **mp, int *offp __unused, int proto __unused) +{ + struct pfsync_softc *sc = V_pfsyncif; + struct mbuf *m = *mp; + struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); + struct pfsync_header *ph; + struct pfsync_subheader subh; + + int offset, len, flags = 0; + int rv; + uint16_t count; + + PF_RULES_RLOCK_TRACKER; + + *mp = NULL; + V_pfsyncstats.pfsyncs_ipackets++; + + /* Verify that we have a sync interface configured. */ + if (!sc || !sc->sc_sync_if || !V_pf_status.running || + (sc->sc_ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + goto done; + + /* verify that the packet came in on the right interface */ + if (sc->sc_sync_if != m->m_pkthdr.rcvif) { + V_pfsyncstats.pfsyncs_badif++; + goto done; + } + + if_inc_counter(sc->sc_ifp, IFCOUNTER_IPACKETS, 1); + if_inc_counter(sc->sc_ifp, IFCOUNTER_IBYTES, m->m_pkthdr.len); + /* verify that the IP TTL is 255. */ + if (ip6->ip6_hlim != PFSYNC_DFLTTL) { + V_pfsyncstats.pfsyncs_badttl++; + goto done; + } + + + offset = sizeof(*ip6); + if (m->m_pkthdr.len < offset + sizeof(*ph)) { + V_pfsyncstats.pfsyncs_hdrops++; + goto done; + } + + if (offset + sizeof(*ph) > m->m_len) { + if (m_pullup(m, offset + sizeof(*ph)) == NULL) { + V_pfsyncstats.pfsyncs_hdrops++; + return (IPPROTO_DONE); + } + ip6 = mtod(m, struct ip6_hdr *); + } + ph = (struct pfsync_header *)((char *)ip6 + offset); + + /* verify the version */ + if (ph->version != PFSYNC_VERSION) { + V_pfsyncstats.pfsyncs_badver++; + goto done; + } + + len = ntohs(ph->len) + offset; + if (m->m_pkthdr.len < len) { + V_pfsyncstats.pfsyncs_badlen++; + goto done; + } + + /* + * Trusting pf_chksum during packet processing, as well as seeking + * in interface name tree, require holding PF_RULES_RLOCK(). + */ + PF_RULES_RLOCK(); + if (!bcmp(&ph->pfcksum, &V_pf_status.pf_chksum, PF_MD5_DIGEST_LENGTH)) + flags = PFSYNC_SI_CKSUM; + + offset += sizeof(*ph); + while (offset <= len - sizeof(subh)) { + m_copydata(m, offset, sizeof(subh), (caddr_t)&subh); + offset += sizeof(subh); + + if (subh.action >= PFSYNC_ACT_MAX) { + V_pfsyncstats.pfsyncs_badact++; + PF_RULES_RUNLOCK(); + goto done; + } + + count = ntohs(subh.count); + V_pfsyncstats.pfsyncs_iacts[subh.action] += count; + rv = (*pfsync_acts[subh.action])(m, offset, count, flags); + if (rv == -1) { + PF_RULES_RUNLOCK(); + return (IPPROTO_DONE); + } + + offset += rv; + } + PF_RULES_RUNLOCK(); + +done: + m_freem(m); + return (IPPROTO_DONE); +} +#endif + static int pfsync_in_clr(struct mbuf *m, int offset, int count, int flags) { @@ -1585,6 +1693,19 @@ ip_fillid(ip); break; } +#endif +#ifdef INET6 + case AF_INET6: + { + struct ip6_hdr *ip6; + + ip6 = mtod(m, struct ip6_hdr *); + bcopy(&sc->sc_template.ipv6, ip6, sizeof(*ip6)); + aflen = offset = sizeof(*ip6); + + ip6->ip6_plen = htons(m->m_pkthdr.len); + break; + } #endif default: m_freem(m); @@ -2357,10 +2478,8 @@ error = ip6_output(m, NULL, NULL, 0, NULL, NULL, NULL); } else { - MPASS(false); - /* We don't support pfsync over IPv6. */ - /*error = ip6_output(m, NULL, NULL, - IP_RAWOUTPUT, &sc->sc_imo6, NULL);*/ + error = ip6_output(m, NULL, NULL, 0, + &sc->sc_im6o, NULL, NULL); } break; #endif @@ -2408,11 +2527,13 @@ } static int -pfsync_multicast_setup(struct pfsync_softc *sc, struct ifnet *ifp, - struct in_mfilter *imf) +pfsync_multicast_setup(struct pfsync_softc *sc, struct ifnet *ifp) { struct ip_moptions *imo = &sc->sc_imo; + struct ip6_moptions *im6o = &sc->sc_im6o; int error; + struct in_mfilter *imf = NULL; + struct in6_mfilter *im6f = NULL; if (!(ifp->if_flags & IFF_MULTICAST)) return (EADDRNOTAVAIL); @@ -2421,11 +2542,15 @@ #ifdef INET case AF_INET: { + imf = ip_mfilter_alloc(M_WAITOK, 0, 0); ip_mfilter_init(&imo->imo_head); imo->imo_multicast_vif = -1; if ((error = in_joingroup(ifp, &((struct sockaddr_in *)&sc->sc_sync_peer)->sin_addr, NULL, &imf->imf_inm)) != 0) + { + ip_mfilter_free(imf); return (error); + } ip_mfilter_insert(&imo->imo_head, imf); imo->imo_multicast_ifp = ifp; @@ -2434,7 +2559,29 @@ break; } #endif +#ifdef INET6 + case AF_INET6: + { + if ((error = in6_setscope(&((struct sockaddr_in6 *)&sc->sc_sync_peer)->sin6_addr, ifp, NULL))) { + return (error); + } + im6f = ip6_mfilter_alloc(M_WAITOK, 0, 0); + ip6_mfilter_init(&im6o->im6o_head); + if ((error = in6_joingroup(ifp, &((struct sockaddr_in6 *)&sc->sc_sync_peer)->sin6_addr, NULL, + &(im6f->im6f_in6m), 0))) + { + ip6_mfilter_free(im6f); + return (error); + } + + ip6_mfilter_insert(&im6o->im6o_head, im6f); + im6o->im6o_multicast_ifp = ifp; + im6o->im6o_multicast_hlim = PFSYNC_DFLTTL; + im6o->im6o_multicast_loop = 0; + break; + } } +#endif return (0); } @@ -2443,7 +2590,9 @@ pfsync_multicast_cleanup(struct pfsync_softc *sc) { struct ip_moptions *imo = &sc->sc_imo; + struct ip6_moptions *im6o = &sc->sc_im6o; struct in_mfilter *imf; + struct in6_mfilter *im6f; while ((imf = ip_mfilter_first(&imo->imo_head)) != NULL) { ip_mfilter_remove(&imo->imo_head, imf); @@ -2451,6 +2600,13 @@ ip_mfilter_free(imf); } imo->imo_multicast_ifp = NULL; + + while ((im6f = ip6_mfilter_first(&im6o->im6o_head)) != NULL) { + ip6_mfilter_remove(&im6o->im6o_head, im6f); + in6_leavegroup(im6f->im6f_in6m, NULL); + ip6_mfilter_free(im6f); + } + im6o->im6o_multicast_ifp = NULL; } void @@ -2470,6 +2626,7 @@ */ ip_mfilter_init(&sc->sc_imo.imo_head); sc->sc_imo.imo_multicast_ifp = NULL; + sc->sc_im6o.im6o_multicast_ifp = NULL; sc->sc_sync_if = NULL; } @@ -2500,9 +2657,7 @@ static int pfsync_kstatus_to_softc(struct pfsync_kstatus *status, struct pfsync_softc *sc) { - struct in_mfilter *imf = NULL; struct ifnet *sifp; - struct ip *ip; int error; int c; @@ -2514,21 +2669,50 @@ else if ((sifp = ifunit_ref(status->syncdev)) == NULL) return (EINVAL); - struct sockaddr_in *status_sin = - (struct sockaddr_in *)&(status->syncpeer); - if (sifp != NULL && (status_sin->sin_addr.s_addr == 0 || - status_sin->sin_addr.s_addr == - htonl(INADDR_PFSYNC_GROUP))) - imf = ip_mfilter_alloc(M_WAITOK, 0, 0); + switch (status->syncpeer.ss_family) { + case AF_UNSPEC: + case AF_INET: { + struct sockaddr_in *status_sin = (struct sockaddr_in *)&(status->syncpeer); + if (sifp != NULL && (status_sin->sin_addr.s_addr == 0 || + status_sin->sin_addr.s_addr == + htonl(INADDR_PFSYNC_GROUP))) { + status_sin->sin_family = AF_INET; + status_sin->sin_len = sizeof(*status_sin); + status_sin->sin_addr.s_addr = htonl(INADDR_PFSYNC_GROUP); + } + break; + } + case AF_INET6: { + struct sockaddr_in6 *status_sin6 = (struct sockaddr_in6*)&(status->syncpeer); + if (sifp != NULL && + (IN6_IS_ADDR_UNSPECIFIED(&status_sin6->sin6_addr) || + IN6_ARE_ADDR_EQUAL(&status_sin6->sin6_addr, &in6addr_linklocal_pfsync_group))) { + status_sin6->sin6_family = AF_INET6; + status_sin6->sin6_len = sizeof(*status_sin6); + status_sin6->sin6_addr = in6addr_linklocal_pfsync_group; + } + break; + } + } PFSYNC_LOCK(sc); - struct sockaddr_in *sc_sin = (struct sockaddr_in *)&sc->sc_sync_peer; - sc_sin->sin_family = AF_INET; - sc_sin->sin_len = sizeof(*sc_sin); - if (status_sin->sin_addr.s_addr == 0) { - sc_sin->sin_addr.s_addr = htonl(INADDR_PFSYNC_GROUP); - } else { - sc_sin->sin_addr.s_addr = status_sin->sin_addr.s_addr; + switch (status->syncpeer.ss_family) { + case AF_INET: { + struct sockaddr_in *status_sin = (struct sockaddr_in *)&(status->syncpeer); + struct sockaddr_in *sc_sin = (struct sockaddr_in *)&sc->sc_sync_peer; + sc_sin->sin_family = AF_INET; + sc_sin->sin_len = sizeof(*sc_sin); + sc_sin->sin_addr = status_sin->sin_addr; + break; + } + case AF_INET6: { + struct sockaddr_in6 *status_sin = (struct sockaddr_in6 *)&(status->syncpeer); + struct sockaddr_in6 *sc_sin = (struct sockaddr_in6 *)&sc->sc_sync_peer; + sc_sin->sin6_family = AF_INET6; + sc_sin->sin6_len = sizeof(*sc_sin); + sc_sin->sin6_addr = status_sin->sin6_addr; + break; + } } sc->sc_maxupdates = status->maxupdates; @@ -2562,11 +2746,15 @@ pfsync_multicast_cleanup(sc); - if (sc_sin->sin_addr.s_addr == htonl(INADDR_PFSYNC_GROUP)) { - error = pfsync_multicast_setup(sc, sifp, imf); + if ( + ((sc->sc_sync_peer.ss_family == AF_INET) && + IN_MULTICAST(ntohl(((struct sockaddr_in *)&sc->sc_sync_peer)->sin_addr.s_addr))) || + ((sc->sc_sync_peer.ss_family == AF_INET6) && + IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6*)&sc->sc_sync_peer)->sin6_addr)) + ) { + error = pfsync_multicast_setup(sc, sifp); if (error) { if_rele(sifp); - ip_mfilter_free(imf); PFSYNC_UNLOCK(sc); return (error); } @@ -2575,17 +2763,39 @@ if_rele(sc->sc_sync_if); sc->sc_sync_if = sifp; - ip = &sc->sc_template.ipv4; - bzero(ip, sizeof(*ip)); - ip->ip_v = IPVERSION; - ip->ip_hl = sizeof(sc->sc_template.ipv4) >> 2; - ip->ip_tos = IPTOS_LOWDELAY; - /* len and id are set later. */ - ip->ip_off = htons(IP_DF); - ip->ip_ttl = PFSYNC_DFLTTL; - ip->ip_p = IPPROTO_PFSYNC; - ip->ip_src.s_addr = INADDR_ANY; - ip->ip_dst.s_addr = sc_sin->sin_addr.s_addr; + switch (sc->sc_sync_peer.ss_family) { + case AF_INET: { + struct ip *ip; + ip = &sc->sc_template.ipv4; + bzero(ip, sizeof(*ip)); + ip->ip_v = IPVERSION; + ip->ip_hl = sizeof(sc->sc_template.ipv4) >> 2; + ip->ip_tos = IPTOS_LOWDELAY; + /* len and id are set later. */ + ip->ip_off = htons(IP_DF); + ip->ip_ttl = PFSYNC_DFLTTL; + ip->ip_p = IPPROTO_PFSYNC; + ip->ip_src.s_addr = INADDR_ANY; + ip->ip_dst = ((struct sockaddr_in *)&sc->sc_sync_peer)->sin_addr; + break; + } + case AF_INET6: { + struct ip6_hdr *ip6; + ip6 = &sc->sc_template.ipv6; + bzero(ip6, sizeof(*ip6)); + ip6->ip6_vfc = IPV6_VERSION; + ip6->ip6_hlim = PFSYNC_DFLTTL; + ip6->ip6_nxt = IPPROTO_PFSYNC; + ip6->ip6_dst = ((struct sockaddr_in6 *)&sc->sc_sync_peer)->sin6_addr; + + struct epoch_tracker et; + NET_EPOCH_ENTER(et); + in6_selectsrc_addr(if_getfib(sc->sc_sync_if), &ip6->ip6_dst, 0, + sc->sc_sync_if, &ip6->ip6_src, NULL); + NET_EPOCH_EXIT(et); + break; + } + } /* Request a full state table update. */ if ((sc->sc_flags & PFSYNCF_OK) && carp_demote_adj_p) @@ -2672,15 +2882,22 @@ static int pfsync_init(void) { -#ifdef INET int error; pfsync_detach_ifnet_ptr = pfsync_detach_ifnet; +#ifdef INET error = ipproto_register(IPPROTO_PFSYNC, pfsync_input, NULL); if (error) return (error); #endif +#ifdef INET6 + error = ip6proto_register(IPPROTO_PFSYNC, pfsync6_input, NULL); + if (error) { + ipproto_unregister(IPPROTO_PFSYNC); + return (error); + } +#endif return (0); } @@ -2693,6 +2910,9 @@ #ifdef INET ipproto_unregister(IPPROTO_PFSYNC); #endif +#ifdef INET6 + ip6proto_unregister(IPPROTO_PFSYNC); +#endif } static int diff --git a/sys/netpfil/pf/pfsync_nv.c b/sys/netpfil/pf/pfsync_nv.c --- a/sys/netpfil/pf/pfsync_nv.c +++ b/sys/netpfil/pf/pfsync_nv.c @@ -35,6 +35,11 @@ #include #include +#include + +#include +#include + #include int @@ -42,6 +47,7 @@ struct sockaddr_storage *sa) { int af; + int error; if (!nvlist_exists_number(nvl, "af")) return (EINVAL); @@ -74,6 +80,11 @@ return (EINVAL); memcpy(in6, addr, sizeof(*in6)); + + error = sa6_embedscope(in6, V_ip6_use_defzone); + if (error) + return (error); + break; } #endif @@ -106,6 +117,7 @@ #ifdef INET6 case AF_INET6: { struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)sa; + sa6_recoverscope(in6); nvlist_add_number(nvl, "af", in6->sin6_family); nvlist_add_binary(nvl, "address", in6, sizeof(*in6)); break; diff --git a/tests/sys/netpfil/pf/pfsync.sh b/tests/sys/netpfil/pf/pfsync.sh --- a/tests/sys/netpfil/pf/pfsync.sh +++ b/tests/sys/netpfil/pf/pfsync.sh @@ -702,6 +702,130 @@ pft_cleanup } +atf_test_case "basic_ipv6_unicast" "cleanup" +basic_ipv6_unicast_head() +{ + atf_set descr 'Basic pfsync test (IPv6)' + atf_set require.user root +} + +basic_ipv6_unicast_body() +{ + pfsynct_init + + epair_sync=$(vnet_mkepair) + epair_one=$(vnet_mkepair) + epair_two=$(vnet_mkepair) + + vnet_mkjail one ${epair_one}a ${epair_sync}a + vnet_mkjail two ${epair_two}a ${epair_sync}b + + # pfsync interface + jexec one ifconfig ${epair_sync}a inet6 fc2c::1/64 up + jexec one ifconfig ${epair_one}a inet6 fc2b::1/64 up + jexec one ifconfig pfsync0 \ + syncdev ${epair_sync}a \ + syncpeer fc2c::2 \ + maxupd 1 \ + up + jexec two ifconfig ${epair_two}a inet6 fc2b::2/64 up + jexec two ifconfig ${epair_sync}b inet6 fc2c::2/64 up + jexec two ifconfig pfsync0 \ + syncdev ${epair_sync}b \ + syncpeer fc2c::1 \ + maxupd 1 \ + up + + # Enable pf! + jexec one pfctl -e + pft_set_rules one \ + "block on ${epair_sync}a inet" \ + "pass out keep state" + jexec two pfctl -e + pft_set_rules two \ + "block on ${epair_sync}b inet" \ + "pass out keep state" + + ifconfig ${epair_one}b inet6 fc2b::f0/64 up + + ping6 -c 1 -S fc2b::f0 fc2b::1 + + # Give pfsync time to do its thing + sleep 2 + + if ! jexec two pfctl -s states | grep icmp | grep fc2b::1 | \ + grep fc2b::f0 ; then + atf_fail "state not found on synced host" + fi +} + +basic_ipv6_unicast_cleanup() +{ + pfsynct_cleanup +} + +atf_test_case "basic_ipv6" "cleanup" +basic_ipv6_head() +{ + atf_set descr 'Basic pfsync test (IPv6)' + atf_set require.user root +} + +basic_ipv6_body() +{ + pfsynct_init + + epair_sync=$(vnet_mkepair) + epair_one=$(vnet_mkepair) + epair_two=$(vnet_mkepair) + + vnet_mkjail one ${epair_one}a ${epair_sync}a + vnet_mkjail two ${epair_two}a ${epair_sync}b + + # pfsync interface + jexec one ifconfig ${epair_sync}a inet6 fc2c::1/64 up + jexec one ifconfig ${epair_one}a inet6 fc2b::1/64 up + jexec one ifconfig pfsync0 \ + syncdev ${epair_sync}a \ + syncpeer ff12::f0 \ + maxupd 1 \ + up + jexec two ifconfig ${epair_two}a inet6 fc2b::2/64 up + jexec two ifconfig ${epair_sync}b inet6 fc2c::2/64 up + jexec two ifconfig pfsync0 \ + syncdev ${epair_sync}b \ + syncpeer ff12::f0 \ + maxupd 1 \ + up + + # Enable pf! + jexec one pfctl -e + pft_set_rules one \ + "block on ${epair_sync}a inet" \ + "pass out keep state" + jexec two pfctl -e + pft_set_rules two \ + "block on ${epair_sync}b inet" \ + "pass out keep state" + + ifconfig ${epair_one}b inet6 fc2b::f0/64 up + + ping6 -c 1 -S fc2b::f0 fc2b::1 + + # Give pfsync time to do its thing + sleep 2 + + if ! jexec two pfctl -s states | grep icmp | grep fc2b::1 | \ + grep fc2b::f0 ; then + atf_fail "state not found on synced host" + fi +} + +basic_ipv6_cleanup() +{ + pfsynct_cleanup +} + atf_init_test_cases() { atf_add_test_case "basic" @@ -712,4 +836,6 @@ atf_add_test_case "pfsync_pbr" atf_add_test_case "ipsec" atf_add_test_case "timeout" + atf_add_test_case "basic_ipv6_unicast" + atf_add_test_case "basic_ipv6" }