inline PktType get_pkt_type() const
{ return type; }
+
+ inline bool dont_fragment() const
+ { return decode_flags & DECODE_MF; }
};
#endif
}
}
+bool HostTracker::add_tcp_fingerprint(uint32_t fpid)
+{
+ lock_guard<mutex> lck(host_tracker_lock);
+ auto result = tcp_fpids.emplace(fpid);
+ return result.second;
+}
+
static inline string to_time_string(uint32_t p_time)
{
time_t raw_time = (time_t) p_time;
while ( total-- )
str += to_string(xport_protos[total]) + (total? ", " : "");
}
+
+ total = tcp_fpids.size();
+ if ( total )
+ {
+ str += "\ntcp fingerprint: ";
+ for ( const auto& fpid : tcp_fpids )
+ str += to_string(fpid) + (--total ? ", " : "");
+ }
}
#include <cstring>
#include <mutex>
#include <list>
+#include <set>
#include <vector>
#include "framework/counts.h"
void remove_inferred_services();
+ bool add_tcp_fingerprint(uint32_t fpid);
+
// This should be updated whenever HostTracker data members are changed
void stringify(std::string& str);
std::vector<uint16_t, HostCacheAllocIp<uint16_t>> network_protos;
std::vector<uint8_t, HostCacheAllocIp<uint8_t>> xport_protos;
std::vector<HostApplication, HostAppAllocator> services;
+ std::set<uint32_t, std::less<uint32_t>, HostCacheAllocIp<uint32_t>> tcp_fpids;
+
bool vlan_tag_present = false;
vlan::VlanTagHdr vlan_tag;
HostType host_type;
set (RNA_INCLUDES
rna_fingerprint.h
rna_fingerprint_tcp.h
- rna_fp_reader.h
+ rna_inspector.h
rna_logger.h
+ rna_name.h
)
set ( RNA_SOURCES
${RNA_INCLUDES}
rna_event_handler.cc
rna_event_handler.h
+ rna_fingerprint.cc
rna_fingerprint.h
rna_fingerprint_tcp.cc
rna_fingerprint_tcp.h
- rna_fp_reader.cc
- rna_fp_reader.h
rna_inspector.cc
rna_inspector.h
rna_logger.cc
needs to be preserved until discover_network_ethernet calls generate_change_vlan_update with this
newly-created host tracker as an argument. This host tracker is deleted at the top level, and we must
not return prior to that to avoid leaking any host trackers.
+
+Fingerprints
+
+Fingerprints are a sequence of features in network traffic used to identify a host's operating system.
+Currently only tcp fingerprints are supported, but future work may include udp fingerprints and
+user agent fingerprints.
+
+Tcp fingerprints are specified in lua and read by snort at configure time.
+Only the SYN and SYN-ACK packets are used for fingerprint matching. We refer to the SYN packet
+as "client" and SYN_ACK packet as "server" traffic.
+
+A typical fingerprint looks like this:
+
+-- centos client
+{
+ fpid = 110005,
+ type = 10,
+ uuid = "2fc04d1a-a2c2-11e2-840f-850c4648cdef",
+ ttl = 64,
+ tcp_window = "5712-5760",
+ mss = "X",
+ id = "X",
+ topts = "2 4 8 3",
+ ws = "7",
+ df = false,
+},
+
+This particular example identifies the host as one of 'CentOS Linux version 5.5','CentOS','Linux','Linux 5.5'.
+Note that there is no reference to the actual operating system in the fingerprint, but
+the fpid or the uuid field can be used for this purpose.
+
+The fields are as follows:
+
+fpid: (int) a unique fingerprint identifier. If snort encounters another fingerprint with the same
+ fpid, it will display a warning message and skip it. This field can be used as an index
+ into a database or table with details in human readable form about the host.
+ This field is not used for matching the fingerprint to the network traffic.
+
+type: (int) the type of traffic this fingerprint should be matched against. Currently, these types
+ are defined in rna_fingerprint.h:
+
+ enum FpType
+ {
+ FINGERPRINT_TYPE_SERVER = 1,
+ FINGERPRINT_TYPE_CLIENT = 2,
+ FINGERPRINT_TYPE_SERVER6 = 10,
+ FINGERPRINT_TYPE_CLIENT6 = 11,
+ };
+
+uuid: (uuid string, not an arbitrary string) similar in purpose to fpid, e.g. we could store
+ the host details in a file named 2fc04d1a-a2c2-11e2-840f-850c4648cdef.
+ Not used in matching.
+
+ttl: (int) time to live
+
+df: (bool) don't fragment flag
+
+The remaining fields are space-separated lists of FpElement, each of FpElementType.
+See rna_fingerprint.h. Such a list might be "1 2-5 6" or simply "X", or "10". Snort
+infers the FpElementType from the input string, e.g. "2-5" will be interpreted as a
+FpElementType::RANGE, and "X" will be interpreted as FpElementType::DONT_CARE.
+See the FpElement::parse_value() function in rna_fingerprint.cc.
+
+Here are all possible element types, with examples for each case:
+
+FpElementType::RANGE: "1" or "20-30"
+FpElementType::INCREMENT: "+5"
+FpElementType::SYN_MATCH: "SYN"
+FpElementType::RANDOM: "R"
+FpElementType::DONT_CARE: "X"
+FpElementType::SYNTS: "TS"
+
+For instance, the list
+
+"1 2-5 SYN"
+
+is a legal list, consisting of 3 elements: 1 (RANGE), 2-5 (RANGE) and SYN (SYN_MATCH).
+While the elements in this list are all valid, there are constraints on the
+type of the elements that make up the fingerprint fields. For instance,
+
+mss = "2 3 X"
+
+would result in error, because DONT_CARE ("X") is not allowed for the mss field.
+
+Here are the remaining fingerprint fields.
+
+tcp_window: the tcp window
+ FpElementType::RANGE
+
+ In the packet, the tcp window is a single integer value, but in the fingerprint
+ we can specify a range like tcp_window = "1234-4321".
+
+ Examples:
+ tcp_window = "1234-4321" -- will match any packet with tcp_window in the range
+ tcp_window = "5678" -- will match only packets with tcp_window = 5678
+ tcp_window = "R" -- error: RANDOM is not a valid tcp_window element
+
+mss: maximum segment size
+ On the client: FpElementType::RANGE, FpFLementType::DONT_CARE
+ On the server: FpElementType::RANGE, FpElementType::DONT_CARE,
+ FpElementType::SYN_MATCH, FpElementType::SYNTS
+
+ Examples:
+ fptype = 2, mss = "12-34" -- client traffic (fptype = 2) with mss beween 12 and 34
+ fptype = 2, mss = "X" -- don't use mss for matching, so match anything from cient
+ fptype = 2, mss = "SYN" -- error: client traffic (fptype = 2) but mss of type SYN_MATCH
+ only accepted for server traffic (fptyp1 = 1 or 10)
+ fptype = 1, mss = "TS" -- OK: server traffic (fptype = 1) and mss of type SYNTS
+ mss = "+5" -- eror: mss cannot be an INCREMENT type
+
+id: ip id
+ FpElementType::RANGE
+ FpElementType::RANDOM
+ FpElementType::INCREMENT
+ FpElementType::DONT_CARE
+
+ Example:
+ id = "X"
+
+topts: tcp options
+ FpElementType::RANGE
+
+ These are defined by TcpOptCode in src/protocols/tcp_options.h.
+ The ones we use for fingerprint matcing are
+
+ - MAXSEG (2)
+ - WSCALE (3)
+ - SACKOK (4)
+ - TIMESTAMP (8)
+
+ We match (a) whether or not an option is set, (b) what its value is and (c) the order
+ of the options.
+
+ Example:
+ topts = [2 4 8 3] -- will match tcp packets with mss (2), window scale (4), sack OK (8)
+ and timestamp (3) set, in this exact order. If we swap any two
+ numbers in topts, the resulting fingerprint will no longer match.
+
+ws: window scale
+ FpElementType::RANGE
+ FpElementType::DONT_CARE
#ifndef RNA_CONFIG_H
#define RNA_CONFIG_H
+namespace snort
+{
+class TcpFpProcessor;
+}
+
struct RnaModuleConfig
{
std::string rna_conf_path;
- std::string fingerprint_dir;
bool enable_logger;
bool log_when_idle;
+ snort::TcpFpProcessor* processor = nullptr;
};
// Give default values so that RNA can work even if rna_conf_path is not provided
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// rna_fingerprint.cc author Silviu Minut <sminut@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "rna_fingerprint.h"
+
+#include <cassert>
+#include <cstring>
+
+#ifdef UNIT_TEST
+#include "catch/snort_catch.h"
+#endif
+
+using namespace snort;
+using namespace std;
+
+FpElement::FpElement(const string& str) : type(FpElementType::INVALID)
+{
+ parse_value(str);
+}
+
+FpElement& FpElement::operator=(const std::string& str)
+{
+ type = FpElementType::INVALID;
+ parse_value(str);
+ return *this;
+}
+
+bool FpElement::operator==(const FpElement& y) const
+{
+ const FpElement& x=*this;
+ if (x.type != y.type)
+ return false;
+
+ switch (x.type)
+ {
+ case FpElementType::RANGE:
+ return (x.d.value == y.d.value) &&
+ (x.d.range.min == y.d.range.min) &&
+ (x.d.range.max == y.d.range.max);
+
+ case FpElementType::SYN_MATCH:
+ case FpElementType::INCREMENT:
+ return x.d.value == y.d.value;
+
+ default:
+ break;
+ }
+ return true;
+}
+
+void FpElement::parse_value(const std::string& str)
+{
+ FpElement& v=*this;
+
+ v.type = FpElementType::INVALID;
+
+ const char* data = str.c_str();
+ for (; *data && isspace(*data); data++);
+
+ if (*data == '+')
+ {
+ v.type = FpElementType::INCREMENT;
+ data++;
+ v.d.value = strtol(data, nullptr, 10);
+ return;
+ }
+ else if (strlen(data) >= sizeof(FP_SYN_KEY)-1 &&
+ !strncmp(data, FP_SYN_KEY, sizeof(FP_SYN_KEY)-1))
+ {
+ v.type = FpElementType::SYN_MATCH;
+ data += sizeof(FP_SYN_KEY)-1;
+ if (*data != '-')
+ {
+ v.d.value = 0;
+ return;
+ }
+ data++;
+ v.d.value = strtol(data, nullptr, 10);
+ return;
+ }
+ else if (strlen(data) >= sizeof(FP_RANDOM_KEY)-1 &&
+ !strncmp(data, FP_RANDOM_KEY, sizeof(FP_RANDOM_KEY)-1))
+ {
+ v.type = FpElementType::RANDOM;
+ return;
+ }
+ else if (strlen(data) >= sizeof(FP_DONT_CARE_KEY)-1 &&
+ !strncmp(data, FP_DONT_CARE_KEY, sizeof(FP_DONT_CARE_KEY)-1))
+ {
+ v.type = FpElementType::DONT_CARE;
+ return;
+ }
+ else if (strlen(data) >= sizeof(FP_SYN_TS_KEY)-1 &&
+ !strncmp(data, FP_SYN_TS_KEY, sizeof(FP_SYN_TS_KEY)-1))
+ {
+ v.type = FpElementType::SYNTS;
+ return;
+ }
+
+ // this converts "-1" to [0,1], in agreement with snort 2
+ if (const char* r=strchr(data, '-'))
+ {
+ v.type = FpElementType::RANGE;
+ string left(data, r);
+ string right(++r);
+ v.d.range.min = strtol(left.c_str(), nullptr, 10);
+ v.d.range.max = strtol(right.c_str(), nullptr, 10);
+ }
+ else
+ {
+ v.type = FpElementType::RANGE;
+ v.d.range.min = strtol(data, nullptr, 10);
+ v.d.range.max = v.d.range.min;
+ }
+
+ assert(v.type != FpElementType::INVALID);
+}
+
+#ifdef UNIT_TEST
+
+TEST_CASE("FpElement", "[rna_fingerprint]")
+{
+ FpElement fpe;
+ FpElement fpe_test;
+
+ // RANGE, single value
+ fpe_test = "10";
+ fpe.type = FpElementType::RANGE;
+ fpe.d.value = 10;
+ fpe.d.range.min = 10;
+ fpe.d.range.max = 10;
+ CHECK(fpe == fpe_test);
+
+ // RANGE, range
+ fpe_test = "1-20";
+ fpe.type = FpElementType::RANGE;
+ fpe.d.value = 0;
+ fpe.d.range.min = 1;
+ fpe.d.range.max = 20;
+ CHECK(fpe == fpe_test);
+
+ // INCREMENT
+ fpe_test = "+20";
+ fpe.type = FpElementType::INCREMENT;
+ fpe.d.value = 20;
+ CHECK(fpe == fpe_test);
+
+ // SYN_MATCH, key only
+ fpe_test = FP_SYN_KEY;
+ fpe.type = FpElementType::SYN_MATCH;
+ fpe.d.value = 0;
+ CHECK(fpe == fpe_test);
+
+ // SYN_MATCH, key and value
+ fpe_test = string(FP_SYN_KEY) + "-20";
+ fpe.type = FpElementType::SYN_MATCH;
+ fpe.d.value = 20;
+ CHECK(fpe == fpe_test);
+
+ fpe_test = FP_RANDOM_KEY;
+ fpe.type = FpElementType::RANDOM;
+ fpe.d.value = 20;
+ CHECK(fpe == fpe_test);
+
+ fpe_test = FP_DONT_CARE_KEY;
+ fpe.type = FpElementType::DONT_CARE;
+ fpe.d.value = 20;
+ CHECK(fpe == fpe_test);
+
+ fpe_test = FP_SYN_TS_KEY;
+ fpe.type = FpElementType::SYNTS;
+ fpe.d.value = 20;
+ CHECK(fpe == fpe_test);
+}
+
+#endif
+
#ifndef RNA_FINGERPRINT_H
#define RNA_FINGERPRINT_H
+#include <cstdint>
+#include <string>
#include <uuid/uuid.h>
+#include "main/snort_types.h"
+
+#define FP_SYN_KEY "SYN"
+#define FP_RANDOM_KEY "R"
+#define FP_DONT_CARE_KEY "X"
+#define FP_SYN_TS_KEY "TS"
+
+#define MAXIMUM_FP_HOPS 32
+
namespace snort
{
class FpFingerprint
{
public:
- uint32_t fpid;
- uint32_t fp_type;
+
+ enum FpType
+ {
+ FINGERPRINT_TYPE_DERIVED = 0,
+ FINGERPRINT_TYPE_SERVER = 1,
+ FINGERPRINT_TYPE_CLIENT = 2,
+ FINGERPRINT_TYPE_SMB = 3,
+ FINGERPRINT_TYPE_DHCP = 4,
+ FINGERPRINT_TYPE_USER = 5,
+ FINGERPRINT_TYPE_SCAN = 6,
+ FINGERPRINT_TYPE_APP = 7,
+ FINGERPRINT_TYPE_CONFLICT = 8,
+ FINGERPRINT_TYPE_MOBILE = 9,
+ FINGERPRINT_TYPE_SERVER6 = 10,
+ FINGERPRINT_TYPE_CLIENT6 = 11,
+ FINGERPRINT_TYPE_DHCP6 = 12,
+ FINGERPRINT_TYPE_USERAGENT = 13,
+ MAX_FINGERPRINT_TYPES = 14
+ };
+
+ uint32_t fpid = 0;
+ uint32_t fp_type = 0;
uuid_t fpuuid;
- uint8_t ttl;
+ uint8_t ttl = 0;
+
+ virtual ~FpFingerprint() { }
+
+ virtual void clear()
+ {
+ fpid = 0;
+ fp_type = 0;
+ uuid_clear(fpuuid);
+ ttl = 0;
+ }
+};
+
+enum FpElementType
+{
+ INVALID = -1,
+ RANGE = 1,
+ INCREMENT,
+ SYN_MATCH,
+ RANDOM,
+ DONT_CARE,
+ SYNTS
+};
+
+class SO_PUBLIC FpElement
+{
+public:
+
+ FpElement() = default;
+ FpElement(const std::string&);
+
+ FpElement& operator=(const FpElement& fpe) = default;
+ FpElement& operator=(const std::string& str);
+ bool operator==(const FpElement& y) const;
+
+ FpElementType type;
+ union
+ {
+ int value;
+ struct
+ {
+ int min;
+ int max;
+ } range;
+ } d;
+
+private:
+ void parse_value(const std::string&);
};
}
+class RawFingerprint
+{
+public:
+
+ uint32_t fpid = 0;
+ uint32_t fp_type = 0;
+ std::string fpuuid;
+ uint8_t ttl = 0;
+
+ std::string tcp_window;
+ std::string mss;
+ std::string id;
+ std::string topts;
+ std::string ws;
+ bool df = false;
+
+ void clear()
+ {
+ fpid = 0;
+ fp_type = 0;
+ fpuuid.clear();
+ ttl = 0;
+ tcp_window.clear();
+ mss.clear();
+ id.clear();
+ topts.clear();
+ ws.clear();
+ df=false;
+ }
+
+};
#endif
#include "rna_fingerprint_tcp.h"
+#include <sstream>
+
#ifdef UNIT_TEST
#include "catch/snort_catch.h"
#endif
+#include "log/messages.h"
+#include "protocols/packet.h"
+#include "protocols/tcp.h"
+#include "protocols/tcp_options.h"
+
using namespace snort;
using namespace std;
-static TcpFpProcessor tcp_fp_processor;
+static THREAD_LOCAL TcpFpProcessor* tcp_fp_processor = nullptr;
-namespace snort
+unsigned RNAFlow::inspector_id = 0;
+
+static int parse_fp_element(const string& data, vector<FpElement>& fpe)
{
+ istringstream in(data);
+ string tok;
+
+ while ( in >> tok )
+ fpe.emplace_back(tok);
+ return 1;
+}
TcpFpProcessor* get_tcp_fp_processor()
{
- return &tcp_fp_processor;
+ return tcp_fp_processor;
+}
+
+void set_tcp_fp_processor(TcpFpProcessor* processor)
+{
+ tcp_fp_processor = processor;
+}
+
+namespace snort
+{
+
+TcpFingerprint::TcpFingerprint(const RawFingerprint& rfp)
+{
+ fpid = rfp.fpid;
+ fp_type = rfp.fp_type;
+ uuid_parse(rfp.fpuuid.c_str(), fpuuid);
+ ttl = rfp.ttl;
+
+ parse_fp_element(rfp.tcp_window, tcp_window);
+ parse_fp_element(rfp.mss, mss);
+ parse_fp_element(rfp.id, id);
+ parse_fp_element(rfp.topts, topts);
+ parse_fp_element(rfp.ws, ws);
+ df = rfp.df;
+}
+
+bool TcpFingerprint::operator==(const TcpFingerprint& y) const
+{
+ return (
+ fpid == y.fpid &&
+ fp_type == y.fp_type &&
+ !uuid_compare(fpuuid, y.fpuuid) &&
+ ttl == y.ttl &&
+ equal(tcp_window.begin(), tcp_window.end(), y.tcp_window.begin()) &&
+ equal(mss.begin(), mss.end(), y.mss.begin()) &&
+ equal(id.begin(), id.end(), y.id.begin()) &&
+ equal(topts.begin(), topts.end(), y.topts.begin()) &&
+ df == y.df);
+}
+
+void TcpFpProcessor::push(const TcpFingerprint& tfp)
+{
+ const auto& result = tcp_fps.emplace(make_pair(tfp.fpid, tfp));
+ if (!result.second)
+ WarningMessage("TcpFpProcessor: ignoring previously seen fingerprint id: %d\n", tfp.fpid);
+
}
-void TcpFpProcessor::push(const vector<FpTcpFingerprint>& fplist, TCP_FP_MODE mode)
+void TcpFpProcessor::make_tcp_fp_tables(TCP_FP_MODE mode)
{
- vector<const FpTcpFingerprint*>* fptable = (mode == TCP_FP_MODE::SERVER ?
+ auto* fptable = (mode == TCP_FP_MODE::SERVER ?
table_tcp_server : table_tcp_client);
- for (const auto& tfp : fplist)
+ for (size_t i = 0; i < table_size; i++)
+ fptable[i].clear();
+
+ for (const auto& tfpit : tcp_fps)
{
+ const auto& tfp = tfpit.second;
for (const auto& fpe : tfp.tcp_window)
{
switch (fpe.type)
}
}
+const TcpFingerprint* TcpFpProcessor::get_tcp_fp(const FpTcpKey& key, uint8_t ttl,
+ TCP_FP_MODE mode) const
+{
+ uint8_t optorder[4];
+ uint8_t fp_optorder[4];
+ int optpos;
+ int fp_optpos;
+ int i;
+ uint32_t fptype;
+
+ const vector<const snort::TcpFingerprint*>* fptable;
+
+ if (mode == TCP_FP_MODE::SERVER)
+ {
+ fptable = table_tcp_server;
+ fptype = key.isIpv6 ?
+ FpFingerprint::FpType::FINGERPRINT_TYPE_SERVER6 :
+ FpFingerprint::FpType::FINGERPRINT_TYPE_SERVER;
+ }
+ else if (mode == TCP_FP_MODE::CLIENT)
+ {
+ fptable = table_tcp_client;
+ fptype = key.isIpv6 ?
+ FpFingerprint::FpType::FINGERPRINT_TYPE_CLIENT6 :
+ FpFingerprint::FpType::FINGERPRINT_TYPE_CLIENT;
+ }
+ else
+ {
+ ErrorMessage("TcpFpProcessor::get_tcp_fingerprint(): Invalid mode - %d", mode);
+ return nullptr;
+ }
+
+ const auto& tfpvec = fptable[key.tcp_window];
+ for (const auto& tfp : tfpvec)
+ {
+ if (tfp->fp_type != fptype )
+ continue; // tfp
+
+ for (const auto& fpe_mss : tfp->mss)
+ {
+ switch (fpe_mss.type)
+ {
+ case FpElementType::RANGE:
+ if (key.mss >= fpe_mss.d.range.min &&
+ key.mss <= fpe_mss.d.range.max)
+ {
+ goto mssgood;
+ }
+ break;
+ case FpElementType::SYN_MATCH:
+ //if synmss is negative, it means that client didn't send MSS option
+ if ((key.synmss >= 0)
+ && ((!fpe_mss.d.value && key.mss <= key.synmss)
+ || (fpe_mss.d.value && ((key.synmss < fpe_mss.d.value && key.mss <= key.synmss)
+ || (key.synmss >= fpe_mss.d.value && key.mss <= fpe_mss.d.value)))))
+ {
+ goto mssgood;
+ }
+ break;
+ case FpElementType::DONT_CARE:
+ case FpElementType::SYNTS:
+ goto mssgood;
+ default:
+ break;
+ }
+ }
+ continue; // tfp
+
+ mssgood:
+ if (key.df == tfp->df &&
+ ttl <= tfp->ttl &&
+ (tfp->ttl < MAXIMUM_FP_HOPS || ttl >= (tfp->ttl - MAXIMUM_FP_HOPS)))
+ {
+ if (key.ws_pos >= 0)
+ {
+ for (const auto& fpe_ws : tfp->ws)
+ {
+ switch (fpe_ws.type)
+ {
+ case FpElementType::RANGE:
+ if (key.ws >= fpe_ws.d.range.min &&
+ key.ws <= fpe_ws.d.range.max)
+ {
+ goto wsgood;
+ }
+ break;
+ case FpElementType::DONT_CARE:
+ goto wsgood;
+ default:
+ break;
+ }
+ }
+ continue; // tfp
+ }
+
+ wsgood:
+ if (mode == TCP_FP_MODE::SERVER)
+ {
+ //create array of options in the order seen in server packet
+ for (i=0, optpos=0; i<TCP_OPTLENMAX && optpos<4; i++)
+ {
+ if (i == key.ws_pos)
+ optorder[optpos++] = (uint8_t) tcp::TcpOptCode::WSCALE;
+ else if (i == key.mss_pos)
+ optorder[optpos++] = (uint8_t) tcp::TcpOptCode::MAXSEG;
+ else if (i == key.sackok_pos)
+ optorder[optpos++] = (uint8_t) tcp::TcpOptCode::SACKOK;
+ else if (i == key.timestamp_pos)
+ optorder[optpos++] = (uint8_t) tcp::TcpOptCode::TIMESTAMP;
+ }
+
+ //create array of options from fingerprint that were present in client packet.
+ //Ordered by fingerprint option order
+ fp_optpos = 0;
+ for (const auto& fpe_topts : tfp->topts)
+ {
+ for (i=0; i<key.num_syn_tcpopts; i++)
+ {
+ if (key.syn_tcpopts[i] == fpe_topts.d.range.min)
+ {
+ fp_optorder[fp_optpos++] = key.syn_tcpopts[i];
+ break;
+ }
+ }
+ }
+
+ //if number, type, or order of option in SYN mismatch those in FP,
+ //goto next check.
+ if (optpos != fp_optpos) goto check_ts;
+
+ for (i=0; i<optpos; i++)
+ {
+ if (optorder[i] != fp_optorder[i]) goto check_ts;
+ }
+ return tfp;
+
+ check_ts:
+ //number and type of options didn't match between SYN and fingerprint.
+ //Ignore Timestamp option if present in SYN. Remaining processing is
+ //the same as the block above.
+ for (i=0; i<key.num_syn_tcpopts; i++)
+ {
+ if (key.syn_tcpopts[i] == (uint8_t) tcp::TcpOptCode::TIMESTAMP)
+ {
+ break;
+ }
+ }
+ if (i >= key.num_syn_tcpopts || key.syn_timestamp)
+ continue; // tfp
+
+ fp_optpos = 0;
+ for (const auto& fpe_topts : tfp->topts)
+ {
+ for (i=0; i<key.num_syn_tcpopts; i++)
+ {
+ if (key.syn_tcpopts[i] == fpe_topts.d.range.min)
+ {
+ if (key.syn_tcpopts[i] != (uint8_t) tcp::TcpOptCode::TIMESTAMP)
+ fp_optorder[fp_optpos++] = key.syn_tcpopts[i];
+ break;
+ }
+ }
+ }
+ if (optpos != fp_optpos)
+ continue; // ftp
+
+ for (i=0; i<optpos; i++)
+ {
+ if (optorder[i] != fp_optorder[i])
+ continue; // tfp
+ }
+ return tfp;
+ }
+ else
+ {
+ for (i=0, optpos=0; i<TCP_OPTLENMAX && optpos<4; i++)
+ {
+ if (i == key.ws_pos)
+ optorder[optpos++] = (uint8_t) tcp::TcpOptCode::WSCALE;
+ else if (i == key.mss_pos)
+ optorder[optpos++] = (uint8_t) tcp::TcpOptCode::MAXSEG;
+ else if (i == key.sackok_pos)
+ optorder[optpos++] = (uint8_t) tcp::TcpOptCode::SACKOK;
+ else if (i == key.timestamp_pos)
+ optorder[optpos++] = (uint8_t) tcp::TcpOptCode::TIMESTAMP;
+ }
+
+ auto fpe_topts = tfp->topts.begin();
+ for (i = 0; i<optpos && fpe_topts != tfp->topts.end(); i++, fpe_topts++)
+ {
+ if (optorder[i] != fpe_topts->d.range.min)
+ break;
+ }
+
+ // if there were more elements in this fingerprint or
+ // there was a mismatch between this fingerprint and the
+ // key, go to the next fingerprint in the store
+ if (fpe_topts != tfp->topts.end() || i < optpos)
+ continue;
+
+ return tfp;
+ }
+ }
+ }
+ return nullptr;
+}
+
+
+static int get_tcp_option(const Packet* p, tcp::TcpOptCode opt_code, int& pos)
+{
+ int maxops = (int) p->ptrs.tcph->options_len();
+ if (maxops < 0 || TCP_OPTLENMAX < maxops)
+ {
+ pos = -1;
+ return -1;
+ }
+
+ pos = 0;
+ tcp::TcpOptIterator opt_iter(p->ptrs.tcph, p);
+ for (const tcp::TcpOption& opt : opt_iter)
+ {
+ if (opt.code == opt_code)
+ {
+ switch (opt.code)
+ {
+ case tcp::TcpOptCode::MAXSEG:
+ return ntohs(*((const uint16_t*)(opt.data)));
+
+ case tcp::TcpOptCode::NOP:
+ return 1;
+
+ case tcp::TcpOptCode::SACKOK:
+ return 1;
+
+ case tcp::TcpOptCode::WSCALE:
+ return opt.data[0] & 0xff;
+
+ case tcp::TcpOptCode::TIMESTAMP:
+ return ntohs(*((const uint32_t*)(opt.data)));
+
+ default:
+ break;
+ }
+ }
+
+ pos++;
+ }
+
+ pos = -1;
+ return -1;
+}
+
+const TcpFingerprint* TcpFpProcessor::get(const Packet* p, RNAFlow* flowp) const
+{
+ FpTcpKey fpk;
+ bool mssOptionPresent = false;
+
+ bzero(&fpk, sizeof(FpTcpKey));
+
+ if (p->is_ip6())
+ fpk.isIpv6 = 1;
+
+ /* build a key for the lookup */
+ fpk.df = p->ptrs.dont_fragment();
+ fpk.tcp_window = p->ptrs.tcph->win();
+
+ fpk.mss = get_tcp_option(p, tcp::TcpOptCode::MAXSEG, fpk.mss_pos);
+ if (fpk.mss_pos >= 0)
+ {
+ get_tcp_option(p, tcp::TcpOptCode::SACKOK, fpk.sackok_pos);
+ fpk.ws = get_tcp_option(p, tcp::TcpOptCode::WSCALE, fpk.ws_pos);
+ get_tcp_option(p, tcp::TcpOptCode::TIMESTAMP, fpk.timestamp_pos);
+ mssOptionPresent = 1;
+ }
+
+ TCP_FP_MODE traffic_source = p->ptrs.tcph->is_ack() ? TCP_FP_MODE::SERVER : TCP_FP_MODE::CLIENT;
+
+ if (traffic_source == TCP_FP_MODE::SERVER)
+ {
+ if (!flowp)
+ return nullptr;
+
+ if (!mssOptionPresent)
+ return nullptr;
+
+ fpk.synmss = flowp->state.initial_mss;
+ fpk.num_syn_tcpopts = flowp->state.numopts;
+ fpk.syn_tcpopts = flowp->state.tcpopts;
+ fpk.syn_timestamp = flowp->state.timestamp;
+ }
+ else
+ {
+ if (!mssOptionPresent)
+ {
+ //client becomes unknown when client does not sent MSS option
+ return nullptr;
+ }
+ }
+
+ /* run the search and return the result */
+ const TcpFingerprint* fp = get_tcp_fp(fpk, p->ptrs.ip_api.ttl(), traffic_source);
+
+ return fp;
+}
+
+}
+
+void RNAFlow::init()
+{
+ inspector_id = snort::FlowData::create_flow_data_id();
+}
+
+size_t RNAFlow::size_of()
+{
+ return sizeof(*this);
+}
+
+bool FpFingerprintState::set(const Packet* p)
+{
+ int pos = 0;
+ numopts = 0;
+ initial_mss = get_tcp_option(p, tcp::TcpOptCode::MAXSEG, pos);
+ tcpopts[numopts++] = (uint8_t) tcp::TcpOptCode::MAXSEG;
+
+ get_tcp_option(p, tcp::TcpOptCode::SACKOK, pos);
+ if (pos >= 0)
+ tcpopts[numopts++] = (uint8_t) tcp::TcpOptCode::SACKOK;
+
+ get_tcp_option(p, tcp::TcpOptCode::WSCALE, pos);
+ if (pos >= 0)
+ tcpopts[numopts++] = (uint8_t) tcp::TcpOptCode::WSCALE;
+
+ timestamp = get_tcp_option(p, tcp::TcpOptCode::TIMESTAMP, pos);
+ if (pos >= 0)
+ tcpopts[numopts++] = (uint8_t) tcp::TcpOptCode::TIMESTAMP;
+ else
+ timestamp = -1;
+ timeout = p->pkth->ts.tv_sec;
+
+ return true;
}
+
#ifdef UNIT_TEST
-TEST_CASE("get_tcp_fp_processor", "[tcp_processor]")
+
+TEST_CASE("get_tcp_fp_processor", "[rna_fingerprint_tcp]")
+{
+ TcpFpProcessor* tfp = get_tcp_fp_processor();
+ CHECK(tfp == tcp_fp_processor);
+}
+
+TEST_CASE("clear_fingerprint", "[rna_fingerprint_tcp]")
+{
+ TcpFingerprint fpx;
+ uuid_clear(fpx.fpuuid);
+
+ RawFingerprint rawfp;
+ rawfp.fpid = 948;
+ rawfp.fp_type = 1;
+ rawfp.fpuuid = "12345678-1234-1234-1234-123456789012";
+ rawfp.ttl = 64;
+ rawfp.tcp_window = "10 20 30-40 50 60-70";
+ rawfp.mss = "X";
+ rawfp.id = "X";
+ rawfp.topts = "2 3 4 8";
+ rawfp.ws = "6";
+ rawfp.df = 1;
+
+ TcpFingerprint tfp(rawfp);
+ tfp.clear();
+ CHECK(tfp == fpx);
+}
+
+TEST_CASE("parse_fp_element", "[rna_fingerprint_tcp]")
+{
+ vector<string> str_elements = {"10", "20", "30-40", "50", "60-70"};
+ vector<FpElement> vfpe;
+ string str;
+ for (const auto& tok : str_elements)
+ {
+ str += tok + " ";
+ vfpe.emplace_back(FpElement(tok));
+ }
+
+ vector<FpElement> vfpe_test;
+ parse_fp_element(str, vfpe_test);
+ CHECK( equal(vfpe.begin(), vfpe.end(), vfpe_test.begin()) );
+}
+
+TEST_CASE("raw_to_tcp_fp", "[rna_fingerprint_tcp]")
+{
+ RawFingerprint rawfp;
+ rawfp.fpid = 948;
+ rawfp.fp_type = 1;
+ rawfp.fpuuid = "12345678-1234-1234-1234-123456789012";
+ rawfp.ttl = 64;
+ rawfp.tcp_window = "10 20 30-40 50 60-70";
+ rawfp.mss = "X";
+ rawfp.id = "X";
+ rawfp.topts = "2 3 4 8";
+ rawfp.ws = "6";
+ rawfp.df = 1;
+
+ TcpFingerprint tfpe;
+ tfpe.fpid = rawfp.fpid;
+ tfpe.fp_type = rawfp.fp_type;
+ uuid_parse(rawfp.fpuuid.c_str(), tfpe.fpuuid);
+ tfpe.ttl = rawfp.ttl;
+ tfpe.tcp_window = vector<FpElement> {
+ FpElement("10"), FpElement("20"), FpElement("30-40"),
+ FpElement("50"), FpElement("60-70") };
+ tfpe.mss = vector<FpElement> { FpElement("X") };
+ tfpe.id = vector<FpElement> { FpElement("X") };
+ tfpe.topts = vector<FpElement> {
+ FpElement("2"), FpElement("3"), FpElement("4"), FpElement("8") };
+ tfpe.ws.emplace_back(FpElement("6"));
+ tfpe.df = rawfp.df;
+
+ TcpFingerprint tfp_test(rawfp);
+
+ CHECK(tfpe == tfp_test);
+}
+
+TEST_CASE("get_tcp_fp", "[rna_fingerprint_tcp]")
{
- vector<FpTcpFingerprint> fplist;
- tcp_fp_processor.push(fplist, TcpFpProcessor::TCP_FP_MODE::SERVER);
+ set_tcp_fp_processor(new TcpFpProcessor);
+ TcpFpProcessor* processor = get_tcp_fp_processor();
+
+ // Push some fingerprints to the processor:
+ RawFingerprint rawfp;
+ rawfp.fpid = 948;
+ rawfp.fp_type = 1;
+ rawfp.fpuuid = "12345678-1234-1234-1234-123456789012";
+ rawfp.ttl = 64;
+ rawfp.tcp_window = "-1";
+ rawfp.mss = "X";
+ rawfp.id = "X";
+ rawfp.topts = "2 3 4 8";
+ rawfp.ws = "6";
+ rawfp.df = 1;
+ processor->push(rawfp);
+ TcpFingerprint f948(rawfp);
+
+ rawfp.fpid = 30962;
+ rawfp.fp_type = 2;
+ rawfp.fpuuid = "12345678-1234-1234-1234-123456789013";
+ rawfp.ttl = 64;
+ rawfp.tcp_window = "-1";
+ rawfp.mss = "X";
+ rawfp.id = "X";
+ rawfp.topts = "2 4 8 3";
+ rawfp.ws = "8";
+ rawfp.df = 1;
+ processor->push(rawfp);
+ TcpFingerprint f30962(rawfp);
+
+ rawfp.fpid = 110005;
+ rawfp.fp_type = 10;
+ rawfp.fpuuid = "12345678-1234-1234-1234-123456789014";
+ rawfp.ttl = 64;
+ rawfp.tcp_window = "5712-5760";
+ rawfp.mss = "X";
+ rawfp.id = "X";
+ rawfp.topts = "2 4 8 3";
+ rawfp.ws = "7";
+ rawfp.df = 0;
+ processor->push(rawfp);
+ TcpFingerprint f110005(rawfp);
+
+ rawfp.fpid = 120001;
+ rawfp.fp_type = 11;
+ rawfp.fpuuid = "12345678-1234-1234-1234-123456789015";
+ rawfp.ttl = 64;
+ rawfp.tcp_window = "14400";
+ rawfp.mss = "X";
+ rawfp.id = "X";
+ rawfp.topts = "2 4 8 3";
+ rawfp.ws = "7";
+ rawfp.df = 0;
+ processor->push(rawfp);
+ TcpFingerprint f120001(rawfp);
+
+ rawfp.fpid = 2;
+ rawfp.fp_type = 1;
+ rawfp.fpuuid = "12345678-1234-1234-1234-123456789016";
+ rawfp.ttl = 64;
+ rawfp.tcp_window = "2144 5040-5840";
+ rawfp.mss = "X";
+ rawfp.id = "X";
+ rawfp.topts = "2";
+ rawfp.ws = "0-1";
+ rawfp.df = 0;
+ processor->push(rawfp);
+ TcpFingerprint f2(rawfp);
+
+ processor->make_tcp_fp_tables(TcpFpProcessor::TCP_FP_MODE::SERVER);
+ processor->make_tcp_fp_tables(TcpFpProcessor::TCP_FP_MODE::CLIENT);
+
+ // match time
+ const TcpFingerprint* tfp;
+ uint8_t ttl = 64;
+ TcpFpProcessor::TCP_FP_MODE mode = TcpFpProcessor::TCP_FP_MODE::SERVER;
+
+ // key that matches f_110005 - server side
+ uint8_t syn_tcpopts[] = {0, 0, 0, 0};
+ FpTcpKey key;
+ key.synmss = 1;
+ key.num_syn_tcpopts = 4;
+ key.syn_tcpopts = syn_tcpopts;
+ key.syn_timestamp = 1;
+ key.tcp_window = 5750;
+ key.mss = 4;
+ key.ws = 7;
+ key.mss_pos = 0; // MAXSEG = 2 in position 0
+ key.sackok_pos = 1; // SACKOK = 4 in position 1
+ key.timestamp_pos = 2; // TIMESTAMP = 8 in position 2
+ key.ws_pos = 3; // WSCALE = 3 in position 3
+ key.df = 0;
+ key.isIpv6 = 1;
+ syn_tcpopts[key.mss_pos] = (uint8_t) tcp::TcpOptCode::MAXSEG;
+ syn_tcpopts[key.timestamp_pos] = (uint8_t) tcp::TcpOptCode::TIMESTAMP;
+ syn_tcpopts[key.sackok_pos] = (uint8_t) tcp::TcpOptCode::SACKOK;
+ syn_tcpopts[key.ws_pos] = (uint8_t) tcp::TcpOptCode::WSCALE;
+
+ tfp = processor->get_tcp_fp(key, ttl, mode);
+ CHECK( (tfp && *tfp == f110005) );
+
+ // as above, except don't set timestamp option 2 4 8 3
+ key.syn_timestamp = 0;
+ key.mss_pos = 0; // MAXSEG = 2 in position 0
+ key.sackok_pos = 1; // SACKOK = 4 in position 1
+ key.ws_pos = 2; // WSCALE = 3 in position 2
+ key.num_syn_tcpopts = 3;
+ syn_tcpopts[key.mss_pos] = (uint8_t) tcp::TcpOptCode::MAXSEG;
+ syn_tcpopts[key.sackok_pos] = (uint8_t) tcp::TcpOptCode::SACKOK;
+ syn_tcpopts[key.ws_pos] = (uint8_t) tcp::TcpOptCode::WSCALE;
+
+ tfp = processor->get_tcp_fp(key, ttl, mode);
+ CHECK( (tfp && *tfp == f110005) );
+
+ // now match something on the client side
+ mode = TcpFpProcessor::TCP_FP_MODE::CLIENT;
+
+ // match f_30962 - client side
+ key.synmss = 1;
+ key.num_syn_tcpopts = 4;
+ key.syn_tcpopts = syn_tcpopts;
+ key.syn_timestamp = 1;
+ key.tcp_window = 1; // fp tcp_window = -1 gets interpreted as 0-1
+ key.mss = 4;
+ key.ws = 8;
+ key.mss_pos = 0; // MAXSEG = 2 in position 0
+ key.sackok_pos = 1; // SACKOK = 4 in position 1
+ key.timestamp_pos = 2; // TIMESTAMP = 8 in position 2
+ key.ws_pos = 3; // WSCALE = 3 in position 3
+ key.df = 1;
+ key.isIpv6 = 0;
+ syn_tcpopts[key.mss_pos] = (uint8_t) tcp::TcpOptCode::MAXSEG;
+ syn_tcpopts[key.timestamp_pos] = (uint8_t) tcp::TcpOptCode::TIMESTAMP;
+ syn_tcpopts[key.sackok_pos] = (uint8_t) tcp::TcpOptCode::SACKOK;
+ syn_tcpopts[key.ws_pos] = (uint8_t) tcp::TcpOptCode::WSCALE;
+
+ tfp = processor->get_tcp_fp(key, ttl, mode);
+ CHECK( (tfp && *tfp == f30962) );
+
+ // again, with no ws
+ key.ws_pos = -1;
+ key.num_syn_tcpopts = 3;
+ tfp = processor->get_tcp_fp(key, ttl, mode);
+ CHECK(tfp == nullptr);
- snort::TcpFpProcessor* tfp = snort::get_tcp_fp_processor();
- CHECK(tfp == &tcp_fp_processor);
+ delete processor;
+ set_tcp_fp_processor(nullptr);
}
#endif
#ifndef RNA_FINGERPRINT_TCP_H
#define RNA_FINGERPRINT_TCP_H
-#include <list>
+#include <unordered_map>
#include <vector>
#include "main/snort_types.h"
#include "protocols/packet.h"
+#include "protocols/tcp.h"
#include "rna_fingerprint.h"
+class RNAFlow;
+
namespace snort
{
-enum FpElementType
-{
- RANGE=1,
- INCREMENT,
- SYN_MATCH,
- RANDOM,
- DONT_CARE,
- SYNTS
-};
-
-class FpElement
+class SO_PUBLIC TcpFingerprint : public FpFingerprint
{
public:
- FpElementType type;
- union
- {
- int value;
- struct
- {
- int min;
- int max;
- } range;
- } d;
-};
-class FpTcpFingerprint : public FpFingerprint
-{
-public:
+ TcpFingerprint() = default;
+ TcpFingerprint(const RawFingerprint& rfp);
std::vector<FpElement> tcp_window;
std::vector<FpElement> mss;
std::vector<FpElement> id;
std::vector<FpElement> topts;
std::vector<FpElement> ws;
+ bool df = false;
+
+ void clear() override
+ {
+ FpFingerprint::clear();
+ tcp_window.clear();
+ mss.clear();
+ id.clear();
+ topts.clear();
+ ws.clear();
+ df = false;
+ }
+
+ bool operator==(const TcpFingerprint& y) const;
+};
+
+struct FpTcpKey
+{
+ int synmss;
+ uint8_t *syn_tcpopts;
+ int num_syn_tcpopts;
+ int syn_timestamp;
+
+ int tcp_window;
+ int mss;
+ int ws;
+
+ int mss_pos;
+ int ws_pos;
+ int sackok_pos;
+ int timestamp_pos;
+
char df;
+ uint8_t isIpv6;
};
-class TcpFpProcessor
+class SO_PUBLIC TcpFpProcessor
{
public:
+ typedef std::unordered_map<uint32_t, TcpFingerprint> TcpFpContainer;
+
enum TCP_FP_MODE { SERVER, CLIENT };
- typedef std::list<snort::FpTcpFingerprint>::iterator Iter_t;
+ void push(const TcpFingerprint& tfp);
+
+ void make_tcp_fp_tables(TCP_FP_MODE mode);
- SO_PUBLIC void push(const std::vector<snort::FpTcpFingerprint>&, TCP_FP_MODE);
+ const TcpFingerprint* get_tcp_fp(const FpTcpKey& key, uint8_t ttl, TCP_FP_MODE mode) const;
+ const TcpFingerprint* get(const Packet* p, RNAFlow* flowp) const;
+
+ const TcpFingerprint* get(uint32_t fpid) const
+ {
+ auto it = tcp_fps.find(fpid);
+ return it != tcp_fps.end() ? &it->second : nullptr;
+ }
+
+ const TcpFpContainer& get_tcp_fps() const
+ { return tcp_fps; }
private:
- // table_tcp_xxx[i] contains all fingerprints whose tcp window range
- // contains i
- std::vector<const snort::FpTcpFingerprint*> table_tcp_server[snort::MAX_PORTS];
- std::vector<const snort::FpTcpFingerprint*> table_tcp_client[snort::MAX_PORTS];
+ // underlying container for input fingerprints
+ TcpFpContainer tcp_fps;
+
+ // table_tcp_xxx[i] contains pointers into tcp_fps to all fingerprints
+ // whose tcp window range contains i
+ static constexpr uint32_t table_size = TCP_MAXWIN + 1;
+ std::vector<const snort::TcpFingerprint*> table_tcp_server[table_size];
+ std::vector<const snort::TcpFingerprint*> table_tcp_client[table_size];
};
-SO_PUBLIC TcpFpProcessor* get_tcp_fp_processor();
}
+snort::TcpFpProcessor* get_tcp_fp_processor();
+void set_tcp_fp_processor(snort::TcpFpProcessor*);
+
+struct FpFingerprintState
+{
+ int initial_mss = -1;
+ int timestamp = -1;
+ int numopts = -1;
+ uint8_t tcpopts[4];
+ time_t timeout = -1;
+
+ bool set(const snort::Packet*);
+};
+
+class RNAFlow : public snort::FlowData
+{
+public:
+ FpFingerprintState state;
+
+
+ RNAFlow() : FlowData(inspector_id) { }
+ ~RNAFlow() override { }
+
+ static void init();
+ size_t size_of() override;
+
+ static unsigned inspector_id;
+};
+
#endif
+++ /dev/null
-//--------------------------------------------------------------------------
-// Copyright (C) 2020-2020 Cisco and/or its affiliates. All rights reserved.
-//
-// This program is free software; you can redistribute it and/or modify it
-// under the terms of the GNU General Public License Version 2 as published
-// by the Free Software Foundation. You may not use, modify or distribute
-// this program under any other version of the GNU General Public License.
-//
-// 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 along
-// with this program; if not, write to the Free Software Foundation, Inc.,
-// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-//--------------------------------------------------------------------------
-
-// rna_fp_reader.h author Silviu Minut <sminut@cisco.com>
-
-#ifndef RNA_FP_READER_H
-#define RNA_FP_READER_H
-
-#include <vector>
-
-#include "main/snort_types.h"
-
-#include "rna_fingerprint_tcp.h"
-
-namespace snort
-{
-
-class RnaFingerprintReader
-{
-public:
- RnaFingerprintReader() { }
- virtual ~RnaFingerprintReader() { }
- virtual bool init(const char*) { return true; }
-
- const std::vector<FpTcpFingerprint>& get_tcp_server_fps() const { return tcp_server_fps; }
- const std::vector<FpTcpFingerprint>& get_tcp_client_fps() const { return tcp_client_fps; }
-
-protected:
- std::vector<FpTcpFingerprint> tcp_server_fps;
- std::vector<FpTcpFingerprint> tcp_client_fps;
-};
-
-SO_PUBLIC const RnaFingerprintReader* get_rna_fp_reader();
-SO_PUBLIC void set_rna_fp_reader(RnaFingerprintReader*);
-
-}
-
-#endif
#include "protocols/packet.h"
#include "rna_event_handler.h"
+#include "rna_fingerprint_tcp.h"
+#include "rna_module.h"
+#include "rna_pnd.h"
#ifdef UNIT_TEST
#include "catch/snort_catch.h"
{
delete pnd;
delete rna_conf;
+ if (mod_conf)
+ delete mod_conf->processor;
delete mod_conf;
}
if ( mod_conf )
{
ConfigLogger::log_value("rna_conf_path", mod_conf->rna_conf_path.c_str());
- ConfigLogger::log_value("fingerprint_dir", mod_conf->fingerprint_dir.c_str());
ConfigLogger::log_flag("enable_logger", mod_conf->enable_logger);
ConfigLogger::log_flag("log_when_idle", mod_conf->log_when_idle);
}
void RnaInspector::tinit()
{
// thread local initialization
+ set_tcp_fp_processor(mod_conf->processor);
}
void RnaInspector::tterm()
in_stream.close();
}
+TcpFpProcessor* RnaInspector::get_or_create_fp_processor()
+{
+ if (mod_conf)
+ {
+ if (!mod_conf->processor)
+ mod_conf->processor = new TcpFpProcessor;
+ return mod_conf->processor;
+ }
+ return nullptr;
+}
+
+
//-------------------------------------------------------------------------
// api stuff
//-------------------------------------------------------------------------
static void rna_inspector_pinit()
{
// global initialization
+ RNAFlow::init();
}
static void rna_inspector_pterm()
#include "framework/inspector.h"
-#include "rna_module.h"
-#include "rna_pnd.h"
+#include <string>
namespace snort
{
struct Packet;
+class TcpFpProcessor;
}
-class RnaInspector : public snort::Inspector
+struct RnaConfig;
+class RnaModule;
+struct RnaModuleConfig;
+class RnaPnd;
+
+class SO_PUBLIC RnaInspector : public snort::Inspector
{
public:
RnaInspector(RnaModule*);
void tinit() override;
void tterm() override;
+ snort::TcpFpProcessor* get_or_create_fp_processor();
+
private:
void load_rna_conf();
- const RnaModuleConfig* mod_conf = nullptr;
+ RnaModuleConfig* mod_conf = nullptr;
RnaConfig* rna_conf = nullptr;
RnaPnd* pnd = nullptr;
};
#endif
-
#include "managers/event_manager.h"
#include "protocols/packet.h"
+#include "rna_fingerprint_tcp.h"
#include "rna_logger_common.h"
#ifdef UNIT_TEST
bool RnaLogger::log(uint16_t type, uint16_t subtype, const Packet* p, RnaTracker* ht,
const struct in6_addr* src_ip, const uint8_t* src_mac, uint32_t event_time,
- void* cond_var, const HostMac* hm, const uint16_t proto)
+ void* cond_var, const HostMac* hm, const uint16_t proto,
+ const TcpFingerprint* tfp)
{
if ( !enabled )
return false;
assert(ht);
- RnaLoggerEvent rle(type, subtype, ht, src_mac, hm, proto);
+ RnaLoggerEvent rle(type, subtype, ht, src_mac, hm, proto, tfp);
if ( src_ip and (!IN6_IS_ADDR_V4MAPPED(src_ip) or src_ip->s6_addr32[3]) )
rle.ip = src_ip;
{
class Flow;
struct Packet;
+class TcpFingerprint;
}
using RnaTracker = std::shared_ptr<snort::HostTracker>;
struct RnaLoggerEvent : public Event
{
RnaLoggerEvent(uint16_t p_type, uint16_t p_subtype, const RnaTracker* p_ht,
- const uint8_t* p_mac, const snort::HostMac* p_hm, const uint16_t p_proto)
- : type(p_type), subtype(p_subtype), ht(p_ht), mac(p_mac), hm(p_hm), proto(p_proto) { }
+ const uint8_t* p_mac, const snort::HostMac* p_hm, const uint16_t p_proto,
+ const snort::TcpFingerprint* tcp_fp)
+ : type(p_type), subtype(p_subtype), ht(p_ht), mac(p_mac), hm(p_hm), proto(p_proto), tfp(tcp_fp) { }
uint16_t type;
uint16_t subtype;
const RnaTracker* ht;
void* cond_var = nullptr;
const snort::HostMac* hm;
const uint16_t proto;
+ const snort::TcpFingerprint* tfp = nullptr;
};
class RnaLogger
bool log(uint16_t type, uint16_t subtype, const snort::Packet* p, RnaTracker* ht,
const struct in6_addr* src_ip, const uint8_t* src_mac,
uint32_t event_time = 0, void* cond_var = nullptr,
- const snort::HostMac* hm = nullptr, const uint16_t proto = 0);
+ const snort::HostMac* hm = nullptr, const uint16_t proto = 0,
+ const snort::TcpFingerprint* tfp = nullptr);
private:
const bool enabled;
#define NEW_HOST 1
#define NEW_NET_PROTOCOL 3
#define NEW_XPORT_PROTOCOL 4
+ #define NEW_OS 8
#define RNA_EVENT_CHANGE 1001
#define CHANGE_HOPS 5
#include "src/main.h"
#include "utils/util.h"
+#include "rna_fingerprint_tcp.h"
#include "rna_mac_cache.h"
#ifdef UNIT_TEST
{ nullptr, nullptr, nullptr, nullptr }
};
+static const Parameter rna_fp_params[] =
+{
+ { "fpid", Parameter::PT_INT, "0:max32", "0",
+ "fingerprint id" },
+
+ { "type", Parameter::PT_INT, "0:max32", "0",
+ "fingerprint type" },
+
+ { "uuid", Parameter::PT_STRING, nullptr, nullptr,
+ "fingerprint uuid" },
+
+ { "ttl", Parameter::PT_INT, "0:256", "0",
+ "fingerprint ttl" },
+
+ { "tcp_window", Parameter::PT_STRING, nullptr, nullptr,
+ "fingerprint tcp window" },
+
+ { "mss", Parameter::PT_STRING, nullptr, "X",
+ "fingerprint mss" },
+
+ { "id", Parameter::PT_STRING, nullptr, "X",
+ "id" },
+
+ { "topts", Parameter::PT_STRING, nullptr, nullptr,
+ "fingerprint tcp options" },
+
+ { "ws", Parameter::PT_STRING, nullptr, "X",
+ "fingerprint window size" },
+
+ { "df", Parameter::PT_BOOL, nullptr, "false",
+ "fingerprint don't fragment flag" },
+
+ { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
static const Parameter rna_params[] =
{
{ "rna_conf_path", Parameter::PT_STRING, nullptr, nullptr,
"path to rna configuration" },
- { "fingerprint_dir", Parameter::PT_STRING, nullptr, nullptr,
- "directory to fingerprint patterns" },
-
{ "enable_logger", Parameter::PT_BOOL, nullptr, "true",
"enable or disable writing discovery events into logger" },
{ "dump_file", Parameter::PT_STRING, nullptr, nullptr,
"file name to dump RNA mac cache on shutdown; won't dump by default" },
+ { "tcp_fingerprints", Parameter::PT_LIST, rna_fp_params, nullptr,
+ "list tcp fingerprints" },
+
{ nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
};
bool RnaModule::begin(const char* fqn, int, SnortConfig*)
{
- if (strcmp(fqn, RNA_NAME))
+ if (!is_valid_fqn(fqn))
return false;
- else if (!mod_conf)
+
+ if (!mod_conf)
mod_conf = new RnaModuleConfig;
+
+ if (!strcmp(fqn, "rna.tcp_fingerprints"))
+ {
+ fingerprint.clear();
+ if (!mod_conf->processor)
+ mod_conf->processor = new TcpFpProcessor;
+ }
+
return true;
}
-bool RnaModule::set(const char*, Value& v, SnortConfig*)
+bool RnaModule::set(const char* fqn, Value& v, SnortConfig*)
{
if (v.is("rna_conf_path"))
mod_conf->rna_conf_path = string(v.get_string());
- else if (v.is("fingerprint_dir"))
- mod_conf->fingerprint_dir = string(v.get_string());
else if (v.is("enable_logger"))
mod_conf->enable_logger = v.get_bool();
else if (v.is("log_when_idle"))
snort_free((void*)dump_file);
dump_file = snort_strdup(v.get_string());
}
+
+ else if (fqn && strstr(fqn, "rna.tcp_fingerprints"))
+ {
+ if (v.is("fpid"))
+ fingerprint.fpid = v.get_uint32();
+ else if (v.is("type"))
+ fingerprint.fp_type = v.get_uint32();
+ else if (v.is("uuid"))
+ fingerprint.fpuuid = v.get_string();
+ else if (v.is("ttl"))
+ fingerprint.ttl = v.get_uint8();
+ else if (v.is("tcp_window"))
+ fingerprint.tcp_window = v.get_string();
+ else if (v.is("mss"))
+ fingerprint.mss = v.get_string();
+ else if (v.is("id"))
+ fingerprint.id = v.get_string();
+ else if (v.is("topts"))
+ fingerprint.topts = v.get_string();
+ else if (v.is("ws"))
+ fingerprint.ws = v.get_string();
+ else if (v.is("df"))
+ fingerprint.df = v.get_uint8();
+ }
+
else
return false;
return true;
}
-bool RnaModule::end(const char* fqn, int, SnortConfig* sc)
+bool RnaModule::end(const char* fqn, int index, SnortConfig* sc)
{
- if ( mod_conf == nullptr and strcmp(fqn, RNA_NAME) == 0 )
+ if ( mod_conf == nullptr || !is_valid_fqn(fqn) )
return false;
- sc->set_run_flags(RUN_FLAG__TRACK_ON_SYN); // Internal flag to track TCP on SYN
+ if ( !strcmp(fqn, RNA_NAME) )
+ {
+ sc->set_run_flags(RUN_FLAG__TRACK_ON_SYN); // Internal flag to track TCP on SYN
+
+ if ( sc->ip_frags_only() )
+ {
+ WarningMessage("RNA: Disabling stream.ip_frags_only option!\n");
+ sc->clear_run_flags(RUN_FLAG__IP_FRAGS_ONLY);
+ }
+
+ if (mod_conf->processor)
+ {
+ mod_conf->processor->make_tcp_fp_tables(TcpFpProcessor::TCP_FP_MODE::SERVER);
+ mod_conf->processor->make_tcp_fp_tables(TcpFpProcessor::TCP_FP_MODE::CLIENT);
+ }
+ }
- if ( sc->ip_frags_only() )
+ if ( index > 0 && mod_conf->processor && !strcmp(fqn, "rna.tcp_fingerprints") )
{
- WarningMessage("RNA: Disabling stream.ip_frags_only option!\n");
- sc->clear_run_flags(RUN_FLAG__IP_FRAGS_ONLY);
+ // there is an implicit conversion here from raw fingerprint (all
+ // strings) to tcp fingerprint, done by the tcp fingerprint constructor
+ mod_conf->processor->push(fingerprint);
+ fingerprint.clear();
}
return true;
return 0;
}
+bool RnaModule::is_valid_fqn(const char* fqn) const
+{
+ return !strcmp(fqn, RNA_NAME) || !strcmp(fqn, "rna.tcp_fingerprints");
+}
+
+
#ifdef UNIT_TEST
+
TEST_CASE("RNA module", "[rna_module]")
{
SECTION("module begin, set, end")
v1.set(Parameter::find(rna_params, "rna_conf_path"));
CHECK(mod.set(nullptr, v1, nullptr) == true);
- Value v2("/dir/fingerprints");
- v2.set(Parameter::find(rna_params, "fingerprint_dir"));
- CHECK(mod.set(nullptr, v2, nullptr) == true);
-
Value v3("dummy");
CHECK(mod.set(nullptr, v3, nullptr) == false);
CHECK(mod.end("rna", 0, &sc) == true);
RnaModuleConfig* rc = mod.get_config();
CHECK(rc != nullptr);
CHECK(rc->rna_conf_path == "rna.conf");
- CHECK(rc->fingerprint_dir == "/dir/fingerprints");
delete rc;
}
delete mod.get_config();
}
}
+
#endif
#include "profiler/profiler.h"
#include "rna_config.h"
-
-#define RNA_NAME "rna"
-#define RNA_HELP "Real-time network awareness and OS fingerprinting (experimental)"
+#include "rna_fingerprint.h"
+#include "rna_name.h"
struct RnaStats
{
RnaModuleConfig* mod_conf = nullptr;
const char* dump_file = nullptr;
+ RawFingerprint fingerprint;
+
+ bool is_valid_fqn(const char* fqn) const;
};
#endif
//--------------------------------------------------------------------------
-// Copyright (C) 2020-2020 Cisco and/or its affiliates. All rights reserved.
+// Copyright (C) 2019-2020 Cisco and/or its affiliates. All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License Version 2 as published
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//--------------------------------------------------------------------------
-// rna_fp_reader.cc author Silviu Minut <sminut@cisco.com>
+// rna_name.h author Silviu Minut <sminut@cisco.com>
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include "rna_fp_reader.h"
-
-#ifdef UNIT_TEST
-#include "catch/snort_catch.h"
-#endif
-
-using namespace snort;
-
-static RnaFingerprintReader* fp_reader = nullptr;
-
-namespace snort
-{
-
-const RnaFingerprintReader* get_rna_fp_reader()
-{
- return fp_reader;
-}
-
-void set_rna_fp_reader(RnaFingerprintReader* fpr)
-{
- fp_reader = fpr;
-}
-
-}
-
-#ifdef UNIT_TEST
-TEST_CASE("rna_fp_reader", "[rna_fp_reader]")
-{
- RnaFingerprintReader rna_fp_reader;
- snort::set_rna_fp_reader(&rna_fp_reader);
- CHECK(fp_reader == &rna_fp_reader);
+#ifndef RNA_NAME_H
+#define RNA_NAME_H
- const RnaFingerprintReader* fpr = snort::get_rna_fp_reader();
- CHECK(fp_reader == fpr);
-}
+#define RNA_NAME "rna"
+#define RNA_HELP "Real-time network awareness and OS fingerprinting (experimental)"
#endif
#include "protocols/protocol_ids.h"
#include "protocols/tcp.h"
+#include "rna_fingerprint_tcp.h"
#include "rna_logger_common.h"
#ifdef UNIT_TEST
void RnaPnd::analyze_flow_tcp(const Packet* p, TcpPacketType type)
{
- // If and when flow stores rna state, process the flow data here before global cache access
- if ( is_eligible_tcp(p) and filter.is_host_monitored(p) )
- discover_network_tcp(p);
+ if ( is_eligible_tcp(p) )
+ {
+ // If it's a tcp SYN packet, create a fingerprint state for
+ // the SYN-ACK, but only if we're monitoring the destination (server)
+ const auto& dst_ip = p->ptrs.ip_api.get_dst();
+ if ( type == TcpPacketType::SYN && filter.is_host_monitored(p, nullptr, dst_ip) )
+ {
+ RNAFlow* rna_flow = new RNAFlow();
+ p->flow->set_flow_data(rna_flow);
+ rna_flow->state.set(p);
+ }
- UNUSED(type);
+ if ( filter.is_host_monitored(p) )
+ discover_network_tcp(p);
+ }
}
void RnaPnd::analyze_flow_udp(const Packet* p)
{
generate_change_host_update(&ht, p, src_ip, src_mac, packet_time());
}
+
+ // Fingerprint stuff
+ const TcpFpProcessor* processor;
+ if ( p->is_tcp() && (processor = get_tcp_fp_processor()) != nullptr )
+ {
+ RNAFlow* rna_flow = nullptr;
+ if ( p->ptrs.tcph->is_syn_ack() )
+ rna_flow = (RNAFlow*) p->flow->get_flow_data(RNAFlow::inspector_id);
+ const TcpFingerprint* tfp = processor->get(p, rna_flow);
+
+ if (tfp && ht->add_tcp_fingerprint(tfp->fpid))
+ {
+ logger.log(RNA_EVENT_NEW, NEW_OS, p, &ht, src_ip_ptr,
+ src_mac, 0, nullptr, nullptr, ptype, tfp);
+ }
+ }
}
inline void RnaPnd::update_vlan(const Packet* p, HostTrackerMac& hm)
add_cpputest( rna_module_test
SOURCES
../../../framework/parameter.cc
+ ../rna_fingerprint.cc
$<TARGET_OBJECTS:catch_tests>
LIBS
${DNET_LIBRARIES}
+ uuid
)
SnortConfig::SnortConfig(SnortConfig const*) {}
SnortConfig::~SnortConfig() {}
time_t packet_time() { return 0; }
+
+// tcp fingerprint functions
+void TcpFpProcessor::push(const TcpFingerprint&) { }
+void TcpFpProcessor::make_tcp_fp_tables(TCP_FP_MODE) { }
+const TcpFingerprint* TcpFpProcessor::get_tcp_fp(const FpTcpKey&, uint8_t, TCP_FP_MODE) const
+{ return nullptr; }
+const TcpFingerprint* TcpFpProcessor::get(const Packet*, RNAFlow*) const
+{ return nullptr; }
+TcpFpProcessor* get_tcp_fp_processor() { return nullptr; }
+void set_tcp_fp_processor(TcpFpProcessor*) { }
+
+TcpFingerprint::TcpFingerprint(const RawFingerprint&) { }
+bool TcpFingerprint::operator==(const TcpFingerprint&) const { return true; }
+
+// inspector
+class RnaInspector
+{
+public:
+
+// The module gets created first, with a mod_conf and fingerprint processor,
+// then, when the module is done, we take ownership of that.
+RnaInspector(RnaModule* mod)
+{
+ mod_conf = mod->get_config();
+}
+
+~RnaInspector()
+{
+ if (mod_conf)
+ {
+ if (mod_conf->processor)
+ delete mod_conf->processor;
+ delete mod_conf;
+ }
+}
+
+TcpFpProcessor* get_fp_processor()
+{
+ return mod_conf->processor;
+}
+
+private:
+ RnaModuleConfig* mod_conf = nullptr;
+};
+
+
} // end of namespace snort
#endif
CHECK_FALSE(Swapper::get_reload_in_progress());
}
+TEST(rna_module_test, push_tcp_fingerprints)
+{
+ // In plain English, we test that the RNA module pushes tcp fingerprints
+ // correctly to the processor:
+ // 1. create a raw fingerprint
+ // 2. create the corresponding expected tcp fingerprint
+ // 3. call Module::set() for each field
+ // 4. call Module::end(), to push the module internal fingerprint to the
+ // config processor
+ // 5. do this for a client-type fingerprint and a server-type fingerprint
+ // 6. the module config is private, so create an inspector and pass the
+ // config on to the inspector
+ // 6. retrieve the client and server vectors from the processors and
+ // match expected in each case
+
+ RnaModule mod;
+
+ // input fingerprint
+ RawFingerprint rawfp;
+ rawfp.fpid = 948;
+ rawfp.fp_type = FpFingerprint::FpType::FINGERPRINT_TYPE_SERVER;
+ rawfp.fpuuid = "12345678-1234-1234-1234-012345678912";
+ rawfp.ttl = 64;
+ rawfp.tcp_window = "10 20 30-40 50 60-70";
+ rawfp.mss = "X";
+ rawfp.id = "X";
+ rawfp.topts = "2 3 4 8";
+ rawfp.ws = "6";
+ rawfp.df = 1;
+
+ // expected
+ TcpFingerprint tfpe;
+ tfpe.fpid = rawfp.fpid;
+ tfpe.fp_type = rawfp.fp_type;
+ uuid_parse(rawfp.fpuuid.c_str(), tfpe.fpuuid);
+ tfpe.ttl = rawfp.ttl;
+ tfpe.tcp_window = vector<FpElement> {
+ FpElement("10"), FpElement("20"), FpElement("30-40"),
+ FpElement("50"), FpElement("60-70") };
+ tfpe.mss = vector<FpElement> { FpElement("X") };
+ tfpe.id = vector<FpElement> { FpElement("X") };
+ tfpe.topts = vector<FpElement> {
+ FpElement("2"), FpElement("3"), FpElement("4"), FpElement("8") };
+ tfpe.ws.emplace_back(FpElement("6"));
+ tfpe.df = rawfp.df;
+
+ CHECK(mod.begin("rna", 0, nullptr) == true);
+ CHECK(mod.begin("rna.tcp_fingerprints", 0, nullptr) == true); // instantiates processor
+
+ auto server_fpid = rawfp.fpid;
+ {
+ Value v((double) server_fpid);
+ v.set(Parameter::find(rna_fp_params, "fpid"));
+ CHECK(mod.set("rna.tcp_fingerprints.fpid", v, nullptr) == true);
+ }
+
+ {
+ Value v((double) rawfp.fp_type);
+ v.set(Parameter::find(rna_fp_params, "type"));
+ CHECK(mod.set("rna.tcp_fingerprints.type", v, nullptr) == true);
+ }
+
+ {
+ Value v(rawfp.fpuuid.c_str());
+ v.set(Parameter::find(rna_fp_params, "uuid"));
+ CHECK(mod.set("rna.tcp_fingerprints.uuid", v, nullptr) == true);
+ }
+
+ {
+ Value v((double) rawfp.ttl);
+ v.set(Parameter::find(rna_fp_params, "ttl"));
+ CHECK(mod.set("rna.tcp_fingerprints.ttl", v, nullptr) == true);
+ }
+
+ {
+ Value v(rawfp.tcp_window.c_str());
+ v.set(Parameter::find(rna_fp_params, "tcp_window"));
+ CHECK(mod.set("rna.tcp_fingerprints.tcp_window", v, nullptr) == true);
+ }
+
+ {
+ Value v(rawfp.mss.c_str());
+ v.set(Parameter::find(rna_fp_params, "mss"));
+ CHECK(mod.set("rna.tcp_fingerprints.mss", v, nullptr) == true);
+ }
+
+ {
+ Value v(rawfp.id.c_str());
+ v.set(Parameter::find(rna_fp_params, "id"));
+ CHECK(mod.set("rna.tcp_fingerprints.id", v, nullptr) == true);
+ }
+
+ {
+ Value v(rawfp.topts.c_str());
+ v.set(Parameter::find(rna_fp_params, "topts"));
+ CHECK(mod.set("rna.tcp_fingerprints.topts", v, nullptr) == true);
+ }
+
+ {
+ Value v(rawfp.ws.c_str());
+ v.set(Parameter::find(rna_fp_params, "ws"));
+ CHECK(mod.set("rna.tcp_fingerprints.ws", v, nullptr) == true);
+ }
+
+ {
+ Value v((double) rawfp.df);
+ v.set(Parameter::find(rna_fp_params, "df"));
+ CHECK(mod.set("rna.tcp_fingerprints.df", v, nullptr) == true);
+ }
+
+ // push it to the processor
+ CHECK(mod.end("rna.tcp_fingerprints", 0, nullptr) == true);
+
+ // add one for the client too, by changing only the type and id
+ auto client_fpid = rawfp.fpid+1; // non duplicate id
+ rawfp.fp_type = FpFingerprint::FpType::FINGERPRINT_TYPE_CLIENT;
+
+ {
+ Value v((double) client_fpid);
+ v.set(Parameter::find(rna_fp_params, "fpid"));
+ CHECK(mod.set("rna.tcp_fingerprints.fpid", v, nullptr) == true);
+ }
+
+ {
+ Value v((double) rawfp.fp_type);
+ v.set(Parameter::find(rna_fp_params, "type"));
+ CHECK(mod.set("rna.tcp_fingerprints.type", v, nullptr) == true);
+ }
+
+ {
+ Value v(rawfp.fpuuid.c_str());
+ v.set(Parameter::find(rna_fp_params, "uuid"));
+ CHECK(mod.set("rna.tcp_fingerprints.uuid", v, nullptr) == true);
+ }
+
+ {
+ Value v((double) rawfp.ttl);
+ v.set(Parameter::find(rna_fp_params, "ttl"));
+ CHECK(mod.set("rna.tcp_fingerprints.ttl", v, nullptr) == true);
+ }
+
+ {
+ Value v(rawfp.tcp_window.c_str());
+ v.set(Parameter::find(rna_fp_params, "tcp_window"));
+ CHECK(mod.set("rna.tcp_fingerprints.tcp_window", v, nullptr) == true);
+ }
+
+ {
+ Value v(rawfp.mss.c_str());
+ v.set(Parameter::find(rna_fp_params, "mss"));
+ CHECK(mod.set("rna.tcp_fingerprints.mss", v, nullptr) == true);
+ }
+
+ {
+ Value v(rawfp.id.c_str());
+ v.set(Parameter::find(rna_fp_params, "id"));
+ CHECK(mod.set("rna.tcp_fingerprints.id", v, nullptr) == true);
+ }
+
+ {
+ Value v(rawfp.topts.c_str());
+ v.set(Parameter::find(rna_fp_params, "topts"));
+ CHECK(mod.set("rna.tcp_fingerprints.topts", v, nullptr) == true);
+ }
+
+ {
+ Value v(rawfp.ws.c_str());
+ v.set(Parameter::find(rna_fp_params, "ws"));
+ CHECK(mod.set("rna.tcp_fingerprints.ws", v, nullptr) == true);
+ }
+
+ {
+ Value v((double) rawfp.df);
+ v.set(Parameter::find(rna_fp_params, "df"));
+ CHECK(mod.set("rna.tcp_fingerprints.df", v, nullptr) == true);
+ }
+
+ // push it to the processor
+ CHECK(mod.end("rna.tcp_fingerprints", 0, nullptr) == true);
+
+ // only now create the inspector
+ RnaInspector inspector(&mod); // inspector owns the processor
+
+ // final check
+ const auto* processor = inspector.get_fp_processor();
+
+ auto tfps = processor->get(server_fpid);
+ auto tfpc = processor->get(client_fpid);
+
+ // test fingerprint equality - does not use ids
+ CHECK(*tfps == tfpe);
+ CHECK(*tfpc == tfpe);
+}
+
int main(int argc, char** argv)
{
return CommandLineTestRunner::RunAllTests(argc, argv);