summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Hartmeier <dhartmei@cvs.openbsd.org>2002-04-23 14:32:24 +0000
committerDaniel Hartmeier <dhartmei@cvs.openbsd.org>2002-04-23 14:32:24 +0000
commitd70cb2c050204739bb31da06800d79bad94f2730 (patch)
tree36a354437114272043ed536e25019006c1c1301d
parent06672d3a1880a142ab171f0458ad27cdb1f8e81a (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.y37
-rw-r--r--sbin/pfctl/pfctl_parser.c4
-rw-r--r--share/man/man5/pf.conf.567
-rw-r--r--sys/net/pf.c79
-rw-r--r--sys/net/pfvar.h3
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;