]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #3836: protocols,codecs: Decode Geneve variable length options.
authorRon Dempster (rdempste) <rdempste@cisco.com>
Tue, 9 May 2023 11:52:21 +0000 (11:52 +0000)
committerRon Dempster (rdempste) <rdempste@cisco.com>
Tue, 9 May 2023 11:52:21 +0000 (11:52 +0000)
Merge in SNORT/snort3 from ~STECHEW/snort3:geneve_update to master

Squashed commit of the following:

commit 6cff0abdd48f869abb22d09f80f4846d88ba7673
Author: Steve Chew <stechew@cisco.com>
Date:   Tue May 2 08:55:38 2023 -0400

    protocols,codecs: Decode Geneve variable length options.

src/codecs/misc/CMakeLists.txt
src/codecs/misc/cd_geneve.cc
src/codecs/misc/test/CMakeLists.txt [new file with mode: 0644]
src/codecs/misc/test/geneve_codec_test.cc [new file with mode: 0644]
src/protocols/geneve.h
src/protocols/layer.cc
src/protocols/layer.h
src/protocols/packet.cc
src/protocols/packet.h

index d8ccd8a8f2d97d5ab1031e85c56a61ccf0824b29..0a6ba398a60d6ca5c4796b41555ad9be6af8f980 100644 (file)
@@ -23,6 +23,8 @@ else(STATIC_CODECS)
 
 endif(STATIC_CODECS)
 
