diff options
author | Daniel Hartmeier <dhartmei@cvs.openbsd.org> | 2002-04-23 14:32:24 +0000 |
---|---|---|
committer | Daniel Hartmeier <dhartmei@cvs.openbsd.org> | 2002-04-23 14:32:24 +0000 |
commit | d70cb2c050204739bb31da06800d79bad94f2730 (patch) | |
tree | 36a354437114272043ed536e25019006c1c1301d | |
parent | 06672d3a1880a142ab171f0458ad27cdb1f8e81a (diff) |
Allow explicit filtering of fragments when they are not reassembled.
Document fragment handling in the man page. Short version: if you're
scrubbing everything (as is recommended, in general), nothing changes.
If you want to deal with fragments manually, read the man page.
ok frantzen.
-rw-r--r-- | sbin/pfctl/parse.y | 37 | ||||
-rw-r--r-- | sbin/pfctl/pfctl_parser.c | 4 | ||||
-rw-r--r-- | share/man/man5/pf.conf.5 | 67 | ||||
-rw-r--r-- | sys/net/pf.c | 79 | ||||
-rw-r--r-- | sys/net/pfvar.h | 3 |
5 files changed, 173 insertions, 17 deletions
diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y index 0e2387fc187..19a50de9ff5 100644 --- a/sbin/pfctl/parse.y +++ b/sbin/pfctl/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.59 2002/04/18 06:02:18 deraadt Exp $ */ +/* $OpenBSD: parse.y,v 1.60 2002/04/23 14:32:23 dhartmei Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. @@ -172,13 +172,13 @@ typedef struct { %token RETURNRST RETURNICMP RETURNICMP6 PROTO INET INET6 ALL ANY ICMPTYPE %token ICMP6TYPE CODE KEEP MODULATE STATE PORT RDR NAT BINAT ARROW NODF %token MINTTL IPV6ADDR ERROR ALLOWOPTS FASTROUTE ROUTETO DUPTO NO LABEL -%token NOROUTE +%token NOROUTE FRAGMENT %token <v.string> STRING %token <v.number> NUMBER %token <v.i> PORTUNARY PORTBINARY %type <v.interface> interface if_list if_item_not if_item %type <v.number> port icmptype icmp6type minttl -%type <v.i> no dir log quick af keep nodf allowopts +%type <v.i> no dir log quick af keep nodf allowopts fragment %type <v.b> action flag flags blockspec %type <v.range> dport rport %type <v.proto> proto proto_list proto_item @@ -213,7 +213,7 @@ varset : STRING PORTUNARY STRING } ; -pfrule : action dir log quick interface route af proto fromto flags icmpspec keep nodf minttl allowopts label +pfrule : action dir log quick interface route af proto fromto flags icmpspec keep fragment nodf minttl allowopts label { struct pf_rule r; @@ -240,10 +240,12 @@ pfrule : action dir log quick interface route af proto fromto flags icmpspec ke r.keep_state = $12; if ($13) - r.rule_flag |= PFRULE_NODF; + r.rule_flag |= PFRULE_FRAGMENT; if ($14) - r.min_ttl = $14; - r.allow_opts = $15; + r.rule_flag |= PFRULE_NODF; + if ($15) + r.min_ttl = $15; + r.allow_opts = $16; if ($6.rt) { r.rt = $6.rt; @@ -266,14 +268,14 @@ pfrule : action dir log quick interface route af proto fromto flags icmpspec ke } } - if ($16) { - if (strlen($16) >= PF_RULE_LABEL_SIZE) { + if ($17) { + if (strlen($17) >= PF_RULE_LABEL_SIZE) { yyerror("rule label too long (max " "%d chars)", PF_RULE_LABEL_SIZE-1); YYERROR; } - strlcpy(r.label, $16, sizeof(r.label)); - free($16); + strlcpy(r.label, $17, sizeof(r.label)); + free($17); } expand_rule(&r, $5, $8, $9.src.host, $9.src.port, @@ -756,6 +758,9 @@ keep : /* empty */ { $$ = 0; } | MODULATE STATE { $$ = PF_STATE_MODULATE; } ; +fragment : /* empty */ { $$ = 0; } + | FRAGMENT { $$ = 1; } + minttl : /* empty */ { $$ = 0; } | MINTTL NUMBER { if ($2 < 0 || $2 > 255) { @@ -1126,6 +1131,10 @@ rule_consistent(struct pf_rule *r) yyerror("icmp-type/code does not apply to scrub"); problems++; } + if (r->rule_flag & PFRULE_FRAGMENT) { + yyerror("fragment flag does not apply to scrub"); + problems++; + } } else { if (r->rule_flag & PFRULE_NODF) { yyerror("nodf only applies to scrub"); @@ -1170,6 +1179,11 @@ rule_consistent(struct pf_rule *r) yyerror("allow-opts can only be specified for pass rules"); problems++; } + if (r->rule_flag & PFRULE_FRAGMENT && (r->src.port_op || + r->dst.port_op || r->flagset || r->type || r->code)) { + yyerror("fragments can be filtered only on IP header fields"); + problems++; + } return (-problems); } @@ -1368,6 +1382,7 @@ lookup(char *s) { "dup-to", DUPTO}, { "fastroute", FASTROUTE}, { "flags", FLAGS}, + { "fragment", FRAGMENT}, { "from", FROM}, { "icmp-type", ICMPTYPE}, { "in", IN}, diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c index 8c4f0a9dac1..d7714290314 100644 --- a/sbin/pfctl/pfctl_parser.c +++ b/sbin/pfctl/pfctl_parser.c @@ -1,4 +1,4 @@ -/* $OpenBSD: pfctl_parser.c,v 1.64 2002/04/15 20:39:58 dhartmei Exp $ */ +/* $OpenBSD: pfctl_parser.c,v 1.65 2002/04/23 14:32:23 dhartmei Exp $ */ /* * Copyright (c) 2001 Daniel Hartmeier @@ -777,6 +777,8 @@ print_rule(struct pf_rule *r) printf("keep state "); else if (r->keep_state == PF_STATE_MODULATE) printf("modulate state "); + if (r->rule_flag & PFRULE_FRAGMENT) + printf("fragment "); if (r->rule_flag & PFRULE_NODF) printf("no-df "); if (r->min_ttl) diff --git a/share/man/man5/pf.conf.5 b/share/man/man5/pf.conf.5 index 8ab6e14746b..19c996c8969 100644 --- a/share/man/man5/pf.conf.5 +++ b/share/man/man5/pf.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: pf.conf.5,v 1.38 2002/04/17 17:25:35 dhartmei Exp $ +.\" $OpenBSD: pf.conf.5,v 1.39 2002/04/23 14:32:23 dhartmei Exp $ .\" .\" Copyright (c) 2001, Daniel Hartmeier .\" All rights reserved. @@ -53,8 +53,8 @@ rule = action ( "in" | "out" ) hosts [ flags ] ( [ icmp-type ] | [ ipv6-icmp-type ] ) [ "keep state" ] [ "modulate state" ] - [ "no-df" ] [ "min-ttl" number ] [ "allow-opts" ] - [ "label" string ] . + [ "fragment" ] [ "no-df" ] [ "min-ttl" number ] + [ "allow-opts" ] [ "label" string ] . action = "pass" | "block" [ return ] | "scrub" . return = "return-rst" | @@ -477,6 +477,67 @@ Normalization occurs before filtering, scrub rules and pass/block rules are evaluated independantly. Hence, their relative position in the rule set is not relevant, and packets can't be blocked before normalization. +.Sh FRAGMENT HANDLING +IP datagrams (packets) can have a size of up to 65335 bytes. +Most network links, however, have a maximum transmission unit (MTU) +that is significantly lower (1500 bytes is common). +When an IP packet's size exceeds the MTU of the interface it has to +be sent out through, the packet is fragmented. +In general, a fragment only contains an IP header, which is sufficient +for the receiver to reassemble the complete packet. +The headers of subprotocols like TCP, UDP or ICMP are only data payload +on IP level, and such headers are not part of all fragments of a packet. +It's even possible that no fragment contains a complete subprotocol +header, because that header is split among fragments. +.Pp +There are two options for handling fragments in the packet filter: +.Pp +Using scrub rules, fragments can be reassembled by normalization. +In this case, fragments are cached until they form a complete +packet, and only complete packets are passed on to the filter. +The advantage is that filter rules have to deal only with complete +packets, and can ignore fragments. +The drawback of caching fragments is the additional memory cost. +.Pp +The alternative is to filter individual fragments with filter rules. +If no scrub rule applies to a fragment, it is passed to the filter. +Filter rules with matching IP header parameters decide whether the +fragment is passed or blocked, in the same way as complete packets +are filtered. +Without reassembly, fragments can only be filtered based on IP header +fields (source/destination address, protocol), since subprotocol header +fields are not available (TCP/UDP port numbers, ICMP code/type). +The +.Pa fragment +option can be used to restrict filter rules to apply only to +fragments but not complete packets. +Filter rules without the +.Pa fragment +option still apply to fragments, if they only specify IP header fields. +For instance, the rule 'pass in proto tcp from any to any port 80' never +applies to a fragment, even if the fragment is part of a TCP packet with +destination port 80, because without reassembly, this information is not +available for each fragment. +This also means that fragments can't create new or match existing +state table entries, which makes stateful filtering and address +translations (NAT, redirection) for fragments impossible. +.Pp +It's also possible to reassemble only certain fragments by specifying +source or destination addresses or protocols as parameters in scrub +rules. +.Pp +In most cases, the benefits of reassembly outweigh the additional +memory cost, and it's recommended to use scrub rules to reassemble +all fragments. +.Pp +The memory allocated for fragment caching can be limited using +.Xr pfctl 8 . +Once this limit is reached, fragments that would have to be cached +are dropped until other entries time out. The timeout value can +also be adjusted. +.Pp +Currently, only IPv4 fragments are supported and IPv6 fragments +are blocked unconditionally. .Sh EXAMPLES .Bd -literal # The external interface is kue0 (157.161.48.183, the only routable address) diff --git a/sys/net/pf.c b/sys/net/pf.c index 7af714ba79a..af5df4d570a 100644 --- a/sys/net/pf.c +++ b/sys/net/pf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: pf.c,v 1.202 2002/04/20 10:13:57 fgsch Exp $ */ +/* $OpenBSD: pf.c,v 1.203 2002/04/23 14:32:22 dhartmei Exp $ */ /* * Copyright (c) 2001 Daniel Hartmeier @@ -245,6 +245,8 @@ int pf_test_icmp(struct pf_rule **, int, struct ifnet *, struct mbuf *, int, int, void *, struct pf_pdesc *); int pf_test_other(struct pf_rule **, int, struct ifnet *, struct mbuf *, void *, struct pf_pdesc *); +int pf_test_fragment(struct pf_rule **, int, struct ifnet *, + struct mbuf *, void *, struct pf_pdesc *); int pf_test_state_tcp(struct pf_state **, int, struct ifnet *, struct mbuf *, int, int, void *, struct pf_pdesc *); @@ -3005,6 +3007,8 @@ pf_test_tcp(struct pf_rule **rm, int direction, struct ifnet *ifp, else if (r->dst.port_op && !pf_match_port(r->dst.port_op, r->dst.port[0], r->dst.port[1], th->th_dport)) r = r->skip[PF_SKIP_DST_PORT]; + else if (r->rule_flag & PFRULE_FRAGMENT) + r = TAILQ_NEXT(r, entries); else if ((r->flagset & th->th_flags) != r->flags) r = TAILQ_NEXT(r, entries); else { @@ -3239,6 +3243,8 @@ pf_test_udp(struct pf_rule **rm, int direction, struct ifnet *ifp, else if (r->dst.port_op && !pf_match_port(r->dst.port_op, r->dst.port[0], r->dst.port[1], uh->uh_dport)) r = r->skip[PF_SKIP_DST_PORT]; + else if (r->rule_flag & PFRULE_FRAGMENT) + r = TAILQ_NEXT(r, entries); else { *rm = r; if ((*rm)->quick) @@ -3513,6 +3519,8 @@ pf_test_icmp(struct pf_rule **rm, int direction, struct ifnet *ifp, r = TAILQ_NEXT(r, entries); else if (r->code && r->code != icmpcode + 1) r = TAILQ_NEXT(r, entries); + else if (r->rule_flag & PFRULE_FRAGMENT) + r = TAILQ_NEXT(r, entries); else { *rm = r; if ((*rm)->quick) @@ -3716,6 +3724,8 @@ pf_test_other(struct pf_rule **rm, int direction, struct ifnet *ifp, !PF_AZERO(&r->dst.mask, af) && !PF_MATCHA(r->dst.not, &r->dst.addr, &r->dst.mask, pd->dst, af)) r = r->skip[PF_SKIP_DST_ADDR]; + else if (r->rule_flag & PFRULE_FRAGMENT) + r = TAILQ_NEXT(r, entries); else { *rm = r; if ((*rm)->quick) @@ -3794,6 +3804,67 @@ pf_test_other(struct pf_rule **rm, int direction, struct ifnet *ifp, } int +pf_test_fragment(struct pf_rule **rm, int direction, struct ifnet *ifp, + struct mbuf *m, void *h, struct pf_pdesc *pd) +{ + struct pf_rule *r; + u_int8_t af = pd->af; + + *rm = NULL; + + r = TAILQ_FIRST(pf_rules_active); + while (r != NULL) { + r->evaluations++; + if (r->action == PF_SCRUB) + r = r->skip[PF_SKIP_ACTION]; + else if (r->ifp != NULL && r->ifp != ifp) + r = r->skip[PF_SKIP_IFP]; + else if (r->direction != direction) + r = r->skip[PF_SKIP_DIR]; + else if (r->af && r->af != af) + r = r->skip[PF_SKIP_AF]; + else if (r->proto && r->proto != pd->proto) + r = r->skip[PF_SKIP_PROTO]; + else if (r->src.noroute && pf_routable(pd->src, af)) + r = TAILQ_NEXT(r, entries); + else if (!r->src.noroute && + !PF_AZERO(&r->src.mask, af) && !PF_MATCHA(r->src.not, + &r->src.addr, &r->src.mask, pd->src, af)) + r = r->skip[PF_SKIP_SRC_ADDR]; + else if (r->dst.noroute && pf_routable(pd->dst, af)) + r = TAILQ_NEXT(r, entries); + else if (!r->src.noroute && + !PF_AZERO(&r->dst.mask, af) && !PF_MATCHA(r->dst.not, + &r->dst.addr, &r->dst.mask, pd->dst, af)) + r = r->skip[PF_SKIP_DST_ADDR]; + else if (r->src.port_op || r->dst.port_op || + r->flagset || r->type || r->code) + r = TAILQ_NEXT(r, entries); + else { + *rm = r; + if ((*rm)->quick) + break; + r = TAILQ_NEXT(r, entries); + } + } + + if (*rm != NULL) { + u_short reason; + + (*rm)->packets++; + (*rm)->bytes += pd->tot_len; + REASON_SET(&reason, PFRES_MATCH); + if ((*rm)->log) + PFLOG_PACKET(ifp, h, m, af, direction, reason, *rm); + + if ((*rm)->action != PF_PASS) + return (PF_DROP); + } + + return (PF_PASS); +} + +int pf_test_state_tcp(struct pf_state **state, int direction, struct ifnet *ifp, struct mbuf *m, int ipoff, int off, void *h, struct pf_pdesc *pd) { @@ -5087,6 +5158,12 @@ pf_test(int dir, struct ifnet *ifp, struct mbuf **m0) pd.af = AF_INET; pd.tot_len = h->ip_len; + /* handle fragments that didn't get reassembled by normalization */ + if (h->ip_off & (IP_MF | IP_OFFMASK)) { + action = pf_test_fragment(&r, dir, ifp, m, h, &pd); + goto done; + } + switch (h->ip_p) { case IPPROTO_TCP: { diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h index 0c32f00293c..824be2b28bd 100644 --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -1,4 +1,4 @@ -/* $OpenBSD: pfvar.h,v 1.66 2002/03/27 18:16:21 mickey Exp $ */ +/* $OpenBSD: pfvar.h,v 1.67 2002/04/23 14:32:22 dhartmei Exp $ */ /* * Copyright (c) 2001 Daniel Hartmeier @@ -245,6 +245,7 @@ struct pf_rule { #define PFRULE_RETURNRST 0x01 #define PFRULE_NODF 0x02 +#define PFRULE_FRAGMENT 0x04 struct pf_state_host { struct pf_addr addr; |