Hello, tech@, especially PF hackers! This is a work-in-progress patch that implements direct packet inspection in PF. This is needed in the cases when traffic could not be easily detected by other mechanisms. The actual example is new UDP-based protocol of uTorrent program that spams networks heavily, and could be detected only by comparing value at the offset 0x40 with 0x7FFFFFFFAB.
The main reason I publish uncompleted work is that I want to receive some clues, particularily: 1. I detect beginning of actual data as "pd->tot_len - pd->p_len" - is it right (method to do so)? 2. I use "m_data", "m_len" and "m_next" to loop through mbuf chain - is it right (method to do so)? Currently, it compiles, runs, but doesn't work - please do not actually run this patch unless you want to duplicate my work. :) Thanks in advance. -- Best wishes, Vadim Zhukov Index: share/man/man5/pf.conf.5 =================================================================== RCS file: /cvs/src/share/man/man5/pf.conf.5,v retrieving revision 1.476 diff -u -r1.476 pf.conf.5 --- share/man/man5/pf.conf.5 19 May 2010 13:51:37 -0000 1.476 +++ share/man/man5/pf.conf.5 12 Jun 2010 23:16:15 -0000 @@ -434,6 +434,33 @@ rule that is used when a packet does not match any rules does not allow IP options. .Pp +.It Xo +.Ar inspect Aq Ar value +.Ar at Aq Ar offset +.Xc +Tests packet contents at the +.Ar offset +to be equal to +.Ar value . +Note that offset starts after protocol header. +.Ar value +can be specified as plain strings, or as hexadecimal raw strings (i.e., +starting with "0x"). +In the latter case you can embed any special characters. +Maximum length of +.Ar value is 64 characters. +.Pp +.It Xo +.Ar inspect Aq Ar mask +.Ar maskop Aq Ar value +.Ar at Aq Ar offset +.Xc +Same as previous, but also allows to specify a mask to applied to +the data from packet before comparing to +.Ar value . +Two operations supported are "logical and" and "logical exclusive or". +They're specified using "&" and "^" characters, respectively. +.Pp .It Ar divert-packet Aq Ar port Used to send matching packets to .Xr divert 4 @@ -2643,7 +2670,8 @@ "nat-to" ( redirhost | "{" redirhost-list "}" ) [ portspec ] [ pooltype ] [ "static-port" ] | [ "fastroute" | route ] | - [ "received-on" ( interface-name | interface-group ) ] + [ "received-on" ( interface-name | interface-group ) ] | + inspect scrubopts = scrubopt [ [ "," ] scrubopts ] scrubopt = "no-df" | "min-ttl" number | "max-mss" number | @@ -2786,6 +2814,8 @@ upperlimit-sc = "upperlimit" sc-spec sc-spec = ( bandwidth-spec | "(" bandwidth-spec number bandwidth-spec ")" ) +inspect = "inspect" [ inspect-op ] string "at" number +inspect-op = string ( "&" | "^" ) include = "include" filename .Ed .Sh FILES Index: sys/net/pf.c =================================================================== RCS file: /cvs/src/sys/net/pf.c,v retrieving revision 1.691 diff -u -r1.691 pf.c --- sys/net/pf.c 7 May 2010 13:33:16 -0000 1.691 +++ sys/net/pf.c 12 Jun 2010 23:16:15 -0000 @@ -230,6 +230,8 @@ struct pf_state_key_cmp *, u_int, struct mbuf *); int pf_src_connlimit(struct pf_state **); int pf_check_congestion(struct ifqueue *); +int pf_inspect(struct pf_pdesc *, struct mbuf *, + struct pf_rule *); int pf_match_rcvif(struct mbuf *, struct pf_rule *); extern struct pool pfr_ktable_pl; @@ -2271,6 +2273,54 @@ } int +pf_inspect(struct pf_pdesc *pd, struct mbuf *m, struct pf_rule *r) { + u_int32_t at, i, mpos, pos; + char cv; + + if (r->inspect_at + r->inspect_len > pd->p_len) + return (0); + at = r->inspect_at + (pd->tot_len - pd->p_len); + + for (pos = 0; pos + m->m_len < at;) { + pos += m->m_len; + m = m->m_next; + if (m == NULL) + /* XXX: Should not be reached */ + return (0); + } + mpos = at - pos; + + for (i = 0; i < r->inspect_len; i++, mpos++) { + while (mpos >= m->m_len) { + pos += m->m_len; + m = m->m_next; + if (m == NULL) + /* XXX: Should not be reached */ + return (0); + mpos = 0; + } + switch (r->inspect_op) { + case PF_INSOP_CMP: + cv = m->m_data[mpos]; + break; + case PF_INSOP_AND: + cv = m->m_data[mpos] & r->inspect_mask[i]; + break; + case PF_INSOP_XOR: + cv = m->m_data[mpos] ^ r->inspect_mask[i]; + break; + default: + DPFPRINTF(LOG_ERR, "pf_Inspect: r->inspect_op=%d", + r->inspect_op); + return (0); + } + if (cv != r->inspect_what[i]) + return (0); + } + return (1); +} + +int pf_match_rcvif(struct mbuf *m, struct pf_rule *r) { struct ifnet *ifp = m->m_pkthdr.rcvif; @@ -2878,6 +2928,8 @@ r->prob <= arc4random_uniform(UINT_MAX - 1) + 1) r = TAILQ_NEXT(r, entries); else if (r->match_tag && !pf_match_tag(m, r, &tag)) + r = TAILQ_NEXT(r, entries); + else if (r->inspect_len > 0 && !pf_inspect(pd, m, r)) r = TAILQ_NEXT(r, entries); else if (r->rcv_kif && !pf_match_rcvif(m, r)) r = TAILQ_NEXT(r, entries); Index: sys/net/pf_ioctl.c =================================================================== RCS file: /cvs/src/sys/net/pf_ioctl.c,v retrieving revision 1.232 diff -u -r1.232 pf_ioctl.c --- sys/net/pf_ioctl.c 18 Jan 2010 23:52:46 -0000 1.232 +++ sys/net/pf_ioctl.c 12 Jun 2010 23:16:15 -0000 @@ -1121,6 +1121,13 @@ PFR_TFLAG_ACTIVE; } + if (rule->inspect_len > 0) { + if (rule->inspect_len > PF_INSPECT_SIZE) + error = EINVAL; + if (rule->inspect_op >= PF_INSOP_COUNT) + error = EINVAL; + } + if (error) { pf_rm_rule(NULL, rule); break; Index: sys/net/pfvar.h =================================================================== RCS file: /cvs/src/sys/net/pfvar.h,v retrieving revision 1.309 diff -u -r1.309 pfvar.h --- sys/net/pfvar.h 7 May 2010 13:33:16 -0000 1.309 +++ sys/net/pfvar.h 12 Jun 2010 23:16:15 -0000 @@ -646,6 +646,17 @@ struct pf_addr addr; u_int16_t port; } divert, divert_packet; + +#define PF_INSPECT_SIZE 64 + char inspect_what[PF_INSPECT_SIZE]; + char inspect_mask[PF_INSPECT_SIZE]; + u_int32_t inspect_at; + u_int16_t inspect_len; +#define PF_INSOP_CMP 0 +#define PF_INSOP_AND 1 +#define PF_INSOP_XOR 2 +#define PF_INSOP_COUNT 3 + u_int16_t inspect_op; }; /* rule flags */ Index: sbin/pfctl/parse.y =================================================================== RCS file: /cvs/src/sbin/pfctl/parse.y,v retrieving revision 1.589 diff -u -r1.589 parse.y --- sbin/pfctl/parse.y 23 Mar 2010 13:31:29 -0000 1.589 +++ sbin/pfctl/parse.y 12 Jun 2010 23:16:15 -0000 @@ -113,6 +113,11 @@ PFCTL_STATE_FILTER }; +struct rawstring { + char *s; + size_t len; +}; + struct node_proto { u_int8_t proto; struct node_proto *next; @@ -229,6 +234,14 @@ int binat; }; +struct inspect_opts { + char what[PF_INSPECT_SIZE]; + char mask[PF_INSPECT_SIZE]; + u_int32_t at; + u_int32_t len; + u_int32_t op; +} inspect_opts; + struct filter_opts { int marker; #define FOM_FLAGS 0x0001 @@ -287,6 +300,8 @@ sa_family_t af; struct pf_poolhashkey *key; } route; + + struct inspect_opts inspect; } filter_opts; struct antispoof_opts { @@ -436,6 +451,8 @@ struct table_opts table_opts; struct pool_opts pool_opts; struct node_hfsc_opts hfsc_opts; + struct rawstring rawstring; + struct inspect_opts inspect_opts; } v; int lineno; } YYSTYPE; @@ -467,6 +484,7 @@ %token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY PFLOW %token TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE SETTOS %token DIVERTTO DIVERTREPLY DIVERTPACKET NATTO RDRTO RECEIVEDON NE LE GE +%token INSPECT AT AND_C XOR_C %token <v.string> STRING %token <v.number> NUMBER %token <v.i> PORTBINARY @@ -515,6 +533,8 @@ %type <v.scrub_opts> scrub_opts scrub_opt scrub_opts_l %type <v.table_opts> table_opts table_opt table_opts_l %type <v.pool_opts> pool_opts pool_opt pool_opts_l +%type <v.inspect_opts> inspect_op +%type <v.rawstring> rawstring %% ruleset : /* empty */ @@ -739,6 +759,42 @@ | STRING ; +rawstring : STRING { + int i; + char c; + + if (strncmp($1, "0x", 2) == 0) { + $$.len = strlen($1) - 2; + if ($$.len % 2) { + yyerror("invalid hex inspect string"); + YYERROR; + } + $$.len /= 2; + $$.s = calloc($$.len, sizeof($$.s[0])); + if ($$.s == NULL) + err(1, "inspect_op: calloc"); + for (i = 0; i < $$.len; i++) { + c = $1[(i + 1) * 2]; + if (!isxdigit(c)) { + yyerror("invalid hex inspect string"); + YYERROR; + } + $$.s[i] = (tolower(c) - 'a') << 4; + c = $1[(i + 1) * 2 + 1]; + if (!isxdigit(c)) { + yyerror("invalid hex inspect string"); + YYERROR; + } + $$.s[i] |= tolower(c) - 'a'; + } + free($1); + } else { + $$.len = strlen($1); + $$.s = $1; + } + } + ; + varset : STRING '=' varstring { if (pf->opts & PF_OPT_VERBOSE) printf("%s = \"%s\"\n", $1, $3); @@ -904,6 +960,17 @@ } r.match_tag_not = $9.match_tag_not; + if ($9.inspect.len > 0) { + memcpy(r.inspect_what, $9.inspect.what, + $9.inspect.len); + if ($9.inspect.op != PF_INSOP_CMP) + memcpy(r.inspect_mask, $9.inspect.mask, + $9.inspect.len); + r.inspect_at = $9.inspect.at; + r.inspect_len = $9.inspect.len; + r.inspect_op = $9.inspect.op; + } + decide_address_family($8.src.host, &r.af); decide_address_family($8.dst.host, &r.af); @@ -2094,6 +2161,17 @@ } r.divert_packet.port = $8.divert_packet.port; + if ($8.inspect.len > 0) { + memcpy(r.inspect_what, $8.inspect.what, + $8.inspect.len); + if ($8.inspect.op != PF_INSOP_CMP) + memcpy(r.inspect_mask, $8.inspect.mask, + $8.inspect.len); + r.inspect_at = $8.inspect.at; + r.inspect_len = $8.inspect.len; + r.inspect_op = $8.inspect.op; + } + expand_rule(&r, 0, $4, &$8.nat, &$8.rdr, &$8.rroute, $6, $7.src_os, $7.src.host, $7.src.port, $7.dst.host, $7.dst.port, @@ -2314,6 +2392,66 @@ } filter_opts.rcv = $2; } + | INSPECT rawstring inspect_op AT NUMBER { + if ($2.len == 0) { + yyerror("inspect string is empty"); + YYERROR; + } else if ($2.len > PF_INSPECT_SIZE) { + yyerror("inspect string is longer that %d bytes", + PF_INSPECT_SIZE); + YYERROR; + } + memcpy(filter_opts.inspect.what, $2.s, $2.len); + filter_opts.inspect.len = $2.len; + + if ($3.len > 0) { + if ($3.len != $2.len) { + yyerror("inspect string and mask have " + "different length"); + YYERROR; + } + memcpy(filter_opts.inspect.mask, $3.mask, $3.len); + } + filter_opts.inspect.op = $3.op; + + if ($5 < 0) { + yyerror("inspect address cannot be negative"); + YYERROR; + } + filter_opts.inspect.at = $5; + } + ; + +inspect_op : /* empty */ { + $$.op = PF_INSOP_CMP; + $$.len = 0; + } + | '&' rawstring { + if ($2.len == 0) { + yyerror("inspect mask string is empty"); + YYERROR; + } else if ($2.len > PF_INSPECT_SIZE) { + yyerror("inspect mask is longer that %d bytes", + PF_INSPECT_SIZE); + YYERROR; + } + memcpy($$.mask, $2.s, $2.len); + $$.len = $2.len; + $$.op = PF_INSOP_AND; + } + | '^' rawstring { + if ($2.len == 0) { + yyerror("inspect mask string is empty"); + YYERROR; + } else if ($2.len > PF_INSPECT_SIZE) { + yyerror("inspect mask is longer that %d bytes", + PF_INSPECT_SIZE); + YYERROR; + } + memcpy($$.mask, $2.s, $2.len); + $$.len = $2.len; + $$.op = PF_INSOP_XOR; + } ; probability : STRING { @@ -5011,6 +5149,7 @@ { "anchor", ANCHOR}, { "antispoof", ANTISPOOF}, { "any", ANY}, + { "at", AT}, { "bandwidth", BANDWIDTH}, { "binat-to", BINATTO}, { "bitmask", BITMASK}, @@ -5046,6 +5185,7 @@ { "include", INCLUDE}, { "inet", INET}, { "inet6", INET6}, + { "inspect", INSPECT}, { "keep", KEEP}, { "label", LABEL}, { "limit", LIMIT}, Index: sbin/pfctl/pfctl_parser.c =================================================================== RCS file: /cvs/src/sbin/pfctl/pfctl_parser.c,v retrieving revision 1.265 diff -u -r1.265 pfctl_parser.c --- sbin/pfctl/pfctl_parser.c 16 May 2010 12:23:30 -0000 1.265 +++ sbin/pfctl/pfctl_parser.c 12 Jun 2010 23:16:15 -0000 @@ -1055,6 +1055,27 @@ print_pool(&r->route, 0, 0, r->af, PF_PASS, verbose); } } + if (r->inspect_len) { + printf(" inspect 0x"); + for (i = 0; i < r->inspect_len; i++) + printf("%02x", (int)r->inspect_what[i]); + if (r->inspect_op != PF_INSOP_CMP) { + switch (r->inspect_op) { + case PF_INSOP_AND: + printf(" & 0x"); + break; + case PF_INSOP_XOR: + printf(" ^ 0x"); + break; + default: + errx(1, "\nUnknown inspect operation %d", + r->inspect_op); + } + for (i = 0; i < r->inspect_len; i++) + printf("%02x", (int)r->inspect_mask[i]); + } + printf(" at %u", (unsigned)r->inspect_at); + } } void