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

Reply via email to