diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h --- a/lib/libpfctl/libpfctl.h +++ b/lib/libpfctl/libpfctl.h @@ -89,6 +89,7 @@ uint8_t direction; uint16_t proto; struct pfctl_eth_addr src, dst; + struct pf_rule_addr ipsrc, ipdst; /* Stats */ uint64_t evaluations; diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c --- a/lib/libpfctl/libpfctl.c +++ b/lib/libpfctl/libpfctl.c @@ -598,6 +598,11 @@ pfctl_nveth_addr_to_eth_addr(nvlist_get_nvlist(nvl, "dst"), &rule->dst); + pf_nvrule_addr_to_rule_addr(nvlist_get_nvlist(nvl, "ipsrc"), + &rule->ipsrc); + pf_nvrule_addr_to_rule_addr(nvlist_get_nvlist(nvl, "ipdst"), + &rule->ipdst); + rule->evaluations = nvlist_get_number(nvl, "evaluations"); rule->packets[0] = nvlist_get_number(nvl, "packets-in"); rule->packets[1] = nvlist_get_number(nvl, "packets-out"); @@ -659,7 +664,7 @@ const char *path, struct pfctl_eth_rule *rule, bool clear, char *anchor_call) { - uint8_t buf[1024]; + uint8_t buf[2048]; struct pfioc_nv nv; nvlist_t *nvl; void *data; @@ -738,6 +743,9 @@ nvlist_add_nvlist(nvl, "dst", addr); nvlist_destroy(addr); + pfctl_nv_add_rule_addr(nvl, "ipsrc", &r->ipsrc); + pfctl_nv_add_rule_addr(nvl, "ipdst", &r->ipdst); + nvlist_add_string(nvl, "qname", r->qname); nvlist_add_string(nvl, "tagname", r->tagname); nvlist_add_number(nvl, "dnpipe", r->dnpipe); diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y --- a/sbin/pfctl/parse.y +++ b/sbin/pfctl/parse.y @@ -350,7 +350,8 @@ struct pfctl_rule *); void expand_eth_rule(struct pfctl_eth_rule *, struct node_if *, struct node_etherproto *, - struct node_mac *, struct node_mac *, const char *); + struct node_mac *, struct node_mac *, + struct node_host *, struct node_host *, const char *); void expand_rule(struct pfctl_rule *, struct node_if *, struct node_host *, struct node_proto *, struct node_os *, struct node_host *, struct node_port *, struct node_host *, @@ -492,7 +493,7 @@ %token REASSEMBLE FRAGDROP FRAGCROP ANCHOR NATANCHOR RDRANCHOR BINATANCHOR %token SET OPTIMIZATION TIMEOUT LIMIT LOGINTERFACE BLOCKPOLICY FAILPOLICY %token RANDOMID REQUIREORDER SYNPROXY FINGERPRINTS NOSYNC DEBUG SKIP HOSTID -%token ANTISPOOF FOR INCLUDE KEEPCOUNTERS SYNCOOKIES +%token ANTISPOOF FOR INCLUDE KEEPCOUNTERS SYNCOOKIES L3 %token ETHER %token BITMASK RANDOM SOURCEHASH ROUNDROBIN STATICPORT PROBABILITY MAPEPORTSET %token ALTQ CBQ CODEL PRIQ HFSC FAIRQ BANDWIDTH TBRSIZE LINKSHARE REALTIME @@ -523,7 +524,7 @@ %type icmp_list icmp_item %type icmp6_list icmp6_item %type reticmpspec reticmp6spec -%type fromto +%type fromto l3fromto %type ipportspec from to %type ipspec toipspec xhost host dynaddr host_list %type redir_host_list redirspec @@ -1182,7 +1183,7 @@ } ; -etherrule : ETHER action dir quick interface etherproto etherfromto etherfilter_opts +etherrule : ETHER action dir quick interface etherproto etherfromto l3fromto etherfilter_opts { struct pfctl_eth_rule r; @@ -1194,14 +1195,15 @@ r.action = $2.b1; r.direction = $3; r.quick = $4.quick; - if ($8.tag != NULL) - memcpy(&r.tagname, $8.tag, sizeof(r.tagname)); - if ($8.queues.qname != NULL) - memcpy(&r.qname, $8.queues.qname, sizeof(r.qname)); - r.dnpipe = $8.dnpipe; - r.dnflags = $8.free_flags; + if ($9.tag != NULL) + memcpy(&r.tagname, $9.tag, sizeof(r.tagname)); + if ($9.queues.qname != NULL) + memcpy(&r.qname, $9.queues.qname, sizeof(r.qname)); + r.dnpipe = $9.dnpipe; + r.dnflags = $9.free_flags; - expand_eth_rule(&r, $5, $6, $7.src, $7.dst, ""); + expand_eth_rule(&r, $5, $6, $7.src, $7.dst, + $8.src.host, $8.dst.host, ""); } ; @@ -1236,7 +1238,7 @@ | /* empty */ ; -etheranchorrule : ETHER ANCHOR anchorname dir quick interface etherproto etherfromto etherpfa_anchor +etheranchorrule : ETHER ANCHOR anchorname dir quick interface etherproto etherfromto l3fromto etherpfa_anchor { struct pfctl_eth_rule r; @@ -1286,6 +1288,7 @@ r.quick = $5.quick; expand_eth_rule(&r, $6, $7, $8.src, $8.dst, + $9.src.host, $9.dst.host, pf->eastack[pf->asd + 1] ? pf->ealast->name : $3); free($3); @@ -3254,6 +3257,13 @@ } ; +l3fromto : /* empty */ { + bzero(&$$, sizeof($$)); + } + | L3 fromto { + $$ = $2; + } + ; etherfromto : ALL { $$.src = NULL; $$.dst = NULL; @@ -5733,23 +5743,45 @@ return (0); } +static int +pf_af_to_proto(sa_family_t af) +{ + if (af == AF_INET) + return (ETHERTYPE_IP); + if (af == AF_INET6) + return (ETHERTYPE_IPV6); + + return (0); +} + void expand_eth_rule(struct pfctl_eth_rule *r, struct node_if *interfaces, struct node_etherproto *protos, - struct node_mac *srcs, struct node_mac *dsts, const char *anchor_call) + struct node_mac *srcs, struct node_mac *dsts, + struct node_host *ipsrcs, struct node_host *ipdsts, const char *anchor_call) { LOOP_THROUGH(struct node_if, interface, interfaces, LOOP_THROUGH(struct node_etherproto, proto, protos, LOOP_THROUGH(struct node_mac, src, srcs, LOOP_THROUGH(struct node_mac, dst, dsts, + LOOP_THROUGH(struct node_host, ipsrc, ipsrcs, + LOOP_THROUGH(struct node_host, ipdst, ipdsts, strlcpy(r->ifname, interface->ifname, sizeof(r->ifname)); r->ifnot = interface->not; r->proto = proto->proto; + if (!r->proto && ipsrc->af) + r->proto = pf_af_to_proto(ipsrc->af); + else if (!r->proto && ipdst->af) + r->proto = pf_af_to_proto(ipdst->af); bcopy(src->mac, r->src.addr, ETHER_ADDR_LEN); bcopy(src->mask, r->src.mask, ETHER_ADDR_LEN); r->src.neg = src->neg; r->src.isset = src->isset; + r->ipsrc.addr = ipsrc->addr; + r->ipsrc.neg = ipsrc->not; + r->ipdst.addr = ipdst->addr; + r->ipdst.neg = ipdst->not; bcopy(dst->mac, r->dst.addr, ETHER_ADDR_LEN); bcopy(dst->mask, r->dst.mask, ETHER_ADDR_LEN); r->dst.neg = dst->neg; @@ -5757,12 +5789,14 @@ r->nr = pf->eastack[pf->asd]->match++; pfctl_append_eth_rule(pf, r, anchor_call); - )))); + )))))); FREE_LIST(struct node_if, interfaces); FREE_LIST(struct node_etherproto, protos); FREE_LIST(struct node_mac, srcs); FREE_LIST(struct node_mac, dsts); + FREE_LIST(struct node_host, ipsrcs); + FREE_LIST(struct node_host, ipdsts); } void @@ -6052,6 +6086,7 @@ { "interval", INTERVAL}, { "keep", KEEP}, { "keepcounters", KEEPCOUNTERS}, + { "l3", L3}, { "label", LABEL}, { "limit", LIMIT}, { "linkshare", LINKSHARE}, diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c --- a/sbin/pfctl/pfctl_parser.c +++ b/sbin/pfctl/pfctl_parser.c @@ -782,7 +782,12 @@ printf(" to "); print_eth_addr(&r->dst); } - + if (r->proto == ETHERTYPE_IP || r->proto == ETHERTYPE_IPV6) { + printf(" l3"); + print_fromto(&r->ipsrc, PF_OSFP_ANY, &r->ipdst, + r->proto == ETHERTYPE_IP ? AF_INET : AF_INET6, 0, + 0, 0); + } if (r->qname[0]) printf(" queue %s", r->qname); if (r->tagname[0]) diff --git a/share/man/man5/pf.conf.5 b/share/man/man5/pf.conf.5 --- a/share/man/man5/pf.conf.5 +++ b/share/man/man5/pf.conf.5 @@ -28,7 +28,7 @@ .\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .\" -.Dd September 25, 2021 +.Dd March 9, 2022 .Dt PF.CONF 5 .Os .Sh NAME @@ -3072,7 +3072,7 @@ ether-rule = "ether" etheraction [ ( "in" | "out" ) ] [ "quick" ] [ "on" ifspec ] [ etherprotospec ] - etherhosts [ etherfilteropt-list ] + etherhosts [ "l3" hosts ] [ etherfilteropt-list ] pf-rule = action [ ( "in" | "out" ) ] [ "log" [ "(" logopts ")"] ] [ "quick" ] diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -646,6 +646,7 @@ uint8_t direction; uint16_t proto; struct pf_keth_rule_addr src, dst; + struct pf_rule_addr ipsrc, ipdst; /* Stats */ counter_u64_t evaluations; diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c --- a/sys/netpfil/pf/pf.c +++ b/sys/netpfil/pf/pf.c @@ -279,7 +279,7 @@ void pf_rule_to_actions(struct pf_krule *, struct pf_rule_actions *); static int pf_test_eth_rule(int, struct pfi_kkif *, - struct mbuf *); + struct mbuf **); static int pf_test_rule(struct pf_krule **, struct pf_kstate **, int, struct pfi_kkif *, struct mbuf *, int, struct pf_pdesc *, struct pf_krule **, @@ -3826,31 +3826,67 @@ } static int -pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf *m) +pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf **m0) { + struct mbuf *m = *m0; struct ether_header *e; struct pf_keth_rule *r, *rm, *a = NULL; struct pf_keth_ruleset *ruleset = NULL; struct pf_mtag *mtag; struct pf_keth_ruleq *rules; + struct pf_addr *src, *dst; + sa_family_t af = 0; + uint16_t proto; int asd = 0, match = 0; uint8_t action; struct pf_keth_anchor_stackframe anchor_stack[PF_ANCHOR_STACKSIZE]; - NET_EPOCH_ASSERT(); - MPASS(kif->pfik_ifp->if_vnet == curvnet); NET_EPOCH_ASSERT(); SDT_PROBE3(pf, eth, test_rule, entry, dir, kif->pfik_ifp, m); - e = mtod(m, struct ether_header *); - ruleset = V_pf_keth; rules = ck_pr_load_ptr(&ruleset->active.rules); r = TAILQ_FIRST(rules); rm = NULL; + e = mtod(m, struct ether_header *); + proto = ntohs(e->ether_type); + + switch (proto) { + case ETHERTYPE_IP: { + struct ip *ip; + m = m_pullup(m, sizeof(struct ether_header) + + sizeof(struct ip)); + if (m == NULL) { + *m0 = NULL; + return (PF_DROP); + } + af = AF_INET; + ip = mtodo(m, sizeof(struct ether_header)); + src = (struct pf_addr *)&ip->ip_src; + dst = (struct pf_addr *)&ip->ip_dst; + break; + } + case ETHERTYPE_IPV6: { + struct ip6_hdr *ip6; + m = m_pullup(m, sizeof(struct ether_header) + + sizeof(struct ip6_hdr)); + if (m == NULL) { + *m0 = NULL; + return (PF_DROP); + } + af = AF_INET6; + ip6 = mtodo(m, sizeof(struct ether_header)); + src = (struct pf_addr *)&ip6->ip6_src; + dst = (struct pf_addr *)&ip6->ip6_dst; + break; + } + } + e = mtod(m, struct ether_header *); + *m0 = m; + while (r != NULL) { counter_u64_add(r->evaluations, 1); SDT_PROBE2(pf, eth, test_rule, test, r->nr, r); @@ -3865,7 +3901,7 @@ "dir"); r = r->skip[PFE_SKIP_DIR].ptr; } - else if (r->proto && r->proto != ntohs(e->ether_type)) { + else if (r->proto && r->proto != proto) { SDT_PROBE3(pf, eth, test_rule, mismatch, r->nr, r, "proto"); r = r->skip[PFE_SKIP_PROTO].ptr; @@ -3880,6 +3916,18 @@ "dst"); r = TAILQ_NEXT(r, entries); } + else if (af != 0 && PF_MISMATCHAW(&r->ipsrc.addr, src, af, + r->ipsrc.neg, kif, M_GETFIB(m))) { + SDT_PROBE3(pf, eth, test_rule, mismatch, r->nr, r, + "ip_src"); + r = TAILQ_NEXT(r, entries); + } + else if (af != 0 && PF_MISMATCHAW(&r->ipdst.addr, dst, af, + r->ipdst.neg, kif, M_GETFIB(m))) { + SDT_PROBE3(pf, eth, test_rule, mismatch, r->nr, r, + "ip_dst"); + r = TAILQ_NEXT(r, entries); + } else { if (r->anchor == NULL) { /* Rule matches */ @@ -6737,7 +6785,7 @@ return (PF_PASS); /* Stateless! */ - return (pf_test_eth_rule(dir, kif, m)); + return (pf_test_eth_rule(dir, kif, m0)); } #ifdef INET diff --git a/sys/netpfil/pf/pf_nv.c b/sys/netpfil/pf/pf_nv.c --- a/sys/netpfil/pf/pf_nv.c +++ b/sys/netpfil/pf/pf_nv.c @@ -1071,6 +1071,22 @@ } nvlist_add_nvlist(nvl, "dst", addr); + addr = pf_rule_addr_to_nvrule_addr(&krule->ipsrc); + if (addr == NULL) { + nvlist_destroy(nvl); + return (NULL); + } + nvlist_add_nvlist(nvl, "ipsrc", addr); + nvlist_destroy(addr); + + addr = pf_rule_addr_to_nvrule_addr(&krule->ipdst); + if (addr == NULL) { + nvlist_destroy(nvl); + return (NULL); + } + nvlist_add_nvlist(nvl, "ipdst", addr); + nvlist_destroy(addr); + nvlist_add_number(nvl, "evaluations", counter_u64_fetch(krule->evaluations)); nvlist_add_number(nvl, "packets-in", @@ -1125,6 +1141,20 @@ return (error); } + if (nvlist_exists_nvlist(nvl, "ipsrc")) { + error = pf_nvrule_addr_to_rule_addr( + nvlist_get_nvlist(nvl, "ipsrc"), &krule->ipsrc); + if (error != 0) + return (error); + } + + if (nvlist_exists_nvlist(nvl, "ipdst")) { + error = pf_nvrule_addr_to_rule_addr( + nvlist_get_nvlist(nvl, "ipdst"), &krule->ipdst); + if (error != 0) + return (error); + } + PFNV_CHK(pf_nvstring(nvl, "qname", krule->qname, sizeof(krule->qname))); PFNV_CHK(pf_nvstring(nvl, "tagname", krule->tagname, sizeof(krule->tagname)));