From 82de6e06590cce00056d6886c8739271fbd33b05 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Wed, 26 Jun 2019 12:22:33 +0200 Subject: [PATCH] decoder/vxlan: improvements and cleanups Implement port config handling. Also check both src port and dest port for tunnels that only set the destination port to the VXLAN port. At the point of the check we don't know the packet direction yet. Implement as Suricata tunnel similar to Teredo. Cleanups. --- src/decode-udp.c | 6 +- src/decode-vxlan.c | 198 +++++++++++++++++++++++++++++----------- src/decode-vxlan.h | 1 + src/decode.c | 4 + src/decode.h | 2 + src/runmode-unittests.c | 1 + suricata.yaml.in | 8 +- 7 files changed, 166 insertions(+), 54 deletions(-) diff --git a/src/decode-udp.c b/src/decode-udp.c index 5585034037..7a90653f12 100644 --- a/src/decode-udp.c +++ b/src/decode-udp.c @@ -89,8 +89,10 @@ int DecodeUDP(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p, uint8_t *pkt, ui FlowSetupPacket(p); return TM_ECODE_OK; } - /* TODO hardcoded port 4789 - to avoid spending time on non-VXLAN */ - if (UDP_GET_DST_PORT(p) == 4789 && unlikely(DecodeVXLAN(tv, dtv, p, pkt,len, pq) == TM_ECODE_OK)) { + + /* Handle VXLAN if configured */ + if (DecodeVXLANEnabledForPort(p->sp, p->dp) && + unlikely(DecodeVXLAN(tv, dtv, p, p->payload, p->payload_len, pq) == TM_ECODE_OK)) { /* Here we have a VXLAN packet and don't need to handle app * layer */ FlowSetupPacket(p); diff --git a/src/decode-vxlan.c b/src/decode-vxlan.c index ff6e4cb563..3887007acb 100644 --- a/src/decode-vxlan.c +++ b/src/decode-vxlan.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2014 Open Information Security Foundation +/* Copyright (C) 2019 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free @@ -27,9 +27,8 @@ #include "decode.h" #include "decode-vxlan.h" #include "decode-events.h" -#include "decode-udp.h" -#include "decode-ethernet.h" +#include "detect-engine-port.h" #include "flow.h" @@ -40,9 +39,51 @@ #include "util-profiling.h" #include "host.h" -#define VXLAN_HEADER_LEN 8 +#define VXLAN_HEADER_LEN 8 +#define VXLAN_DEFAULT_PORT 4789 +#define VXLAN_DEFAULT_PORT_S "4789" static bool g_vxlan_enabled = true; +static int g_vxlan_ports[4] = { VXLAN_DEFAULT_PORT, -1, -1, -1 }; +static int g_vxlan_ports_idx = 0; + +bool DecodeVXLANEnabledForPort(const uint16_t sp, const uint16_t dp) +{ + SCLogDebug("ports %u->%u ports %d %d %d %d", sp, dp, + g_vxlan_ports[0], g_vxlan_ports[1], + g_vxlan_ports[2], g_vxlan_ports[3]); + + if (g_vxlan_enabled) { + for (int i = 0; i < g_vxlan_ports_idx; i++) { + if (g_vxlan_ports[i] == -1) + return false; + const int port = g_vxlan_ports[i]; + if (port == (const int)sp || + port == (const int)dp) + return true; + } + } + return false; +} + +static void DecodeVXLANConfigPorts(const char *pstr) +{ + SCLogDebug("parsing \'%s\'", pstr); + + DetectPort *head = NULL; + DetectPortParse(NULL, &head, pstr); + + g_vxlan_ports_idx = 0; + for (DetectPort *p = head; p != NULL; p = p->next) { + if (g_vxlan_ports_idx >= 4) { + SCLogWarning(SC_ERR_INVALID_YAML_CONF_ENTRY, + "more than 4 VXLAN ports defined"); + break; + } + g_vxlan_ports[g_vxlan_ports_idx++] = (int)p->port; + } + DetectPortCleanupList(NULL, head); +} void DecodeVXLANConfig(void) { @@ -54,78 +95,86 @@ void DecodeVXLANConfig(void) g_vxlan_enabled = false; } } -} -static int DecodeVXLANPacket(ThreadVars *t, Packet *p, uint8_t *pkt, uint16_t len) -{ - if (unlikely(len < UDP_HEADER_LEN)) { - ENGINE_SET_INVALID_EVENT(p, UDP_HLEN_TOO_SMALL); - return -1; + if (g_vxlan_enabled) { + ConfNode *node = ConfGetNode("decoder.vxlan.ports"); + if (node && node->val) { + DecodeVXLANConfigPorts(node->val); + } else { + DecodeVXLANConfigPorts(VXLAN_DEFAULT_PORT_S); + } } - - p->udph = (UDPHdr *)pkt; - - SET_UDP_SRC_PORT(p,&p->sp); - SET_UDP_DST_PORT(p,&p->dp); - - p->payload = pkt + UDP_HEADER_LEN; - p->payload_len = len - UDP_HEADER_LEN; - p->proto = IPPROTO_UDP; - - return 0; } +typedef struct VXLANHeader_ { + uint8_t flags[2]; + uint16_t gdp; + uint8_t vni[3]; + uint8_t res; +} VXLANHeader; + +/** \param pkt payload data directly above UDP header + * \param len length in bytes of pkt + */ int DecodeVXLAN(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p, uint8_t *pkt, uint32_t len, PacketQueue *pq) { - if (!g_vxlan_enabled) + if (unlikely(!g_vxlan_enabled)) return TM_ECODE_FAILED; - /* Is this packet to short to contain an IPv4/IPv6 packet ? */ - if (len < IPV4_HEADER_LEN) + if (len < (sizeof(VXLANHeader) + sizeof(EthernetHdr))) return TM_ECODE_FAILED; - int event = 0; - - if (unlikely(DecodeVXLANPacket(tv, p,pkt,len) < 0)) { - p->udph = NULL; + const VXLANHeader *vxlanh = (const VXLANHeader *)pkt; + if ((vxlanh->flags[0] & 0x08) == 0 || vxlanh->res != 0) { return TM_ECODE_FAILED; } - StatsIncr(tv, dtv->counter_vxlan); - SCLogDebug("VXLAN UDP sp: %" PRIu32 " -> dp: %" PRIu32 " - HLEN: %" PRIu32 " LEN: %" PRIu32 "", - UDP_GET_SRC_PORT(p), UDP_GET_DST_PORT(p), UDP_HEADER_LEN, p->payload_len); +#if DEBUG + uint32_t vni = (vxlanh->vni[0] << 16) + (vxlanh->vni[1] << 8) + (vxlanh->vni[2]); + SCLogDebug("VXLAN vni %u", vni); +#endif + + StatsIncr(tv, dtv->counter_vxlan); /* VXLAN encapsulate Layer 2 in UDP, most likely IPv4 and IPv6 */ - p->ethh = (EthernetHdr *)(pkt + UDP_HEADER_LEN + VXLAN_HEADER_LEN); - SCLogDebug("VXLAN Ethertype 0x%04x", SCNtohs(p->ethh->eth_type)); + EthernetHdr *ethh = (EthernetHdr *)(pkt + VXLAN_HEADER_LEN); + SCLogDebug("VXLAN ethertype 0x%04x", SCNtohs(ethh->eth_type)); + /* Best guess at inner packet. */ - switch (SCNtohs(p->ethh->eth_type)) { + switch (SCNtohs(ethh->eth_type)) { case ETHERNET_TYPE_ARP: SCLogDebug("VXLAN found ARP"); break; case ETHERNET_TYPE_IP: SCLogDebug("VXLAN found IPv4"); - /* DecodeIPV4(tv, dtv, p, pkt, len, pq); */ + if (pq != NULL) { + Packet *tp = PacketTunnelPktSetup(tv, dtv, p, pkt + VXLAN_HEADER_LEN + ETHERNET_HEADER_LEN, + len - (VXLAN_HEADER_LEN + ETHERNET_HEADER_LEN), DECODE_TUNNEL_IPV4, pq); + if (tp != NULL) { + PKT_SET_SRC(tp, PKT_SRC_DECODER_VXLAN); + PacketEnqueue(pq, tp); + } + } break; case ETHERNET_TYPE_IPV6: SCLogDebug("VXLAN found IPv6"); - /* DecodeIPV6(tv, dtv, p, pkt, len, pq); */ + if (pq != NULL) { + Packet *tp = PacketTunnelPktSetup(tv, dtv, p, pkt + VXLAN_HEADER_LEN + ETHERNET_HEADER_LEN, + len - (VXLAN_HEADER_LEN + ETHERNET_HEADER_LEN), DECODE_TUNNEL_IPV6, pq); + if (tp != NULL) { + PKT_SET_SRC(tp, PKT_SRC_DECODER_VXLAN); + PacketEnqueue(pq, tp); + } + } break; default: SCLogDebug("VXLAN found no known Ethertype - only checks for IPv4, IPv6, ARP"); /* ENGINE_SET_INVALID_EVENT(p, VXLAN_UNKNOWN_PAYLOAD_TYPE);*/ - /* return TM_ECODE_OK; */ + break; } - SCLogDebug("VXLAN trying to decode with Ethernet"); - DecodeEthernet(tv, dtv, p, pkt + UDP_HEADER_LEN + VXLAN_HEADER_LEN, - len - UDP_HEADER_LEN - VXLAN_HEADER_LEN, pq); - - if (event) { - ENGINE_SET_EVENT(p, event); - } return TM_ECODE_OK; } @@ -134,9 +183,6 @@ int DecodeVXLAN(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p, uint8_t *pkt, /** * \test DecodeVXLANTest01 test a good vxlan header. * Contains a DNS request packet - * - * \retval 1 on success - * \retval 0 on failure */ static int DecodeVXLANtest01 (void) { @@ -145,7 +191,50 @@ static int DecodeVXLANtest01 (void) 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x00, /* VXLAN header */ 0x10, 0x00, 0x00, 0x0c, 0x01, 0x00, /* inner destination MAC */ 0x00, 0x51, 0x52, 0xb3, 0x54, 0xe5, /* inner source MAC */ - 0x08, 0x00, /* wot another IPv4 0x0800 */ + 0x08, 0x00, /* another IPv4 0x0800 */ + 0x45, 0x00, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11, + 0x44, 0x45, 0x0a, 0x60, 0x00, 0x0a, 0xb9, 0x1b, 0x73, 0x06, /* IPv4 hdr */ + 0x00, 0x35, 0x30, 0x39, 0x00, 0x08, 0x98, 0xe4 /* UDP probe src port 53 */ + }; + Packet *p = PacketGetFromAlloc(); + FAIL_IF_NULL(p); + ThreadVars tv; + DecodeThreadVars dtv; + PacketQueue pq; + + DecodeVXLANConfigPorts("4789"); + + memset(&pq, 0, sizeof(PacketQueue)); + memset(&tv, 0, sizeof(ThreadVars)); + memset(p, 0, SIZE_OF_PACKET); + memset(&dtv, 0, sizeof(DecodeThreadVars)); + + FlowInitConfig(FLOW_QUIET); + DecodeUDP(&tv, &dtv, p, raw_vxlan, sizeof(raw_vxlan), &pq); + + FAIL_IF(p->udph == NULL); + FAIL_IF(pq.top == NULL); + Packet *tp = PacketDequeue(&pq); + FAIL_IF(tp->udph == NULL); + FAIL_IF_NOT(tp->sp == 53); + + FlowShutdown(); + PacketFree(p); + PacketFree(tp); + PASS; +} + +/** + * \test test port disabled in config + */ +static int DecodeVXLANtest02 (void) +{ + uint8_t raw_vxlan[] = { + 0x12, 0xb5, 0x12, 0xb5, 0x00, 0x3a, 0x87, 0x51, /* UDP header */ + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x00, /* VXLAN header */ + 0x10, 0x00, 0x00, 0x0c, 0x01, 0x00, /* inner destination MAC */ + 0x00, 0x51, 0x52, 0xb3, 0x54, 0xe5, /* inner source MAC */ + 0x08, 0x00, /* another IPv4 0x0800 */ 0x45, 0x00, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11, 0x44, 0x45, 0x0a, 0x60, 0x00, 0x0a, 0xb9, 0x1b, 0x73, 0x06, /* IPv4 hdr */ 0x00, 0x35, 0x30, 0x39, 0x00, 0x08, 0x98, 0xe4 /* UDP probe src port 53 */ @@ -154,19 +243,24 @@ static int DecodeVXLANtest01 (void) FAIL_IF_NULL(p); ThreadVars tv; DecodeThreadVars dtv; + PacketQueue pq; + + DecodeVXLANConfigPorts("1"); + memset(&pq, 0, sizeof(PacketQueue)); memset(&tv, 0, sizeof(ThreadVars)); memset(p, 0, SIZE_OF_PACKET); memset(&dtv, 0, sizeof(DecodeThreadVars)); FlowInitConfig(FLOW_QUIET); - DecodeVXLAN(&tv, &dtv, p, raw_vxlan, sizeof(raw_vxlan), NULL); + DecodeUDP(&tv, &dtv, p, raw_vxlan, sizeof(raw_vxlan), &pq); FAIL_IF(p->udph == NULL); + FAIL_IF(pq.top != NULL); - PACKET_RECYCLE(p); + DecodeVXLANConfigPorts("4789"); /* reset */ FlowShutdown(); - SCFree(p); + PacketFree(p); PASS; } #endif /* UNITTESTS */ @@ -176,5 +270,7 @@ void DecodeVXLANRegisterTests(void) #ifdef UNITTESTS UtRegisterTest("DecodeVXLANtest01", DecodeVXLANtest01); + UtRegisterTest("DecodeVXLANtest02", + DecodeVXLANtest02); #endif /* UNITTESTS */ } diff --git a/src/decode-vxlan.h b/src/decode-vxlan.h index 747f2af5d6..988b35125a 100644 --- a/src/decode-vxlan.h +++ b/src/decode-vxlan.h @@ -28,5 +28,6 @@ void DecodeVXLANRegisterTests(void); void DecodeVXLANConfig(void); +bool DecodeVXLANEnabledForPort(const uint16_t sp, const uint16_t dp); #endif /* !__DECODE_VXLAN_H__ */ diff --git a/src/decode.c b/src/decode.c index 6b295a8e67..48af810dee 100644 --- a/src/decode.c +++ b/src/decode.c @@ -691,6 +691,9 @@ const char *PktSrcToString(enum PktSrcEnum pkt_src) case PKT_SRC_FFR: pkt_src_str = "stream (flow timeout)"; break; + case PKT_SRC_DECODER_VXLAN: + pkt_src_str = "vxlan encapsulation"; + break; } return pkt_src_str; } @@ -719,6 +722,7 @@ void CaptureStatsSetup(ThreadVars *tv, CaptureStats *s) void DecodeGlobalConfig(void) { DecodeTeredoConfig(); + DecodeVXLANConfig(); } /** diff --git a/src/decode.h b/src/decode.h index c4c4a51a42..719f15da87 100644 --- a/src/decode.h +++ b/src/decode.h @@ -55,6 +55,7 @@ enum PktSrcEnum { PKT_SRC_STREAM_TCP_STREAM_END_PSEUDO, PKT_SRC_FFR, PKT_SRC_STREAM_TCP_DETECTLOG_FLUSH, + PKT_SRC_DECODER_VXLAN, }; #include "source-nflog.h" @@ -1168,3 +1169,4 @@ static inline bool VerdictTunnelPacket(Packet *p) } #endif /* __DECODE_H__ */ + diff --git a/src/runmode-unittests.c b/src/runmode-unittests.c index 9ca42eb1f7..792e6f5c8d 100644 --- a/src/runmode-unittests.c +++ b/src/runmode-unittests.c @@ -310,3 +310,4 @@ void RunUnittests(int list_unittests, const char *regex_arg) exit(EXIT_FAILURE); #endif /* UNITTESTS */ } + diff --git a/suricata.yaml.in b/suricata.yaml.in index 4f7c1be79f..3d564ee8dd 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -44,6 +44,7 @@ vars: MODBUS_PORTS: 502 FILE_DATA_PORTS: "[$HTTP_PORTS,110,143]" FTP_PORTS: 21 + VXLAN_PORTS: 4789 ## ## Step 2: select outputs to enable @@ -1370,9 +1371,14 @@ host: decoder: # Teredo decoder is known to not be completely accurate - # it will sometimes detect non-teredo as teredo. + # as it will sometimes detect non-teredo as teredo. teredo: enabled: true + # VXLAN decoder is assigned to up to 4 UDP ports. By default only the + # IANA assigned port 4789 is enabled. + vxlan: + enabled: true + ports: $VXLAN_PORTS # syntax: '8472, 4789' ## -- 2.47.2