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

Reply via email to