From 660b68a08331e9eb711eb4ace74c7a3c9493f298 Mon Sep 17 00:00:00 2001 From: Carl Smith Date: Mon, 17 Aug 2020 17:29:05 +1200 Subject: [PATCH] nsh: Parsing of Network Services Header and payload Support for EtherType 0x894F and basic header --- src/Makefile.am | 1 + src/decode-ethernet.h | 1 + src/decode-events.c | 26 ++++ src/decode-events.h | 10 +- src/decode-nsh.c | 289 ++++++++++++++++++++++++++++++++++++++++ src/decode-nsh.h | 52 ++++++++ src/decode.c | 3 + src/decode.h | 7 + src/runmode-unittests.c | 1 + 9 files changed, 389 insertions(+), 1 deletion(-) create mode 100644 src/decode-nsh.c create mode 100644 src/decode-nsh.h diff --git a/src/Makefile.am b/src/Makefile.am index 670848c70b..825ce65d0d 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -75,6 +75,7 @@ decode-icmpv4.c decode-icmpv4.h \ decode-icmpv6.c decode-icmpv6.h \ decode-ipv4.c decode-ipv4.h \ decode-ipv6.c decode-ipv6.h \ +decode-nsh.c decode-nsh.h \ decode-null.c decode-null.h \ decode-ppp.c decode-ppp.h \ decode-pppoe.c decode-pppoe.h \ diff --git a/src/decode-ethernet.h b/src/decode-ethernet.h index 93ed61af89..7bbfa62df8 100644 --- a/src/decode-ethernet.h +++ b/src/decode-ethernet.h @@ -48,6 +48,7 @@ #define ETHERNET_TYPE_ERSPAN 0x88BE #define ETHERNET_TYPE_DCE 0x8903 /* Data center ethernet, * Cisco Fabric Path */ +#define ETHERNET_TYPE_NSH 0x894F typedef struct EthernetHdr_ { uint8_t eth_dst[6]; diff --git a/src/decode-events.c b/src/decode-events.c index 3f18aabf9a..2f2373cd0b 100644 --- a/src/decode-events.c +++ b/src/decode-events.c @@ -535,6 +535,32 @@ const struct DecodeEvents_ DEvents[] = { CHDLC_PKT_TOO_SMALL, }, + /* NSH events */ + { + "decoder.nsh.header_too_small", + NSH_HEADER_TOO_SMALL, + }, + { + "decoder.nsh.unsupported_version", + NSH_UNSUPPORTED_VERSION, + }, + { + "decoder.nsh.bad_header_length", + NSH_BAD_HEADER_LENGTH, + }, + { + "decoder.nsh.reserved_type", + NSH_RESERVED_TYPE, + }, + { + "decoder.nsh.unsupported_type", + NSH_UNSUPPORTED_TYPE, + }, + { + "decoder.nsh.unknown_payload", + NSH_UNKNOWN_PAYLOAD, + }, + /* STREAM EVENTS */ { "stream.3whs_ack_in_wrong_dir", diff --git a/src/decode-events.h b/src/decode-events.h index 434dae1391..2c8467f1cd 100644 --- a/src/decode-events.h +++ b/src/decode-events.h @@ -200,8 +200,16 @@ enum { /* Cisco HDLC events. */ CHDLC_PKT_TOO_SMALL, + /* NSH events */ + NSH_HEADER_TOO_SMALL, + NSH_UNSUPPORTED_VERSION, + NSH_BAD_HEADER_LENGTH, + NSH_RESERVED_TYPE, + NSH_UNSUPPORTED_TYPE, + NSH_UNKNOWN_PAYLOAD, + /* END OF DECODE EVENTS ON SINGLE PACKET */ - DECODE_EVENT_PACKET_MAX = CHDLC_PKT_TOO_SMALL, + DECODE_EVENT_PACKET_MAX = NSH_UNKNOWN_PAYLOAD, /* STREAM EVENTS */ STREAM_3WHS_ACK_IN_WRONG_DIR, diff --git a/src/decode-nsh.c b/src/decode-nsh.c new file mode 100644 index 0000000000..7a3355ddb1 --- /dev/null +++ b/src/decode-nsh.c @@ -0,0 +1,289 @@ +/* Copyright (C) 2020 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 + * Software Foundation. + * + * 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 + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \ingroup decode + * + * @{ + */ + +/** + * \file + * + * \author Carl Smith + * + * Decodes Network Service Header (NSH) + */ + +#include "suricata-common.h" +#include "suricata.h" +#include "decode.h" +#include "decode-events.h" +#include "decode-nsh.h" + +#include "util-unittest.h" +#include "util-debug.h" + +/** + * \brief Function to decode NSH packets + */ + +int DecodeNSH(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p, const uint8_t *pkt, uint32_t len) +{ + StatsIncr(tv, dtv->counter_nsh); + + /* Check mimimum header size */ + if (len < sizeof(NshHdr)) { + ENGINE_SET_INVALID_EVENT(p, NSH_HEADER_TOO_SMALL); + return TM_ECODE_FAILED; + } + + /* Sanity check the header version */ + const NshHdr *hdr = (const NshHdr *)pkt; + uint16_t version = SCNtohs(hdr->ver_flags_len) >> 14; + if (version != 0) { + ENGINE_SET_EVENT(p, NSH_UNSUPPORTED_VERSION); + return TM_ECODE_OK; + } + + /* Should always be some data after the header */ + uint16_t length = (SCNtohs(hdr->ver_flags_len) & 0x003f) * 4; + if (length >= len) { + ENGINE_SET_INVALID_EVENT(p, NSH_BAD_HEADER_LENGTH); + return TM_ECODE_FAILED; + } + + /* Check for valid MD types */ + uint8_t md_type = hdr->md_type; + if (md_type == 0 || md_type == 0xF) { + /* We should silently ignore these packets */ + ENGINE_SET_EVENT(p, NSH_RESERVED_TYPE); + return TM_ECODE_OK; + } else if (md_type == 1) { + /* Fixed header length format */ + if (length != 24) { + ENGINE_SET_INVALID_EVENT(p, NSH_BAD_HEADER_LENGTH); + return TM_ECODE_FAILED; + } + } else if (md_type != 2) { + /* Not variable header length either */ + ENGINE_SET_EVENT(p, NSH_UNSUPPORTED_TYPE); + return TM_ECODE_OK; + } + + /* Now we can safely read the rest of the header */ + uint8_t next_protocol = hdr->next_protocol; +#ifdef DEBUG + if (SCLogDebugEnabled()) { + uint32_t spi_si = SCNtohl(hdr->spi_si); + uint32_t spi = ((spi_si & 0xFFFFFF00) >> 8); + uint8_t si = (uint8_t)(spi_si & 0xFF); + SCLogDebug("NSH: version %u length %u spi %u si %u next_protocol %u", version, length, spi, + si, next_protocol); + } +#endif /* DEBUG */ + + /* Try to decode the payload */ + switch (next_protocol) { + case NSH_NEXT_PROTO_IPV4: + return DecodeIPV4(tv, dtv, p, pkt + length, len - length); + case NSH_NEXT_PROTO_IPV6: + return DecodeIPV6(tv, dtv, p, pkt + length, len - length); + case NSH_NEXT_PROTO_ETHERNET: + return DecodeEthernet(tv, dtv, p, pkt + length, len - length); + case NSH_NEXT_PROTO_MPLS: + return DecodeMPLS(tv, dtv, p, pkt + length, len - length); + case NSH_NEXT_PROTO_NSH: + default: + SCLogDebug("NSH next protocol %u not supported", next_protocol); + ENGINE_SET_EVENT(p, NSH_UNKNOWN_PAYLOAD); + break; + } + return TM_ECODE_OK; +} + +#ifdef UNITTESTS + +static uint8_t valid_nsh_packet[] = { 0x00, 0x04, 0x02, 0x01, 0x00, 0x00, 0x02, 0x02, 0x45, 0x10, + 0x00, 0x3c, 0x78, 0x8f, 0x40, 0x00, 0x3f, 0x06, 0x79, 0x05, 0x0b, 0x06, 0x06, 0x06, 0x33, 0x06, + 0x06, 0x06, 0xbd, 0x2e, 0x00, 0x16, 0xc9, 0xee, 0x07, 0x62, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x02, + 0x16, 0xd0, 0x2f, 0x36, 0x00, 0x00, 0x02, 0x04, 0x05, 0xb4, 0x04, 0x02, 0x08, 0x0a, 0xa9, 0x5f, + 0x7f, 0xed, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x07 }; + +static int DecodeNSHTestHeaderTooSmall(void) +{ + ThreadVars tv; + DecodeThreadVars dtv; + Packet *p; + + p = SCMalloc(SIZE_OF_PACKET); + FAIL_IF_NULL(p); + memset(&dtv, 0, sizeof(DecodeThreadVars)); + memset(&tv, 0, sizeof(ThreadVars)); + memset(p, 0, SIZE_OF_PACKET); + + /* A packet that is too small to have a complete NSH header */ + DecodeNSH(&tv, &dtv, p, valid_nsh_packet, 7); + FAIL_IF_NOT(ENGINE_ISSET_EVENT(p, NSH_HEADER_TOO_SMALL)); + + SCFree(p); + PASS; +} + +static int DecodeNSHTestUnsupportedVersion(void) +{ + ThreadVars tv; + DecodeThreadVars dtv; + Packet *p; + + p = SCMalloc(SIZE_OF_PACKET); + FAIL_IF_NULL(p); + memset(&dtv, 0, sizeof(DecodeThreadVars)); + memset(&tv, 0, sizeof(ThreadVars)); + memset(p, 0, SIZE_OF_PACKET); + + /* Non-zero version field */ + valid_nsh_packet[0] = 0xFF; + DecodeNSH(&tv, &dtv, p, valid_nsh_packet, sizeof(valid_nsh_packet)); + valid_nsh_packet[0] = 0x00; + FAIL_IF_NOT(ENGINE_ISSET_EVENT(p, NSH_UNSUPPORTED_VERSION)); + + SCFree(p); + PASS; +} + +static int DecodeNSHTestPacketTooSmall(void) +{ + ThreadVars tv; + DecodeThreadVars dtv; + Packet *p; + + p = SCMalloc(SIZE_OF_PACKET); + FAIL_IF_NULL(p); + memset(&dtv, 0, sizeof(DecodeThreadVars)); + memset(&tv, 0, sizeof(ThreadVars)); + memset(p, 0, SIZE_OF_PACKET); + + /* A packet that has no payload */ + DecodeNSH(&tv, &dtv, p, valid_nsh_packet, 8); + FAIL_IF_NOT(ENGINE_ISSET_EVENT(p, NSH_BAD_HEADER_LENGTH)); + + SCFree(p); + PASS; +} + +static int DecodeNSHTestReservedType(void) +{ + ThreadVars tv; + DecodeThreadVars dtv; + Packet *p; + + p = SCMalloc(SIZE_OF_PACKET); + FAIL_IF_NULL(p); + memset(&dtv, 0, sizeof(DecodeThreadVars)); + memset(&tv, 0, sizeof(ThreadVars)); + memset(p, 0, SIZE_OF_PACKET); + + /* Reserved type */ + valid_nsh_packet[2] = 0x00; + DecodeNSH(&tv, &dtv, p, valid_nsh_packet, sizeof(valid_nsh_packet)); + valid_nsh_packet[2] = 0x02; + FAIL_IF_NOT(ENGINE_ISSET_EVENT(p, NSH_RESERVED_TYPE)); + + SCFree(p); + PASS; +} + +static int DecodeNSHTestInvalidType(void) +{ + ThreadVars tv; + DecodeThreadVars dtv; + Packet *p; + + p = SCMalloc(SIZE_OF_PACKET); + FAIL_IF_NULL(p); + memset(&dtv, 0, sizeof(DecodeThreadVars)); + memset(&tv, 0, sizeof(ThreadVars)); + memset(p, 0, SIZE_OF_PACKET); + + /* Type length mismatch */ + valid_nsh_packet[2] = 0x01; + DecodeNSH(&tv, &dtv, p, valid_nsh_packet, sizeof(valid_nsh_packet)); + valid_nsh_packet[2] = 0x02; + FAIL_IF_NOT(ENGINE_ISSET_EVENT(p, NSH_BAD_HEADER_LENGTH)); + SCFree(p); + PASS; +} + +static int DecodeNSHTestUnsupportedType(void) +{ + ThreadVars tv; + DecodeThreadVars dtv; + Packet *p; + + p = SCMalloc(SIZE_OF_PACKET); + FAIL_IF_NULL(p); + memset(&dtv, 0, sizeof(DecodeThreadVars)); + memset(&tv, 0, sizeof(ThreadVars)); + memset(p, 0, SIZE_OF_PACKET); + + /* Unsupported type */ + valid_nsh_packet[2] = 0x03; + DecodeNSH(&tv, &dtv, p, valid_nsh_packet, sizeof(valid_nsh_packet)); + valid_nsh_packet[2] = 0x02; + FAIL_IF_NOT(ENGINE_ISSET_EVENT(p, NSH_UNSUPPORTED_TYPE)); + + SCFree(p); + PASS; +} + +static int DecodeNSHTestUnknownPayload(void) +{ + ThreadVars tv; + DecodeThreadVars dtv; + Packet *p; + + p = SCMalloc(SIZE_OF_PACKET); + FAIL_IF_NULL(p); + memset(&dtv, 0, sizeof(DecodeThreadVars)); + memset(&tv, 0, sizeof(ThreadVars)); + memset(p, 0, SIZE_OF_PACKET); + + /* Unknown type */ + valid_nsh_packet[3] = 0x99; + DecodeNSH(&tv, &dtv, p, valid_nsh_packet, sizeof(valid_nsh_packet)); + valid_nsh_packet[3] = 0x01; + FAIL_IF_NOT(ENGINE_ISSET_EVENT(p, NSH_UNKNOWN_PAYLOAD)); + + SCFree(p); + PASS; +} + +#endif /* UNITTESTS */ + +void DecodeNSHRegisterTests(void) +{ +#ifdef UNITTESTS + UtRegisterTest("DecodeNSHTestHeaderTooSmall", DecodeNSHTestHeaderTooSmall); + UtRegisterTest("DecodeNSHTestUnsupportedVersion", DecodeNSHTestUnsupportedVersion); + UtRegisterTest("DecodeNSHTestPacketTooSmall", DecodeNSHTestPacketTooSmall); + UtRegisterTest("DecodeNSHTestReservedType", DecodeNSHTestReservedType); + UtRegisterTest("DecodeNSHTestInvalidType", DecodeNSHTestInvalidType); + UtRegisterTest("DecodeNSHTestUnsupportedType", DecodeNSHTestUnsupportedType); + UtRegisterTest("DecodeNSHTestUnknownPayload", DecodeNSHTestUnknownPayload); +#endif /* UNITTESTS */ +} diff --git a/src/decode-nsh.h b/src/decode-nsh.h new file mode 100644 index 0000000000..e9c6af065b --- /dev/null +++ b/src/decode-nsh.h @@ -0,0 +1,52 @@ +/* Copyright (C) 2020 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 + * Software Foundation. + * + * 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 + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Carl Smith + * + */ + +#ifndef __DECODE_NSH_H__ +#define __DECODE_NSH_H__ + +#include "decode.h" +#include "threadvars.h" + +#define NSH_NEXT_PROTO_UNASSIGNED 0x0 +#define NSH_NEXT_PROTO_IPV4 0x1 +#define NSH_NEXT_PROTO_IPV6 0x2 +#define NSH_NEXT_PROTO_ETHERNET 0x3 +#define NSH_NEXT_PROTO_NSH 0x4 +#define NSH_NEXT_PROTO_MPLS 0x5 +#define NSH_NEXT_PROTO_EXPERIMENT1 0xFE +#define NSH_NEXT_PROTO_EXPERIMENT2 0xFF + +/* + * Network Service Header (NSH) + */ +typedef struct NshHdr_ { + uint16_t ver_flags_len; + uint8_t md_type; + uint8_t next_protocol; + uint32_t spi_si; +} __attribute__((packed)) NshHdr; + +void DecodeNSHRegisterTests(void); + +#endif /* __DECODE_NSH_H__ */ diff --git a/src/decode.c b/src/decode.c index 0c0ca72f1d..93aec6338c 100644 --- a/src/decode.c +++ b/src/decode.c @@ -92,6 +92,8 @@ int DecodeTunnel(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p, return DecodeERSPAN(tv, dtv, p, pkt, len); case DECODE_TUNNEL_ERSPANI: return DecodeERSPANTypeI(tv, dtv, p, pkt, len); + case DECODE_TUNNEL_NSH: + return DecodeNSH(tv, dtv, p, pkt, len); default: SCLogDebug("FIXME: DecodeTunnel: protocol %" PRIu32 " not supported.", proto); break; @@ -517,6 +519,7 @@ void DecodeRegisterPerfCounters(DecodeThreadVars *dtv, ThreadVars *tv) dtv->counter_max_mac_addrs_src = StatsRegisterMaxCounter("decoder.max_mac_addrs_src", tv); dtv->counter_max_mac_addrs_dst = StatsRegisterMaxCounter("decoder.max_mac_addrs_dst", tv); dtv->counter_erspan = StatsRegisterMaxCounter("decoder.erspan", tv); + dtv->counter_nsh = StatsRegisterMaxCounter("decoder.nsh", tv); dtv->counter_flow_memcap = StatsRegisterCounter("flow.memcap", tv); dtv->counter_flow_tcp = StatsRegisterCounter("flow.tcp", tv); diff --git a/src/decode.h b/src/decode.h index fc298d94bd..7a583cf29b 100644 --- a/src/decode.h +++ b/src/decode.h @@ -95,6 +95,7 @@ enum PktSrcEnum { #include "decode-vlan.h" #include "decode-vxlan.h" #include "decode-mpls.h" +#include "decode-nsh.h" #include "detect-reference.h" @@ -668,6 +669,7 @@ typedef struct DecodeThreadVars_ uint16_t counter_ipv4inipv6; uint16_t counter_ipv6inipv6; uint16_t counter_erspan; + uint16_t counter_nsh; /** frag stats - defrag runs in the context of the decoder. */ uint16_t counter_defrag_ipv4_fragments; @@ -908,6 +910,7 @@ enum DecodeTunnelProto { DECODE_TUNNEL_IPV6, DECODE_TUNNEL_IPV6_TEREDO, /**< separate protocol for stricter error handling */ DECODE_TUNNEL_PPP, + DECODE_TUNNEL_NSH, DECODE_TUNNEL_UNSET }; @@ -962,6 +965,7 @@ int DecodeERSPAN(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, ui int DecodeERSPANTypeI(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint32_t); int DecodeCHDLC(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint32_t); int DecodeTEMPLATE(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint32_t); +int DecodeNSH(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint32_t); #ifdef UNITTESTS void DecodeIPV6FragHeader(Packet *p, const uint8_t *pkt, @@ -1259,6 +1263,9 @@ static inline bool DecodeNetworkLayer(ThreadVars *tv, DecodeThreadVars *dtv, DecodeEthernet(tv, dtv, p, data, len); } break; + case ETHERNET_TYPE_NSH: + DecodeNSH(tv, dtv, p, data, len); + break; default: SCLogDebug("unknown ether type: %" PRIx16 "", proto); return false; diff --git a/src/runmode-unittests.c b/src/runmode-unittests.c index 7f458808cf..0a631a59b5 100644 --- a/src/runmode-unittests.c +++ b/src/runmode-unittests.c @@ -159,6 +159,7 @@ static void RegisterUnittests(void) DecodeUDPV4RegisterTests(); DecodeGRERegisterTests(); DecodeMPLSRegisterTests(); + DecodeNSHRegisterTests(); AppLayerProtoDetectUnittestsRegister(); ConfRegisterTests(); ConfYamlRegisterTests();