diff --git a/doc/userguide/Makefile.am b/doc/userguide/Makefile.am index e17ba86e12fe..b6c9888ba7fc 100644 --- a/doc/userguide/Makefile.am +++ b/doc/userguide/Makefile.am @@ -31,11 +31,11 @@ EXTRA_DIST = \ what-is-suricata.rst if HAVE_SURICATA_MAN -dist_man1_MANS = suricata.1 +dist_man1_MANS = suricata.1 suricatasc.1 suricatactl.1 suricatactl-filestore.1 endif if HAVE_SPHINXBUILD -dist_man1_MANS = suricata.1 +dist_man1_MANS = suricata.1 suricatasc.1 suricatactl.1 suricatactl-filestore.1 if HAVE_PDFLATEX EXTRA_DIST += userguide.pdf @@ -70,22 +70,23 @@ userguide.pdf: _build/latex/Suricata.pdf pdf: userguide.pdf -_build/man/suricata.1: +_build/man: manpages/suricata.rst manpages/suricatasc.rst manpages/suricatactl.rst manpages/suricatactl-filestore.rst sysconfdir=$(sysconfdir) \ localstatedir=$(localstatedir) \ version=$(PACKAGE_VERSION) \ $(SPHINX_BUILD) -W -b man -d _build/doctrees \ $(top_srcdir)/doc/userguide _build/man + touch _build/man -suricata.1: _build/man/suricata.1 - cp _build/man/suricata.1 suricata.1 +$(dist_man1_MANS): _build/man + cp _build/man/$@ . -man: _build/man/suricata.1 +man: $(dist_man1_MANS) # Remove build artifacts that aren't tracked by autotools. clean-local: rm -rf $(top_builddir)/doc/userguide/_build - rm -f $(top_builddir)/doc/userguide/suricata.1 + rm -f $(top_builddir)/doc/userguide/suricata*.1 rm -f $(top_builddir)/doc/userguide/userguide.pdf endif # HAVE_SPHINXBUILD diff --git a/doc/userguide/configuration/multi-tenant.rst b/doc/userguide/configuration/multi-tenant.rst index 683953a1669b..5ad0ee7c76ad 100644 --- a/doc/userguide/configuration/multi-tenant.rst +++ b/doc/userguide/configuration/multi-tenant.rst @@ -215,3 +215,10 @@ unregister-tenant-handler vlan The registration of tenant and tenant handlers can be done on a running engine. + +Eve JSON output +--------------- + +When multi-tenant support is configured and the detect engine is active then +all EVE-types that report based on flows will also report the corresponding +``tenant_id`` for events matching a tenant configuration. diff --git a/doc/userguide/lua/lua-functions.rst b/doc/userguide/lua/lua-functions.rst index 60a29ebab526..293b31cd7680 100644 --- a/doc/userguide/lua/lua-functions.rst +++ b/doc/userguide/lua/lua-functions.rst @@ -792,6 +792,15 @@ SCRuleIds sid, rev, gid = SCRuleIds() +SCRuleAction +~~~~~~~~~~~~ + +:: + + action = SCRuleAction() + +returns one of 'pass', 'reject', 'drop' or 'alert' + SCRuleMsg ~~~~~~~~~ diff --git a/doc/userguide/output/eve/eve-json-output.rst b/doc/userguide/output/eve/eve-json-output.rst index a195a021bdf8..b2079ea4571d 100644 --- a/doc/userguide/output/eve/eve-json-output.rst +++ b/doc/userguide/output/eve/eve-json-output.rst @@ -501,5 +501,12 @@ YAML:: # Seed value for the ID output. Valid values are 0-65535. community-id-seed: 0 +Multi Tenancy +------------- + +Suricata can be configured to support multiple tenants with different detection +engine configurations. When these tenants are configured and the detection +engine is running then all EVE logging will also report the ``tenant_id`` field +for traffic matching a specific tenant. .. _deprecation policy: https://suricata-ids.org/about/deprecation-policy/ diff --git a/ebpf/xdp_lb.c b/ebpf/xdp_lb.c index 2ee924572d67..dc1d8660c4df 100644 --- a/ebpf/xdp_lb.c +++ b/ebpf/xdp_lb.c @@ -23,6 +23,9 @@ #include #include #include +/* Workaround to avoid the need of 32bit headers */ +#define _LINUX_IF_H +#define IFNAMSIZ 16 #include #include #include diff --git a/rust/src/smb/nbss_records.rs b/rust/src/smb/nbss_records.rs index ae66f10880c7..8dae379e5079 100644 --- a/rust/src/smb/nbss_records.rs +++ b/rust/src/smb/nbss_records.rs @@ -87,3 +87,139 @@ named!(pub parse_nbss_record_partial, data:data, }) )); + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_parse_nbss_record() { + let buff:&[u8] = &[ + /* message type */ 0x00, + /* length */ 0x00, 0x00, 0x55, + /* data */ 0xff, 0x53, 0x4d, 0x42, 0x72, 0x00, 0x00, 0x00, 0x00, + 0x98, 0x53, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xfe, 0x00, 0x00, 0x00, 0x00, 0x11, 0x05, 0x00, 0x03, + 0x0a, 0x00, 0x01, 0x00, 0x04, 0x11, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xe3, + 0x00, 0x80, 0x2a, 0x55, 0xc4, 0x38, 0x89, 0x03, 0xcd, + 0x01, 0x2c, 0x01, 0x00, 0x10, 0x00, 0xfe, 0x82, 0xf1, + 0x64, 0x0b, 0x66, 0xba, 0x4a, 0xbb, 0x81, 0xe1, 0xea, + 0x54, 0xae, 0xb8, 0x66]; + + let result = parse_nbss_record(&buff); + match result { + Ok((remainder, p)) => { + assert_eq!(p.message_type, NBSS_MSGTYPE_SESSION_MESSAGE); + assert_eq!(p.length, 85); + assert_eq!(p.data.len(), 85); + assert_ne!(p.message_type, NBSS_MSGTYPE_KEEP_ALIVE); + + // this packet had an acceptable length, we don't need more + assert_eq!(p.needs_more(), false); + + // does this really look like smb? + assert_eq!(p.is_smb(), true); + + // there should be nothing left + assert_eq!(remainder.len(), 0); + } + Err(nom::Err::Error((remainder, err))) => { + panic!("Result should not be an error: {:?}.", err); + } + Err(nom::Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + _ => { + panic!("Unexpected behavior!"); + } + } + + // Non-SMB packet scenario + let buff_not_smb:&[u8] = &[ + /* message type */ 0x00, + /* length */ 0x00, 0x00, 0x55, + /* data !SMB */ 0xff, 0x52, 0x4e, 0x41, 0x72, 0x00, 0x00, 0x00, 0x00, + 0x98, 0x53, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xfe, 0x00, 0x00, 0x00, 0x00, 0x11, 0x05, 0x00, 0x03, + 0x0a, 0x00, 0x01, 0x00, 0x04, 0x11, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xe3, + 0x00, 0x80, 0x2a, 0x55, 0xc4, 0x38, 0x89, 0x03, 0xcd, + 0x01, 0x2c, 0x01, 0x00, 0x10, 0x00, 0xfe, 0x82, 0xf1, + 0x64, 0x0b, 0x66, 0xba, 0x4a, 0xbb, 0x81, 0xe1, 0xea, + 0x54, 0xae, 0xb8, 0x66]; + + let result_not_smb = parse_nbss_record(&buff_not_smb); + match result_not_smb { + Ok((remainder, p_not_smb)) => { + assert_eq!(p_not_smb.message_type, NBSS_MSGTYPE_SESSION_MESSAGE); + assert_eq!(p_not_smb.length, 85); + assert_eq!(p_not_smb.data.len(), 85); + assert_ne!(p_not_smb.message_type, NBSS_MSGTYPE_KEEP_ALIVE); + + // this packet had an acceptable length, we don't need more + assert_eq!(p_not_smb.needs_more(), false); + + // this packet doesn't have the SMB keyword + // is_smb must be false + assert_eq!(p_not_smb.is_smb(), false); + + // there should be nothing left + assert_eq!(remainder.len(), 0); + } + Err(nom::Err::Error((remainder, err))) => { + panic!("Result should not be an error: {:?}.", err); + } + Err(nom::Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + _ => { + panic!("Unexpected behavior!"); + } + } + } + + #[test] + fn test_parse_nbss_record_partial() { + let buff:&[u8] = &[ + /* message type */ 0x00, + /* length */ 0x00, 0x00, 0x29, + /* data < lenght*/ 0xff, 0x53, 0x4d, 0x42, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x18, 0x43, 0xc8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x08, 0xbd, 0x20, 0x02, 0x08, 0x06, 0x00, + 0x02, 0x40, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00]; + + let result = parse_nbss_record_partial(&buff); + match result { + Ok((remainder, p)) => { + assert_eq!(p.message_type, NBSS_MSGTYPE_SESSION_MESSAGE); + assert_eq!(p.length, 41); + assert_ne!(p.data.len(), 41); + assert_ne!(p.message_type, NBSS_MSGTYPE_KEEP_ALIVE); + + // this packet had an acceptable length, we don't need more + assert_eq!(p.needs_more(), false); + + // does this really look like smb? + assert_eq!(p.is_smb(), true); + + // there should be nothing left + assert_eq!(remainder.len(), 0); + } + Err(nom::Err::Error((remainder, err))) => { + panic!("Result should not be an error: {:?}.", err); + } + Err(nom::Err::Incomplete(_)) => { + panic!("Result should not have returned as incomplete."); + } + _ => { + panic!("Unexpected behavior!"); + } + } + + } +} diff --git a/src/Makefile.am b/src/Makefile.am index 670848c70b4b..825ce65d0dc0 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 93ed61af8937..7bbfa62df8d3 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 3f18aabf9a24..2f2373cd0b2d 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 434dae1391da..2c8467f1cd42 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 000000000000..7a3355ddb19e --- /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 000000000000..e9c6af065be9 --- /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 0c0ca72f1d49..93aec6338cbe 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 fc298d94bd66..7a583cf29b49 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/detect-fast-pattern.c b/src/detect-fast-pattern.c index 0a3d0d1fc146..408aafe4b6af 100644 --- a/src/detect-fast-pattern.c +++ b/src/detect-fast-pattern.c @@ -175,7 +175,7 @@ void DetectFastPatternRegister(void) #ifdef UNITTESTS sigmatch_table[DETECT_FAST_PATTERN].RegisterTests = DetectFastPatternRegisterTests; #endif - sigmatch_table[DETECT_FAST_PATTERN].flags |= SIGMATCH_NOOPT; + sigmatch_table[DETECT_FAST_PATTERN].flags |= SIGMATCH_OPTIONAL_OPT; DetectSetupParseRegexes(PARSE_REGEX, &parse_regex); } @@ -188,7 +188,7 @@ void DetectFastPatternRegister(void) * * \param de_ctx Pointer to the Detection Engine Context. * \param s Pointer to the Signature to which the current keyword belongs. - * \param null_str Should hold an empty string always. + * \param arg May hold an argument * * \retval 0 On success. * \retval -1 On failure. diff --git a/src/detect-parse.c b/src/detect-parse.c index 3f33f360e841..59517b55918d 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -704,8 +704,14 @@ static int SigParseOptions(DetectEngineCtx *de_ctx, Signature *s, char *optstr, if (!(st->flags & (SIGMATCH_NOOPT|SIGMATCH_OPTIONAL_OPT))) { if (optvalue == NULL || strlen(optvalue) == 0) { - SCLogError(SC_ERR_INVALID_SIGNATURE, "invalid formatting or malformed option to %s keyword: \'%s\'", - optname, optstr); + SCLogError(SC_ERR_INVALID_SIGNATURE, + "invalid formatting or malformed option to %s keyword: '%s'", optname, optstr); + goto error; + } + } else if (st->flags & SIGMATCH_NOOPT) { + if (optvalue && strlen(optvalue)) { + SCLogError(SC_ERR_INVALID_SIGNATURE, "unexpected option to %s keyword: '%s'", optname, + optstr); goto error; } } diff --git a/src/output-json-dhcp.c b/src/output-json-dhcp.c index 273ba806a596..b1b43c4a7a17 100644 --- a/src/output-json-dhcp.c +++ b/src/output-json-dhcp.c @@ -78,7 +78,6 @@ static int JsonDHCPLogger(ThreadVars *tv, void *thread_data, rs_dhcp_logger_log(ctx->rs_logger, tx, js); - EveAddCommonOptions(&thread->dhcplog_ctx->cfg, p, f, js); MemBufferReset(thread->buffer); OutputJsonBuilderBuffer(js, thread->file_ctx, &thread->buffer); jb_free(js); diff --git a/src/output-json.c b/src/output-json.c index 57b42220020d..1c1bda58b5d5 100644 --- a/src/output-json.c +++ b/src/output-json.c @@ -453,6 +453,9 @@ void EveAddCommonOptions(const OutputJsonCommonSettings *cfg, if (cfg->include_community_id && f != NULL) { CreateEveCommunityFlowId(js, f, cfg->community_id_seed); } + if (f != NULL && f->tenant_id > 0) { + jb_set_uint(js, "tenant_id", f->tenant_id); + } } /** diff --git a/src/runmode-unittests.c b/src/runmode-unittests.c index 7f458808cfaa..0a631a59b537 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(); diff --git a/src/stream-tcp.c b/src/stream-tcp.c index a54b9fd9b6f2..ac0341f089f5 100644 --- a/src/stream-tcp.c +++ b/src/stream-tcp.c @@ -2045,12 +2045,38 @@ static int StreamTcpPacketStateSynRecv(ThreadVars *tv, Packet *p, StreamTcpSetEvent(p, STREAM_3WHS_RIGHT_SEQ_WRONG_ACK_EVASION); return -1; - /* if we get a packet with a proper ack, but a seq that is beyond - * next_seq but in-window, we probably missed some packets */ + /* SYN/ACK followed by more TOCLIENT suggesting packet loss */ + } else if (PKT_IS_TOCLIENT(p) && !StreamTcpInlineMode() && + SEQ_GT(TCP_GET_SEQ(p), ssn->client.next_seq) && + SEQ_GT(TCP_GET_ACK(p), ssn->client.last_ack)) { + SCLogDebug("ssn %p: ACK for missing data", ssn); + + if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { + StreamTcpHandleTimestamp(ssn, p); + } + + StreamTcpUpdateLastAck(ssn, &ssn->client, TCP_GET_ACK(p)); + + ssn->server.next_seq = TCP_GET_SEQ(p) + p->payload_len; + SCLogDebug("ssn %p: ACK for missing data: ssn->server.next_seq %u", ssn, + ssn->server.next_seq); + ssn->client.window = TCP_GET_WINDOW(p) << ssn->client.wscale; + + ssn->client.next_win = ssn->client.last_ack + ssn->client.window; + + ssn->client.window = TCP_GET_WINDOW(p); + ssn->server.next_win = ssn->server.last_ack + ssn->server.window; + + StreamTcpPacketSetState(p, ssn, TCP_ESTABLISHED); + SCLogDebug("ssn %p: =~ ssn state is now TCP_ESTABLISHED", ssn); + + StreamTcpReassembleHandleSegment(tv, stt->ra_ctx, ssn, &ssn->server, p, pq); + + /* if we get a packet with a proper ack, but a seq that is beyond + * next_seq but in-window, we probably missed some packets */ } else if (SEQ_GT(TCP_GET_SEQ(p), ssn->client.next_seq) && - SEQ_LEQ(TCP_GET_SEQ(p),ssn->client.next_win) && - SEQ_EQ(TCP_GET_ACK(p), ssn->server.next_seq)) - { + SEQ_LEQ(TCP_GET_SEQ(p), ssn->client.next_win) && + SEQ_EQ(TCP_GET_ACK(p), ssn->server.next_seq)) { SCLogDebug("ssn %p: ACK for missing data", ssn); if (ssn->flags & STREAMTCP_FLAG_TIMESTAMP) { @@ -2084,10 +2110,9 @@ static int StreamTcpPacketStateSynRecv(ThreadVars *tv, Packet *p, /* toclient packet: after having missed the 3whs's final ACK */ } else if ((ack_indicates_missed_3whs_ack_packet || - (ssn->flags & STREAMTCP_FLAG_TCP_FAST_OPEN)) && - SEQ_EQ(TCP_GET_ACK(p), ssn->client.last_ack) && - SEQ_EQ(TCP_GET_SEQ(p), ssn->server.next_seq)) - { + (ssn->flags & STREAMTCP_FLAG_TCP_FAST_OPEN)) && + SEQ_EQ(TCP_GET_ACK(p), ssn->client.last_ack) && + SEQ_EQ(TCP_GET_SEQ(p), ssn->server.next_seq)) { if (ack_indicates_missed_3whs_ack_packet) { SCLogDebug("ssn %p: packet fits perfectly after a missed 3whs-ACK", ssn); } else { @@ -2926,10 +2951,13 @@ static int StreamTcpPacketStateFinWait1(ThreadVars *tv, Packet *p, if (StreamTcpPacketIsRetransmission(&ssn->server, p)) { SCLogDebug("ssn %p: packet is retransmission", ssn); retransmission = 1; + } else if (SEQ_EQ(ssn->server.next_seq, TCP_GET_SEQ(p)) && + SEQ_EQ(ssn->client.last_ack, TCP_GET_ACK(p))) { + SCLogDebug("ssn %p: packet is retransmission", ssn); + retransmission = 1; } else if (SEQ_LT(TCP_GET_SEQ(p), ssn->server.next_seq) || - SEQ_GT(TCP_GET_SEQ(p), (ssn->server.last_ack + ssn->server.window))) - { + SEQ_GT(TCP_GET_SEQ(p), (ssn->server.last_ack + ssn->server.window))) { SCLogDebug("ssn %p: -> SEQ mismatch, packet SEQ %" PRIu32 "" " != %" PRIu32 " from stream", ssn, TCP_GET_SEQ(p), ssn->server.next_seq); @@ -3334,6 +3362,10 @@ static int StreamTcpPacketStateFinWait2(ThreadVars *tv, Packet *p, StreamTcpPacketSetState(p, ssn, TCP_TIME_WAIT); SCLogDebug("ssn %p: state changed to TCP_TIME_WAIT", ssn); + if (SEQ_EQ(ssn->client.next_seq, TCP_GET_SEQ(p))) { + StreamTcpUpdateNextSeq( + ssn, &ssn->client, (ssn->client.next_seq + p->payload_len)); + } ssn->server.window = TCP_GET_WINDOW(p) << ssn->server.wscale; } diff --git a/src/tests/detect-parse.c b/src/tests/detect-parse.c index b7fb452b386b..d807c838f8c3 100644 --- a/src/tests/detect-parse.c +++ b/src/tests/detect-parse.c @@ -38,6 +38,23 @@ static int DetectParseTest01 (void) PASS; } +/** + * \test DetectParseTestNoOpt is a regression test to make sure that we reject + * any signature where a NOOPT rule option is given a value. This can hide rule + * errors which make other options disappear, eg: foo: bar: baz; where "foo" is + * the NOOPT option, we will end up with a signature which is missing "bar". + */ + +static int DetectParseTestNoOpt(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF(DetectEngineAppendSig(de_ctx, + "alert http any any -> any any (msg:\"sid 1 version 0\"; " + "content:\"dummy1\"; endswith: reference: ref; sid:1;)") != NULL); + DetectEngineCtxFree(de_ctx); + + PASS; +} /** * \brief this function registers unit tests for DetectParse @@ -45,4 +62,5 @@ static int DetectParseTest01 (void) void DetectParseRegisterTests(void) { UtRegisterTest("DetectParseTest01", DetectParseTest01); + UtRegisterTest("DetectParseTestNoOpt", DetectParseTestNoOpt); } diff --git a/src/tm-threads.c b/src/tm-threads.c index 51f74c2a7764..84a0eb2784fa 100644 --- a/src/tm-threads.c +++ b/src/tm-threads.c @@ -1900,8 +1900,11 @@ void TmThreadCheckThreadState(void) */ TmEcode TmThreadWaitOnThreadInit(void) { - uint16_t mgt_num = 0; - uint16_t ppt_num = 0; + uint16_t RX_num = 0; + uint16_t W_num = 0; + uint16_t FM_num = 0; + uint16_t FR_num = 0; + uint16_t TX_num = 0; struct timeval start_ts; struct timeval cur_ts; @@ -1951,18 +1954,54 @@ TmEcode TmThreadWaitOnThreadInit(void) return TM_ECODE_FAILED; } - if (i == TVT_MGMT) - mgt_num++; - else if (i == TVT_PPT) - ppt_num++; + if (strncmp(thread_name_autofp, tv->name, strlen(thread_name_autofp)) == 0) + RX_num++; + else if (strncmp(thread_name_workers, tv->name, strlen(thread_name_workers)) == 0) + W_num++; + else if (strncmp(thread_name_verdict, tv->name, strlen(thread_name_verdict)) == 0) + TX_num++; + else if (strncmp(thread_name_flow_mgr, tv->name, strlen(thread_name_flow_mgr)) == 0) + FM_num++; + else if (strncmp(thread_name_flow_rec, tv->name, strlen(thread_name_flow_rec)) == 0) + FR_num++; tv = tv->next; } } SCMutexUnlock(&tv_root_lock); - SCLogNotice("all %"PRIu16" packet processing threads, %"PRIu16" management " - "threads initialized, engine started.", ppt_num, mgt_num); + /* Construct a welcome string displaying + * initialized thread types and counts */ + uint16_t app_len = 32; + uint16_t buf_len = 256; + + char append_str[app_len]; + char thread_counts[buf_len]; + + strlcpy(thread_counts, "Threads created -> ", strlen("Threads created -> ") + 1); + if (RX_num > 0) { + snprintf(append_str, app_len, "RX: %u ", RX_num); + strlcat(thread_counts, append_str, buf_len); + } + if (W_num > 0) { + snprintf(append_str, app_len, "W: %u ", W_num); + strlcat(thread_counts, append_str, buf_len); + } + if (TX_num > 0) { + snprintf(append_str, app_len, "TX: %u ", TX_num); + strlcat(thread_counts, append_str, buf_len); + } + if (FM_num > 0) { + snprintf(append_str, app_len, "FM: %u ", FM_num); + strlcat(thread_counts, append_str, buf_len); + } + if (FR_num > 0) { + snprintf(append_str, app_len, "FR: %u ", FR_num); + strlcat(thread_counts, append_str, buf_len); + } + snprintf(append_str, app_len, " Engine started."); + strlcat(thread_counts, append_str, buf_len); + SCLogNotice("%s", thread_counts); return TM_ECODE_OK; } diff --git a/src/util-lua-common.c b/src/util-lua-common.c index 4d6aa26e7578..5ae952501f43 100644 --- a/src/util-lua-common.c +++ b/src/util-lua-common.c @@ -576,6 +576,45 @@ static int LuaCallbackRuleIds(lua_State *luastate) return LuaCallbackRuleIdsPushToStackFromPacketAlert(luastate, pa); } +/** \internal + * \brief fill lua stack with alert info + * \param luastate the lua state + * \param pa pointer to packet alert struct + * \retval cnt number of data items placed on the stack + * + * Places: action (string) + */ +static int LuaCallbackRuleActionPushToStackFromPacketAlert( + lua_State *luastate, const PacketAlert *pa) +{ + const char *action = ""; + if (pa->s->action & ACTION_PASS) { + action = "pass"; + } else if ((pa->s->action & ACTION_REJECT) || (pa->s->action & ACTION_REJECT_BOTH) || + (pa->s->action & ACTION_REJECT_DST)) { + action = "reject"; + } else if (pa->s->action & ACTION_DROP) { + action = "drop"; + } else if (pa->s->action & ACTION_ALERT) { + action = "alert"; + } + lua_pushstring(luastate, action); + return 1; +} + +/** \internal + * \brief Wrapper for getting tuple info into a lua script + * \retval cnt number of items placed on the stack + */ +static int LuaCallbackRuleAction(lua_State *luastate) +{ + const PacketAlert *pa = LuaStateGetPacketAlert(luastate); + if (pa == NULL) + return LuaCallbackError(luastate, "internal error: no packet"); + + return LuaCallbackRuleActionPushToStackFromPacketAlert(luastate, pa); +} + /** \internal * \brief fill lua stack with alert info * \param luastate the lua state @@ -908,6 +947,8 @@ int LuaRegisterFunctions(lua_State *luastate) lua_pushcfunction(luastate, LuaCallbackRuleIds); lua_setglobal(luastate, "SCRuleIds"); + lua_pushcfunction(luastate, LuaCallbackRuleAction); + lua_setglobal(luastate, "SCRuleAction"); lua_pushcfunction(luastate, LuaCallbackRuleMsg); lua_setglobal(luastate, "SCRuleMsg"); lua_pushcfunction(luastate, LuaCallbackRuleClass);