Index: usr.sbin/pfctl/parse.y =================================================================== RCS file: /cvs/src/usr.sbin/pfctl/parse.y,v retrieving revision 1.4 diff -u -p -r1.4 parse.y --- usr.sbin/pfctl/parse.y 6 Apr 2008 21:12:40 -0000 1.4 +++ usr.sbin/pfctl/parse.y 7 Apr 2008 16:50:49 -0000 @@ -121,7 +121,8 @@ }; enum { PF_STATE_OPT_MAX, PF_STATE_OPT_NOSYNC, PF_STATE_OPT_SRCTRACK, PF_STATE_OPT_MAX_SRC_STATES, PF_STATE_OPT_MAX_SRC_NODES, - PF_STATE_OPT_STATELOCK, PF_STATE_OPT_TIMEOUT }; + PF_STATE_OPT_STATELOCK, PF_STATE_OPT_TIMEOUT, + PF_STATE_OPT_PICKUPS }; enum { PF_SRCTRACK_NONE, PF_SRCTRACK, PF_SRCTRACK_GLOBAL, PF_SRCTRACK_RULE }; @@ -132,6 +133,7 @@ u_int32_t max_states; u_int32_t max_src_states; u_int32_t max_src_nodes; u_int8_t src_track; + u_int8_t pickup_mode; u_int32_t statelock; struct { int number; @@ -410,6 +412,7 @@ %token BITMASK RANDOM SOURCEHASH ROUNDRO %token ALTQ CBQ PRIQ HFSC FAIRQ BANDWIDTH TBRSIZE LINKSHARE REALTIME UPPERLIMIT %token QUEUE PRIORITY QLIMIT HOGS BUCKETS %token LOAD +%token PICKUPS NOPICKUPS HASHONLY %token STICKYADDRESS MAXSRCSTATES MAXSRCNODES SOURCETRACK GLOBAL RULE %token TAGGED TAG IFBOUND GRBOUND FLOATING STATEPOLICY %token STRING @@ -1681,10 +1684,38 @@ YYERROR; } r.timeout[o->data.timeout.number] = o->data.timeout.seconds; + break; + case PF_STATE_OPT_PICKUPS: + r.pickup_mode = o->data.pickup_mode; + break; } o = o->next; free(p); } + + /* + * 'flags S/SA' by default on stateful rules if + * the pickup mode is unspecified or disabled. + * + * If the pickup mode is enabled or hashonly we + * want to create state regardless of the flags. + */ + if (!r.action && !r.flags && !r.flagset && + !$9.fragment && !($9.marker & FOM_FLAGS) && + r.keep_state) { + switch(r.pickup_mode) { + case PF_PICKUPS_UNSPECIFIED: + case PF_PICKUPS_DISABLED: + r.flags = parse_flags("S"); + r.flagset = parse_flags("SA"); + break; + case PF_PICKUPS_HASHONLY: + case PF_PICKUPS_ENABLED: + /* no flag restrictions */ + break; + } + } + if (srctrack) { if (srctrack == PF_SRCTRACK_GLOBAL && r.max_src_nodes) { @@ -2828,6 +2859,33 @@ $$->data.max_src_nodes = $2; $$->next = NULL; $$->tail = $$; } + | PICKUPS { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_PICKUPS; + $$->data.pickup_mode = PF_PICKUPS_ENABLED; + $$->next = NULL; + $$->tail = $$; + } + | NOPICKUPS { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_PICKUPS; + $$->data.pickup_mode = PF_PICKUPS_DISABLED; + $$->next = NULL; + $$->tail = $$; + } + | HASHONLY { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_PICKUPS; + $$->data.pickup_mode = PF_PICKUPS_HASHONLY; + $$->next = NULL; + $$->tail = $$; + } | sourcetrack { $$ = calloc(1, sizeof(struct node_state_opt)); if ($$ == NULL) @@ -4425,6 +4483,7 @@ { "from", FROM}, { "global", GLOBAL}, { "group", GROUP}, { "group-bound", GRBOUND}, + { "hash-only", HASHONLY}, { "hfsc", HFSC}, { "hogs", HOGS}, { "hostid", HOSTID}, @@ -4452,6 +4511,7 @@ { "nat", NAT}, { "nat-anchor", NATANCHOR}, { "no", NO}, { "no-df", NODF}, + { "no-pickups", NOPICKUPS}, { "no-route", NOROUTE}, { "no-sync", NOSYNC}, { "on", ON}, @@ -4459,6 +4519,7 @@ { "optimization", OPTIMIZATION}, { "os", OS}, { "out", OUT}, { "pass", PASS}, + { "pickups", PICKUPS}, { "port", PORT}, { "priority", PRIORITY}, { "priq", PRIQ}, Index: usr.sbin/pfctl/pf.conf.5 =================================================================== RCS file: /cvs/src/usr.sbin/pfctl/pf.conf.5,v retrieving revision 1.12 diff -u -p -r1.12 pf.conf.5 --- usr.sbin/pfctl/pf.conf.5 6 Apr 2008 21:12:40 -0000 1.12 +++ usr.sbin/pfctl/pf.conf.5 7 Apr 2008 17:33:53 -0000 @@ -718,6 +718,11 @@ .Ar fairq to function properly, .Ar keep state must be enabled on most of the rule sets that route packets to the queue. +Any rules for which keep state is not enabled are added to the end of the +queue. If you do not wish keep state to do TCP sequence space checks use +.Ar keep state ( no-pickups ) +or +.Ar keep state ( hash-only ) . .Pp Packet selection operates as follows: The queues are scanned from highest priority to lowest priority. @@ -1966,7 +1971,35 @@ have state table entries. .It Ar max-src-states Limits the maximum number of simultaneous state entries that a single source address can create with this rule. +.It Ar pickups +Specify that mid-stream pickups are to be allowed. The default +is to NOT allow mid-stream pickups and implies flags S/SA for TCP +connections. If pickups are enabled, flags S/SA are not implied +for TCP connections and state can be created for any packet. +.Pp +The implied flags parameters need not be specified in either case +unless you explicitly wish to override them, which also allows +you to roll-up several protocols into a single rule. +.Pp +Certain validations are disabled when mid-stream pickups occur. +For example, the window scaling options are not known for +TCP pickups and sequence space comparisons must be disabled. +.Pp +This does not effect state representing fully quantified +connections (for which the SYN/SYN-ACK passed through the routing +engine). Those connections continue to be fully validated. +.It Ar hash-only +Specify that mid-stream pickups are to be allowed, but unconditionally +disables sequence space checks even if full state is available. +.It Ar no-pickups +Specify that mid-stream pickups are not to be allowed. This is the +default and this keyword does not normally need to be specified. +However, if you are concerned about rule set portability then +specifying this keyword will at least result in an error from pfctl +if it doesn't understand the feature. TCP flags of S/SA are implied +and do not need to explicitly specified. .El +.Pp For a list of all valid timeout names, see .Sx OPTIONS above. @@ -2501,6 +2534,24 @@ # a viral worm. Alternately we could lim block in on $ext_if proto tcp from any os {"Windows 95", "Windows 98"} \e to any port smtp +# Using the pickup options to keep/modulate/synproxy state +# +# no-pickups (default) Do not allow connections to be picked up in the +# middle. Implies flags S/SA (the 'no-pickups' option need +# not be specified, it is the default). +# +# pickups Allow connections to be picked up in the middle, even if +# no window scaling information is known. Such connections +# will disable sequence space checks. Implies no flag +# restrictions. +# +# hash-only Do not fail packets on sequence space checks. Implies no +# flag restrictions. + +pass in on $ext_if proto tcp ... keep state (no-pickups) +pass in on $ext_if proto tcp ... keep state (pickups) +pass in on $ext_if proto tcp ... keep state (hash-only) + # Packet Tagging # three interfaces: $int_if, $ext_if, and $wifi_if (wireless). NAT is @@ -2678,7 +2729,8 @@ state-opts = state-opt [ [ "," ] sta state-opt = ( "max" number | "no-sync" | timeout | "source-track" [ ( "rule" | "global" ) ] | "max-src-nodes" number | "max-src-states" number | - "if-bound" | "group-bound" | "floating" ) + "if-bound" | "group-bound" | "floating" | + "pickups" | "no-pickups" | "hash-only" ) fragmentation = [ "fragment reassemble" | "fragment crop" | "fragment drop-ovl" ] Index: usr.sbin/pfctl/pf_print_state.c =================================================================== RCS file: /cvs/src/usr.sbin/pfctl/pf_print_state.c,v retrieving revision 1.2 diff -u -p -r1.2 pf_print_state.c --- usr.sbin/pfctl/pf_print_state.c 11 Feb 2005 22:31:45 -0000 1.2 +++ usr.sbin/pfctl/pf_print_state.c 7 Apr 2008 21:07:02 -0000 @@ -286,8 +286,19 @@ printf(", sticky-address"); printf("\n"); } if (opts & PF_OPT_VERBOSE2) { - printf(" id: %016llx creatorid: %08x\n", + printf(" id: %016llx creatorid: %08x", be64toh(s->id), ntohl(s->creatorid)); + + printf(" synchronization: "); + if ((s->sync_flags & PFSTATE_GOT_SYN_MASK) == + PFSTATE_GOT_SYN_MASK) { + printf("good"); + } else if (s->sync_flags & PFSTATE_GOT_SYN_MASK) { + printf("incomplete"); + } else { + printf("indeterminate"); + } + printf("\n"); } } Index: usr.sbin/pfctl/pfctl_parser.c =================================================================== RCS file: /cvs/src/usr.sbin/pfctl/pfctl_parser.c,v retrieving revision 1.2 diff -u -p -r1.2 pfctl_parser.c --- usr.sbin/pfctl/pfctl_parser.c 6 Apr 2008 21:12:40 -0000 1.2 +++ usr.sbin/pfctl/pfctl_parser.c 7 Apr 2008 15:12:08 -0000 @@ -946,9 +946,9 @@ printf("\n"); } int -parse_flags(char *s) +parse_flags(const char *s) { - char *p, *q; + const char *p, *q; u_int8_t f = 0; for (p = s; *p; p++) { Index: usr.sbin/pfctl/pfctl_parser.h =================================================================== RCS file: /cvs/src/usr.sbin/pfctl/pfctl_parser.h,v retrieving revision 1.2 diff -u -p -r1.2 pfctl_parser.h --- usr.sbin/pfctl/pfctl_parser.h 6 Apr 2008 18:58:14 -0000 1.2 +++ usr.sbin/pfctl/pfctl_parser.h 7 Apr 2008 15:11:53 -0000 @@ -204,7 +204,7 @@ int pfctl_set_hostid(struct pfctl *, u_i int pfctl_set_debug(struct pfctl *, char *); int parse_rules(FILE *, struct pfctl *); -int parse_flags(char *); +int parse_flags(const char *); int pfctl_load_anchors(int, int, struct pfr_buffer *); void print_pool(struct pf_pool *, u_int16_t, u_int16_t, sa_family_t, int); Index: sys/net/pf/if_pfsync.c =================================================================== RCS file: /cvs/src/sys/net/pf/if_pfsync.c,v retrieving revision 1.6 diff -u -p -r1.6 if_pfsync.c --- sys/net/pf/if_pfsync.c 22 Dec 2006 23:44:57 -0000 1.6 +++ sys/net/pf/if_pfsync.c 7 Apr 2008 00:45:50 -0000 @@ -225,6 +225,7 @@ pf_state_peer_ntoh(&sp->src, &st->src); pf_state_peer_ntoh(&sp->dst, &st->dst); bcopy(&sp->rt_addr, &st->rt_addr, sizeof(st->rt_addr)); + st->hash = pf_state_hash(st); st->creation = ntohl(sp->creation) + time_second; st->expire = ntohl(sp->expire) + time_second; @@ -932,6 +933,7 @@ pf_state_host_hton(&st->ext, &sp->ext) bcopy(&st->rt_addr, &sp->rt_addr, sizeof(sp->rt_addr)); + sp->hash = pf_state_hash(sp); sp->creation = htonl(secs - st->creation); sp->packets[0] = htonl(st->packets[0]); sp->packets[1] = htonl(st->packets[1]); Index: sys/net/pf/pf.c =================================================================== RCS file: /cvs/src/sys/net/pf/pf.c,v retrieving revision 1.17 diff -u -p -r1.17 pf.c --- sys/net/pf/pf.c 7 Apr 2008 00:43:44 -0000 1.17 +++ sys/net/pf/pf.c 7 Apr 2008 21:24:27 -0000 @@ -322,15 +322,16 @@ } return (0); } -static -int +u_int32_t pf_state_hash(struct pf_state *s) { - int hv = (intptr_t)s / sizeof(*s); + u_int32_t hv = (intptr_t)s / sizeof(*s); hv ^= crc32(&s->lan, sizeof(s->lan)); hv ^= crc32(&s->gwy, sizeof(s->gwy)); hv ^= crc32(&s->ext, sizeof(s->ext)); + if (hv == 0) /* disallow 0 */ + hv = 1; return(hv); } @@ -2724,8 +2725,11 @@ s->gwy.port = s->lan.port; } } + s->hash = pf_state_hash(s); s->src.seqlo = ntohl(th->th_seq); s->src.seqhi = s->src.seqlo + len + 1; + s->pickup_mode = r->pickup_mode; + if ((th->th_flags & (TH_SYN|TH_ACK)) == TH_SYN && r->keep_state == PF_STATE_MODULATE) { /* Generate sequence number modulator */ @@ -2740,6 +2744,7 @@ s->src.max_win = MAX(ntohs(th->th_win) if (th->th_flags & TH_SYN) { s->src.seqhi++; s->src.wscale = pf_get_wscale(m, off, th->th_off, af); + s->sync_flags |= PFSTATE_GOT_SYN1; } else if (s->src.wscale & PF_WSCALE_MASK) { /* Remove scale factor from initial window */ u_int win = s->src.max_win; @@ -3053,6 +3058,7 @@ PF_ACPY(&s->gwy.addr, &s->lan.addr, s->gwy.port = s->lan.port; } } + s->hash = pf_state_hash(s); s->src.state = PFUDPS_SINGLE; s->dst.state = PFUDPS_NO_TRAFFIC; s->creation = time_second; @@ -3328,6 +3334,7 @@ else PF_ACPY(&s->gwy.addr, &s->lan.addr, af); s->gwy.port = icmpid; } + s->hash = pf_state_hash(s); s->creation = time_second; s->expire = time_second; s->timeout = PFTM_ICMP_FIRST_PACKET; @@ -3582,6 +3589,7 @@ PF_ACPY(&s->gwy.addr, &pd->baddr, af else PF_ACPY(&s->gwy.addr, &s->lan.addr, af); } + s->hash = pf_state_hash(s); s->src.state = PFOTHERS_SINGLE; s->dst.state = PFOTHERS_NO_TRAFFIC; s->creation = time_second; @@ -3638,9 +3646,17 @@ else if (PF_MISMATCHAW(&r->dst.addr, p r = r->skip[PF_SKIP_DST_ADDR].ptr; else if (r->tos && !(r->tos & pd->tos)) r = TAILQ_NEXT(r, entries); - else if (r->src.port_op || r->dst.port_op || - r->flagset || r->type || r->code || - r->os_fingerprint != PF_OSFP_ANY) + else if (r->os_fingerprint != PF_OSFP_ANY) + r = TAILQ_NEXT(r, entries); + else if (pd->proto == IPPROTO_UDP && + (r->src.port_op || r->dst.port_op)) + r = TAILQ_NEXT(r, entries); + else if (pd->proto == IPPROTO_TCP && + (r->src.port_op || r->dst.port_op || r->flagset)) + r = TAILQ_NEXT(r, entries); + else if ((pd->proto == IPPROTO_ICMP || + pd->proto == IPPROTO_ICMPV6) && + (r->type || r->code)) r = TAILQ_NEXT(r, entries); else if (r->prob && r->prob <= karc4random()) r = TAILQ_NEXT(r, entries); @@ -3804,8 +3820,10 @@ */ seq = ntohl(th->th_seq); if (src->seqlo == 0) { - /* First packet from this end. Set its state */ - + /* + * First packet from this end. The other end has already set + * the seqlo field. Set its state. + */ if ((pd->flags & PFDESC_TCP_NORM || dst->scrub) && src->scrub == NULL) { if (pf_normalize_tcp_init(m, off, pd, th, src, dst)) { @@ -3830,6 +3848,7 @@ end = seq + pd->p_len; if (th->th_flags & TH_SYN) { end++; + (*state)->sync_flags |= PFSTATE_GOT_SYN2; if (dst->wscale & PF_WSCALE_FLAG) { src->wscale = pf_get_wscale(m, off, th->th_off, pd->af); @@ -3906,6 +3925,7 @@ ackskew = dst->seqlo - ack; #define MAXACKWINDOW (0xffff + 1500) /* 1500 is an arbitrary fudge factor */ + if (SEQ_GEQ(src->seqhi, end) && /* Last octet inside other's window space */ SEQ_GEQ(seq, src->seqlo - (dst->max_win << dws)) && @@ -4022,7 +4042,25 @@ src->state = dst->state = TCPS_TIME_W /* Fall through to PASS packet */ - } else { + } else if ((*state)->pickup_mode == PF_PICKUPS_HASHONLY || + ((*state)->pickup_mode == PF_PICKUPS_ENABLED && + ((*state)->sync_flags & PFSTATE_GOT_SYN_MASK) != + PFSTATE_GOT_SYN_MASK)) { + /* + * If pickup mode is hash only, do not fail on sequence checks. + * + * If pickup mode is enabled and we did not see the SYN in + * both direction, do not fail on sequence checks because + * we do not have complete information on window scale. + * + * Adjust expiration and fall through to PASS packet. + * XXX Add a FIN check to reduce timeout? + */ + (*state)->expire = time_second; + } else { + /* + * Failure processing + */ if ((*state)->dst.state == TCPS_SYN_SENT && (*state)->src.state == TCPS_SYN_SENT) { /* Send RST for state mismatches during handshake */ @@ -5483,8 +5521,9 @@ m->m_pkthdr.altq_qid = r->pqid; else m->m_pkthdr.altq_qid = r->qid; if (s) { + KKASSERT(s->hash != 0); m->m_pkthdr.fw_flags |= ALTQ_MBUF_STATE_HASHED; - m->m_pkthdr.altq_state_hash = pf_state_hash(s); + m->m_pkthdr.altq_state_hash = s->hash; } m->m_pkthdr.ecn_af = AF_INET; m->m_pkthdr.header = h; @@ -5797,8 +5836,9 @@ m->m_pkthdr.altq_qid = r->pqid; else m->m_pkthdr.altq_qid = r->qid; if (s) { + KKASSERT(s->hash != 0); m->m_pkthdr.fw_flags |= ALTQ_MBUF_STATE_HASHED; - m->m_pkthdr.altq_state_hash = pf_state_hash(s); + m->m_pkthdr.altq_state_hash = s->hash; } m->m_pkthdr.ecn_af = AF_INET6; m->m_pkthdr.header = h; Index: sys/net/pf/pf_ioctl.c =================================================================== RCS file: /cvs/src/sys/net/pf/pf_ioctl.c,v retrieving revision 1.13 diff -u -p -r1.13 pf_ioctl.c --- sys/net/pf/pf_ioctl.c 5 Jan 2008 14:02:38 -0000 1.13 +++ sys/net/pf/pf_ioctl.c 7 Apr 2008 00:50:15 -0000 @@ -1494,6 +1494,7 @@ state->creation = time_second; state->pfsync_time = 0; state->packets[0] = state->packets[1] = 0; state->bytes[0] = state->bytes[1] = 0; + state->hash = pf_state_hash(state); if (pf_insert_state(kif, state)) { pfi_maybe_destroy(kif); Index: sys/net/pf/pfvar.h =================================================================== RCS file: /cvs/src/sys/net/pf/pfvar.h,v retrieving revision 1.7 diff -u -p -r1.7 pfvar.h --- sys/net/pf/pfvar.h 6 Apr 2008 21:12:42 -0000 1.7 +++ sys/net/pf/pfvar.h 7 Apr 2008 21:02:53 -0000 @@ -585,6 +585,12 @@ u_int8_t allow_opts; u_int8_t rt; u_int8_t return_ttl; u_int8_t tos; +#define PF_PICKUPS_UNSPECIFIED 0 +#define PF_PICKUPS_DISABLED 1 +#define PF_PICKUPS_HASHONLY 2 +#define PF_PICKUPS_ENABLED 3 + u_int8_t pickup_mode; + u_int8_t unused01; /* available for use */ }; /* rule flags */ @@ -678,6 +684,7 @@ struct pf_addr rt_addr; struct pfi_kif *rt_kif; struct pf_src_node *src_node; struct pf_src_node *nat_src_node; + u_int32_t hash; u_int32_t creation; u_int32_t expire; u_int32_t pfsync_time; @@ -693,9 +700,14 @@ u_int8_t timeout; u_int8_t sync_flags; #define PFSTATE_NOSYNC 0x01 #define PFSTATE_FROMSYNC 0x02 +#define PFSTATE_GOT_SYN1 0x04 /* got SYN in one direction */ +#define PFSTATE_GOT_SYN2 0x08 /* got SYN in the other direction */ + u_int8_t pickup_mode; u_int8_t pad; }; +#define PFSTATE_GOT_SYN_MASK (PFSTATE_GOT_SYN1|PFSTATE_GOT_SYN2) + TAILQ_HEAD(pf_rulequeue, pf_rule); struct pf_anchor; @@ -1398,6 +1410,7 @@ extern int pf_insert_src_node(struct struct pf_rule *, struct pf_addr *, sa_family_t); void pf_src_tree_remove_state(struct pf_state *); +u_int32_t pf_state_hash(struct pf_state *s); extern struct pf_state *pf_find_state_byid(struct pf_state *); extern struct pf_state *pf_find_state_all(struct pf_state *key, u_int8_t tree, int *more);