Is there some reason that divert sockets (``man divert'') can't do this for you?
On Sun, Jun 13, 2010 at 03:27:57AM +0400, Vadim Jukov wrote: > 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