]> The Tcpdump Group git mirrors - libpcap/commitdiff
Add support for Geneve (Generic Network Virtualization Encapsulation).
authorJesse Gross <[email protected]>
Mon, 5 Jan 2015 21:55:37 +0000 (13:55 -0800)
committerFrancois-Xavier Le Bail <[email protected]>
Tue, 17 Feb 2015 14:40:00 +0000 (15:40 +0100)
"geneve" can be used to filter on Geneve encapsulated packets. It
also allows later filters to look at frames inside the tunnel
headers.

The Geneve protocol is documented here:
http://tools.ietf.org/html/draft-gross-geneve-02

CHANGES
gencode.c
gencode.h
grammar.y
pcap-filter.manmisc.in
scanner.l

diff --git a/CHANGES b/CHANGES
index 27fa0d247c3adbbb67495c81f1ff359881ab51b9..2a50776c05dceacdebf72ba323d982b11962712f 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,7 @@
+Thursday Feb. 12, 2015 [email protected]/[email protected]
+  Summary for 1.8.0 libpcap release
+       Support for filtering Geneve encapsulated packets.
+
 Wednesday Nov. 12, 2014 [email protected]/[email protected]
   Summary for 1.7.0 libpcap release
        Fix handling of zones for BPF on Solaris
index c5229550243f1ec49a59191c360cb92feb72e282..33e7ee9c04a1c30ded7ff173cfc6d3833ec1dd65 100644 (file)
--- a/gencode.c
+++ b/gencode.c
 
 #define ETHERMTU       1500
 
+#ifndef ETHERTYPE_TEB
+#define ETHERTYPE_TEB 0x6558
+#endif
+
 #ifndef IPPROTO_HOPOPTS
 #define IPPROTO_HOPOPTS 0
 #endif
 #define IPPROTO_SCTP 132
 #endif
 
+#define GENEVE_PORT 6081
+
 #ifdef HAVE_OS_PROTO_H
 #include "os-proto.h"
 #endif
@@ -309,6 +315,7 @@ static struct slist *xfer_to_a(struct arth *);
 static struct block *gen_mac_multicast(int);
 static struct block *gen_len(int, int);
 static struct block *gen_check_802_11_data_frame(void);
+static struct block *gen_geneve_ll_check(void);
 
 static struct block *gen_ppi_dlt_check(void);
 static struct block *gen_msg_abbrev(int type);
@@ -826,6 +833,7 @@ static bpf_abs_offset off_outermostlinkhdr;
        off_linkhdr.is_variable = new_is_variable; \
        off_linkhdr.constant_part = new_constant_part; \
        off_linkhdr.reg = new_reg; \
+       is_geneve = 0; \
 }
 
 /*
@@ -863,6 +871,13 @@ static bpf_abs_offset off_linktype;
  */
 static int is_atm = 0;
 
+/*
+ * TRUE if "geneve" appeared in the filter; it causes us to generate
+ * code that checks for a Geneve header and assume that later filters
+ * apply to the encapsulated payload.
+ */
+static int is_geneve = 0;
+
 /*
  * These are offsets for the ATM pseudo-header.
  */
@@ -965,6 +980,11 @@ init_linktype(p)
        off_proto = -1;
        off_payload = -1;
 
+       /*
+        * And not Geneve.
+        */
+       is_geneve = 0;
+
        /*
         * And assume we're not doing SS7.
         */
