endif(STATIC_CODECS)
+add_subdirectory(test)
+
add_library( misc_codecs OBJECT
cd_default.cc
${PLUGIN_LIST}
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" },
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
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)
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)
{
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;
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;
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,
--- /dev/null
+if ( ENABLE_SHELL )
+ add_cpputest(geneve_codec_test
+ SOURCES
+ ../../../framework/module.cc
+ )
+endif ( ENABLE_SHELL )
--- /dev/null
+//--------------------------------------------------------------------------
+// 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);
+}
+
{
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;
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
{ 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
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)
namespace geneve
{
-struct GeneveHdr;
+struct GeneveLyr;
}
namespace gre
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*);
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)
#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
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
{