diff --git a/scripts/base/frameworks/tunnels/main.bro b/scripts/base/frameworks/tunnels/main.bro index 7721ce3a02..3faf267eee 100644 --- a/scripts/base/frameworks/tunnels/main.bro +++ b/scripts/base/frameworks/tunnels/main.bro @@ -85,7 +85,8 @@ export { const ayiya_ports = { 5072/udp }; const teredo_ports = { 3544/udp }; const gtpv1_ports = { 2152/udp, 2123/udp }; -redef likely_server_ports += { ayiya_ports, teredo_ports, gtpv1_ports }; +const vxlan_ports = { 4789/udp }; +redef likely_server_ports += { ayiya_ports, teredo_ports, gtpv1_ports, vxlan_ports }; event bro_init() &priority=5 { @@ -93,6 +94,7 @@ event bro_init() &priority=5 Analyzer::register_for_ports(Analyzer::ANALYZER_AYIYA, ayiya_ports); Analyzer::register_for_ports(Analyzer::ANALYZER_TEREDO, teredo_ports); + Analyzer::register_for_ports(Analyzer::ANALYZER_VXLAN, vxlan_ports); Analyzer::register_for_ports(Analyzer::ANALYZER_GTPV1, gtpv1_ports); } diff --git a/src/TunnelEncapsulation.h b/src/TunnelEncapsulation.h index b853fc01b3..30b7b48569 100644 --- a/src/TunnelEncapsulation.h +++ b/src/TunnelEncapsulation.h @@ -88,6 +88,7 @@ public: return false; if ( ec1.type == BifEnum::Tunnel::IP || + ec1.type == BifEnum::Tunnel::VXLAN || ec1.type == BifEnum::Tunnel::GRE ) // Reversing endpoints is still same tunnel. return ec1.uid == ec2.uid && ec1.proto == ec2.proto && diff --git a/src/analyzer/protocol/CMakeLists.txt b/src/analyzer/protocol/CMakeLists.txt index ff34d243e8..882ba23da9 100644 --- a/src/analyzer/protocol/CMakeLists.txt +++ b/src/analyzer/protocol/CMakeLists.txt @@ -47,5 +47,6 @@ add_subdirectory(syslog) add_subdirectory(tcp) add_subdirectory(teredo) add_subdirectory(udp) +add_subdirectory(vxlan) add_subdirectory(xmpp) add_subdirectory(zip) diff --git a/src/analyzer/protocol/vxlan/CMakeLists.txt b/src/analyzer/protocol/vxlan/CMakeLists.txt new file mode 100644 index 0000000000..e531555321 --- /dev/null +++ b/src/analyzer/protocol/vxlan/CMakeLists.txt @@ -0,0 +1,9 @@ + +include(BroPlugin) + +include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) + +bro_plugin_begin(Bro VXLAN) +bro_plugin_cc(VXLAN.cc Plugin.cc) +bro_plugin_bif(events.bif) +bro_plugin_end() diff --git a/src/analyzer/protocol/vxlan/Plugin.cc b/src/analyzer/protocol/vxlan/Plugin.cc new file mode 100644 index 0000000000..89bbcc8e8c --- /dev/null +++ b/src/analyzer/protocol/vxlan/Plugin.cc @@ -0,0 +1,25 @@ +// See the file in the main distribution directory for copyright. + + +#include "plugin/Plugin.h" + +#include "VXLAN.h" + +namespace plugin { +namespace Bro_VXLAN { + +class Plugin : public plugin::Plugin { +public: + plugin::Configuration Configure() + { + AddComponent(new ::analyzer::Component("VXLAN", ::analyzer::vxlan::VXLAN_Analyzer::Instantiate)); + + plugin::Configuration config; + config.name = "Bro::VXLAN"; + config.description = "VXLAN analyzer"; + return config; + } +} plugin; + +} +} diff --git a/src/analyzer/protocol/vxlan/VXLAN.cc b/src/analyzer/protocol/vxlan/VXLAN.cc new file mode 100644 index 0000000000..5c922a43c4 --- /dev/null +++ b/src/analyzer/protocol/vxlan/VXLAN.cc @@ -0,0 +1,169 @@ + +#include "VXLAN.h" +#include "TunnelEncapsulation.h" +#include "Conn.h" +#include "IP.h" +#include "../arp/ARP.h" +#include "Reporter.h" + +#include "events.bif.h" + +using namespace analyzer::vxlan; + +void VXLAN_Analyzer::Done() + { + Analyzer::Done(); + Event(udp_session_done); + } + +bool VXLANEncapsulation::DoParse(const u_char* data, int& len) + { + int eth_len = 14; + int vxlan_len = 8; + int eth_mac = 6; + int proto = 0; + reporter->Error("VXLANEncapsulation::DoParse len: %d", len); + /* Note: outer Ethernet, IP, UDP layers already skipped */ + if ( len < vxlan_len ) + { + Weird("VXLAN_truncated missing VXLAN header"); + return false; + } + /* Flags (8 bits): where the I flag MUST be set to 1 for a valid + VXLAN Network ID (VNI). The other 7 bits (designated "R") are + reserved fields and MUST be set to zero on transmission and + ignored on receipt.*/ + if ( ! (data[0] & 0x8) ) + { + Weird("VXLAN_flags packet missing I flag set "); + return false; + } + if ( len < vxlan_len + eth_len ) + { + Weird("VXLAN_truncated missing inner packet header"); + return false; + } + printf("Checking packet ethertype for inner packet:\n"); + uint16 proto_typ = ntohs(*((uint16*)(data+vxlan_len+2*eth_mac))); + if ( proto_typ == 0x0800 ) + proto = IPPROTO_IPV4; + else if ( proto_typ == 0x86dd ) + proto = IPPROTO_IPV6; + else { + Weird("VXLAN_ethertype inner packet should be ethertype: IPv4 or IPv6"); + int i; + for (i=0; i < 2; i++) + printf("%02x ",data[vxlan_len+2*eth_mac+i]); + return false; + } + data += vxlan_len + eth_len; + len -= vxlan_len + eth_len; + inner_ip = data; + return true; + } + +RecordVal* VXLANEncapsulation::BuildVal(const IP_Hdr* inner) const + { + static RecordType* vxlan_hdr_type = 0; + static RecordType* vxlan_auth_type = 0; + static RecordType* vxlan_origin_type = 0; + reporter->Error("VXLANEncapsulation::BuildVal"); + + RecordVal* vxlan_hdr = new RecordVal(vxlan_hdr_type); + vxlan_hdr->Assign(1, inner->BuildPktHdrVal()); + return vxlan_hdr; + } + +void VXLAN_Analyzer::DeliverPacket(int len, const u_char* data, bool orig, + uint64 seq, const IP_Hdr* ip, int caplen) + { + Analyzer::DeliverPacket(len, data, orig, seq, ip, caplen); + /* Note: it seems we get the packet AFTER UDP header. */ + + VXLANEncapsulation vx(this); + + // If a carried packet has ethernet, this will help skip it. + int eth_len = 14; + int udp_len = 8; + int vlan_len = 4; + int vxlan_len = 8; + int eth_mac = 6; + int i = 0; + int vni= 0; + int proto = 0; + + const EncapsulationStack* e = Conn()->GetEncapsulation(); + IP_Hdr* inner = 0; + int rslt = sessions->ParseIPPacket(len, data + vxlan_len + eth_len, IPPROTO_IPV4, inner); + + reporter->Info("VXLAN_Analyzer::DeliverPacket"); + reporter->Info("len: %d", len); + printf("Packet hex:\n"); + for (i=0; i < len; i++) + printf("%0x ",data[i]); + printf("\n"); + /* Note: outer Ethernet, IP, UDP layers already skipped */ + if ( len < vxlan_len ) + { + Weird("VXLAN_truncated missing VXLAN header"); + return; + } + /* Flags (8 bits): where the I flag MUST be set to 1 for a valid + VXLAN Network ID (VNI). The other 7 bits (designated "R") are + reserved fields and MUST be set to zero on transmission and + ignored on receipt.*/ + if ( ! (data[0] & 0x8) ) + { + Weird("VXLAN_flags packet missing I flag set "); + return; + } + if ( len < vxlan_len + eth_len ) + { + Weird("VXLAN_truncated missing inner packet header"); + return; + } + printf("Checking packet ethertype for inner packet:\n"); + uint16 proto_typ = ntohs(*((uint16*)(data+vxlan_len+2*eth_mac))); + switch (proto_typ) + { + case 0x0800: + proto = IPPROTO_IPV4; + break; + case 0x86dd: + proto = IPPROTO_IPV6; + break; + case 0x8100: + case 0x9100: + /* 802.1q / 802.1ad */ + proto = proto_typ; + if (len < vxlan_len + eth_len + vlan_len) + { + Weird("VXLAN truncated inner packet VLAN ether header "); + return; + } + /* Set type then to next ethertype ? */ + break; + default: + Weird("VXLAN_ethertype inner packet should be ethertype: VLAN, IPv4 or IPv6"); + int i; + for (i=0; i < 2; i++) + printf("%02x ",data[vxlan_len+2*eth_mac+i]); + return; + + } + + printf("Packet safety checks done\n"); + vni = (data[4] << 16) + (data[5] << 8) + (data[6] << 0); + printf("VXLAN VNI %d\n",vni); + + /* Do we want the inner packet with or without Ethernet header? + data += vxlan_len + udp_len + eth_len; + len -= vxlan_len + udp_len + eth_len; + caplen -= vxlan_len + udp_len + eth_len; +*/ + data += udp_len + vxlan_len; + len -= udp_len + vxlan_len; + caplen -= udp_len + vxlan_len; + EncapsulatingConn ec(Conn(), BifEnum::Tunnel::VXLAN); + sessions->DoNextInnerPacket(network_time, 0, inner, e, ec); + } diff --git a/src/analyzer/protocol/vxlan/VXLAN.h b/src/analyzer/protocol/vxlan/VXLAN.h new file mode 100644 index 0000000000..e0f8dd99aa --- /dev/null +++ b/src/analyzer/protocol/vxlan/VXLAN.h @@ -0,0 +1,87 @@ +#ifndef ANALYZER_PROTOCOL_VXLAN_VXLAN_H +#define ANALYZER_PROTOCOL_VXLAN_VXLAN_H + +#include "analyzer/Analyzer.h" +#include "NetVar.h" +#include "Reporter.h" + +namespace analyzer { namespace vxlan { + +class VXLAN_Analyzer : public analyzer::Analyzer { +public: + explicit VXLAN_Analyzer(Connection* conn) : Analyzer("VXLAN", conn), + valid_orig(false), valid_resp(false) + {} + + ~VXLAN_Analyzer() override + {} + + void Done() override; + + void DeliverPacket(int len, const u_char* data, bool orig, + uint64 seq, const IP_Hdr* ip, int caplen) override; + + static analyzer::Analyzer* Instantiate(Connection* conn) + { return new VXLAN_Analyzer(conn); } + + /** + * Emits a weird only if the analyzer has previously been able to + * decapsulate a VXLAN packet in both directions or if *force* param is + * set, since otherwise the weirds could happen frequently enough to be less + * than helpful. The *force* param is meant for cases where just one side + * has a valid encapsulation and so the weird would be informative. + */ + void Weird(const char* name, bool force = false) const + { + if ( ProtocolConfirmed() || force ) + reporter->Weird(Conn(), name); + } + + /** + * If the delayed confirmation option is set, then a valid encapsulation + * seen from both end points is required before confirming. + */ +/* copied from Teredo, do we want this too for VXLAN? + void Confirm() + { + if ( ! BifConst::Tunnel::delay_vxlan_confirmation || + ( valid_orig && valid_resp ) ) + ProtocolConfirmation(); + }*/ + +protected: + bool valid_orig; + bool valid_resp; +}; + +class VXLANEncapsulation { +public: + explicit VXLANEncapsulation(const VXLAN_Analyzer* ta) + : inner_ip(0), analyzer(ta) + {} + + /** + * Returns whether input data parsed as a valid VXLAN encapsulation type. + * If it was valid, the len argument is decremented appropriately. + */ + bool Parse(const u_char* data, int& len) + { return DoParse(data, len); } + + const u_char* InnerIP() const + { return inner_ip; } + + RecordVal* BuildVal(const IP_Hdr* inner) const; + +protected: + bool DoParse(const u_char* data, int& len); + + void Weird(const char* name) const + { analyzer->Weird(name); } + + const u_char* inner_ip; + const VXLAN_Analyzer* analyzer; +}; + +} } // namespace analyzer::* + +#endif diff --git a/src/analyzer/protocol/vxlan/events.bif b/src/analyzer/protocol/vxlan/events.bif new file mode 100644 index 0000000000..9ed9fdc52b --- /dev/null +++ b/src/analyzer/protocol/vxlan/events.bif @@ -0,0 +1,12 @@ +## Generated for any packet encapsulated in a VXLAN tunnel. +## See :rfc:`7348` for more information about the VXLAN protocol. +## +## outer: The VXLAN tunnel connection. +## +## inner: The VXLAN-encapsulated Ethernet packet header and transport header. +## +## .. bro:see:: vxlan_authentication vxlan_origin_indication vxlan_bubble +## +## .. note:: Since this event may be raised on a per-packet basis, handling +## it may become particularly expensive for real-time analysis. +event vxlan_packet%(outer: connection, inner: vxlan_hdr%); diff --git a/src/const.bif b/src/const.bif index 2d062d854a..468929de05 100644 --- a/src/const.bif +++ b/src/const.bif @@ -19,6 +19,7 @@ const Tunnel::enable_ayiya: bool; const Tunnel::enable_teredo: bool; const Tunnel::enable_gtpv1: bool; const Tunnel::enable_gre: bool; +const Tunnel::enable_vxlan: bool; const Tunnel::delay_teredo_confirmation: bool; const Tunnel::delay_gtp_confirmation: bool; const Tunnel::ip_tunnel_timeout: interval; diff --git a/src/types.bif b/src/types.bif index 145a8af89e..babccb0f0d 100644 --- a/src/types.bif +++ b/src/types.bif @@ -192,6 +192,7 @@ enum Type %{ GTPv1, HTTP, GRE, + VXLAN, %} type EncapsulatingConn: record; diff --git a/testing/btest/Baseline/core.tunnels.vxlan/conn.log b/testing/btest/Baseline/core.tunnels.vxlan/conn.log new file mode 100644 index 0000000000..6531850a0c --- /dev/null +++ b/testing/btest/Baseline/core.tunnels.vxlan/conn.log @@ -0,0 +1,14 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path conn +#open 2018-10-18-11-51-46 +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto service duration orig_bytes resp_bytes conn_state local_orig local_resp missed_bytes history orig_pkts orig_ip_bytes resp_pkts resp_ip_bytes tunnel_parents +#types time string addr port addr port enum string interval count count string bool bool count string count count count count set[string] +1368908504.882198 CUY3VO38piNbzBWoCf 192.168.202.1 42710 192.168.203.1 4789 udp - - - - S0 - - 0 D 1 78 0 0 - +1368908504.882536 C938WE2Zxjsr1dQt8 192.168.203.1 52102 192.168.202.1 4789 udp - - - - S0 - - 0 D 1 78 0 0 - +1368908504.925960 CPPxeT3vy9lhCeFyzf 192.168.202.1 32894 192.168.203.1 4789 udp - 2.959399 424 0 S0 - - 0 D 4 536 0 0 - +1368908504.837063 CAL8II3MrNKoLygbR 192.168.203.1 45149 192.168.202.1 4789 udp - 3.004913 424 0 S0 - - 0 D 4 536 0 0 - +1368908504.837063 C3MYEy2ilZOiJASuTk 192.168.203.3 8 192.168.203.5 0 icmp - 3.048296 224 224 OTH - - 0 - 4 336 4 336 CAL8II3MrNKoLygbR,CPPxeT3vy9lhCeFyzf +#close 2018-10-18-11-51-46 diff --git a/testing/btest/Baseline/core.tunnels.vxlan/tunnel.log b/testing/btest/Baseline/core.tunnels.vxlan/tunnel.log new file mode 100644 index 0000000000..0e9523525c --- /dev/null +++ b/testing/btest/Baseline/core.tunnels.vxlan/tunnel.log @@ -0,0 +1,13 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path tunnel +#open 2018-10-18-11-51-46 +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p tunnel_type action +#types time string addr port addr port enum enum +1368908504.837063 CAL8II3MrNKoLygbR 192.168.203.1 45149 192.168.202.1 4789 Tunnel::VXLAN Tunnel::DISCOVER +1368908504.925960 CPPxeT3vy9lhCeFyzf 192.168.202.1 32894 192.168.203.1 4789 Tunnel::VXLAN Tunnel::DISCOVER +1368908507.885359 CPPxeT3vy9lhCeFyzf 192.168.202.1 32894 192.168.203.1 4789 Tunnel::VXLAN Tunnel::CLOSE +1368908507.885359 CAL8II3MrNKoLygbR 192.168.203.1 45149 192.168.202.1 4789 Tunnel::VXLAN Tunnel::CLOSE +#close 2018-10-18-11-51-46 diff --git a/testing/btest/core/tunnels/vxlan.test b/testing/btest/core/tunnels/vxlan.test new file mode 100644 index 0000000000..9a77f9c285 --- /dev/null +++ b/testing/btest/core/tunnels/vxlan.test @@ -0,0 +1,3 @@ +# @TEST-EXEC: bro -r $TRACES/tunnels/vxlan-sample.pcap +# @TEST-EXEC: btest-diff conn.log +# @TEST-EXEC: btest-diff tunnel.log