@@ -2778,6 +2798,9 @@ gen_prevlinkhdr_check(void)
 {
        struct block *b0;
 
+       if (is_geneve)
+               return gen_geneve_ll_check();
+
        switch (prevlinktype) {
 
        case DLT_SUNATM:
@@ -2839,7 +2862,13 @@ gen_linktype(proto)
        case DLT_EN10MB:
        case DLT_NETANALYZER:
        case DLT_NETANALYZER_TRANSPARENT:
-               b0 = gen_prevlinkhdr_check();
+               /* Geneve has an EtherType regardless of whether there is an
+                * L2 header. */
+               if (!is_geneve)
+                       b0 = gen_prevlinkhdr_check();
+               else
+                       b0 = NULL;
+
                b1 = gen_ether_linktype(proto);
                if (b0 != NULL)
                        gen_and(b0, b1);
@@ -8140,6 +8169,300 @@ gen_pppoes(sess_num)
        return b0;
 }
 
+/* Check that this is Geneve and the VNI is correct if
+ * specified. Parameterized to handle both IPv4 and IPv6. */
+static struct block *
+gen_geneve_check(struct block *(*gen_portfn)(int, int, int),
+                enum e_offrel offrel, int vni)
+{
+       struct block *b0, *b1;
+
+       b0 = gen_portfn(GENEVE_PORT, IPPROTO_UDP, Q_DST);
+
+       /* Check that we are operating on version 0. Otherwise, we
+        * can't decode the rest of the fields. The version is 2 bits
+        * in the first byte of the Geneve header. */
+       b1 = gen_mcmp(offrel, 8, BPF_B, (bpf_int32)0, 0xc0);
+       gen_and(b0, b1);
+       b0 = b1;
+
+       if (vni >= 0) {
+               vni <<= 8; /* VNI is in the upper 3 bytes */
+               b1 = gen_mcmp(offrel, 12, BPF_W, (bpf_int32)vni,
+                             0xffffff00);
+               gen_and(b0, b1);
+               b0 = b1;
+       }
+
+       return b0;
+}
+
+/* The IPv4 and IPv6 Geneve checks need to do two things:
+ * - Verify that this actually is Geneve with the right VNI.
+ * - Place the IP header length (plus variable link prefix if
+ *   needed) into register A to be used later to compute
+ *   the inner packet offsets. */
+static struct block *
+gen_geneve4(int vni)
+{
+       struct block *b0, *b1;
+       struct slist *s, *s1;
+
+       b0 = gen_geneve_check(gen_port, OR_TRAN_IPV4, vni);
+
+       /* Load the IP header length into A. */
+       s = gen_loadx_iphdrlen();
+
+       s1 = new_stmt(BPF_MISC|BPF_TXA);
+       sappend(s, s1);
+
+       /* Forcibly append these statements to the true condition
+        * of the protocol check by creating a new block that is
+        * always true and ANDing them. */
+       b1 = new_block(BPF_JMP|BPF_JEQ|BPF_X);
+       b1->stmts = s;
+       b1->s.k = 0;
+
+       gen_and(b0, b1);
+
+       return b1;
+}
+
+static struct block *
+gen_geneve6(int vni)
+{
+       struct block *b0, *b1;
+       struct slist *s, *s1;
+
+       b0 = gen_geneve_check(gen_port6, OR_TRAN_IPV6, vni);
+
+       /* Load the IP header length. We need to account for a
+        * variable length link prefix if there is one. */
+       s = gen_abs_offset_varpart(&off_linkpl);
+       if (s) {
+               s1 = new_stmt(BPF_LD|BPF_IMM);
+               s1->s.k = 40;
+               sappend(s, s1);
+
+               s1 = new_stmt(BPF_ALU|BPF_ADD|BPF_X);
+               s1->s.k = 0;
+               sappend(s, s1);
+       } else {
+               s = new_stmt(BPF_LD|BPF_IMM);
+               s->s.k = 40;;
+       }
+
+       /* Forcibly append these statements to the true condition
+        * of the protocol check by creating a new block that is
+        * always true and ANDing them. */
+       s1 = new_stmt(BPF_MISC|BPF_TAX);
+       sappend(s, s1);
+
+       b1 = new_block(BPF_JMP|BPF_JEQ|BPF_X);
+       b1->stmts = s;
+       b1->s.k = 0;
+
+       gen_and(b0, b1);
+
+       return b1;
+}
+
+/* We need to store three values based on the Geneve header::
+ * - The offset of the linktype.
+ * - The offset of the end of the Geneve header.
+ * - The offset of the end of the encapsulated MAC header. */
+static struct slist *
+gen_geneve_offsets(void)
+{
+       struct slist *s, *s1, *s_proto;
+
+       /* First we need to calculate the offset of the Geneve header
+        * itself. This is composed of the IP header previously calculated
+        * (include any variable link prefix) and stored in A plus the
+        * fixed sized headers (fixed link prefix, MAC length, and UDP
+        * header). */
+       s = new_stmt(BPF_ALU|BPF_ADD|BPF_K);
+       s->s.k = off_linkpl.constant_part + off_nl + 8;
+
+       /* Stash this in X since we'll need it later. */ 
+       s1 = new_stmt(BPF_MISC|BPF_TAX);
+       sappend(s, s1);
+
+       /* The EtherType in Geneve is 2 bytes in. Calculate this and
+        * store it. */
+       s1 = new_stmt(BPF_ALU|BPF_ADD|BPF_K);
+       s1->s.k = 2;
+       sappend(s, s1);
+
+       off_linktype.reg = alloc_reg();
+       off_linktype.is_variable = 1;
+       off_linktype.constant_part = 0;
+
+       s1 = new_stmt(BPF_ST);
+       s1->s.k = off_linktype.reg;
+       sappend(s, s1);
+
+       /* Load the Geneve option length and mask and shift to get the
+        * number of bytes. It is stored in the first byte of the Geneve
+        * header. */
+       s1 = new_stmt(BPF_LD|BPF_IND|BPF_B);
+       s1->s.k = 0;
+       sappend(s, s1);
+
+       s1 = new_stmt(BPF_ALU|BPF_AND|BPF_K);
+       s1->s.k = 0x3f;
+       sappend(s, s1);
+
+       s1 = new_stmt(BPF_ALU|BPF_MUL|BPF_K);
+       s1->s.k = 4;
+       sappend(s, s1);
+
+       /* Add in the rest of the Geneve base header. */
+       s1 = new_stmt(BPF_ALU|BPF_ADD|BPF_K);
+       s1->s.k = 8;
+       sappend(s, s1);
+
+       /* Add the Geneve header length to its offset and store. */
+       s1 = new_stmt(BPF_ALU|BPF_ADD|BPF_X);
+       s1->s.k = 0;
+       sappend(s, s1);
+
+       /* Set the encapsulated type as Ethernet. Even though we may
+        * not actually have Ethernet inside there are two reasons this
+        * is useful:
+        * - The linktype field is always in EtherType format regardless
+        *   of whether it is in Geneve or an inner Ethernet frame.
+        * - The only link layer that we have specific support for is
+        *   Ethernet. We will confirm that the packet actually is
+        *   Ethernet at runtime before executing these checks. */
+       PUSH_LINKHDR(DLT_EN10MB, 1, 0, alloc_reg());
+
+       s1 = new_stmt(BPF_ST);
+       s1->s.k = off_linkhdr.reg;
+       sappend(s, s1);
+
+       /* Calculate whether we have an Ethernet header or just raw IP/
+        * MPLS/etc. If we have Ethernet, advance the end of the MAC offset
+        * and linktype by 14 bytes so that the network header can be found
+        * seamlessly. Otherwise, keep what we've calculated already. */
+
+       /* We have a bare jmp so we can't use the optimizer. */
+       no_optimize = 1;
+
+       /* Load the EtherType in the Geneve header, 2 bytes in. */
+       s1 = new_stmt(BPF_LD|BPF_IND|BPF_H);
+       s1->s.k = 2;
+       sappend(s, s1);
+
+       /* Load X with the end of the Geneve header. */
+       s1 = new_stmt(BPF_LDX|BPF_MEM);
+       s1->s.k = off_linkhdr.reg;
+       sappend(s, s1);
+
+       /* Check if the EtherType is Transparent Ethernet Bridging. At the
+        * end of this check, we should have the total length in X. In
+        * the non-Ethernet case, it's already there. */
+       s_proto = new_stmt(JMP(BPF_JEQ));
+       s_proto->s.k = ETHERTYPE_TEB;
+       sappend(s, s_proto);
+
+       s1 = new_stmt(BPF_MISC|BPF_TXA);
+       sappend(s, s1);
+       s_proto->s.jt = s1;
+
+       /* Since this is Ethernet, use the EtherType of the payload
+        * directly as the linktype. Overwrite what we already have. */
+       s1 = new_stmt(BPF_ALU|BPF_ADD|BPF_K);
+       s1->s.k = 12;
+       sappend(s, s1);
+
+       s1 = new_stmt(BPF_ST);
+       s1->s.k = off_linktype.reg;
+       sappend(s, s1);
+
+       /* Advance two bytes further to get the end of the Ethernet
+        * header. */
+       s1 = new_stmt(BPF_ALU|BPF_ADD|BPF_K);
+       s1->s.k = 2;
+       sappend(s, s1);
+
+       /* Move the result to X. */
+       s1 = new_stmt(BPF_MISC|BPF_TAX);
+       sappend(s, s1);
+
+       /* Store the final result of our linkpl calculation. */
+       off_linkpl.reg = alloc_reg();
+       off_linkpl.is_variable = 1;
+       off_linkpl.constant_part = 0;
+
+       s1 = new_stmt(BPF_STX);
+       s1->s.k = off_linkpl.reg;
+       sappend(s, s1);
+       s_proto->s.jf = s1;
+
+       off_nl = 0;
+
+       return s;
+}
+
+/* Check to see if this is a Geneve packet. */
+struct block *
+gen_geneve(int vni)
+{
+       struct block *b0, *b1;
+       struct slist *s;
+
+       b0 = gen_geneve4(vni);
+       b1 = gen_geneve6(vni);
+
+       gen_or(b0, b1);
+       b0 = b1;
+
+       /* Later filters should act on the payload of the Geneve frame,
+        * update all of the header pointers. Attach this code so that
+        * it gets executed in the event that the Geneve filter matches. */
+       s = gen_geneve_offsets();
+
+       b1 = gen_true();
+       sappend(s, b1->stmts);
+       b1->stmts = s;
+
+       gen_and(b0, b1);
+
+       is_geneve = 1;
+
+       return b1;
+}
+
+/* Check that the encapsulated frame has a link layer header
+ * for Ethernet filters. */
+static struct block *
+gen_geneve_ll_check()
+{
+       struct block *b0;
+       struct slist *s, *s1;
+
+       /* The easiest way to see if there is a link layer present
+        * is to check if the link layer header and payload are not
+        * the same. */
+
+       /* Geneve always generates pure variable offsets so we can
+        * compare only the registers. */
+       s = new_stmt(BPF_LD|BPF_MEM);
+       s->s.k = off_linkhdr.reg;
+
+       s1 = new_stmt(BPF_LDX|BPF_MEM);
+       s1->s.k = off_linkpl.reg;
+       sappend(s, s1);
+
+       b0 = new_block(BPF_JMP|BPF_JEQ|BPF_X);
+       b0->stmts = s;
+       b0->s.k = 0;
+       gen_not(b0);
+
+       return b0;
+}
+
 struct block *
 gen_atmfield_code(atmfield, jvalue, jtype, reverse)
        int atmfield;
index 6ffee53bcc267b788c13d47a5d9e00d7f523891e..6c05b3935662106644152ac20f84fc28a53703e9 100644 (file)
--- a/gencode.h
+++ b/gencode.h
@@ -321,6 +321,8 @@ struct block *gen_mpls(int);
 struct block *gen_pppoed(void);
 struct block *gen_pppoes(int);
 
+struct block *gen_geneve(int);
+
 struct block *gen_atmfield_code(int atmfield, bpf_int32 jvalue, bpf_u_int32 jtype, int reverse);
 struct block *gen_atmtype_abbrev(int type);
 struct block *gen_atmmulti_abbrev(int type);
index b269d27173e9c59d6a6fb45545f98e63ab01fe91..ad98964648e8aedb46dffe7a7fc9b8812df9d7c0 100644 (file)
--- a/grammar.y
+++ b/grammar.y
@@ -299,7 +299,7 @@ pfaction_to_num(const char *action)
 %token  LEN
 %token  IPV6 ICMPV6 AH ESP
 %token VLAN MPLS
-%token PPPOED PPPOES
+%token PPPOED PPPOES GENEVE
 %token  ISO ESIS CLNP ISIS L1 L2 IIH LSP SNP CSNP PSNP 
 %token  STP
 %token  IPX
@@ -523,6 +523,8 @@ other:        pqual TK_BROADCAST    { $$ = gen_broadcast($1); }
        | PPPOED                { $$ = gen_pppoed(); }
        | PPPOES pnum           { $$ = gen_pppoes($2); }
        | PPPOES                { $$ = gen_pppoes(-1); }
+       | GENEVE pnum           { $$ = gen_geneve($2); }
+       | GENEVE                { $$ = gen_geneve(-1); }
        | pfvar                 { $$ = $1; }
        | pqual p80211          { $$ = $2; }
        | pllc                  { $$ = $1; }
index f65a6a95f75fedb7581870454b65d3945163d349..4c84ebf5c587ef1214b644655fb9f605306a0cb2 100644 (file)
@@ -733,6 +733,22 @@ For example:
 .fi
 .in -.5i
 filters IPv4 protocols encapsulated in PPPoE session id 0x27.
+.IP "\fBgeneve \fI[vni]\fR"
+True if the packet is a Geneve packet (UDP port 6081). If \fI[vni]\fR
+is specified, only true if the packet has the specified \fIvni\fR.
+Note that when the \fBgeneve\fR keyword is encountered in
+\fIexpression\fR, it changes the decoding offsets for the remainder of
+\fIexpression\fR on the assumption that the packet is a Geneve packet.
+.IP
+For example:
+.in +.5i
+.nf
+\fBgeneve 0xb && ip\fR
+.fi
+.in -.5i
+filters IPv4 protocols encapsulated in Geneve with VNI 0xb. This will
+match both IP directly encapsulated in Geneve as well as IP contained
+inside an Ethernet frame.
 .IP "\fBiso proto \fIprotocol\fR"
 True if the packet is an OSI packet of protocol type \fIprotocol\fP.
 \fIProtocol\fP can be a number or one of the names
index 1666b03bda6e131b4eb1386616aa741c8a0f7bec..72a83f7fa2562095352cbfc6529b9df79b11e476 100644 (file)
--- a/scanner.l
+++ b/scanner.l
@@ -275,6 +275,7 @@ vlan                return VLAN;
 mpls           return MPLS;
 pppoed         return PPPOED;
 pppoes         return PPPOES;
+geneve         return GENEVE;
 
 lane           return LANE;
 llc            return LLC;