Index: sys/net/pfvar.h =================================================================== --- sys/net/pfvar.h +++ sys/net/pfvar.h @@ -51,6 +51,7 @@ #include #include +#include #include #include #ifdef _KERNEL @@ -568,6 +569,61 @@ uint32_t flags; }; +union pf_keth_rule_ptr { + struct pf_keth_rule *ptr; + uint32_t nr; +}; + +struct pf_keth_rule_addr { + uint8_t addr[ETHER_ADDR_LEN]; + bool neg; + uint8_t isset; +}; + +struct pf_keth_rule { +#define PFE_SKIP_IFP 0 +#define PFE_SKIP_DIR 1 +#define PFE_SKIP_PROTO 2 +#define PFE_SKIP_SRC_ADDR 3 +#define PFE_SKIP_DST_ADDR 4 +#define PFE_SKIP_COUNT 5 + union pf_keth_rule_ptr skip[PFE_SKIP_COUNT]; + + TAILQ_ENTRY(pf_keth_rule) entries; + + uint32_t nr; + + bool quick; + + /* Filter */ + char ifname[IFNAMSIZ]; + struct pfi_kkif *kif; + bool ifnot; + uint8_t direction; + uint16_t proto; + struct pf_keth_rule_addr src, dst; + + /* Stats */ + counter_u64_t evaluations; + counter_u64_t packets[2]; + counter_u64_t bytes[2]; + + /* Action */ + char qname[PF_QNAME_SIZE]; + int qid; + char tagname[PF_TAG_NAME_SIZE]; + uint16_t tag; + uint8_t action; +}; + +TAILQ_HEAD(pf_keth_rules, pf_keth_rule); + +struct pf_keth_settings { + struct pf_keth_rules rules; + uint32_t ticket; + int open; +}; + union pf_krule_ptr { struct pf_krule *ptr; u_int32_t nr; @@ -1614,6 +1670,7 @@ #define PF_RULESET_ALTQ (PF_RULESET_MAX) #define PF_RULESET_TABLE (PF_RULESET_MAX+1) +#define PF_RULESET_ETH (PF_RULESET_MAX+2) struct pfioc_trans { int size; /* number of elements */ int esize; /* size of each element in bytes */ @@ -1750,6 +1807,9 @@ #define DIOCGETSTATESV2 _IOWR('D', 93, struct pfioc_states_v2) #define DIOCGETSYNCOOKIES _IOWR('D', 94, struct pfioc_nv) #define DIOCSETSYNCOOKIES _IOWR('D', 95, struct pfioc_nv) +#define DIOCADDETHRULE _IOWR('D', 96, struct pfioc_nv) +#define DIOCGETETHRULE _IOWR('D', 97, struct pfioc_nv) +#define DIOCGETETHRULES _IOWR('D', 98, struct pfioc_nv) struct pf_ifspeed_v0 { char ifname[IFNAMSIZ]; @@ -1979,6 +2039,7 @@ u_int8_t); void pf_free_rule(struct pf_krule *); +int pf_test_eth(int, int, struct ifnet *, struct mbuf **, struct inpcb *); #ifdef INET int pf_test(int, int, struct ifnet *, struct mbuf **, struct inpcb *); int pf_normalize_ip(struct mbuf **, int, struct pfi_kkif *, u_short *, @@ -2141,7 +2202,13 @@ #define V_pf_main_anchor VNET(pf_main_anchor) #define pf_main_ruleset V_pf_main_anchor.ruleset +VNET_DECLARE(struct pf_keth_settings*, pf_keth); +#define V_pf_keth VNET(pf_keth) +VNET_DECLARE(struct pf_keth_settings*, pf_keth_inactive); +#define V_pf_keth_inactive VNET(pf_keth_inactive) + void pf_init_kruleset(struct pf_kruleset *); +void pf_init_keth(struct pf_keth_settings *); int pf_kanchor_setup(struct pf_krule *, const struct pf_kruleset *, const char *); int pf_kanchor_nvcopyout(const struct pf_kruleset *, Index: sys/netpfil/pf/pf.c =================================================================== --- sys/netpfil/pf/pf.c +++ sys/netpfil/pf/pf.c @@ -269,6 +269,8 @@ static u_int32_t pf_tcp_iss(struct pf_pdesc *); void pf_rule_to_actions(struct pf_krule *, struct pf_rule_actions *); +static int pf_test_eth_rule(int, struct pfi_kkif *, + 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 **, @@ -3565,6 +3567,108 @@ #undef ISN_RANDOM_INCREMENT } +static bool +pf_match_eth_addr(const uint8_t *a, const struct pf_keth_rule_addr *r) +{ + /* Always matches if not set */ + if (! r->isset) + return (!r->neg); + + if (memcmp(a, r->addr, ETHER_ADDR_LEN) == 0) + return (!r->neg); + + return (r->neg); +} + +static int +pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf *m) +{ + struct ether_header *e; + struct pf_keth_rule *r, *rm; + struct pf_mtag *mtag; + uint8_t action; + + PF_RULES_RLOCK_TRACKER; + + MPASS(kif->pfik_ifp->if_vnet == curvnet); + NET_EPOCH_ASSERT(); + + e = mtod(m, struct ether_header *); + + PF_RULES_RLOCK(); + + r = TAILQ_FIRST(&V_pf_keth->rules); + rm = NULL; + + while (r != NULL) { + counter_u64_add(r->evaluations, 1); + if (pfi_kkif_match(r->kif, kif) == r->ifnot) + r = r->skip[PFE_SKIP_IFP].ptr; + else if (r->direction && r->direction != dir) + r = r->skip[PFE_SKIP_DIR].ptr; + else if (r->proto && r->proto != ntohs(e->ether_type)) + r = r->skip[PFE_SKIP_PROTO].ptr; + else if (! pf_match_eth_addr(e->ether_shost, &r->src)) + r = r->skip[PFE_SKIP_SRC_ADDR].ptr; + else if (! pf_match_eth_addr(e->ether_dhost, &r->dst)) { + r = TAILQ_NEXT(r, entries); + } + else { + /* Rule matches */ + rm = r; + + if (r->quick) + break; + + r = TAILQ_NEXT(r, entries); + } + } + + r = rm; + + /* Default to pass. */ + if (r == NULL) { + PF_RULES_RUNLOCK(); + return (PF_PASS); + } + + /* Execute action. */ + counter_u64_add(r->packets[dir == PF_OUT], 1); + counter_u64_add(r->bytes[dir == PF_OUT], m_length(m, NULL)); + + /* Shortcut. Don't tag if we're just going to drop anyway. */ + if (r->action == PF_DROP) { + PF_RULES_RUNLOCK(); + return (PF_DROP); + } + + if (r->tag > 0) { + mtag = pf_get_mtag(m); + if (mtag == NULL) { + counter_u64_add(V_pf_status.counters[PFRES_MEMORY], 1); + PF_RULES_RUNLOCK(); + return (PF_DROP); + } + mtag->tag = r->tag; + } + + if (r->qid != 0) { + mtag = pf_get_mtag(m); + if (mtag == NULL) { + counter_u64_add(V_pf_status.counters[PFRES_MEMORY], 1); + PF_RULES_RUNLOCK(); + return (PF_DROP); + } + mtag->qid = r->qid; + } + + action = r->action; + + PF_RULES_RUNLOCK(); + + return (action); +} + static int pf_test_rule(struct pf_krule **rm, struct pf_kstate **sm, int direction, struct pfi_kkif *kif, struct mbuf *m, int off, struct pf_pdesc *pd, @@ -6315,6 +6419,37 @@ return (true); } +int +pf_test_eth(int dir, int pflags, struct ifnet *ifp, struct mbuf **m0, + struct inpcb *inp) +{ + struct pfi_kkif *kif; + struct mbuf *m = *m0; + + M_ASSERTPKTHDR(m); + MPASS(ifp->if_vnet == curvnet); + NET_EPOCH_ASSERT(); + + if (!V_pf_status.running) + return (PF_PASS); + + kif = (struct pfi_kkif *)ifp->if_pf_kif; + + if (kif == NULL) { + DPFPRINTF(PF_DEBUG_URGENT, + ("pf_test: kif == NULL, if_xname %s\n", ifp->if_xname)); + return (PF_DROP); + } + if (kif->pfik_flags & PFI_IFLAG_SKIP) + return (PF_PASS); + + if (m->m_flags & M_SKIP_FIREWALL) + return (PF_PASS); + + /* Stateless! */ + return (pf_test_eth_rule(dir, kif, m)); +} + #ifdef INET int pf_test(int dir, int pflags, struct ifnet *ifp, struct mbuf **m0, struct inpcb *inp) Index: sys/netpfil/pf/pf_ioctl.c =================================================================== --- sys/netpfil/pf/pf_ioctl.c +++ sys/netpfil/pf/pf_ioctl.c @@ -106,6 +106,10 @@ static void pf_empty_kpool(struct pf_kpalist *); static int pfioctl(struct cdev *, u_long, caddr_t, int, struct thread *); +static int pf_begin_eth(uint32_t *); +static int pf_rollback_eth(uint32_t); +static int pf_commit_eth(uint32_t); +static void pf_free_eth_rule(struct pf_keth_rule *); #ifdef ALTQ static int pf_begin_altq(u_int32_t *); static int pf_rollback_altq(u_int32_t); @@ -218,6 +222,10 @@ /* * Wrapper functions for pfil(9) hooks */ +static pfil_return_t pf_eth_check_in(struct mbuf **m, struct ifnet *ifp, + int flags, void *ruleset __unused, struct inpcb *inp); +static pfil_return_t pf_eth_check_out(struct mbuf **m, struct ifnet *ifp, + int flags, void *ruleset __unused, struct inpcb *inp); #ifdef INET static pfil_return_t pf_check_in(struct mbuf **m, struct ifnet *ifp, int flags, void *ruleset __unused, struct inpcb *inp); @@ -304,6 +312,9 @@ RB_INIT(&V_pf_anchors); pf_init_kruleset(&pf_main_ruleset); + pf_init_keth(V_pf_keth); + pf_init_keth(V_pf_keth_inactive); + /* default rule should never be garbage collected */ V_pf_default_rule.entries.tqe_prev = &V_pf_default_rule.entries.tqe_next; #ifdef PF_DEFAULT_TO_DROP @@ -471,6 +482,32 @@ PF_UNLNKDRULES_UNLOCK(); } +static void +pf_free_eth_rule(struct pf_keth_rule *rule) +{ + PF_RULES_WASSERT(); + + if (rule == NULL) + return; + + if (rule->tag) + tag_unref(&V_pf_tags, rule->tag); +#ifdef ALTQ + pf_qid_unref(rule->qid); +#endif + + if (rule->kif) + pfi_kkif_unref(rule->kif); + + counter_u64_free(rule->evaluations); + for (int i = 0; i < 2; i++) { + counter_u64_free(rule->packets[i]); + counter_u64_free(rule->bytes[i]); + } + + free(rule, M_PFRULE); +} + void pf_free_rule(struct pf_krule *rule) { @@ -657,6 +694,103 @@ return (tagname2tag(&V_pf_tags, tagname)); } +static int +pf_begin_eth(uint32_t *ticket) +{ + struct pf_keth_rule *rule, *tmp; + + PF_RULES_WASSERT(); + + /* Purge old inactive rules. */ + TAILQ_FOREACH_SAFE(rule, &V_pf_keth_inactive->rules, entries, tmp) { + TAILQ_REMOVE(&V_pf_keth_inactive->rules, rule, entries); + pf_free_eth_rule(rule); + } + + *ticket = ++V_pf_keth_inactive->ticket; + V_pf_keth_inactive->open = 1; + + return (0); +} + +static int +pf_rollback_eth(uint32_t ticket) +{ + struct pf_keth_rule *rule, *tmp; + + PF_RULES_WASSERT(); + + if (!V_pf_keth_inactive->open || ticket != V_pf_keth_inactive->ticket) + return (0); + + /* Purge old inactive rules. */ + TAILQ_FOREACH_SAFE(rule, &V_pf_keth_inactive->rules, entries, tmp) { + TAILQ_REMOVE(&V_pf_keth_inactive->rules, rule, entries); + pf_free_eth_rule(rule); + } + + V_pf_keth_inactive->open = 0; + + return (0); +} + +#define PF_SET_SKIP_STEPS(i) \ + do { \ + while (head[i] != cur) { \ + head[i]->skip[i].ptr = cur; \ + head[i] = TAILQ_NEXT(head[i], entries); \ + } \ + } while (0) + +static void +pf_eth_calc_skip_steps(struct pf_keth_rules *rules) +{ + struct pf_keth_rule *cur, *prev, *head[PFE_SKIP_COUNT]; + int i; + + cur = TAILQ_FIRST(rules); + prev = cur; + for (i = 0; i < PFE_SKIP_COUNT; ++i) + head[i] = cur; + while (cur != NULL) { + if (cur->kif != prev->kif || cur->ifnot != prev->ifnot) + PF_SET_SKIP_STEPS(PFE_SKIP_IFP); + if (cur->direction != prev->direction) + PF_SET_SKIP_STEPS(PFE_SKIP_DIR); + if (cur->proto != prev->proto) + PF_SET_SKIP_STEPS(PFE_SKIP_PROTO); + if (memcmp(&cur->src, &prev->src, sizeof(cur->src)) != 0) + PF_SET_SKIP_STEPS(PFE_SKIP_SRC_ADDR); + if (memcmp(&cur->dst, &prev->dst, sizeof(cur->dst)) != 0) + PF_SET_SKIP_STEPS(PFE_SKIP_DST_ADDR); + + prev = cur; + cur = TAILQ_NEXT(cur, entries); + } + for (i = 0; i < PFE_SKIP_COUNT; ++i) + PF_SET_SKIP_STEPS(i); +} + +static int +pf_commit_eth(uint32_t ticket) +{ + struct pf_keth_settings *settings; + + if (!V_pf_keth_inactive->open || + ticket != V_pf_keth_inactive->ticket) + return (EBUSY); + + pf_eth_calc_skip_steps(&V_pf_keth_inactive->rules); + + settings = V_pf_keth; + V_pf_keth = V_pf_keth_inactive; + V_pf_keth_inactive = settings; + V_pf_keth_inactive->ticket = V_pf_keth->ticket; + + /* Clean up inactive rules. */ + return (pf_rollback_eth(ticket)); +} + #ifdef ALTQ static uint16_t pf_qname2qid(const char *qname) @@ -2213,6 +2347,8 @@ case DIOCGIFSPEEDV1: case DIOCSETIFFLAG: case DIOCCLRIFFLAG: + case DIOCGETETHRULES: + case DIOCGETETHRULE: break; case DIOCRCLRTABLES: case DIOCRADDTABLES: @@ -2260,6 +2396,8 @@ case DIOCGIFSPEEDV1: case DIOCGIFSPEEDV0: case DIOCGETRULENV: + case DIOCGETETHRULES: + case DIOCGETETHRULE: break; case DIOCRCLRTABLES: case DIOCRADDTABLES: @@ -2318,6 +2456,213 @@ } break; + case DIOCGETETHRULES: { + struct pfioc_nv *nv = (struct pfioc_nv *)addr; + nvlist_t *nvl; + void *packed; + struct pf_keth_rule *tail; + u_int32_t ticket, nr; + + nvl = NULL; + packed = NULL; + +#define ERROUT(x) do { error = (x); goto DIOCGETETHRULES_error; } while (0) + + nvl = nvlist_create(0); + if (nvl == NULL) + ERROUT(ENOMEM); + + PF_RULES_RLOCK(); + + ticket = V_pf_keth->ticket; + tail = TAILQ_LAST(&V_pf_keth->rules, pf_keth_rules); + if (tail) + nr = tail->nr + 1; + else + nr = 0; + + PF_RULES_RUNLOCK(); + + nvlist_add_number(nvl, "ticket", ticket); + nvlist_add_number(nvl, "nr", nr); + + packed = nvlist_pack(nvl, &nv->len); + if (packed == NULL) + ERROUT(ENOMEM); + + if (nv->size == 0) + ERROUT(0); + else if (nv->size < nv->len) + ERROUT(ENOSPC); + + error = copyout(packed, nv->data, nv->len); + +#undef ERROUT +DIOCGETETHRULES_error: + free(packed, M_TEMP); + nvlist_destroy(nvl); + break; + } + + case DIOCGETETHRULE: { + struct epoch_tracker et; + struct pfioc_nv *nv = (struct pfioc_nv *)addr; + nvlist_t *nvl = NULL; + void *nvlpacked = NULL; + struct pf_keth_rule *rule = NULL; + u_int32_t ticket, nr; + +#define ERROUT(x) do { error = (x); goto DIOCGETETHRULE_error; } while (0) + + nvlpacked = malloc(nv->len, M_TEMP, M_WAITOK); + if (nvlpacked == NULL) + ERROUT(ENOMEM); + + error = copyin(nv->data, nvlpacked, nv->len); + if (error) + ERROUT(error); + + nvl = nvlist_unpack(nvlpacked, nv->len, 0); + if (! nvlist_exists_number(nvl, "ticket")) + ERROUT(EBADMSG); + ticket = nvlist_get_number(nvl, "ticket"); + + if (! nvlist_exists_number(nvl, "nr")) + ERROUT(EBADMSG); + nr = nvlist_get_number(nvl, "nr"); + + nvlist_destroy(nvl); + nvl = NULL; + free(nvlpacked, M_TEMP); + nvlpacked = NULL; + + nvl = nvlist_create(0); + + PF_RULES_RLOCK(); + if (ticket != V_pf_keth->ticket) { + PF_RULES_RUNLOCK(); + ERROUT(EBUSY); + } + rule = TAILQ_FIRST(&V_pf_keth->rules); + while ((rule != NULL) && (rule->nr != nr)) + rule = TAILQ_NEXT(rule, entries); + if (rule == NULL) { + PF_RULES_RUNLOCK(); + ERROUT(ENOENT); + } + /* Make sure rule can't go away. */ + NET_EPOCH_ENTER(et); + PF_RULES_RUNLOCK(); + nvl = pf_keth_rule_to_nveth_rule(rule); + NET_EPOCH_EXIT(et); + if (nvl == NULL) + ERROUT(ENOMEM); + + nvlpacked = nvlist_pack(nvl, &nv->len); + if (nvlpacked == NULL) + ERROUT(ENOMEM); + + if (nv->size == 0) + ERROUT(0); + else if (nv->size < nv->len) + ERROUT(ENOSPC); + + error = copyout(nvlpacked, nv->data, nv->len); + +#undef ERROUT +DIOCGETETHRULE_error: + free(nvlpacked, M_TEMP); + nvlist_destroy(nvl); + break; + } + + case DIOCADDETHRULE: { + struct pfioc_nv *nv = (struct pfioc_nv *)addr; + nvlist_t *nvl = NULL; + void *nvlpacked = NULL; + struct pf_keth_rule *rule = NULL; + struct pfi_kkif *kif = NULL; + +#define ERROUT(x) do { error = (x); goto DIOCADDETHRULE_error; } while (0) + + nvlpacked = malloc(nv->len, M_TEMP, M_WAITOK); + if (nvlpacked == NULL) + ERROUT(ENOMEM); + + error = copyin(nv->data, nvlpacked, nv->len); + if (error) + ERROUT(error); + + nvl = nvlist_unpack(nvlpacked, nv->len, 0); + if (nvl == NULL) + ERROUT(EBADMSG); + + if (! nvlist_exists_number(nvl, "ticket")) + ERROUT(EBADMSG); + + if (nvlist_get_number(nvl, "ticket") != + V_pf_keth_inactive->ticket) { + DPFPRINTF(PF_DEBUG_MISC, + ("ticket: %d != %d\n", + (u_int32_t)nvlist_get_number(nvl, "ticket"), + V_pf_keth_inactive->ticket)); + ERROUT(EBUSY); + } + + rule = malloc(sizeof(*rule), M_PFRULE, M_WAITOK); + if (rule == NULL) + ERROUT(ENOMEM); + + error = pf_nveth_rule_to_keth_rule(nvl, rule); + if (error != 0) + ERROUT(error); + + if (rule->ifname[0]) + kif = pf_kkif_create(M_WAITOK); + rule->evaluations = counter_u64_alloc(M_WAITOK); + for (int i = 0; i < 2; i++) { + rule->packets[i] = counter_u64_alloc(M_WAITOK); + rule->bytes[i] = counter_u64_alloc(M_WAITOK); + } + + PF_RULES_WLOCK(); + + if (rule->ifname[0]) { + rule->kif = pfi_kkif_attach(kif, rule->ifname); + pfi_kkif_ref(rule->kif); + } else + rule->kif = NULL; + +#ifdef ALTQ + /* set queue IDs */ + if (rule->qname[0] != 0) { + if ((rule->qid = pf_qname2qid(rule->qname)) == 0) + error = EBUSY; + else + rule->qid = rule->qid; + } +#endif + if (rule->tagname[0]) + if ((rule->tag = pf_tagname2tag(rule->tagname)) == 0) + error = EBUSY; + + if (error) { + pf_free_eth_rule(rule); + PF_RULES_WUNLOCK(); + ERROUT(error); + } + + TAILQ_INSERT_TAIL(&V_pf_keth_inactive->rules, rule, entries); + + PF_RULES_WUNLOCK(); + +#undef ERROUT +DIOCADDETHRULE_error: + nvlist_destroy(nvl); + free(nvlpacked, M_TEMP); + break; + } + case DIOCADDRULENV: { struct pfioc_nv *nv = (struct pfioc_nv *)addr; nvlist_t *nvl = NULL; @@ -4358,6 +4703,19 @@ for (i = 0, ioe = ioes; i < io->size; i++, ioe++) { ioe->anchor[sizeof(ioe->anchor) - 1] = '\0'; switch (ioe->rs_num) { + case PF_RULESET_ETH: + if (ioe->anchor[0]) { + PF_RULES_WUNLOCK(); + free(ioes, M_TEMP); + error = EINVAL; + goto fail; + } + if ((error = pf_begin_eth(&ioe->ticket))) { + PF_RULES_WUNLOCK(); + free(ioes, M_TEMP); + goto fail; + } + break; #ifdef ALTQ case PF_RULESET_ALTQ: if (ioe->anchor[0]) { @@ -4432,6 +4790,19 @@ for (i = 0, ioe = ioes; i < io->size; i++, ioe++) { ioe->anchor[sizeof(ioe->anchor) - 1] = '\0'; switch (ioe->rs_num) { + case PF_RULESET_ETH: + if (ioe->anchor[0]) { + PF_RULES_WUNLOCK(); + free(ioes, M_TEMP); + error = EINVAL; + goto fail; + } + if ((error = pf_rollback_eth(ioe->ticket))) { + PF_RULES_WUNLOCK(); + free(ioes, M_TEMP); + goto fail; /* really bad */ + } + break; #ifdef ALTQ case PF_RULESET_ALTQ: if (ioe->anchor[0]) { @@ -4509,6 +4880,21 @@ for (i = 0, ioe = ioes; i < io->size; i++, ioe++) { ioe->anchor[sizeof(ioe->anchor) - 1] = 0; switch (ioe->rs_num) { + case PF_RULESET_ETH: + if (ioe->anchor[0]) { + PF_RULES_WUNLOCK(); + free(ioes, M_TEMP); + error = EINVAL; + goto fail; + } + if (!V_pf_keth_inactive->ticket || + ioe->ticket != V_pf_keth_inactive->ticket) { + PF_RULES_WUNLOCK(); + free(ioes, M_TEMP); + error = EBUSY; + goto fail; + } + break; #ifdef ALTQ case PF_RULESET_ALTQ: if (ioe->anchor[0]) { @@ -4560,6 +4946,13 @@ /* Now do the commit - no errors should happen here. */ for (i = 0, ioe = ioes; i < io->size; i++, ioe++) { switch (ioe->rs_num) { + case PF_RULESET_ETH: + if ((error = pf_commit_eth(ioe->ticket))) { + PF_RULES_WUNLOCK(); + free(ioes, M_TEMP); + goto fail; /* really bad */ + } + break; #ifdef ALTQ case PF_RULESET_ALTQ: if ((error = pf_commit_altq(ioe->ticket))) { @@ -5498,6 +5891,12 @@ if ((error = pf_clear_tables()) != 0) break; + if ((error = pf_begin_eth(&t[0])) != 0) { + DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: eth\n")); + break; + } + pf_commit_eth(t[0]); + #ifdef ALTQ if ((error = pf_begin_altq(&t[0])) != 0) { DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: ALTQ\n")); @@ -5537,6 +5936,28 @@ } } +static pfil_return_t +pf_eth_check_in(struct mbuf **m, struct ifnet *ifp, int flags, + void *ruleset __unused, struct inpcb *inp) +{ + int chk; + + chk = pf_test_eth(PF_IN, flags, ifp, m, inp); + + return (pf_check_return(chk, m)); +} + +static pfil_return_t +pf_eth_check_out(struct mbuf **m, struct ifnet *ifp, int flags, + void *ruleset __unused, struct inpcb *inp) +{ + int chk; + + chk = pf_test_eth(PF_OUT, flags, ifp, m, inp); + + return (pf_check_return(chk, m)); +} + #ifdef INET static pfil_return_t pf_check_in(struct mbuf **m, struct ifnet *ifp, int flags, @@ -5594,6 +6015,11 @@ } #endif /* INET6 */ +VNET_DEFINE_STATIC(pfil_hook_t, pf_eth_in_hook); +VNET_DEFINE_STATIC(pfil_hook_t, pf_eth_out_hook); +#define V_pf_eth_in_hook VNET(pf_eth_in_hook) +#define V_pf_eth_out_hook VNET(pf_eth_out_hook) + #ifdef INET VNET_DEFINE_STATIC(pfil_hook_t, pf_ip4_in_hook); VNET_DEFINE_STATIC(pfil_hook_t, pf_ip4_out_hook); @@ -5623,6 +6049,24 @@ pla.pa_version = PFIL_VERSION; + pha.pa_type = PFIL_TYPE_ETHERNET; + pha.pa_func = pf_eth_check_in; + pha.pa_flags = PFIL_IN; + pha.pa_rulname = "eth-in"; + V_pf_eth_in_hook = pfil_add_hook(&pha); + pla.pa_flags = PFIL_IN | PFIL_HEADPTR | PFIL_HOOKPTR; + pla.pa_head = V_link_pfil_head; + pla.pa_hook = V_pf_eth_in_hook; + (void)pfil_link(&pla); + pha.pa_func = pf_eth_check_out; + pha.pa_flags = PFIL_OUT; + pha.pa_rulname = "eth-out"; + V_pf_eth_out_hook = pfil_add_hook(&pha); + pla.pa_flags = PFIL_OUT | PFIL_HEADPTR | PFIL_HOOKPTR; + pla.pa_head = V_link_pfil_head; + pla.pa_hook = V_pf_eth_out_hook; + (void)pfil_link(&pla); + #ifdef INET pha.pa_type = PFIL_TYPE_IP4; pha.pa_func = pf_check_in; @@ -5676,6 +6120,9 @@ if (V_pf_pfil_hooked == 0) return; + pfil_remove_hook(V_pf_eth_in_hook); + pfil_remove_hook(V_pf_eth_out_hook); + #ifdef INET pfil_remove_hook(V_pf_ip4_in_hook); pfil_remove_hook(V_pf_ip4_out_hook); @@ -5701,6 +6148,10 @@ PF_QUEUE_TAG_HASH_SIZE_DEFAULT); #endif + V_pf_keth = malloc(sizeof(*V_pf_keth), M_PFRULE, M_WAITOK); + V_pf_keth_inactive = malloc(sizeof(*V_pf_keth_inactive), + M_PFRULE, M_WAITOK); + pfattach_vnet(); V_pf_vnet_active = 1; } @@ -5810,6 +6261,9 @@ pf_counter_u64_deinit(&V_pf_status.fcounters[i]); for (int i = 0; i < SCNT_MAX; i++) counter_u64_free(V_pf_status.scounters[i]); + + free(V_pf_keth, M_PFRULE); + free(V_pf_keth_inactive, M_PFRULE); } static void Index: sys/netpfil/pf/pf_nv.h =================================================================== --- sys/netpfil/pf/pf_nv.h +++ sys/netpfil/pf/pf_nv.h @@ -84,4 +84,6 @@ struct pf_kstate_kill *); nvlist_t *pf_state_to_nvstate(const struct pf_kstate *); +nvlist_t *pf_keth_rule_to_nveth_rule(const struct pf_keth_rule *); +int pf_nveth_rule_to_keth_rule(const nvlist_t *, struct pf_keth_rule *); #endif Index: sys/netpfil/pf/pf_nv.c =================================================================== --- sys/netpfil/pf/pf_nv.c +++ sys/netpfil/pf/pf_nv.c @@ -986,3 +986,127 @@ nvlist_destroy(nvl); return (NULL); } + +static int +pf_nveth_rule_addr_to_keth_rule_addr(const nvlist_t *nvl, + struct pf_keth_rule_addr *krule) +{ + static const u_int8_t EMPTY_MAC[ETHER_ADDR_LEN] = { 0 }; + int error = 0; + + PFNV_CHK(pf_nvbinary(nvl, "addr", &krule->addr, sizeof(krule->addr))); + PFNV_CHK(pf_nvbool(nvl, "neg", &krule->neg)); + + /* To make checks for 'is this address set?' easier. */ + if (memcmp(krule->addr, EMPTY_MAC, ETHER_ADDR_LEN) != 0) + krule->isset = 1; + +errout: + return (error); +} + +static nvlist_t* +pf_keth_rule_addr_to_nveth_rule_addr(const struct pf_keth_rule_addr *krule) +{ + nvlist_t *nvl; + + nvl = nvlist_create(0); + if (nvl == NULL) + return (NULL); + + nvlist_add_binary(nvl, "addr", &krule->addr, sizeof(krule->addr)); + nvlist_add_bool(nvl, "neg", krule->neg); + + return (nvl); +} + +nvlist_t* +pf_keth_rule_to_nveth_rule(const struct pf_keth_rule *krule) +{ + nvlist_t *nvl, *addr; + + nvl = nvlist_create(0); + if (nvl == NULL) + return (NULL); + + nvlist_add_number(nvl, "nr", krule->nr); + nvlist_add_bool(nvl, "quick", krule->quick); + nvlist_add_string(nvl, "ifname", krule->ifname); + nvlist_add_bool(nvl, "ifnot", krule->ifnot); + nvlist_add_number(nvl, "direction", krule->direction); + nvlist_add_number(nvl, "proto", krule->proto); + + addr = pf_keth_rule_addr_to_nveth_rule_addr(&krule->src); + if (addr == NULL) { + nvlist_destroy(nvl); + return (NULL); + } + nvlist_add_nvlist(nvl, "src", addr); + + addr = pf_keth_rule_addr_to_nveth_rule_addr(&krule->dst); + if (addr == NULL) { + nvlist_destroy(nvl); + return (NULL); + } + nvlist_add_nvlist(nvl, "dst", addr); + + nvlist_add_number(nvl, "evaluations", + counter_u64_fetch(krule->evaluations)); + nvlist_add_number(nvl, "packets-in", + counter_u64_fetch(krule->packets[0])); + nvlist_add_number(nvl, "packets-out", + counter_u64_fetch(krule->packets[1])); + nvlist_add_number(nvl, "bytes-in", + counter_u64_fetch(krule->bytes[0])); + nvlist_add_number(nvl, "bytes-out", + counter_u64_fetch(krule->bytes[1])); + + nvlist_add_string(nvl, "qname", krule->qname); + nvlist_add_string(nvl, "tagname", krule->tagname); + + nvlist_add_number(nvl, "action", krule->action); + + return (nvl); +} + +int +pf_nveth_rule_to_keth_rule(const nvlist_t *nvl, + struct pf_keth_rule *krule) +{ + int error; + + bzero(krule, sizeof(*krule)); + + PFNV_CHK(pf_nvuint32(nvl, "nr", &krule->nr)); + PFNV_CHK(pf_nvbool(nvl, "quick", &krule->quick)); + PFNV_CHK(pf_nvstring(nvl, "ifname", krule->ifname, + sizeof(krule->ifname))); + PFNV_CHK(pf_nvbool(nvl, "ifnot", &krule->ifnot)); + PFNV_CHK(pf_nvuint8(nvl, "direction", &krule->direction)); + PFNV_CHK(pf_nvuint16(nvl, "proto", &krule->proto)); + + if (nvlist_exists_nvlist(nvl, "src")) { + error = pf_nveth_rule_addr_to_keth_rule_addr( + nvlist_get_nvlist(nvl, "src"), &krule->src); + if (error) + return (error); + } + if (nvlist_exists_nvlist(nvl, "dst")) { + error = pf_nveth_rule_addr_to_keth_rule_addr( + nvlist_get_nvlist(nvl, "dst"), &krule->dst); + if (error) + return (error); + } + + PFNV_CHK(pf_nvstring(nvl, "qname", krule->qname, sizeof(krule->qname))); + PFNV_CHK(pf_nvstring(nvl, "tagname", krule->tagname, + sizeof(krule->tagname))); + + PFNV_CHK(pf_nvuint8(nvl, "action", &krule->action)); + + if (krule->action != PF_PASS && krule->action != PF_DROP) + return (EBADMSG); + +errout: + return (error); +} Index: sys/netpfil/pf/pf_ruleset.c =================================================================== --- sys/netpfil/pf/pf_ruleset.c +++ sys/netpfil/pf/pf_ruleset.c @@ -70,6 +70,8 @@ VNET_DEFINE(struct pf_kanchor_global, pf_anchors); VNET_DEFINE(struct pf_kanchor, pf_main_anchor); +VNET_DEFINE(struct pf_keth_settings*, pf_keth); +VNET_DEFINE(struct pf_keth_settings*, pf_keth_inactive); static __inline int pf_kanchor_compare(struct pf_kanchor *, struct pf_kanchor *); @@ -145,6 +147,15 @@ } } +void +pf_init_keth(struct pf_keth_settings *settings) +{ + + TAILQ_INIT(&settings->rules); + settings->ticket = 0; + settings->open = 0; +} + struct pf_kruleset * pf_find_kruleset(const char *path) {