From 490029d336ba2409745c9d5a8f73a12454c15d1a Mon Sep 17 00:00:00 2001 From: Benjamin Bannier Date: Wed, 8 Dec 2021 09:30:43 +0100 Subject: [PATCH] Import DNS from zeek/spicy-analyzers@2e5a185. --- .cmake-format.json | 26 +++++ .github/workflows/check.yml | 39 +++++++ .github/workflows/pre-commit.yml | 14 +++ .mdlrc | 1 + .pre-commit-config.yaml | 23 ++++ CMakeLists.txt | 18 +++ README.md | 6 + analyzer/CMakeLists.txt | 5 + analyzer/__load__.zeek | 2 + analyzer/analyzer.evt | 85 ++++++++++++++ analyzer/analyzer.spicy | 155 +++++++++++++++++++++++++ analyzer/zeek_analyzer.spicy | 47 ++++++++ cmake/FindSpicyPlugin.cmake | 78 +++++++++++++ tests/analyzer/availability.zeek | 5 + tests/analyzer/basic.zeek | 15 +++ tests/analyzer/svr.zeek | 10 ++ tests/baseline/analyzer.basic/.stdout | 3 + tests/baseline/analyzer.basic/conn.log | 12 ++ tests/baseline/analyzer.basic/dns.log | 12 ++ tests/baseline/analyzer.svr/output | 4 + tests/btest.cfg | 34 ++++++ tests/scripts/zeek-path-install | 5 + tests/traces/.gitignore | 0 tests/traces/README | 7 ++ tests/traces/dns-svr.pcap | Bin 0 -> 686 bytes tests/traces/dns53.pcap | Bin 0 -> 515 bytes zkg.meta | 7 ++ 27 files changed, 613 insertions(+) create mode 100644 .cmake-format.json create mode 100644 .github/workflows/check.yml create mode 100644 .github/workflows/pre-commit.yml create mode 100644 .mdlrc create mode 100644 .pre-commit-config.yaml create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 analyzer/CMakeLists.txt create mode 100644 analyzer/__load__.zeek create mode 100644 analyzer/analyzer.evt create mode 100644 analyzer/analyzer.spicy create mode 100644 analyzer/zeek_analyzer.spicy create mode 100644 cmake/FindSpicyPlugin.cmake create mode 100644 tests/analyzer/availability.zeek create mode 100644 tests/analyzer/basic.zeek create mode 100644 tests/analyzer/svr.zeek create mode 100644 tests/baseline/analyzer.basic/.stdout create mode 100644 tests/baseline/analyzer.basic/conn.log create mode 100644 tests/baseline/analyzer.basic/dns.log create mode 100644 tests/baseline/analyzer.svr/output create mode 100644 tests/btest.cfg create mode 100755 tests/scripts/zeek-path-install create mode 100644 tests/traces/.gitignore create mode 100644 tests/traces/README create mode 100644 tests/traces/dns-svr.pcap create mode 100644 tests/traces/dns53.pcap create mode 100644 zkg.meta diff --git a/.cmake-format.json b/.cmake-format.json new file mode 100644 index 0000000..42b1e14 --- /dev/null +++ b/.cmake-format.json @@ -0,0 +1,26 @@ +{ + "parse": { + "additional_commands": { + "spicy_add_analyzer": { + "kwargs": { + "NAME": "*", + "PACKAGE_NAME": "*", + "SOURCES": "*", + "SCRIPTS": "*" + } + } + } + }, + "format": { + "line_width": 100, + "tab_size": 4, + "separate_ctrl_name_with_space": true, + "max_subgroups_hwrap": 3 + }, + "markup": { + "enable_markup": false + }, + "lint": { + "disabled_codes": ["C0103"] + } +} diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..8871527 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,39 @@ +name: Check + +on: + pull_request: + push: + branches: [main] + +jobs: + Check: + runs-on: ubuntu-latest + container: zeekurity/spicy + + steps: + - uses: actions/checkout@v2 + - name: Prepare + env: + PATH: /usr/local/bin:/opt/cmake/bin:/opt/spicy/bin:/opt/zeek/bin:/opt/zeek/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + run: | + # Clean out leftover logs for to make it easier to print "our" logs. + zkg autoconfig + + rm -f $(zkg config state_dir)/logs/*.log + zkg -vvvvv purge --force + + # Manually install spicy-plugin until the image contains a zkg version with a fix for https://github.com/zeek/package-manager/issues/106 (>=zkg-2.12.0). + zkg -vvvvv install --force --skiptests spicy-plugin + + - name: Install + env: + PATH: /usr/local/bin:/opt/cmake/bin:/opt/spicy/bin:/opt/zeek/bin:/opt/zeek/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + run: | + git clean -fd + eval $(zkg env) + echo Y | zkg -vvvvv install . + + - name: Show logs + if: always() + run: | + tail -n 1000000 $(zkg config state_dir)/logs/*.log diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..9035d06 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,14 @@ +name: pre-commit + +on: + pull_request: + push: + branches: [main] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - uses: pre-commit/action@v2.0.3 diff --git a/.mdlrc b/.mdlrc new file mode 100644 index 0000000..da394dc --- /dev/null +++ b/.mdlrc @@ -0,0 +1 @@ +rules "~MD033", "~MD013", "~MD046", "~MD010" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..0ad081b --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,23 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + +- repo: https://github.com/markdownlint/markdownlint + rev: v0.11.0 + hooks: + - id: markdownlint + +- repo: https://github.com/cheshirekow/cmake-format-precommit + rev: v0.6.13 + hooks: + - id: cmake-format + - id: cmake-lint + +exclude: '^tests/Baseline' diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ad1d9ff --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.15 FATAL_ERROR) + +project(Message LANGUAGES C) + +list(PREPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") +find_package(SpicyPlugin REQUIRED) + +# Set mininum versions that this plugin needs. Make sure to use "x.y.z" format. +spicy_require_version("1.2.0") +spicy_plugin_require_version("0.99.0") +zeek_require_version("3.0.0") + +if (NOT CMAKE_BUILD_TYPE) + # Default to release build. + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") +endif () + +add_subdirectory(analyzer) diff --git a/README.md b/README.md new file mode 100644 index 0000000..9746d92 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +DNS analyzer +============ + +This repository contains a +[Spicy](https://docs.zeek.org/projects/spicy/en/latest/)-based analyzer for the +DNS protocol. This analyzer replaces the builtin Zeek DNS analyzer. diff --git a/analyzer/CMakeLists.txt b/analyzer/CMakeLists.txt new file mode 100644 index 0000000..f845466 --- /dev/null +++ b/analyzer/CMakeLists.txt @@ -0,0 +1,5 @@ +spicy_add_analyzer( + NAME Message + PACKAGE_NAME Message + SOURCES analyzer.spicy analyzer.evt zeek_analyzer.spicy + SCRIPTS __load__.zeek) diff --git a/analyzer/__load__.zeek b/analyzer/__load__.zeek new file mode 100644 index 0000000..4f7f22c --- /dev/null +++ b/analyzer/__load__.zeek @@ -0,0 +1,2 @@ +@load-sigs ./dpd.sig +@load ./main.zeek diff --git a/analyzer/analyzer.evt b/analyzer/analyzer.evt new file mode 100644 index 0000000..2921c8b --- /dev/null +++ b/analyzer/analyzer.evt @@ -0,0 +1,85 @@ +# Copyright (c) 2021 by the Zeek Project. See LICENSE for details. + +protocol analyzer spicy::DNS over UDP: + parse with DNS::Message, + port 53/udp, + port 137/udp, + port 5353/udp, + port 5355/udp, + replaces DNS; + +import Zeek_DNS; + + +@if ZEEK_VERSION >= 30200 + # Zeek >= 3.2 adds an additional parameter with the query in its original spelling. + + on DNS::Question if ( msg.header.flags.qr == 0 ) + -> event dns_request($conn, Zeek_DNS::message(msg.header), Zeek_DNS::name(self.qname).lower(), self.qtype, self.qclass, Zeek_DNS::name(self.qname)); + + on DNS::Question if ( msg.header.flags.qr == 1 && ! msg.header.rejected ) + -> event dns_query_reply($conn, Zeek_DNS::message(msg.header), Zeek_DNS::name(self.qname).lower(), self.qtype, self.qclass, Zeek_DNS::name(self.qname)); + + on DNS::Question if ( msg.header.flags.qr == 1 && msg.header.rejected ) + -> event dns_rejected($conn, Zeek_DNS::message(msg.header), Zeek_DNS::name(self.qname).lower(), self.qtype, self.qclass, Zeek_DNS::name(self.qname)); + + on DNS::Message if ( |self.question| == 0 && ! self.header.rejected ) + -> event dns_query_reply($conn, Zeek_DNS::message(self.header), b"", cast(0), cast(0), b""); + + on DNS::Message if ( |self.question| == 0 && self.header.rejected ) + -> event dns_rejected($conn, Zeek_DNS::message(self.header), b"", cast(0), cast(0), b""); +@else + on DNS::Question if ( msg.header.flags.qr == 0 ) + -> event dns_request($conn, Zeek_DNS::message(msg.header), Zeek_DNS::name(self.qname).lower(), self.qtype, self.qclass); + + on DNS::Question if ( msg.header.flags.qr == 1 && ! msg.header.rejected ) + -> event dns_query_reply($conn, Zeek_DNS::message(msg.header), Zeek_DNS::name(self.qname).lower(), self.qtype, self.qclass); + + on DNS::Question if ( msg.header.flags.qr == 1 && msg.header.rejected ) + -> event dns_rejected($conn, Zeek_DNS::message(msg.header), Zeek_DNS::name(self.qname).lower(), self.qtype, self.qclass); + + on DNS::Message if ( |self.question| == 0 && ! self.header.rejected ) + -> event dns_query_reply($conn, Zeek_DNS::message(self.header), b"", cast(0), cast(0)); + + on DNS::Message if ( |self.question| == 0 && self.header.rejected ) + -> event dns_rejected($conn, Zeek_DNS::message(self.header), b"", cast(0), cast(0)); +@endif + + # TODO: Length of raw payload? +on DNS::Message::header -> event dns_message($conn, Zeek_DNS::is_query(self.header), Zeek_DNS::message(self.header), cast(0)); + +on DNS::Message + -> event dns_end($conn, Zeek_DNS::message(self.header)); + +on DNS::ResourceRecord if ( self.ty == DNS::RDType::A ) + -> event dns_A_reply($conn, Zeek_DNS::message(msg.header), Zeek_DNS::answer(self, rrtype), self.a); + +on DNS::ResourceRecord if ( self.ty == DNS::RDType::A6 ) + -> event dns_A6_reply($conn, Zeek_DNS::message(msg.header), Zeek_DNS::answer(self, rrtype), self.a); + +on DNS::ResourceRecord if ( self.ty == DNS::RDType::AAAA ) + -> event dns_AAAA_reply($conn, Zeek_DNS::message(msg.header), Zeek_DNS::answer(self, rrtype), self.a); + +on DNS::ResourceRecord if ( self.ty == DNS::RDType::NS ) + -> event dns_NS_reply($conn, Zeek_DNS::message(msg.header), Zeek_DNS::answer(self, rrtype), Zeek_DNS::name(self.rname)); + +on DNS::ResourceRecord if ( self.ty == DNS::RDType::CNAME ) + -> event dns_CNAME_reply($conn, Zeek_DNS::message(msg.header), Zeek_DNS::answer(self, rrtype), Zeek_DNS::name(self.rname)); + +on DNS::ResourceRecord if ( self.ty == DNS::RDType::PTR ) + -> event dns_PTR_reply($conn, Zeek_DNS::message(msg.header), Zeek_DNS::answer(self, rrtype), Zeek_DNS::name(self.rname)); + +on DNS::ResourceRecord if ( self.ty == DNS::RDType::MX ) + -> event dns_MX_reply($conn, Zeek_DNS::message(msg.header), Zeek_DNS::answer(self, rrtype), Zeek_DNS::name(self.mx.name), self.mx.preference); + +on DNS::ResourceRecord if ( self.ty == DNS::RDType::SOA ) + -> event dns_SOA_reply($conn, Zeek_DNS::message(msg.header), Zeek_DNS::answer(self, rrtype), Zeek_DNS::soa(self.soa)); + +on DNS::ResourceRecord if ( self.ty == DNS::RDType::TXT ) + -> event dns_TXT_reply($conn, Zeek_DNS::message(msg.header), Zeek_DNS::answer(self, rrtype), [i.data for i in self.txt]); + +on DNS::ResourceRecord if ( self.ty == DNS::RDType::WKS ) + -> event dns_WKS_reply($conn, Zeek_DNS::message(msg.header), Zeek_DNS::answer(self, rrtype)); + +on DNS::ResourceRecord if ( self.ty == DNS::RDType::SRV ) + -> event dns_SRV_reply($conn, Zeek_DNS::message(msg.header), Zeek_DNS::answer(self, rrtype), Zeek_DNS::name(self.srv.target), self.srv.priority_, self.srv.weight, self.srv.port_); diff --git a/analyzer/analyzer.spicy b/analyzer/analyzer.spicy new file mode 100644 index 0000000..86e620d --- /dev/null +++ b/analyzer/analyzer.spicy @@ -0,0 +1,155 @@ +# Copyright (c) 2021 by the Zeek Project. See LICENSE for details. + +module DNS; + +import spicy; + +type RDType = enum { + A = 1, NS = 2, MD = 3, MF = 4, CNAME = 5, SOA = 6, MB = 7, MG = 8, MR = 9, + NULL = 10, WKS = 11, PTR = 12, HINFO = 13, MINFO = 14, MX = 15, TXT = 16, + AAAA = 28, NBS = 32, SRV= 33, A6 = 38, EDNS = 41 }; + +type RRType = enum { + ANSWER = 1, AUTH = 2, ADDL = 3 +}; + +public type Message = unit { + %random-access; + + header: Header; + question: (Question(self))[self.header.qdcount]; + answer: (ResourceRecord(self, RRType::ANSWER))[self.header.ancount]; + authority: (ResourceRecord(self, RRType::AUTH))[self.header.nscount]; + additional: (ResourceRecord(self, RRType::ADDL))[self.header.arcount]; +}; + +type Header = unit { + id : uint16; + flags : bitfield(16) { + qr: 0; + opcode: 1..4; + aa: 5; + tc: 6; + rd: 7; + ra: 8; + z: 9..11; + rcode: 12..15; + } &bit-order = spicy::BitOrder::MSB0; + + qdcount: uint16; + ancount: uint16; + nscount: uint16; + arcount: uint16; + + var rejected : bool; + + on %done { + # Mimic Zeek in determining when a request has been rejected. + if ( self.qdcount == 0 ) + self.rejected = (self.flags.rcode != 0); # 0 == NoError; + + else + self.rejected = (self.flags.qr == 1 && + self.ancount == 0 && + self.nscount == 0 && + self.arcount == 0); + } +}; + +type Question = unit(msg: Message) { + qname: Name(msg); + qtype: uint16; + qclass: uint16; +}; + +type ResourceRecord = unit(msg: Message, rrtype: RRType) { + name: Name(msg); + ty: uint16 &convert=RDType($$); + class: uint16; + ttl: uint32 &convert=cast($$); + rdlen: uint16; + + switch ( self.ty ) { + RDType::NS, RDType::CNAME, RDType::PTR + -> rname: Name(msg); + RDType::A -> a: addr &ipv4; + RDType::AAAA -> a: addr &ipv6; + RDType::MX -> mx: RDataMX(msg); + RDType::SOA -> soa: RDataSOA(msg); + RDType::SRV -> srv: RDataSRV(msg); + RDType::TXT -> txt: (CharacterString(msg))[self.rdlen]; + + * -> rdata: bytes &size=self.rdlen; + }; +}; + +type RDataMX = unit(msg: Message) { + preference: uint16; + name: Name(msg); +}; + +type RDataSOA = unit(msg: Message) { + mname: Name(msg); + rname: Name(msg); + serial: uint32; + refresh: uint32 &convert=cast($$); + retry: uint32 &convert=cast($$); + expire: uint32 &convert=cast($$); + minimum: uint32 &convert=cast($$); +}; + +type RDataSRV = unit(msg: Message) { + priority_: uint16; + weight: uint16; + port_: uint16; + target: Name(msg); +}; + +type CharacterString = unit(msg: Message) { + len: uint8; + data: bytes &size=(self.len); +}; + +type Name = unit(msg: Message) { + : (Label(msg, self))[] &until=($$.len.offset == 0 || $$.len.compressed != 0); + + var label: bytes = b""; +}; + +type Pointer = unit(msg: Message, label: Label) { + len: bitfield(16) { + offset: 0..13; + }; + + name: Name(msg) &parse-at=(msg.input() + self.len.offset); + }; + +type Label = unit(msg: Message, inout name: Name) { + %random-access; + + len: bitfield(8) { + offset: 0..5; + compressed: 6..7; + }; + + switch ( self.len.compressed ) { + 0 -> label: bytes &size=self.len.offset { + if ( |self.label| ) { + name.label += b"."; + name.label += self.label; + } + } + + 3 -> ptr: Pointer(msg, self) &parse-at=self.input() { + name.label += self.ptr.name.label; + self.adjust = 2; # Consume the additional byte in %done. + } + }; + + on %done { + if ( self.adjust > 0 ) + self.set_input(self.input() + self.adjust); + } + + var adjust: uint64 = 0; +}; diff --git a/analyzer/zeek_analyzer.spicy b/analyzer/zeek_analyzer.spicy new file mode 100644 index 0000000..fa50c96 --- /dev/null +++ b/analyzer/zeek_analyzer.spicy @@ -0,0 +1,47 @@ +# Copyright (c) 2021 by the Zeek Project. See LICENSE for details. +# +# Augment standard DNS grammar with Zeek-specific logic. + +module Zeek_DNS; + +import DNS from protocols; +import spicy; +import zeek; + +on DNS::Message::%done { + zeek::confirm_protocol(); +} + +on DNS::Header::%done { + if ( zeek::is_orig() && self.flags.qr && zeek::number_packets() == 1 ) + zeek::flip_roles(); +} + +# Follow Zeek's heuristic if it's a query. +public function is_query(header: DNS::Header) : bool { + if ( zeek::is_orig() && header.flags.qr && zeek::number_packets() == 1 ) + return True; + + return zeek::is_orig(); +} + +# Normalize the label. +public function name(n: DNS::Name) : bytes { + return n.label.strip(spicy::Side::Both, b".").lower(); +} + +# Converts a DNS::Header into a Zeek dns_msg. +public function message(hdr: DNS::Header) : tuple { + return (hdr.id, hdr.flags.opcode, hdr.flags.rcode, hdr.flags.qr != 0, hdr.flags.aa != 0, hdr.flags.tc != 0, hdr.flags.rd != 0, + hdr.flags.ra != 0, hdr.flags.z, hdr.qdcount, hdr.ancount, hdr.nscount, hdr.arcount); +} + +# Converts a DNS::ResourceRecord into a Zeek dns_answer. +public function answer(rr: DNS::ResourceRecord, rrtype: DNS::RRType) : tuple { + return (cast(rrtype), name(rr.name), cast(rr.ty), rr.class, rr.ttl); +} + +# Converts a DNS::RDataSOA into a Zeek dns_soa. +public function soa(soa: DNS::RDataSOA) : tuple { + return (name(soa.mname), name(soa.rname), soa.serial, soa.refresh, soa.retry, soa.expire, soa.minimum); +} diff --git a/cmake/FindSpicyPlugin.cmake b/cmake/FindSpicyPlugin.cmake new file mode 100644 index 0000000..67cbd47 --- /dev/null +++ b/cmake/FindSpicyPlugin.cmake @@ -0,0 +1,78 @@ +# Find the Spicy plugin to get access to the infrastructure it provides. +# +# While most of the actual CMake logic for building analyzers comes with the Spicy +# plugin for Zeek, this code bootstraps us by asking "spicyz" for the plugin's +# location. Either make sure that "spicyz" is in PATH, set the environment +# variable SPICYZ to point to its location, or set variable ZEEK_SPICY_ROOT +# in either CMake or environment to point to its installation or build +# directory. +# +# This exports: +# +# SPICY_PLUGIN_FOUND True if plugin and all dependencies were found +# SPICYZ Path to spicyz +# SPICY_PLUGIN_VERSION Version string of plugin +# SPICY_PLUGIN_VERSION_NUMBER Numerical version number of plugin + +# Runs `spicyz` with the flags given as second argument and stores the output in the variable named +# by the first argument. +function (run_spicycz output) + execute_process(COMMAND "${SPICYZ}" ${ARGN} OUTPUT_VARIABLE output_ + OUTPUT_STRIP_TRAILING_WHITESPACE) + + string(STRIP "${output_}" output_) + set(${output} "${output_}" PARENT_SCOPE) +endfunction () + +# Checks that the Spicy plugin version it at least the given version. +function (spicy_plugin_require_version version) + string(REGEX MATCH "([0-9]*)\.([0-9]*)\.([0-9]*).*" _ ${version}) + math(EXPR version_number "${CMAKE_MATCH_1} * 10000 + ${CMAKE_MATCH_2} * 100 + ${CMAKE_MATCH_3}") + + if ("${SPICY_PLUGIN_VERSION_NUMBER}" LESS "${version_number}") + message(FATAL_ERROR "Package requires at least Spicy plugin version ${version}, " + "have ${SPICY_PLUGIN_VERSION}") + endif () +endfunction () + +### +### Main +### + +if (NOT SPICYZ) + set(SPICYZ "$ENV{SPICYZ}") +endif () + +if (NOT SPICYZ) + # Support an in-tree Spicy build. + find_program( + spicyz spicyz + HINTS ${ZEEK_SPICY_ROOT}/bin ${ZEEK_SPICY_ROOT}/build/bin $ENV{ZEEK_SPICY_ROOT}/bin + $ENV{ZEEK_SPICY_ROOT}/build/bin ${PROJECT_SOURCE_DIR}/../../build/bin) + set(SPICYZ "${spicyz}") +endif () + +message(STATUS "spicyz: ${SPICYZ}") + +if (SPICYZ) + set(SPICYZ "${SPICYZ}" CACHE PATH "" FORCE) # make sure it's in the cache + + run_spicycz(SPICY_PLUGIN_VERSION "--version") + run_spicycz(SPICY_PLUGIN_VERSION_NUMBER "--version-number") + message(STATUS "Zeek plugin version: ${SPICY_PLUGIN_VERSION}") + + run_spicycz(spicy_plugin_path "--print-plugin-path") + set(spicy_plugin_cmake_path "${spicy_plugin_path}/cmake") + message(STATUS "Zeek plugin CMake path: ${spicy_plugin_cmake_path}") + + list(PREPEND CMAKE_MODULE_PATH "${spicy_plugin_cmake_path}") + find_package(Zeek REQUIRED) + find_package(Spicy REQUIRED) + zeek_print_summary() + spicy_print_summary() + + include(ZeekSpicyAnalyzerSupport) +endif () + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(SpicyPlugin DEFAULT_MSG SPICYZ ZEEK_FOUND) diff --git a/tests/analyzer/availability.zeek b/tests/analyzer/availability.zeek new file mode 100644 index 0000000..7ec95c4 --- /dev/null +++ b/tests/analyzer/availability.zeek @@ -0,0 +1,5 @@ +# Copyright (c) 2021 by the Zeek Project. See LICENSE for details. + +# @TEST-EXEC: zeek -NN | grep -q ANALYZER_SPICY_DNS +# +# @TEST-DOC: Check that DNS analyzer is available. diff --git a/tests/analyzer/basic.zeek b/tests/analyzer/basic.zeek new file mode 100644 index 0000000..871e707 --- /dev/null +++ b/tests/analyzer/basic.zeek @@ -0,0 +1,15 @@ +# Copyright (c) 2021 by the Zeek Project. See LICENSE for details. + +# @TEST-EXEC: zeek -r ${TRACES}/dns53.pcap %INPUT +# @TEST-EXEC: btest-diff conn.log +# @TEST-EXEC: btest-diff dns.log +# @TEST-EXEC: if zeek-version 32000; then btest-diff .stdout; fi +# +# @TEST-DOC: Test DNS analyzer with small trace. + +@if ( Version::number >= 32000 ) +# Check the new signature of the event +event dns_query_reply(c: connection, msg: dns_msg, query: string, qtype: count, qclass: count, original_query: string) { + print query, original_query; # both are the same with our trace +} +@endif diff --git a/tests/analyzer/svr.zeek b/tests/analyzer/svr.zeek new file mode 100644 index 0000000..706cc0f --- /dev/null +++ b/tests/analyzer/svr.zeek @@ -0,0 +1,10 @@ +# @TEST-EXEC: zeek -r ${TRACES}/dns-svr.pcap %INPUT >output +# Zeek 3.0 prints intervals differently, leading to a change in TTL; not worth worrying about, so we just skip the diff for 3.0. +# @TEST-EXEC: if zeek-version 40000; then btest-diff output; fi +# +# @TEST-DOC: Test the DNS SVR event. + +event dns_SRV_reply(c: connection, msg: dns_msg, ans: dns_answer, target: string, priority: count, weight: count, p: count) + { + print c$id, msg, ans, target, priority, weight, p; + } diff --git a/tests/baseline/analyzer.basic/.stdout b/tests/baseline/analyzer.basic/.stdout new file mode 100644 index 0000000..3a3f341 --- /dev/null +++ b/tests/baseline/analyzer.basic/.stdout @@ -0,0 +1,3 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +### NOTE: This file has been sorted with diff-sort. +us.v27.distributed.net, us.v27.distributed.net diff --git a/tests/baseline/analyzer.basic/conn.log b/tests/baseline/analyzer.basic/conn.log new file mode 100644 index 0000000..83f4bc4 --- /dev/null +++ b/tests/baseline/analyzer.basic/conn.log @@ -0,0 +1,12 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +### NOTE: This file has been sorted with diff-sort. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path conn +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto service duration orig_bytes resp_bytes conn_state local_orig local_resp missed_bytes history orig_pkts orig_ip_bytes resp_pkts resp_ip_bytes tunnel_parents +#types time string addr port addr port enum string interval count count string bool bool count string count count count count set[string] +#close XXXX-XX-XX-XX-XX-XX +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.20.1.31 53 207.158.192.40 53 udp dns - - - S0 - - 0 D^ 1 461 0 0 - diff --git a/tests/baseline/analyzer.basic/dns.log b/tests/baseline/analyzer.basic/dns.log new file mode 100644 index 0000000..afefb25 --- /dev/null +++ b/tests/baseline/analyzer.basic/dns.log @@ -0,0 +1,12 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +### NOTE: This file has been sorted with diff-sort. +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path dns +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto trans_id rtt query qclass qclass_name qtype qtype_name rcode rcode_name AA TC RD RA Z answers TTLs rejected +#types time string addr port addr port enum count interval string count string count string count string bool bool bool bool count vector[string] vector[interval] bool +#close XXXX-XX-XX-XX-XX-XX +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.20.1.31 53 207.158.192.40 53 udp 25701 - us.v27.distributed.net - - - - 0 NOERROR T F F T 0 206.109.64.186,216.1.205.81,205.149.163.211,134.53.131.135,134.53.131.192,128.104.18.148,204.152.186.139,63.77.33.226 900.000000,900.000000,900.000000,900.000000,900.000000,900.000000,900.000000,900.000000 F diff --git a/tests/baseline/analyzer.svr/output b/tests/baseline/analyzer.svr/output new file mode 100644 index 0000000..1d69a3d --- /dev/null +++ b/tests/baseline/analyzer.svr/output @@ -0,0 +1,4 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +### NOTE: This file has been sorted with diff-sort. +[orig_h=192.168.7.105, orig_p=54488/udp, resp_h=1.1.1.1, resp_p=53/udp], [id=19549, opcode=0, rcode=0, QR=T, AA=F, TC=F, RD=T, RA=T, Z=0, num_queries=1, num_answers=2, num_auth=0, num_addl=1], [answer_type=1, query=_sip._udp.sip.voice.google.com, qtype=33, qclass=1, TTL=4.0 mins 10.0 secs], sip-anycast-1.voice.google.com, 10, 1, 5060 +[orig_h=192.168.7.105, orig_p=54488/udp, resp_h=1.1.1.1, resp_p=53/udp], [id=19549, opcode=0, rcode=0, QR=T, AA=F, TC=F, RD=T, RA=T, Z=0, num_queries=1, num_answers=2, num_auth=0, num_addl=1], [answer_type=1, query=_sip._udp.sip.voice.google.com, qtype=33, qclass=1, TTL=4.0 mins 10.0 secs], sip-anycast-2.voice.google.com, 20, 1, 5060 diff --git a/tests/btest.cfg b/tests/btest.cfg new file mode 100644 index 0000000..914dea4 --- /dev/null +++ b/tests/btest.cfg @@ -0,0 +1,34 @@ +[btest] +MinVersion = 0.66 + +TestDirs = analyzer +TmpDir = %(testbase)s/.tmp +BaselineDir = %(testbase)s/baseline +IgnoreDirs = .svn CVS .tmp Baseline Failing traces Traces +IgnoreFiles = .DS_Store *.pcap data.* *.dat *.wmv *.der *.tmp *.swp .*.swp #* CMakeLists.txt + +[environment] +DIST=%(testbase)s/.. +PATH=%(testbase)s/../tests/scripts:`spicyz --print-plugin-path`/tests/scripts:%(default_path)s +SCRIPTS=`spicyz --print-plugin-path`/tests/Scripts +ZEEK_SPICY_MODULE_PATH=%(testbase)s/../build/spicy-modules +TEST_DIFF_CANONIFIER=`spicyz --print-plugin-path`/tests/Scripts/canonify-zeek-log-sorted +TRACES=%(testbase)s/traces +ZEEKPATH=%(testbase)s/..:`zeek-config --zeekpath` +ZEEK_SEED_FILE=`spicyz --print-plugin-path`/tests/random.seed + +# Set variables to well-defined state. +LANG=C +LC_ALL=C +TZ=UTC +CC= +CXX= +CFLAGS= +CPPFLAGS= +CXXFLAGS= +LDFLAGS= +DYLDFLAGS= + +[environment-installation] +ZEEK_SPICY_MODULE_PATH= +ZEEKPATH=`%(testbase)s/scripts/zeek-path-install` diff --git a/tests/scripts/zeek-path-install b/tests/scripts/zeek-path-install new file mode 100755 index 0000000..3ce6131 --- /dev/null +++ b/tests/scripts/zeek-path-install @@ -0,0 +1,5 @@ +#! /bin/sh +# +# Assembles the Zeek path for testing the installed version (can't do that in btest.cfg directly). + +echo $(spicyz --print-scripts-path):$(zkg config script_dir):$(zeek-config --zeekpath) diff --git a/tests/traces/.gitignore b/tests/traces/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/tests/traces/README b/tests/traces/README new file mode 100644 index 0000000..749987c --- /dev/null +++ b/tests/traces/README @@ -0,0 +1,7 @@ +The test suite comes with a set of traces collected from a variety of +places that we document below. While these traces are all coming from +public sources, please note that they may carry their own licenses. +We collect them here for convenience only. + +- [dns53.pcap](https://github.com/zeek/zeek/blob/master/testing/btest/Traces/dns53.pcap) +- dns-svr.pcap (self-made) diff --git a/tests/traces/dns-svr.pcap b/tests/traces/dns-svr.pcap new file mode 100644 index 0000000000000000000000000000000000000000..02c12813802cecf480f15a226ab3bd8481553aa1 GIT binary patch literal 686 zcmca|c+)~A1{MYcU}0bcacoi&_MBs52mvxd_{Z-3j-P}k%<}c7`iJ^FbJ5I>aWh9mm#AcVMsR!Jtvhp zIX{esf#Zo^V>|D+tK(3kogT6LQ|0W zXJ+i`M&{f5Dn7#QFea#z*qHN*4G*k2uz~?u#0W#g7(>JiMT8Gz4ogyMaS3yBel7#L zc?X5jM2vwV#~}e@;lRLhF-d{%z)b|-fpht?1FsQ$a6tV<@Ie7}kRQxvdb0+oUt+4} XK}|59anBbZe{LH0L2D$w#8fQ+S+IRu literal 0 HcmV?d00001 diff --git a/zkg.meta b/zkg.meta new file mode 100644 index 0000000..c63ddf7 --- /dev/null +++ b/zkg.meta @@ -0,0 +1,7 @@ +[package] +summary = Spicy-based analyzer for the DNS protocol +description = Spicy-based analyzer for the DNS protocol. +script_dir = analyzer +plugin_dir = build/spicy-modules +build_command = unset -v CXX CXXFLAGS LD LDFLAGS && mkdir -p build && cd build && SPICYZ=%(package_base)s/spicy-plugin/build/bin/spicyz cmake .. && cmake --build . +test_command = unset -v CXX CXXFLAGS LD LDFLAGS && cd tests && PATH=$(zkg config plugin_dir)/packages/spicy-plugin/bin:$PATH btest -d -j $(nproc)