+add_subdirectory(test)
+
 add_library( misc_codecs OBJECT
     cd_default.cc
     ${PLUGIN_LIST}
index 043586e639b1d992c4dba73fd8b7b5b25d020138..42a2b5e408181a50e679d106c2741c9901ec0e2c 100644 (file)
@@ -43,28 +43,6 @@ using namespace snort;
 
 namespace
 {
-struct GeneveOpt
-{
-    uint16_t g_class;
-    uint8_t  g_type;
-    uint8_t  g_len;
-
-    uint16_t optclass() const
-    { return (ntohs(g_class)); }
-
-    bool is_set(uint16_t which) const
-    { return (g_type & which); }
-
-    uint8_t type() const
-    { return (g_type); }
-
-    uint8_t olen() const
-    { return (sizeof(GeneveOpt) + ((g_len & 0x1f) * 4)); }
-
-    uint8_t len() const
-    { return ((g_len & 0x1f) * 4); }
-};
-
 static const RuleMap geneve_rules[] =
 {
     { DECODE_GENEVE_DGRAM_LT_GENEVE_HDR, "insufficient room for geneve header" },
@@ -98,7 +76,7 @@ public:
 private:
 
     void log_opts(TextLog* const, const uint8_t*, const uint16_t len);
-    bool validate_options(const uint8_t* rptr, uint16_t optlen, CodecData& codec);
+    bool validate_options(const uint8_t* rptr, uint16_t opts_len, CodecData& codec);
 };
 
 } // namespace
@@ -119,7 +97,7 @@ bool GeneveCodec::validate_options(const uint8_t* rptr, uint16_t hdrlen, CodecDa
 
     while (offset < hdrlen)
     {
-        const GeneveOpt* const opt = reinterpret_cast<const GeneveOpt*>(rptr);
+        const geneve::GeneveOpt* const opt = reinterpret_cast<const geneve::GeneveOpt*>(rptr);
         uint8_t olen = opt->olen();
 
         if ((offset + olen) > hdrlen)
@@ -165,7 +143,7 @@ bool GeneveCodec::decode(const RawData& raw, CodecData& codec, DecodeData&)
         return false;
     }
 
-    const uint16_t optlen = hdr->optlen();
+    const uint16_t opts_len = hdr->opts_len();
     const uint32_t hdrlen = hdr->hlen();
     if (raw.len <  hdrlen)
     {
@@ -173,8 +151,8 @@ bool GeneveCodec::decode(const RawData& raw, CodecData& codec, DecodeData&)
         return false;
     }
 
-    /* If critical header present bit is set, optlen cannot be 0 */
-    if (hdr->is_set(GENEVE_FLAG_C) && (optlen == 0))
+    /* If critical header present bit is set, opts_len cannot be 0 */
+    if (hdr->is_set(GENEVE_FLAG_C) && (opts_len == 0))
     {
         codec_event(codec, DECODE_GENEVE_INVALID_FLAGS);
         return false;
@@ -199,21 +177,21 @@ bool GeneveCodec::decode(const RawData& raw, CodecData& codec, DecodeData&)
     return true;
 }
 
-void GeneveCodec::log_opts(TextLog* const text_log, const uint8_t *rptr, uint16_t optlen)
+void GeneveCodec::log_opts(TextLog* const text_log, const uint8_t *rptr, uint16_t opts_len)
 {
     uint16_t offset = 0;
 
-    while (offset < optlen)
+    while (offset < opts_len)
     {
-        const GeneveOpt* const opt = reinterpret_cast<const GeneveOpt*>(rptr);
+        const geneve::GeneveOpt* const opt = reinterpret_cast<const geneve::GeneveOpt*>(rptr);
         uint8_t olen = opt->olen();
 
         TextLog_Print(text_log, "\n\tclass 0x%04x, type 0x%02x%s, len %3u%s", opt->optclass(),
             opt->type(), (opt->is_set(GENEVE_OPT_TYPE_C) ? " (C)" : ""), olen, (olen ? " value " : ""));
 
-        rptr += sizeof(GeneveOpt);
+        rptr += sizeof(geneve::GeneveOpt);
 
-        for (int idx=0; idx < opt->len(); idx++)
+        for (int idx=0; idx < opt->data_len(); idx++)
             TextLog_Print(text_log, "%02x ", *rptr++);
 
         offset += olen;
@@ -238,10 +216,10 @@ void GeneveCodec::log(TextLog* const text_log, const uint8_t* raw_pkt,
     if (flags == "")
         flags = "none";
 
-    TextLog_Print(text_log, "version %u, optlen %u flags [%s]", hdr->version(), hdr->optlen(), flags.c_str());
+    TextLog_Print(text_log, "version %u, opts_len %u flags [%s]", hdr->version(), hdr->opts_len(), flags.c_str());
     TextLog_Print(text_log, " network id %u, next protocol: 0x%04x", hdr->vni(), hdr->proto());
 
-    log_opts(text_log, rptr, hdr->optlen());
+    log_opts(text_log, rptr, hdr->opts_len());
 }
 
 bool GeneveCodec::encode(const uint8_t* const raw_in, const uint16_t raw_len,
diff --git a/src/codecs/misc/test/CMakeLists.txt b/src/codecs/misc/test/CMakeLists.txt
new file mode 100644 (file)
index 0000000..14ed27a
--- /dev/null
@@ -0,0 +1,6 @@
+if ( ENABLE_SHELL )
+    add_cpputest(geneve_codec_test
+        SOURCES
+            ../../../framework/module.cc
+    )
+endif ( ENABLE_SHELL )
diff --git a/src/codecs/misc/test/geneve_codec_test.cc b/src/codecs/misc/test/geneve_codec_test.cc
new file mode 100644 (file)
index 0000000..3f8bae8
--- /dev/null
@@ -0,0 +1,154 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2023-2023 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+// geneve_codec_test.cc author Steve Chew <stechew@cisco.com>
+
+#include "../cd_geneve.cc"
+
+#include "utils/endian.h"
+
+#include <CppUTest/CommandLineTestRunner.h>
+#include <CppUTest/TestHarness.h>
+#include <CppUTestExt/MockSupport.h>
+
+void show_stats(PegCount*, const PegInfo*, unsigned, const char*) { }
+void show_stats(PegCount*, const PegInfo*, const IndexVec&, const char*, FILE*) { }
+
+namespace snort
+{
+    bool TextLog_Print(TextLog* const, const char*, ...) { return false; }
+    void Codec::codec_event(const CodecData&, CodecSid) { }
+    bool SnortConfig::tunnel_bypass_enabled(unsigned short) const { return false; }
+}
+
+// Geneve data with 2 variable options.
+uint8_t geneve_pkt_data[] = {
+0x05,                       // version and option length in 4-byte chunks
+0x00,                       // flags
+0x65,0x58,                  // protocol type
+0x00,0x00,0x01,             // VNI
+0x00,                       // reserved
+0x01, 0x06,                 // Cisco variable option class
+0x56,                       // Variable option type
+0x02,                       // Variable option data length in 4-byte chunks
+0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x40,    // Variable option data
+0x01,0x06,                  // Cisco variable option class
+0x02,                       // Variable option type
+0x01,                       // Variable option data length in 4-byte chunks
+0xac,0x10,0xa0,0x02         // Variable option data
+};
+
+// Geneve data with 0 variable options.
+uint8_t geneve_pkt_data_no_options[] = {
+0x00,                       // version and option length in 4-byte chunks
+0x00,                       // flags
+0x65,0x58,                  // protocol type
+0x00,0x00,0x01,             // VNI
+0x00,                       // reserved
+};
+
+
+// A sample function to convert the option data to uint64_t.
+bool get_geneve_opt_data(std::vector<snort::geneve::GeneveOptData> options, uint16_t g_class, uint8_t g_type, uint64_t& value)
+{
+    value = 0;
+
+    for (const auto& opt_data : options)
+    {
+        if (opt_data.opt.optclass() == g_class and opt_data.opt.type() == g_type)
+        {
+            uint8_t data_len = opt_data.opt.data_len();
+            if (data_len == 4)
+                value = ntohl(*(uint32_t*)opt_data.data);
+            else if (data_len == 8)
+                value = ntohll(*(uint64_t*)opt_data.data);
+            else
+                return false;
+
+            return true;
+        }
+    }
+
+    return false;
+}
+
+//--------------------------------------------------------------------------
+// Geneve codec tests
+//--------------------------------------------------------------------------
+
+TEST_GROUP(geneve_codec_tests)
+{
+};
+
+TEST(geneve_codec_tests, decode)
+{
+    GeneveCodec geneve_codec;
+    RawData raw_data(nullptr, geneve_pkt_data, sizeof(geneve_pkt_data));
+    CodecData codec_data(nullptr, ProtocolId::GENEVE);
+    DecodeData decode_data;
+
+    CHECK_TRUE( geneve_codec.decode(raw_data, codec_data, decode_data) );
+    CHECK(codec_data.lyr_len == sizeof(geneve_pkt_data));
+    CHECK(codec_data.proto_bits == PROTO_BIT__GENEVE);
+    CHECK(codec_data.next_prot_id == ProtocolId::ETHERNET_802_3);
+    CHECK(codec_data.codec_flags == CODEC_NON_IP_TUNNEL);
+}
+
+TEST(geneve_codec_tests, geneve_lyr)
+{
+    geneve::GeneveLyr* glyr = (geneve::GeneveLyr*)geneve_pkt_data;
+
+    std::vector<geneve::GeneveOptData> opt_data = glyr->get_opt_data();
+
+    CHECK( opt_data.size() == 2 );
+
+    CHECK( opt_data[0].opt.olen() == 12 );
+    CHECK( opt_data[0].opt.data_len() == 8 );
+    CHECK( opt_data[0].opt.optclass() == 0x0106 );
+    CHECK( opt_data[0].opt.type() == 0x56 );
+
+    uint8_t opt0_data[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x40};
+    CHECK( memcmp(opt_data[0].data, opt0_data, opt_data[0].opt.data_len()) == 0);
+
+    CHECK( opt_data[1].opt.olen() == 8 );
+    CHECK( opt_data[1].opt.data_len() == 4 );
+    CHECK( opt_data[1].opt.optclass() == 0x0106 );
+    CHECK( opt_data[1].opt.type() == 0x02 );
+
+    uint8_t opt1_data[] = {0xac,0x10,0xa0,0x02};
+    CHECK( memcmp(opt_data[1].data, opt1_data, opt_data[1].opt.data_len()) == 0);
+
+    uint64_t value = 0;
+    CHECK( get_geneve_opt_data(opt_data, 0x0106, 0x56, value) );
+    CHECK( value == 2112 );
+}
+
+TEST(geneve_codec_tests, geneve_lyr_no_options)
+{
+    geneve::GeneveLyr* glyr = (geneve::GeneveLyr*)geneve_pkt_data_no_options;
+    std::vector<geneve::GeneveOptData> opt_data = glyr->get_opt_data();
+    CHECK( opt_data.empty() );
+}
+
+//-------------------------------------------------------------------------
+// main
+//-------------------------------------------------------------------------
+int main(int argc, char** argv)
+{
+    return CommandLineTestRunner::RunAllTests(argc, argv);
+}
+
index 05e6764e80575916e613f8473e74a3b2be0aa369..4326254f47f220ad6cffee9632532c01992d263b 100644 (file)
@@ -24,6 +24,69 @@ namespace snort
 {
 namespace geneve
 {
+
+// The max size of the data portion of the option (((2 ^ 5) - 1) * 4).
+#define MAX_OPT_DATA_LEN 124
+
+// Geneve Variable-Length Option (from RFC8926):
+//    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |          Option Class         |      Type     |R|R|R| Length  |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |                                                               |
+//   ~                  Variable-Length Option Data                  ~
+//   |                                                               |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//
+struct GeneveOpt
+{
+    uint16_t g_class;
+    uint8_t  g_type;
+    uint8_t  g_len;
+
+    uint16_t optclass() const
+    { return (ntohs(g_class)); }
+
+    bool is_set(uint16_t which) const
+    { return (g_type & which); }
+
+    uint8_t type() const
+    { return (g_type); }
+
+    // Size of GeneveOpt header plus the variable-length data for this option.
+    uint8_t olen() const
+    { return (sizeof(GeneveOpt) + ((g_len & 0x1f) * 4)); }
+
+    // Size of the variable-length data section for this option.
+    uint8_t data_len() const
+    { return ((g_len & 0x1f) * 4); }
+};
+
+struct GeneveOptData
+{
+    GeneveOptData(const GeneveOpt* g_opt, const uint8_t* opt_data, uint8_t len)
+    {
+        assert(len <= MAX_OPT_DATA_LEN);
+        opt = *g_opt;
+        memcpy(data, opt_data, len);
+    }
+
+    GeneveOpt opt;
+    uint8_t data[MAX_OPT_DATA_LEN];
+};
+
+// Geneve Header (from RFC8926):
+//    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |Ver|  Opt Len  |O|C|    Rsvd.  |          Protocol Type        |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |        Virtual Network Identifier (VNI)       |    Reserved   |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |                                                               |
+//   ~                    Variable-Length Options                    ~
+//   |                                                               |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//
 struct GeneveHdr
 {
     uint8_t g_vl;
@@ -32,13 +95,15 @@ struct GeneveHdr
     uint8_t g_vni[ 3 ];
     uint8_t g_rsvd;
 
+    // Size of header fields plus the variable-length option section.
     uint16_t hlen() const
     { return (sizeof(GeneveHdr) + ((g_vl & 0x3f) * 4)); }
 
     uint8_t version() const
     { return (g_vl >> 6); }
 
-    uint8_t optlen() const
+    // Size of the variable-length option section.
+    uint8_t opts_len() const
     { return ((g_vl & 0x3f) * 4); }
 
     bool is_set(uint16_t which) const
@@ -51,6 +116,36 @@ struct GeneveHdr
     { return ((g_vni[0] << 16) | (g_vni[1] << 8) | g_vni[2]); }
 };
 
+struct GeneveLyr
+{
+    GeneveHdr hdr;      // Must be first in structure.
+    uint8_t data[256];  // Max size of variable options.
+
+    std::vector<GeneveOptData> get_opt_data() const
+    {
+        std::vector<GeneveOptData> options;
+        const uint16_t all_opt_len = hdr.opts_len();
+        uint16_t offset = 0;
+        const uint8_t* dptr = data;
+
+        while (offset < all_opt_len)
+        {
+            const GeneveOpt* const opt = reinterpret_cast<const GeneveOpt*>(dptr);
+            const uint8_t olen = opt->olen();
+
+            if ((offset + olen) > all_opt_len)
+                break;  // Invalid opt length.
+
+            options.emplace_back(opt, dptr + sizeof(GeneveOpt), opt->data_len());
+
+            dptr += olen;
+            offset += olen;
+        }
+
+        return options;
+    }
+} __attribute__((packed));
+
 } // namespace geneve
 } // namespace snort
 
index 73d7ca3245d2a3d2867348315cda5dff19274715..9804eb64f8cefe3440d6354fd3ba956bd99f3a2a 100644 (file)
@@ -115,13 +115,21 @@ const arp::EtherARP* get_arp_layer(const Packet* const p)
             ProtocolId::ETHERTYPE_REVARP));
 }
 
-const geneve::GeneveHdr* get_geneve_layer(const Packet* const p)
+const geneve::GeneveLyr* get_geneve_layer(const Packet* const p, bool inner)
 {
     uint8_t num_layers = p->num_layers;
     const Layer* lyr = p->layers;
 
-    return reinterpret_cast<const geneve::GeneveHdr*>(
-        find_inner_layer(lyr, num_layers, ProtocolId::GENEVE));
+    if (inner)
+    {
+        return reinterpret_cast<const geneve::GeneveLyr*>(
+            find_inner_layer(lyr, num_layers, ProtocolId::GENEVE));
+    }
+    else
+    {
+        return reinterpret_cast<const geneve::GeneveLyr*>(
+            find_outer_layer(lyr, num_layers, ProtocolId::GENEVE));
+    }
 }
 
 const gre::GREHdr* get_gre_layer(const Packet* const p)
index 750eae2a0ef1da623495da63d88889602a1962d0..7ad3d009b1fadd981fca8da370ea087c8a25c40c 100644 (file)
@@ -57,7 +57,7 @@ struct EtherHdr;
 
 namespace geneve
 {
-struct GeneveHdr;
+struct GeneveLyr;
 }
 
 namespace gre
@@ -112,7 +112,7 @@ SO_PUBLIC const arp::EtherARP* get_arp_layer(const Packet*);
 SO_PUBLIC const cisco_meta_data::CiscoMetaDataHdr* get_cisco_meta_data_layer(const Packet* const);
 SO_PUBLIC const eapol::EtherEapol* get_eapol_layer(const Packet*);
 SO_PUBLIC const eth::EtherHdr* get_eth_layer(const Packet*);
-SO_PUBLIC const geneve::GeneveHdr* get_geneve_layer(const Packet*);
+SO_PUBLIC const geneve::GeneveLyr* get_geneve_layer(const Packet*, bool inner);
 SO_PUBLIC const gre::GREHdr* get_gre_layer(const Packet*);
 SO_PUBLIC const vlan::VlanTagHdr* get_vlan_layer(const Packet*);
 SO_PUBLIC const wlan::WifiHdr* get_wifi_layer(const Packet*);
index 94be58c84317bb15795b2336444c98ead95591f1..ab299bd3a7ed155a057bf8403f0bee89ea474924 100644 (file)
@@ -271,11 +271,17 @@ uint32_t Packet::get_flow_geneve_vni() const
     uint32_t vni = 0;
 
     if (proto_bits & PROTO_BIT__GENEVE)
-        vni = layer::get_geneve_layer(this)->vni();
+        vni = layer::get_geneve_layer(this, true)->hdr.vni();
 
     return vni;
 }
 
+std::vector<snort::geneve::GeneveOptData> Packet::get_geneve_options(bool inner) const
+{
+    const snort::geneve::GeneveLyr* lyr = layer::get_geneve_layer(this, inner);
+    return lyr->get_opt_data();
+}
+
 bool Packet::is_from_application_client() const
 {
     if (flow)
index b04858ba51576d4238fe52a386c8cab8c3ac866b..0b1fbe549a6c413baf92c21e1f509a1ad3b05181 100644 (file)
@@ -26,6 +26,7 @@
 #include "framework/decode_data.h"
 #include "framework/pdu_section.h"
 #include "main/snort_types.h"
+#include "protocols/geneve.h"
 #include "target_based/snort_protocols.h"
 
 namespace snort
@@ -348,6 +349,7 @@ struct SO_PUBLIC Packet
 
     uint16_t get_flow_vlan_id() const;
     uint32_t get_flow_geneve_vni() const;
+    std::vector<snort::geneve::GeneveOptData> get_geneve_options(bool inner) const;
 
     int16_t get_ingress_group() const
     {