From: Ron Dempster (rdempste) Date: Tue, 9 May 2023 11:52:21 +0000 (+0000) Subject: Pull request #3836: protocols,codecs: Decode Geneve variable length options. X-Git-Tag: 3.1.62.0~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a7ed5e78d91e83c076fd2243ab5e000a9d4e8407;p=thirdparty%2Fsnort3.git Pull request #3836: protocols,codecs: Decode Geneve variable length options. Merge in SNORT/snort3 from ~STECHEW/snort3:geneve_update to master Squashed commit of the following: commit 6cff0abdd48f869abb22d09f80f4846d88ba7673 Author: Steve Chew Date: Tue May 2 08:55:38 2023 -0400 protocols,codecs: Decode Geneve variable length options. --- diff --git a/src/codecs/misc/CMakeLists.txt b/src/codecs/misc/CMakeLists.txt index d8ccd8a8f..0a6ba398a 100644 --- a/src/codecs/misc/CMakeLists.txt +++ b/src/codecs/misc/CMakeLists.txt @@ -23,6 +23,8 @@ else(STATIC_CODECS) endif(STATIC_CODECS) +add_subdirectory(test) + add_library( misc_codecs OBJECT cd_default.cc ${PLUGIN_LIST} diff --git a/src/codecs/misc/cd_geneve.cc b/src/codecs/misc/cd_geneve.cc index 043586e63..42a2b5e40 100644 --- a/src/codecs/misc/cd_geneve.cc +++ b/src/codecs/misc/cd_geneve.cc @@ -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(rptr); + const geneve::GeneveOpt* const opt = reinterpret_cast(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(rptr); + const geneve::GeneveOpt* const opt = reinterpret_cast(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 index 000000000..14ed27aea --- /dev/null +++ b/src/codecs/misc/test/CMakeLists.txt @@ -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 index 000000000..3f8bae834 --- /dev/null +++ b/src/codecs/misc/test/geneve_codec_test.cc @@ -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 + +#include "../cd_geneve.cc" + +#include "utils/endian.h" + +#include +#include +#include + +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 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 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 opt_data = glyr->get_opt_data(); + CHECK( opt_data.empty() ); +} + +//------------------------------------------------------------------------- +// main +//------------------------------------------------------------------------- +int main(int argc, char** argv) +{ + return CommandLineTestRunner::RunAllTests(argc, argv); +} + diff --git a/src/protocols/geneve.h b/src/protocols/geneve.h index 05e6764e8..4326254f4 100644 --- a/src/protocols/geneve.h +++ b/src/protocols/geneve.h @@ -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 get_opt_data() const + { + std::vector 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(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 diff --git a/src/protocols/layer.cc b/src/protocols/layer.cc index 73d7ca324..9804eb64f 100644 --- a/src/protocols/layer.cc +++ b/src/protocols/layer.cc @@ -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( - find_inner_layer(lyr, num_layers, ProtocolId::GENEVE)); + if (inner) + { + return reinterpret_cast( + find_inner_layer(lyr, num_layers, ProtocolId::GENEVE)); + } + else + { + return reinterpret_cast( + find_outer_layer(lyr, num_layers, ProtocolId::GENEVE)); + } } const gre::GREHdr* get_gre_layer(const Packet* const p) diff --git a/src/protocols/layer.h b/src/protocols/layer.h index 750eae2a0..7ad3d009b 100644 --- a/src/protocols/layer.h +++ b/src/protocols/layer.h @@ -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*); diff --git a/src/protocols/packet.cc b/src/protocols/packet.cc index 94be58c84..ab299bd3a 100644 --- a/src/protocols/packet.cc +++ b/src/protocols/packet.cc @@ -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 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) diff --git a/src/protocols/packet.h b/src/protocols/packet.h index b04858ba5..0b1fbe549 100644 --- a/src/protocols/packet.h +++ b/src/protocols/packet.h @@ -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 get_geneve_options(bool inner) const; int16_t get_ingress_group() const {