From ccb1eab57580c84e4b9ed789c33ebed8cb7c43aa Mon Sep 17 00:00:00 2001 From: Christian Kreibich Date: Fri, 11 Apr 2025 15:52:26 -0700 Subject: [PATCH] Add VLAN-aware connection tuples. Loading policy/protocols/conntuple/vlan adapts Zeek's flow hashing and the script-layer conn_id record to show VLAN tags when present. I'm using script-layer ints for the VLAN tag representation for consistency with what we alrady do elsewhere, but it seems odd since they can never be negative. I'm currently skipping protocols/conntuple/vlan in test-all-policy since it otherwise affects the external testsuites -- could revisit if people feel it should run on these. --- scripts/policy/protocols/conntuple/vlan.zeek | 14 +++ scripts/test-all-policy.zeek | 1 + scripts/zeekygen/__load__.zeek | 1 + src/conntuple/CMakeLists.txt | 1 + src/conntuple/vlan/Builder.cc | 108 ++++++++++++++++++ src/conntuple/vlan/Builder.h | 20 ++++ src/conntuple/vlan/CMakeLists.txt | 3 + src/conntuple/vlan/Plugin.cc | 24 ++++ .../conn.log.cut | 4 + .../conntuple/tuple-collision-vlan.pcap | Bin 0 -> 13396 bytes .../policy/protocols/conntuple/vlan.zeek | 8 ++ 11 files changed, 184 insertions(+) create mode 100644 scripts/policy/protocols/conntuple/vlan.zeek create mode 100644 src/conntuple/vlan/Builder.cc create mode 100644 src/conntuple/vlan/Builder.h create mode 100644 src/conntuple/vlan/CMakeLists.txt create mode 100644 src/conntuple/vlan/Plugin.cc create mode 100644 testing/btest/Baseline/scripts.policy.protocols.conntuple.vlan/conn.log.cut create mode 100644 testing/btest/Traces/conntuple/tuple-collision-vlan.pcap create mode 100644 testing/btest/scripts/policy/protocols/conntuple/vlan.zeek diff --git a/scripts/policy/protocols/conntuple/vlan.zeek b/scripts/policy/protocols/conntuple/vlan.zeek new file mode 100644 index 0000000000..d34d1cfd2e --- /dev/null +++ b/scripts/policy/protocols/conntuple/vlan.zeek @@ -0,0 +1,14 @@ +##! This script adapts Zeek's connection tuples to include 802.1Q VLAN and +##! Q-in-Q tags, when available. Zeek normally ignores VLAN tags in its flow +##! lookups; this change makes it factor them in and also makes those VLAN tags +##! part of the conn_id record. + +redef record conn_id += { + ## The outer VLAN for this connection, if applicable. + vlan: int &log &optional; + + ## The inner VLAN for this connection, if applicable. + inner_vlan: int &log &optional; +}; + +redef ConnTuple::builder = ConnTuple::CONNTUPLE_VLAN; diff --git a/scripts/test-all-policy.zeek b/scripts/test-all-policy.zeek index 2358afda5b..635bc4fa55 100644 --- a/scripts/test-all-policy.zeek +++ b/scripts/test-all-policy.zeek @@ -110,6 +110,7 @@ @load protocols/conn/mac-logging.zeek @load protocols/conn/vlan-logging.zeek @load protocols/conn/weirds.zeek +#@load protocols/conntuple/vlan.zeek #@load protocols/conn/speculative-service.zeek @load protocols/dhcp/msg-orig.zeek @load protocols/dhcp/software.zeek diff --git a/scripts/zeekygen/__load__.zeek b/scripts/zeekygen/__load__.zeek index 5be9d5d977..7f8ba54371 100644 --- a/scripts/zeekygen/__load__.zeek +++ b/scripts/zeekygen/__load__.zeek @@ -16,6 +16,7 @@ @load frameworks/signatures/iso-9660.zeek @load policy/misc/dump-events.zeek @load policy/protocols/conn/speculative-service.zeek +@load policy/protocols/conntuple/vlan.zeek # Remove in v8.1: This script is deprecated and conflicts with detect-sql-injection.zeek # @load policy/protocols/http/detect-sqli.zeek diff --git a/src/conntuple/CMakeLists.txt b/src/conntuple/CMakeLists.txt index df478d7f0c..be3f5dc81a 100644 --- a/src/conntuple/CMakeLists.txt +++ b/src/conntuple/CMakeLists.txt @@ -9,3 +9,4 @@ zeek_add_subdir_library( Manager.cc) add_subdirectory(fivetuple) +add_subdirectory(vlan) diff --git a/src/conntuple/vlan/Builder.cc b/src/conntuple/vlan/Builder.cc new file mode 100644 index 0000000000..450e63ab8b --- /dev/null +++ b/src/conntuple/vlan/Builder.cc @@ -0,0 +1,108 @@ +// See the file "COPYING" in the main distribution directory for copyright. + +#include "zeek/conntuple/vlan/Builder.h" + +#include "zeek/Conn.h" +#include "zeek/ID.h" +#include "zeek/IPAddr.h" +#include "zeek/session/Session.h" + +namespace zeek::plugin::Zeek_Conntuple_VLAN { + +// Add 802.1Q vlan tags to connection tuples. The tag representation here is as +// in the Packet class, since that's where we learn the tag values from. +struct VlanConnTuple : public ConnTuple { + uint32_t vlan = 0; + uint32_t inner_vlan = 0; +}; + +class VlanConnKey : public detail::ConnKey { +public: + uint32_t vlan = 0; + uint32_t inner_vlan = 0; + + VlanConnKey(const ConnTuple& conn) : detail::ConnKey(conn) {}; + VlanConnKey(const VlanConnTuple& conn) : detail::ConnKey(conn), vlan(conn.vlan), inner_vlan(conn.inner_vlan) {} + + VlanConnKey(Val* v) : detail::ConnKey(v) { + const auto& vt = v->GetType(); + if ( ! IsRecord(vt->Tag()) ) + return; + + RecordType* rt = vt->AsRecordType(); + auto vl = v->As(); + + // Be safe in case we get here without the expected + // conn_id redef from policy/protocols/conntuple/vlan. + if ( rt == id::conn_id && vl->HasField(5) && rt->GetFieldType(5)->Tag() == TYPE_INT ) + vlan = vl->GetField(5)->AsCount(); + if ( rt == id::conn_id && vl->HasField(6) && rt->GetFieldType(6)->Tag() == TYPE_INT ) + inner_vlan = vl->GetField(6)->AsCount(); + } + + size_t PackedSize() const override { + // This depends on whether we actually have VLANs. + // We can go with the basic 5-tuple if not. + size_t result = detail::ConnKey::PackedSize(); + + if ( vlan > 0 ) + result += sizeof(vlan); + if ( inner_vlan > 0 ) + result += sizeof(inner_vlan); + + return result; + } + + size_t Pack(uint8_t* data, size_t size) const override { + if ( size < PackedSize() ) + return 0; + + uint8_t* ptr = data; + + ptr += detail::ConnKey::Pack(data, size); + + if ( vlan > 0 ) { + memcpy(ptr, &vlan, sizeof(vlan)); + ptr += sizeof(vlan); + } + if ( inner_vlan > 0 ) { + memcpy(ptr, &inner_vlan, sizeof(inner_vlan)); + ptr += sizeof(inner_vlan); + } + + return ptr - data; + } +}; + +ConnTuplePtr Builder::GetTuple(const Packet* pkt) { + auto res = std::make_shared(); + res->vlan = pkt->vlan; + res->inner_vlan = pkt->inner_vlan; + return res; +} + +zeek::detail::ConnKeyPtr Builder::GetKey(const ConnTuple& tuple) { + const VlanConnTuple& vtuple = dynamic_cast(tuple); + auto res = std::make_shared(tuple); + res->vlan = vtuple.vlan; + res->inner_vlan = vtuple.inner_vlan; + return res; +} + +zeek::detail::ConnKeyPtr Builder::GetKey(Val* v) { return std::make_shared(v); } + +void Builder::FillConnIdVal(detail::ConnKeyPtr key, RecordValPtr& tuple) { + if ( tuple->NumFields() <= 5 ) + return; + + RecordType* rt = tuple->GetType()->AsRecordType(); + auto vkey = dynamic_cast(key.get()); + + // Assign only if VLAN tags are present and the record has compatible fields: + if ( vkey->vlan > 0 && rt->GetFieldType(5)->Tag() == TYPE_INT ) + tuple->Assign(5, vkey->vlan); + if ( vkey->inner_vlan > 0 && tuple->NumFields() >= 6 && rt->GetFieldType(6)->Tag() == TYPE_INT ) + tuple->Assign(6, vkey->inner_vlan); +} + +} // namespace zeek::plugin::Zeek_Conntuple_VLAN diff --git a/src/conntuple/vlan/Builder.h b/src/conntuple/vlan/Builder.h new file mode 100644 index 0000000000..fa65b7ce91 --- /dev/null +++ b/src/conntuple/vlan/Builder.h @@ -0,0 +1,20 @@ +// See the file "COPYING" in the main distribution directory for copyright. + +#include "zeek/conntuple/Builder.h" +#include "zeek/plugin/Plugin.h" + +namespace zeek::plugin::Zeek_Conntuple_VLAN { + +class Builder : public conntuple::Builder { +public: + ConnTuplePtr GetTuple(const Packet* pkt) override; + + zeek::detail::ConnKeyPtr GetKey(const ConnTuple& tuple) override; + zeek::detail::ConnKeyPtr GetKey(Val* v) override; + + void FillConnIdVal(detail::ConnKeyPtr key, RecordValPtr& tuple) override; + + static zeek::conntuple::BuilderPtr Instantiate() { return std::make_unique(); } +}; + +} // namespace zeek::plugin::Zeek_Conntuple_VLAN diff --git a/src/conntuple/vlan/CMakeLists.txt b/src/conntuple/vlan/CMakeLists.txt new file mode 100644 index 0000000000..4bc6347d8c --- /dev/null +++ b/src/conntuple/vlan/CMakeLists.txt @@ -0,0 +1,3 @@ +zeek_add_plugin( + Zeek Conntuple_VLAN + SOURCES Builder.cc Plugin.cc) diff --git a/src/conntuple/vlan/Plugin.cc b/src/conntuple/vlan/Plugin.cc new file mode 100644 index 0000000000..86309d53e6 --- /dev/null +++ b/src/conntuple/vlan/Plugin.cc @@ -0,0 +1,24 @@ +// See the file "COPYING" in the main distribution directory for copyright. + +#include "zeek/plugin/Plugin.h" + +#include "zeek/conntuple/Component.h" +#include "zeek/conntuple/vlan/Builder.h" + +namespace zeek::plugin::Zeek_Conntuple_VLAN { + +class Plugin : public zeek::plugin::Plugin { +public: + zeek::plugin::Configuration Configure() { + AddComponent(new conntuple::Component("VLAN", zeek::plugin::Zeek_Conntuple_VLAN::Builder::Instantiate)); + + zeek::plugin::Configuration config; + config.name = "Zeek::Conntuple_VLAN"; + config.description = "Conntuple builder for 802.1Q VLAN- and Q-in-Q-aware flows"; + return config; + } +}; + +Plugin plugin; + +} // namespace zeek::plugin::Zeek_Conntuple_VLAN diff --git a/testing/btest/Baseline/scripts.policy.protocols.conntuple.vlan/conn.log.cut b/testing/btest/Baseline/scripts.policy.protocols.conntuple.vlan/conn.log.cut new file mode 100644 index 0000000000..27c6ae3579 --- /dev/null +++ b/testing/btest/Baseline/scripts.policy.protocols.conntuple.vlan/conn.log.cut @@ -0,0 +1,4 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +ts uid id.orig_h id.orig_p id.resp_h id.resp_p id.vlan id.inner_vlan +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 141.142.228.5 59856 192.150.187.43 80 - - +XXXXXXXXXX.XXXXXX ClEkJM2Vm5giqnMf4h 141.142.228.5 59856 192.150.187.43 80 42 - diff --git a/testing/btest/Traces/conntuple/tuple-collision-vlan.pcap b/testing/btest/Traces/conntuple/tuple-collision-vlan.pcap new file mode 100644 index 0000000000000000000000000000000000000000..d49110ef44bdbb4c52ff26713a511411842aa315 GIT binary patch literal 13396 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 Am;e9( literal 0 HcmV?d00001 diff --git a/testing/btest/scripts/policy/protocols/conntuple/vlan.zeek b/testing/btest/scripts/policy/protocols/conntuple/vlan.zeek new file mode 100644 index 0000000000..c3d27573d8 --- /dev/null +++ b/testing/btest/scripts/policy/protocols/conntuple/vlan.zeek @@ -0,0 +1,8 @@ +# @TEST-DOC: Verifies that the VLAN-aware conntuple builder correctly distinguishes colliding 5-tuples that only differ in their vlan tagging. +# +# @TEST-EXEC: zeek -b -r $TRACES/conntuple/tuple-collision-vlan.pcap %INPUT +# @TEST-EXEC: zeek-cut -m ts uid id.orig_h id.orig_p id.resp_h id.resp_p id.vlan id.inner_vlan conn.log.cut +# @TEST-EXEC: btest-diff conn.log.cut + +@load base/protocols/conn +@load protocols/conntuple/vlan