This introduces the concept of "packet tags" to PF, so that one can mark a mbuf coming into the system and use that mark to permit it to pass out another inteface without an explicit rule to do so. Simply add "tag sufficient" to a packet on the input side, and then use "tagged sufficient" on the output side to let the packet out. This was implemented as a way to introduce compatibility with ipfilter and simplify a transition to pf, but could easily be made more versatile and have more than one "tag" name for more flexible usage (remove hardcoded "sufficient" name and store a tag id in the mbuf tag). Chris Pascoe 2003/04/28 Index: sys/net/pf.c =================================================================== RCS file: /cvs/src/sys/net/pf.c,v retrieving revision 1.338 diff -u -r1.338 pf.c --- sys/net/pf.c 2003/04/25 17:41:25 1.338 +++ sys/net/pf.c 2003/04/28 05:29:21 @@ -1912,6 +1912,8 @@ struct pf_ruleset *ruleset = NULL; u_short reason; int rewrite = 0; + struct m_tag *sufficient_tag = NULL; + u_int8_t sufficient_state = 0; if (direction == PF_OUT) { bport = nport = th->th_sport; @@ -1977,6 +1979,9 @@ !pf_match_gid(r->gid.op, r->gid.gid[0], r->gid.gid[1], gid)) r = TAILQ_NEXT(r, entries); + else if (r->sufficient && r->direction == PF_OUT && + (sufficient_tag = m_tag_find(m, PACKET_TAG_PF_SUFFICIENT, NULL)) == NULL) + r = TAILQ_NEXT(r, entries); else if (r->anchorname[0] && r->anchor == NULL) r = TAILQ_NEXT(r, entries); else { @@ -2040,7 +2045,25 @@ if (r->action == PF_DROP) return (PF_DROP); - if (r->keep_state || nat != NULL || rdr != NULL) { + if (r->sufficient) { + if (direction == PF_IN) { + /* + * Tag this packet so it can be identified on its output path, + * recording if it should generate a state entry. + */ + sufficient_tag = m_tag_get(PACKET_TAG_PF_SUFFICIENT, sizeof(u_int8_t), M_NOWAIT); + if (sufficient_tag == NULL) { + REASON_SET(&reason, PFRES_MEMORY); + return (PF_DROP); + } + *(u_int8_t *)(sufficient_tag + 1) = r->keep_state; + m_tag_prepend(m, sufficient_tag); + } else if (direction == PF_OUT) { + sufficient_state = *(u_int8_t *)(sufficient_tag + 1); + } + } + + if (r->keep_state || nat != NULL || rdr != NULL || sufficient_state) { /* create new state */ u_int16_t len; struct pf_state *s = NULL; @@ -2099,7 +2122,8 @@ s->src.seqlo = ntohl(th->th_seq); s->src.seqhi = s->src.seqlo + len + 1; if ((th->th_flags & (TH_SYN|TH_ACK)) == TH_SYN && - r->keep_state == PF_STATE_MODULATE) { + (r->keep_state == PF_STATE_MODULATE || + sufficient_state == PF_STATE_MODULATE)) { /* Generate sequence number modulator */ while ((s->src.seqdiff = arc4random()) == 0) ; @@ -2158,6 +2182,8 @@ struct pf_ruleset *ruleset = NULL; u_short reason; int rewrite = 0; + struct m_tag *sufficient_tag = NULL; + u_int8_t sufficient_state = 0; if (direction == PF_OUT) { bport = nport = uh->uh_sport; @@ -2221,6 +2247,9 @@ !pf_match_gid(r->gid.op, r->gid.gid[0], r->gid.gid[1], gid)) r = TAILQ_NEXT(r, entries); + else if (r->sufficient && r->direction == PF_OUT && + (sufficient_tag = m_tag_find(m, PACKET_TAG_PF_SUFFICIENT, NULL)) == NULL) + r = TAILQ_NEXT(r, entries); else if (r->anchorname[0] && r->anchor == NULL) r = TAILQ_NEXT(r, entries); else { @@ -2279,7 +2308,25 @@ if (r->action == PF_DROP) return (PF_DROP); - if (r->keep_state || nat != NULL || rdr != NULL) { + if (r->sufficient) { + if (direction == PF_IN) { + /* + * Tag this packet so it can be identified on its output path, + * recording if it should generate a state entry. + */ + sufficient_tag = m_tag_get(PACKET_TAG_PF_SUFFICIENT, sizeof(u_int8_t), M_NOWAIT); + if (sufficient_tag == NULL) { + REASON_SET(&reason, PFRES_MEMORY); + return (PF_DROP); + } + *(u_int8_t *)(sufficient_tag + 1) = r->keep_state; + m_tag_prepend(m, sufficient_tag); + } else if (direction == PF_OUT) { + sufficient_state = *(u_int8_t *)(sufficient_tag + 1); + } + } + + if (r->keep_state || nat != NULL || rdr != NULL || sufficient_state) { /* create new state */ struct pf_state *s = NULL; @@ -2377,6 +2424,8 @@ #ifdef INET6 int rewrite = 0; #endif /* INET6 */ + struct m_tag *sufficient_tag = NULL; + u_int8_t sufficient_state = 0; switch (pd->proto) { #ifdef INET @@ -2476,6 +2525,9 @@ r = TAILQ_NEXT(r, entries); else if (r->rule_flag & PFRULE_FRAGMENT) r = TAILQ_NEXT(r, entries); + else if (r->sufficient && r->direction == PF_OUT && + (sufficient_tag = m_tag_find(m, PACKET_TAG_PF_SUFFICIENT, NULL)) == NULL) + r = TAILQ_NEXT(r, entries); else if (r->anchorname[0] && r->anchor == NULL) r = TAILQ_NEXT(r, entries); else { @@ -2516,8 +2568,26 @@ if (r->action != PF_PASS) return (PF_DROP); + if (r->sufficient) { + if (direction == PF_IN) { + /* + * Tag this packet so it can be identified on its output path, + * recording if it should generate a state entry. + */ + sufficient_tag = m_tag_get(PACKET_TAG_PF_SUFFICIENT, sizeof(u_int8_t), M_NOWAIT); + if (sufficient_tag == NULL) { + REASON_SET(&reason, PFRES_MEMORY); + return (PF_DROP); + } + *(u_int8_t *)(sufficient_tag + 1) = r->keep_state; + m_tag_prepend(m, sufficient_tag); + } else if (direction == PF_OUT) { + sufficient_state = *(u_int8_t *)(sufficient_tag + 1); + } + } + if (!state_icmp && (r->keep_state || - nat != NULL || rdr != NULL)) { + nat != NULL || rdr != NULL || sufficient_state)) { /* create new state */ struct pf_state *s = NULL; @@ -2608,6 +2678,8 @@ struct pf_addr baddr, naddr; sa_family_t af = pd->af; u_short reason; + struct m_tag *sufficient_tag = NULL; + u_int8_t sufficient_state = 0; if (direction == PF_OUT) { /* check outgoing packet for BINAT/NAT */ @@ -2668,6 +2740,9 @@ r = TAILQ_NEXT(r, entries); else if (r->rule_flag & PFRULE_FRAGMENT) r = TAILQ_NEXT(r, entries); + else if (r->sufficient && r->direction == PF_OUT && + (sufficient_tag = m_tag_find(m, PACKET_TAG_PF_SUFFICIENT, NULL)) == NULL) + r = TAILQ_NEXT(r, entries); else if (r->anchorname[0] && r->anchor == NULL) r = TAILQ_NEXT(r, entries); else { @@ -2700,8 +2775,26 @@ if (r->action != PF_PASS) return (PF_DROP); + + if (r->sufficient) { + if (direction == PF_IN) { + /* + * Tag this packet so it can be identified on its output path, + * recording if it should generate a state entry. + */ + sufficient_tag = m_tag_get(PACKET_TAG_PF_SUFFICIENT, sizeof(u_int8_t), M_NOWAIT); + if (sufficient_tag == NULL) { + REASON_SET(&reason, PFRES_MEMORY); + return (PF_DROP); + } + *(u_int8_t *)(sufficient_tag + 1) = r->keep_state; + m_tag_prepend(m, sufficient_tag); + } else if (direction == PF_OUT) { + sufficient_state = *(u_int8_t *)(sufficient_tag + 1); + } + } - if (r->keep_state || nat != NULL || rdr != NULL) { + if (r->keep_state || nat != NULL || rdr != NULL || sufficient_state) { /* create new state */ struct pf_state *s = NULL; @@ -2784,6 +2877,7 @@ struct pf_ruleset *ruleset = NULL; sa_family_t af = pd->af; u_short reason; + struct m_tag *sufficient_tag = NULL; r = TAILQ_FIRST(pf_main_ruleset.rules[PF_RULESET_FILTER].active.ptr); while (r != NULL) { @@ -2806,6 +2900,9 @@ else if (r->src.port_op || r->dst.port_op || r->flagset || r->type || r->code) r = TAILQ_NEXT(r, entries); + else if (r->sufficient && r->direction == PF_OUT && + (sufficient_tag = m_tag_find(m, PACKET_TAG_PF_SUFFICIENT, NULL)) == NULL) + r = TAILQ_NEXT(r, entries); else if (r->anchorname[0] && r->anchor == NULL) r = TAILQ_NEXT(r, entries); else { @@ -2838,6 +2935,19 @@ if (r->action != PF_PASS) return (PF_DROP); + + if (r->sufficient) { + if (direction == PF_IN) { + /* Tag this packet so it can be identified on its output path. */ + sufficient_tag = m_tag_get(PACKET_TAG_PF_SUFFICIENT, sizeof(u_int8_t), M_NOWAIT); + if (sufficient_tag == NULL) { + REASON_SET(&reason, PFRES_MEMORY); + return (PF_DROP); + } + *(u_int8_t *)(sufficient_tag + 1) = r->keep_state; + m_tag_prepend(m, sufficient_tag); + } + } return (PF_PASS); } Index: sys/net/pfvar.h =================================================================== RCS file: /cvs/src/sys/net/pfvar.h,v retrieving revision 1.141 diff -u -r1.141 pfvar.h --- sys/net/pfvar.h 2003/04/27 16:02:08 1.141 +++ sys/net/pfvar.h 2003/04/28 05:29:26 @@ -385,6 +385,8 @@ u_int8_t rt; u_int8_t return_ttl; u_int8_t tos; + + u_int8_t sufficient; }; #define PFRULE_DROP 0x00 Index: sys/sys/mbuf.h =================================================================== RCS file: /cvs/src/sys/sys/mbuf.h,v retrieving revision 1.68 diff -u -r1.68 mbuf.h --- sys/sys/mbuf.h 2003/02/12 14:41:08 1.68 +++ sys/sys/mbuf.h 2003/04/28 05:29:31 @@ -598,6 +598,7 @@ #define PACKET_TAG_PF_ROUTED 12 /* PF routed, no route loops */ #define PACKET_TAG_PF_FRAGCACHE 13 /* PF fragment cached */ #define PACKET_TAG_PF_QID 14 /* PF queue id */ +#define PACKET_TAG_PF_SUFFICIENT 15 /* PF rule tagged sufficient */ #ifdef MBTYPES int mbtypes[] = { /* XXX */ Index: sbin/pfctl/parse.y =================================================================== RCS file: /cvs/src/sbin/pfctl/parse.y,v retrieving revision 1.368 diff -u -r1.368 parse.y --- sbin/pfctl/parse.y 2003/04/25 17:36:33 1.368 +++ sbin/pfctl/parse.y 2003/04/28 05:29:38 @@ -169,6 +169,7 @@ int allowopts; char *label; struct node_qassign queues; + int sufficient; } filter_opts; struct scrub_opts { @@ -348,6 +349,7 @@ %token BITMASK RANDOM SOURCEHASH ROUNDROBIN STATICPORT %token ALTQ CBQ PRIQ HFSC BANDWIDTH TBRSIZE LINKSHARE REALTIME UPPERLIMIT %token QUEUE PRIORITY QLIMIT +%token TAG TAGGED SUFFICIENT %token STRING %token PORTBINARY %type interface if_list if_item_not if_item @@ -1229,6 +1231,7 @@ if ($9.fragment) r.rule_flag |= PFRULE_FRAGMENT; r.allow_opts = $9.allowopts; + r.sufficient = $9.sufficient; decide_address_family($8.src.host, &r.af); decide_address_family($8.dst.host, &r.af); @@ -1367,6 +1370,12 @@ | ALLOWOPTS { filter_opts.allowopts = 1; } + | TAG SUFFICIENT { + filter_opts.sufficient = PF_IN; + } + | TAGGED SUFFICIENT { + filter_opts.sufficient = PF_OUT; + } | label { if (filter_opts.label) { yyerror("label cannot be redefined"); @@ -2847,6 +2856,18 @@ yyerror("modulate state can only be applied to TCP rules"); problems++; } + if (r->sufficient == PF_IN && r->direction != PF_IN) { + yyerror("tag sufficient only makes sense on in rules"); + problems++; + } + if (r->sufficient == PF_OUT && r->direction != PF_OUT) { + yyerror("tagged sufficient only makes sense on out rules"); + problems++; + } + if (r->sufficient == PF_OUT && r->keep_state) { + yyerror("tagged sufficient inherits state from tag rule"); + problems++; + } if (r->allow_opts && r->action != PF_PASS) { yyerror("allow-opts can only be specified for pass rules"); problems++; @@ -3606,7 +3627,10 @@ { "source-hash", SOURCEHASH}, { "state", STATE}, { "static-port", STATICPORT}, + { "sufficient", SUFFICIENT}, { "table", TABLE}, + { "tag", TAG}, + { "tagged", TAGGED}, { "tbrsize", TBRSIZE}, { "timeout", TIMEOUT}, { "to", TO}, Index: sbin/pfctl/pfctl_parser.c =================================================================== RCS file: /cvs/src/sbin/pfctl/pfctl_parser.c,v retrieving revision 1.151 diff -u -r1.151 pfctl_parser.c --- sbin/pfctl/pfctl_parser.c 2003/04/25 19:44:57 1.151 +++ sbin/pfctl/pfctl_parser.c 2003/04/28 05:29:44 @@ -696,6 +696,10 @@ printf("keep state "); else if (r->keep_state == PF_STATE_MODULATE) printf("modulate state "); + if (r->sufficient == PF_IN) + printf("tag sufficient "); + else if (r->sufficient == PF_OUT) + printf("tagged sufficient "); opts = 0; if (r->max_states) opts = 1;