summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--.clang-format6
-rw-r--r--.copr/Makefile23
-rw-r--r--.github/FUNDING.yml1
-rw-r--r--.gitignore100
-rw-r--r--.lgtm.yml21
-rw-r--r--.travis.yml29
-rw-r--r--CHANGES106
-rw-r--r--LICENSE674
-rw-r--r--Makefile.am31
-rw-r--r--README.md153
-rwxr-xr-xautogen.sh20
-rw-r--r--configure.ac131
-rw-r--r--examples/Makefile.am21
-rwxr-xr-xexamples/capture.lua47
-rwxr-xr-xexamples/count-pkts-per-ip.lua50
-rwxr-xr-xexamples/dumpdns-qr.lua85
-rwxr-xr-xexamples/dumpdns.lua46
-rwxr-xr-xexamples/dumpdns2pcap.lua38
-rwxr-xr-xexamples/filter_rcode.lua38
-rwxr-xr-xexamples/qr-multi-pcap-state.lua270
-rwxr-xr-xexamples/readme.lua20
-rwxr-xr-xexamples/replay.lua119
-rwxr-xr-xexamples/replay_multicli.lua192
-rwxr-xr-xexamples/respdiff.lua160
-rwxr-xr-xexamples/test_pcap_read.lua149
-rwxr-xr-xexamples/test_throughput.lua539
-rwxr-xr-xfmt.sh26
-rw-r--r--m4/ax_append_flag.m450
-rw-r--r--m4/ax_cflags_warn_all.m4158
-rw-r--r--m4/ax_check_compile_flag.m453
-rw-r--r--m4/ax_compiler_vendor.m4117
-rw-r--r--m4/ax_ext.m4328
-rw-r--r--m4/ax_gcc_x86_avx_xgetbv.m479
-rw-r--r--m4/ax_gcc_x86_cpuid.m489
-rw-r--r--m4/ax_prepend_flag.m451
-rw-r--r--m4/ax_pthread.m4507
-rw-r--r--m4/ax_require_defined.m437
-rwxr-xr-xm4/dl.sh27
-rw-r--r--rpm/dnsjit.spec168
-rw-r--r--src/Makefile.am280
-rw-r--r--src/core.lua45
-rw-r--r--src/core/assert.h46
-rw-r--r--src/core/channel.c169
-rw-r--r--src/core/channel.h41
-rw-r--r--src/core/channel.hh49
-rw-r--r--src/core/channel.lua142
-rw-r--r--src/core/compat.c25
-rw-r--r--src/core/compat.h32
-rw-r--r--src/core/compat.lua41
-rw-r--r--src/core/log.c399
-rw-r--r--src/core/log.h105
-rw-r--r--src/core/log.hh44
-rw-r--r--src/core/log.lua639
-rw-r--r--src/core/object.c135
-rw-r--r--src/core/object.h51
-rw-r--r--src/core/object.hh28
-rw-r--r--src/core/object.lua177
-rw-r--r--src/core/object/dns.c471
-rw-r--r--src/core/object/dns.h184
-rw-r--r--src/core/object/dns.hh119
-rw-r--r--src/core/object/dns.lua797
-rw-r--r--src/core/object/dns/label.lua118
-rw-r--r--src/core/object/dns/q.lua52
-rw-r--r--src/core/object/dns/rr.lua85
-rw-r--r--src/core/object/ether.c45
-rw-r--r--src/core/object/ether.h38
-rw-r--r--src/core/object/ether.hh33
-rw-r--r--src/core/object/ether.lua78
-rw-r--r--src/core/object/gre.c45
-rw-r--r--src/core/object/gre.h38
-rw-r--r--src/core/object/gre.hh37
-rw-r--r--src/core/object/gre.lua87
-rw-r--r--src/core/object/icmp.c45
-rw-r--r--src/core/object/icmp.h38
-rw-r--r--src/core/object/icmp.hh33
-rw-r--r--src/core/object/icmp.lua78
-rw-r--r--src/core/object/icmp6.c45
-rw-r--r--src/core/object/icmp6.h38
-rw-r--r--src/core/object/icmp6.hh33
-rw-r--r--src/core/object/icmp6.lua78
-rw-r--r--src/core/object/ieee802.c45
-rw-r--r--src/core/object/ieee802.h38
-rw-r--r--src/core/object/ieee802.hh35
-rw-r--r--src/core/object/ieee802.lua84
-rw-r--r--src/core/object/ip.c45
-rw-r--r--src/core/object/ip.h38
-rw-r--r--src/core/object/ip.hh40
-rw-r--r--src/core/object/ip.lua122
-rw-r--r--src/core/object/ip6.c45
-rw-r--r--src/core/object/ip6.h42
-rw-r--r--src/core/object/ip6.hh42
-rw-r--r--src/core/object/ip6.lua130
-rw-r--r--src/core/object/linuxsll.c45
-rw-r--r--src/core/object/linuxsll.h38
-rw-r--r--src/core/object/linuxsll.hh35
-rw-r--r--src/core/object/linuxsll.lua84
-rw-r--r--src/core/object/loop.c45
-rw-r--r--src/core/object/loop.h38
-rw-r--r--src/core/object/loop.hh31
-rw-r--r--src/core/object/loop.lua72
-rw-r--r--src/core/object/null.c45
-rw-r--r--src/core/object/null.h38
-rw-r--r--src/core/object/null.hh31
-rw-r--r--src/core/object/null.lua72
-rw-r--r--src/core/object/payload.c50
-rw-r--r--src/core/object/payload.h38
-rw-r--r--src/core/object/payload.hh32
-rw-r--r--src/core/object/payload.lua83
-rw-r--r--src/core/object/pcap.c50
-rw-r--r--src/core/object/pcap.h38
-rw-r--r--src/core/object/pcap.hh38
-rw-r--r--src/core/object/pcap.lua98
-rw-r--r--src/core/object/tcp.c45
-rw-r--r--src/core/object/tcp.h39
-rw-r--r--src/core/object/tcp.hh43
-rw-r--r--src/core/object/tcp.lua110
-rw-r--r--src/core/object/udp.c45
-rw-r--r--src/core/object/udp.h38
-rw-r--r--src/core/object/udp.hh34
-rw-r--r--src/core/object/udp.lua86
-rw-r--r--src/core/objects.lua61
-rw-r--r--src/core/producer.c23
-rw-r--r--src/core/producer.h28
-rw-r--r--src/core/producer.hh23
-rw-r--r--src/core/producer.lua28
-rw-r--r--src/core/receiver.c23
-rw-r--r--src/core/receiver.h28
-rw-r--r--src/core/receiver.hh23
-rw-r--r--src/core/receiver.lua28
-rw-r--r--src/core/thread.c229
-rw-r--r--src/core/thread.h31
-rw-r--r--src/core/thread.hh56
-rw-r--r--src/core/thread.lua141
-rw-r--r--src/core/timespec.h33
-rw-r--r--src/core/timespec.hh24
-rw-r--r--src/core/timespec.lua32
-rw-r--r--src/dnsjit.1in144
-rw-r--r--src/dnsjit.c122
-rw-r--r--src/filter.lua31
-rw-r--r--src/filter/copy.c192
-rw-r--r--src/filter/copy.h30
-rw-r--r--src/filter/copy.hh40
-rw-r--r--src/filter/copy.lua74
-rw-r--r--src/filter/ipsplit.c270
-rw-r--r--src/filter/ipsplit.h29
-rw-r--r--src/filter/ipsplit.hh61
-rw-r--r--src/filter/ipsplit.lua122
-rw-r--r--src/filter/layer.c689
-rw-r--r--src/filter/layer.h44
-rw-r--r--src/filter/layer.hh70
-rw-r--r--src/filter/layer.lua93
-rw-r--r--src/filter/split.c114
-rw-r--r--src/filter/split.h29
-rw-r--r--src/filter/split.hh50
-rw-r--r--src/filter/split.lua80
-rw-r--r--src/filter/timing.c557
-rw-r--r--src/filter/timing.h30
-rw-r--r--src/filter/timing.hh52
-rw-r--r--src/filter/timing.lua123
-rw-r--r--src/gen-compat.lua34
-rwxr-xr-xsrc/gen-errno.sh28
-rwxr-xr-xsrc/gen-makefile.sh130
-rw-r--r--src/gen-manpage.lua125
-rw-r--r--src/globals.c60
-rw-r--r--src/globals.h28
-rw-r--r--src/input.lua29
-rw-r--r--src/input/fpcap.c338
-rw-r--r--src/input/fpcap.h31
-rw-r--r--src/input/fpcap.hh62
-rw-r--r--src/input/fpcap.lua131
-rw-r--r--src/input/mmpcap.c335
-rw-r--r--src/input/mmpcap.h31
-rw-r--r--src/input/mmpcap.hh60
-rw-r--r--src/input/mmpcap.lua120
-rw-r--r--src/input/pcap.c206
-rw-r--r--src/input/pcap.h33
-rw-r--r--src/input/pcap.hh56
-rw-r--r--src/input/pcap.lua130
-rw-r--r--src/input/zero.c75
-rw-r--r--src/input/zero.h32
-rw-r--r--src/input/zero.hh37
-rw-r--r--src/input/zero.lua75
-rw-r--r--src/lib.lua29
-rw-r--r--src/lib/base64url.c480
-rw-r--r--src/lib/base64url.h28
-rw-r--r--src/lib/base64url.hh99
-rw-r--r--src/lib/base64url.lua98
-rw-r--r--src/lib/clock.c75
-rw-r--r--src/lib/clock.h28
-rw-r--r--src/lib/clock.hh29
-rw-r--r--src/lib/clock.lua47
-rw-r--r--src/lib/getopt.lua365
-rw-r--r--src/lib/ip.lua125
-rw-r--r--src/lib/parseconf.lua181
-rw-r--r--src/lib/trie.c923
-rw-r--r--src/lib/trie.h39
-rw-r--r--src/lib/trie.hh118
-rw-r--r--src/lib/trie.lua172
-rw-r--r--src/lib/trie/iter.lua93
-rw-r--r--src/lib/trie/node.lua84
-rw-r--r--src/output.lua34
-rw-r--r--src/output/dnscli.c888
-rw-r--r--src/output/dnscli.h38
-rw-r--r--src/output/dnscli.hh72
-rw-r--r--src/output/dnscli.lua187
-rw-r--r--src/output/dnssim.c502
-rw-r--r--src/output/dnssim.h31
-rw-r--r--src/output/dnssim.hh123
-rw-r--r--src/output/dnssim.lua433
-rw-r--r--src/output/dnssim/CHANGELOG.md16
-rw-r--r--src/output/dnssim/common.c384
-rw-r--r--src/output/dnssim/connection.c471
-rw-r--r--src/output/dnssim/https2.c592
-rw-r--r--src/output/dnssim/internal.h343
-rw-r--r--src/output/dnssim/ll.h83
-rw-r--r--src/output/dnssim/tcp.c356
-rw-r--r--src/output/dnssim/tls.c475
-rw-r--r--src/output/dnssim/udp.c156
-rw-r--r--src/output/null.c87
-rw-r--r--src/output/null.h33
-rw-r--r--src/output/null.hh37
-rw-r--r--src/output/null.lua80
-rw-r--r--src/output/pcap.c111
-rw-r--r--src/output/pcap.h32
-rw-r--r--src/output/pcap.hh41
-rw-r--r--src/output/pcap.lua79
-rw-r--r--src/output/respdiff.c298
-rw-r--r--src/output/respdiff.h31
-rw-r--r--src/output/respdiff.hh36
-rw-r--r--src/output/respdiff.lua94
-rw-r--r--src/output/tcpcli.c381
-rw-r--r--src/output/tcpcli.h32
-rw-r--r--src/output/tcpcli.hh51
-rw-r--r--src/output/tcpcli.lua131
-rw-r--r--src/output/tlscli.c345
-rw-r--r--src/output/tlscli.h34
-rw-r--r--src/output/tlscli.hh52
-rw-r--r--src/output/tlscli.lua103
-rw-r--r--src/output/udpcli.c300
-rw-r--r--src/output/udpcli.h35
-rw-r--r--src/output/udpcli.hh53
-rw-r--r--src/output/udpcli.lua121
-rw-r--r--src/test/Makefile.am47
-rw-r--r--src/test/dns.pcapbin0 -> 20228 bytes
-rw-r--r--src/test/pellets.pcapbin0 -> 9177 bytes
-rwxr-xr-xsrc/test/test-base64url.sh20
-rwxr-xr-xsrc/test/test-ipsplit.sh20
-rwxr-xr-xsrc/test/test-trie.sh20
-rw-r--r--src/test/test1.gold1493
-rwxr-xr-xsrc/test/test1.sh21
-rw-r--r--src/test/test2.gold42
-rwxr-xr-xsrc/test/test2.sh21
-rw-r--r--src/test/test3.gold82
-rwxr-xr-xsrc/test/test3.sh22
-rw-r--r--src/test/test4.gold82
-rwxr-xr-xsrc/test/test4.sh21
-rwxr-xr-xsrc/test/test5.sh24
-rwxr-xr-xsrc/test/test6.sh23
-rw-r--r--src/test/test_base64url.lua24
-rwxr-xr-xsrc/test/test_ipsplit.lua294
-rwxr-xr-xsrc/test/test_trie.lua134
261 files changed, 31196 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..1bd4430
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,6 @@
+BasedOnStyle: webkit
+IndentWidth: 4
+AlignConsecutiveAssignments: true
+AlignConsecutiveDeclarations: true
+AlignOperands: true
+SortIncludes: false
diff --git a/.copr/Makefile b/.copr/Makefile
new file mode 100644
index 0000000..29ed0bc
--- /dev/null
+++ b/.copr/Makefile
@@ -0,0 +1,23 @@
+top=..
+
+all: srpm
+
+prereq: $(top)/rpmbuild
+ rpm -q git rpm-build >/dev/null || dnf -y install git rpm-build
+
+update-dist-tools: $(top)/dist-tools
+ ( cd "$(top)/dist-tools" && git pull )
+
+$(top)/dist-tools:
+ git clone https://github.com/jelu/dist-tools.git "$(top)/dist-tools"
+
+$(top)/rpmbuild:
+ mkdir -p "$(top)"/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
+
+srpm: prereq update-dist-tools
+ test -f .gitmodules && git submodule update --init || true
+ echo "$(spec)" | grep -q "develop.spec" && auto_build_number=`date --utc +%s` message="Auto build `date --utc --iso-8601=seconds`" "$(top)/dist-tools/spec-new-changelog-entry" || true
+ overwrite=yes nosign=yes "$(top)/dist-tools/create-source-packages" rpm
+ cp ../*.orig.tar.gz "$(top)/rpmbuild/SOURCES/"
+ echo "$(spec)" | grep -q "develop.spec" && rpmbuild -bs --define "%_topdir $(top)/rpmbuild" --undefine=dist rpm/*.spec || rpmbuild -bs --define "%_topdir $(top)/rpmbuild" --undefine=dist "$(spec)"
+ cp "$(top)"/rpmbuild/SRPMS/*.src.rpm "$(outdir)"
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..38cc1c4
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+custom: https://www.dns-oarc.net/donate
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..41aaabd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,100 @@
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf
+
+# Automake
+Makefile.in
+aclocal.m4
+ar-lib
+autom4te.cache
+compile
+config.guess
+config.sub
+configure
+depcomp
+install-sh
+ltmain.sh
+m4/libtool.m4
+m4/ltoptions.m4
+m4/ltsugar.m4
+m4/ltversion.m4
+m4/lt~obsolete.m4
+missing
+config.h.in
+config.h.in~
+test-driver
+
+# Configure
+Makefile
+config.log
+config.status
+libtool
+.deps
+src/config.h
+src/stamp-h1
+build
+.dirstamp
+
+# Project specific files
+*.luao
+*.luaho
+src/dnsjit
+src/dnsjit.1
+src/dnsjit.*.3in
+src/dnsjit.*.3
+src/core/compat.hh
+src/core/log_errstr.c
+src/test/test-suite.log
+src/test/test*.sh.log
+src/test/test*.sh.trs
+src/test/*.dist
+src/test/*-dist
diff --git a/.lgtm.yml b/.lgtm.yml
new file mode 100644
index 0000000..c79c033
--- /dev/null
+++ b/.lgtm.yml
@@ -0,0 +1,21 @@
+extraction:
+ cpp:
+ prepare:
+ packages:
+ - build-essential
+ - automake
+ - autoconf
+ - libtool
+ - pkg-config
+ - libluajit-5.1-dev
+ - libpcap-dev
+ - luajit
+ - liblmdb-dev
+ - libck-dev
+ - libgnutls28-dev
+ - libuv1-dev
+ - libnghttp2-dev
+ configure:
+ command:
+ - ./autogen.sh
+ - ./configure
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..d831324
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,29 @@
+dist: xenial
+addons:
+ apt:
+ update: true
+ packages:
+ - libluajit-5.1-dev
+ - libpcap-dev
+ - luajit
+ - liblmdb-dev
+ - libck-dev
+ - libgnutls28-dev
+ - libuv1-dev
+ - libnghttp2-dev
+language: c
+compiler:
+ - clang
+ - gcc
+install: ./autogen.sh
+script:
+ - ./configure --enable-warn-all
+ - make dist
+ - tar zxvf *.tar.gz
+ - cd dnsjit-[0-9]*
+ - mkdir build
+ - cd build
+ - ../configure --enable-warn-all
+ - make
+ - make test
+ - cat src/test/test*.sh.log
diff --git a/CHANGES b/CHANGES
new file mode 100644
index 0000000..448409a
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,106 @@
+2021-02-03 Jerry Lundström
+
+ Release 1.1.0
+
+ This releases adds a new module for handling Base64 URLs and new calls
+ for error handling and opening PCAPs using file descriptors, along with
+ a bug fix in `lib.getopt` and other changes.
+
+ The `dnssim` module has also gotten its own version and changelog, this
+ is to prepare it for being moved outside of dnsjit's repository in the
+ future.
+
+ New modules, calls, features:
+ - New `lib.base64url`: Utility library to convert data to base64url format
+ - `core.log`: New call `Log.errstr()`: Convert error number to its text representation
+ - `input.fpcap`: New call `Fpcap.openfp()`: Open a PCAP file for processing using a file descriptor, for example `io.stdin`
+ - `output.dnssim`: Support for DNS-over-HTTPS
+
+ Bug fixes:
+ - `lib.getopt`: Fix bug where `-` and `--` could not be used as arguments to options
+
+ Other changes:
+ - Fix typo in configure help text
+ - Add coverage
+ - `filter.ipsplit`: Extend PRNG modulus to 2^31, new implementation is the same as glibc's `rand()`
+ - `lib.ip`: Fix typo in documentation
+ - `output.dnssim`:
+ - This module now has it's own changelog
+ - Updated to v20210129
+ - Depend on libhttp2 for dnssim DNS-over-HTTPS capabilities
+ - `output.pcap`: Log libpcap error when failing to open
+ - SUSE packages now depend on moonjit because of lack of LuaJIT support
+
+ d001ccb m4
+ 4b63bce output/dnssim: add changelog
+ 7355810 output/dnssim: add version checks
+ 95fa6a9 input pcap/fpcap, getopt
+ 99c3d9f test/test_ipsplit: update to use new PRNG
+ 3235b09 filter/ipsplit: extend PRNG modulus to 2^31
+ 8ff81a0 fixup! input.fpcap: filename "-" reads from stdin
+ 63cf0a4 output/dnssim: fix regression in DoH GET
+ 367d0b8 input.pcap: document stdin feature of open_offline()
+ 8d94504 input.fpcap: filename "-" reads from stdin
+ 617058e getopt: accept singleton - also as option value
+ 7d7f17c output/dnssim: unify failed to bind error messages
+ bdf1517 output/dnssim: add IPv4 support
+ 15a21da Sonarcloud
+ ceeea1d SUSE
+ 1fc3c82 PR179
+ 2f5d38f output/dnssim: allow user-set instance log name
+ b036c68 Info
+ 0af1ffb Travis, configure
+ 49bdc08 output/dnssim: implement udp(tcp_fallback) method
+ b4f9cf9 man: update gitlab.labs.nic.cz to gitlab.nic.cz
+ 45b977d output/dnssim: update man page
+ 4184090 output/dnssim: https2 - fix connection closure issues
+ 342f33e output/dnssim: https2 - omit closing connection inside callback
+ 67a76d5 output/dnssim: handle all states when closing connection
+ 41f04d8 output/dnssim: document importance of conn state enum ordering
+ 795ab6f output/dnssim: tls - fix handling of CONGESTED connections
+ 8792b32 output/dnssim: match QUESTION section of received responses
+ 3a88f5b Coverage
+ 4f611c8 dnssim
+ 6e35d5b Compile
+ 63faa44 README, format code, man-page
+ 925f85e lib: add missing man reference
+ 9239087 output/dnssim: fix man formatting
+ bd7bee5 fix lua log levels
+ 4083efd output/dnssim: fix doc typo
+ 24c22b8 lib/base64url: add lua bindings
+ 69be2a1 core/log: add errstr() utility function
+ 0c14d74 output/dnssim: improve https2() documentation and behaviour
+ f74e19c output/pcap: log errors when opening output PCAP
+ 6fe699a output/dnssim: cleanup and nitpicks
+ 96db8a9 output/https2: handle max_concurrent_streams similar to nghttp2
+ 15ea609 output/dnssim: https2 - ensure uri authority is always set
+ fad3ed6 output/dnssim: https2 - fix some TODOs
+ 0bee6d8 output/dnssim: https2 - lua documentation
+ e83e010 output/dnssim: https2 - implement GET method
+ b553e0f output/dnssim: https2 - configure method
+ a431a0d contrib: add base64url functions
+ c753097 output/dnssim: https2 - set default concurrent stream limit
+ d49f275 output/dnssim: https2 - track number of open streams
+ 2f7217f output/dnssim: https2 - improve data send edge cases
+ c0abebc output/dnssim: https2 - return correct error code on send failure
+ 5b1f6c3 output/dnssim: conn - avoid assert when tearing down failed connections
+ 5c42266 output/dnssim: exit when file descriptors run out
+ 1ab2ab6 output/dnssim: https2 - additional asserts to detect invalid data
+ 4424eb3 output/dnssim: https2 - check response code
+ 303f2cd output/dnssim: https2 - improve QID mismatch debug msg
+ 86e3761 output/dnssim: https2 - bugfixes
+ 4a52f47 output/dnssim: https2 - use more consistent code style for pointers
+ c8d853e output/dnssim: conn - fix potential memory leak
+ 3e6038b output/dnssim: https2 - enable zero-ing out msgid
+ 712634c output/dnssim: https2 - properly match dnsmsg to query from http request
+ 5abe943 output/dnssim: https2 - free memory on teardown
+ 39a9e9e output/dnssim: https2 - initial implementation
+ 058aee2 output/dnssim: https2 - initialize and setup session
+ 85eb4a3 output/dnssim: https2 - add libnghttp2 dependency
+ 6712bd6 output/dnssim: https2 - add skeleton
+
+2020-07-23 Jerry Lundström
+
+ Release 1.0.0
+
+ First release of dnsjit.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f288702
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ 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, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<https://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..6f4de9b
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,31 @@
+# Copyright (c) 2018-2021, OARC, Inc.
+# All rights reserved.
+#
+# This file is part of dnsjit.
+#
+# dnsjit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+ACLOCAL_AMFLAGS = -I m4
+
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in \
+ $(srcdir)/src/config.h.in~ \
+ $(srcdir)/configure
+
+SUBDIRS = src examples
+
+dist_doc_DATA = CHANGES README.md LICENSE
+
+EXTRA_DIST = m4
+
+test: check
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..7c6238f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,153 @@
+# Engine for capturing, parsing and replaying DNS
+
+[![Build Status](https://travis-ci.com/DNS-OARC/dnsjit.svg?branch=develop)](https://travis-ci.com/DNS-OARC/dnsjit) [![Total alerts](https://img.shields.io/lgtm/alerts/g/DNS-OARC/dnsjit.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/DNS-OARC/dnsjit/alerts/) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=dns-oarc%3Adnsjit&metric=bugs)](https://sonarcloud.io/dashboard?id=dns-oarc%3Adnsjit) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=dns-oarc%3Adnsjit&metric=security_rating)](https://sonarcloud.io/dashboard?id=dns-oarc%3Adnsjit)
+
+**dnsjit** is a combination of parts taken from **dsc**, **dnscap**, **drool**,
+and put together around Lua to create a script-based engine for easy
+capturing, parsing and statistics gathering of DNS messages while also
+providing facilities for replaying DNS traffic.
+
+One of the core functionality that **dnsjit** brings is to tie together C
+and Lua modules through a receiver/producer interface.
+This allows creation of custom chains of functionality to meet various
+requirements.
+Another core functionality is the ability to parse and process DNS messages
+even if the messages are non-compliant with the DNS standards.
+
+The following Lua module categories exists:
+- `dnsjit.core`: Core modules for handling things like logging, DNS messages and receiver/receive functionality.
+- `dnsjit.lib`: Various Lua libraries or C library bindings.
+- `dnsjit.input`: Input modules used to read DNS messages in various ways.
+- `dnsjit.filter`: Filter modules to process or manipulate DNS messages.
+- `dnsjit.output`: Output modules used to display DNS message, export to various formats or replay them against other targets.
+
+See each category's man-page for more information.
+
+More information may be found here:
+- https://www.dns-oarc.net/tools/dnsjit
+
+Issues should be reported here:
+- https://github.com/DNS-OARC/dnsjit/issues
+
+General support and discussion:
+- Mattermost: https://chat.dns-oarc.net/community/channels/oarc-software
+
+## Packages
+
+https://dev.dns-oarc.net/packages
+
+Packages for Debian, Ubuntu, EPEL, SLE, openSUSE can be found in the
+PRE-RELEASE channel. Some distributions are limited to certain
+architectures because of LuaJIT.
+
+## Dependencies
+
+- [libluajit](http://luajit.org/) 2.0+ (or compatible alternatives)
+- [libpcap](http://www.tcpdump.org/)
+- [liblmdb](https://github.com/LMDB/lmdb)
+- [libck](https://github.com/concurrencykit/ck)
+- [libgnutls](https://www.gnutls.org/)
+- [libuv](http://libuv.org/)
+- [libnghttp2](https://www.nghttp2.org/)
+- [luajit](http://luajit.org/) (for building)
+- automake/autoconf/libtool/pkg-config (for building)
+
+Debian/Ubuntu: `apt-get install libluajit-5.1-dev libpcap-dev luajit liblmdb-dev libck-dev libgnutls28-dev libuv1-dev libnghttp2-dev`
+
+CentOS: `yum install luajit-devel libpcap-devel lmdb-devel ck-devel gnutls-devel libuv-devel libnghttp2-devel`
+
+FreeBSD: `pkg install luajit libpcap lmdb gnutls concurrencykit libuv libnghttp2`
+
+OpenBSD: `pkg_add luajit gnutls libuv nghttp2` + manual install of libpcap, liblmdb and libck
+
+On some version of SUSE Linux Enterprise moonjit is used as an compatible
+alternative to luajit.
+
+## Build
+
+```shell
+git clone https://github.com/DNS-OARC/dnsjit
+cd dnsjit
+sh autogen.sh
+./configure
+make
+```
+
+## Documentation
+
+Most documentation exists in man-pages and you do not have to install to
+access them, after building you can do:
+
+```shell
+man src/dnsjit.1
+man src/dnsjit.core.3
+man src/dnsjit.lib.3
+man src/dnsjit.input.3
+man src/dnsjit.filter.3
+man src/dnsjit.output.3
+```
+
+## Usage
+
+Run a Lua script:
+
+```shell
+dnsjit file.lua ...
+```
+
+Shebang-style:
+```lua
+#!/usr/bin/env dnsjit
+...
+```
+
+## Example
+
+Following example display the DNS ID found in queries.
+
+```lua
+require("dnsjit.core.objects")
+local input = require("dnsjit.input.pcap").new()
+local layer = require("dnsjit.filter.layer").new()
+local dns = require("dnsjit.core.object.dns").new()
+
+input:open_offline(arg[2])
+layer:producer(input)
+local producer, ctx = layer:produce()
+
+while true do
+ local object = producer(ctx)
+ if object == nil then break end
+ if object:type() == "payload" then
+ dns.obj_prev = object
+ if dns:parse_header() == 0 then
+ print(dns.id)
+ end
+ end
+end
+```
+
+See more examples in the [examples](https://github.com/DNS-OARC/dnsjit/tree/develop/examples) directory.
+
+## Copyright
+
+Copyright (c) 2018-2021, OARC, Inc.
+
+All rights reserved.
+
+```
+This file is part of dnsjit.
+
+dnsjit is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+```
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..18096c6
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,20 @@
+#!/bin/sh -e
+# Copyright (c) 2018-2021, OARC, Inc.
+# All rights reserved.
+#
+# This file is part of dnsjit.
+#
+# dnsjit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+autoreconf --force --install --no-recursive --include=m4
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..24c6142
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,131 @@
+# Copyright (c) 2018-2021, OARC, Inc.
+# All rights reserved.
+#
+# This file is part of dnsjit.
+#
+# dnsjit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+AC_PREREQ(2.64)
+AC_INIT([dnsjit], [1.1.0], [admin@dns-oarc.net], [dnsjit], [https://github.com/DNS-OARC/dnsjit/issues])
+AC_DEFINE([PACKAGE_MAJOR_VERSION], [1], [Define to the major version of this package.])
+AC_DEFINE([PACKAGE_MINOR_VERSION], [1], [Define to the minor version of this package.])
+AC_DEFINE([PACKAGE_PATCH_VERSION], [0], [Define to the patch version of this package.])
+AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects])
+AC_CONFIG_SRCDIR([src/dnsjit.c])
+AC_CONFIG_HEADER([src/config.h])
+AC_CONFIG_MACRO_DIR([m4])
+
+# Checks for programs.
+AC_PROG_CC
+AM_PROG_CC_C_O
+AC_CANONICAL_HOST
+m4_ifdef([AM_PROG_AR], [AM_PROG_AR])
+LT_INIT([disable-static])
+
+# Check --enable-warn-all
+AC_ARG_ENABLE([warn-all], [AS_HELP_STRING([--enable-warn-all], [Enable all compiler warnings])], [AX_CFLAGS_WARN_ALL()])
+
+# Check --with-extra-cflags
+AC_ARG_WITH([extra-cflags], [AS_HELP_STRING([--with-extra-cflags=CFLAGS], [Add extra CFLAGS])], [
+ AC_MSG_NOTICE([appending extra CFLAGS... $withval])
+ AS_VAR_APPEND(CFLAGS, [" $withval"])
+])
+
+# Check --with-extra-ldflags
+AC_ARG_WITH([extra-ldflags], [AS_HELP_STRING([--with-extra-ldflags=LDFLAGS], [Add extra LDFLAGS])], [
+ AC_MSG_NOTICE([appending extra LDFLAGS... $withval])
+ AS_VAR_APPEND(LDFLAGS, [" $withval"])
+])
+
+# Check --enable-gcov
+AC_ARG_ENABLE([gcov], [AS_HELP_STRING([--enable-gcov], [Enable coverage testing])], [
+ coverage_cflags="--coverage" # ld fails with: -g -O0 -fno-inline -fno-inline-small-functions -fno-default-inline
+ AC_MSG_NOTICE([enabling coverage testing... $coverage_cflags])
+ AS_VAR_APPEND(CFLAGS, [" $coverage_cflags"])
+])
+AM_CONDITIONAL([ENABLE_GCOV], [test "x$enable_gcov" != "xno"])
+AM_EXTRA_RECURSIVE_TARGETS([gcov])
+
+# Checks for support.
+AC_ARG_ENABLE([cpuext], [AS_HELP_STRING([--enable-cpuext], [check for and enable all available CPU extensions])], [
+case "${enableval}" in
+ yes) AX_EXT ;;
+ *) ;;
+esac])
+AC_HEADER_TIME
+AX_PTHREAD
+AC_CHECK_LIB([pcap], [pcap_open_live], [], [AC_MSG_ERROR([libpcap not found])])
+AC_CHECK_HEADER([pcap/pcap.h], [], [AC_MSG_ERROR([libpcap header not found])])
+AC_CHECK_HEADERS([endian.h sys/endian.h machine/endian.h sys/time.h byteswap.h])
+AC_CHECK_FUNCS([pcap_create pcap_set_tstamp_precision pcap_set_immediate_mode])
+AC_CHECK_FUNCS([pcap_set_tstamp_type pcap_setdirection sched_yield])
+AC_CHECK_FUNCS([pcap_open_offline_with_tstamp_precision pcap_activate])
+AC_CHECK_TYPES([pcap_direction_t], [], [], [[#include <pcap/pcap.h>]])
+AC_CHECK_HEADERS([net/ethernet.h])
+AC_CHECK_HEADERS([net/ethertypes.h])
+AC_SEARCH_LIBS([clock_gettime],[rt])
+AC_CHECK_FUNCS([clock_nanosleep nanosleep])
+PKG_CHECK_MODULES([luajit], [luajit >= 2],, [AC_MSG_ERROR([luajit v2+ not found])])
+AC_PATH_PROGS([LUAJIT], [luajit luajit51])
+if test "x$ac_cv_path_LUAJIT" = "x"; then
+ AC_MSG_ERROR([luajit not found])
+fi
+AC_CHECK_HEADERS([lmdb.h])
+AC_CHECK_LIB([lmdb], [mdb_env_create])
+PKG_CHECK_MODULES([libuv], [libuv])
+PKG_CHECK_MODULES([libnghttp2], [libnghttp2])
+PKG_CHECK_MODULES([ck], [ck >= 0], [
+ AS_VAR_APPEND([CFLAGS], [" $ck_CFLAGS"])
+ AS_VAR_APPEND([LIBS], [" $ck_LIBS"])
+], [
+ AC_CHECK_HEADERS([ck_ring.h ck_pr.h],, [AC_MSG_ERROR([libck headers not found])])
+ AC_CHECK_LIB([ck], [ck_array_init],, [AC_MSG_ERROR([libck not found])])
+])
+AC_CHECK_LIB([gnutls], [gnutls_init],, [AC_MSG_ERROR([libgnutls not found])])
+
+# Checks for sizes
+AC_CHECK_SIZEOF([void*])
+AC_CHECK_SIZEOF([pthread_t],,[#include <pthread.h>])
+AC_CHECK_SIZEOF([pthread_mutex_t],,[#include <pthread.h>])
+AC_CHECK_SIZEOF([pthread_cond_t],,[#include <pthread.h>])
+AC_CHECK_SIZEOF([struct sockaddr_storage],,[#include <sys/types.h>
+#include <sys/socket.h>])
+AC_CHECK_SIZEOF([ck_ring_t],,[#if defined(__GNUC__) || defined(__SUNPRO_C)
+#include "gcc/ck_cc.h"
+#ifdef CK_CC_RESTRICT
+#undef CK_CC_RESTRICT
+#define CK_CC_RESTRICT __restrict__
+#endif
+#endif
+#include <ck_ring.h>])
+AC_CHECK_SIZEOF([ck_ring_buffer_t],,[#if defined(__GNUC__) || defined(__SUNPRO_C)
+#include "gcc/ck_cc.h"
+#ifdef CK_CC_RESTRICT
+#undef CK_CC_RESTRICT
+#define CK_CC_RESTRICT __restrict__
+#endif
+#endif
+#include <ck_ring.h>])
+AC_CHECK_SIZEOF([gnutls_session_t],,[#include <gnutls/gnutls.h>])
+AC_CHECK_SIZEOF([gnutls_certificate_credentials_t],,[#include <gnutls/gnutls.h>])
+AC_CHECK_SIZEOF([struct pollfd],,[#include <poll.h>])
+
+# Output Makefiles
+AC_CONFIG_FILES([
+ Makefile
+ src/Makefile
+ src/test/Makefile
+ examples/Makefile
+])
+AC_OUTPUT
diff --git a/examples/Makefile.am b/examples/Makefile.am
new file mode 100644
index 0000000..96361b9
--- /dev/null
+++ b/examples/Makefile.am
@@ -0,0 +1,21 @@
+# Copyright (c) 2018-2021, OARC, Inc.
+# All rights reserved.
+#
+# This file is part of dnsjit.
+#
+# dnsjit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+dist_doc_DATA = capture.lua dumpdns2pcap.lua dumpdns.lua dumpdns-qr.lua \
+ filter_rcode.lua qr-multi-pcap-state.lua readme.lua replay.lua \
+ replay_multicli.lua respdiff.lua test_pcap_read.lua test_throughput.lua
diff --git a/examples/capture.lua b/examples/capture.lua
new file mode 100755
index 0000000..a4b9e75
--- /dev/null
+++ b/examples/capture.lua
@@ -0,0 +1,47 @@
+#!/usr/bin/env dnsjit
+local interface = arg[2]
+
+if interface == nil then
+ print("usage: "..arg[1].." <interface or any/all>")
+ return
+end
+
+local object = require("dnsjit.core.objects")
+local input = require("dnsjit.input.pcap").new()
+local layer = require("dnsjit.filter.layer").new()
+local dns = require("dnsjit.core.object.dns").new()
+
+input:create(interface)
+input:activate()
+layer:producer(input)
+local producer, ctx = layer:produce()
+
+while true do
+ local obj = producer(ctx)
+ if obj == nil then break end
+ local pl = obj:cast()
+ if obj:type() == "payload" and pl.len > 0 then
+ local transport = obj.obj_prev
+ while transport ~= nil do
+ if transport.obj_type == object.IP or transport.obj_type == object.IP6 then
+ break
+ end
+ transport = transport.obj_prev
+ end
+ local protocol = obj.obj_prev
+ while protocol ~= nil do
+ if protocol.obj_type == object.UDP or protocol.obj_type == object.TCP then
+ break
+ end
+ protocol = protocol.obj_prev
+ end
+
+ dns.obj_prev = obj
+ if transport ~= nil and protocol ~= nil then
+ transport = transport:cast()
+ protocol = protocol:cast()
+ print(protocol:type().." "..transport:source()..":"..tonumber(protocol.sport).." -> "..transport:destination()..":"..tonumber(protocol.dport))
+ dns:print()
+ end
+ end
+end
diff --git a/examples/count-pkts-per-ip.lua b/examples/count-pkts-per-ip.lua
new file mode 100755
index 0000000..9a6908e
--- /dev/null
+++ b/examples/count-pkts-per-ip.lua
@@ -0,0 +1,50 @@
+#!/usr/bin/env dnsjit
+-- count-pkts-per-ip.lua: count number of packets received from each IP/IPv6 address
+
+local input = require("dnsjit.input.pcap").new()
+local layer = require("dnsjit.filter.layer").new()
+local object = require("dnsjit.core.objects")
+local ip = require("dnsjit.lib.ip")
+local trie = require("dnsjit.lib.trie").new("uint64_t", true)
+local getopt = require("dnsjit.lib.getopt").new({})
+
+local pcap = unpack(getopt:parse())
+if pcap == nil then
+ print("usage: "..arg[1].." <pcap>")
+end
+
+-- Set up input
+input:open_offline(pcap)
+layer:producer(input)
+local produce, pctx = layer:produce()
+
+-- Read input and count packets
+while true do
+ local obj = produce(pctx)
+ if obj == nil then break end
+ local pkt = obj:cast_to(object.IP) or obj:cast_to(object.IP6)
+
+ if pkt ~= nil then
+ local iplen = 4
+ if pkt:type() == "ip6" then
+ iplen = 16
+ end
+
+ local node = trie:get_ins(pkt.src, iplen)
+ node:set(node:get() + 1)
+ end
+end
+
+-- Print statistics
+local iter = trie:iter()
+local node = iter:node()
+
+while node ~= nil do
+ local npkts = tonumber(node:get())
+ local key = node:key()
+ local ipstr = ip.tostring(key, true)
+
+ print(ipstr.." sent "..npkts.." packets")
+ iter:next()
+ node = iter:node()
+end
diff --git a/examples/dumpdns-qr.lua b/examples/dumpdns-qr.lua
new file mode 100755
index 0000000..d726e96
--- /dev/null
+++ b/examples/dumpdns-qr.lua
@@ -0,0 +1,85 @@
+#!/usr/bin/env dnsjit
+local pcap = arg[2]
+
+if pcap == nil then
+ print("usage: "..arg[1].." <pcap>")
+ return
+end
+
+local object = require("dnsjit.core.objects")
+local input = require("dnsjit.input.pcap").new()
+local layer = require("dnsjit.filter.layer").new()
+local dns = require("dnsjit.core.object.dns").new()
+local label = require("dnsjit.core.object.dns.label")
+
+local ffi = require("ffi")
+local labels = require("dnsjit.core.object.dns.label").new(16)
+local q = require("dnsjit.core.object.dns.q").new()
+
+input:open_offline(pcap)
+layer:producer(input)
+local producer, ctx = layer:produce()
+
+local queries = {}
+local responses = {}
+
+while true do
+ local obj = producer(ctx)
+ if obj == nil then break end
+ local pl = obj:cast()
+ if obj:type() == "payload" and pl.len > 0 then
+ local transport = obj.obj_prev
+ while transport ~= nil do
+ if transport.obj_type == object.IP or transport.obj_type == object.IP6 then
+ break
+ end
+ transport = transport.obj_prev
+ end
+ local protocol = obj.obj_prev
+ while protocol ~= nil do
+ if protocol.obj_type == object.UDP or protocol.obj_type == object.TCP then
+ break
+ end
+ protocol = protocol.obj_prev
+ end
+
+ dns.obj_prev = obj
+ if transport ~= nil and protocol ~= nil and dns:parse_header() == 0 then
+ transport = transport:cast()
+ protocol = protocol:cast()
+
+ if dns.qr == 1 then
+ table.insert(responses, {
+ src = transport:source(),
+ sport = protocol.sport,
+ dst = transport:destination(),
+ dport = protocol.dport,
+ id = dns.id,
+ rcode = dns.rcode_tostring(dns.rcode),
+ })
+ else
+ if dns.qdcount > 0 and dns:parse_q(q, labels, 16) == 0 then
+ table.insert(queries, {
+ src = transport:source(),
+ sport = protocol.sport,
+ dst = transport:destination(),
+ dport = protocol.dport,
+ id = dns.id,
+ qname = label.tooffstr(dns, labels, 16),
+ qtype = dns.type_tostring(q.type)
+ })
+ end
+ end
+ end
+ end
+end
+
+print("src", "dst", "id", "rcode", "qname", "qtype")
+local q, r
+for _, q in pairs(queries) do
+ for _, r in pairs(responses) do
+ if q.id == r.id and q.sport == r.dport and q.dport == r.sport and q.src == r.dst and q.dst == r.src then
+ print(q.src, q.dst, q.id, r.rcode, q.qname, q.qtype)
+ end
+ end
+end
diff --git a/examples/dumpdns.lua b/examples/dumpdns.lua
new file mode 100755
index 0000000..7c6fb8c
--- /dev/null
+++ b/examples/dumpdns.lua
@@ -0,0 +1,46 @@
+#!/usr/bin/env dnsjit
+local pcap = arg[2]
+
+if pcap == nil then
+ print("usage: "..arg[1].." <pcap>")
+ return
+end
+
+local object = require("dnsjit.core.objects")
+local input = require("dnsjit.input.pcap").new()
+local layer = require("dnsjit.filter.layer").new()
+local dns = require("dnsjit.core.object.dns").new()
+
+input:open_offline(pcap)
+layer:producer(input)
+local producer, ctx = layer:produce()
+
+while true do
+ local obj = producer(ctx)
+ if obj == nil then break end
+ local pl = obj:cast()
+ if obj:type() == "payload" and pl.len > 0 then
+ local transport = obj.obj_prev
+ while transport ~= nil do
+ if transport.obj_type == object.IP or transport.obj_type == object.IP6 then
+ break
+ end
+ transport = transport.obj_prev
+ end
+ local protocol = obj.obj_prev
+ while protocol ~= nil do
+ if protocol.obj_type == object.UDP or protocol.obj_type == object.TCP then
+ break
+ end
+ protocol = protocol.obj_prev
+ end
+
+ dns.obj_prev = obj
+ if transport ~= nil and protocol ~= nil then
+ transport = transport:cast()
+ protocol = protocol:cast()
+ print(protocol:type().." "..transport:source()..":"..tonumber(protocol.sport).." -> "..transport:destination()..":"..tonumber(protocol.dport))
+ dns:print()
+ end
+ end
+end
diff --git a/examples/dumpdns2pcap.lua b/examples/dumpdns2pcap.lua
new file mode 100755
index 0000000..08656cc
--- /dev/null
+++ b/examples/dumpdns2pcap.lua
@@ -0,0 +1,38 @@
+#!/usr/bin/env dnsjit
+local pcap_in = arg[2]
+local pcap_out = arg[3]
+
+if pcap_in == nil or pcap_out == nil then
+ print("usage: "..arg[1].." <pcap in> <pcap out>")
+ return
+end
+
+local object = require("dnsjit.core.objects")
+local input = require("dnsjit.input.pcap").new()
+local layer = require("dnsjit.filter.layer").new()
+local dns = require("dnsjit.core.object.dns").new()
+local output = require("dnsjit.output.pcap").new()
+
+input:open_offline(pcap_in)
+layer:producer(input)
+local producer, ctx = layer:produce()
+
+output:open(pcap_out, input:linktype(), input:snaplen())
+local receiver, rctx = output:receive()
+
+local n = 0
+while true do
+ local obj = producer(ctx)
+ if obj == nil then break end
+ local pl = obj:cast()
+ if obj:type() == "payload" and pl.len > 0 then
+ dns.obj_prev = obj
+ if dns:parse_header() == 0 then
+ receiver(rctx, obj)
+ n = n + 1
+ end
+ end
+end
+
+output:close()
+print(n, "DNS packets dumped")
diff --git a/examples/filter_rcode.lua b/examples/filter_rcode.lua
new file mode 100755
index 0000000..c3f0254
--- /dev/null
+++ b/examples/filter_rcode.lua
@@ -0,0 +1,38 @@
+#!/usr/bin/env dnsjit
+local pcap = arg[2]
+local rcode = tonumber(arg[3])
+
+if pcap == nil or rcode == nil then
+ print("usage: "..arg[1].." <pcap> <rcode>")
+ return
+end
+
+local object = require("dnsjit.core.objects")
+local input = require("dnsjit.input.pcap").new()
+local layer = require("dnsjit.filter.layer").new()
+local dns = require("dnsjit.core.object.dns").new()
+
+input:open_offline(pcap)
+layer:producer(input)
+local producer, ctx = layer:produce()
+
+while true do
+ local obj = producer(ctx)
+ if obj == nil then break end
+ local pl = obj:cast()
+ if obj:type() == "payload" and pl.len > 0 then
+ local transport = obj.obj_prev
+ while transport ~= nil do
+ if transport.obj_type == object.IP or transport.obj_type == object.IP6 then
+ break
+ end
+ transport = transport.obj_prev
+ end
+
+ dns.obj_prev = obj
+ if transport and dns and dns:parse_header() == 0 and dns.have_rcode == 1 and dns.rcode == rcode then
+ transport = transport:cast()
+ print(dns.id, transport:source().." -> "..transport:destination())
+ end
+ end
+end
diff --git a/examples/qr-multi-pcap-state.lua b/examples/qr-multi-pcap-state.lua
new file mode 100755
index 0000000..cf2cf0d
--- /dev/null
+++ b/examples/qr-multi-pcap-state.lua
@@ -0,0 +1,270 @@
+#!/usr/bin/env dnsjit
+local clock = require("dnsjit.lib.clock")
+local log = require("dnsjit.core.log")
+local getopt = require("dnsjit.lib.getopt").new({
+ { "v", "verbose", 0, "Enable and increase verbosity for each time given", "?+" },
+ { "r", "read-state", "", "File to read state from before processing", "?" },
+ { "w", "write-state", "", "File to write state to after processing", "?" },
+})
+local pcap = unpack(getopt:parse())
+if getopt:val("help") then
+ getopt:usage()
+ return
+end
+local v = getopt:val("v")
+if v > 0 then
+ log.enable("warning")
+end
+if v > 1 then
+ log.enable("notice")
+end
+if v > 2 then
+ log.enable("info")
+end
+if v > 3 then
+ log.enable("debug")
+end
+
+if pcap == nil then
+ print("usage: "..arg[1].." <pcap>")
+ return
+end
+
+local object = require("dnsjit.core.objects")
+local input = require("dnsjit.input.mmpcap").new()
+local layer = require("dnsjit.filter.layer").new()
+local dns = require("dnsjit.core.object.dns").new()
+local label = require("dnsjit.core.object.dns.label")
+local ffi = require("ffi")
+local labels = require("dnsjit.core.object.dns.label").new(16)
+local q = require("dnsjit.core.object.dns.q").new()
+local bit = require("bit")
+
+hash_u32 = ffi.new("uint32_t[2]")
+hash_u32p = ffi.cast("uint32_t*", hash_u32)
+function hashkey(dns, transport, protocol)
+ if transport.obj_type == object.IP then
+ ffi.copy(hash_u32p, transport.src, 4)
+ hash_u32[1] = hash_u32[0]
+ ffi.copy(hash_u32p, transport.dst, 4)
+ hash_u32[1] = bit.bxor(hash_u32[0], hash_u32[1])
+ else
+ local srcp = ffi.cast("uint8_t*", transport.src)
+ ffi.copy(hash_u32p, srcp, 4)
+ hash_u32[1] = hash_u32[0]
+ ffi.copy(hash_u32p, srcp+4, 4)
+ hash_u32[1] = bit.bxor(hash_u32[0], hash_u32[1])
+ srcp = ffi.cast("uint8_t*", transport.dst)
+ ffi.copy(hash_u32p, srcp, 4)
+ hash_u32[1] = bit.bxor(hash_u32[0], hash_u32[1])
+ ffi.copy(hash_u32p, srcp+4, 4)
+ hash_u32[1] = bit.bxor(hash_u32[0], hash_u32[1])
+ end
+ if dns.qr == 1 then
+ hash_u32[0] = protocol.dport + bit.lshift(protocol.sport, 16)
+ else
+ hash_u32[0] = protocol.sport + bit.lshift(protocol.dport, 16)
+ end
+ hash_u32[1] = bit.bxor(hash_u32[0], hash_u32[1])
+ hash_u32[0] = dns.id + bit.lshift(dns.id, 16)
+ return bit.bxor(hash_u32[0], hash_u32[1])
+end
+
+function dump_inflight(hkey, obj)
+ return string.format("return %d, { qsec = %d, qnsec = %d, rsec = %d, rnsec = %d, src = %q, sport = %d, dst = %q, dport = %d, id = %d, qname = %q, qtype = %q, rcode = %d }",
+ hkey,
+ tonumber(obj.qsec), tonumber(obj.qnsec),
+ tonumber(obj.rsec), tonumber(obj.rnsec),
+ obj.src, obj.sport,
+ obj.dst, obj.dport,
+ obj.id,
+ obj.qname, obj.qtype,
+ obj.rcode
+ )
+end
+
+function qrout(res)
+ local tsq = tonumber(res.qsec) + (tonumber(res.qnsec)/1000000000)
+ local tsr = tonumber(res.rsec) + (tonumber(res.rnsec)/1000000000)
+ print(tsq, tsr, math.floor(((tsr-tsq)*1000000)+0.5),
+ res.src, res.dst, res.id, res.rcode, res.qname, res.qtype)
+end
+
+input:open(pcap)
+layer:producer(input)
+local producer, ctx = layer:produce()
+
+local inflight = {}
+
+if getopt:val("read-state") > "" then
+ local f, _ = io.open(getopt:val("read-state"))
+ local inflights = 0
+ if f ~= nil then
+ for chunk in f:lines() do
+ local hkey, query = assert(loadstring(chunk))()
+ if hkey and query then
+ if not inflight[hkey] then
+ inflight[hkey] = {
+ queries = {},
+ size = 0,
+ }
+ end
+
+ table.insert(inflight[hkey].queries, query)
+ inflight[hkey].size = inflight[hkey].size + 1
+ inflights = inflights + 1
+ end
+ end
+ f:close()
+ print(string.format("== read %d inflight states from %q", inflights, getopt:val("read-state")))
+ end
+end
+
+local stat = {
+ packets = 0,
+ queries = 0,
+ responses = 0,
+ dropped = 0,
+}
+local start_sec, start_nsec = clock:monotonic()
+while true do
+ local obj = producer(ctx)
+ if obj == nil then break end
+ stat.packets = stat.packets + 1
+ local pl = obj:cast()
+ if obj:type() == "payload" and pl.len > 0 then
+ local protocol = obj.obj_prev
+ while protocol ~= nil do
+ if protocol.obj_type == object.UDP or protocol.obj_type == object.TCP then
+ break
+ end
+ protocol = protocol.obj_prev
+ end
+ local transport = protocol.obj_prev
+ while transport ~= nil do
+ if transport.obj_type == object.IP or transport.obj_type == object.IP6 then
+ break
+ end
+ transport = transport.obj_prev
+ end
+ local pcap = transport.obj_prev
+ while pcap ~= nil do
+ if pcap.obj_type == object.PCAP then
+ break
+ end
+ pcap = pcap.obj_prev
+ end
+
+ dns.obj_prev = obj
+ if pcap ~= nil and transport ~= nil and protocol ~= nil and dns:parse_header() == 0 then
+ transport = transport:cast()
+ protocol = protocol:cast()
+ pcap = pcap:cast()
+
+ local hkey = hashkey(dns, transport, protocol)
+
+ if dns.qr == 1 then
+ stat.responses = stat.responses + 1
+ if inflight[hkey] then
+ for k, n in pairs(inflight[hkey].queries) do
+ if n.id == dns.id
+ and n.sport == protocol.dport
+ and n.dport == protocol.sport
+ and n.src == transport:destination()
+ and n.dst == transport:source()
+ then
+ n.rsec = pcap.ts.sec
+ n.rnsec = pcap.ts.nsec
+ n.rcode = dns.rcode
+ qrout(n)
+ inflight[hkey].queries[k] = nil
+ inflight[hkey].size = inflight[hkey].size - 1
+ if inflight[hkey].size < 1 then
+ inflight[hkey] = nil
+ end
+ break
+ end
+ end
+ else
+ print("== dropped",
+ tonumber(pcap.ts.sec) + (tonumber(pcap.ts.nsec) / 1000000000),
+ transport:source(),
+ transport:destination(),
+ dns.id,
+ label.tooffstr(dns, labels, 16),
+ dns.type_tostring(q.type)
+ )
+ stat.dropped = stat.dropped + 1
+ end
+ else
+ stat.queries = stat.queries + 1
+ if dns.qdcount > 0 and dns:parse_q(q, labels, 16) == 0 then
+ if not inflight[hkey] then
+ inflight[hkey] = {
+ queries = {},
+ size = 0,
+ }
+ end
+
+ table.insert(inflight[hkey].queries, {
+ qsec = pcap.ts.sec,
+ qnsec = pcap.ts.nsec,
+ rsec = -1,
+ rnsec = -1,
+ src = transport:source(),
+ sport = protocol.sport,
+ dst = transport:destination(),
+ dport = protocol.dport,
+ id = dns.id,
+ qname = label.tooffstr(dns, labels, 16),
+ qtype = dns.type_tostring(q.type),
+ rcode = -1,
+ })
+ inflight[hkey].size = inflight[hkey].size + 1
+ end
+ end
+ end
+ end
+end
+local end_sec, end_nsec = clock:monotonic()
+
+local runtime = 0
+if end_sec > start_sec then
+ runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000)
+elseif end_sec == start_sec and end_nsec > start_nsec then
+ runtime = (end_nsec - start_nsec) / 1000000000
+end
+
+print("== runtime", runtime)
+print("== packets", stat.packets, stat.packets/runtime)
+print("== queries", stat.queries, stat.queries/runtime)
+print("== responses", stat.responses, stat.responses/runtime)
+print("== dropped", stat.dropped, stat.dropped/runtime)
+
+if getopt:val("write-state") > "" then
+ local f, _ = io.open(getopt:val("write-state"), "w+")
+ local inflights = 0
+ if f ~= nil then
+ for hkey, unanswered in pairs(inflight) do
+ for _, query in pairs(unanswered.queries) do
+ f:write(dump_inflight(hkey, query), "\n")
+ inflights = inflights + 1
+ end
+ end
+ f:close()
+ print(string.format("== wrote %d inflight states to %q", inflights, getopt:val("write-state")))
+ end
+else
+ inflights = 0
+ for hkey, unanswered in pairs(inflight) do
+ inflights = inflights + unanswered.size
+ end
+ if inflights > 0 then
+ print("== inflight queries (tsq, src, dst, id, qname, qtype)")
+ for hkey, unanswered in pairs(inflight) do
+ for _, query in pairs(unanswered.queries) do
+ print(tonumber(query.qsec) + (tonumber(query.qnsec)/1000000000), query.src, query.dst, query.id, query.qname, query.qtype)
+ end
+ end
+ end
+end
diff --git a/examples/readme.lua b/examples/readme.lua
new file mode 100755
index 0000000..dc6de89
--- /dev/null
+++ b/examples/readme.lua
@@ -0,0 +1,20 @@
+#!/usr/bin/env dnsjit
+require("dnsjit.core.objects")
+local input = require("dnsjit.input.pcap").new()
+local layer = require("dnsjit.filter.layer").new()
+local dns = require("dnsjit.core.object.dns").new()
+
+input:open_offline(arg[2])
+layer:producer(input)
+local producer, ctx = layer:produce()
+
+while true do
+ local object = producer(ctx)
+ if object == nil then break end
+ if object:type() == "payload" then
+ dns.obj_prev = object
+ if dns:parse_header() == 0 then
+ print(dns.id)
+ end
+ end
+end
diff --git a/examples/replay.lua b/examples/replay.lua
new file mode 100755
index 0000000..5281133
--- /dev/null
+++ b/examples/replay.lua
@@ -0,0 +1,119 @@
+#!/usr/bin/env dnsjit
+local clock = require("dnsjit.lib.clock")
+local log = require("dnsjit.core.log")
+local getopt = require("dnsjit.lib.getopt").new({
+ { "v", "verbose", 0, "Enable and increase verbosity for each time given", "?+" },
+ { "R", "responses", false, "Wait for responses to the queries and print both", "?" },
+ { "t", "tcp", false, "Use TCP instead of UDP", "?"},
+ { "T", "tls", false, "Use TLS instead of UDP/TCP", "?"},
+})
+local pcap, host, port = unpack(getopt:parse())
+if getopt:val("help") then
+ getopt:usage()
+ return
+end
+local v = getopt:val("v")
+if v > 0 then
+ log.enable("warning")
+end
+if v > 1 then
+ log.enable("notice")
+end
+if v > 2 then
+ log.enable("info")
+end
+if v > 3 then
+ log.enable("debug")
+end
+
+if pcap == nil or host == nil or port == nil then
+ print("usage: "..arg[1].." <pcap> <host> <port>")
+ return
+end
+
+local ffi = require("ffi")
+
+require("dnsjit.core.objects")
+local input = require("dnsjit.input.mmpcap").new()
+local layer = require("dnsjit.filter.layer").new()
+
+input:open(pcap)
+layer:producer(input)
+
+local query = require("dnsjit.core.object.dns").new()
+local response = require("dnsjit.core.object.dns").new()
+
+local dnscli = require("dnsjit.output.dnscli")
+local output
+if getopt:val("t") then
+ output = dnscli.new(dnscli.TCP)
+ response.includes_dnslen = 1
+elseif getopt:val("T") then
+ output = dnscli.new(dnscli.TLS)
+ response.includes_dnslen = 1
+else
+ output = dnscli.new(dnscli.UDP)
+end
+output:connect(host, port)
+
+local printdns = false
+if getopt:val("responses") then
+ printdns = true
+end
+
+local prod, pctx = layer:produce()
+local recv, rctx = output:receive()
+local oprod, opctx = output:produce()
+local start_sec, start_nsec = clock:monotonic()
+
+while true do
+ local obj = prod(pctx)
+ if obj == nil then break end
+ local pl = obj:cast()
+ if obj:type() == "payload" and pl.len > 0 then
+ query.obj_prev = obj
+
+ local trs = pl.obj_prev:cast()
+ if trs:type() == "tcp" then
+ query.includes_dnslen = 1
+ else
+ query.includes_dnslen = 0
+ end
+
+ if query:parse_header() == 0 and query.qr == 0 then
+ recv(rctx, query:uncast())
+
+ if printdns then
+ print("query:")
+ query:print()
+
+ local pobj = oprod(opctx)
+ if pobj == nil then
+ log.fatal("producer error")
+ end
+ local rpl = pobj:cast()
+ if rpl.len == 0 then
+ print("timed out")
+ else
+ response.obj_prev = pobj
+ print("response:")
+ response:print()
+ end
+ end
+ end
+ end
+end
+
+local end_sec, end_nsec = clock:monotonic()
+
+local runtime = 0
+if end_sec > start_sec then
+ runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000)
+elseif end_sec == start_sec and end_nsec > start_nsec then
+ runtime = (end_nsec - start_nsec) / 1000000000
+end
+
+print("runtime", runtime)
+print("packets", input:packets(), input:packets()/runtime, "/pps")
+print("queries", output:packets(), output:packets()/runtime, "/qps")
+print("errors", output:errors())
diff --git a/examples/replay_multicli.lua b/examples/replay_multicli.lua
new file mode 100755
index 0000000..c883aa5
--- /dev/null
+++ b/examples/replay_multicli.lua
@@ -0,0 +1,192 @@
+#!/usr/bin/env dnsjit
+local clock = require("dnsjit.lib.clock")
+local log = require("dnsjit.core.log")
+local getopt = require("dnsjit.lib.getopt").new({
+ { "c", "clients", 10, "Number of clients run", "?" },
+ { "v", "verbose", 0, "Enable and increase verbosity for each time given", "?+" },
+ { "R", "responses", false, "Wait for responses to the queries and print both", "?" },
+ { "t", "tcp", false, "Use TCP instead of UDP", "?"},
+ { "T", "tls", false, "Use TLS instead of UDP/TCP", "?"},
+})
+local pcap, host, port = unpack(getopt:parse())
+if getopt:val("help") then
+ getopt:usage()
+ return
+end
+local v = getopt:val("v")
+if v > 0 then
+ log.enable("warning")
+end
+if v > 1 then
+ log.enable("notice")
+end
+if v > 2 then
+ log.enable("info")
+end
+if v > 3 then
+ log.enable("debug")
+end
+
+if pcap == nil or host == nil or port == nil then
+ print("usage: "..arg[1].." <pcap> <host> <port>")
+ return
+end
+
+local ffi = require("ffi")
+
+require("dnsjit.core.objects")
+local input = require("dnsjit.input.mmpcap").new()
+local layer = require("dnsjit.filter.layer").new()
+
+input:open(pcap)
+layer:producer(input)
+
+local query = require("dnsjit.core.object.dns").new()
+local response = require("dnsjit.core.object.dns").new()
+
+local dnscli = require("dnsjit.output.dnscli")
+
+local clients = {}
+local last_client = nil
+local num_clients = getopt:val("c")
+for n = 1, num_clients do
+ local output
+ if getopt:val("t") then
+ output = dnscli.new(dnscli.TCP + dnscli.NONBLOCKING)
+ elseif getopt:val("T") then
+ output = dnscli.new(dnscli.TLS + dnscli.NONBLOCKING)
+ else
+ output = dnscli.new(dnscli.UDP + dnscli.NONBLOCKING)
+ end
+ output:connect(host, port)
+
+ local recv, rctx = output:receive()
+ local prod, pctx = output:produce()
+ local client = {
+ output = output,
+ last = last_client,
+ busy = false,
+ recv = recv, rctx = rctx,
+ prod = prod, pctx = pctx,
+ id = nil,
+ done = false,
+ }
+ table.insert(clients, client)
+ last_client = client
+end
+local output = clients[1]
+output.last = last_client
+
+if getopt:val("t") then
+ response.includes_dnslen = 1
+elseif getopt:val("T") then
+ response.includes_dnslen = 1
+end
+
+local printdns = false
+if getopt:val("responses") then
+ printdns = true
+end
+
+local prod, pctx = layer:produce()
+local start_sec, start_nsec = clock:monotonic()
+
+local done = false
+while true do
+ output = output.last
+ if printdns and output.busy then
+ local pobj = output.prod(output.pctx)
+ if pobj == nil then
+ log.fatal("producer error")
+ end
+ local rpl = pobj:cast()
+ if rpl.len == 0 then
+ -- print(output, "busy")
+ else
+ response.obj_prev = pobj
+ if response:parse_header() == 0 and response.qr == 1 and response.id == output.id then
+ print("response:")
+ response:print()
+ output.busy = false
+ end
+ end
+ end
+ if not output.busy then
+ while true do
+ local obj = prod(pctx)
+ if obj == nil then
+ done = true
+ break
+ end
+ local pl = obj:cast()
+ if obj:type() == "payload" and pl.len > 0 then
+ query.obj_prev = obj
+
+ local trs = pl.obj_prev:cast()
+ if trs:type() == "tcp" then
+ query.includes_dnslen = 1
+ else
+ query.includes_dnslen = 0
+ end
+
+ if query:parse_header() == 0 and query.qr == 0 then
+ output.recv(output.rctx, query:uncast())
+ if printdns then
+ print("query:")
+ query:print()
+ output.busy = true
+ output.id = query.id
+ end
+ break
+ end
+ end
+ end
+ end
+ if done then break end
+end
+
+local queries, timeouts, errors = 0, 0, 0
+done = 0
+while true do
+ output = output.last
+ if printdns and output.busy then
+ local pobj = output.prod(output.pctx)
+ if pobj == nil then
+ log.fatal("producer error")
+ end
+ local rpl = pobj:cast()
+ if rpl.len == 0 then
+ -- print(output, "busy")
+ else
+ response.obj_prev = pobj
+ if response:parse_header() == 0 and response.qr == 1 and response.id == output.id then
+ print("response:")
+ response:print()
+ output.busy = false
+ end
+ end
+ end
+ if not output.busy and not output.done then
+ output.done = true
+ done = done + 1
+ queries = queries + output.output:packets()
+ timeouts = timeouts + output.output:timeouts()
+ errors = errors + output.output:errors()
+ end
+ if done >= num_clients then break end
+end
+
+local end_sec, end_nsec = clock:monotonic()
+
+local runtime = 0
+if end_sec > start_sec then
+ runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000)
+elseif end_sec == start_sec and end_nsec > start_nsec then
+ runtime = (end_nsec - start_nsec) / 1000000000
+end
+
+print("runtime", runtime)
+print("packets", input:packets(), input:packets()/runtime, "/pps")
+print("queries", queries, queries/runtime, "/qps")
+print("timeouts", timeouts)
+print("errors", errors)
diff --git a/examples/respdiff.lua b/examples/respdiff.lua
new file mode 100755
index 0000000..831f349
--- /dev/null
+++ b/examples/respdiff.lua
@@ -0,0 +1,160 @@
+#!/usr/bin/env dnsjit
+local ffi = require("ffi")
+local clock = require("dnsjit.lib.clock")
+local log = require("dnsjit.core.log")
+log.display_file_line(true)
+local getopt = require("dnsjit.lib.getopt").new({
+ { "v", "verbose", 0, "Enable and increase verbosity for each time given", "?+" },
+})
+local pcap, host, port, path, origname, recvname = unpack(getopt:parse())
+if getopt:val("help") then
+ getopt:usage()
+ return
+end
+local v = getopt:val("v")
+if v > 0 then
+ log.enable("warning")
+end
+if v > 1 then
+ log.enable("notice")
+end
+if v > 2 then
+ log.enable("info")
+end
+if v > 3 then
+ log.enable("debug")
+end
+
+if pcap == nil or host == nil or port == nil or path == nil or origname == nil or recvname == nil then
+ print("usage: "..arg[1].." <pcap> <host> <port> <LMDB path> <origname> <recvname>")
+ return
+end
+
+local object = require("dnsjit.core.objects")
+local dns = require("dnsjit.core.object.dns").new()
+local input = require("dnsjit.input.mmpcap").new()
+input:open(pcap)
+local layer = require("dnsjit.filter.layer").new()
+layer:producer(input)
+
+local udpcli, tcpcli
+local udprecv, udpctx, tcprecv, tcpctx
+local udpprod, tcpprod
+
+local prod, pctx = layer:produce()
+local queries = {}
+local clipayload = ffi.new("core_object_payload_t")
+clipayload.obj_type = object.PAYLOAD
+local cliobject = ffi.cast("core_object_t*", clipayload)
+
+local respdiff = require("dnsjit.output.respdiff").new(path, origname, recvname)
+local resprecv, respctx = respdiff:receive()
+local query_payload, original_payload, response_payload = ffi.new("core_object_payload_t"), ffi.new("core_object_payload_t"), ffi.new("core_object_payload_t")
+query_payload.obj_type = object.PAYLOAD
+original_payload.obj_type = object.PAYLOAD
+response_payload.obj_type = object.PAYLOAD
+local query_payload_obj = ffi.cast("core_object_t*", query_payload)
+query_payload.obj_prev = ffi.cast("core_object_t*", original_payload)
+original_payload.obj_prev = ffi.cast("core_object_t*", response_payload)
+
+local start_sec, start_nsec = clock:realtime()
+while true do
+ local obj = prod(pctx)
+ if obj == nil then break end
+ local payload = obj:cast()
+ if obj:type() == "payload" and payload.len > 0 then
+ dns.obj_prev = obj
+ if dns:parse_header() == 0 then
+ local transport = obj.obj_prev
+ while transport ~= nil do
+ if transport.obj_type == object.IP or transport.obj_type == object.IP6 then
+ break
+ end
+ transport = transport.obj_prev
+ end
+ local protocol = obj.obj_prev
+ while protocol ~= nil do
+ if protocol.obj_type == object.UDP or protocol.obj_type == object.TCP then
+ break
+ end
+ protocol = protocol.obj_prev
+ end
+
+ if transport ~= nil and protocol ~= nil then
+ transport = transport:cast()
+ protocol = protocol:cast()
+
+ if dns.qr == 0 then
+ local k = string.format("%s %d %s %d", transport:source(), protocol.sport, transport:destination(), protocol.dport)
+ local q = {
+ id = dns.id,
+ proto = protocol:type(),
+ payload = ffi.new("uint8_t[?]", payload.len),
+ len = tonumber(payload.len)
+ }
+ ffi.copy(q.payload, payload.payload, payload.len)
+ queries[k] = q
+ else
+ local k = string.format("%s %d %s %d", transport:destination(), protocol.dport, transport:source(), protocol.sport)
+ local q = queries[k]
+ if q then
+ queries[k] = nil
+ clipayload.payload = q.payload
+ clipayload.len = q.len
+
+ local prod, pctx
+
+ if q.proto == "udp" then
+ if not udpcli then
+ udpcli = require("dnsjit.output.udpcli").new()
+ udpcli:connect(host, port)
+ udprecv, udpctx = udpcli:receive()
+ udpprod, _ = udpcli:produce()
+ end
+ udprecv(udpctx, cliobject)
+ prod = udpprod
+ pctx = udpctx
+ elseif q.proto == "tcp" then
+ if not tcpcli then
+ tcpcli = require("dnsjit.output.tcpcli").new()
+ tcpcli:connect(host, port)
+ tcprecv, tcpctx = tcpcli:receive()
+ tcpprod, _ = tcpcli:produce()
+ end
+ tcprecv(tcpctx, cliobject)
+ prod = tcpprod
+ pctx = tcpctx
+ end
+
+ while true do
+ local response = prod(pctx)
+ if response == nil then
+ log.fatal("producer error")
+ end
+ local rpl = response:cast()
+ if rpl.len == 0 then
+ log.info("timed out")
+ else
+ dns.obj_prev = response
+ if dns:parse_header() == 0 and dns.id == q.id then
+ query_payload.payload = q.payload
+ query_payload.len = q.len
+ original_payload.payload = payload.payload
+ original_payload.len = payload.len
+ response_payload.payload = rpl.payload
+ response_payload.len = rpl.len
+
+ resprecv(respctx, query_payload_obj)
+ break
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
+local end_sec, end_nsec = clock:realtime()
+
+respdiff:commit(start_sec, end_sec)
diff --git a/examples/test_pcap_read.lua b/examples/test_pcap_read.lua
new file mode 100755
index 0000000..7d29c7a
--- /dev/null
+++ b/examples/test_pcap_read.lua
@@ -0,0 +1,149 @@
+#!/usr/bin/env dnsjit
+local clock = require("dnsjit.lib.clock")
+local log = require("dnsjit.core.log")
+local getopt = require("dnsjit.lib.getopt").new({
+ { "v", "verbose", 0, "Enable and increase verbosity for each time given", "?+" },
+ { "l", "layer", false, "Test also with dnsjit.filter.layer", "?" },
+ { "p", "producer", false, "Test with the producer interface rather then receiver interface", "?" },
+})
+local pcap, runs = unpack(getopt:parse())
+if getopt:val("help") then
+ getopt:usage()
+ return
+end
+local v = getopt:val("v")
+if v > 0 then
+ log.enable("warning")
+end
+if v > 1 then
+ log.enable("notice")
+end
+if v > 2 then
+ log.enable("info")
+end
+if v > 3 then
+ log.enable("debug")
+end
+
+if pcap == nil then
+ print("usage: "..arg[1].." <pcap> [runs]")
+ return
+end
+
+inputs = { "fpcap", "mmpcap", "pcap" }
+result = {}
+results = {}
+highest = nil
+
+if runs == nil then
+ runs = 10
+else
+ runs = tonumber(runs)
+end
+
+if getopt:val("p") then
+ for _, name in pairs(inputs) do
+ rt = 0.0
+ p = 0
+
+ print("run", name)
+ for n = 1, runs do
+ o = require("dnsjit.output.null").new()
+ i = require("dnsjit.input."..name).new()
+
+ if name == "pcap" then
+ i:open_offline(pcap)
+ else
+ i:open(pcap)
+ end
+
+ if getopt:val("l") then
+ f = require("dnsjit.filter.layer").new()
+ f:producer(i)
+ o:producer(f)
+ else
+ o:producer(i)
+ end
+
+ ss, sns = clock:monotonic()
+ o:run()
+ es, ens = clock:monotonic()
+
+ if es > ss then
+ rt = rt + ((es - ss) - 1) + ((1000000000 - sns + ens)/1000000000)
+ elseif es == ss and ens > sns then
+ rt = rt + (ens - sns) / 1000000000
+ end
+
+ p = p + o:packets()
+ end
+
+ result[name] = {
+ rt = rt,
+ p = p
+ }
+ if highest == nil or rt > result[highest].rt then
+ highest = name
+ end
+ table.insert(results, name)
+ end
+else
+ for _, name in pairs(inputs) do
+ rt = 0.0
+ p = 0
+
+ print("run", name)
+ for n = 1, runs do
+ o = require("dnsjit.output.null").new()
+ i = require("dnsjit.input."..name).new()
+
+ if name == "pcap" then
+ i:open_offline(pcap)
+ else
+ i:open(pcap)
+ end
+
+ if getopt:val("l") then
+ f = require("dnsjit.filter.layer").new()
+ f:receiver(o)
+ i:receiver(f)
+ else
+ i:receiver(o)
+ end
+
+ ss, sns = clock:monotonic()
+ if name == "pcap" then
+ i:dispatch()
+ else
+ i:run()
+ end
+ es, ens = clock:monotonic()
+
+ if es > ss then
+ rt = rt + ((es - ss) - 1) + ((1000000000 - sns + ens)/1000000000)
+ elseif es == ss and ens > sns then
+ rt = rt + (ens - sns) / 1000000000
+ end
+
+ p = p + o:packets()
+ end
+
+ result[name] = {
+ rt = rt,
+ p = p
+ }
+ if highest == nil or rt > result[highest].rt then
+ highest = name
+ end
+ table.insert(results, name)
+ end
+end
+
+print("name", "runtime", "pps", "x", "pkts")
+print(highest, result[highest].rt, result[highest].p/result[highest].rt, 1.0, result[highest].p)
+for _, name in pairs(results) do
+ if name ~= highest then
+ local f = result[name].p / result[highest].p
+ print(name, result[name].rt, result[name].p/result[name].rt, (result[highest].rt/result[name].rt)*f, result[name].p)
+ end
+end
diff --git a/examples/test_throughput.lua b/examples/test_throughput.lua
new file mode 100755
index 0000000..b936c41
--- /dev/null
+++ b/examples/test_throughput.lua
@@ -0,0 +1,539 @@
+#!/usr/bin/env dnsjit
+local ffi = require("ffi")
+local object = require("dnsjit.core.objects")
+local clock = require("dnsjit.lib.clock")
+local log = require("dnsjit.core.log")
+local getopt = require("dnsjit.lib.getopt").new({
+ { "v", "verbose", 0, "Enable and increase verbosity for each time given", "?+" },
+ { "s", "split", false, "Test also with dnsjit.filter.split", "?" },
+ { "t", "thread", false, "Test also with dnsjit.core.thread using dnsjit.core.channel", "?" },
+})
+local num, runs = unpack(getopt:parse())
+if getopt:val("help") then
+ getopt:usage()
+ return
+end
+local v = getopt:val("v")
+if v > 0 then
+ log.enable("warning")
+end
+if v > 1 then
+ log.enable("notice")
+end
+if v > 2 then
+ log.enable("info")
+end
+if v > 3 then
+ log.enable("debug")
+end
+
+if num == nil then
+ print("usage: "..arg[1].." <num> [runs]")
+ return
+else
+ num = tonumber(num)
+end
+
+if runs == nil then
+ runs = 1
+else
+ runs = tonumber(runs)
+end
+
+print("zero:receiver() -> null:receive()")
+local run
+for run = 1, runs do
+ local i = require("dnsjit.input.zero").new()
+ local o = require("dnsjit.output.null").new()
+
+ i:receiver(o)
+ local start_sec, start_nsec = clock:monotonic()
+ i:run(num)
+ local end_sec, end_nsec = clock:monotonic()
+
+ local runtime = 0
+ if end_sec > start_sec then
+ runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000)
+ elseif end_sec == start_sec and end_nsec > start_nsec then
+ runtime = (end_nsec - start_nsec) / 1000000000
+ end
+
+ print(run, "runtime", runtime, num/runtime, "/sec", o:packets())
+end
+
+print("lua -> null:receive()")
+local run
+for run = 1, runs do
+ local o = require("dnsjit.output.null").new()
+ local recv, rctx = o:receive()
+ local pkt = ffi.new("core_object_null_t")
+ pkt.obj_type = object.NULL
+ local obj = ffi.cast("core_object_t*", pkt)
+
+ local start_sec, start_nsec = clock:monotonic()
+ for n = 1, num do
+ recv(rctx, obj)
+ end
+ local end_sec, end_nsec = clock:monotonic()
+
+ local runtime = 0
+ if end_sec > start_sec then
+ runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000)
+ elseif end_sec == start_sec and end_nsec > start_nsec then
+ runtime = (end_nsec - start_nsec) / 1000000000
+ end
+
+ print(run, "runtime", runtime, num/runtime, "/sec", o:packets())
+end
+
+-- TODO: use core.thread
+
+print("zero:produce() <- null:producer()")
+local run
+for run = 1, runs do
+ local i = require("dnsjit.input.zero").new()
+ local o = require("dnsjit.output.null").new()
+
+ local start_sec, start_nsec = clock:monotonic()
+ o:producer(i)
+ o:run(num)
+ local end_sec, end_nsec = clock:monotonic()
+
+ local runtime = 0
+ if end_sec > start_sec then
+ runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000)
+ elseif end_sec == start_sec and end_nsec > start_nsec then
+ runtime = (end_nsec - start_nsec) / 1000000000
+ end
+
+ print(run, "runtime", runtime, num/runtime, "/sec", o:packets())
+end
+
+print("zero:produce() <- lua")
+local run
+for run = 1, runs do
+ local i = require("dnsjit.input.zero").new()
+ local prod, pctx = i:produce()
+
+ local start_sec, start_nsec = clock:monotonic()
+ for n = 1, num do
+ prod(pctx)
+ end
+ local end_sec, end_nsec = clock:monotonic()
+
+ local runtime = 0
+ if end_sec > start_sec then
+ runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000)
+ elseif end_sec == start_sec and end_nsec > start_nsec then
+ runtime = (end_nsec - start_nsec) / 1000000000
+ end
+
+ print(run, "runtime", runtime, num/runtime, "/sec", num)
+end
+
+print("zero:produce() <- lua -> null:receive()")
+local run
+for run = 1, runs do
+ local i = require("dnsjit.input.zero").new()
+ local o = require("dnsjit.output.null").new()
+ local prod, pctx = i:produce()
+ local recv, rctx = o:receive()
+
+ local start_sec, start_nsec = clock:monotonic()
+ for n = 1, num do
+ recv(rctx, prod(pctx))
+ end
+ local end_sec, end_nsec = clock:monotonic()
+
+ local runtime = 0
+ if end_sec > start_sec then
+ runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000)
+ elseif end_sec == start_sec and end_nsec > start_nsec then
+ runtime = (end_nsec - start_nsec) / 1000000000
+ end
+
+ print(run, "runtime", runtime, num/runtime, "/sec", num)
+end
+
+if getopt:val("s") then
+ print("zero:receiver() -> split:receiver() -> null:receive() x1")
+ local run
+ for run = 1, runs do
+ local i = require("dnsjit.input.zero").new()
+ local s = require("dnsjit.filter.split").new()
+ local o1 = require("dnsjit.output.null").new()
+
+ s:receiver(o1)
+ i:receiver(s)
+ local start_sec, start_nsec = clock:monotonic()
+ i:run(num)
+ local end_sec, end_nsec = clock:monotonic()
+
+ local runtime = 0
+ if end_sec > start_sec then
+ runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000)
+ elseif end_sec == start_sec and end_nsec > start_nsec then
+ runtime = (end_nsec - start_nsec) / 1000000000
+ end
+
+ print(run, "runtime", runtime, num/runtime, "/sec", o1:packets(), o1:packets())
+ end
+
+ print("zero:receiver() -> split:receiver() -> null:receive() x2")
+ local run
+ for run = 1, runs do
+ local i = require("dnsjit.input.zero").new()
+ local s = require("dnsjit.filter.split").new()
+ local o1 = require("dnsjit.output.null").new()
+ local o2 = require("dnsjit.output.null").new()
+
+ s:receiver(o1)
+ s:receiver(o2)
+ i:receiver(s)
+ local start_sec, start_nsec = clock:monotonic()
+ i:run(num)
+ local end_sec, end_nsec = clock:monotonic()
+
+ local runtime = 0
+ if end_sec > start_sec then
+ runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000)
+ elseif end_sec == start_sec and end_nsec > start_nsec then
+ runtime = (end_nsec - start_nsec) / 1000000000
+ end
+
+ print(run, "runtime", runtime, num/runtime, "/sec", o1:packets() + o2:packets(), o1:packets(), o2:packets())
+ end
+
+ print("zero:receiver() -> split:receiver() -> null:receive() x4")
+ local run
+ for run = 1, runs do
+ local i = require("dnsjit.input.zero").new()
+ local s = require("dnsjit.filter.split").new()
+ local o1 = require("dnsjit.output.null").new()
+ local o2 = require("dnsjit.output.null").new()
+ local o3 = require("dnsjit.output.null").new()
+ local o4 = require("dnsjit.output.null").new()
+
+ s:receiver(o1)
+ s:receiver(o2)
+ s:receiver(o3)
+ s:receiver(o4)
+ i:receiver(s)
+ local start_sec, start_nsec = clock:monotonic()
+ i:run(num)
+ local end_sec, end_nsec = clock:monotonic()
+
+ local runtime = 0
+ if end_sec > start_sec then
+ runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000)
+ elseif end_sec == start_sec and end_nsec > start_nsec then
+ runtime = (end_nsec - start_nsec) / 1000000000
+ end
+
+ print(run, "runtime", runtime, num/runtime, "/sec", o1:packets() + o2:packets() + o3:packets() + o4:packets(), o1:packets(), o2:packets(), o3:packets(), o4:packets())
+ end
+
+ print("zero:receiver() -> lua split table -> null:receive() x4")
+ local run
+ for run = 1, runs do
+ local i = require("dnsjit.input.zero").new()
+ local o1 = require("dnsjit.output.null").new()
+ local o2 = require("dnsjit.output.null").new()
+ local o3 = require("dnsjit.output.null").new()
+ local o4 = require("dnsjit.output.null").new()
+
+ local prod, pctx = i:produce()
+ local recv, rctx = {}, {}
+
+ local f, c = o1:receive()
+ table.insert(recv, f)
+ table.insert(rctx, c)
+ f, c = o2:receive()
+ table.insert(recv, f)
+ table.insert(rctx, c)
+ f, c = o3:receive()
+ table.insert(recv, f)
+ table.insert(rctx, c)
+ f, c = o4:receive()
+ table.insert(recv, f)
+ table.insert(rctx, c)
+
+ local start_sec, start_nsec = clock:monotonic()
+ local idx = 1
+ for n = 1, num do
+ local f, c = recv[idx], rctx[idx]
+ if not f then
+ idx = 1
+ f, c = recv[1], rctx[1]
+ end
+ f(c, prod(pctx))
+ idx = idx + 1
+ end
+ local end_sec, end_nsec = clock:monotonic()
+
+ local runtime = 0
+ if end_sec > start_sec then
+ runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000)
+ elseif end_sec == start_sec and end_nsec > start_nsec then
+ runtime = (end_nsec - start_nsec) / 1000000000
+ end
+
+ print(run, "runtime", runtime, num/runtime, "/sec", o1:packets() + o2:packets() + o3:packets() + o4:packets(), o1:packets(), o2:packets(), o3:packets(), o4:packets())
+ end
+
+ print("zero:receiver() -> lua split gen code -> null:receive() x4")
+ local run
+ for run = 1, runs do
+ local i = require("dnsjit.input.zero").new()
+ local o1 = require("dnsjit.output.null").new()
+ local o2 = require("dnsjit.output.null").new()
+ local o3 = require("dnsjit.output.null").new()
+ local o4 = require("dnsjit.output.null").new()
+
+ local prod, pctx = i:produce()
+ local f1, c1 = o1:receive()
+ local f2, c2 = o2:receive()
+ local f3, c3 = o3:receive()
+ local f4, c4 = o4:receive()
+
+ local code = "return function (num, prod, pctx, f1, c1, f2, c2, f3, c3, f4, c4)\nlocal n = 0\nwhile n < num do\n"
+ code = code .. "f1(c1,prod(pctx))\n"
+ code = code .. "n = n + 1\n"
+ code = code .. "f2(c2,prod(pctx))\n"
+ code = code .. "n = n + 1\n"
+ code = code .. "f3(c3,prod(pctx))\n"
+ code = code .. "n = n + 1\n"
+ code = code .. "f4(c4,prod(pctx))\n"
+ code = code .. "n = n + 1\n"
+ code = code .. "end\n"
+ code = code .. "end"
+ local f = assert(loadstring(code))()
+
+ local start_sec, start_nsec = clock:monotonic()
+ f(num, prod, pctx, f1, c1, f2, c2, f3, c3, f4, c4)
+ local end_sec, end_nsec = clock:monotonic()
+
+ local runtime = 0
+ if end_sec > start_sec then
+ runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000)
+ elseif end_sec == start_sec and end_nsec > start_nsec then
+ runtime = (end_nsec - start_nsec) / 1000000000
+ end
+
+ print(run, "runtime", runtime, num/runtime, "/sec", o1:packets() + o2:packets() + o3:packets() + o4:packets(), o1:packets(), o2:packets(), o3:packets(), o4:packets())
+ end
+end
+
+if getopt:val("t") then
+ print("zero:receiver() -> thread lua x1")
+ local run
+ for run = 1, runs do
+ local i = require("dnsjit.input.zero").new()
+ local c = require("dnsjit.core.channel").new()
+ local t = require("dnsjit.core.thread").new()
+
+ t:start(function(t)
+ local c = t:pop()
+
+ while true do
+ local o = c:get()
+ if o == nil then break end
+ end
+ end)
+ t:push(c)
+
+ local prod, pctx = i:produce()
+ local start_sec, start_nsec = clock:monotonic()
+ for n = 1, num do
+ c:put(prod(pctx))
+ end
+ c:close()
+ t:stop()
+ local end_sec, end_nsec = clock:monotonic()
+
+ local runtime = 0
+ if end_sec > start_sec then
+ runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000)
+ elseif end_sec == start_sec and end_nsec > start_nsec then
+ runtime = (end_nsec - start_nsec) / 1000000000
+ end
+
+ print(run, "runtime", runtime, num/runtime, "/sec")
+ end
+
+ print("zero:receiver() -> thread lua x2")
+ local run
+ for run = 1, runs do
+ local i = require("dnsjit.input.zero").new()
+ local c = require("dnsjit.core.channel").new()
+ local c2 = require("dnsjit.core.channel").new()
+ local t = require("dnsjit.core.thread").new()
+ local t2 = require("dnsjit.core.thread").new()
+
+ local f = function(t)
+ local c = t:pop()
+
+ while true do
+ local o = c:get()
+ if o == nil then break end
+ end
+ end
+
+ t:start(f)
+ t2:start(f)
+ t:push(c)
+ t2:push(c2)
+
+ local prod, pctx = i:produce()
+ local start_sec, start_nsec = clock:monotonic()
+ for n = 1, num/2 do
+ c:put(prod(pctx))
+ c2:put(prod(pctx))
+ end
+ c:close()
+ c2:close()
+ t:stop()
+ t2:stop()
+ local end_sec, end_nsec = clock:monotonic()
+
+ local runtime = 0
+ if end_sec > start_sec then
+ runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000)
+ elseif end_sec == start_sec and end_nsec > start_nsec then
+ runtime = (end_nsec - start_nsec) / 1000000000
+ end
+
+ print(run, "runtime", runtime, num/runtime, "/sec")
+ end
+
+ print("zero:receiver() -> thread lua x4")
+ local run
+ for run = 1, runs do
+ local i = require("dnsjit.input.zero").new()
+ local c = require("dnsjit.core.channel").new()
+ local c2 = require("dnsjit.core.channel").new()
+ local c3 = require("dnsjit.core.channel").new()
+ local c4 = require("dnsjit.core.channel").new()
+ local t = require("dnsjit.core.thread").new()
+ local t2 = require("dnsjit.core.thread").new()
+ local t3 = require("dnsjit.core.thread").new()
+ local t4 = require("dnsjit.core.thread").new()
+
+ local f = function(t)
+ local c = t:pop()
+
+ while true do
+ local o = c:get()
+ if o == nil then break end
+ end
+ end
+
+ t:start(f)
+ t2:start(f)
+ t3:start(f)
+ t4:start(f)
+ t:push(c)
+ t2:push(c2)
+ t3:push(c3)
+ t4:push(c4)
+
+ local prod, pctx = i:produce()
+ local start_sec, start_nsec = clock:monotonic()
+ for n = 1, num/4 do
+ c:put(prod(pctx))
+ c2:put(prod(pctx))
+ c3:put(prod(pctx))
+ c4:put(prod(pctx))
+ end
+ c:close()
+ c2:close()
+ c3:close()
+ c4:close()
+ t:stop()
+ t2:stop()
+ t3:stop()
+ t4:stop()
+ local end_sec, end_nsec = clock:monotonic()
+
+ local runtime = 0
+ if end_sec > start_sec then
+ runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000)
+ elseif end_sec == start_sec and end_nsec > start_nsec then
+ runtime = (end_nsec - start_nsec) / 1000000000
+ end
+
+ print(run, "runtime", runtime, num/runtime, "/sec")
+ end
+
+ print("zero:receiver() -> thread lua x1 -> null:receiver()")
+ local run
+ for run = 1, runs do
+ local i = require("dnsjit.input.zero").new()
+ local c = require("dnsjit.core.channel").new()
+ local t = require("dnsjit.core.thread").new()
+
+ t:start(function(t)
+ local c = t:pop()
+ local o = require("dnsjit.output.null").new()
+
+ local recv, rctx = o:receive()
+ while true do
+ local obj = c:get()
+ if obj == nil then break end
+ recv(rctx, obj)
+ end
+ end)
+ t:push(c)
+
+ local prod, pctx = i:produce()
+ local start_sec, start_nsec = clock:monotonic()
+ for n = 1, num do
+ c:put(prod(pctx))
+ end
+ c:close()
+ t:stop()
+ local end_sec, end_nsec = clock:monotonic()
+
+ local runtime = 0
+ if end_sec > start_sec then
+ runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000)
+ elseif end_sec == start_sec and end_nsec > start_nsec then
+ runtime = (end_nsec - start_nsec) / 1000000000
+ end
+
+ print(run, "runtime", runtime, num/runtime, "/sec")
+ end
+
+ print("zero:receiver() -> thread x1 -> null:receiver()")
+ local run
+ for run = 1, runs do
+ local i = require("dnsjit.input.zero").new()
+ local c = require("dnsjit.core.channel").new()
+ local t = require("dnsjit.core.thread").new()
+
+ t:start(function(t)
+ local c = t:pop()
+ local o = require("dnsjit.output.null").new()
+
+ c:receiver(o)
+ c:run()
+ end)
+ t:push(c)
+
+ i:receiver(c)
+ local start_sec, start_nsec = clock:monotonic()
+ i:run(num)
+ c:close()
+ t:stop()
+ local end_sec, end_nsec = clock:monotonic()
+
+ local runtime = 0
+ if end_sec > start_sec then
+ runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000)
+ elseif end_sec == start_sec and end_nsec > start_nsec then
+ runtime = (end_nsec - start_nsec) / 1000000000
+ end
+
+ print(run, "runtime", runtime, num/runtime, "/sec")
+ end
+end
diff --git a/fmt.sh b/fmt.sh
new file mode 100755
index 0000000..8585229
--- /dev/null
+++ b/fmt.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+# Copyright (c) 2018-2021, OARC, Inc.
+# All rights reserved.
+#
+# This file is part of dnsjit.
+#
+# dnsjit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+clang-format \
+ -style=file \
+ -i \
+ src/*.c \
+ `find src/core src/input src/filter src/output src/lib -name '*.c'` \
+ `find src/core src/input src/filter src/output src/lib -name '*.h'` \
+ `find src/core src/input src/filter src/output src/lib -name '*.hh'`
diff --git a/m4/ax_append_flag.m4 b/m4/ax_append_flag.m4
new file mode 100644
index 0000000..dd6d8b6
--- /dev/null
+++ b/m4/ax_append_flag.m4
@@ -0,0 +1,50 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_append_flag.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_APPEND_FLAG(FLAG, [FLAGS-VARIABLE])
+#
+# DESCRIPTION
+#
+# FLAG is appended to the FLAGS-VARIABLE shell variable, with a space
+# added in between.
+#
+# If FLAGS-VARIABLE is not specified, the current language's flags (e.g.
+# CFLAGS) is used. FLAGS-VARIABLE is not changed if it already contains
+# FLAG. If FLAGS-VARIABLE is unset in the shell, it is set to exactly
+# FLAG.
+#
+# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
+# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
+#
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved. This file is offered as-is, without any
+# warranty.
+
+#serial 8
+
+AC_DEFUN([AX_APPEND_FLAG],
+[dnl
+AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_SET_IF
+AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[FLAGS])])
+AS_VAR_SET_IF(FLAGS,[
+ AS_CASE([" AS_VAR_GET(FLAGS) "],
+ [*" $1 "*], [AC_RUN_LOG([: FLAGS already contains $1])],
+ [
+ AS_VAR_APPEND(FLAGS,[" $1"])
+ AC_RUN_LOG([: FLAGS="$FLAGS"])
+ ])
+ ],
+ [
+ AS_VAR_SET(FLAGS,[$1])
+ AC_RUN_LOG([: FLAGS="$FLAGS"])
+ ])
+AS_VAR_POPDEF([FLAGS])dnl
+])dnl AX_APPEND_FLAG
diff --git a/m4/ax_cflags_warn_all.m4 b/m4/ax_cflags_warn_all.m4
new file mode 100644
index 0000000..9235a18
--- /dev/null
+++ b/m4/ax_cflags_warn_all.m4
@@ -0,0 +1,158 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_cflags_warn_all.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_CFLAGS_WARN_ALL [(shellvar[, default[, action-if-found[, action-if-not-found]]])]
+# AX_CXXFLAGS_WARN_ALL [(shellvar[, default[, action-if-found[, action-if-not-found]]])]
+# AX_FCFLAGS_WARN_ALL [(shellvar[, default[, action-if-found[, action-if-not-found]]])]
+#
+# DESCRIPTION
+#
+# Specify compiler options that enable most reasonable warnings. For the
+# GNU Compiler Collection (GCC), for example, it will be "-Wall". The
+# result is added to shellvar, one of CFLAGS, CXXFLAGS or FCFLAGS if the
+# first parameter is not specified.
+#
+# Each of these macros accepts the following optional arguments:
+#
+# - $1 - shellvar
+# shell variable to use (CFLAGS, CXXFLAGS or FCFLAGS if not
+# specified, depending on macro)
+#
+# - $2 - default
+# value to use for flags if compiler vendor cannot be determined (by
+# default, "")
+#
+# - $3 - action-if-found
+# action to take if the compiler vendor has been successfully
+# determined (by default, add the appropriate compiler flags to
+# shellvar)
+#
+# - $4 - action-if-not-found
+# action to take if the compiler vendor has not been determined or
+# is unknown (by default, add the default flags, or "" if not
+# specified, to shellvar)
+#
+# These macros use AX_COMPILER_VENDOR to determine which flags should be
+# returned for a given compiler. Not all compilers currently have flags
+# defined for them; patches are welcome. If need be, compiler flags may
+# be made language-dependent: use a construct like the following:
+#
+# [vendor_name], [m4_if(_AC_LANG_PREFIX,[C], VAR="--relevant-c-flags",dnl
+# m4_if(_AC_LANG_PREFIX,[CXX], VAR="--relevant-c++-flags",dnl
+# m4_if(_AC_LANG_PREFIX,[FC], VAR="--relevant-fortran-flags",dnl
+# VAR="$2"; FOUND="no")))],
+#
+# Note: These macros also depend on AX_PREPEND_FLAG.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
+# Copyright (c) 2010 Rhys Ulerich <rhys.ulerich@gmail.com>
+# Copyright (c) 2018 John Zaitseff <J.Zaitseff@zap.org.au>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 3 of the License, or (at your
+# option) any later version.
+#
+# 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, see <https://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 25
+
+AC_DEFUN([AX_FLAGS_WARN_ALL], [
+ AX_REQUIRE_DEFINED([AX_PREPEND_FLAG])dnl
+ AC_REQUIRE([AX_COMPILER_VENDOR])dnl
+
+ AS_VAR_PUSHDEF([FLAGS], [m4_default($1,_AC_LANG_PREFIX[]FLAGS)])dnl
+ AS_VAR_PUSHDEF([VAR], [ac_cv_[]_AC_LANG_ABBREV[]flags_warn_all])dnl
+ AS_VAR_PUSHDEF([FOUND], [ac_save_[]_AC_LANG_ABBREV[]flags_warn_all_found])dnl
+
+ AC_CACHE_CHECK([FLAGS for most reasonable warnings], VAR, [
+ VAR=""
+ FOUND="yes"
+ dnl Cases are listed in the order found in ax_compiler_vendor.m4
+ AS_CASE("$ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor",
+ [intel], [VAR="-w2"],
+ [ibm], [VAR="-qsrcmsg -qinfo=all:noppt:noppc:noobs:nocnd"],
+ [pathscale], [],
+ [clang], [VAR="-Wall"],
+ [cray], [VAR="-h msglevel 2"],
+ [fujitsu], [],
+ [sdcc], [],
+ [sx], [VAR="-pvctl[,]fullmsg"],
+ [portland], [],
+ [gnu], [VAR="-Wall"],
+ [sun], [VAR="-v"],
+ [hp], [VAR="+w1"],
+ [dec], [VAR="-verbose -w0 -warnprotos"],
+ [borland], [],
+ [comeau], [],
+ [kai], [],
+ [lcc], [],
+ [sgi], [VAR="-fullwarn"],
+ [microsoft], [],
+ [metrowerks], [],
+ [watcom], [],
+ [tcc], [],
+ [unknown], [
+ VAR="$2"
+ FOUND="no"
+ ],
+ [
+ AC_MSG_WARN([Unknown compiler vendor returned by [AX_COMPILER_VENDOR]])
+ VAR="$2"
+ FOUND="no"
+ ]
+ )
+
+ AS_IF([test "x$FOUND" = "xyes"], [dnl
+ m4_default($3, [AS_IF([test "x$VAR" != "x"], [AX_PREPEND_FLAG([$VAR], [FLAGS])])])
+ ], [dnl
+ m4_default($4, [m4_ifval($2, [AX_PREPEND_FLAG([$VAR], [FLAGS])], [true])])
+ ])dnl
+ ])dnl
+
+ AS_VAR_POPDEF([FOUND])dnl
+ AS_VAR_POPDEF([VAR])dnl
+ AS_VAR_POPDEF([FLAGS])dnl
+])dnl AX_FLAGS_WARN_ALL
+
+AC_DEFUN([AX_CFLAGS_WARN_ALL], [dnl
+ AC_LANG_PUSH([C])
+ AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4])
+ AC_LANG_POP([C])
+])dnl
+
+AC_DEFUN([AX_CXXFLAGS_WARN_ALL], [dnl
+ AC_LANG_PUSH([C++])
+ AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4])
+ AC_LANG_POP([C++])
+])dnl
+
+AC_DEFUN([AX_FCFLAGS_WARN_ALL], [dnl
+ AC_LANG_PUSH([Fortran])
+ AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4])
+ AC_LANG_POP([Fortran])
+])dnl
diff --git a/m4/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4
new file mode 100644
index 0000000..bd753b3
--- /dev/null
+++ b/m4/ax_check_compile_flag.m4
@@ -0,0 +1,53 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT])
+#
+# DESCRIPTION
+#
+# Check whether the given FLAG works with the current language's compiler
+# or gives an error. (Warnings, however, are ignored)
+#
+# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on
+# success/failure.
+#
+# If EXTRA-FLAGS is defined, it is added to the current language's default
+# flags (e.g. CFLAGS) when the check is done. The check is thus made with
+# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to
+# force the compiler to issue an error when a bad flag is given.
+#
+# INPUT gives an alternative input source to AC_COMPILE_IFELSE.
+#
+# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
+# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
+# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
+#
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved. This file is offered as-is, without any
+# warranty.
+
+#serial 6
+
+AC_DEFUN([AX_CHECK_COMPILE_FLAG],
+[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF
+AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
+AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
+ ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
+ _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
+ AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
+ [AS_VAR_SET(CACHEVAR,[yes])],
+ [AS_VAR_SET(CACHEVAR,[no])])
+ _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
+AS_VAR_IF(CACHEVAR,yes,
+ [m4_default([$2], :)],
+ [m4_default([$3], :)])
+AS_VAR_POPDEF([CACHEVAR])dnl
+])dnl AX_CHECK_COMPILE_FLAGS
diff --git a/m4/ax_compiler_vendor.m4 b/m4/ax_compiler_vendor.m4
new file mode 100644
index 0000000..f06e865
--- /dev/null
+++ b/m4/ax_compiler_vendor.m4
@@ -0,0 +1,117 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_compiler_vendor.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_COMPILER_VENDOR
+#
+# DESCRIPTION
+#
+# Determine the vendor of the C, C++ or Fortran compiler. The vendor is
+# returned in the cache variable $ax_cv_c_compiler_vendor for C,
+# $ax_cv_cxx_compiler_vendor for C++ or $ax_cv_fc_compiler_vendor for
+# (modern) Fortran. The value is one of "intel", "ibm", "pathscale",
+# "clang" (LLVM), "cray", "fujitsu", "sdcc", "sx", "portland" (PGI), "gnu"
+# (GCC), "sun" (Oracle Developer Studio), "hp", "dec", "borland",
+# "comeau", "kai", "lcc", "sgi", "microsoft", "metrowerks", "watcom",
+# "tcc" (Tiny CC) or "unknown" (if the compiler cannot be determined).
+#
+# To check for a Fortran compiler, you must first call AC_FC_PP_SRCEXT
+# with an appropriate preprocessor-enabled extension. For example:
+#
+# AC_LANG_PUSH([Fortran])
+# AC_PROG_FC
+# AC_FC_PP_SRCEXT([F])
+# AX_COMPILER_VENDOR
+# AC_LANG_POP([Fortran])
+#
+# LICENSE
+#
+# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu>
+# Copyright (c) 2008 Matteo Frigo
+# Copyright (c) 2018-19 John Zaitseff <J.Zaitseff@zap.org.au>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# 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, see <https://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 30
+
+AC_DEFUN([AX_COMPILER_VENDOR], [dnl
+ AC_CACHE_CHECK([for _AC_LANG compiler vendor], ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor, [dnl
+ dnl If you modify this list of vendors, please add similar support
+ dnl to ax_compiler_version.m4 if at all possible.
+ dnl
+ dnl Note: Do NOT check for GCC first since some other compilers
+ dnl define __GNUC__ to remain compatible with it. Compilers that
+ dnl are very slow to start (such as Intel) are listed first.
+
+ vendors="
+ intel: __ICC,__ECC,__INTEL_COMPILER
+ ibm: __xlc__,__xlC__,__IBMC__,__IBMCPP__,__ibmxl__
+ pathscale: __PATHCC__,__PATHSCALE__
+ clang: __clang__
+ cray: _CRAYC
+ fujitsu: __FUJITSU
+ sdcc: SDCC,__SDCC
+ sx: _SX
+ portland: __PGI
+ gnu: __GNUC__
+ sun: __SUNPRO_C,__SUNPRO_CC,__SUNPRO_F90,__SUNPRO_F95
+ hp: __HP_cc,__HP_aCC
+ dec: __DECC,__DECCXX,__DECC_VER,__DECCXX_VER
+ borland: __BORLANDC__,__CODEGEARC__,__TURBOC__
+ comeau: __COMO__
+ kai: __KCC
+ lcc: __LCC__
+ sgi: __sgi,sgi
+ microsoft: _MSC_VER
+ metrowerks: __MWERKS__
+ watcom: __WATCOMC__
+ tcc: __TINYC__
+ unknown: UNKNOWN
+ "
+ for ventest in $vendors; do
+ case $ventest in
+ *:)
+ vendor=$ventest
+ continue
+ ;;
+ *)
+ vencpp="defined("`echo $ventest | sed 's/,/) || defined(/g'`")"
+ ;;
+ esac
+
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [[
+#if !($vencpp)
+ thisisanerror;
+#endif
+ ]])], [break])
+ done
+
+ ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor=`echo $vendor | cut -d: -f1`
+ ])
+])dnl
diff --git a/m4/ax_ext.m4 b/m4/ax_ext.m4
new file mode 100644
index 0000000..b95d75a
--- /dev/null
+++ b/m4/ax_ext.m4
@@ -0,0 +1,328 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_ext.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_EXT
+#
+# DESCRIPTION
+#
+# Find supported SIMD extensions by requesting cpuid. When a SIMD
+# extension is found, the -m"simdextensionname" is added to SIMD_FLAGS if
+# compiler supports it. For example, if "sse2" is available then "-msse2"
+# is added to SIMD_FLAGS.
+#
+# Find other supported CPU extensions by requesting cpuid. When a
+# processor extension is found, the -m"extensionname" is added to
+# CPUEXT_FLAGS if compiler supports it. For example, if "bmi2" is
+# available then "-mbmi2" is added to CPUEXT_FLAGS.
+#
+# This macro calls:
+#
+# AC_SUBST(SIMD_FLAGS)
+# AC_SUBST(CPUEXT_FLAGS)
+#
+# And defines:
+#
+# HAVE_RDRND / HAVE_BMI1 / HAVE_BMI2 / HAVE_ADX / HAVE_MPX
+# HAVE_PREFETCHWT1 / HAVE_ABM / HAVE_MMX / HAVE_SSE / HAVE_SSE2
+# HAVE_SSE3 / HAVE_SSSE3 / HAVE_SSE4_1 / HAVE_SSE4_2 / HAVE_SSE4a
+# HAVE_SHA / HAVE_AES / HAVE_AVX / HAVE_FMA3 / HAVE_FMA4 / HAVE_XOP
+# HAVE_AVX2 / HAVE_AVX512_F / HAVE_AVX512_CD / HAVE_AVX512_PF
+# HAVE_AVX512_ER / HAVE_AVX512_VL / HAVE_AVX512_BW / HAVE_AVX512_DQ
+# HAVE_AVX512_IFMA / HAVE_AVX512_VBMI / HAVE_ALTIVEC / HAVE_VSX
+#
+# LICENSE
+#
+# Copyright (c) 2007 Christophe Tournayre <turn3r@users.sourceforge.net>
+# Copyright (c) 2013,2015 Michael Petch <mpetch@capp-sysware.com>
+# Copyright (c) 2017 Rafael de Lucena Valle <rafaeldelucena@gmail.com>
+#
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved. This file is offered as-is, without any
+# warranty.
+
+#serial 18
+
+AC_DEFUN([AX_EXT],
+[
+ AC_REQUIRE([AC_CANONICAL_HOST])
+ AC_REQUIRE([AC_PROG_CC])
+
+ CPUEXT_FLAGS=""
+ SIMD_FLAGS=""
+
+ case $host_cpu in
+ powerpc*)
+ AC_CACHE_CHECK([whether altivec is supported for old distros], [ax_cv_have_altivec_old_ext],
+ [
+ if test `/usr/sbin/sysctl -a 2>/dev/null| grep -c hw.optional.altivec` != 0; then
+ if test `/usr/sbin/sysctl -n hw.optional.altivec` = 1; then
+ ax_cv_have_altivec_old_ext=yes
+ fi
+ fi
+ ])
+
+ if test "$ax_cv_have_altivec_old_ext" = yes; then
+ AC_DEFINE(HAVE_ALTIVEC,,[Support Altivec instructions])
+ AX_CHECK_COMPILE_FLAG(-faltivec, SIMD_FLAGS="$SIMD_FLAGS -faltivec", [])
+ fi
+
+ AC_CACHE_CHECK([whether altivec is supported], [ax_cv_have_altivec_ext],
+ [
+ if test `LD_SHOW_AUXV=1 /bin/true 2>/dev/null|grep -c altivec` != 0; then
+ ax_cv_have_altivec_ext=yes
+ fi
+ ])
+
+ if test "$ax_cv_have_altivec_ext" = yes; then
+ AC_DEFINE(HAVE_ALTIVEC,,[Support Altivec instructions])
+ AX_CHECK_COMPILE_FLAG(-maltivec, SIMD_FLAGS="$SIMD_FLAGS -maltivec", [])
+ fi
+
+ AC_CACHE_CHECK([whether vsx is supported], [ax_cv_have_vsx_ext],
+ [
+ if test `LD_SHOW_AUXV=1 /bin/true 2>/dev/null|grep -c vsx` != 0; then
+ ax_cv_have_vsx_ext=yes
+ fi
+ ])
+
+ if test "$ax_cv_have_vsx_ext" = yes; then
+ AC_DEFINE(HAVE_VSX,,[Support VSX instructions])
+ AX_CHECK_COMPILE_FLAG(-mvsx, SIMD_FLAGS="$SIMD_FLAGS -mvsx", [])
+ fi
+ ;;
+
+ i[[3456]]86*|x86_64*|amd64*)
+
+ AC_REQUIRE([AX_GCC_X86_CPUID])
+ AC_REQUIRE([AX_GCC_X86_CPUID_COUNT])
+ AC_REQUIRE([AX_GCC_X86_AVX_XGETBV])
+
+ eax_cpuid0=0
+ AX_GCC_X86_CPUID(0x00000000)
+ if test "$ax_cv_gcc_x86_cpuid_0x00000000" != "unknown";
+ then
+ eax_cpuid0=`echo $ax_cv_gcc_x86_cpuid_0x00000000 | cut -d ":" -f 1`
+ fi
+
+ eax_cpuid80000000=0
+ AX_GCC_X86_CPUID(0x80000000)
+ if test "$ax_cv_gcc_x86_cpuid_0x80000000" != "unknown";
+ then
+ eax_cpuid80000000=`echo $ax_cv_gcc_x86_cpuid_0x80000000 | cut -d ":" -f 1`
+ fi
+
+ ecx_cpuid1=0
+ edx_cpuid1=0
+ if test "$((0x$eax_cpuid0))" -ge 1 ; then
+ AX_GCC_X86_CPUID(0x00000001)
+ if test "$ax_cv_gcc_x86_cpuid_0x00000001" != "unknown";
+ then
+ ecx_cpuid1=`echo $ax_cv_gcc_x86_cpuid_0x00000001 | cut -d ":" -f 3`
+ edx_cpuid1=`echo $ax_cv_gcc_x86_cpuid_0x00000001 | cut -d ":" -f 4`
+ fi
+ fi
+
+ ebx_cpuid7=0
+ ecx_cpuid7=0
+ if test "$((0x$eax_cpuid0))" -ge 7 ; then
+ AX_GCC_X86_CPUID_COUNT(0x00000007, 0x00)
+ if test "$ax_cv_gcc_x86_cpuid_0x00000007" != "unknown";
+ then
+ ebx_cpuid7=`echo $ax_cv_gcc_x86_cpuid_0x00000007 | cut -d ":" -f 2`
+ ecx_cpuid7=`echo $ax_cv_gcc_x86_cpuid_0x00000007 | cut -d ":" -f 3`
+ fi
+ fi
+
+ ecx_cpuid80000001=0
+ edx_cpuid80000001=0
+ if test "$((0x$eax_cpuid80000000))" -ge "$((0x80000001))" ; then
+ AX_GCC_X86_CPUID(0x80000001)
+ if test "$ax_cv_gcc_x86_cpuid_0x80000001" != "unknown";
+ then
+ ecx_cpuid80000001=`echo $ax_cv_gcc_x86_cpuid_0x80000001 | cut -d ":" -f 3`
+ edx_cpuid80000001=`echo $ax_cv_gcc_x86_cpuid_0x80000001 | cut -d ":" -f 4`
+ fi
+ fi
+
+ AC_CACHE_VAL([ax_cv_have_mmx_os_support_ext],
+ [
+ ax_cv_have_mmx_os_support_ext=yes
+ ])
+
+ ax_cv_have_none_os_support_ext=yes
+
+ AC_CACHE_VAL([ax_cv_have_sse_os_support_ext],
+ [
+ ax_cv_have_sse_os_support_ext=no,
+ if test "$((0x$edx_cpuid1>>25&0x01))" = 1; then
+ AC_LANG_PUSH([C])
+ AC_RUN_IFELSE([AC_LANG_SOURCE([[
+#include <signal.h>
+#include <stdlib.h>
+ /* No way at ring1 to ring3 in protected mode to check the CR0 and CR4
+ control registers directly. Execute an SSE instruction.
+ If it raises SIGILL then OS doesn't support SSE based instructions */
+ void sig_handler(int signum){ exit(1); }
+ int main(){
+ signal(SIGILL, sig_handler);
+ /* SSE instruction xorps %xmm0,%xmm0 */
+ __asm__ __volatile__ (".byte 0x0f, 0x57, 0xc0");
+ return 0;
+ }]])],
+ [ax_cv_have_sse_os_support_ext=yes],
+ [ax_cv_have_sse_os_support_ext=no],
+ [ax_cv_have_sse_os_support_ext=no])
+ AC_LANG_POP([C])
+ fi
+ ])
+
+ xgetbv_eax=0
+ if test "$((0x$ecx_cpuid1>>28&0x01))" = 1; then
+ AX_GCC_X86_AVX_XGETBV(0x00000000)
+
+ if test x"$ax_cv_gcc_x86_avx_xgetbv_0x00000000" != x"unknown"; then
+ xgetbv_eax=`echo $ax_cv_gcc_x86_avx_xgetbv_0x00000000 | cut -d ":" -f 1`
+ fi
+
+ AC_CACHE_VAL([ax_cv_have_avx_os_support_ext],
+ [
+ ax_cv_have_avx_os_support_ext=no
+ if test "$((0x$ecx_cpuid1>>27&0x01))" = 1; then
+ if test "$((0x$xgetbv_eax&0x6))" = 6; then
+ ax_cv_have_avx_os_support_ext=yes
+ fi
+ fi
+ ])
+ fi
+
+ AC_CACHE_VAL([ax_cv_have_avx512_os_support_ext],
+ [
+ ax_cv_have_avx512_os_support_ext=no
+ if test "$ax_cv_have_avx_os_support_ext" = yes; then
+ if test "$((0x$xgetbv_eax&0xe6))" = "$((0xe6))"; then
+ ax_cv_have_avx512_os_support_ext=yes
+ fi
+ fi
+ ])
+
+ for ac_instr_info dnl
+ in "none;rdrnd;RDRND;ecx_cpuid1,30;-mrdrnd;HAVE_RDRND;CPUEXT_FLAGS" dnl
+ "none;bmi1;BMI1;ebx_cpuid7,3;-mbmi;HAVE_BMI1;CPUEXT_FLAGS" dnl
+ "none;bmi2;BMI2;ebx_cpuid7,8;-mbmi2;HAVE_BMI2;CPUEXT_FLAGS" dnl
+ "none;adx;ADX;ebx_cpuid7,19;-madx;HAVE_ADX;CPUEXT_FLAGS" dnl
+ "none;mpx;MPX;ebx_cpuid7,14;-mmpx;HAVE_MPX;CPUEXT_FLAGS" dnl
+ "none;prefetchwt1;PREFETCHWT1;ecx_cpuid7,0;-mprefetchwt1;HAVE_PREFETCHWT1;CPUEXT_FLAGS" dnl
+ "none;abm;ABM;ecx_cpuid80000001,5;-mabm;HAVE_ABM;CPUEXT_FLAGS" dnl
+ "mmx;mmx;MMX;edx_cpuid1,23;-mmmx;HAVE_MMX;SIMD_FLAGS" dnl
+ "sse;sse;SSE;edx_cpuid1,25;-msse;HAVE_SSE;SIMD_FLAGS" dnl
+ "sse;sse2;SSE2;edx_cpuid1,26;-msse2;HAVE_SSE2;SIMD_FLAGS" dnl
+ "sse;sse3;SSE3;ecx_cpuid1,1;-msse3;HAVE_SSE3;SIMD_FLAGS" dnl
+ "sse;ssse3;SSSE3;ecx_cpuid1,9;-mssse3;HAVE_SSSE3;SIMD_FLAGS" dnl
+ "sse;sse41;SSE4.1;ecx_cpuid1,19;-msse4.1;HAVE_SSE4_1;SIMD_FLAGS" dnl
+ "sse;sse42;SSE4.2;ecx_cpuid1,20;-msse4.2;HAVE_SSE4_2;SIMD_FLAGS" dnl
+ "sse;sse4a;SSE4a;ecx_cpuid80000001,6;-msse4a;HAVE_SSE4a;SIMD_FLAGS" dnl
+ "sse;sha;SHA;ebx_cpuid7,29;-msha;HAVE_SHA;SIMD_FLAGS" dnl
+ "sse;aes;AES;ecx_cpuid1,25;-maes;HAVE_AES;SIMD_FLAGS" dnl
+ "avx;avx;AVX;ecx_cpuid1,28;-mavx;HAVE_AVX;SIMD_FLAGS" dnl
+ "avx;fma3;FMA3;ecx_cpuid1,12;-mfma;HAVE_FMA3;SIMD_FLAGS" dnl
+ "avx;fma4;FMA4;ecx_cpuid80000001,16;-mfma4;HAVE_FMA4;SIMD_FLAGS" dnl
+ "avx;xop;XOP;ecx_cpuid80000001,11;-mxop;HAVE_XOP;SIMD_FLAGS" dnl
+ "avx;avx2;AVX2;ebx_cpuid7,5;-mavx2;HAVE_AVX2;SIMD_FLAGS" dnl
+ "avx512;avx512f;AVX512-F;ebx_cpuid7,16;-mavx512f;HAVE_AVX512_F;SIMD_FLAGS" dnl
+ "avx512;avx512cd;AVX512-CD;ebx_cpuid7,28;-mavx512cd;HAVE_AVX512_CD;SIMD_FLAGS" dnl
+ "avx512;avx512pf;AVX512-PF;ebx_cpuid7,26;-mavx512pf;HAVE_AVX512_PF;SIMD_FLAGS" dnl
+ "avx512;avx512er;AVX512-ER;ebx_cpuid7,27;-mavx512er;HAVE_AVX512_ER;SIMD_FLAGS" dnl
+ "avx512;avx512vl;AVX512-VL;ebx_cpuid7,31;-mavx512vl;HAVE_AVX512_VL;SIMD_FLAGS" dnl
+ "avx512;avx512bw;AVX512-BW;ebx_cpuid7,30;-mavx512bw;HAVE_AVX512_BW;SIMD_FLAGS" dnl
+ "avx512;avx512dq;AVX512-DQ;ebx_cpuid7,17;-mavx512dq;HAVE_AVX512_DQ;SIMD_FLAGS" dnl
+ "avx512;avx512ifma;AVX512-IFMA;ebx_cpuid7,21;-mavx512ifma;HAVE_AVX512_IFMA;SIMD_FLAGS" dnl
+ "avx512;avx512vbmi;AVX512-VBMI;ecx_cpuid7,1;-mavx512vbmi;HAVE_AVX512_VBMI;SIMD_FLAGS" dnl
+ #
+ do ac_instr_os_support=$(eval echo \$ax_cv_have_$(echo $ac_instr_info | cut -d ";" -f 1)_os_support_ext)
+ ac_instr_acvar=$(echo $ac_instr_info | cut -d ";" -f 2)
+ ac_instr_shortname=$(echo $ac_instr_info | cut -d ";" -f 3)
+ ac_instr_chk_loc=$(echo $ac_instr_info | cut -d ";" -f 4)
+ ac_instr_chk_reg=0x$(eval echo \$$(echo $ac_instr_chk_loc | cut -d "," -f 1))
+ ac_instr_chk_bit=$(echo $ac_instr_chk_loc | cut -d "," -f 2)
+ ac_instr_compiler_flags=$(echo $ac_instr_info | cut -d ";" -f 5)
+ ac_instr_have_define=$(echo $ac_instr_info | cut -d ";" -f 6)
+ ac_instr_flag_type=$(echo $ac_instr_info | cut -d ";" -f 7)
+
+ AC_CACHE_CHECK([whether ${ac_instr_shortname} is supported by the processor], [ax_cv_have_${ac_instr_acvar}_cpu_ext],
+ [
+ eval ax_cv_have_${ac_instr_acvar}_cpu_ext=no
+ if test "$((${ac_instr_chk_reg}>>${ac_instr_chk_bit}&0x01))" = 1 ; then
+ eval ax_cv_have_${ac_instr_acvar}_cpu_ext=yes
+ fi
+ ])
+
+ if test x"$(eval echo \$ax_cv_have_${ac_instr_acvar}_cpu_ext)" = x"yes"; then
+ AC_CACHE_CHECK([whether ${ac_instr_shortname} is supported by the processor and OS], [ax_cv_have_${ac_instr_acvar}_ext],
+ [
+ eval ax_cv_have_${ac_instr_acvar}_ext=no
+ if test x"${ac_instr_os_support}" = x"yes"; then
+ eval ax_cv_have_${ac_instr_acvar}_ext=yes
+ fi
+ ])
+
+ if test "$(eval echo \$ax_cv_have_${ac_instr_acvar}_ext)" = yes; then
+ AX_CHECK_COMPILE_FLAG(${ac_instr_compiler_flags}, eval ax_cv_support_${ac_instr_acvar}_ext=yes,
+ eval ax_cv_support_${ac_instr_acvar}_ext=no)
+ if test x"$(eval echo \$ax_cv_support_${ac_instr_acvar}_ext)" = x"yes"; then
+ eval ${ac_instr_flag_type}=\"\$${ac_instr_flag_type} ${ac_instr_compiler_flags}\"
+ AC_DEFINE_UNQUOTED([${ac_instr_have_define}])
+ else
+ AC_MSG_WARN([Your processor and OS supports ${ac_instr_shortname} instructions but not your compiler, can you try another compiler?])
+ fi
+ else
+ if test x"${ac_instr_os_support}" = x"no"; then
+ AC_CACHE_VAL(ax_cv_support_${ac_instr_acvar}_ext, eval ax_cv_support_${ac_instr_acvar}_ext=no)
+ AC_MSG_WARN([Your processor supports ${ac_instr_shortname}, but your OS doesn't])
+ fi
+ fi
+ else
+ AC_CACHE_VAL(ax_cv_have_${ac_instr_acvar}_ext, eval ax_cv_have_${ac_instr_acvar}_ext=no)
+ AC_CACHE_VAL(ax_cv_support_${ac_instr_acvar}_ext, eval ax_cv_support_${ac_instr_acvar}_ext=no)
+ fi
+ done
+ ;;
+ esac
+
+ AH_TEMPLATE([HAVE_RDRND],[Define to 1 to support Digital Random Number Generator])
+ AH_TEMPLATE([HAVE_BMI1],[Define to 1 to support Bit Manipulation Instruction Set 1])
+ AH_TEMPLATE([HAVE_BMI2],[Define to 1 to support Bit Manipulation Instruction Set 2])
+ AH_TEMPLATE([HAVE_ADX],[Define to 1 to support Multi-Precision Add-Carry Instruction Extensions])
+ AH_TEMPLATE([HAVE_MPX],[Define to 1 to support Memory Protection Extensions])
+ AH_TEMPLATE([HAVE_PREFETCHWT1],[Define to 1 to support Prefetch Vector Data Into Caches WT1])
+ AH_TEMPLATE([HAVE_ABM],[Define to 1 to support Advanced Bit Manipulation])
+ AH_TEMPLATE([HAVE_MMX],[Define to 1 to support Multimedia Extensions])
+ AH_TEMPLATE([HAVE_SSE],[Define to 1 to support Streaming SIMD Extensions])
+ AH_TEMPLATE([HAVE_SSE2],[Define to 1 to support Streaming SIMD Extensions])
+ AH_TEMPLATE([HAVE_SSE3],[Define to 1 to support Streaming SIMD Extensions 3])
+ AH_TEMPLATE([HAVE_SSSE3],[Define to 1 to support Supplemental Streaming SIMD Extensions 3])
+ AH_TEMPLATE([HAVE_SSE4_1],[Define to 1 to support Streaming SIMD Extensions 4.1])
+ AH_TEMPLATE([HAVE_SSE4_2],[Define to 1 to support Streaming SIMD Extensions 4.2])
+ AH_TEMPLATE([HAVE_SSE4a],[Define to 1 to support AMD Streaming SIMD Extensions 4a])
+ AH_TEMPLATE([HAVE_SHA],[Define to 1 to support Secure Hash Algorithm Extension])
+ AH_TEMPLATE([HAVE_AES],[Define to 1 to support Advanced Encryption Standard New Instruction Set (AES-NI)])
+ AH_TEMPLATE([HAVE_AVX],[Define to 1 to support Advanced Vector Extensions])
+ AH_TEMPLATE([HAVE_FMA3],[Define to 1 to support Fused Multiply-Add Extensions 3])
+ AH_TEMPLATE([HAVE_FMA4],[Define to 1 to support Fused Multiply-Add Extensions 4])
+ AH_TEMPLATE([HAVE_XOP],[Define to 1 to support eXtended Operations Extensions])
+ AH_TEMPLATE([HAVE_AVX2],[Define to 1 to support Advanced Vector Extensions 2])
+ AH_TEMPLATE([HAVE_AVX512_F],[Define to 1 to support AVX-512 Foundation Extensions])
+ AH_TEMPLATE([HAVE_AVX512_CD],[Define to 1 to support AVX-512 Conflict Detection Instructions])
+ AH_TEMPLATE([HAVE_AVX512_PF],[Define to 1 to support AVX-512 Conflict Prefetch Instructions])
+ AH_TEMPLATE([HAVE_AVX512_ER],[Define to 1 to support AVX-512 Exponential & Reciprocal Instructions])
+ AH_TEMPLATE([HAVE_AVX512_VL],[Define to 1 to support AVX-512 Vector Length Extensions])
+ AH_TEMPLATE([HAVE_AVX512_BW],[Define to 1 to support AVX-512 Byte and Word Instructions])
+ AH_TEMPLATE([HAVE_AVX512_DQ],[Define to 1 to support AVX-512 Doubleword and Quadword Instructions])
+ AH_TEMPLATE([HAVE_AVX512_IFMA],[Define to 1 to support AVX-512 Integer Fused Multiply Add Instructions])
+ AH_TEMPLATE([HAVE_AVX512_VBMI],[Define to 1 to support AVX-512 Vector Byte Manipulation Instructions])
+ AC_SUBST(SIMD_FLAGS)
+ AC_SUBST(CPUEXT_FLAGS)
+])
diff --git a/m4/ax_gcc_x86_avx_xgetbv.m4 b/m4/ax_gcc_x86_avx_xgetbv.m4
new file mode 100644
index 0000000..a57fc5e
--- /dev/null
+++ b/m4/ax_gcc_x86_avx_xgetbv.m4
@@ -0,0 +1,79 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_gcc_x86_avx_xgetbv.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_GCC_X86_AVX_XGETBV
+#
+# DESCRIPTION
+#
+# On later x86 processors with AVX SIMD support, with gcc or a compiler
+# that has a compatible syntax for inline assembly instructions, run a
+# small program that executes the xgetbv instruction with input OP. This
+# can be used to detect if the OS supports AVX instruction usage.
+#
+# On output, the values of the eax and edx registers are stored as
+# hexadecimal strings as "eax:edx" in the cache variable
+# ax_cv_gcc_x86_avx_xgetbv.
+#
+# If the xgetbv instruction fails (because you are running a
+# cross-compiler, or because you are not using gcc, or because you are on
+# a processor that doesn't have this instruction),
+# ax_cv_gcc_x86_avx_xgetbv_OP is set to the string "unknown".
+#
+# This macro mainly exists to be used in AX_EXT.
+#
+# LICENSE
+#
+# Copyright (c) 2013 Michael Petch <mpetch@capp-sysware.com>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# 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, see <https://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 3
+
+AC_DEFUN([AX_GCC_X86_AVX_XGETBV],
+[AC_REQUIRE([AC_PROG_CC])
+AC_LANG_PUSH([C])
+AC_CACHE_CHECK(for x86-AVX xgetbv $1 output, ax_cv_gcc_x86_avx_xgetbv_$1,
+ [AC_RUN_IFELSE([AC_LANG_PROGRAM([#include <stdio.h>], [
+ int op = $1, eax, edx;
+ FILE *f;
+ /* Opcodes for xgetbv */
+ __asm__ __volatile__ (".byte 0x0f, 0x01, 0xd0"
+ : "=a" (eax), "=d" (edx)
+ : "c" (op));
+ f = fopen("conftest_xgetbv", "w"); if (!f) return 1;
+ fprintf(f, "%x:%x\n", eax, edx);
+ fclose(f);
+ return 0;
+])],
+ [ax_cv_gcc_x86_avx_xgetbv_$1=`cat conftest_xgetbv`; rm -f conftest_xgetbv],
+ [ax_cv_gcc_x86_avx_xgetbv_$1=unknown; rm -f conftest_xgetbv],
+ [ax_cv_gcc_x86_avx_xgetbv_$1=unknown])])
+AC_LANG_POP([C])
+])
diff --git a/m4/ax_gcc_x86_cpuid.m4 b/m4/ax_gcc_x86_cpuid.m4
new file mode 100644
index 0000000..df95465
--- /dev/null
+++ b/m4/ax_gcc_x86_cpuid.m4
@@ -0,0 +1,89 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_gcc_x86_cpuid.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_GCC_X86_CPUID(OP)
+# AX_GCC_X86_CPUID_COUNT(OP, COUNT)
+#
+# DESCRIPTION
+#
+# On Pentium and later x86 processors, with gcc or a compiler that has a
+# compatible syntax for inline assembly instructions, run a small program
+# that executes the cpuid instruction with input OP. This can be used to
+# detect the CPU type. AX_GCC_X86_CPUID_COUNT takes an additional COUNT
+# parameter that gets passed into register ECX before calling cpuid.
+#
+# On output, the values of the eax, ebx, ecx, and edx registers are stored
+# as hexadecimal strings as "eax:ebx:ecx:edx" in the cache variable
+# ax_cv_gcc_x86_cpuid_OP.
+#
+# If the cpuid instruction fails (because you are running a
+# cross-compiler, or because you are not using gcc, or because you are on
+# a processor that doesn't have this instruction), ax_cv_gcc_x86_cpuid_OP
+# is set to the string "unknown".
+#
+# This macro mainly exists to be used in AX_GCC_ARCHFLAG.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu>
+# Copyright (c) 2008 Matteo Frigo
+# Copyright (c) 2015 Michael Petch <mpetch@capp-sysware.com>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# 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, see <https://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 10
+
+AC_DEFUN([AX_GCC_X86_CPUID],
+[AX_GCC_X86_CPUID_COUNT($1, 0)
+])
+
+AC_DEFUN([AX_GCC_X86_CPUID_COUNT],
+[AC_REQUIRE([AC_PROG_CC])
+AC_LANG_PUSH([C])
+AC_CACHE_CHECK(for x86 cpuid $1 output, ax_cv_gcc_x86_cpuid_$1,
+ [AC_RUN_IFELSE([AC_LANG_PROGRAM([#include <stdio.h>], [
+ int op = $1, level = $2, eax, ebx, ecx, edx;
+ FILE *f;
+ __asm__ __volatile__ ("xchg %%ebx, %1\n"
+ "cpuid\n"
+ "xchg %%ebx, %1\n"
+ : "=a" (eax), "=r" (ebx), "=c" (ecx), "=d" (edx)
+ : "a" (op), "2" (level));
+
+ f = fopen("conftest_cpuid", "w"); if (!f) return 1;
+ fprintf(f, "%x:%x:%x:%x\n", eax, ebx, ecx, edx);
+ fclose(f);
+ return 0;
+])],
+ [ax_cv_gcc_x86_cpuid_$1=`cat conftest_cpuid`; rm -f conftest_cpuid],
+ [ax_cv_gcc_x86_cpuid_$1=unknown; rm -f conftest_cpuid],
+ [ax_cv_gcc_x86_cpuid_$1=unknown])])
+AC_LANG_POP([C])
+])
diff --git a/m4/ax_prepend_flag.m4 b/m4/ax_prepend_flag.m4
new file mode 100644
index 0000000..adac8c5
--- /dev/null
+++ b/m4/ax_prepend_flag.m4
@@ -0,0 +1,51 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_prepend_flag.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_PREPEND_FLAG(FLAG, [FLAGS-VARIABLE])
+#
+# DESCRIPTION
+#
+# FLAG is added to the front of the FLAGS-VARIABLE shell variable, with a
+# space added in between.
+#
+# If FLAGS-VARIABLE is not specified, the current language's flags (e.g.
+# CFLAGS) is used. FLAGS-VARIABLE is not changed if it already contains
+# FLAG. If FLAGS-VARIABLE is unset in the shell, it is set to exactly
+# FLAG.
+#
+# NOTE: Implementation based on AX_APPEND_FLAG.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
+# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
+# Copyright (c) 2018 John Zaitseff <J.Zaitseff@zap.org.au>
+#
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved. This file is offered as-is, without any
+# warranty.
+
+#serial 2
+
+AC_DEFUN([AX_PREPEND_FLAG],
+[dnl
+AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_SET_IF
+AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[FLAGS])])
+AS_VAR_SET_IF(FLAGS,[
+ AS_CASE([" AS_VAR_GET(FLAGS) "],
+ [*" $1 "*], [AC_RUN_LOG([: FLAGS already contains $1])],
+ [
+ FLAGS="$1 $FLAGS"
+ AC_RUN_LOG([: FLAGS="$FLAGS"])
+ ])
+ ],
+ [
+ AS_VAR_SET(FLAGS,[$1])
+ AC_RUN_LOG([: FLAGS="$FLAGS"])
+ ])
+AS_VAR_POPDEF([FLAGS])dnl
+])dnl AX_PREPEND_FLAG
diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4
new file mode 100644
index 0000000..1598d07
--- /dev/null
+++ b/m4/ax_pthread.m4
@@ -0,0 +1,507 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_pthread.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]])
+#
+# DESCRIPTION
+#
+# This macro figures out how to build C programs using POSIX threads. It
+# sets the PTHREAD_LIBS output variable to the threads library and linker
+# flags, and the PTHREAD_CFLAGS output variable to any special C compiler
+# flags that are needed. (The user can also force certain compiler
+# flags/libs to be tested by setting these environment variables.)
+#
+# Also sets PTHREAD_CC to any special C compiler that is needed for
+# multi-threaded programs (defaults to the value of CC otherwise). (This
+# is necessary on AIX to use the special cc_r compiler alias.)
+#
+# NOTE: You are assumed to not only compile your program with these flags,
+# but also to link with them as well. For example, you might link with
+# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS
+#
+# If you are only building threaded programs, you may wish to use these
+# variables in your default LIBS, CFLAGS, and CC:
+#
+# LIBS="$PTHREAD_LIBS $LIBS"
+# CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+# CC="$PTHREAD_CC"
+#
+# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant
+# has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to
+# that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX).
+#
+# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the
+# PTHREAD_PRIO_INHERIT symbol is defined when compiling with
+# PTHREAD_CFLAGS.
+#
+# ACTION-IF-FOUND is a list of shell commands to run if a threads library
+# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it
+# is not found. If ACTION-IF-FOUND is not specified, the default action
+# will define HAVE_PTHREAD.
+#
+# Please let the authors know if this macro fails on any platform, or if
+# you have any other suggestions or comments. This macro was based on work
+# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help
+# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by
+# Alejandro Forero Cuervo to the autoconf macro repository. We are also
+# grateful for the helpful feedback of numerous users.
+#
+# Updated for Autoconf 2.68 by Daniel Richard G.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu>
+# Copyright (c) 2011 Daniel Richard G. <skunk@iSKUNK.ORG>
+# Copyright (c) 2019 Marc Stevens <marc.stevens@cwi.nl>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# 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, see <https://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 27
+
+AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD])
+AC_DEFUN([AX_PTHREAD], [
+AC_REQUIRE([AC_CANONICAL_HOST])
+AC_REQUIRE([AC_PROG_CC])
+AC_REQUIRE([AC_PROG_SED])
+AC_LANG_PUSH([C])
+ax_pthread_ok=no
+
+# We used to check for pthread.h first, but this fails if pthread.h
+# requires special compiler flags (e.g. on Tru64 or Sequent).
+# It gets checked for in the link test anyway.
+
+# First of all, check if the user has set any of the PTHREAD_LIBS,
+# etcetera environment variables, and if threads linking works using
+# them:
+if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then
+ ax_pthread_save_CC="$CC"
+ ax_pthread_save_CFLAGS="$CFLAGS"
+ ax_pthread_save_LIBS="$LIBS"
+ AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"])
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS])
+ AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes])
+ AC_MSG_RESULT([$ax_pthread_ok])
+ if test "x$ax_pthread_ok" = "xno"; then
+ PTHREAD_LIBS=""
+ PTHREAD_CFLAGS=""
+ fi
+ CC="$ax_pthread_save_CC"
+ CFLAGS="$ax_pthread_save_CFLAGS"
+ LIBS="$ax_pthread_save_LIBS"
+fi
+
+# We must check for the threads library under a number of different
+# names; the ordering is very important because some systems
+# (e.g. DEC) have both -lpthread and -lpthreads, where one of the
+# libraries is broken (non-POSIX).
+
+# Create a list of thread flags to try. Items with a "," contain both
+# C compiler flags (before ",") and linker flags (after ","). Other items
+# starting with a "-" are C compiler flags, and remaining items are
+# library names, except for "none" which indicates that we try without
+# any flags at all, and "pthread-config" which is a program returning
+# the flags for the Pth emulation library.
+
+ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config"
+
+# The ordering *is* (sometimes) important. Some notes on the
+# individual items follow:
+
+# pthreads: AIX (must check this before -lpthread)
+# none: in case threads are in libc; should be tried before -Kthread and
+# other compiler flags to prevent continual compiler warnings
+# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h)
+# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64
+# (Note: HP C rejects this with "bad form for `-t' option")
+# -pthreads: Solaris/gcc (Note: HP C also rejects)
+# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it
+# doesn't hurt to check since this sometimes defines pthreads and
+# -D_REENTRANT too), HP C (must be checked before -lpthread, which
+# is present but should not be used directly; and before -mthreads,
+# because the compiler interprets this as "-mt" + "-hreads")
+# -mthreads: Mingw32/gcc, Lynx/gcc
+# pthread: Linux, etcetera
+# --thread-safe: KAI C++
+# pthread-config: use pthread-config program (for GNU Pth library)
+
+case $host_os in
+
+ freebsd*)
+
+ # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able)
+ # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread)
+
+ ax_pthread_flags="-kthread lthread $ax_pthread_flags"
+ ;;
+
+ hpux*)
+
+ # From the cc(1) man page: "[-mt] Sets various -D flags to enable
+ # multi-threading and also sets -lpthread."
+
+ ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags"
+ ;;
+
+ openedition*)
+
+ # IBM z/OS requires a feature-test macro to be defined in order to
+ # enable POSIX threads at all, so give the user a hint if this is
+ # not set. (We don't define these ourselves, as they can affect
+ # other portions of the system API in unpredictable ways.)
+
+ AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING],
+ [
+# if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS)
+ AX_PTHREAD_ZOS_MISSING
+# endif
+ ],
+ [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])])
+ ;;
+
+ solaris*)
+
+ # On Solaris (at least, for some versions), libc contains stubbed
+ # (non-functional) versions of the pthreads routines, so link-based
+ # tests will erroneously succeed. (N.B.: The stubs are missing
+ # pthread_cleanup_push, or rather a function called by this macro,
+ # so we could check for that, but who knows whether they'll stub
+ # that too in a future libc.) So we'll check first for the
+ # standard Solaris way of linking pthreads (-mt -lpthread).
+
+ ax_pthread_flags="-mt,-lpthread pthread $ax_pthread_flags"
+ ;;
+esac
+
+# Are we compiling with Clang?
+
+AC_CACHE_CHECK([whether $CC is Clang],
+ [ax_cv_PTHREAD_CLANG],
+ [ax_cv_PTHREAD_CLANG=no
+ # Note that Autoconf sets GCC=yes for Clang as well as GCC
+ if test "x$GCC" = "xyes"; then
+ AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG],
+ [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */
+# if defined(__clang__) && defined(__llvm__)
+ AX_PTHREAD_CC_IS_CLANG
+# endif
+ ],
+ [ax_cv_PTHREAD_CLANG=yes])
+ fi
+ ])
+ax_pthread_clang="$ax_cv_PTHREAD_CLANG"
+
+
+# GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC)
+
+# Note that for GCC and Clang -pthread generally implies -lpthread,
+# except when -nostdlib is passed.
+# This is problematic using libtool to build C++ shared libraries with pthread:
+# [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25460
+# [2] https://bugzilla.redhat.com/show_bug.cgi?id=661333
+# [3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=468555
+# To solve this, first try -pthread together with -lpthread for GCC
+
+AS_IF([test "x$GCC" = "xyes"],
+ [ax_pthread_flags="-pthread,-lpthread -pthread -pthreads $ax_pthread_flags"])
+
+# Clang takes -pthread (never supported any other flag), but we'll try with -lpthread first
+
+AS_IF([test "x$ax_pthread_clang" = "xyes"],
+ [ax_pthread_flags="-pthread,-lpthread -pthread"])
+
+
+# The presence of a feature test macro requesting re-entrant function
+# definitions is, on some systems, a strong hint that pthreads support is
+# correctly enabled
+
+case $host_os in
+ darwin* | hpux* | linux* | osf* | solaris*)
+ ax_pthread_check_macro="_REENTRANT"
+ ;;
+
+ aix*)
+ ax_pthread_check_macro="_THREAD_SAFE"
+ ;;
+
+ *)
+ ax_pthread_check_macro="--"
+ ;;
+esac
+AS_IF([test "x$ax_pthread_check_macro" = "x--"],
+ [ax_pthread_check_cond=0],
+ [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"])
+
+
+if test "x$ax_pthread_ok" = "xno"; then
+for ax_pthread_try_flag in $ax_pthread_flags; do
+
+ case $ax_pthread_try_flag in
+ none)
+ AC_MSG_CHECKING([whether pthreads work without any flags])
+ ;;
+
+ *,*)
+ PTHREAD_CFLAGS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\1/"`
+ PTHREAD_LIBS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\2/"`
+ AC_MSG_CHECKING([whether pthreads work with "$PTHREAD_CFLAGS" and "$PTHREAD_LIBS"])
+ ;;
+
+ -*)
+ AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag])
+ PTHREAD_CFLAGS="$ax_pthread_try_flag"
+ ;;
+
+ pthread-config)
+ AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no])
+ AS_IF([test "x$ax_pthread_config" = "xno"], [continue])
+ PTHREAD_CFLAGS="`pthread-config --cflags`"
+ PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`"
+ ;;
+
+ *)
+ AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag])
+ PTHREAD_LIBS="-l$ax_pthread_try_flag"
+ ;;
+ esac
+
+ ax_pthread_save_CFLAGS="$CFLAGS"
+ ax_pthread_save_LIBS="$LIBS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+
+ # Check for various functions. We must include pthread.h,
+ # since some functions may be macros. (On the Sequent, we
+ # need a special flag -Kthread to make this header compile.)
+ # We check for pthread_join because it is in -lpthread on IRIX
+ # while pthread_create is in libc. We check for pthread_attr_init
+ # due to DEC craziness with -lpthreads. We check for
+ # pthread_cleanup_push because it is one of the few pthread
+ # functions on Solaris that doesn't have a non-functional libc stub.
+ # We try pthread_create on general principles.
+
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>
+# if $ax_pthread_check_cond
+# error "$ax_pthread_check_macro must be defined"
+# endif
+ static void *some_global = NULL;
+ static void routine(void *a)
+ {
+ /* To avoid any unused-parameter or
+ unused-but-set-parameter warning. */
+ some_global = a;
+ }
+ static void *start_routine(void *a) { return a; }],
+ [pthread_t th; pthread_attr_t attr;
+ pthread_create(&th, 0, start_routine, 0);
+ pthread_join(th, 0);
+ pthread_attr_init(&attr);
+ pthread_cleanup_push(routine, 0);
+ pthread_cleanup_pop(0) /* ; */])],
+ [ax_pthread_ok=yes],
+ [])
+
+ CFLAGS="$ax_pthread_save_CFLAGS"
+ LIBS="$ax_pthread_save_LIBS"
+
+ AC_MSG_RESULT([$ax_pthread_ok])
+ AS_IF([test "x$ax_pthread_ok" = "xyes"], [break])
+
+ PTHREAD_LIBS=""
+ PTHREAD_CFLAGS=""
+done
+fi
+
+
+# Clang needs special handling, because older versions handle the -pthread
+# option in a rather... idiosyncratic way
+
+if test "x$ax_pthread_clang" = "xyes"; then
+
+ # Clang takes -pthread; it has never supported any other flag
+
+ # (Note 1: This will need to be revisited if a system that Clang
+ # supports has POSIX threads in a separate library. This tends not
+ # to be the way of modern systems, but it's conceivable.)
+
+ # (Note 2: On some systems, notably Darwin, -pthread is not needed
+ # to get POSIX threads support; the API is always present and
+ # active. We could reasonably leave PTHREAD_CFLAGS empty. But
+ # -pthread does define _REENTRANT, and while the Darwin headers
+ # ignore this macro, third-party headers might not.)
+
+ # However, older versions of Clang make a point of warning the user
+ # that, in an invocation where only linking and no compilation is
+ # taking place, the -pthread option has no effect ("argument unused
+ # during compilation"). They expect -pthread to be passed in only
+ # when source code is being compiled.
+ #
+ # Problem is, this is at odds with the way Automake and most other
+ # C build frameworks function, which is that the same flags used in
+ # compilation (CFLAGS) are also used in linking. Many systems
+ # supported by AX_PTHREAD require exactly this for POSIX threads
+ # support, and in fact it is often not straightforward to specify a
+ # flag that is used only in the compilation phase and not in
+ # linking. Such a scenario is extremely rare in practice.
+ #
+ # Even though use of the -pthread flag in linking would only print
+ # a warning, this can be a nuisance for well-run software projects
+ # that build with -Werror. So if the active version of Clang has
+ # this misfeature, we search for an option to squash it.
+
+ AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread],
+ [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG],
+ [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown
+ # Create an alternate version of $ac_link that compiles and
+ # links in two steps (.c -> .o, .o -> exe) instead of one
+ # (.c -> exe), because the warning occurs only in the second
+ # step
+ ax_pthread_save_ac_link="$ac_link"
+ ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g'
+ ax_pthread_link_step=`$as_echo "$ac_link" | sed "$ax_pthread_sed"`
+ ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)"
+ ax_pthread_save_CFLAGS="$CFLAGS"
+ for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do
+ AS_IF([test "x$ax_pthread_try" = "xunknown"], [break])
+ CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS"
+ ac_link="$ax_pthread_save_ac_link"
+ AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])],
+ [ac_link="$ax_pthread_2step_ac_link"
+ AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])],
+ [break])
+ ])
+ done
+ ac_link="$ax_pthread_save_ac_link"
+ CFLAGS="$ax_pthread_save_CFLAGS"
+ AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no])
+ ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try"
+ ])
+
+ case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in
+ no | unknown) ;;
+ *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;;
+ esac
+
+fi # $ax_pthread_clang = yes
+
+
+
+# Various other checks:
+if test "x$ax_pthread_ok" = "xyes"; then
+ ax_pthread_save_CFLAGS="$CFLAGS"
+ ax_pthread_save_LIBS="$LIBS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+
+ # Detect AIX lossage: JOINABLE attribute is called UNDETACHED.
+ AC_CACHE_CHECK([for joinable pthread attribute],
+ [ax_cv_PTHREAD_JOINABLE_ATTR],
+ [ax_cv_PTHREAD_JOINABLE_ATTR=unknown
+ for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>],
+ [int attr = $ax_pthread_attr; return attr /* ; */])],
+ [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break],
+ [])
+ done
+ ])
+ AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \
+ test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \
+ test "x$ax_pthread_joinable_attr_defined" != "xyes"],
+ [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE],
+ [$ax_cv_PTHREAD_JOINABLE_ATTR],
+ [Define to necessary symbol if this constant
+ uses a non-standard name on your system.])
+ ax_pthread_joinable_attr_defined=yes
+ ])
+
+ AC_CACHE_CHECK([whether more special flags are required for pthreads],
+ [ax_cv_PTHREAD_SPECIAL_FLAGS],
+ [ax_cv_PTHREAD_SPECIAL_FLAGS=no
+ case $host_os in
+ solaris*)
+ ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS"
+ ;;
+ esac
+ ])
+ AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \
+ test "x$ax_pthread_special_flags_added" != "xyes"],
+ [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS"
+ ax_pthread_special_flags_added=yes])
+
+ AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT],
+ [ax_cv_PTHREAD_PRIO_INHERIT],
+ [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <pthread.h>]],
+ [[int i = PTHREAD_PRIO_INHERIT;
+ return i;]])],
+ [ax_cv_PTHREAD_PRIO_INHERIT=yes],
+ [ax_cv_PTHREAD_PRIO_INHERIT=no])
+ ])
+ AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \
+ test "x$ax_pthread_prio_inherit_defined" != "xyes"],
+ [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.])
+ ax_pthread_prio_inherit_defined=yes
+ ])
+
+ CFLAGS="$ax_pthread_save_CFLAGS"
+ LIBS="$ax_pthread_save_LIBS"
+
+ # More AIX lossage: compile with *_r variant
+ if test "x$GCC" != "xyes"; then
+ case $host_os in
+ aix*)
+ AS_CASE(["x/$CC"],
+ [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6],
+ [#handle absolute path differently from PATH based program lookup
+ AS_CASE(["x$CC"],
+ [x/*],
+ [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])],
+ [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])])
+ ;;
+ esac
+ fi
+fi
+
+test -n "$PTHREAD_CC" || PTHREAD_CC="$CC"
+
+AC_SUBST([PTHREAD_LIBS])
+AC_SUBST([PTHREAD_CFLAGS])
+AC_SUBST([PTHREAD_CC])
+
+# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND:
+if test "x$ax_pthread_ok" = "xyes"; then
+ ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1])
+ :
+else
+ ax_pthread_ok=no
+ $2
+fi
+AC_LANG_POP
+])dnl AX_PTHREAD
diff --git a/m4/ax_require_defined.m4 b/m4/ax_require_defined.m4
new file mode 100644
index 0000000..17c3eab
--- /dev/null
+++ b/m4/ax_require_defined.m4
@@ -0,0 +1,37 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_require_defined.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_REQUIRE_DEFINED(MACRO)
+#
+# DESCRIPTION
+#
+# AX_REQUIRE_DEFINED is a simple helper for making sure other macros have
+# been defined and thus are available for use. This avoids random issues
+# where a macro isn't expanded. Instead the configure script emits a
+# non-fatal:
+#
+# ./configure: line 1673: AX_CFLAGS_WARN_ALL: command not found
+#
+# It's like AC_REQUIRE except it doesn't expand the required macro.
+#
+# Here's an example:
+#
+# AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG])
+#
+# LICENSE
+#
+# Copyright (c) 2014 Mike Frysinger <vapier@gentoo.org>
+#
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved. This file is offered as-is, without any
+# warranty.
+
+#serial 2
+
+AC_DEFUN([AX_REQUIRE_DEFINED], [dnl
+ m4_ifndef([$1], [m4_fatal([macro ]$1[ is not defined; is a m4 file missing?])])
+])dnl AX_REQUIRE_DEFINED
diff --git a/m4/dl.sh b/m4/dl.sh
new file mode 100755
index 0000000..08414cf
--- /dev/null
+++ b/m4/dl.sh
@@ -0,0 +1,27 @@
+#!/bin/sh -e
+# Copyright (c) 2018-2021, OARC, Inc.
+# All rights reserved.
+#
+# This file is part of dnsjit.
+#
+# dnsjit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+m4_files="ax_append_flag.m4 ax_cflags_warn_all.m4 ax_ext.m4 ax_pthread.m4
+ ax_require_defined.m4 ax_gcc_x86_avx_xgetbv.m4 ax_gcc_x86_cpuid.m4
+ ax_check_compile_flag.m4 ax_prepend_flag.m4 ax_compiler_vendor.m4"
+
+for ax in $m4_files; do
+ rm -f "$ax"
+ wget -O "$ax" "http://git.savannah.gnu.org/gitweb/?p=autoconf-archive.git;a=blob_plain;f=m4/$ax"
+done
diff --git a/rpm/dnsjit.spec b/rpm/dnsjit.spec
new file mode 100644
index 0000000..c2deb72
--- /dev/null
+++ b/rpm/dnsjit.spec
@@ -0,0 +1,168 @@
+Name: dnsjit
+Version: 1.1.0
+Release: 1%{?dist}
+Summary: Engine for capturing, parsing and replaying DNS
+Group: Productivity/Networking/DNS/Utilities
+
+License: GPL-3.0-or-later
+URL: https://github.com/DNS-OARC/dnsjit
+# Source needs to be generated by dist-tools/create-source-packages, see
+# https://github.com/jelu/dist-tools
+Source0: https://github.com/DNS-OARC/dnsjit/archive/v%{version}.tar.gz?/%{name}_%{version}.orig.tar.gz
+
+BuildRequires: libpcap-devel
+%if 0%{?suse_version} || 0%{?sle_version}
+BuildRequires: moonjit-devel >= 2.0.0
+%else
+BuildRequires: luajit-devel >= 2.0.0
+%endif
+BuildRequires: lmdb-devel
+BuildRequires: ck-devel
+BuildRequires: gnutls-devel
+BuildRequires: libuv-devel
+BuildRequires: libnghttp2-devel
+BuildRequires: autoconf >= 2.64
+BuildRequires: automake
+BuildRequires: libtool
+
+%description
+dnsjit is a combination of parts taken from dsc, dnscap, drool,
+and put together around Lua to create a script-based engine for easy
+capturing, parsing and statistics gathering of DNS message while also
+providing facilities for replaying DNS traffic.
+
+
+%prep
+%setup -q -n %{name}_%{version}
+
+
+%build
+sh autogen.sh
+%configure
+make %{?_smp_mflags}
+
+
+%install
+rm -rf $RPM_BUILD_ROOT
+make install DESTDIR=$RPM_BUILD_ROOT
+
+
+%check
+make test
+
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+
+%files
+%defattr(-,root,root)
+%{_bindir}/*
+%{_datadir}/doc/*
+%{_mandir}/man1/*
+%{_mandir}/man3/*
+
+
+%changelog
+* Wed Feb 03 2021 Jerry Lundström <lundstrom.jerry@gmail.com> 1.1.0-1
+- Release 1.1.0
+ * This releases adds a new module for handling Base64 URLs and new calls
+ for error handling and opening PCAPs using file descriptors, along with
+ a bug fix in `lib.getopt` and other changes.
+ * The `dnssim` module has also gotten its own version and changelog, this
+ is to prepare it for being moved outside of dnsjit's repository in the
+ future.
+ * New modules, calls, features:
+ - New `lib.base64url`: Utility library to convert data to base64url format
+ - `core.log`: New call `Log.errstr()`: Convert error number to its text representation
+ - `input.fpcap`: New call `Fpcap.openfp()`: Open a PCAP file for processing using a file descriptor, for example `io.stdin`
+ - `output.dnssim`: Support for DNS-over-HTTPS
+ * Bug fixes:
+ - `lib.getopt`: Fix bug where `-` and `--` could not be used as arguments to options
+ * Other changes:
+ - Fix typo in configure help text
+ - Add coverage
+ - `filter.ipsplit`: Extend PRNG modulus to 2^31, new implementation is the same as glibc's `rand()`
+ - `lib.ip`: Fix typo in documentation
+ - `output.dnssim`:
+ - This module now has it's own changelog
+ - Updated to v20210129
+ - Depend on libhttp2 for dnssim DNS-over-HTTPS capabilities
+ - `output.pcap`: Log libpcap error when failing to open
+ - SUSE packages now depend on moonjit because of lack of LuaJIT support
+ * Commits:
+ d001ccb m4
+ 4b63bce output/dnssim: add changelog
+ 7355810 output/dnssim: add version checks
+ 95fa6a9 input pcap/fpcap, getopt
+ 99c3d9f test/test_ipsplit: update to use new PRNG
+ 3235b09 filter/ipsplit: extend PRNG modulus to 2^31
+ 8ff81a0 fixup! input.fpcap: filename "-" reads from stdin
+ 63cf0a4 output/dnssim: fix regression in DoH GET
+ 367d0b8 input.pcap: document stdin feature of open_offline()
+ 8d94504 input.fpcap: filename "-" reads from stdin
+ 617058e getopt: accept singleton - also as option value
+ 7d7f17c output/dnssim: unify failed to bind error messages
+ bdf1517 output/dnssim: add IPv4 support
+ 15a21da Sonarcloud
+ ceeea1d SUSE
+ 1fc3c82 PR179
+ 2f5d38f output/dnssim: allow user-set instance log name
+ b036c68 Info
+ 0af1ffb Travis, configure
+ 49bdc08 output/dnssim: implement udp(tcp_fallback) method
+ b4f9cf9 man: update gitlab.labs.nic.cz to gitlab.nic.cz
+ 45b977d output/dnssim: update man page
+ 4184090 output/dnssim: https2 - fix connection closure issues
+ 342f33e output/dnssim: https2 - omit closing connection inside callback
+ 67a76d5 output/dnssim: handle all states when closing connection
+ 41f04d8 output/dnssim: document importance of conn state enum ordering
+ 795ab6f output/dnssim: tls - fix handling of CONGESTED connections
+ 8792b32 output/dnssim: match QUESTION section of received responses
+ 3a88f5b Coverage
+ 4f611c8 dnssim
+ 6e35d5b Compile
+ 63faa44 README, format code, man-page
+ 925f85e lib: add missing man reference
+ 9239087 output/dnssim: fix man formatting
+ bd7bee5 fix lua log levels
+ 4083efd output/dnssim: fix doc typo
+ 24c22b8 lib/base64url: add lua bindings
+ 69be2a1 core/log: add errstr() utility function
+ 0c14d74 output/dnssim: improve https2() documentation and behaviour
+ f74e19c output/pcap: log errors when opening output PCAP
+ 6fe699a output/dnssim: cleanup and nitpicks
+ 96db8a9 output/https2: handle max_concurrent_streams similar to nghttp2
+ 15ea609 output/dnssim: https2 - ensure uri authority is always set
+ fad3ed6 output/dnssim: https2 - fix some TODOs
+ 0bee6d8 output/dnssim: https2 - lua documentation
+ e83e010 output/dnssim: https2 - implement GET method
+ b553e0f output/dnssim: https2 - configure method
+ a431a0d contrib: add base64url functions
+ c753097 output/dnssim: https2 - set default concurrent stream limit
+ d49f275 output/dnssim: https2 - track number of open streams
+ 2f7217f output/dnssim: https2 - improve data send edge cases
+ c0abebc output/dnssim: https2 - return correct error code on send failure
+ 5b1f6c3 output/dnssim: conn - avoid assert when tearing down failed connections
+ 5c42266 output/dnssim: exit when file descriptors run out
+ 1ab2ab6 output/dnssim: https2 - additional asserts to detect invalid data
+ 4424eb3 output/dnssim: https2 - check response code
+ 303f2cd output/dnssim: https2 - improve QID mismatch debug msg
+ 86e3761 output/dnssim: https2 - bugfixes
+ 4a52f47 output/dnssim: https2 - use more consistent code style for pointers
+ c8d853e output/dnssim: conn - fix potential memory leak
+ 3e6038b output/dnssim: https2 - enable zero-ing out msgid
+ 712634c output/dnssim: https2 - properly match dnsmsg to query from http request
+ 5abe943 output/dnssim: https2 - free memory on teardown
+ 39a9e9e output/dnssim: https2 - initial implementation
+ 058aee2 output/dnssim: https2 - initialize and setup session
+ 85eb4a3 output/dnssim: https2 - add libnghttp2 dependency
+ 6712bd6 output/dnssim: https2 - add skeleton
+* Thu Jul 23 2020 Jerry Lundström <lundstrom.jerry@gmail.com> 1.0.0-1
+- Release 1.0.0
+* Tue Jun 04 2019 Jerry Lundström <lundstrom.jerry@gmail.com> 0.9.8-1
+- Alpha release 0.9.8
+* Fri Jan 25 2019 Jerry Lundström <lundstrom.jerry@gmail.com> 0.9.7-1
+- Alpha release 0.9.7
+* Wed Aug 01 2018 Jerry Lundström <lundstrom.jerry@gmail.com> 0.9.6-1
+- Alpha release 0.9.6
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..96f8b31
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,280 @@
+# Copyright (c) 2018-2021, OARC, Inc.
+# All rights reserved.
+#
+# This file is part of dnsjit.
+#
+# dnsjit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
+CLEANFILES = *.gcda *.gcno *.gcov
+
+SUBDIRS = test
+
+AM_CFLAGS = -Werror=attributes \
+ -I$(srcdir) \
+ -I$(top_srcdir) \
+ $(SIMD_FLAGS) $(CPUEXT_FLAGS) \
+ $(PTHREAD_CFLAGS) \
+ $(luajit_CFLAGS) \
+ $(libuv_CFLAGS) \
+ $(libnghttp2_CFLAGS)
+
+EXTRA_DIST = gen-manpage.lua gen-compat.lua gen-errno.sh dnsjit.1in
+
+BUILT_SOURCES = core/compat.hh core/log_errstr.c
+
+bin_PROGRAMS = dnsjit
+
+dnsjit_SOURCES = dnsjit.c globals.c
+dist_dnsjit_SOURCES = core.lua lib.lua input.lua filter.lua globals.h \
+ output.lua
+lua_hobjects = core/compat.luaho
+lua_objects = core.luao lib.luao input.luao filter.luao output.luao
+dnsjit_LDADD = $(PTHREAD_LIBS) $(luajit_LIBS) $(libuv_LIBS) $(libnghttp2_LIBS)
+
+# C source and headers
+dnsjit_SOURCES += core/channel.c core/compat.c core/log.c core/object.c core/object/dns.c core/object/ether.c core/object/gre.c core/object/icmp6.c core/object/icmp.c core/object/ieee802.c core/object/ip6.c core/object/ip.c core/object/linuxsll.c core/object/loop.c core/object/null.c core/object/payload.c core/object/pcap.c core/object/tcp.c core/object/udp.c core/producer.c core/receiver.c core/thread.c filter/copy.c filter/ipsplit.c filter/layer.c filter/split.c filter/timing.c input/fpcap.c input/mmpcap.c input/pcap.c input/zero.c lib/base64url.c lib/clock.c lib/trie.c output/dnscli.c output/dnssim.c output/dnssim/common.c output/dnssim/connection.c output/dnssim/https2.c output/dnssim/tcp.c output/dnssim/tls.c output/dnssim/udp.c output/null.c output/pcap.c output/respdiff.c output/tcpcli.c output/tlscli.c output/udpcli.c
+dist_dnsjit_SOURCES += core/assert.h core/channel.h core/compat.h core/log.h core/object/dns.h core/object/ether.h core/object/gre.h core/object.h core/object/icmp6.h core/object/icmp.h core/object/ieee802.h core/object/ip6.h core/object/ip.h core/object/linuxsll.h core/object/loop.h core/object/null.h core/object/payload.h core/object/pcap.h core/object/tcp.h core/object/udp.h core/producer.h core/receiver.h core/thread.h core/timespec.h filter/copy.h filter/ipsplit.h filter/layer.h filter/split.h filter/timing.h input/fpcap.h input/mmpcap.h input/pcap.h input/zero.h lib/base64url.h lib/clock.h lib/trie.h output/dnscli.h output/dnssim.h output/dnssim/internal.h output/dnssim/ll.h output/null.h output/pcap.h output/respdiff.h output/tcpcli.h output/tlscli.h output/udpcli.h
+
+# Lua headers
+dist_dnsjit_SOURCES += core/channel.hh core/log.hh core/object/dns.hh core/object/ether.hh core/object/gre.hh core/object.hh core/object/icmp6.hh core/object/icmp.hh core/object/ieee802.hh core/object/ip6.hh core/object/ip.hh core/object/linuxsll.hh core/object/loop.hh core/object/null.hh core/object/payload.hh core/object/pcap.hh core/object/tcp.hh core/object/udp.hh core/producer.hh core/receiver.hh core/thread.hh core/timespec.hh filter/copy.hh filter/ipsplit.hh filter/layer.hh filter/split.hh filter/timing.hh input/fpcap.hh input/mmpcap.hh input/pcap.hh input/zero.hh lib/base64url.hh lib/clock.hh lib/trie.hh output/dnscli.hh output/dnssim.hh output/null.hh output/pcap.hh output/respdiff.hh output/tcpcli.hh output/tlscli.hh output/udpcli.hh
+lua_hobjects += core/channel.luaho core/log.luaho core/object/dns.luaho core/object/ether.luaho core/object/gre.luaho core/object/icmp6.luaho core/object/icmp.luaho core/object/ieee802.luaho core/object/ip6.luaho core/object/ip.luaho core/object/linuxsll.luaho core/object/loop.luaho core/object.luaho core/object/null.luaho core/object/payload.luaho core/object/pcap.luaho core/object/tcp.luaho core/object/udp.luaho core/producer.luaho core/receiver.luaho core/thread.luaho core/timespec.luaho filter/copy.luaho filter/ipsplit.luaho filter/layer.luaho filter/split.luaho filter/timing.luaho input/fpcap.luaho input/mmpcap.luaho input/pcap.luaho input/zero.luaho lib/base64url.luaho lib/clock.luaho lib/trie.luaho output/dnscli.luaho output/dnssim.luaho output/null.luaho output/pcap.luaho output/respdiff.luaho output/tcpcli.luaho output/tlscli.luaho output/udpcli.luaho
+
+# Lua sources
+dist_dnsjit_SOURCES += core/channel.lua core/compat.lua core/log.lua core/object/dns/label.lua core/object/dns.lua core/object/dns/q.lua core/object/dns/rr.lua core/object/ether.lua core/object/gre.lua core/object/icmp6.lua core/object/icmp.lua core/object/ieee802.lua core/object/ip6.lua core/object/ip.lua core/object/linuxsll.lua core/object/loop.lua core/object.lua core/object/null.lua core/object/payload.lua core/object/pcap.lua core/objects.lua core/object/tcp.lua core/object/udp.lua core/producer.lua core/receiver.lua core/thread.lua core/timespec.lua filter/copy.lua filter/ipsplit.lua filter/layer.lua filter/split.lua filter/timing.lua input/fpcap.lua input/mmpcap.lua input/pcap.lua input/zero.lua lib/base64url.lua lib/clock.lua lib/getopt.lua lib/ip.lua lib/parseconf.lua lib/trie/iter.lua lib/trie.lua lib/trie/node.lua output/dnscli.lua output/dnssim.lua output/null.lua output/pcap.lua output/respdiff.lua output/tcpcli.lua output/tlscli.lua output/udpcli.lua
+lua_objects += core/channel.luao core/compat.luao core/log.luao core/object/dns/label.luao core/object/dns.luao core/object/dns/q.luao core/object/dns/rr.luao core/object/ether.luao core/object/gre.luao core/object/icmp6.luao core/object/icmp.luao core/object/ieee802.luao core/object/ip6.luao core/object/ip.luao core/object/linuxsll.luao core/object/loop.luao core/object.luao core/object/null.luao core/object/payload.luao core/object/pcap.luao core/objects.luao core/object/tcp.luao core/object/udp.luao core/producer.luao core/receiver.luao core/thread.luao core/timespec.luao filter/copy.luao filter/ipsplit.luao filter/layer.luao filter/split.luao filter/timing.luao input/fpcap.luao input/mmpcap.luao input/pcap.luao input/zero.luao lib/base64url.luao lib/clock.luao lib/getopt.luao lib/ip.luao lib/parseconf.luao lib/trie/iter.luao lib/trie.luao lib/trie/node.luao output/dnscli.luao output/dnssim.luao output/null.luao output/pcap.luao output/respdiff.luao output/tcpcli.luao output/tlscli.luao output/udpcli.luao
+
+dnsjit_LDFLAGS = -Wl,-E
+dnsjit_LDADD += $(lua_hobjects) $(lua_objects)
+CLEANFILES += $(lua_hobjects) $(lua_objects)
+
+man1_MANS = dnsjit.1
+CLEANFILES += $(man1_MANS)
+
+man3_MANS = dnsjit.core.3 dnsjit.lib.3 dnsjit.input.3 dnsjit.filter.3 dnsjit.output.3
+man3_MANS += dnsjit.core.channel.3 dnsjit.core.compat.3 dnsjit.core.log.3 dnsjit.core.object.3 dnsjit.core.object.dns.3 dnsjit.core.object.dns.label.3 dnsjit.core.object.dns.q.3 dnsjit.core.object.dns.rr.3 dnsjit.core.object.ether.3 dnsjit.core.object.gre.3 dnsjit.core.object.icmp.3 dnsjit.core.object.icmp6.3 dnsjit.core.object.ieee802.3 dnsjit.core.object.ip.3 dnsjit.core.object.ip6.3 dnsjit.core.object.linuxsll.3 dnsjit.core.object.loop.3 dnsjit.core.object.null.3 dnsjit.core.object.payload.3 dnsjit.core.object.pcap.3 dnsjit.core.objects.3 dnsjit.core.object.tcp.3 dnsjit.core.object.udp.3 dnsjit.core.producer.3 dnsjit.core.receiver.3 dnsjit.core.thread.3 dnsjit.core.timespec.3 dnsjit.filter.copy.3 dnsjit.filter.ipsplit.3 dnsjit.filter.layer.3 dnsjit.filter.split.3 dnsjit.filter.timing.3 dnsjit.input.fpcap.3 dnsjit.input.mmpcap.3 dnsjit.input.pcap.3 dnsjit.input.zero.3 dnsjit.lib.base64url.3 dnsjit.lib.clock.3 dnsjit.lib.getopt.3 dnsjit.lib.ip.3 dnsjit.lib.parseconf.3 dnsjit.lib.trie.3 dnsjit.lib.trie.iter.3 dnsjit.lib.trie.node.3 dnsjit.output.dnscli.3 dnsjit.output.dnssim.3 dnsjit.output.null.3 dnsjit.output.pcap.3 dnsjit.output.respdiff.3 dnsjit.output.tcpcli.3 dnsjit.output.tlscli.3 dnsjit.output.udpcli.3
+CLEANFILES += *.3in $(man3_MANS)
+
+.lua.luao:
+ @mkdir -p `dirname "$@"`
+ $(LUAJIT) -bg -n "dnsjit.`echo \"$@\" | sed 's%\..*%%' | sed 's%/%.%g'`" -t o "$<" "$@"
+
+.luah.luaho:
+ @mkdir -p `dirname "$@"`
+ $(LUAJIT) -bg -n "dnsjit.`echo \"$@\" | sed 's%\..*%%' | sed 's%/%.%g'`_h" -t o "$<" "$@"
+
+.hh.luah:
+ @mkdir -p `dirname "$@"`
+ @echo 'module(...,package.seeall);' > "$@"
+ @cat "$<" | grep '^//lua:' | sed 's%^//lua:%%' >> "$@"
+ @echo 'require("ffi").cdef[[' >> "$@"
+ @cat "$<" | grep -v '^#' >> "$@"
+ @echo ']]' >> "$@"
+
+.1in.1:
+ sed -e 's,[@]PACKAGE_VERSION[@],$(PACKAGE_VERSION),g' \
+ -e 's,[@]PACKAGE_URL[@],$(PACKAGE_URL),g' \
+ -e 's,[@]PACKAGE_BUGREPORT[@],$(PACKAGE_BUGREPORT),g' \
+ < "$<" > "$@"
+
+.3in.3:
+ sed -e 's,[@]PACKAGE_VERSION[@],$(PACKAGE_VERSION),g' \
+ -e 's,[@]PACKAGE_URL[@],$(PACKAGE_URL),g' \
+ -e 's,[@]PACKAGE_BUGREPORT[@],$(PACKAGE_BUGREPORT),g' \
+ < "$<" > "$@"
+
+if ENABLE_GCOV
+gcov-local:
+ for src in $(dnsjit_SOURCES); do \
+ gcov -x -l -r -s "$(srcdir)" "$$src"; \
+ done
+endif
+
+core/compat.hh: gen-compat.lua
+ $(LUAJIT) "$(srcdir)/gen-compat.lua" > "$@"
+
+core/log_errstr.c: gen-errno.sh
+ "$(srcdir)/gen-errno.sh" > "$@"
+
+
+dnsjit.core.3in: core.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core.lua" > "$@"
+
+dnsjit.lib.3in: lib.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/lib.lua" > "$@"
+
+dnsjit.input.3in: input.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/input.lua" > "$@"
+
+dnsjit.filter.3in: filter.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/filter.lua" > "$@"
+
+dnsjit.output.3in: output.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/output.lua" > "$@"
+
+dnsjit.core.channel.3in: core/channel.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/channel.lua" > "$@"
+
+dnsjit.core.compat.3in: core/compat.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/compat.lua" > "$@"
+
+dnsjit.core.log.3in: core/log.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/log.lua" > "$@"
+
+dnsjit.core.object.dns.label.3in: core/object/dns/label.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/object/dns/label.lua" > "$@"
+
+dnsjit.core.object.dns.3in: core/object/dns.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/object/dns.lua" > "$@"
+
+dnsjit.core.object.dns.q.3in: core/object/dns/q.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/object/dns/q.lua" > "$@"
+
+dnsjit.core.object.dns.rr.3in: core/object/dns/rr.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/object/dns/rr.lua" > "$@"
+
+dnsjit.core.object.ether.3in: core/object/ether.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/object/ether.lua" > "$@"
+
+dnsjit.core.object.gre.3in: core/object/gre.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/object/gre.lua" > "$@"
+
+dnsjit.core.object.icmp6.3in: core/object/icmp6.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/object/icmp6.lua" > "$@"
+
+dnsjit.core.object.icmp.3in: core/object/icmp.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/object/icmp.lua" > "$@"
+
+dnsjit.core.object.ieee802.3in: core/object/ieee802.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/object/ieee802.lua" > "$@"
+
+dnsjit.core.object.ip6.3in: core/object/ip6.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/object/ip6.lua" > "$@"
+
+dnsjit.core.object.ip.3in: core/object/ip.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/object/ip.lua" > "$@"
+
+dnsjit.core.object.linuxsll.3in: core/object/linuxsll.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/object/linuxsll.lua" > "$@"
+
+dnsjit.core.object.loop.3in: core/object/loop.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/object/loop.lua" > "$@"
+
+dnsjit.core.object.3in: core/object.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/object.lua" > "$@"
+
+dnsjit.core.object.null.3in: core/object/null.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/object/null.lua" > "$@"
+
+dnsjit.core.object.payload.3in: core/object/payload.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/object/payload.lua" > "$@"
+
+dnsjit.core.object.pcap.3in: core/object/pcap.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/object/pcap.lua" > "$@"
+
+dnsjit.core.objects.3in: core/objects.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/objects.lua" > "$@"
+
+dnsjit.core.object.tcp.3in: core/object/tcp.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/object/tcp.lua" > "$@"
+
+dnsjit.core.object.udp.3in: core/object/udp.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/object/udp.lua" > "$@"
+
+dnsjit.core.producer.3in: core/producer.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/producer.lua" > "$@"
+
+dnsjit.core.receiver.3in: core/receiver.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/receiver.lua" > "$@"
+
+dnsjit.core.thread.3in: core/thread.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/thread.lua" > "$@"
+
+dnsjit.core.timespec.3in: core/timespec.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/core/timespec.lua" > "$@"
+
+dnsjit.filter.copy.3in: filter/copy.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/filter/copy.lua" > "$@"
+
+dnsjit.filter.ipsplit.3in: filter/ipsplit.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/filter/ipsplit.lua" > "$@"
+
+dnsjit.filter.layer.3in: filter/layer.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/filter/layer.lua" > "$@"
+
+dnsjit.filter.split.3in: filter/split.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/filter/split.lua" > "$@"
+
+dnsjit.filter.timing.3in: filter/timing.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/filter/timing.lua" > "$@"
+
+dnsjit.input.fpcap.3in: input/fpcap.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/input/fpcap.lua" > "$@"
+
+dnsjit.input.mmpcap.3in: input/mmpcap.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/input/mmpcap.lua" > "$@"
+
+dnsjit.input.pcap.3in: input/pcap.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/input/pcap.lua" > "$@"
+
+dnsjit.input.zero.3in: input/zero.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/input/zero.lua" > "$@"
+
+dnsjit.lib.base64url.3in: lib/base64url.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/lib/base64url.lua" > "$@"
+
+dnsjit.lib.clock.3in: lib/clock.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/lib/clock.lua" > "$@"
+
+dnsjit.lib.getopt.3in: lib/getopt.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/lib/getopt.lua" > "$@"
+
+dnsjit.lib.ip.3in: lib/ip.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/lib/ip.lua" > "$@"
+
+dnsjit.lib.parseconf.3in: lib/parseconf.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/lib/parseconf.lua" > "$@"
+
+dnsjit.lib.trie.iter.3in: lib/trie/iter.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/lib/trie/iter.lua" > "$@"
+
+dnsjit.lib.trie.3in: lib/trie.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/lib/trie.lua" > "$@"
+
+dnsjit.lib.trie.node.3in: lib/trie/node.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/lib/trie/node.lua" > "$@"
+
+dnsjit.output.dnscli.3in: output/dnscli.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/output/dnscli.lua" > "$@"
+
+dnsjit.output.dnssim.3in: output/dnssim.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/output/dnssim.lua" > "$@"
+
+dnsjit.output.null.3in: output/null.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/output/null.lua" > "$@"
+
+dnsjit.output.pcap.3in: output/pcap.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/output/pcap.lua" > "$@"
+
+dnsjit.output.respdiff.3in: output/respdiff.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/output/respdiff.lua" > "$@"
+
+dnsjit.output.tcpcli.3in: output/tcpcli.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/output/tcpcli.lua" > "$@"
+
+dnsjit.output.tlscli.3in: output/tlscli.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/output/tlscli.lua" > "$@"
+
+dnsjit.output.udpcli.3in: output/udpcli.lua gen-manpage.lua
+ $(LUAJIT) "$(srcdir)/gen-manpage.lua" "$(srcdir)/output/udpcli.lua" > "$@"
diff --git a/src/core.lua b/src/core.lua
new file mode 100644
index 0000000..47723e6
--- /dev/null
+++ b/src/core.lua
@@ -0,0 +1,45 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core
+-- Core modules for dnsjit
+--
+-- Core modules for handling things like logging, DNS messages and
+-- receiver/receive functionality.
+-- .SS Global Variables
+-- The following global variables exists in
+-- .IR dnsjit .
+-- .TP
+-- .B arg
+-- A table with the arguments given on the command line, the first will be
+-- the path to the
+-- .I dnsjit
+-- binary, second will be the path to the
+-- .IR script .
+module(...,package.seeall)
+
+-- dnsjit.core.channel (3),
+-- dnsjit.core.compat (3),
+-- dnsjit.core.log (3),
+-- dnsjit.core.object (3),
+-- dnsjit.core.objects (3),
+-- dnsjit.core.producer (3),
+-- dnsjit.core.receiver (3),
+-- dnsjit.core.thread (3),
+-- dnsjit.core.timespec (3)
+return
diff --git a/src/core/assert.h b/src/core/assert.h
new file mode 100644
index 0000000..3f2f503
--- /dev/null
+++ b/src/core/assert.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __dnsjit_core_assert_h
+#define __dnsjit_core_assert_h
+
+#include "core/log.h"
+
+#define mlassert_self() \
+ if (!self) \
+ core_log_fatal(&_log, __FILE__, __LINE__, "self is nil")
+#define glassert_self() \
+ if (!self) \
+ core_log_fatal(0, __FILE__, __LINE__, "self is nil")
+
+#define lassert(expression, msg...) \
+ if (!(expression)) \
+ core_log_fatal(&self->_log, __FILE__, __LINE__, msg)
+#define lpassert(expression, msg...) \
+ if (!(expression)) \
+ core_log_fatal(self->_log, __FILE__, __LINE__, msg)
+#define mlassert(expression, msg...) \
+ if (!(expression)) \
+ core_log_fatal(&_log, __FILE__, __LINE__, msg)
+#define glassert(expression, msg...) \
+ if (!(expression)) \
+ core_log_fatal(0, __FILE__, __LINE__, msg)
+
+#endif
diff --git a/src/core/channel.c b/src/core/channel.c
new file mode 100644
index 0000000..691ad1a
--- /dev/null
+++ b/src/core/channel.c
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "core/channel.h"
+#include "core/assert.h"
+
+#include <sched.h>
+
+static core_log_t _log = LOG_T_INIT("core.channel");
+static core_channel_t _defaults = {
+ LOG_T_INIT_OBJ("core.channel"),
+ 0, { 0 }, 0, 0,
+ 0, 0
+};
+
+core_log_t* core_channel_log()
+{
+ return &_log;
+}
+
+static inline bool _is_pow2(size_t num)
+{
+ while (num != 1) {
+ if (num % 2 != 0)
+ return false;
+ num = num / 2;
+ }
+ return true;
+}
+
+void core_channel_init(core_channel_t* self, size_t capacity)
+{
+ mlassert_self();
+ if (capacity < 4 || !_is_pow2(capacity)) {
+ mlfatal("invalid capacity");
+ }
+
+ *self = _defaults;
+ self->capacity = capacity;
+
+ lfatal_oom(self->ring_buf = malloc(sizeof(ck_ring_buffer_t) * capacity));
+ ck_ring_init(&self->ring, capacity);
+}
+
+void core_channel_destroy(core_channel_t* self)
+{
+ mlassert_self();
+ free(self->ring_buf);
+}
+
+void core_channel_put(core_channel_t* self, const void* obj)
+{
+ mlassert_self();
+ lassert(self->ring_buf, "ring_buf is nil");
+
+ while (!ck_ring_enqueue_spsc(&self->ring, self->ring_buf, (void*)obj)) {
+ sched_yield();
+ }
+}
+
+int core_channel_try_put(core_channel_t* self, const void* obj)
+{
+ mlassert_self();
+ lassert(self->ring_buf, "ring_buf is nil");
+
+ if (!ck_ring_enqueue_spsc(&self->ring, self->ring_buf, (void*)obj)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+void* core_channel_get(core_channel_t* self)
+{
+ void* obj = 0;
+ mlassert_self();
+ lassert(self->ring_buf, "ring_buf is nil");
+
+ while (!ck_ring_dequeue_spsc(&self->ring, self->ring_buf, &obj)) {
+ sched_yield();
+ if (ck_pr_load_int(&self->closed)) {
+ linfo("channel closed");
+ return 0;
+ }
+ }
+
+ return obj;
+}
+
+void* core_channel_try_get(core_channel_t* self)
+{
+ void* obj = 0;
+ mlassert_self();
+ lassert(self->ring_buf, "ring_buf is nil");
+
+ if (!ck_ring_dequeue_spsc(&self->ring, self->ring_buf, &obj)) {
+ return 0;
+ }
+
+ return obj;
+}
+
+int core_channel_size(core_channel_t* self)
+{
+ mlassert_self();
+ return ck_ring_size(&self->ring);
+}
+
+bool core_channel_full(core_channel_t* self)
+{
+ mlassert_self();
+
+ /* ck_ring can only hold capacity minus one enties at a time */
+ if (ck_ring_size(&self->ring) < (self->capacity - 1)) {
+ return false;
+ }
+ return true;
+}
+
+void core_channel_close(core_channel_t* self)
+{
+ mlassert_self();
+ ck_pr_store_int(&self->closed, 1);
+}
+
+core_receiver_t core_channel_receiver()
+{
+ return (core_receiver_t)core_channel_put;
+}
+
+void core_channel_run(core_channel_t* self)
+{
+ void* obj = 0;
+ mlassert_self();
+ lassert(self->ring_buf, "ring_buf is nil");
+ if (!self->recv) {
+ lfatal("no receiver set");
+ }
+
+ for (;;) {
+ while (!ck_ring_dequeue_spsc(&self->ring, self->ring_buf, &obj)) {
+ sched_yield();
+ if (ck_pr_load_int(&self->closed)) {
+ linfo("channel closed");
+ return;
+ }
+ }
+ self->recv(self->ctx, obj);
+ }
+}
diff --git a/src/core/channel.h b/src/core/channel.h
new file mode 100644
index 0000000..75e84ac
--- /dev/null
+++ b/src/core/channel.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/log.h"
+#include "core/receiver.h"
+
+#ifndef __dnsjit_core_channel_h
+#define __dnsjit_core_channel_h
+
+#if defined(__GNUC__) || defined(__SUNPRO_C)
+#include "gcc/ck_cc.h"
+#ifdef CK_CC_RESTRICT
+#undef CK_CC_RESTRICT
+#define CK_CC_RESTRICT __restrict__
+#endif
+#endif
+
+#include <ck_ring.h>
+#include <ck_pr.h>
+#include <stdbool.h>
+
+#include "core/channel.hh"
+
+#endif
diff --git a/src/core/channel.hh b/src/core/channel.hh
new file mode 100644
index 0000000..e6ec0e0
--- /dev/null
+++ b/src/core/channel.hh
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.compat_h")
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+
+typedef struct core_channel {
+ core_log_t _log;
+ ck_ring_buffer_t* ring_buf;
+ ck_ring_t ring;
+ int closed;
+ size_t capacity;
+
+ core_receiver_t recv;
+ void* ctx;
+} core_channel_t;
+
+core_log_t* core_channel_log();
+
+void core_channel_init(core_channel_t* self, size_t capacity);
+void core_channel_destroy(core_channel_t* self);
+void core_channel_put(core_channel_t* self, const void* obj);
+int core_channel_try_put(core_channel_t* self, const void* obj);
+void* core_channel_get(core_channel_t* self);
+void* core_channel_try_get(core_channel_t* self);
+int core_channel_size(core_channel_t* self);
+bool core_channel_full(core_channel_t* self);
+void core_channel_close(core_channel_t* self);
+
+core_receiver_t core_channel_receiver();
+void core_channel_run(core_channel_t* self);
diff --git a/src/core/channel.lua b/src/core/channel.lua
new file mode 100644
index 0000000..b837cc4
--- /dev/null
+++ b/src/core/channel.lua
@@ -0,0 +1,142 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.channel
+-- Send data to another thread
+-- local chan = require("dnsjit.core.channel").new()
+-- local thr = require("dnsjit.core.thread").new()
+-- thr:start(function(thr)
+-- local chan = thr:pop()
+-- local obj = chan:get()
+-- ...
+-- end)
+-- thr:push(chan)
+-- chan:put(...)
+-- chan:close()
+-- thr:stop()
+--
+-- A channel can be used to send data to another thread, this is done by
+-- putting a pointer to the data into a wait-free and lock-free ring buffer
+-- (concurrency kit).
+-- The channel uses the single producer, single consumer model (SPSC) so
+-- there can only be one writer and one reader.
+-- .SS Attributes
+-- .TP
+-- int closed
+-- Is 1 if the channel has been closed.
+module(...,package.seeall)
+
+require("dnsjit.core.channel_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "core_channel_t"
+local core_channel_t
+local Channel = {}
+
+-- Create a new Channel, use the optional
+-- .I capacity
+-- to specify the capacity of the channel (buffer).
+-- Capacity must be a power-of-two greater than or equal to 4.
+-- Default capacity is 2048.
+function Channel.new(capacity)
+ if capacity == nil then
+ capacity = 2048
+ end
+ local self = core_channel_t()
+ C.core_channel_init(self, capacity)
+ ffi.gc(self, C.core_channel_destroy)
+ return self
+end
+
+-- Return the Log object to control logging of this instance or module.
+function Channel:log()
+ if self == nil then
+ return C.core_channel_log()
+ end
+ return self._log
+end
+
+-- Return information to use when sharing this object between threads.
+function Channel:share()
+ return ffi.cast("void*", self), t_name.."*", "dnsjit.core.channel"
+end
+
+-- Put an object into the channel, if the channel is full then it will
+-- stall and wait until space becomes available.
+-- Object may be nil.
+function Channel:put(obj)
+ C.core_channel_put(self, obj)
+end
+
+-- Try and put an object into the channel.
+-- Returns 0 on success.
+function Channel:put(obj)
+ C.core_channel_try_put(self, obj)
+end
+
+-- Get an object from the channel, if the channel is empty it will wait until
+-- an object is available.
+-- Returns nil if the channel is closed or if a nil object was explicitly put
+-- into the channel.
+function Channel:get()
+ return C.core_channel_get(self)
+end
+
+-- Try and get an object from the channel.
+-- Returns nil if there was no objects to get.
+function Channel:try_get()
+ return C.core_channel_try_get(self)
+end
+
+-- Return number of enqueued objects.
+function Channel:size()
+ return C.core_channel_size(self)
+end
+
+-- Returns true when channel is full.
+function Channel:full()
+ return C.core_channel_full(self)
+end
+
+-- Close the channel.
+function Channel:close()
+ C.core_channel_close(self)
+end
+
+-- Return the C functions and context for receiving objects.
+function Channel:receive()
+ return C.core_channel_receiver(), self
+end
+
+-- Set the receiver to pass objects to.
+-- NOTE; The channel keeps no reference of the receiver, it needs to live as
+-- long as the channel does.
+function Channel:receiver(o)
+ self.recv, self.ctx = o:receive()
+end
+
+-- Retrieve all objects from the channel and send it to the receiver.
+function Channel:run()
+ C.core_channel_run(self)
+end
+
+core_channel_t = ffi.metatype(t_name, { __index = Channel })
+
+-- dnsjit.core.thread (3)
+return Channel
diff --git a/src/core/compat.c b/src/core/compat.c
new file mode 100644
index 0000000..9f2be98
--- /dev/null
+++ b/src/core/compat.c
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdint.h>
+
+#include "core/compat.hh"
diff --git a/src/core/compat.h b/src/core/compat.h
new file mode 100644
index 0000000..c9486c3
--- /dev/null
+++ b/src/core/compat.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __dnsjit_core_compat_h
+#define __dnsjit_core_compat_h
+
+#include <unistd.h>
+
+#ifdef __ssize_t_defined
+typedef ssize_t luajit_ssize_t;
+#else
+typedef long luajit_ssize_t;
+#endif
+
+#endif
diff --git a/src/core/compat.lua b/src/core/compat.lua
new file mode 100644
index 0000000..bb6d45c
--- /dev/null
+++ b/src/core/compat.lua
@@ -0,0 +1,41 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.compat
+-- Cross platform compatibility support
+-- require("dnsjit.core.compat_h")
+--
+-- This module defines various system structures so they can be exposed into
+-- Lua but not really used. The size is generated at compile time to match
+-- that of the structures on the platform.
+-- .SS Structures
+-- .TP
+-- pthread_t
+-- .TP
+-- pthread_cond_t
+-- .TP
+-- pthread_mutex_t
+-- .TP
+-- struct sockaddr_storage
+-- .TP
+-- ck_ring_t
+-- .TP
+-- ck_ring_buffer_t
+module(...,package.seeall)
+
+require("dnsjit.core.compat_h")
diff --git a/src/core/log.c b/src/core/log.c
new file mode 100644
index 0000000..a334e8e
--- /dev/null
+++ b/src/core/log.c
@@ -0,0 +1,399 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "core/log.h"
+
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+static core_log_t _log = LOG_T_INIT("core");
+
+void core_log_debug(const core_log_t* l, const char* file, size_t line, const char* msg, ...)
+{
+ char buf[512];
+ va_list ap;
+ if (!l) {
+ if (_log.settings.debug != 3) {
+ return;
+ }
+ } else {
+ if (l->settings.debug) {
+ if (l->settings.debug != 3) {
+ return;
+ }
+ } else if (l->module && l->module->debug) {
+ if (l->module->debug != 3) {
+ return;
+ }
+ } else if (_log.settings.debug != 3) {
+ return;
+ }
+ }
+ va_start(ap, msg);
+ vsnprintf(buf, sizeof(buf), msg, ap);
+ va_end(ap);
+ buf[sizeof(buf) - 1] = 0;
+ for (;;) {
+ if (!l) {
+ if (_log.settings.display_file_line != 3) {
+ break;
+ }
+ } else {
+ if (l->settings.display_file_line) {
+ if (l->settings.display_file_line != 3) {
+ break;
+ }
+ } else if (l->module && l->module->display_file_line) {
+ if (l->module->display_file_line != 3) {
+ break;
+ }
+ } else if (_log.settings.display_file_line != 3) {
+ break;
+ }
+ }
+ if (l) {
+ if (l->is_obj) {
+ fprintf(stderr, "%s[%zu] %s[%p] debug: %s\n", file, line, l->name, l, buf);
+ return;
+ }
+ fprintf(stderr, "%s[%zu] %s debug: %s\n", file, line, l->name, buf);
+ return;
+ }
+ fprintf(stderr, "%s[%zu] %s debug: %s\n", file, line, _log.name, buf);
+ return;
+ }
+
+ if (l) {
+ if (l->is_obj) {
+ fprintf(stderr, "%s[%p] debug: %s\n", l->name, l, buf);
+ return;
+ }
+ fprintf(stderr, "%s debug: %s\n", l->name, buf);
+ return;
+ }
+ fprintf(stderr, "%s debug: %s\n", _log.name, buf);
+}
+
+void core_log_info(const core_log_t* l, const char* file, size_t line, const char* msg, ...)
+{
+ char buf[512];
+ va_list ap;
+ if (!l) {
+ if (_log.settings.info != 3) {
+ return;
+ }
+ } else {
+ if (l->settings.info) {
+ if (l->settings.info != 3) {
+ return;
+ }
+ } else if (l->module && l->module->info) {
+ if (l->module->info != 3) {
+ return;
+ }
+ } else if (_log.settings.info != 3) {
+ return;
+ }
+ }
+ va_start(ap, msg);
+ vsnprintf(buf, sizeof(buf), msg, ap);
+ va_end(ap);
+ buf[sizeof(buf) - 1] = 0;
+ for (;;) {
+ if (!l) {
+ if (_log.settings.display_file_line != 3) {
+ break;
+ }
+ } else {
+ if (l->settings.display_file_line) {
+ if (l->settings.display_file_line != 3) {
+ break;
+ }
+ } else if (l->module && l->module->display_file_line) {
+ if (l->module->display_file_line != 3) {
+ break;
+ }
+ } else if (_log.settings.display_file_line != 3) {
+ break;
+ }
+ }
+ if (l) {
+ if (l->is_obj) {
+ fprintf(stderr, "%s[%zu] %s[%p] info: %s\n", file, line, l->name, l, buf);
+ return;
+ }
+ fprintf(stderr, "%s[%zu] %s info: %s\n", file, line, l->name, buf);
+ return;
+ }
+ fprintf(stderr, "%s[%zu] %s info: %s\n", file, line, _log.name, buf);
+ return;
+ }
+
+ if (l) {
+ if (l->is_obj) {
+ fprintf(stderr, "%s[%p] info: %s\n", l->name, l, buf);
+ return;
+ }
+ fprintf(stderr, "%s info: %s\n", l->name, buf);
+ return;
+ }
+ fprintf(stderr, "%s info: %s\n", _log.name, buf);
+}
+
+void core_log_notice(const core_log_t* l, const char* file, size_t line, const char* msg, ...)
+{
+ char buf[512];
+ va_list ap;
+ if (!l) {
+ if (_log.settings.notice != 3) {
+ return;
+ }
+ } else {
+ if (l->settings.notice) {
+ if (l->settings.notice != 3) {
+ return;
+ }
+ } else if (l->module && l->module->notice) {
+ if (l->module->notice != 3) {
+ return;
+ }
+ } else if (_log.settings.notice != 3) {
+ return;
+ }
+ }
+ va_start(ap, msg);
+ vsnprintf(buf, sizeof(buf), msg, ap);
+ va_end(ap);
+ buf[sizeof(buf) - 1] = 0;
+ for (;;) {
+ if (!l) {
+ if (_log.settings.display_file_line != 3) {
+ break;
+ }
+ } else {
+ if (l->settings.display_file_line) {
+ if (l->settings.display_file_line != 3) {
+ break;
+ }
+ } else if (l->module && l->module->display_file_line) {
+ if (l->module->display_file_line != 3) {
+ break;
+ }
+ } else if (_log.settings.display_file_line != 3) {
+ break;
+ }
+ }
+ if (l) {
+ if (l->is_obj) {
+ fprintf(stderr, "%s[%zu] %s[%p] notice: %s\n", file, line, l->name, l, buf);
+ return;
+ }
+ fprintf(stderr, "%s[%zu] %s notice: %s\n", file, line, l->name, buf);
+ return;
+ }
+ fprintf(stderr, "%s[%zu] %s notice: %s\n", file, line, _log.name, buf);
+ return;
+ }
+
+ if (l) {
+ if (l->is_obj) {
+ fprintf(stderr, "%s[%p] notice: %s\n", l->name, l, buf);
+ return;
+ }
+ fprintf(stderr, "%s notice: %s\n", l->name, buf);
+ return;
+ }
+ fprintf(stderr, "%s notice: %s\n", _log.name, buf);
+}
+
+void core_log_warning(const core_log_t* l, const char* file, size_t line, const char* msg, ...)
+{
+ char buf[512];
+ va_list ap;
+ if (!l) {
+ if (_log.settings.warning != 3) {
+ return;
+ }
+ } else {
+ if (l->settings.warning) {
+ if (l->settings.warning != 3) {
+ return;
+ }
+ } else if (l->module && l->module->warning) {
+ if (l->module->warning != 3) {
+ return;
+ }
+ } else if (_log.settings.warning != 3) {
+ return;
+ }
+ }
+ va_start(ap, msg);
+ vsnprintf(buf, sizeof(buf), msg, ap);
+ va_end(ap);
+ buf[sizeof(buf) - 1] = 0;
+ for (;;) {
+ if (!l) {
+ if (_log.settings.display_file_line != 3) {
+ break;
+ }
+ } else {
+ if (l->settings.display_file_line) {
+ if (l->settings.display_file_line != 3) {
+ break;
+ }
+ } else if (l->module && l->module->display_file_line) {
+ if (l->module->display_file_line != 3) {
+ break;
+ }
+ } else if (_log.settings.display_file_line != 3) {
+ break;
+ }
+ }
+ if (l) {
+ if (l->is_obj) {
+ fprintf(stderr, "%s[%zu] %s[%p] warning: %s\n", file, line, l->name, l, buf);
+ return;
+ }
+ fprintf(stderr, "%s[%zu] %s warning: %s\n", file, line, l->name, buf);
+ return;
+ }
+ fprintf(stderr, "%s[%zu] %s warning: %s\n", file, line, _log.name, buf);
+ return;
+ }
+
+ if (l) {
+ if (l->is_obj) {
+ fprintf(stderr, "%s[%p] warning: %s\n", l->name, l, buf);
+ return;
+ }
+ fprintf(stderr, "%s warning: %s\n", l->name, buf);
+ return;
+ }
+ fprintf(stderr, "%s warning: %s\n", _log.name, buf);
+}
+
+void core_log_critical(const core_log_t* l, const char* file, size_t line, const char* msg, ...)
+{
+ char buf[512];
+ va_list ap;
+ va_start(ap, msg);
+ vsnprintf(buf, sizeof(buf), msg, ap);
+ va_end(ap);
+ buf[sizeof(buf) - 1] = 0;
+ for (;;) {
+ if (!l) {
+ if (_log.settings.display_file_line != 3) {
+ break;
+ }
+ } else {
+ if (l->settings.display_file_line) {
+ if (l->settings.display_file_line != 3) {
+ break;
+ }
+ } else if (l->module && l->module->display_file_line) {
+ if (l->module->display_file_line != 3) {
+ break;
+ }
+ } else if (_log.settings.display_file_line != 3) {
+ break;
+ }
+ }
+ if (l) {
+ if (l->is_obj) {
+ fprintf(stderr, "%s[%zu] %s[%p] critical: %s\n", file, line, l->name, l, buf);
+ return;
+ }
+ fprintf(stderr, "%s[%zu] %s critical: %s\n", file, line, l->name, buf);
+ return;
+ }
+ fprintf(stderr, "%s[%zu] %s critical: %s\n", file, line, _log.name, buf);
+ return;
+ }
+
+ if (l) {
+ if (l->is_obj) {
+ fprintf(stderr, "%s[%p] critical: %s\n", l->name, l, buf);
+ return;
+ }
+ fprintf(stderr, "%s critical: %s\n", l->name, buf);
+ return;
+ }
+ fprintf(stderr, "%s critical: %s\n", _log.name, buf);
+}
+
+void core_log_fatal(const core_log_t* l, const char* file, size_t line, const char* msg, ...)
+{
+ char buf[512];
+ va_list ap;
+ va_start(ap, msg);
+ vsnprintf(buf, sizeof(buf), msg, ap);
+ va_end(ap);
+ buf[sizeof(buf) - 1] = 0;
+ for (;;) {
+ if (!l) {
+ if (_log.settings.display_file_line != 3) {
+ break;
+ }
+ } else {
+ if (l->settings.display_file_line) {
+ if (l->settings.display_file_line != 3) {
+ break;
+ }
+ } else if (l->module && l->module->display_file_line) {
+ if (l->module->display_file_line != 3) {
+ break;
+ }
+ } else if (_log.settings.display_file_line != 3) {
+ break;
+ }
+ }
+ if (l) {
+ if (l->is_obj) {
+ fprintf(stderr, "%s[%zu] %s[%p] fatal: %s\n", file, line, l->name, l, buf);
+ exit(1);
+ }
+ fprintf(stderr, "%s[%zu] %s fatal: %s\n", file, line, l->name, buf);
+ exit(1);
+ }
+ fprintf(stderr, "%s[%zu] %s fatal: %s\n", file, line, _log.name, buf);
+ exit(1);
+ }
+
+ if (l) {
+ if (l->is_obj) {
+ fprintf(stderr, "%s[%p] fatal: %s\n", l->name, l, buf);
+ exit(1);
+ }
+ fprintf(stderr, "%s fatal: %s\n", l->name, buf);
+ exit(1);
+ }
+ fprintf(stderr, "%s fatal: %s\n", _log.name, buf);
+ exit(1);
+}
+
+core_log_t* core_log_log()
+{
+ return &_log;
+}
+
+#include "core/log_errstr.c"
diff --git a/src/core/log.h b/src/core/log.h
new file mode 100644
index 0000000..d17c733
--- /dev/null
+++ b/src/core/log.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __dnsjit_core_log_h
+#define __dnsjit_core_log_h
+
+#include <stdlib.h>
+#include <errno.h>
+#include <stdint.h>
+
+#define LOG_SETTINGS_T_INIT \
+ { \
+ 0, 0, 0, 0, 0 \
+ }
+#define LOG_T_INIT(name) \
+ { \
+ name, 0, LOG_SETTINGS_T_INIT, 0 \
+ }
+#define LOG_T_INIT_OBJ(name) \
+ { \
+ name, 1, LOG_SETTINGS_T_INIT, &_log.settings \
+ }
+
+#include "core/log.hh"
+
+#ifdef DNSJIT_NO_LOGGING
+#define ldebug(msg...)
+#define linfo(msg...)
+#define lnotice(msg...)
+#define lwarning(msg...)
+#define lcritical(msg...)
+#define lpdebug(msg...)
+#define lpinfo(msg...)
+#define lpnotice(msg...)
+#define lpwarning(msg...)
+#define lpcritical(msg...)
+#define mldebug(msg...)
+#define mlinfo(msg...)
+#define mlnotice(msg...)
+#define mlwarning(msg...)
+#define mlcritical(msg...)
+#define gldebug(msg...)
+#define glinfo(msg...)
+#define glnotice(msg...)
+#define glwarning(msg...)
+#define glcritical(msg...)
+#else
+#define ldebug(msg...) core_log_debug(&self->_log, __FILE__, __LINE__, msg)
+#define linfo(msg...) core_log_info(&self->_log, __FILE__, __LINE__, msg)
+#define lnotice(msg...) core_log_notice(&self->_log, __FILE__, __LINE__, msg)
+#define lwarning(msg...) core_log_warning(&self->_log, __FILE__, __LINE__, msg)
+#define lcritical(msg...) core_log_critical(&self->_log, __FILE__, __LINE__, msg)
+#define lpdebug(msg...) core_log_debug(self->_log, __FILE__, __LINE__, msg)
+#define lpinfo(msg...) core_log_info(self->_log, __FILE__, __LINE__, msg)
+#define lpnotice(msg...) core_log_notice(self->_log, __FILE__, __LINE__, msg)
+#define lpwarning(msg...) core_log_warning(self->_log, __FILE__, __LINE__, msg)
+#define lpcritical(msg...) core_log_critical(self->_log, __FILE__, __LINE__, msg)
+#define mldebug(msg...) core_log_debug(&_log, __FILE__, __LINE__, msg)
+#define mlinfo(msg...) core_log_info(&_log, __FILE__, __LINE__, msg)
+#define mlnotice(msg...) core_log_notice(&_log, __FILE__, __LINE__, msg)
+#define mlwarning(msg...) core_log_warning(&_log, __FILE__, __LINE__, msg)
+#define mlcritical(msg...) core_log_critical(&_log, __FILE__, __LINE__, msg)
+#define gldebug(msg...) core_log_debug(0, __FILE__, __LINE__, msg)
+#define glinfo(msg...) core_log_info(0, __FILE__, __LINE__, msg)
+#define glnotice(msg...) core_log_notice(0, __FILE__, __LINE__, msg)
+#define glwarning(msg...) core_log_warning(0, __FILE__, __LINE__, msg)
+#define glcritical(msg...) core_log_critical(0, __FILE__, __LINE__, msg)
+#endif
+
+#define lfatal(msg...) core_log_fatal(&self->_log, __FILE__, __LINE__, msg)
+#define lpfatal(msg...) core_log_fatal(self->_log, __FILE__, __LINE__, msg)
+#define mlfatal(msg...) core_log_fatal(&_log, __FILE__, __LINE__, msg)
+#define glfatal(msg...) core_log_fatal(0, __FILE__, __LINE__, msg)
+
+#define lfatal_oom(expression) \
+ if (!(expression)) \
+ core_log_fatal(&self->_log, __FILE__, __LINE__, "out of memory")
+#define lpfatal_oom(expression) \
+ if (!(expression)) \
+ core_log_fatal(self->_log, __FILE__, __LINE__, "out of memory")
+#define mlfatal_oom(expression) \
+ if (!(expression)) \
+ core_log_fatal(&_log, __FILE__, __LINE__, "out of memory")
+#define glfatal_oom(expression) \
+ if (!(expression)) \
+ core_log_fatal(0, __FILE__, __LINE__, "out of memory")
+
+#endif
diff --git a/src/core/log.hh b/src/core/log.hh
new file mode 100644
index 0000000..7273879
--- /dev/null
+++ b/src/core/log.hh
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+typedef struct core_log_settings {
+ uint8_t debug;
+ uint8_t info;
+ uint8_t notice;
+ uint8_t warning;
+ uint8_t display_file_line;
+} core_log_settings_t;
+
+typedef struct core_log {
+ char name[32];
+ uint8_t is_obj;
+ core_log_settings_t settings;
+ const core_log_settings_t* module;
+} core_log_t;
+
+void core_log_debug(const core_log_t* l, const char* file, size_t line, const char* msg, ...);
+void core_log_info(const core_log_t* l, const char* file, size_t line, const char* msg, ...);
+void core_log_notice(const core_log_t* l, const char* file, size_t line, const char* msg, ...);
+void core_log_warning(const core_log_t* l, const char* file, size_t line, const char* msg, ...);
+void core_log_critical(const core_log_t* l, const char* file, size_t line, const char* msg, ...);
+void core_log_fatal(const core_log_t* l, const char* file, size_t line, const char* msg, ...);
+const char* core_log_errstr(int err);
+
+core_log_t* core_log_log();
diff --git a/src/core/log.lua b/src/core/log.lua
new file mode 100644
index 0000000..d62cbbc
--- /dev/null
+++ b/src/core/log.lua
@@ -0,0 +1,639 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.log
+-- Core logging facility
+-- .SS Usage to control global log level
+-- local log = require("dnsjit.core.log")
+-- log.enable("all")
+-- log.disable("debug")
+-- .SS Usage to control module log level
+-- local example = require("example") -- Example as below
+-- example.log():enable("all")
+-- example.log():disable("debug")
+-- .SS Usage to control object instance log level
+-- local example = require("example") -- Example as below
+-- local obj = example.new()
+-- obj:log():enable("all")
+-- obj:log():disable("debug")
+-- .SS Usage in C module
+-- .B NOTE
+-- naming of variables and module only globals are required to exactly as
+-- described in order for the macros to work;
+-- .B self
+-- is the pointer to the object instance,
+-- .B self->_log
+-- is the object instance logging configuration struct,
+-- .B _log
+-- is the module logging configuration struct.
+-- .LP
+-- Include logging:
+-- #include "core/log.h"
+-- .LP
+-- Add the logging struct to the module struct:
+-- typedef struct example {
+-- core_log_t _log;
+-- ...
+-- } example_t;
+-- .LP
+-- Add a module logging configuration and a struct default:
+-- static core_log_t _log = LOG_T_INIT("example");
+-- static example_t _defaults = {
+-- LOG_T_INIT_OBJ("example"),
+-- ...
+-- };
+-- .LP
+-- Use new/free and/or init/destroy functions (depends if you create the
+-- object in Lua or not):
+-- example_t* example_new() {
+-- example_t* self = calloc(1, sizeof(example_t));
+-- .
+-- *self = _defaults;
+-- ldebug("new()");
+-- .
+-- return self;
+-- }
+-- .
+-- void example_free(example_t* self) {
+-- ldebug("free()");
+-- free(self);
+-- }
+-- .
+-- int example_init(example_t* self) {
+-- *self = _defaults;
+-- .
+-- ldebug("init()");
+-- .
+-- return 0;
+-- }
+-- .
+-- void example_destroy(example_t* self) {
+-- ldebug("destroy()");
+-- ...
+-- }
+-- .LP
+-- In the Lua part of the C module you need to create a function that
+-- returns either the object instance Log or the modules Log.
+-- .LP
+-- Add C function to get module only Log:
+-- core_log_t* example_log() {
+-- return &_log;
+-- }
+-- .LP
+-- For the structures metatable add the following function:
+-- local ffi = require("ffi")
+-- local C = ffi.C
+-- .
+-- function Example:log()
+-- if self == nil then
+-- return C.example_log()
+-- end
+-- return self._log
+-- end
+-- .SS Usage in pure Lua module
+-- local log = require("dnsjit.core.log")
+-- local ffi = require("ffi")
+-- local C = ffi.C
+-- .
+-- local Example = {}
+-- local module_log = log.new("example")
+-- .
+-- function Example.new()
+-- local self = setmetatable({
+-- _log = log.new("example", module_log),
+-- }, { __index = Example })
+-- .
+-- self._log:debug("new()")
+-- .
+-- return self
+-- end
+-- .
+-- function Example:log()
+-- if self == nil then
+-- return module_log
+-- end
+-- return self._log
+-- end
+--
+-- Core logging facility used by all modules.
+-- .SS Log levels
+-- .TP
+-- all
+-- Keyword to enable/disable all changeable log levels.
+-- .TP
+-- debug
+-- Used for debug information.
+-- .TP
+-- info
+-- Used for informational processing messages.
+-- .TP
+-- notice
+-- Used for messages of that may have impact on processing.
+-- .TP
+-- warning
+-- Used for messages that has impact on processing.
+-- .TP
+-- critical
+-- Used for messages that have severe impact on processing, this level can
+-- not be disabled.
+-- .TP
+-- fatal
+-- Used to display a message before stopping all processing and existing,
+-- this level can not be disabled.
+-- .SS C macros
+-- .TP
+-- Object instance macros
+-- The following macros uses
+-- .IR &self->_log :
+-- .BR ldebug(msg...) ,
+-- .BR linfo(msg...) ,
+-- .BR lnotice(msg...) ,
+-- .BR lwarning(msg...) ,
+-- .BR lcritical(msg...) ,
+-- .BR lfatal(msg...) .
+-- .TP
+-- Object pointer instance macros
+-- The following macros uses
+-- .IR self->_log :
+-- .BR lpdebug(msg...) ,
+-- .BR lpinfo(msg...) ,
+-- .BR lpnotice(msg...) ,
+-- .BR lpwarning(msg...) ,
+-- .BR lpcritical(msg...) ,
+-- .BR lpfatal(msg...) .
+-- .TP
+-- Module macros
+-- The following macros uses
+-- .IR &_log :
+-- .BR mldebug(msg...) ,
+-- .BR mlinfo(msg...) ,
+-- .BR mlnotice(msg...) ,
+-- .BR mlwarning(msg...) ,
+-- .BR mlcritical(msg...) ,
+-- .BR mlfatal(msg...) .
+-- .TP
+-- Global macros
+-- The following macros uses the global logging configuration:
+-- .BR gldebug(msg...) ,
+-- .BR glinfo(msg...) ,
+-- .BR glnotice(msg...) ,
+-- .BR glwarning(msg...) ,
+-- .BR glcritical(msg...) ,
+-- .BR glfatal(msg...) .
+module(...,package.seeall)
+
+require("dnsjit.core.log_h")
+local ffi = require("ffi")
+local C = ffi.C
+local L = C.core_log_log()
+
+local t_name = "core_log_t"
+local core_log_t
+local Log = {}
+
+-- Create a new Log object with the given module
+-- .I name
+-- and an optional shared
+-- .I module
+-- Log object.
+function Log.new(name, module)
+ local self
+ if ffi.istype(t_name, module) then
+ self = core_log_t({ is_obj = 1, module = module.settings })
+ else
+ self = core_log_t()
+ end
+
+ local len = #name
+ if len > 31 then
+ len = 31
+ end
+ ffi.copy(self.name, name, len)
+ self.name[len] = 0
+
+ return self
+end
+
+-- Enable specified log level.
+function Log:enable(level)
+ if not ffi.istype(t_name, self) then
+ level = self
+ self = L
+ end
+ if level == "all" then
+ self.settings.debug = 3
+ self.settings.info = 3
+ self.settings.notice = 3
+ self.settings.warning = 3
+ elseif level == "debug" then
+ self.settings.debug = 3
+ elseif level == "info" then
+ self.settings.info = 3
+ elseif level == "notice" then
+ self.settings.notice = 3
+ elseif level == "warning" then
+ self.settings.warning = 3
+ else
+ error("invalid log level: "..level)
+ end
+end
+
+-- Disable specified log level.
+function Log:disable(level)
+ if not ffi.istype(t_name, self) then
+ level = self
+ self = L
+ end
+ if level == "all" then
+ self.settings.debug = 2
+ self.settings.info = 2
+ self.settings.notice = 2
+ self.settings.warning = 2
+ elseif level == "debug" then
+ self.settings.debug = 2
+ elseif level == "info" then
+ self.settings.info = 2
+ elseif level == "notice" then
+ self.settings.notice = 2
+ elseif level == "warning" then
+ self.settings.warning = 2
+ else
+ error("invalid log level: "..level)
+ end
+end
+
+-- Clear specified log level, which means it will revert back to default
+-- or inherited settings.
+function Log:clear(level)
+ if not ffi.istype(t_name, self) then
+ level = self
+ self = L
+ end
+ if level == "all" then
+ self.settings.debug = 0
+ self.settings.info = 0
+ self.settings.notice = 0
+ self.settings.warning = 0
+ elseif level == "debug" then
+ self.settings.debug = 0
+ elseif level == "info" then
+ self.settings.info = 0
+ elseif level == "notice" then
+ self.settings.notice = 0
+ elseif level == "warning" then
+ self.settings.warning = 0
+ else
+ error("invalid log level: "..level)
+ end
+end
+
+-- Enable or disable the displaying of file and line for messages.
+function Log:display_file_line(bool)
+ if not ffi.istype(t_name, self) then
+ bool = self
+ self = L
+ end
+ if bool == true then
+ self.settings.display_file_line = 3
+ else
+ self.settings.display_file_line = 0
+ end
+end
+
+-- Convert error number to its text representation.
+function Log.errstr(errno)
+ return ffi.string(C.core_log_errstr(errno))
+end
+
+-- Generate a debug message.
+function Log.debug(self, ...)
+ local format
+ if not ffi.istype(t_name, self) then
+ format = self
+ self = nil
+ end
+ if not self then
+ if L.settings.debug ~= 3 then
+ return
+ end
+ else
+ if self.settings.debug ~= 0 then
+ if self.settings.debug ~= 3 then
+ return
+ end
+ elseif self.module ~= nil and self.module.debug ~= 0 then
+ if self.module.debug ~= 3 then
+ return
+ end
+ elseif L.settings.debug ~= 3 then
+ return
+ end
+ end
+ while true do
+ if not self then
+ if L.settings.display_file_line ~= 3 then
+ break
+ end
+ else
+ if self.settings.display_file_line ~= 0 then
+ if self.settings.display_file_line ~= 3 then
+ break
+ end
+ elseif self.module ~= nil and self.module.display_file_line ~= 0 then
+ if self.module.display_file_line ~= 3 then
+ break
+ end
+ elseif L.settings.display_file_line ~= 3 then
+ break
+ end
+ end
+ local info = debug.getinfo(2, "S")
+ if format then
+ C.core_log_debug(self, info.source, info.linedefined, format, ...)
+ return
+ end
+ C.core_log_debug(self, info.source, info.linedefined, ...)
+ return
+ end
+
+ if format then
+ C.core_log_debug(self, nil, 0, format, ...)
+ return
+ end
+ C.core_log_debug(self, nil, 0, ...)
+end
+
+-- Generate an info message.
+function Log.info(self, ...)
+ local format
+ if not ffi.istype(t_name, self) then
+ format = self
+ self = nil
+ end
+ if not self then
+ if L.settings.info ~= 3 then
+ return
+ end
+ else
+ if self.settings.info ~= 0 then
+ if self.settings.info ~= 3 then
+ return
+ end
+ elseif self.module ~= nil and self.module.info ~= 0 then
+ if self.module.info ~= 3 then
+ return
+ end
+ elseif L.settings.info ~= 3 then
+ return
+ end
+ end
+ while true do
+ if not self then
+ if L.settings.display_file_line ~= 3 then
+ break
+ end
+ else
+ if self.settings.display_file_line ~= 0 then
+ if self.settings.display_file_line ~= 3 then
+ break
+ end
+ elseif self.module ~= nil and self.module.display_file_line ~= 0 then
+ if self.module.display_file_line ~= 3 then
+ break
+ end
+ elseif L.settings.display_file_line ~= 3 then
+ break
+ end
+ end
+ local info = debug.getinfo(2, "S")
+ if format then
+ C.core_log_info(self, info.source, info.linedefined, format, ...)
+ return
+ end
+ C.core_log_info(self, info.source, info.linedefined, ...)
+ return
+ end
+
+ if format then
+ C.core_log_info(self, nil, 0, format, ...)
+ return
+ end
+ C.core_log_info(self, nil, 0, ...)
+end
+
+-- Generate a notice message.
+function Log.notice(self, ...)
+ local format
+ if not ffi.istype(t_name, self) then
+ format = self
+ self = nil
+ end
+ if not self then
+ if L.settings.notice ~= 3 then
+ return
+ end
+ else
+ if self.settings.notice ~= 0 then
+ if self.settings.notice ~= 3 then
+ return
+ end
+ elseif self.module ~= nil and self.module.notice ~= 0 then
+ if self.module.notice ~= 3 then
+ return
+ end
+ elseif L.settings.notice ~= 3 then
+ return
+ end
+ end
+ while true do
+ if not self then
+ if L.settings.display_file_line ~= 3 then
+ break
+ end
+ else
+ if self.settings.display_file_line ~= 0 then
+ if self.settings.display_file_line ~= 3 then
+ break
+ end
+ elseif self.module ~= nil and self.module.display_file_line ~= 0 then
+ if self.module.display_file_line ~= 3 then
+ break
+ end
+ elseif L.settings.display_file_line ~= 3 then
+ break
+ end
+ end
+ local info = debug.getinfo(2, "S")
+ if format then
+ C.core_log_notice(self, info.source, info.linedefined, format, ...)
+ return
+ end
+ C.core_log_notice(self, info.source, info.linedefined, ...)
+ return
+ end
+
+ if format then
+ C.core_log_notice(self, nil, 0, format, ...)
+ return
+ end
+ C.core_log_notice(self, nil, 0, ...)
+end
+
+-- Generate a warning message.
+function Log.warning(self, ...)
+ local format
+ if not ffi.istype(t_name, self) then
+ format = self
+ self = nil
+ end
+ if not self then
+ if L.settings.warning ~= 3 then
+ return
+ end
+ else
+ if self.settings.warning ~= 0 then
+ if self.settings.warning ~= 3 then
+ return
+ end
+ elseif self.module ~= nil and self.module.warning ~= 0 then
+ if self.module.warning ~= 3 then
+ return
+ end
+ elseif L.settings.warning ~= 3 then
+ return
+ end
+ end
+ while true do
+ if not self then
+ if L.settings.display_file_line ~= 3 then
+ break
+ end
+ else
+ if self.settings.display_file_line ~= 0 then
+ if self.settings.display_file_line ~= 3 then
+ break
+ end
+ elseif self.module ~= nil and self.module.display_file_line ~= 0 then
+ if self.module.display_file_line ~= 3 then
+ break
+ end
+ elseif L.settings.display_file_line ~= 3 then
+ break
+ end
+ end
+ local info = debug.getinfo(2, "S")
+ if format then
+ C.core_log_warning(self, info.source, info.linedefined, format, ...)
+ return
+ end
+ C.core_log_warning(self, info.source, info.linedefined, ...)
+ return
+ end
+
+ if format then
+ C.core_log_warning(self, nil, 0, format, ...)
+ return
+ end
+ C.core_log_warning(self, nil, 0, ...)
+end
+
+-- Generate a critical message.
+function Log.critical(self, ...)
+ local format
+ if not ffi.istype(t_name, self) then
+ format = self
+ self = nil
+ end
+ while true do
+ if not self then
+ if L.settings.display_file_line ~= 3 then
+ break
+ end
+ else
+ if self.settings.display_file_line ~= 0 then
+ if self.settings.display_file_line ~= 3 then
+ break
+ end
+ elseif self.module ~= nil and self.module.display_file_line ~= 0 then
+ if self.module.display_file_line ~= 3 then
+ break
+ end
+ elseif L.settings.display_file_line ~= 3 then
+ break
+ end
+ end
+ local info = debug.getinfo(2, "S")
+ if format then
+ C.core_log_critical(self, info.source, info.linedefined, format, ...)
+ return
+ end
+ C.core_log_critical(self, info.source, info.linedefined, ...)
+ return
+ end
+
+ if format then
+ C.core_log_critical(self, nil, 0, format, ...)
+ return
+ end
+ C.core_log_critical(self, nil, 0, ...)
+end
+
+-- Generate a fatal message.
+function Log.fatal(self, ...)
+ local format
+ if not ffi.istype(t_name, self) then
+ format = self
+ self = nil
+ end
+ while true do
+ if not self then
+ if L.settings.display_file_line ~= 3 then
+ break
+ end
+ else
+ if self.settings.display_file_line ~= 0 then
+ if self.settings.display_file_line ~= 3 then
+ break
+ end
+ elseif self.module ~= nil and self.module.display_file_line ~= 0 then
+ if self.module.display_file_line ~= 3 then
+ break
+ end
+ elseif L.settings.display_file_line ~= 3 then
+ break
+ end
+ end
+ local info = debug.getinfo(2, "S")
+ if format then
+ C.core_log_fatal(self, info.source, info.linedefined, format, ...)
+ return
+ end
+ C.core_log_fatal(self, info.source, info.linedefined, ...)
+ return
+ end
+
+ if format then
+ C.core_log_fatal(self, nil, 0, format, ...)
+ return
+ end
+ C.core_log_fatal(self, nil, 0, ...)
+end
+
+core_log_t = ffi.metatype(t_name, { __index = Log })
+
+return Log
diff --git a/src/core/object.c b/src/core/object.c
new file mode 100644
index 0000000..8356f25
--- /dev/null
+++ b/src/core/object.c
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "core/object.h"
+#include "core/assert.h"
+#include "core/object/pcap.h"
+#include "core/object/ether.h"
+#include "core/object/null.h"
+#include "core/object/loop.h"
+#include "core/object/linuxsll.h"
+#include "core/object/ieee802.h"
+#include "core/object/gre.h"
+#include "core/object/ip.h"
+#include "core/object/ip6.h"
+#include "core/object/icmp.h"
+#include "core/object/icmp6.h"
+#include "core/object/udp.h"
+#include "core/object/tcp.h"
+#include "core/object/payload.h"
+#include "core/object/dns.h"
+
+core_object_t* core_object_copy(const core_object_t* self)
+{
+ glassert_self();
+
+ switch (self->obj_type) {
+ case CORE_OBJECT_PCAP:
+ return (core_object_t*)core_object_pcap_copy((core_object_pcap_t*)self);
+ case CORE_OBJECT_ETHER:
+ return (core_object_t*)core_object_ether_copy((core_object_ether_t*)self);
+ case CORE_OBJECT_NULL:
+ return (core_object_t*)core_object_null_copy((core_object_null_t*)self);
+ case CORE_OBJECT_LOOP:
+ return (core_object_t*)core_object_loop_copy((core_object_loop_t*)self);
+ case CORE_OBJECT_LINUXSLL:
+ return (core_object_t*)core_object_linuxsll_copy((core_object_linuxsll_t*)self);
+ case CORE_OBJECT_IEEE802:
+ return (core_object_t*)core_object_ieee802_copy((core_object_ieee802_t*)self);
+ case CORE_OBJECT_GRE:
+ return (core_object_t*)core_object_gre_copy((core_object_gre_t*)self);
+ case CORE_OBJECT_IP:
+ return (core_object_t*)core_object_ip_copy((core_object_ip_t*)self);
+ case CORE_OBJECT_IP6:
+ return (core_object_t*)core_object_ip6_copy((core_object_ip6_t*)self);
+ case CORE_OBJECT_ICMP:
+ return (core_object_t*)core_object_icmp_copy((core_object_icmp_t*)self);
+ case CORE_OBJECT_ICMP6:
+ return (core_object_t*)core_object_icmp6_copy((core_object_icmp6_t*)self);
+ case CORE_OBJECT_UDP:
+ return (core_object_t*)core_object_udp_copy((core_object_udp_t*)self);
+ case CORE_OBJECT_TCP:
+ return (core_object_t*)core_object_tcp_copy((core_object_tcp_t*)self);
+ case CORE_OBJECT_PAYLOAD:
+ return (core_object_t*)core_object_payload_copy((core_object_payload_t*)self);
+ case CORE_OBJECT_DNS:
+ return (core_object_t*)core_object_dns_copy((core_object_dns_t*)self);
+ default:
+ glfatal("unknown type %d", self->obj_type);
+ }
+ return 0;
+}
+
+void core_object_free(core_object_t* self)
+{
+ glassert_self();
+
+ switch (self->obj_type) {
+ case CORE_OBJECT_PCAP:
+ core_object_pcap_free((core_object_pcap_t*)self);
+ break;
+ case CORE_OBJECT_ETHER:
+ core_object_ether_free((core_object_ether_t*)self);
+ break;
+ case CORE_OBJECT_NULL:
+ core_object_null_free((core_object_null_t*)self);
+ break;
+ case CORE_OBJECT_LOOP:
+ core_object_loop_free((core_object_loop_t*)self);
+ break;
+ case CORE_OBJECT_LINUXSLL:
+ core_object_linuxsll_free((core_object_linuxsll_t*)self);
+ break;
+ case CORE_OBJECT_IEEE802:
+ core_object_ieee802_free((core_object_ieee802_t*)self);
+ break;
+ case CORE_OBJECT_GRE:
+ core_object_gre_free((core_object_gre_t*)self);
+ break;
+ case CORE_OBJECT_IP:
+ core_object_ip_free((core_object_ip_t*)self);
+ break;
+ case CORE_OBJECT_IP6:
+ core_object_ip6_free((core_object_ip6_t*)self);
+ break;
+ case CORE_OBJECT_ICMP:
+ core_object_icmp_free((core_object_icmp_t*)self);
+ break;
+ case CORE_OBJECT_ICMP6:
+ core_object_icmp6_free((core_object_icmp6_t*)self);
+ break;
+ case CORE_OBJECT_UDP:
+ core_object_udp_free((core_object_udp_t*)self);
+ break;
+ case CORE_OBJECT_TCP:
+ core_object_tcp_free((core_object_tcp_t*)self);
+ break;
+ case CORE_OBJECT_PAYLOAD:
+ core_object_payload_free((core_object_payload_t*)self);
+ break;
+ case CORE_OBJECT_DNS:
+ core_object_dns_free((core_object_dns_t*)self);
+ break;
+ default:
+ glfatal("unknown type %d", self->obj_type);
+ }
+}
diff --git a/src/core/object.h b/src/core/object.h
new file mode 100644
index 0000000..c2eebb0
--- /dev/null
+++ b/src/core/object.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __dnsjit_core_object_h
+#define __dnsjit_core_object_h
+
+#define CORE_OBJECT_NONE 0
+#define CORE_OBJECT_PCAP 1
+/* link level objects */
+#define CORE_OBJECT_ETHER 10
+#define CORE_OBJECT_NULL 11
+#define CORE_OBJECT_LOOP 12
+#define CORE_OBJECT_LINUXSLL 13
+#define CORE_OBJECT_IEEE802 14
+#define CORE_OBJECT_GRE 15
+/* protocol objects */
+#define CORE_OBJECT_IP 20
+#define CORE_OBJECT_IP6 21
+#define CORE_OBJECT_ICMP 22
+#define CORE_OBJECT_ICMP6 23
+/* payload carrying objects */
+#define CORE_OBJECT_UDP 30
+#define CORE_OBJECT_TCP 31
+/* payload */
+#define CORE_OBJECT_PAYLOAD 40
+/* service object(s) */
+#define CORE_OBJECT_DNS 50
+
+#include <stdint.h>
+#include "core/object.hh"
+
+#define CORE_OBJECT_INIT(type, prev) (core_object_t*)prev, type
+
+#endif
diff --git a/src/core/object.hh b/src/core/object.hh
new file mode 100644
index 0000000..b19f858
--- /dev/null
+++ b/src/core/object.hh
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 objectd a copy of the GNU General Public License
+ * along with dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+typedef struct core_object core_object_t;
+struct core_object {
+ const core_object_t* obj_prev;
+ int32_t obj_type;
+};
+
+core_object_t* core_object_copy(const core_object_t* self);
+void core_object_free(core_object_t* self);
diff --git a/src/core/object.lua b/src/core/object.lua
new file mode 100644
index 0000000..7f58829
--- /dev/null
+++ b/src/core/object.lua
@@ -0,0 +1,177 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.object
+-- Base object that is passed between receiver and receivee
+-- require("dnsjit.core.object")
+-- print(object:type())
+-- packet = object:cast()
+--
+-- This is the base object that can be casted to other objects that to
+-- describe a DNS message, how it was captured or generated.
+-- Objects can be chained together, for example a DNS message is created
+-- ontop of a packet.
+-- .SS Attributes
+-- .TP
+-- obj_type
+-- The enum of the object type.
+-- .TP
+-- obj_prev
+-- The previous object in the object chain.
+module(...,package.seeall)
+
+require("dnsjit.core.object_h")
+require("dnsjit.core.object.pcap_h")
+require("dnsjit.core.object.ether_h")
+require("dnsjit.core.object.null_h")
+require("dnsjit.core.object.loop_h")
+require("dnsjit.core.object.linuxsll_h")
+require("dnsjit.core.object.ieee802_h")
+require("dnsjit.core.object.gre_h")
+require("dnsjit.core.object.ip_h")
+require("dnsjit.core.object.ip6_h")
+require("dnsjit.core.object.icmp_h")
+require("dnsjit.core.object.icmp6_h")
+require("dnsjit.core.object.udp_h")
+require("dnsjit.core.object.tcp_h")
+require("dnsjit.core.object.payload_h")
+require("dnsjit.core.object.dns_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "core_object_t"
+local core_object_t
+local Object = {
+ NONE = 0,
+ PCAP = 1,
+ ETHER = 10,
+ NULL = 11,
+ LOOP = 12,
+ LINUXSLL = 13,
+ IEEE802 = 14,
+ GRE = 15,
+ IP = 20,
+ IP6 = 21,
+ ICMP = 22,
+ ICMP6 = 23,
+ UDP = 30,
+ TCP = 31,
+ PAYLOAD = 40,
+ DNS = 50
+}
+
+local _type = {}
+_type[Object.PCAP] = "pcap"
+_type[Object.ETHER] = "ether"
+_type[Object.NULL] = "null"
+_type[Object.LOOP] = "loop"
+_type[Object.LINUXSLL] = "linuxsll"
+_type[Object.IEEE802] = "ieee802"
+_type[Object.GRE] = "gre"
+_type[Object.IP] = "ip"
+_type[Object.IP6] = "ip6"
+_type[Object.ICMP] = "icmp"
+_type[Object.ICMP6] = "icmp6"
+_type[Object.UDP] = "udp"
+_type[Object.TCP] = "tcp"
+_type[Object.PAYLOAD] = "payload"
+_type[Object.DNS] = "dns"
+
+_type[Object.NONE] = "none"
+
+-- Return the textual type of the object.
+function Object:type()
+ return _type[self.obj_type]
+end
+
+-- Return the previous object.
+function Object:prev()
+ return self.obj_prev
+end
+
+local _cast = {}
+_cast[Object.PCAP] = "core_object_pcap_t*"
+_cast[Object.ETHER] = "core_object_ether_t*"
+_cast[Object.NULL] = "core_object_null_t*"
+_cast[Object.LOOP] = "core_object_loop_t*"
+_cast[Object.LINUXSLL] = "core_object_linuxsll_t*"
+_cast[Object.IEEE802] = "core_object_ieee802_t*"
+_cast[Object.GRE] = "core_object_gre_t*"
+_cast[Object.IP] = "core_object_ip_t*"
+_cast[Object.IP6] = "core_object_ip6_t*"
+_cast[Object.ICMP] = "core_object_icmp_t*"
+_cast[Object.ICMP6] = "core_object_icmp6_t*"
+_cast[Object.UDP] = "core_object_udp_t*"
+_cast[Object.TCP] = "core_object_tcp_t*"
+_cast[Object.PAYLOAD] = "core_object_payload_t*"
+_cast[Object.DNS] = "core_object_dns_t*"
+
+-- Cast the object to the underlining object module and return it.
+function Object:cast()
+ return ffi.cast(_cast[self.obj_type], self)
+end
+
+-- Cast the object to the specified object module and return it.
+-- Returns nil if the object chain doesn't contained the specified object type.
+function Object:cast_to(obj_type)
+ if obj_type == nil then
+ obj_type = self.obj_type
+ end
+
+ local obj = self
+ while obj.obj_type ~= obj_type do
+ obj = obj.obj_prev
+ if obj == nil then return nil end
+ end
+
+ return ffi.cast(_cast[obj_type], obj)
+end
+
+-- Cast the object to the generic object module and return it.
+function Object:uncast()
+ return self
+end
+
+-- Make a copy of the object and return it.
+function Object:copy()
+ return C.core_object_copy(self)
+end
+
+-- Free the object, should only be used on copies or otherwise allocated.
+function Object:free()
+ C.core_object_free(self)
+end
+
+core_object_t = ffi.metatype(t_name, { __index = Object })
+
+-- dnsjit.core.object.pcap (3),
+-- dnsjit.core.object.ether (3),
+-- dnsjit.core.object.null (3),
+-- dnsjit.core.object.loop (3),
+-- dnsjit.core.object.linuxsll (3),
+-- dnsjit.core.object.ieee802 (3),
+-- dnsjit.core.object.gre (3),
+-- dnsjit.core.object.ip (3),
+-- dnsjit.core.object.ip6 (3),
+-- dnsjit.core.object.icmp (3),
+-- dnsjit.core.object.icmp6 (3),
+-- dnsjit.core.object.udp (3),
+-- dnsjit.core.object.tcp (3),
+-- dnsjit.core.object.payload (3),
+-- dnsjit.core.object.dns (3)
+return Object
diff --git a/src/core/object/dns.c b/src/core/object/dns.c
new file mode 100644
index 0000000..49aec6d
--- /dev/null
+++ b/src/core/object/dns.c
@@ -0,0 +1,471 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "core/object/dns.h"
+#include "core/object/payload.h"
+#include "core/assert.h"
+
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_ENDIAN_H
+#include <endian.h>
+#else
+#ifdef HAVE_SYS_ENDIAN_H
+#include <sys/endian.h>
+#else
+#ifdef HAVE_MACHINE_ENDIAN_H
+#include <machine/endian.h>
+#endif
+#endif
+#endif
+#ifdef HAVE_BYTESWAP_H
+#include <byteswap.h>
+#endif
+#ifndef bswap_16
+#ifndef bswap16
+#define bswap_16(x) swap16(x)
+#define bswap_32(x) swap32(x)
+#define bswap_64(x) swap64(x)
+#else
+#define bswap_16(x) bswap16(x)
+#define bswap_32(x) bswap32(x)
+#define bswap_64(x) bswap64(x)
+#endif
+#endif
+
+#define _ERR_MALFORMED -2
+#define _ERR_NEEDLABELS -3
+
+static core_log_t _log = LOG_T_INIT("core.object.dns");
+static core_object_dns_t _defaults = CORE_OBJECT_DNS_INIT(0);
+
+static core_object_dns_label_t _defaults_label = { 0 };
+static core_object_dns_rr_t _defaults_rr = { 0 };
+static core_object_dns_q_t _defaults_q = { 0 };
+
+core_log_t* core_object_dns_log()
+{
+ return &_log;
+}
+
+core_object_dns_t* core_object_dns_new()
+{
+ core_object_dns_t* self;
+
+ mlfatal_oom(self = malloc(sizeof(core_object_dns_t)));
+ *self = _defaults;
+
+ return self;
+}
+
+core_object_dns_t* core_object_dns_copy(const core_object_dns_t* self)
+{
+ core_object_dns_t* copy;
+ mlassert_self();
+
+ mlfatal_oom(copy = malloc(sizeof(core_object_dns_t)));
+ memcpy(copy, self, sizeof(core_object_dns_t));
+ copy->obj_prev = 0;
+
+ return (core_object_dns_t*)copy;
+}
+
+void core_object_dns_free(core_object_dns_t* self)
+{
+ mlassert_self();
+ free(self);
+}
+
+#define need8(v, p, l) \
+ if (l < 1) { \
+ break; \
+ } \
+ v = *p; \
+ p += 1; \
+ l -= 1
+
+static inline uint16_t _need16(const void* ptr)
+{
+ uint16_t v;
+ memcpy(&v, ptr, sizeof(v));
+ return be16toh(v);
+}
+
+#define need16(v, p, l) \
+ if (l < 2) { \
+ break; \
+ } \
+ v = _need16(p); \
+ p += 2; \
+ l -= 2
+
+static inline uint32_t _need32(const void* ptr)
+{
+ uint32_t v;
+ memcpy(&v, ptr, sizeof(v));
+ return be32toh(v);
+}
+
+#define need32(v, p, l) \
+ if (l < 4) { \
+ break; \
+ } \
+ v = _need32(p); \
+ p += 4; \
+ l -= 4
+
+#define needxb(b, x, p, l) \
+ if (l < x) { \
+ break; \
+ } \
+ memcpy(b, p, x); \
+ p += x; \
+ l -= x
+
+#define advancexb(x, p, l) \
+ if (l < x) { \
+ break; \
+ } \
+ p += x; \
+ l -= x
+
+int core_object_dns_parse_header(core_object_dns_t* self)
+{
+ const core_object_payload_t* payload;
+ uint8_t byte;
+ mlassert_self();
+
+ if (!(payload = (core_object_payload_t*)self->obj_prev) || payload->obj_type != CORE_OBJECT_PAYLOAD) {
+ mlfatal("no obj_prev or invalid type");
+ }
+ if (!payload->payload || !payload->len) {
+ mlfatal("no payload set or zero length");
+ }
+
+ self->payload = self->at = payload->payload;
+ self->len = self->left = payload->len;
+
+ for (;;) {
+ if (self->includes_dnslen) {
+ need16(self->dnslen, self->at, self->left);
+ self->have_dnslen = 1;
+ }
+ need16(self->id, self->at, self->left);
+ self->have_id = 1;
+
+ need8(byte, self->at, self->left);
+ self->qr = byte & (1 << 7) ? 1 : 0;
+ self->opcode = (byte >> 3) & 0xf;
+ self->aa = byte & (1 << 2) ? 1 : 0;
+ self->tc = byte & (1 << 1) ? 1 : 0;
+ self->rd = byte & (1 << 0) ? 1 : 0;
+ self->have_qr = self->have_opcode = self->have_aa = self->have_tc = self->have_rd = 1;
+
+ need8(byte, self->at, self->left);
+ self->ra = byte & (1 << 7) ? 1 : 0;
+ self->z = byte & (1 << 6) ? 1 : 0;
+ self->ad = byte & (1 << 5) ? 1 : 0;
+ self->cd = byte & (1 << 4) ? 1 : 0;
+ self->rcode = byte & 0xf;
+ self->have_ra = self->have_z = self->have_ad = self->have_cd = self->have_rcode = 1;
+
+ need16(self->qdcount, self->at, self->left);
+ self->have_qdcount = 1;
+
+ need16(self->ancount, self->at, self->left);
+ self->have_ancount = 1;
+
+ need16(self->nscount, self->at, self->left);
+ self->have_nscount = 1;
+
+ need16(self->arcount, self->at, self->left);
+ self->have_arcount = 1;
+
+ return 0;
+ }
+
+ // TODO: error here on malformed/truncated? could be quite spammy
+ return _ERR_MALFORMED;
+}
+
+static inline size_t _rdata_labels(uint16_t type)
+{
+ switch (type) {
+ case CORE_OBJECT_DNS_TYPE_NS:
+ case CORE_OBJECT_DNS_TYPE_MD:
+ case CORE_OBJECT_DNS_TYPE_MF:
+ case CORE_OBJECT_DNS_TYPE_CNAME:
+ case CORE_OBJECT_DNS_TYPE_MB:
+ case CORE_OBJECT_DNS_TYPE_MG:
+ case CORE_OBJECT_DNS_TYPE_MR:
+ case CORE_OBJECT_DNS_TYPE_PTR:
+ case CORE_OBJECT_DNS_TYPE_NXT:
+ case CORE_OBJECT_DNS_TYPE_DNAME:
+ case CORE_OBJECT_DNS_TYPE_NSEC:
+ case CORE_OBJECT_DNS_TYPE_TKEY:
+ case CORE_OBJECT_DNS_TYPE_TSIG:
+ return 1;
+
+ case CORE_OBJECT_DNS_TYPE_SOA:
+ case CORE_OBJECT_DNS_TYPE_MINFO:
+ case CORE_OBJECT_DNS_TYPE_RP:
+ case CORE_OBJECT_DNS_TYPE_TALINK:
+ return 2;
+
+ case CORE_OBJECT_DNS_TYPE_MX:
+ case CORE_OBJECT_DNS_TYPE_AFSDB:
+ case CORE_OBJECT_DNS_TYPE_RT:
+ case CORE_OBJECT_DNS_TYPE_KX:
+ case CORE_OBJECT_DNS_TYPE_LP:
+ return 1;
+
+ case CORE_OBJECT_DNS_TYPE_PX:
+ return 2;
+
+ case CORE_OBJECT_DNS_TYPE_SIG:
+ case CORE_OBJECT_DNS_TYPE_RRSIG:
+ return 1;
+
+ case CORE_OBJECT_DNS_TYPE_SRV:
+ return 1;
+
+ case CORE_OBJECT_DNS_TYPE_NAPTR:
+ return 1;
+
+ case CORE_OBJECT_DNS_TYPE_HIP:
+ return 1;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static inline size_t _label(core_object_dns_t* self, core_object_dns_label_t* label, size_t labels)
+{
+ size_t n;
+
+ for (n = 0; self->left && n < labels; n++) {
+ core_object_dns_label_t* l = &label[n];
+ *l = _defaults_label;
+
+ need8(l->length, self->at, self->left);
+
+ if ((l->length & 0xc0) == 0xc0) {
+ need8(l->offset, self->at, self->left);
+ l->offset |= (l->length & 0x3f) << 8;
+ l->have_offset = 1;
+ return n;
+ } else if (l->length & 0xc0) {
+ l->extension_bits = l->length >> 6;
+ l->have_extension_bits = 1;
+ return n;
+ } else if (l->length) {
+ l->have_length = 1;
+
+ l->offset = self->at - self->payload - 1;
+ advancexb(l->length, self->at, self->left);
+ l->have_dn = 1;
+ } else {
+ l->is_end = 1;
+ return n;
+ }
+ }
+
+ return n;
+}
+
+int core_object_dns_parse_q(core_object_dns_t* self, core_object_dns_q_t* q, core_object_dns_label_t* label, size_t labels)
+{
+ mlassert_self();
+ mlassert(q, "q is nil");
+ mlassert(label, "label is nil");
+ mlassert(labels, "labels is zero");
+ mlassert(self->at, "at is nil");
+
+ for (;;) {
+ *q = _defaults_q;
+ q->labels = _label(self, label, labels);
+ if (q->labels < labels) {
+ core_object_dns_label_t* l = &label[q->labels];
+ if (!(l->have_offset | l->have_extension_bits | l->is_end)) {
+ // TODO: error here on malformed/truncated? could be quite spammy
+ return _ERR_MALFORMED;
+ }
+ } else {
+ mlwarning("need more labels, aborting DNS parsing");
+ return _ERR_NEEDLABELS;
+ }
+ q->labels++;
+
+ need16(q->type, self->at, self->left);
+ q->have_type = 1;
+
+ need16(q->class, self->at, self->left);
+ q->have_class = 1;
+
+ return 0;
+ }
+
+ // TODO: error here on malformed/truncated? could be quite spammy
+ return _ERR_MALFORMED;
+}
+
+int core_object_dns_parse_rr(core_object_dns_t* self, core_object_dns_rr_t* rr, core_object_dns_label_t* label, size_t labels)
+{
+ size_t rdata_label_sets;
+ mlassert_self();
+ mlassert(rr, "rr is nil");
+ mlassert(label, "label is nil");
+ mlassert(labels, "labels is zero");
+ mlassert(self->at, "at is nil");
+
+ for (;;) {
+ *rr = _defaults_rr;
+ rr->labels = _label(self, label, labels);
+ if (rr->labels < labels) {
+ core_object_dns_label_t* l = &label[rr->labels];
+ if (!(l->have_offset | l->have_extension_bits | l->is_end)) {
+ // TODO: error here on malformed/truncated? could be quite spammy
+ return _ERR_MALFORMED;
+ }
+ } else {
+ mlwarning("need more labels, aborting DNS parsing");
+ return _ERR_NEEDLABELS;
+ }
+ rr->labels++;
+
+ need16(rr->type, self->at, self->left);
+ rr->have_type = 1;
+
+ need16(rr->class, self->at, self->left);
+ rr->have_class = 1;
+
+ need32(rr->ttl, self->at, self->left);
+ rr->have_ttl = 1;
+
+ need16(rr->rdlength, self->at, self->left);
+ rr->have_rdlength = 1;
+
+ rr->rdata_offset = self->at - self->payload;
+ if (!(rdata_label_sets = _rdata_labels(rr->type))) {
+ advancexb(rr->rdlength, self->at, self->left);
+ rr->have_rdata = 1;
+ return 0;
+ }
+
+ switch (rr->type) {
+ case CORE_OBJECT_DNS_TYPE_MX:
+ case CORE_OBJECT_DNS_TYPE_AFSDB:
+ case CORE_OBJECT_DNS_TYPE_RT:
+ case CORE_OBJECT_DNS_TYPE_KX:
+ case CORE_OBJECT_DNS_TYPE_LP:
+ case CORE_OBJECT_DNS_TYPE_PX:
+ advancexb(2, self->at, self->left);
+ break;
+
+ case CORE_OBJECT_DNS_TYPE_SIG:
+ case CORE_OBJECT_DNS_TYPE_RRSIG:
+ advancexb(18, self->at, self->left);
+ break;
+
+ case CORE_OBJECT_DNS_TYPE_SRV:
+ advancexb(6, self->at, self->left);
+ break;
+
+ case CORE_OBJECT_DNS_TYPE_NAPTR: {
+ uint8_t naptr_length;
+
+ advancexb(4, self->at, self->left);
+ need8(naptr_length, self->at, self->left);
+ advancexb(naptr_length, self->at, self->left);
+ need8(naptr_length, self->at, self->left);
+ advancexb(naptr_length, self->at, self->left);
+ need8(naptr_length, self->at, self->left);
+ advancexb(naptr_length, self->at, self->left);
+ } break;
+
+ case CORE_OBJECT_DNS_TYPE_HIP: {
+ uint8_t hit_length;
+ uint16_t pk_length;
+
+ need8(hit_length, self->at, self->left);
+ advancexb(1, self->at, self->left);
+ need16(pk_length, self->at, self->left);
+ advancexb(hit_length, self->at, self->left);
+ advancexb(pk_length, self->at, self->left);
+
+ if (self->at - self->payload >= rr->rdata_offset + rr->rdlength) {
+ rdata_label_sets = 0;
+ }
+ } break;
+ }
+
+ while (rdata_label_sets) {
+ rr->rdata_labels += _label(self, &label[rr->labels + rr->rdata_labels], labels - rr->labels - rr->rdata_labels);
+ if (rr->labels + rr->rdata_labels < labels) {
+ core_object_dns_label_t* l = &label[rr->labels + rr->rdata_labels];
+ if (!(l->have_offset | l->have_extension_bits | l->is_end)) {
+ // TODO: error here on malformed/truncated? could be quite spammy
+ return _ERR_MALFORMED;
+ }
+ } else {
+ mlwarning("need more labels, aborting DNS parsing");
+ return _ERR_NEEDLABELS;
+ }
+ rr->rdata_labels++;
+
+ if (rr->type == CORE_OBJECT_DNS_TYPE_HIP && self->at - self->payload < rr->rdata_offset + rr->rdlength) {
+ continue;
+ }
+
+ rdata_label_sets--;
+ }
+
+ if (self->at - self->payload < rr->rdata_offset + rr->rdlength) {
+ rr->padding_offset = self->at - self->payload;
+ rr->padding_length = rr->rdlength - (rr->padding_offset - rr->rdata_offset);
+
+ advancexb(rr->padding_length, self->at, self->left);
+
+ /*
+ * TODO:
+ *
+ * This can indicate padding but we do not set that we have padding
+ * yet because we need to fully understand all record types before
+ * that and process valid data after the labels
+ *
+ rr->have_padding = 1;
+ */
+ } else if (self->at - self->payload > rr->rdata_offset + rr->rdlength) {
+ // TODO: error here on malformed/truncated? could be quite spammy
+ return _ERR_MALFORMED;
+ }
+ rr->have_rdata = 1;
+
+ return 0;
+ }
+
+ // TODO: error here on malformed/truncated? could be quite spammy
+ return _ERR_MALFORMED;
+}
diff --git a/src/core/object/dns.h b/src/core/object/dns.h
new file mode 100644
index 0000000..5fbaf53
--- /dev/null
+++ b/src/core/object/dns.h
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/log.h"
+#include "core/object.h"
+
+#ifndef __dnsjit_core_object_dns_h
+#define __dnsjit_core_object_dns_h
+
+#include <netinet/in.h>
+#include <sys/types.h>
+
+#include "core/object/dns.hh"
+
+#define CORE_OBJECT_DNS_INIT(prev) \
+ { \
+ CORE_OBJECT_INIT(CORE_OBJECT_DNS, prev) \
+ , \
+ 0, 0, 0, 0, 0, \
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
+ }
+
+/*
+ * 2016-12-09 https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml
+ */
+
+#define CORE_OBJECT_DNS_CLASS_IN 1
+#define CORE_OBJECT_DNS_CLASS_CH 3
+#define CORE_OBJECT_DNS_CLASS_HS 4
+#define CORE_OBJECT_DNS_CLASS_NONE 254
+#define CORE_OBJECT_DNS_CLASS_ANY 255
+
+#define CORE_OBJECT_DNS_TYPE_A 1
+#define CORE_OBJECT_DNS_TYPE_NS 2
+#define CORE_OBJECT_DNS_TYPE_MD 3
+#define CORE_OBJECT_DNS_TYPE_MF 4
+#define CORE_OBJECT_DNS_TYPE_CNAME 5
+#define CORE_OBJECT_DNS_TYPE_SOA 6
+#define CORE_OBJECT_DNS_TYPE_MB 7
+#define CORE_OBJECT_DNS_TYPE_MG 8
+#define CORE_OBJECT_DNS_TYPE_MR 9
+#define CORE_OBJECT_DNS_TYPE_NULL 10
+#define CORE_OBJECT_DNS_TYPE_WKS 11
+#define CORE_OBJECT_DNS_TYPE_PTR 12
+#define CORE_OBJECT_DNS_TYPE_HINFO 13
+#define CORE_OBJECT_DNS_TYPE_MINFO 14
+#define CORE_OBJECT_DNS_TYPE_MX 15
+#define CORE_OBJECT_DNS_TYPE_TXT 16
+#define CORE_OBJECT_DNS_TYPE_RP 17
+#define CORE_OBJECT_DNS_TYPE_AFSDB 18
+#define CORE_OBJECT_DNS_TYPE_X25 19
+#define CORE_OBJECT_DNS_TYPE_ISDN 20
+#define CORE_OBJECT_DNS_TYPE_RT 21
+#define CORE_OBJECT_DNS_TYPE_NSAP 22
+#define CORE_OBJECT_DNS_TYPE_NSAP_PTR 23
+#define CORE_OBJECT_DNS_TYPE_SIG 24
+#define CORE_OBJECT_DNS_TYPE_KEY 25
+#define CORE_OBJECT_DNS_TYPE_PX 26
+#define CORE_OBJECT_DNS_TYPE_GPOS 27
+#define CORE_OBJECT_DNS_TYPE_AAAA 28
+#define CORE_OBJECT_DNS_TYPE_LOC 29
+#define CORE_OBJECT_DNS_TYPE_NXT 30
+#define CORE_OBJECT_DNS_TYPE_EID 31
+#define CORE_OBJECT_DNS_TYPE_NIMLOC 32
+#define CORE_OBJECT_DNS_TYPE_SRV 33
+#define CORE_OBJECT_DNS_TYPE_ATMA 34
+#define CORE_OBJECT_DNS_TYPE_NAPTR 35
+#define CORE_OBJECT_DNS_TYPE_KX 36
+#define CORE_OBJECT_DNS_TYPE_CERT 37
+#define CORE_OBJECT_DNS_TYPE_A6 38
+#define CORE_OBJECT_DNS_TYPE_DNAME 39
+#define CORE_OBJECT_DNS_TYPE_SINK 40
+#define CORE_OBJECT_DNS_TYPE_OPT 41
+#define CORE_OBJECT_DNS_TYPE_APL 42
+#define CORE_OBJECT_DNS_TYPE_DS 43
+#define CORE_OBJECT_DNS_TYPE_SSHFP 44
+#define CORE_OBJECT_DNS_TYPE_IPSECKEY 45
+#define CORE_OBJECT_DNS_TYPE_RRSIG 46
+#define CORE_OBJECT_DNS_TYPE_NSEC 47
+#define CORE_OBJECT_DNS_TYPE_DNSKEY 48
+#define CORE_OBJECT_DNS_TYPE_DHCID 49
+#define CORE_OBJECT_DNS_TYPE_NSEC3 50
+#define CORE_OBJECT_DNS_TYPE_NSEC3PARAM 51
+#define CORE_OBJECT_DNS_TYPE_TLSA 52
+#define CORE_OBJECT_DNS_TYPE_SMIMEA 53
+#define CORE_OBJECT_DNS_TYPE_HIP 55
+#define CORE_OBJECT_DNS_TYPE_NINFO 56
+#define CORE_OBJECT_DNS_TYPE_RKEY 57
+#define CORE_OBJECT_DNS_TYPE_TALINK 58
+#define CORE_OBJECT_DNS_TYPE_CDS 59
+#define CORE_OBJECT_DNS_TYPE_CDNSKEY 60
+#define CORE_OBJECT_DNS_TYPE_OPENPGPKEY 61
+#define CORE_OBJECT_DNS_TYPE_CSYNC 62
+#define CORE_OBJECT_DNS_TYPE_SPF 99
+#define CORE_OBJECT_DNS_TYPE_UINFO 100
+#define CORE_OBJECT_DNS_TYPE_UID 101
+#define CORE_OBJECT_DNS_TYPE_GID 102
+#define CORE_OBJECT_DNS_TYPE_UNSPEC 103
+#define CORE_OBJECT_DNS_TYPE_NID 104
+#define CORE_OBJECT_DNS_TYPE_L32 105
+#define CORE_OBJECT_DNS_TYPE_L64 106
+#define CORE_OBJECT_DNS_TYPE_LP 107
+#define CORE_OBJECT_DNS_TYPE_EUI48 108
+#define CORE_OBJECT_DNS_TYPE_EUI64 109
+#define CORE_OBJECT_DNS_TYPE_TKEY 249
+#define CORE_OBJECT_DNS_TYPE_TSIG 250
+#define CORE_OBJECT_DNS_TYPE_IXFR 251
+#define CORE_OBJECT_DNS_TYPE_AXFR 252
+#define CORE_OBJECT_DNS_TYPE_MAILB 253
+#define CORE_OBJECT_DNS_TYPE_MAILA 254
+#define CORE_OBJECT_DNS_TYPE_ANY 255
+#define CORE_OBJECT_DNS_TYPE_URI 256
+#define CORE_OBJECT_DNS_TYPE_CAA 257
+#define CORE_OBJECT_DNS_TYPE_AVC 258
+#define CORE_OBJECT_DNS_TYPE_TA 32768
+#define CORE_OBJECT_DNS_TYPE_DLV 32769
+
+#define CORE_OBJECT_DNS_OPCODE_QUERY 0
+#define CORE_OBJECT_DNS_OPCODE_IQUERY 1
+#define CORE_OBJECT_DNS_OPCODE_STATUS 2
+#define CORE_OBJECT_DNS_OPCODE_NOTIFY 4
+#define CORE_OBJECT_DNS_OPCODE_UPDATE 5
+
+#define CORE_OBJECT_DNS_RCODE_NOERROR 0
+#define CORE_OBJECT_DNS_RCODE_FORMERR 1
+#define CORE_OBJECT_DNS_RCODE_SERVFAIL 2
+#define CORE_OBJECT_DNS_RCODE_NXDOMAIN 3
+#define CORE_OBJECT_DNS_RCODE_NOTIMP 4
+#define CORE_OBJECT_DNS_RCODE_REFUSED 5
+#define CORE_OBJECT_DNS_RCODE_YXDOMAIN 6
+#define CORE_OBJECT_DNS_RCODE_YXRRSET 7
+#define CORE_OBJECT_DNS_RCODE_NXRRSET 8
+#define CORE_OBJECT_DNS_RCODE_NOTAUTH 9
+#define CORE_OBJECT_DNS_RCODE_NOTZONE 10
+#define CORE_OBJECT_DNS_RCODE_BADVERS 16
+#define CORE_OBJECT_DNS_RCODE_BADSIG 16
+#define CORE_OBJECT_DNS_RCODE_BADKEY 17
+#define CORE_OBJECT_DNS_RCODE_BADTIME 18
+#define CORE_OBJECT_DNS_RCODE_BADMODE 19
+#define CORE_OBJECT_DNS_RCODE_BADNAME 20
+#define CORE_OBJECT_DNS_RCODE_BADALG 21
+#define CORE_OBJECT_DNS_RCODE_BADTRUNC 22
+#define CORE_OBJECT_DNS_RCODE_BADCOOKIE 23
+
+#define CORE_OBJECT_DNS_AFSDB_SUBTYPE_AFS3LOCSRV 1
+#define CORE_OBJECT_DNS_AFSDB_SUBTYPE_DCENCA_ROOT 2
+
+#define CORE_OBJECT_DNS_DHCID_TYPE_1OCTET 0
+#define CORE_OBJECT_DNS_DHCID_TYPE_DATAOCTET 1
+#define CORE_OBJECT_DNS_DHCID_TYPE_CLIENT_DUID 2
+
+#define CORE_OBJECT_DNS_EDNS0_OPT_LLQ 1
+#define CORE_OBJECT_DNS_EDNS0_OPT_UL 2
+#define CORE_OBJECT_DNS_EDNS0_OPT_NSID 3
+#define CORE_OBJECT_DNS_EDNS0_OPT_DAU 5
+#define CORE_OBJECT_DNS_EDNS0_OPT_DHU 6
+#define CORE_OBJECT_DNS_EDNS0_OPT_N3U 7
+#define CORE_OBJECT_DNS_EDNS0_OPT_CLIENT_SUBNET 8
+#define CORE_OBJECT_DNS_EDNS0_OPT_EXPIRE 9
+#define CORE_OBJECT_DNS_EDNS0_OPT_COOKIE 10
+#define CORE_OBJECT_DNS_EDNS0_OPT_TCP_KEEPALIVE 11
+#define CORE_OBJECT_DNS_EDNS0_OPT_PADDING 12
+#define CORE_OBJECT_DNS_EDNS0_OPT_CHAIN 13
+#define CORE_OBJECT_DNS_EDNS0_OPT_DEVICEID 26946
+
+#endif
diff --git a/src/core/object/dns.hh b/src/core/object/dns.hh
new file mode 100644
index 0000000..dfe9719
--- /dev/null
+++ b/src/core/object/dns.hh
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.object_h")
+
+typedef struct core_object_dns_label {
+ uint8_t is_end;
+ uint8_t have_length;
+ uint8_t have_offset;
+ uint8_t have_extension_bits;
+ uint8_t have_dn;
+ uint8_t extension_bits;
+
+ uint8_t length;
+ uint16_t offset;
+} core_object_dns_label_t;
+
+typedef struct core_object_dns_rr {
+ uint8_t have_type;
+ uint8_t have_class;
+ uint8_t have_ttl;
+ uint8_t have_rdlength;
+ uint8_t have_rdata;
+ uint8_t have_rdata_labels;
+ uint8_t have_padding;
+
+ uint16_t type;
+ uint16_t class;
+ uint32_t ttl;
+ uint16_t rdlength;
+
+ size_t labels;
+ size_t rdata_offset;
+ size_t rdata_labels;
+ size_t padding_offset;
+ size_t padding_length;
+} core_object_dns_rr_t;
+
+typedef struct core_object_dns_q {
+ uint8_t have_type;
+ uint8_t have_class;
+
+ uint16_t type;
+ uint16_t class;
+
+ size_t labels;
+} core_object_dns_q_t;
+
+typedef struct core_object_dns {
+ const core_object_t* obj_prev;
+ int32_t obj_type;
+
+ int includes_dnslen;
+ const uint8_t *payload, *at;
+ size_t len, left;
+
+ uint8_t have_dnslen;
+ uint8_t have_id;
+ uint8_t have_qr;
+ uint8_t have_opcode;
+ uint8_t have_aa;
+ uint8_t have_tc;
+ uint8_t have_rd;
+ uint8_t have_ra;
+ uint8_t have_z;
+ uint8_t have_ad;
+ uint8_t have_cd;
+ uint8_t have_rcode;
+ uint8_t have_qdcount;
+ uint8_t have_ancount;
+ uint8_t have_nscount;
+ uint8_t have_arcount;
+
+ uint16_t dnslen;
+ uint16_t id;
+ int8_t qr;
+ uint8_t opcode;
+ uint8_t aa;
+ uint8_t tc;
+ uint8_t rd;
+ uint8_t ra;
+ uint8_t z;
+ uint8_t ad;
+ uint8_t cd;
+ uint8_t rcode;
+ uint16_t qdcount;
+ uint16_t ancount;
+ uint16_t nscount;
+ uint16_t arcount;
+} core_object_dns_t;
+
+core_log_t* core_object_dns_log();
+
+core_object_dns_t* core_object_dns_new();
+core_object_dns_t* core_object_dns_copy(const core_object_dns_t* self);
+void core_object_dns_free(core_object_dns_t* self);
+void core_object_dns_reset(core_object_dns_t* self, const core_object_t* obj);
+
+int core_object_dns_parse_header(core_object_dns_t* self);
+int core_object_dns_parse_q(core_object_dns_t* self, core_object_dns_q_t* q, core_object_dns_label_t* label, size_t labels);
+int core_object_dns_parse_rr(core_object_dns_t* self, core_object_dns_rr_t* rr, core_object_dns_label_t* label, size_t labels);
diff --git a/src/core/object/dns.lua b/src/core/object/dns.lua
new file mode 100644
index 0000000..e8fb12b
--- /dev/null
+++ b/src/core/object/dns.lua
@@ -0,0 +1,797 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.object.dns
+-- Container of a DNS message
+-- .SS Parse DNS header and check if query or response
+-- local dns = require("dnsjit.core.object.dns").new(payload)
+-- if dns:parse_header() == 0 then
+-- if dns.qr == 0 then
+-- print(dns.id, dns.opcode_tostring(dns.opcode))
+-- else
+-- print(dns.id, dns.rcode_tostring(dns.rcode))
+-- end
+-- end
+-- .SS Print a DNS payload
+-- local dns = require("dnsjit.core.object.dns").new(payload)
+-- dns:print()
+-- .SS Parse a DNS payload
+-- local dns = require("dnsjit.core.object.dns").new(payload)
+-- local qs, q_labels, rrs, rr_labels = dns:parse()
+-- if qs and q_labels then
+-- ...
+-- if rrs and rr_labels then
+-- ...
+-- end
+-- end
+--
+-- The object that describes a DNS message.
+-- .SS Attributes
+-- .TP
+-- includes_dnslen
+-- If non-zero then this indicates that the DNS length is included in the
+-- payload (for example if the transport is TCP) and will affect parsing of it.
+-- .TP
+-- have_dnslen
+-- Set if the dnslen was included in the payload and could be read.
+-- .TP
+-- have_id
+-- Set if there is a DNS ID.
+-- .TP
+-- have_qr
+-- Set if there is a QR flag.
+-- .TP
+-- have_opcode
+-- Set if there is an OPCODE.
+-- .TP
+-- have_aa
+-- Set if there is a AA flag.
+-- .TP
+-- have_tc
+-- Set if there is a TC flag.
+-- .TP
+-- have_rd
+-- Set if there is a RD flag.
+-- .TP
+-- have_ra
+-- Set if there is a RA flag.
+-- .TP
+-- have_z
+-- Set if there is a Z flag.
+-- .TP
+-- have_ad
+-- Set if there is a AD flag.
+-- .TP
+-- have_cd
+-- Set if there is a CD flag.
+-- .TP
+-- have_rcode
+-- Set if there is a RCODE.
+-- .TP
+-- have_qdcount
+-- Set if there is an QDCOUNT.
+-- .TP
+-- have_ancount
+-- Set if there is an ANCOUNT.
+-- .TP
+-- have_nscount
+-- Set if there is a NSCOUNT.
+-- .TP
+-- have_arcount
+-- Set if there is an ARCOUNT.
+-- .TP
+-- dnslen
+-- The DNS length found in the payload.
+-- .TP
+-- id
+-- The DNS ID.
+-- .TP
+-- qr
+-- The QR flag.
+-- .TP
+-- opcode
+-- The OPCODE.
+-- .TP
+-- aa
+-- The AA flag.
+-- .TP
+-- tc
+-- The TC flag.
+-- .TP
+-- rd
+-- The RD flag.
+-- .TP
+-- ra
+-- The RA flag.
+-- .TP
+-- z
+-- The Z flag.
+-- .TP
+-- ad
+-- The AD flag.
+-- .TP
+-- cd
+-- The CD flag.
+-- .TP
+-- rcode
+-- The RCODE.
+-- .TP
+-- qdcount
+-- The QDCOUNT.
+-- .TP
+-- ancount
+-- The ANCOUNT.
+-- .TP
+-- nscount
+-- The NSCOUNT.
+-- .TP
+-- arcount
+-- The ARCOUNT.
+-- .SS Constants
+-- The following tables exists for DNS parameters, taken from
+-- .I https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml
+-- on the 2016-12-09.
+-- .LP
+-- .IR CLASS ,
+-- .IR CLASS_STR ,
+-- .IR TYPE ,
+-- .IR TYPE_STR ,
+-- .IR OPCODE ,
+-- .IR OPCODE_STR ,
+-- .IR RCODE ,
+-- .IR RCODE_STR ,
+-- .IR AFSDB ,
+-- .IR AFSDB_STR ,
+-- .IR DHCID ,
+-- .IR DHCID_STR ,
+-- .IR ENDS0 ,
+-- .IR ENDS0_STR
+-- .LP
+-- The
+-- .I *_STR
+-- tables can be used to get a textual representation of the numbers, see also
+-- .IR class_tostring() ,
+-- .IR type_tostring() ,
+-- .IR opcode_tostring() ,
+-- .IR rcode_tostring() ,
+-- .IR afsdb_tostring() ,
+-- .I dhcid_tostring()
+-- and
+-- .IR edns0_tostring() .
+module(...,package.seeall)
+
+require("dnsjit.core.object.dns_h")
+local label = require("dnsjit.core.object.dns.label")
+local Q = require("dnsjit.core.object.dns.q")
+local RR = require("dnsjit.core.object.dns.rr")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "core_object_dns_t"
+local core_object_dns_t
+local Dns = {
+ CLASS = {
+ IN = 1,
+ CH = 3,
+ HS = 4,
+ NONE = 254,
+ ANY = 255,
+ },
+ TYPE = {
+ 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,
+ RP = 17,
+ AFSDB = 18,
+ X25 = 19,
+ ISDN = 20,
+ RT = 21,
+ NSAP = 22,
+ NSAP_PTR = 23,
+ SIG = 24,
+ KEY = 25,
+ PX = 26,
+ GPOS = 27,
+ AAAA = 28,
+ LOC = 29,
+ NXT = 30,
+ EID = 31,
+ NIMLOC = 32,
+ SRV = 33,
+ ATMA = 34,
+ NAPTR = 35,
+ KX = 36,
+ CERT = 37,
+ A6 = 38,
+ DNAME = 39,
+ SINK = 40,
+ OPT = 41,
+ APL = 42,
+ DS = 43,
+ SSHFP = 44,
+ IPSECKEY = 45,
+ RRSIG = 46,
+ NSEC = 47,
+ DNSKEY = 48,
+ DHCID = 49,
+ NSEC3 = 50,
+ NSEC3PARAM = 51,
+ TLSA = 52,
+ SMIMEA = 53,
+ HIP = 55,
+ NINFO = 56,
+ RKEY = 57,
+ TALINK = 58,
+ CDS = 59,
+ CDNSKEY = 60,
+ OPENPGPKEY = 61,
+ CSYNC = 62,
+ SPF = 99,
+ UINFO = 100,
+ UID = 101,
+ GID = 102,
+ UNSPEC = 103,
+ NID = 104,
+ L32 = 105,
+ L64 = 106,
+ LP = 107,
+ EUI48 = 108,
+ EUI64 = 109,
+ TKEY = 249,
+ TSIG = 250,
+ IXFR = 251,
+ AXFR = 252,
+ MAILB = 253,
+ MAILA = 254,
+ ANY = 255,
+ URI = 256,
+ CAA = 257,
+ AVC = 258,
+ TA = 32768,
+ DLV = 32769,
+ },
+ OPCODE = {
+ QUERY = 0,
+ IQUERY = 1,
+ STATUS = 2,
+ NOTIFY = 4,
+ UPDATE = 5,
+ },
+ RCODE = {
+ NOERROR = 0,
+ FORMERR = 1,
+ SERVFAIL = 2,
+ NXDOMAIN = 3,
+ NOTIMP = 4,
+ REFUSED = 5,
+ YXDOMAIN = 6,
+ YXRRSET = 7,
+ NXRRSET = 8,
+ NOTAUTH = 9,
+ NOTZONE = 10,
+ BADVERS = 16,
+ BADSIG = 16,
+ BADKEY = 17,
+ BADTIME = 18,
+ BADMODE = 19,
+ BADNAME = 20,
+ BADALG = 21,
+ BADTRUNC = 22,
+ BADCOOKIE = 23,
+ },
+ AFSDB = {
+ SUBTYPE_AFS3LOCSRV = 1,
+ SUBTYPE_DCENCA_ROOT = 2,
+ },
+ DHCID = {
+ TYPE_1OCTET = 0,
+ TYPE_DATAOCTET = 1,
+ TYPE_CLIENT_DUID = 2,
+ },
+ EDNS0 = {
+ OPT_LLQ = 1,
+ OPT_UL = 2,
+ OPT_NSID = 3,
+ OPT_DAU = 5,
+ OPT_DHU = 6,
+ OPT_N3U = 7,
+ OPT_CLIENT_SUBNET = 8,
+ OPT_EXPIRE = 9,
+ OPT_COOKIE = 10,
+ OPT_TCP_KEEPALIVE = 11,
+ OPT_PADDING = 12,
+ OPT_CHAIN = 13,
+ OPT_DEVICEID = 26946,
+ },
+}
+local _CLASS = {}
+_CLASS[Dns.CLASS.IN] = "IN"
+_CLASS[Dns.CLASS.CH] = "CH"
+_CLASS[Dns.CLASS.HS] = "HS"
+_CLASS[Dns.CLASS.NONE] = "NONE"
+_CLASS[Dns.CLASS.ANY] = "ANY"
+local _TYPE = {}
+_TYPE[Dns.TYPE.A] = "A"
+_TYPE[Dns.TYPE.NS] = "NS"
+_TYPE[Dns.TYPE.MD] = "MD"
+_TYPE[Dns.TYPE.MF] = "MF"
+_TYPE[Dns.TYPE.CNAME] = "CNAME"
+_TYPE[Dns.TYPE.SOA] = "SOA"
+_TYPE[Dns.TYPE.MB] = "MB"
+_TYPE[Dns.TYPE.MG] = "MG"
+_TYPE[Dns.TYPE.MR] = "MR"
+_TYPE[Dns.TYPE.NULL] = "NULL"
+_TYPE[Dns.TYPE.WKS] = "WKS"
+_TYPE[Dns.TYPE.PTR] = "PTR"
+_TYPE[Dns.TYPE.HINFO] = "HINFO"
+_TYPE[Dns.TYPE.MINFO] = "MINFO"
+_TYPE[Dns.TYPE.MX] = "MX"
+_TYPE[Dns.TYPE.TXT] = "TXT"
+_TYPE[Dns.TYPE.RP] = "RP"
+_TYPE[Dns.TYPE.AFSDB] = "AFSDB"
+_TYPE[Dns.TYPE.X25] = "X25"
+_TYPE[Dns.TYPE.ISDN] = "ISDN"
+_TYPE[Dns.TYPE.RT] = "RT"
+_TYPE[Dns.TYPE.NSAP] = "NSAP"
+_TYPE[Dns.TYPE.NSAP_PTR] = "NSAP_PTR"
+_TYPE[Dns.TYPE.SIG] = "SIG"
+_TYPE[Dns.TYPE.KEY] = "KEY"
+_TYPE[Dns.TYPE.PX] = "PX"
+_TYPE[Dns.TYPE.GPOS] = "GPOS"
+_TYPE[Dns.TYPE.AAAA] = "AAAA"
+_TYPE[Dns.TYPE.LOC] = "LOC"
+_TYPE[Dns.TYPE.NXT] = "NXT"
+_TYPE[Dns.TYPE.EID] = "EID"
+_TYPE[Dns.TYPE.NIMLOC] = "NIMLOC"
+_TYPE[Dns.TYPE.SRV] = "SRV"
+_TYPE[Dns.TYPE.ATMA] = "ATMA"
+_TYPE[Dns.TYPE.NAPTR] = "NAPTR"
+_TYPE[Dns.TYPE.KX] = "KX"
+_TYPE[Dns.TYPE.CERT] = "CERT"
+_TYPE[Dns.TYPE.A6] = "A6"
+_TYPE[Dns.TYPE.DNAME] = "DNAME"
+_TYPE[Dns.TYPE.SINK] = "SINK"
+_TYPE[Dns.TYPE.OPT] = "OPT"
+_TYPE[Dns.TYPE.APL] = "APL"
+_TYPE[Dns.TYPE.DS] = "DS"
+_TYPE[Dns.TYPE.SSHFP] = "SSHFP"
+_TYPE[Dns.TYPE.IPSECKEY] = "IPSECKEY"
+_TYPE[Dns.TYPE.RRSIG] = "RRSIG"
+_TYPE[Dns.TYPE.NSEC] = "NSEC"
+_TYPE[Dns.TYPE.DNSKEY] = "DNSKEY"
+_TYPE[Dns.TYPE.DHCID] = "DHCID"
+_TYPE[Dns.TYPE.NSEC3] = "NSEC3"
+_TYPE[Dns.TYPE.NSEC3PARAM] = "NSEC3PARAM"
+_TYPE[Dns.TYPE.TLSA] = "TLSA"
+_TYPE[Dns.TYPE.SMIMEA] = "SMIMEA"
+_TYPE[Dns.TYPE.HIP] = "HIP"
+_TYPE[Dns.TYPE.NINFO] = "NINFO"
+_TYPE[Dns.TYPE.RKEY] = "RKEY"
+_TYPE[Dns.TYPE.TALINK] = "TALINK"
+_TYPE[Dns.TYPE.CDS] = "CDS"
+_TYPE[Dns.TYPE.CDNSKEY] = "CDNSKEY"
+_TYPE[Dns.TYPE.OPENPGPKEY] = "OPENPGPKEY"
+_TYPE[Dns.TYPE.CSYNC] = "CSYNC"
+_TYPE[Dns.TYPE.SPF] = "SPF"
+_TYPE[Dns.TYPE.UINFO] = "UINFO"
+_TYPE[Dns.TYPE.UID] = "UID"
+_TYPE[Dns.TYPE.GID] = "GID"
+_TYPE[Dns.TYPE.UNSPEC] = "UNSPEC"
+_TYPE[Dns.TYPE.NID] = "NID"
+_TYPE[Dns.TYPE.L32] = "L32"
+_TYPE[Dns.TYPE.L64] = "L64"
+_TYPE[Dns.TYPE.LP] = "LP"
+_TYPE[Dns.TYPE.EUI48] = "EUI48"
+_TYPE[Dns.TYPE.EUI64] = "EUI64"
+_TYPE[Dns.TYPE.TKEY] = "TKEY"
+_TYPE[Dns.TYPE.TSIG] = "TSIG"
+_TYPE[Dns.TYPE.IXFR] = "IXFR"
+_TYPE[Dns.TYPE.AXFR] = "AXFR"
+_TYPE[Dns.TYPE.MAILB] = "MAILB"
+_TYPE[Dns.TYPE.MAILA] = "MAILA"
+_TYPE[Dns.TYPE.ANY] = "ANY"
+_TYPE[Dns.TYPE.URI] = "URI"
+_TYPE[Dns.TYPE.CAA] = "CAA"
+_TYPE[Dns.TYPE.AVC] = "AVC"
+_TYPE[Dns.TYPE.TA] = "TA"
+_TYPE[Dns.TYPE.DLV] = "DLV"
+local _OPCODE = {}
+_OPCODE[Dns.OPCODE.QUERY] = "QUERY"
+_OPCODE[Dns.OPCODE.IQUERY] = "IQUERY"
+_OPCODE[Dns.OPCODE.STATUS] = "STATUS"
+_OPCODE[Dns.OPCODE.NOTIFY] = "NOTIFY"
+_OPCODE[Dns.OPCODE.UPDATE] = "UPDATE"
+local _RCODE = {}
+_RCODE[Dns.RCODE.NOERROR] = "NOERROR"
+_RCODE[Dns.RCODE.FORMERR] = "FORMERR"
+_RCODE[Dns.RCODE.SERVFAIL] = "SERVFAIL"
+_RCODE[Dns.RCODE.NXDOMAIN] = "NXDOMAIN"
+_RCODE[Dns.RCODE.NOTIMP] = "NOTIMP"
+_RCODE[Dns.RCODE.REFUSED] = "REFUSED"
+_RCODE[Dns.RCODE.YXDOMAIN] = "YXDOMAIN"
+_RCODE[Dns.RCODE.YXRRSET] = "YXRRSET"
+_RCODE[Dns.RCODE.NXRRSET] = "NXRRSET"
+_RCODE[Dns.RCODE.NOTAUTH] = "NOTAUTH"
+_RCODE[Dns.RCODE.NOTZONE] = "NOTZONE"
+_RCODE[Dns.RCODE.BADVERS] = "BADVERS"
+_RCODE[Dns.RCODE.BADSIG] = "BADSIG"
+_RCODE[Dns.RCODE.BADKEY] = "BADKEY"
+_RCODE[Dns.RCODE.BADTIME] = "BADTIME"
+_RCODE[Dns.RCODE.BADMODE] = "BADMODE"
+_RCODE[Dns.RCODE.BADNAME] = "BADNAME"
+_RCODE[Dns.RCODE.BADALG] = "BADALG"
+_RCODE[Dns.RCODE.BADTRUNC] = "BADTRUNC"
+_RCODE[Dns.RCODE.BADCOOKIE] = "BADCOOKIE"
+local _AFSDB = {}
+_AFSDB[Dns.AFSDB.SUBTYPE_AFS3LOCSRV] = "SUBTYPE_AFS3LOCSRV"
+_AFSDB[Dns.AFSDB.SUBTYPE_DCENCA_ROOT] = "SUBTYPE_DCENCA_ROOT"
+local _DHCID = {}
+_DHCID[Dns.DHCID.TYPE_1OCTET] = "TYPE_1OCTET"
+_DHCID[Dns.DHCID.TYPE_DATAOCTET] = "TYPE_DATAOCTET"
+_DHCID[Dns.DHCID.TYPE_CLIENT_DUID] = "TYPE_CLIENT_DUID"
+local _EDNS0 = {}
+_EDNS0[Dns.EDNS0.OPT_LLQ] = "OPT_LLQ"
+_EDNS0[Dns.EDNS0.OPT_UL] = "OPT_UL"
+_EDNS0[Dns.EDNS0.OPT_NSID] = "OPT_NSID"
+_EDNS0[Dns.EDNS0.OPT_DAU] = "OPT_DAU"
+_EDNS0[Dns.EDNS0.OPT_DHU] = "OPT_DHU"
+_EDNS0[Dns.EDNS0.OPT_N3U] = "OPT_N3U"
+_EDNS0[Dns.EDNS0.OPT_CLIENT_SUBNET] = "OPT_CLIENT_SUBNET"
+_EDNS0[Dns.EDNS0.OPT_EXPIRE] = "OPT_EXPIRE"
+_EDNS0[Dns.EDNS0.OPT_COOKIE] = "OPT_COOKIE"
+_EDNS0[Dns.EDNS0.OPT_TCP_KEEPALIVE] = "OPT_TCP_KEEPALIVE"
+_EDNS0[Dns.EDNS0.OPT_PADDING] = "OPT_PADDING"
+_EDNS0[Dns.EDNS0.OPT_CHAIN] = "OPT_CHAIN"
+_EDNS0[Dns.EDNS0.OPT_DEVICEID] = "OPT_DEVICEID"
+Dns.CLASS_STR = _CLASS
+Dns.TYPE_STR = _TYPE
+Dns.OPCODE_STR = _OPCODE
+Dns.RCODE_STR = _RCODE
+Dns.AFSDB_STR = _AFSDB
+Dns.DHCID_STR = _DHCID
+Dns.EDNS0_STR = _EDNS0
+
+-- Create a new DNS object, optionally on-top of another object.
+function Dns.new(obj)
+ local self = C.core_object_dns_new()
+ self.obj_prev = obj
+ ffi.gc(self, C.core_object_dns_free)
+ return self
+end
+
+-- Return the textual type of the object.
+function Dns:type()
+ return "dns"
+end
+
+-- Return the previous object.
+function Dns:prev()
+ return self.obj_prev
+end
+
+-- Cast the object to the underlining object module and return it.
+function Dns:cast()
+ return self
+end
+
+-- Cast the object to the generic object module and return it.
+function Dns:uncast()
+ return ffi.cast("core_object_t*", self)
+end
+
+-- Make a copy of the object and return it.
+function Dns:copy()
+ return C.core_object_dns_copy(self)
+end
+
+-- Free the object, should only be used on copies or otherwise allocated.
+function Dns:free()
+ C.core_object_dns_free(self)
+end
+
+-- Return the Log object to control logging of this module.
+function Dns:log()
+ return C.core_object_dns_log()
+end
+
+-- Begin parsing the underlaying object, first the header is parsed then
+-- optionally continue calling
+-- .IR parse_q ()
+-- for the number of questions (see
+-- .IR qdcount ).
+-- After that continue calling
+-- .IR parse_rr ()
+-- for the number of answers, authorities and additionals resource records
+-- (see
+-- .IR ancount ", "
+-- .I nscount
+-- and
+-- .IR arcount ).
+-- Returns 0 on success or negative integer on error which can be for
+-- malformed or truncated DNS (-2) or if more space for labels is needed (-3).
+function Dns:parse_header()
+ return C.core_object_dns_parse_header(self)
+end
+
+-- Parse the next resource record as a question.
+-- Returns 0 on success or negative integer on error which can be for
+-- malformed or truncated DNS (-2) or if more space for labels is needed (-3).
+function Dns:parse_q(q, labels, num_labels)
+ return C.core_object_dns_parse_q(self, q, labels, num_labels)
+end
+
+-- Parse the next resource record.
+-- Returns 0 on success or negative integer on error which can be for
+-- malformed or truncated DNS (-2) or if more space for labels is needed (-3).
+function Dns:parse_rr(rr, labels, num_labels)
+ return C.core_object_dns_parse_rr(self, rr, labels, num_labels)
+end
+
+-- Begin parsing the underlaying object using
+-- .IR parse_header "(), "
+-- .IR parse_q ()
+-- and
+-- .IR parse_rr ().
+-- The optional
+-- .I num_labels
+-- can be used to set a specific number of labels used for each question
+-- and resource record (default 16).
+-- Returns result code, an array of questions, an array of question labels,
+-- an array of resource records and an array of resource records labels.
+-- Result code is 0 on success or negative integer on error which can be for
+-- malformed or truncated DNS (-2) or if more space for labels is needed (-3).
+function Dns:parse(num_labels)
+ local qs, qls, rrs, rrls = {}, {}, {}, {}
+ if num_labels == nil then
+ num_labels = 16
+ end
+
+ ret = self:parse_header()
+ if ret ~= 0 then
+ return ret, qs, qls, rrs, rrls
+ end
+ for n = 1, self.qdcount do
+ local labels = label.new(num_labels)
+ local q = Q.new()
+ local ret = C.core_object_dns_parse_q(self, q, labels, num_labels)
+
+ if ret ~= 0 then
+ return ret, qs, qls, rrs, rrls
+ end
+ table.insert(qs, q)
+ table.insert(qls, labels)
+ end
+ for n = 1, self.ancount do
+ local labels = label.new(num_labels)
+ local rr = RR.new()
+ local ret = C.core_object_dns_parse_rr(self, rr, labels, num_labels)
+
+ if ret ~= 0 then
+ return ret, qs, qls, rrs, rrls
+ end
+ table.insert(rrs, rr)
+ table.insert(rrls, labels)
+ end
+ for n = 1, self.nscount do
+ local labels = label.new(num_labels)
+ local rr = RR.new()
+ local ret = C.core_object_dns_parse_rr(self, rr, labels, num_labels)
+
+ if ret ~= 0 then
+ return ret, qs, qls, rrs, rrls
+ end
+ table.insert(rrs, rr)
+ table.insert(rrls, labels)
+ end
+ for n = 1, self.arcount do
+ local labels = label.new(num_labels)
+ local rr = RR.new()
+ local ret = C.core_object_dns_parse_rr(self, rr, labels, num_labels)
+
+ if ret ~= 0 then
+ return ret, qs, qls, rrs, rrls
+ end
+ table.insert(rrs, rr)
+ table.insert(rrls, labels)
+ end
+
+ return 0, qs, qls, rrs, rrls
+end
+
+-- Begin parsing the underlaying object using
+-- .IR parse_header "(), "
+-- .IR parse_q ()
+-- and
+-- .IR parse_rr (),
+-- and print it's content.
+-- The optional
+-- .I num_labels
+-- can be used to set a specific number of labels used for each question
+-- and resource record (default 16).
+function Dns:print(num_labels)
+ if num_labels == nil then
+ num_labels = 16
+ end
+ local labels = label.new(num_labels)
+ local q = Q.new()
+ local rr = RR.new()
+
+ if self:parse_header() ~= 0 then
+ return
+ end
+
+ local flags = {}
+ if self.have_aa and self.aa == 1 then
+ table.insert(flags, "AA")
+ end
+ if self.have_tc and self.tc == 1 then
+ table.insert(flags, "TC")
+ end
+ if self.have_rd and self.rd == 1 then
+ table.insert(flags, "RD")
+ end
+ if self.have_ra and self.ra == 1 then
+ table.insert(flags, "RA")
+ end
+ if self.have_z and self.z == 1 then
+ table.insert(flags, "Z")
+ end
+ if self.have_ad and self.ad == 1 then
+ table.insert(flags, "AD")
+ end
+ if self.have_cd and self.cd == 1 then
+ table.insert(flags, "CD")
+ end
+
+ print("id:", self.id)
+ print("", "qr:", self.qr)
+ print("", "opcode:", Dns.opcode_tostring(self.opcode))
+ print("", "flags:", table.concat(flags, " "))
+ print("", "rcode:", Dns.rcode_tostring(self.rcode))
+ print("", "qdcount:", self.qdcount)
+ print("", "ancount:", self.ancount)
+ print("", "nscount:", self.nscount)
+ print("", "arcount:", self.arcount)
+
+ if self.qdcount > 0 then
+ print("questions:", "class", "type", "labels")
+ for n = 1, self.qdcount do
+ if C.core_object_dns_parse_q(self, q, labels, num_labels) ~= 0 then
+ return
+ end
+ print("", Dns.class_tostring(q.class), Dns.type_tostring(q.type), label.tooffstr(self, labels, num_labels))
+ end
+ end
+ if self.ancount > 0 then
+ print("answers:", "class", "type", "ttl", "labels", "RR labels")
+ for n = 1, self.ancount do
+ if C.core_object_dns_parse_rr(self, rr, labels, num_labels) ~= 0 then
+ return
+ end
+ if rr.rdata_labels == 0 then
+ print("", Dns.class_tostring(rr.class), Dns.type_tostring(rr.type), rr.ttl, label.tooffstr(self, labels, rr.labels))
+ else
+ print("", Dns.class_tostring(rr.class), Dns.type_tostring(rr.type), rr.ttl, label.tooffstr(self, labels, rr.labels), label.tooffstr(self, labels, rr.rdata_labels, rr.labels))
+ end
+ end
+ end
+ if self.nscount > 0 then
+ print("authorities:", "class", "type", "ttl", "labels", "RR labels")
+ for n = 1, self.nscount do
+ if C.core_object_dns_parse_rr(self, rr, labels, num_labels) ~= 0 then
+ return
+ end
+ if rr.rdata_labels == 0 then
+ print("", Dns.class_tostring(rr.class), Dns.type_tostring(rr.type), rr.ttl, label.tooffstr(self, labels, rr.labels))
+ else
+ print("", Dns.class_tostring(rr.class), Dns.type_tostring(rr.type), rr.ttl, label.tooffstr(self, labels, rr.labels), label.tooffstr(self, labels, rr.rdata_labels, rr.labels))
+ end
+ end
+ end
+ if self.arcount > 0 then
+ print("additionals:", "class", "type", "ttl", "labels", "RR labels")
+ for n = 1, self.arcount do
+ if C.core_object_dns_parse_rr(self, rr, labels, num_labels) ~= 0 then
+ return
+ end
+ if rr.rdata_labels == 0 then
+ print("", Dns.class_tostring(rr.class), Dns.type_tostring(rr.type), rr.ttl, label.tooffstr(self, labels, rr.labels))
+ else
+ print("", Dns.class_tostring(rr.class), Dns.type_tostring(rr.type), rr.ttl, label.tooffstr(self, labels, rr.labels), label.tooffstr(self, labels, rr.rdata_labels, rr.labels))
+ end
+ end
+ end
+end
+
+-- Return the textual name for a class.
+function Dns.class_tostring(class)
+ if Dns.CLASS_STR[class] == nil then
+ return "UNKNOWN("..class..")"
+ end
+ return Dns.CLASS_STR[class]
+end
+
+-- Return the textual name for a type.
+function Dns.type_tostring(type)
+ if Dns.TYPE_STR[type] == nil then
+ return "UNKNOWN("..type..")"
+ end
+ return Dns.TYPE_STR[type]
+end
+
+-- Return the textual name for an opcode.
+function Dns.opcode_tostring(opcode)
+ if Dns.OPCODE_STR[opcode] == nil then
+ return "UNKNOWN("..opcode..")"
+ end
+ return Dns.OPCODE_STR[opcode]
+end
+
+-- Return the textual name for a rcode.
+function Dns.rcode_tostring(rcode)
+ if Dns.RCODE_STR[rcode] == nil then
+ return "UNKNOWN("..rcode..")"
+ end
+ return Dns.RCODE_STR[rcode]
+end
+
+-- Return the textual name for an afsdb subtype.
+function Dns.afsdb_tostring(afsdb)
+ if Dns.AFSDB_STR[afsdb] == nil then
+ return "UNKNOWN("..afsdb..")"
+ end
+ return Dns.AFSDB_STR[afsdb]
+end
+
+-- Return the textual name for a dhcid type.
+function Dns.dhcid_tostring(dhcid)
+ if Dns.DHCID_STR[dhcid] == nil then
+ return "UNKNOWN("..dhcid..")"
+ end
+ return Dns.DHCID_STR[dhcid]
+end
+
+-- Return the textual name for an EDNS0 OPT record.
+function Dns.edns0_tostring(edns0)
+ if Dns.EDNS0_STR[edns0] == nil then
+ return "UNKNOWN("..edns0..")"
+ end
+ return Dns.EDNS0_STR[edns0]
+end
+
+core_object_dns_t = ffi.metatype(t_name, { __index = Dns })
+
+-- dnsjit.core.object (3),
+-- dnsjit.core.object.payload (3),
+-- dnsjit.core.object.dns.label (3),
+-- dnsjit.core.object.dns.q (3),
+-- dnsjit.core.object.dns.rr (3)
+return Dns
diff --git a/src/core/object/dns/label.lua b/src/core/object/dns/label.lua
new file mode 100644
index 0000000..bc78758
--- /dev/null
+++ b/src/core/object/dns/label.lua
@@ -0,0 +1,118 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.object.dns.label
+-- Container of a DNS label
+--
+-- The object that describes a DNS label.
+-- To extract a domain name label first check that
+-- .I have_dn
+-- is set, then use
+-- .I "offset + 1"
+-- to indicate where in the payload the label start and
+-- .I length
+-- for how many bytes long it is.
+-- .SS Attributes
+-- .TP
+-- is_end
+-- .TP
+-- have_length
+-- Set if there is a length.
+-- .TP
+-- have_offset
+-- Set if there is an offset.
+-- .TP
+-- have_extension_bits
+-- Set if there is extension bits.
+-- .TP
+-- have_dn
+-- Set if the label contained a domain name.
+-- .TP
+-- extension_bits
+-- The extension bits.
+-- .TP
+-- length
+-- The length of the domain name.
+-- .TP
+-- offset
+-- If
+-- .I have_dn
+-- is set then this contains the offset within the payload to where this label
+-- start otherwise it contains the offset to another label.
+module(...,package.seeall)
+
+require("dnsjit.core.object.dns_h")
+local ffi = require("ffi")
+
+local Label = {}
+
+-- Create a new array of labels.
+function Label.new(size)
+ return ffi.new("core_object_dns_label_t[?]", size)
+end
+
+-- Returns labels as a string and an offset to the next label.
+-- The string may be nil if the first label was an offset.
+-- The offset may be nil if the last label was an extension bits or end marker.
+function Label.tostring(dns, labels, num_labels, offset_labels)
+ if offset_labels == nil then
+ offset_labels = 0
+ end
+ local dn
+ for n = 1, tonumber(num_labels) do
+ local label = labels[n - 1 + offset_labels]
+
+ if label.have_dn == 1 then
+ if dn == nil then
+ dn = ""
+ end
+ dn = dn .. ffi.string(dns.payload + label.offset + 1, label.length) .. "."
+ elseif label.have_offset == 1 then
+ return dn, label.offset
+ else
+ return dn, nil
+ end
+ end
+ return dn, nil
+end
+
+-- Returns labels as a string which also includes a textual notation of the
+-- offset in the form of
+-- .IR "<offset>label" .
+function Label.tooffstr(dns, labels, num_labels, offset_labels)
+ if offset_labels == nil then
+ offset_labels = 0
+ end
+ local dn = ""
+ for n = 1, tonumber(num_labels) do
+ local label = labels[n - 1 + offset_labels]
+
+ if label.have_dn == 1 then
+ dn = dn .. "<" .. tonumber(label.offset) .. ">" .. ffi.string(dns.payload + label.offset + 1, label.length) .. "."
+ elseif label.have_offset == 1 then
+ dn = dn .. "<" .. tonumber(label.offset) .. ">"
+ break
+ else
+ break
+ end
+ end
+ return dn
+end
+
+-- dnsjit.core.object.dns (3)
+return Label
diff --git a/src/core/object/dns/q.lua b/src/core/object/dns/q.lua
new file mode 100644
index 0000000..f65d9e8
--- /dev/null
+++ b/src/core/object/dns/q.lua
@@ -0,0 +1,52 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.object.dns.q
+-- Container of a DNS question
+--
+-- The object that describes a DNS question.
+-- .SS Attributes
+-- .TP
+-- have_type
+-- Set if there is a type.
+-- .TP
+-- have_class
+-- Set if there is a class.
+-- .TP
+-- type
+-- The type.
+-- .TP
+-- class
+-- The class.
+-- .TP
+-- labels
+-- The number of labels found in the question.
+module(...,package.seeall)
+
+require("dnsjit.core.object.dns_h")
+local ffi = require("ffi")
+
+local Q = {}
+
+-- Create a new question.
+function Q.new(size)
+ return ffi.new("core_object_dns_q_t")
+end
+
+-- dnsjit.core.object.dns (3)
+return Q
diff --git a/src/core/object/dns/rr.lua b/src/core/object/dns/rr.lua
new file mode 100644
index 0000000..d360d96
--- /dev/null
+++ b/src/core/object/dns/rr.lua
@@ -0,0 +1,85 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.object.dns.rr
+-- Container of a DNS resource record
+--
+-- The object that describes a DNS resource record.
+-- .SS Attributes
+-- .TP
+-- have_type
+-- Set if there is a type.
+-- .TP
+-- have_class
+-- Set if there is a class.
+-- .TP
+-- have_ttl
+-- Set if there is a ttl.
+-- .TP
+-- have_rdlength
+-- Set if there is a rdlength.
+-- .TP
+-- have_rdata
+-- Set if there is resource record data.
+-- .TP
+-- have_rdata_labels
+-- Set if there are any labels within the rdata.
+-- .TP
+-- have_padding
+-- Set if there is padding.
+-- .TP
+-- type
+-- The type.
+-- .TP
+-- class
+-- The class.
+-- .TP
+-- ttl
+-- The TTL.
+-- .TP
+-- rdlength
+-- The resource record data length.
+-- .TP
+-- labels
+-- The number of labels found in the record.
+-- .TP
+-- rdata_offset
+-- The offset within the payload for the resource record data.
+-- .TP
+-- rdata_labels
+-- The number of labels found inside the resource record data.
+-- .TP
+-- padding_offset
+-- The offset within the payload where the padding starts.
+-- .TP
+-- padding_length
+-- The length of the padding.
+module(...,package.seeall)
+
+require("dnsjit.core.object.dns_h")
+local ffi = require("ffi")
+
+local Rr = {}
+
+-- Create a new resource record.
+function Rr.new()
+ return ffi.new("core_object_dns_rr_t")
+end
+
+-- dnsjit.core.object.dns (3)
+return Rr
diff --git a/src/core/object/ether.c b/src/core/object/ether.c
new file mode 100644
index 0000000..dd7cd98
--- /dev/null
+++ b/src/core/object/ether.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "core/object/ether.h"
+#include "core/assert.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+core_object_ether_t* core_object_ether_copy(const core_object_ether_t* self)
+{
+ core_object_ether_t* copy;
+ glassert_self();
+
+ glfatal_oom(copy = malloc(sizeof(core_object_ether_t)));
+ memcpy(copy, self, sizeof(core_object_ether_t));
+ copy->obj_prev = 0;
+
+ return copy;
+}
+
+void core_object_ether_free(core_object_ether_t* self)
+{
+ glassert_self();
+ free(self);
+}
diff --git a/src/core/object/ether.h b/src/core/object/ether.h
new file mode 100644
index 0000000..e35ecb2
--- /dev/null
+++ b/src/core/object/ether.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/object.h"
+#include "core/timespec.h"
+
+#ifndef __dnsjit_core_object_ether_h
+#define __dnsjit_core_object_ether_h
+
+#include <stddef.h>
+
+#include "core/object/ether.hh"
+
+#define CORE_OBJECT_ETHER_INIT(prev) \
+ { \
+ CORE_OBJECT_INIT(CORE_OBJECT_ETHER, prev) \
+ , \
+ { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0 }, 0 \
+ }
+
+#endif
diff --git a/src/core/object/ether.hh b/src/core/object/ether.hh
new file mode 100644
index 0000000..3b7a0cc
--- /dev/null
+++ b/src/core/object/ether.hh
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.object_h")
+
+typedef struct core_object_ether {
+ const core_object_t* obj_prev;
+ int32_t obj_type;
+
+ uint8_t dhost[6];
+ uint8_t shost[6];
+ uint16_t type;
+} core_object_ether_t;
+
+core_object_ether_t* core_object_ether_copy(const core_object_ether_t* self);
+void core_object_ether_free(core_object_ether_t* self);
diff --git a/src/core/object/ether.lua b/src/core/object/ether.lua
new file mode 100644
index 0000000..3359d1f
--- /dev/null
+++ b/src/core/object/ether.lua
@@ -0,0 +1,78 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.object.ether
+-- Ether part of a packet
+--
+-- The ether part of a packet that usually can be found in the object chain
+-- after parsing with, for example, Layer filter.
+-- .SS Attributes
+-- .TP
+-- dhost
+-- The destination ether address.
+-- .TP
+-- shost
+-- The source ether address.
+-- .TP
+-- type
+-- The packet type ID field / EtherType field.
+module(...,package.seeall)
+
+require("dnsjit.core.object.ether_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "core_object_ether_t"
+local core_object_ether_t
+local Ether = {}
+
+-- Return the textual type of the object.
+function Ether:type()
+ return "ether"
+end
+
+-- Return the previous object.
+function Ether:prev()
+ return self.obj_prev
+end
+
+-- Cast the object to the underlining object module and return it.
+function Ether:cast()
+ return self
+end
+
+-- Cast the object to the generic object module and return it.
+function Ether:uncast()
+ return ffi.cast("core_object_t*", self)
+end
+
+-- Make a copy of the object and return it.
+function Ether:copy()
+ return C.core_object_ether_copy(self)
+end
+
+-- Free the object, should only be used on copies or otherwise allocated.
+function Ether:free()
+ C.core_object_ether_free(self)
+end
+
+core_object_ether_t = ffi.metatype(t_name, { __index = Ether })
+
+-- dnsjit.core.object (3),
+-- dnsjit.filter.layer (3)
+return Ether
diff --git a/src/core/object/gre.c b/src/core/object/gre.c
new file mode 100644
index 0000000..9a467c3
--- /dev/null
+++ b/src/core/object/gre.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "core/object/gre.h"
+#include "core/assert.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+core_object_gre_t* core_object_gre_copy(const core_object_gre_t* self)
+{
+ core_object_gre_t* copy;
+ glassert_self();
+
+ glfatal_oom(copy = malloc(sizeof(core_object_gre_t)));
+ memcpy(copy, self, sizeof(core_object_gre_t));
+ copy->obj_prev = 0;
+
+ return copy;
+}
+
+void core_object_gre_free(core_object_gre_t* self)
+{
+ glassert_self();
+ free(self);
+}
diff --git a/src/core/object/gre.h b/src/core/object/gre.h
new file mode 100644
index 0000000..8efa93c
--- /dev/null
+++ b/src/core/object/gre.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/object.h"
+#include "core/timespec.h"
+
+#ifndef __dnsjit_core_object_gre_h
+#define __dnsjit_core_object_gre_h
+
+#include <stddef.h>
+
+#include "core/object/gre.hh"
+
+#define CORE_OBJECT_GRE_INIT(prev) \
+ { \
+ CORE_OBJECT_INIT(CORE_OBJECT_GRE, prev) \
+ , \
+ 0, 0, 0, 0, 0 \
+ }
+
+#endif
diff --git a/src/core/object/gre.hh b/src/core/object/gre.hh
new file mode 100644
index 0000000..67a89b1
--- /dev/null
+++ b/src/core/object/gre.hh
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.object_h")
+
+typedef struct core_object_gre {
+ const core_object_t* obj_prev;
+ int32_t obj_type;
+
+ uint16_t gre_flags;
+ uint16_t ether_type;
+ uint16_t checksum;
+ uint16_t offset;
+ uint32_t key;
+ uint32_t sequence;
+ // TODO: routing list, check RFC 1701.
+} core_object_gre_t;
+
+core_object_gre_t* core_object_gre_copy(const core_object_gre_t* self);
+void core_object_gre_free(core_object_gre_t* self);
diff --git a/src/core/object/gre.lua b/src/core/object/gre.lua
new file mode 100644
index 0000000..703b02f
--- /dev/null
+++ b/src/core/object/gre.lua
@@ -0,0 +1,87 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.object.gre
+-- Generic Routing Encapsulation (GRE) part of a packet
+--
+-- The GRE part of a packet that usually can be found in the object chain
+-- after parsing with, for example, Layer filter.
+-- See RFC 1701.
+-- .SS Attributes
+-- .TP
+-- gre_flags
+-- The GRE flags.
+-- .TP
+-- ether_type
+-- The protocol type of the payload packet.
+-- .TP
+-- checksum
+-- The checksum of the GRE header and the payload packet.
+-- .TP
+-- key
+-- The Key field contains a four octet number which was inserted by
+-- the encapsulator.
+-- .TP
+-- sequence
+-- The Sequence Number field contains an unsigned 32 bit integer which is
+-- inserted by the encapsulator.
+module(...,package.seeall)
+
+require("dnsjit.core.object.gre_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "core_object_gre_t"
+local core_object_gre_t
+local Gre = {}
+
+-- Return the textual type of the object.
+function Gre:type()
+ return "gre"
+end
+
+-- Return the previous object.
+function Gre:prev()
+ return self.obj_prev
+end
+
+-- Cast the object to the underlining object module and return it.
+function Gre:cast()
+ return self
+end
+
+-- Cast the object to the generic object module and return it.
+function Gre:uncast()
+ return ffi.cast("core_object_t*", self)
+end
+
+-- Make a copy of the object and return it.
+function Gre:copy()
+ return C.core_object_gre_copy(self)
+end
+
+-- Free the object, should only be used on copies or otherwise allocated.
+function Gre:free()
+ C.core_object_gre_free(self)
+end
+
+core_object_gre_t = ffi.metatype(t_name, { __index = Gre })
+
+-- dnsjit.core.object (3),
+-- dnsjit.filter.layer (3)
+return Gre
diff --git a/src/core/object/icmp.c b/src/core/object/icmp.c
new file mode 100644
index 0000000..ebd13dc
--- /dev/null
+++ b/src/core/object/icmp.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "core/object/icmp.h"
+#include "core/assert.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+core_object_icmp_t* core_object_icmp_copy(const core_object_icmp_t* self)
+{
+ core_object_icmp_t* copy;
+ glassert_self();
+
+ glfatal_oom(copy = malloc(sizeof(core_object_icmp_t)));
+ memcpy(copy, self, sizeof(core_object_icmp_t));
+ copy->obj_prev = 0;
+
+ return copy;
+}
+
+void core_object_icmp_free(core_object_icmp_t* self)
+{
+ glassert_self();
+ free(self);
+}
diff --git a/src/core/object/icmp.h b/src/core/object/icmp.h
new file mode 100644
index 0000000..346a3ea
--- /dev/null
+++ b/src/core/object/icmp.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/object.h"
+#include "core/timespec.h"
+
+#ifndef __dnsjit_core_object_icmp_h
+#define __dnsjit_core_object_icmp_h
+
+#include <stddef.h>
+
+#include "core/object/icmp.hh"
+
+#define CORE_OBJECT_ICMP_INIT(prev) \
+ { \
+ CORE_OBJECT_INIT(CORE_OBJECT_ICMP, prev) \
+ , \
+ 0, 0, 0 \
+ }
+
+#endif
diff --git a/src/core/object/icmp.hh b/src/core/object/icmp.hh
new file mode 100644
index 0000000..1a7aa88
--- /dev/null
+++ b/src/core/object/icmp.hh
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.object_h")
+
+typedef struct core_object_icmp {
+ const core_object_t* obj_prev;
+ int32_t obj_type;
+
+ uint8_t type;
+ uint8_t code;
+ uint16_t cksum;
+} core_object_icmp_t;
+
+core_object_icmp_t* core_object_icmp_copy(const core_object_icmp_t* self);
+void core_object_icmp_free(core_object_icmp_t* self);
diff --git a/src/core/object/icmp.lua b/src/core/object/icmp.lua
new file mode 100644
index 0000000..52062ad
--- /dev/null
+++ b/src/core/object/icmp.lua
@@ -0,0 +1,78 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.object.icmp
+-- An ICMP packet
+--
+-- An ICMP packet which is usually at the top of the object chain
+-- after parsing with, for example, Layer filter.
+-- .SS Attributes
+-- .TP
+-- type
+-- The type of ICMP message.
+-- .TP
+-- code
+-- The (response/error) code for the ICMP type message.
+-- .TP
+-- cksum
+-- The ICMP checksum.
+module(...,package.seeall)
+
+require("dnsjit.core.object.icmp_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "core_object_icmp_t"
+local core_object_icmp_t
+local Icmp = {}
+
+-- Return the textual type of the object.
+function Icmp:type()
+ return "icmp"
+end
+
+-- Return the previous object.
+function Icmp:prev()
+ return self.obj_prev
+end
+
+-- Cast the object to the underlining object module and return it.
+function Icmp:cast()
+ return self
+end
+
+-- Cast the object to the generic object module and return it.
+function Icmp:uncast()
+ return ffi.cast("core_object_t*", self)
+end
+
+-- Make a copy of the object and return it.
+function Icmp:copy()
+ return C.core_object_icmp_copy(self)
+end
+
+-- Free the object, should only be used on copies or otherwise allocated.
+function Icmp:free()
+ C.core_object_icmp_free(self)
+end
+
+core_object_icmp_t = ffi.metatype(t_name, { __index = Icmp })
+
+-- dnsjit.core.object (3),
+-- dnsjit.filter.layer (3)
+return Icmp
diff --git a/src/core/object/icmp6.c b/src/core/object/icmp6.c
new file mode 100644
index 0000000..b79fb17
--- /dev/null
+++ b/src/core/object/icmp6.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "core/object/icmp6.h"
+#include "core/assert.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+core_object_icmp6_t* core_object_icmp6_copy(const core_object_icmp6_t* self)
+{
+ core_object_icmp6_t* copy;
+ glassert_self();
+
+ glfatal_oom(copy = malloc(sizeof(core_object_icmp6_t)));
+ memcpy(copy, self, sizeof(core_object_icmp6_t));
+ copy->obj_prev = 0;
+
+ return copy;
+}
+
+void core_object_icmp6_free(core_object_icmp6_t* self)
+{
+ glassert_self();
+ free(self);
+}
diff --git a/src/core/object/icmp6.h b/src/core/object/icmp6.h
new file mode 100644
index 0000000..569bf7f
--- /dev/null
+++ b/src/core/object/icmp6.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/object.h"
+#include "core/timespec.h"
+
+#ifndef __dnsjit_core_object_icmp6_h
+#define __dnsjit_core_object_icmp6_h
+
+#include <stddef.h>
+
+#include "core/object/icmp6.hh"
+
+#define CORE_OBJECT_ICMP6_INIT(prev) \
+ { \
+ CORE_OBJECT_INIT(CORE_OBJECT_ICMP6, prev) \
+ , \
+ 0, 0, 0 \
+ }
+
+#endif
diff --git a/src/core/object/icmp6.hh b/src/core/object/icmp6.hh
new file mode 100644
index 0000000..2aa1ed4
--- /dev/null
+++ b/src/core/object/icmp6.hh
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.object_h")
+
+typedef struct core_object_icmp6 {
+ const core_object_t* obj_prev;
+ int32_t obj_type;
+
+ uint8_t type;
+ uint8_t code;
+ uint16_t cksum;
+} core_object_icmp6_t;
+
+core_object_icmp6_t* core_object_icmp6_copy(const core_object_icmp6_t* self);
+void core_object_icmp6_free(core_object_icmp6_t* self);
diff --git a/src/core/object/icmp6.lua b/src/core/object/icmp6.lua
new file mode 100644
index 0000000..a3929bf
--- /dev/null
+++ b/src/core/object/icmp6.lua
@@ -0,0 +1,78 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.object.icmp6
+-- An ICMPv6 packet
+--
+-- An ICMPv6 packet which is usually at the top of the object chain
+-- after parsing with, for example, Layer filter.
+-- .SS Attributes
+-- .TP
+-- type
+-- The type of ICMPv6 message.
+-- .TP
+-- code
+-- The (response/error) code for the ICMPv6 type message.
+-- .TP
+-- cksum
+-- The ICMPv6 checksum.
+module(...,package.seeall)
+
+require("dnsjit.core.object.icmp6_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "core_object_icmp6_t"
+local core_object_icmp6_t
+local Icmp6 = {}
+
+-- Return the textual type of the object.
+function Icmp6:type()
+ return "icmp6"
+end
+
+-- Return the previous object.
+function Icmp6:prev()
+ return self.obj_prev
+end
+
+-- Cast the object to the underlining object module and return it.
+function Icmp6:cast()
+ return self
+end
+
+-- Cast the object to the generic object module and return it.
+function Icmp6:uncast()
+ return ffi.cast("core_object_t*", self)
+end
+
+-- Make a copy of the object and return it.
+function Icmp6:copy()
+ return C.core_object_icmp6_copy(self)
+end
+
+-- Free the object, should only be used on copies or otherwise allocated.
+function Icmp6:free()
+ C.core_object_icmp6_free(self)
+end
+
+core_object_icmp6_t = ffi.metatype(t_name, { __index = Icmp6 })
+
+-- dnsjit.core.object (3),
+-- dnsjit.filter.layer (3)
+return Icmp6
diff --git a/src/core/object/ieee802.c b/src/core/object/ieee802.c
new file mode 100644
index 0000000..80dd4ea
--- /dev/null
+++ b/src/core/object/ieee802.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "core/object/ieee802.h"
+#include "core/assert.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+core_object_ieee802_t* core_object_ieee802_copy(const core_object_ieee802_t* self)
+{
+ core_object_ieee802_t* copy;
+ glassert_self();
+
+ glfatal_oom(copy = malloc(sizeof(core_object_ieee802_t)));
+ memcpy(copy, self, sizeof(core_object_ieee802_t));
+ copy->obj_prev = 0;
+
+ return copy;
+}
+
+void core_object_ieee802_free(core_object_ieee802_t* self)
+{
+ glassert_self();
+ free(self);
+}
diff --git a/src/core/object/ieee802.h b/src/core/object/ieee802.h
new file mode 100644
index 0000000..ca4847c
--- /dev/null
+++ b/src/core/object/ieee802.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/object.h"
+#include "core/timespec.h"
+
+#ifndef __dnsjit_core_object_ieee802_h
+#define __dnsjit_core_object_ieee802_h
+
+#include <stddef.h>
+
+#include "core/object/ieee802.hh"
+
+#define CORE_OBJECT_IEEE802_INIT(prev) \
+ { \
+ CORE_OBJECT_INIT(CORE_OBJECT_IEEE802, prev) \
+ , \
+ 0, 0, 0, 0, 0 \
+ }
+
+#endif
diff --git a/src/core/object/ieee802.hh b/src/core/object/ieee802.hh
new file mode 100644
index 0000000..1bce670
--- /dev/null
+++ b/src/core/object/ieee802.hh
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.object_h")
+
+typedef struct core_object_ieee802 {
+ const core_object_t* obj_prev;
+ int32_t obj_type;
+
+ uint16_t tpid;
+ uint8_t pcp;
+ uint8_t dei;
+ uint8_t vid;
+ uint16_t ether_type;
+} core_object_ieee802_t;
+
+core_object_ieee802_t* core_object_ieee802_copy(const core_object_ieee802_t* self);
+void core_object_ieee802_free(core_object_ieee802_t* self);
diff --git a/src/core/object/ieee802.lua b/src/core/object/ieee802.lua
new file mode 100644
index 0000000..77519ef
--- /dev/null
+++ b/src/core/object/ieee802.lua
@@ -0,0 +1,84 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.object.ieee802
+-- IEEE802 part of a packet
+--
+-- The IEEE802 part of a packet that usually can be found in the object chain
+-- after parsing with, for example, Layer filter.
+-- .SS Attributes
+-- .TP
+-- tpid
+-- Tag protocol identifier.
+-- .TP
+-- pcp
+-- Priority code point.
+-- .TP
+-- dei
+-- Drop eligible indicator.
+-- .TP
+-- vid
+-- VLAN identifier.
+-- .TP
+-- ether_type
+-- The packet type ID field / EtherType field.
+module(...,package.seeall)
+
+require("dnsjit.core.object.ieee802_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "core_object_ieee802_t"
+local core_object_ieee802_t
+local Ieee802 = {}
+
+-- Return the textual type of the object.
+function Ieee802:type()
+ return "ieee802"
+end
+
+-- Return the previous object.
+function Ieee802:prev()
+ return self.obj_prev
+end
+
+-- Cast the object to the underlining object module and return it.
+function Ieee802:cast()
+ return self
+end
+
+-- Cast the object to the generic object module and return it.
+function Ieee802:uncast()
+ return ffi.cast("core_object_t*", self)
+end
+
+-- Make a copy of the object and return it.
+function Ieee802:copy()
+ return C.core_object_ieee802_copy(self)
+end
+
+-- Free the object, should only be used on copies or otherwise allocated.
+function Ieee802:free()
+ C.core_object_ieee802_free(self)
+end
+
+core_object_ieee802_t = ffi.metatype(t_name, { __index = Ieee802 })
+
+-- dnsjit.core.object (3),
+-- dnsjit.filter.layer (3)
+return Ieee802
diff --git a/src/core/object/ip.c b/src/core/object/ip.c
new file mode 100644
index 0000000..4e50b60
--- /dev/null
+++ b/src/core/object/ip.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "core/object/ip.h"
+#include "core/assert.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+core_object_ip_t* core_object_ip_copy(const core_object_ip_t* self)
+{
+ core_object_ip_t* copy;
+ glassert_self();
+
+ glfatal_oom(copy = malloc(sizeof(core_object_ip_t)));
+ memcpy(copy, self, sizeof(core_object_ip_t));
+ copy->obj_prev = 0;
+
+ return copy;
+}
+
+void core_object_ip_free(core_object_ip_t* self)
+{
+ glassert_self();
+ free(self);
+}
diff --git a/src/core/object/ip.h b/src/core/object/ip.h
new file mode 100644
index 0000000..c2b9312
--- /dev/null
+++ b/src/core/object/ip.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/object.h"
+#include "core/timespec.h"
+
+#ifndef __dnsjit_core_object_ip_h
+#define __dnsjit_core_object_ip_h
+
+#include <stddef.h>
+
+#include "core/object/ip.hh"
+
+#define CORE_OBJECT_IP_INIT(prev) \
+ { \
+ CORE_OBJECT_INIT(CORE_OBJECT_IP, prev) \
+ , \
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, \
+ }
+
+#endif
diff --git a/src/core/object/ip.hh b/src/core/object/ip.hh
new file mode 100644
index 0000000..9a379a4
--- /dev/null
+++ b/src/core/object/ip.hh
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.object_h")
+
+typedef struct core_object_ip {
+ const core_object_t* obj_prev;
+ int32_t obj_type;
+
+ uint8_t v;
+ uint8_t hl;
+ uint8_t tos;
+ uint16_t len;
+ uint16_t id;
+ uint16_t off;
+ uint8_t ttl;
+ uint8_t p;
+ uint16_t sum;
+ uint8_t src[4], dst[4];
+} core_object_ip_t;
+
+core_object_ip_t* core_object_ip_copy(const core_object_ip_t* self);
+void core_object_ip_free(core_object_ip_t* self);
diff --git a/src/core/object/ip.lua b/src/core/object/ip.lua
new file mode 100644
index 0000000..0294612
--- /dev/null
+++ b/src/core/object/ip.lua
@@ -0,0 +1,122 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.object.ip
+-- An IP packet
+--
+-- An IP packet that usually can be found in the object chain
+-- after parsing with, for example, Layer filter.
+-- .SS Attributes
+-- .TP
+-- v
+-- Version.
+-- .TP
+-- hl
+-- Header length.
+-- .TP
+-- tos
+-- Type of service.
+-- .TP
+-- len
+-- Total length.
+-- .TP
+-- id
+-- Identification.
+-- .TP
+-- off
+-- Fragment offset field.
+-- .TP
+-- ttl
+-- Time to live.
+-- .TP
+-- p
+-- Protocol.
+-- .TP
+-- sum
+-- Checksum.
+-- .TP
+-- src
+-- Source address.
+-- .TP
+-- dst
+-- Destination address.
+-- .TP
+-- payload
+-- A pointer to the payload.
+-- .TP
+-- plen
+-- The length of the payload.
+-- .TP
+-- pad_len
+-- The length of padding found, if any.
+module(...,package.seeall)
+
+require("dnsjit.core.object.ip_h")
+local ffi = require("ffi")
+local C = ffi.C
+local libip = require("dnsjit.lib.ip")
+
+local t_name = "core_object_ip_t"
+local core_object_ip_t
+local Ip = {}
+
+-- Return the textual type of the object.
+function Ip:type()
+ return "ip"
+end
+
+-- Return the previous object.
+function Ip:prev()
+ return self.obj_prev
+end
+
+-- Cast the object to the underlining object module and return it.
+function Ip:cast()
+ return self
+end
+
+-- Cast the object to the generic object module and return it.
+function Ip:uncast()
+ return ffi.cast("core_object_t*", self)
+end
+
+-- Make a copy of the object and return it.
+function Ip:copy()
+ return C.core_object_ip_copy(self)
+end
+
+-- Free the object, should only be used on copies or otherwise allocated.
+function Ip:free()
+ C.core_object_ip_free(self)
+end
+
+-- Return the IP source as a string.
+function Ip:source()
+ return libip.ipstring(self.src)
+end
+
+-- Return the IP destination as a string.
+function Ip:destination()
+ return libip.ipstring(self.dst)
+end
+
+core_object_ip_t = ffi.metatype(t_name, { __index = Ip })
+
+-- dnsjit.core.object (3),
+-- dnsjit.filter.layer (3)
+return Ip
diff --git a/src/core/object/ip6.c b/src/core/object/ip6.c
new file mode 100644
index 0000000..45db361
--- /dev/null
+++ b/src/core/object/ip6.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "core/object/ip6.h"
+#include "core/assert.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+core_object_ip6_t* core_object_ip6_copy(const core_object_ip6_t* self)
+{
+ core_object_ip6_t* copy;
+ glassert_self();
+
+ glfatal_oom(copy = malloc(sizeof(core_object_ip6_t)));
+ memcpy(copy, self, sizeof(core_object_ip6_t));
+ copy->obj_prev = 0;
+
+ return copy;
+}
+
+void core_object_ip6_free(core_object_ip6_t* self)
+{
+ glassert_self();
+ free(self);
+}
diff --git a/src/core/object/ip6.h b/src/core/object/ip6.h
new file mode 100644
index 0000000..711f1a5
--- /dev/null
+++ b/src/core/object/ip6.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/object.h"
+#include "core/timespec.h"
+
+#ifndef __dnsjit_core_object_ip6_h
+#define __dnsjit_core_object_ip6_h
+
+#include <stddef.h>
+
+#include "core/object/ip6.hh"
+
+#define CORE_OBJECT_IP6_INIT(prev) \
+ { \
+ CORE_OBJECT_INIT(CORE_OBJECT_IP6, prev) \
+ , \
+ 0, 0, 0, 0, \
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, \
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, \
+ 0, 0, 0, 0, \
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, \
+ }
+
+#endif
diff --git a/src/core/object/ip6.hh b/src/core/object/ip6.hh
new file mode 100644
index 0000000..5411dda
--- /dev/null
+++ b/src/core/object/ip6.hh
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.object_h")
+
+typedef struct core_object_ip6 {
+ const core_object_t* obj_prev;
+ int32_t obj_type;
+
+ uint32_t flow;
+ uint16_t plen;
+ uint8_t nxt;
+ uint8_t hlim;
+ uint8_t src[16];
+ uint8_t dst[16];
+
+ uint8_t is_frag;
+ uint8_t have_rtdst;
+ uint16_t frag_offlg;
+ uint16_t frag_ident;
+ uint8_t rtdst[16];
+} core_object_ip6_t;
+
+core_object_ip6_t* core_object_ip6_copy(const core_object_ip6_t* self);
+void core_object_ip6_free(core_object_ip6_t* self);
diff --git a/src/core/object/ip6.lua b/src/core/object/ip6.lua
new file mode 100644
index 0000000..5271be3
--- /dev/null
+++ b/src/core/object/ip6.lua
@@ -0,0 +1,130 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.object.ip6
+-- An IPv6 packet
+--
+-- An IPv6 packet that usually can be found in the object chain
+-- after parsing with, for example, Layer filter.
+-- .SS Attributes
+-- .TP
+-- flow
+-- 4 bits version, 8 bits TC and 20 bits flow-ID.
+-- .TP
+-- plen
+-- Payload length (as in the IPv6 header).
+-- .TP
+-- nxt
+-- Next header.
+-- .TP
+-- hlim
+-- Hop limit.
+-- .TP
+-- src
+-- Source address.
+-- .TP
+-- dst
+-- Destination address.
+-- .TP
+-- is_frag
+-- 1 bit, set if packet is a fragment.
+-- .TP
+-- have_rtdst
+-- 1 bit, set if
+-- .I rtdst
+-- is set.
+-- .TP
+-- frag_offlg
+-- Offset, reserved, and flag taken from the fragment header.
+-- .TP
+-- frag_ident
+-- Identification taken from the fragment header.
+-- .TP
+-- rtdst
+-- Destination address found in the routing extension header.
+-- .TP
+-- payload
+-- A pointer to the payload.
+-- .TP
+-- len
+-- The length of the payload.
+-- .TP
+-- pad_len
+-- The length of padding found, if any.
+module(...,package.seeall)
+
+require("dnsjit.core.object.ip6_h")
+local ffi = require("ffi")
+local C = ffi.C
+local libip = require("dnsjit.lib.ip")
+
+local t_name = "core_object_ip6_t"
+local core_object_ip6_t
+local Ip6 = {}
+
+-- Return the textual type of the object.
+function Ip6:type()
+ return "ip6"
+end
+
+-- Return the previous object.
+function Ip6:prev()
+ return self.obj_prev
+end
+
+-- Cast the object to the underlining object module and return it.
+function Ip6:cast()
+ return self
+end
+
+-- Cast the object to the generic object module and return it.
+function Ip6:uncast()
+ return ffi.cast("core_object_t*", self)
+end
+
+-- Make a copy of the object and return it.
+function Ip6:copy()
+ return C.core_object_ip6_copy(self)
+end
+
+-- Free the object, should only be used on copies or otherwise allocated.
+function Ip6:free()
+ C.core_object_ip6_free(self)
+end
+
+-- Return the IPv6 source as a string.
+-- If
+-- .I pretty
+-- is true then return an easier to read IPv6 address.
+function Ip6:source(pretty)
+ return libip.ip6string(self.src, pretty)
+end
+
+-- Return the IPv6 destination as a string.
+-- If
+-- .I pretty
+-- is true then return an easier to read IPv6 address.
+function Ip6:destination(pretty)
+ return libip.ip6string(self.dst, pretty)
+end
+
+core_object_ip6_t = ffi.metatype(t_name, { __index = Ip6 })
+
+-- dnsjit.core.object (3),
+-- dnsjit.filter.layer (3)
+return Ip6
diff --git a/src/core/object/linuxsll.c b/src/core/object/linuxsll.c
new file mode 100644
index 0000000..cacd23a
--- /dev/null
+++ b/src/core/object/linuxsll.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "core/object/linuxsll.h"
+#include "core/assert.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+core_object_linuxsll_t* core_object_linuxsll_copy(const core_object_linuxsll_t* self)
+{
+ core_object_linuxsll_t* copy;
+ glassert_self();
+
+ glfatal_oom(copy = malloc(sizeof(core_object_linuxsll_t)));
+ memcpy(copy, self, sizeof(core_object_linuxsll_t));
+ copy->obj_prev = 0;
+
+ return copy;
+}
+
+void core_object_linuxsll_free(core_object_linuxsll_t* self)
+{
+ glassert_self();
+ free(self);
+}
diff --git a/src/core/object/linuxsll.h b/src/core/object/linuxsll.h
new file mode 100644
index 0000000..9484c0d
--- /dev/null
+++ b/src/core/object/linuxsll.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/object.h"
+#include "core/timespec.h"
+
+#ifndef __dnsjit_core_object_linuxsll_h
+#define __dnsjit_core_object_linuxsll_h
+
+#include <stddef.h>
+
+#include "core/object/linuxsll.hh"
+
+#define CORE_OBJECT_LINUXSLL_INIT(prev) \
+ { \
+ CORE_OBJECT_INIT(CORE_OBJECT_LINUXSLL, prev) \
+ , \
+ 0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0 \
+ }
+
+#endif
diff --git a/src/core/object/linuxsll.hh b/src/core/object/linuxsll.hh
new file mode 100644
index 0000000..e808f96
--- /dev/null
+++ b/src/core/object/linuxsll.hh
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.object_h")
+
+typedef struct core_object_linuxsll {
+ const core_object_t* obj_prev;
+ int32_t obj_type;
+
+ uint16_t packet_type;
+ uint16_t arp_hardware;
+ uint16_t link_layer_address_length;
+ uint8_t link_layer_address[8];
+ uint16_t ether_type;
+} core_object_linuxsll_t;
+
+core_object_linuxsll_t* core_object_linuxsll_copy(const core_object_linuxsll_t* self);
+void core_object_linuxsll_free(core_object_linuxsll_t* self);
diff --git a/src/core/object/linuxsll.lua b/src/core/object/linuxsll.lua
new file mode 100644
index 0000000..6b47525
--- /dev/null
+++ b/src/core/object/linuxsll.lua
@@ -0,0 +1,84 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.object.linuxsll
+-- Linux cooked-mode capture (SLL) part of a packet
+--
+-- The SLL part of a packet that usually can be found in the object chain
+-- after parsing with, for example, Layer filter.
+-- .SS Attributes
+-- .TP
+-- packet_type
+-- The packet type.
+-- .TP
+-- arp_hardware
+-- The link-layer device type.
+-- .TP
+-- link_layer_address_length
+-- The length of the link-layer address.
+-- .TP
+-- link_layer_address
+-- The link-layer address.
+-- .TP
+-- ether_type
+-- An Ethernet protocol type.
+module(...,package.seeall)
+
+require("dnsjit.core.object.linuxsll_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "core_object_linuxsll_t"
+local core_object_linuxsll_t
+local Linuxsll = {}
+
+-- Return the textual type of the object.
+function Linuxsll:type()
+ return "linuxsll"
+end
+
+-- Return the previous object.
+function Linuxsll:prev()
+ return self.obj_prev
+end
+
+-- Cast the object to the underlining object module and return it.
+function Linuxsll:cast()
+ return self
+end
+
+-- Cast the object to the generic object module and return it.
+function Linuxsll:uncast()
+ return ffi.cast("core_object_t*", self)
+end
+
+-- Make a copy of the object and return it.
+function Linuxsll:copy()
+ return C.core_object_linuxsll_copy(self)
+end
+
+-- Free the object, should only be used on copies or otherwise allocated.
+function Linuxsll:free()
+ C.core_object_linuxsll_free(self)
+end
+
+core_object_linuxsll_t = ffi.metatype(t_name, { __index = Linuxsll })
+
+-- dnsjit.core.object (3).
+-- dnsjit.filter.layer (3)
+return Linuxsll
diff --git a/src/core/object/loop.c b/src/core/object/loop.c
new file mode 100644
index 0000000..4149f71
--- /dev/null
+++ b/src/core/object/loop.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "core/object/loop.h"
+#include "core/assert.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+core_object_loop_t* core_object_loop_copy(const core_object_loop_t* self)
+{
+ core_object_loop_t* copy;
+ glassert_self();
+
+ glfatal_oom(copy = malloc(sizeof(core_object_loop_t)));
+ memcpy(copy, self, sizeof(core_object_loop_t));
+ copy->obj_prev = 0;
+
+ return copy;
+}
+
+void core_object_loop_free(core_object_loop_t* self)
+{
+ glassert_self();
+ free(self);
+}
diff --git a/src/core/object/loop.h b/src/core/object/loop.h
new file mode 100644
index 0000000..1d8670f
--- /dev/null
+++ b/src/core/object/loop.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/object.h"
+#include "core/timespec.h"
+
+#ifndef __dnsjit_core_object_loop_h
+#define __dnsjit_core_object_loop_h
+
+#include <stddef.h>
+
+#include "core/object/loop.hh"
+
+#define CORE_OBJECT_LOOP_INIT(prev) \
+ { \
+ CORE_OBJECT_INIT(CORE_OBJECT_LOOP, prev) \
+ , \
+ 0 \
+ }
+
+#endif
diff --git a/src/core/object/loop.hh b/src/core/object/loop.hh
new file mode 100644
index 0000000..e8c5573
--- /dev/null
+++ b/src/core/object/loop.hh
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.object_h")
+
+typedef struct core_object_loop {
+ const core_object_t* obj_prev;
+ int32_t obj_type;
+
+ uint32_t family;
+} core_object_loop_t;
+
+core_object_loop_t* core_object_loop_copy(const core_object_loop_t* self);
+void core_object_loop_free(core_object_loop_t* self);
diff --git a/src/core/object/loop.lua b/src/core/object/loop.lua
new file mode 100644
index 0000000..2cb441f
--- /dev/null
+++ b/src/core/object/loop.lua
@@ -0,0 +1,72 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.object.loop
+-- OpenBSD loopback encapsulation (loop) part of a packet
+--
+-- The loop part of a packet that usually can be found in the object chain
+-- after parsing with, for example, Layer filter.
+-- .SS Attributes
+-- .TP
+-- family
+-- The link-layer header describing what type of packet is encapsulated.
+module(...,package.seeall)
+
+require("dnsjit.core.object.loop_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "core_object_loop_t"
+local core_object_loop_t
+local Loop = {}
+
+-- Return the textual type of the object.
+function Loop:type()
+ return "loop"
+end
+
+-- Return the previous object.
+function Loop:prev()
+ return self.obj_prev
+end
+
+-- Cast the object to the underlining object module and return it.
+function Loop:cast()
+ return self
+end
+
+-- Cast the object to the generic object module and return it.
+function Loop:uncast()
+ return ffi.cast("core_object_t*", self)
+end
+
+-- Make a copy of the object and return it.
+function Loop:copy()
+ return C.core_object_loop_copy(self)
+end
+
+-- Free the object, should only be used on copies or otherwise allocated.
+function Loop:free()
+ C.core_object_loop_free(self)
+end
+
+core_object_loop_t = ffi.metatype(t_name, { __index = Loop })
+
+-- dnsjit.core.object (3),
+-- dnsjit.filter.layer (3)
+return Loop
diff --git a/src/core/object/null.c b/src/core/object/null.c
new file mode 100644
index 0000000..7fe617a
--- /dev/null
+++ b/src/core/object/null.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "core/object/null.h"
+#include "core/assert.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+core_object_null_t* core_object_null_copy(const core_object_null_t* self)
+{
+ core_object_null_t* copy;
+ glassert_self();
+
+ glfatal_oom(copy = malloc(sizeof(core_object_null_t)));
+ memcpy(copy, self, sizeof(core_object_null_t));
+ copy->obj_prev = 0;
+
+ return copy;
+}
+
+void core_object_null_free(core_object_null_t* self)
+{
+ glassert_self();
+ free(self);
+}
diff --git a/src/core/object/null.h b/src/core/object/null.h
new file mode 100644
index 0000000..0317fce
--- /dev/null
+++ b/src/core/object/null.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/object.h"
+#include "core/timespec.h"
+
+#ifndef __dnsjit_core_object_null_h
+#define __dnsjit_core_object_null_h
+
+#include <stddef.h>
+
+#include "core/object/null.hh"
+
+#define CORE_OBJECT_NULL_INIT(prev) \
+ { \
+ CORE_OBJECT_INIT(CORE_OBJECT_NULL, prev) \
+ , \
+ 0 \
+ }
+
+#endif
diff --git a/src/core/object/null.hh b/src/core/object/null.hh
new file mode 100644
index 0000000..2e6c639
--- /dev/null
+++ b/src/core/object/null.hh
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.object_h")
+
+typedef struct core_object_null {
+ const core_object_t* obj_prev;
+ int32_t obj_type;
+
+ uint32_t family;
+} core_object_null_t;
+
+core_object_null_t* core_object_null_copy(const core_object_null_t* self);
+void core_object_null_free(core_object_null_t* self);
diff --git a/src/core/object/null.lua b/src/core/object/null.lua
new file mode 100644
index 0000000..e6dfc5d
--- /dev/null
+++ b/src/core/object/null.lua
@@ -0,0 +1,72 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.object.null
+-- BSD loopback encapsulation (null) part of a packet
+--
+-- The null part of a packet that usually can be found in the object chain
+-- after parsing with, for example, Layer filter.
+-- .SS Attributes
+-- .TP
+-- family
+-- The link-layer header describing what type of packet is encapsulated.
+module(...,package.seeall)
+
+require("dnsjit.core.object.null_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "core_object_null_t"
+local core_object_null_t
+local Null = {}
+
+-- Return the textual type of the object.
+function Null:type()
+ return "null"
+end
+
+-- Return the previous object.
+function Null:prev()
+ return self.obj_prev
+end
+
+-- Cast the object to the underlining object module and return it.
+function Null:cast()
+ return self
+end
+
+-- Cast the object to the generic object module and return it.
+function Null:uncast()
+ return ffi.cast("core_object_t*", self)
+end
+
+-- Make a copy of the object and return it.
+function Null:copy()
+ return C.core_object_null_copy(self)
+end
+
+-- Free the object, should only be used on copies or otherwise allocated.
+function Null:free()
+ C.core_object_null_free(self)
+end
+
+core_object_null_t = ffi.metatype(t_name, { __index = Null })
+
+-- dnsjit.core.object (3),
+-- dnsjit.filter.layer (3)
+return Null
diff --git a/src/core/object/payload.c b/src/core/object/payload.c
new file mode 100644
index 0000000..d6d2ee4
--- /dev/null
+++ b/src/core/object/payload.c
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "core/object/payload.h"
+#include "core/assert.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+core_object_payload_t* core_object_payload_copy(const core_object_payload_t* self)
+{
+ core_object_payload_t* copy;
+ glassert_self();
+
+ glfatal_oom(copy = malloc(sizeof(core_object_payload_t) + self->len + self->padding));
+ memcpy(copy, self, sizeof(core_object_payload_t));
+ copy->obj_prev = 0;
+
+ if (copy->payload) {
+ copy->payload = (void*)copy + sizeof(core_object_payload_t);
+ memcpy((void*)copy->payload, self->payload, self->len + self->padding);
+ }
+
+ return copy;
+}
+
+void core_object_payload_free(core_object_payload_t* self)
+{
+ glassert_self();
+ free(self);
+}
diff --git a/src/core/object/payload.h b/src/core/object/payload.h
new file mode 100644
index 0000000..4b42a7c
--- /dev/null
+++ b/src/core/object/payload.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/object.h"
+#include "core/timespec.h"
+
+#ifndef __dnsjit_core_object_payload_h
+#define __dnsjit_core_object_payload_h
+
+#include <stddef.h>
+
+#include "core/object/payload.hh"
+
+#define CORE_OBJECT_PAYLOAD_INIT(prev) \
+ { \
+ CORE_OBJECT_INIT(CORE_OBJECT_PAYLOAD, prev) \
+ , \
+ 0, 0, 0 \
+ }
+
+#endif
diff --git a/src/core/object/payload.hh b/src/core/object/payload.hh
new file mode 100644
index 0000000..fc2ad71
--- /dev/null
+++ b/src/core/object/payload.hh
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.object_h")
+
+typedef struct core_object_payload {
+ const core_object_t* obj_prev;
+ int32_t obj_type;
+
+ const uint8_t* payload;
+ size_t len, padding;
+} core_object_payload_t;
+
+core_object_payload_t* core_object_payload_copy(const core_object_payload_t* self);
+void core_object_payload_free(core_object_payload_t* self);
diff --git a/src/core/object/payload.lua b/src/core/object/payload.lua
new file mode 100644
index 0000000..61ea867
--- /dev/null
+++ b/src/core/object/payload.lua
@@ -0,0 +1,83 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.object.payload
+-- Application data payload
+--
+-- Payload object contains the data carried by the underlying transport
+-- protocol.
+-- Payload is usually at the top of the object chain after parsing with,
+-- for example,
+-- .IR dnsjit.filter.layer .
+-- .SS Attributes
+-- .TP
+-- payload
+-- A pointer to the payload.
+-- .TP
+-- len
+-- The length of the payload.
+-- .TP
+-- padding
+-- The length of padding in the underlying packet.
+module(...,package.seeall)
+
+require("dnsjit.core.object.payload_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "core_object_payload_t"
+local core_object_payload_t
+local Payload = {}
+
+-- Return the textual type of the object.
+function Payload:type()
+ return "payload"
+end
+
+-- Return the previous object.
+function Payload:prev()
+ return self.obj_prev
+end
+
+-- Cast the object to the underlining object module and return it.
+function Payload:cast()
+ return self
+end
+
+-- Cast the object to the generic object module and return it.
+function Payload:uncast()
+ return ffi.cast("core_object_t*", self)
+end
+
+-- Make a copy of the object and return it.
+function Payload:copy()
+ return C.core_object_payload_copy(self)
+end
+
+-- Free the object, should only be used on copies or otherwise allocated.
+function Payload:free()
+ C.core_object_payload_free(self)
+end
+
+core_object_payload_t = ffi.metatype(t_name, { __index = Payload })
+
+-- dnsjit.core.object (3),
+-- dnsjit.core.object.udp (3),
+-- dnsjit.core.object.tcp (3),
+-- dnsjit.filter.layer (3)
+return Payload
diff --git a/src/core/object/pcap.c b/src/core/object/pcap.c
new file mode 100644
index 0000000..576a94e
--- /dev/null
+++ b/src/core/object/pcap.c
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "core/object/pcap.h"
+#include "core/assert.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+core_object_pcap_t* core_object_pcap_copy(const core_object_pcap_t* self)
+{
+ core_object_pcap_t* copy;
+ glassert_self();
+
+ glfatal_oom(copy = malloc(sizeof(core_object_pcap_t) + self->caplen));
+ memcpy(copy, self, sizeof(core_object_pcap_t));
+ copy->obj_prev = 0;
+
+ if (copy->bytes) {
+ copy->bytes = (void*)copy + sizeof(core_object_pcap_t);
+ memcpy((void*)copy->bytes, self->bytes, self->caplen);
+ }
+
+ return copy;
+}
+
+void core_object_pcap_free(core_object_pcap_t* self)
+{
+ glassert_self();
+ free(self);
+}
diff --git a/src/core/object/pcap.h b/src/core/object/pcap.h
new file mode 100644
index 0000000..093549b
--- /dev/null
+++ b/src/core/object/pcap.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/object.h"
+#include "core/timespec.h"
+
+#ifndef __dnsjit_core_object_pcap_h
+#define __dnsjit_core_object_pcap_h
+
+#include "core/object/pcap.hh"
+
+#define CORE_OBJECT_PCAP_INIT(prev) \
+ { \
+ CORE_OBJECT_INIT(CORE_OBJECT_PCAP, prev) \
+ , \
+ 0, 0, \
+ { 0, 0 }, 0, 0, 0, \
+ 0 \
+ }
+
+#endif
diff --git a/src/core/object/pcap.hh b/src/core/object/pcap.hh
new file mode 100644
index 0000000..642a7e7
--- /dev/null
+++ b/src/core/object/pcap.hh
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.object_h")
+//lua:require("dnsjit.core.timespec_h")
+
+typedef struct core_object_pcap {
+ const core_object_t* obj_prev;
+ int32_t obj_type;
+
+ uint32_t snaplen, linktype;
+
+ core_timespec_t ts;
+ uint32_t caplen, len;
+ const unsigned char* bytes;
+
+ uint8_t is_swapped;
+} core_object_pcap_t;
+
+core_object_pcap_t* core_object_pcap_copy(const core_object_pcap_t* self);
+void core_object_pcap_free(core_object_pcap_t* self);
diff --git a/src/core/object/pcap.lua b/src/core/object/pcap.lua
new file mode 100644
index 0000000..3d49326
--- /dev/null
+++ b/src/core/object/pcap.lua
@@ -0,0 +1,98 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.object.pcap
+-- Container of a packet found in a PCAP
+--
+-- Container of a PCAP packet which contains information both from the PCAP
+-- itself and the
+-- .I pcap_pkthdr
+-- object receied for each packet in the PCAP.
+-- .SS Attributes
+-- .TP
+-- snaplen
+-- Max length saved portion of each packet.
+-- .TP
+-- linktype
+-- Data link type for the PCAP.
+-- .TP
+-- ts
+-- Time stamp of this packet.
+-- .TP
+-- caplen
+-- Length of portion present.
+-- .TP
+-- len
+-- Length of this packet (off wire).
+-- .TP
+-- bytes
+-- A pointer to the packet.
+-- .TP
+-- is_swapped
+-- Indicate if the byte order of the PCAP is different then the host.
+-- This is used in, for example, the Layer filter to correctly parse null
+-- objects since they are stored in the capturers host byte order.
+module(...,package.seeall)
+
+require("dnsjit.core.object.pcap_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "core_object_pcap_t"
+local core_object_pcap_t
+local Pcap = {}
+
+-- Return the textual type of the object.
+function Pcap:type()
+ return "pcap"
+end
+
+-- Return the previous object.
+function Pcap:prev()
+ return self.obj_prev
+end
+
+-- Cast the object to the underlining object module and return it.
+function Pcap:cast()
+ return self
+end
+
+-- Cast the object to the generic object module and return it.
+function Pcap:uncast()
+ return ffi.cast("core_object_t*", self)
+end
+
+-- Make a copy of the object and return it.
+function Pcap:copy()
+ return C.core_object_pcap_copy(self)
+end
+
+-- Free the object, should only be used on copies or otherwise allocated.
+function Pcap:free()
+ C.core_object_pcap_free(self)
+end
+
+core_object_pcap_t = ffi.metatype(t_name, { __index = Pcap })
+
+-- dnsjit.core.object (3),
+-- dnsjit.input.pcap (3),
+-- dnsjit.input.fpcap (3),
+-- dnsjit.input.mmpcap (3),
+-- dnsjit.filter.layer (3),
+-- dnsjit.output.pcap (3)
+return Pcap
diff --git a/src/core/object/tcp.c b/src/core/object/tcp.c
new file mode 100644
index 0000000..37f9548
--- /dev/null
+++ b/src/core/object/tcp.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "core/object/tcp.h"
+#include "core/assert.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+core_object_tcp_t* core_object_tcp_copy(const core_object_tcp_t* self)
+{
+ core_object_tcp_t* copy;
+ glassert_self();
+
+ glfatal_oom(copy = malloc(sizeof(core_object_tcp_t)));
+ memcpy(copy, self, sizeof(core_object_tcp_t));
+ copy->obj_prev = 0;
+
+ return copy;
+}
+
+void core_object_tcp_free(core_object_tcp_t* self)
+{
+ glassert_self();
+ free(self);
+}
diff --git a/src/core/object/tcp.h b/src/core/object/tcp.h
new file mode 100644
index 0000000..e62ad42
--- /dev/null
+++ b/src/core/object/tcp.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/object.h"
+#include "core/timespec.h"
+
+#ifndef __dnsjit_core_object_tcp_h
+#define __dnsjit_core_object_tcp_h
+
+#include <stddef.h>
+
+#include "core/object/tcp.hh"
+
+#define CORE_OBJECT_TCP_INIT(prev) \
+ { \
+ CORE_OBJECT_INIT(CORE_OBJECT_TCP, prev) \
+ , \
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
+ { 0 }, 0 \
+ }
+
+#endif
diff --git a/src/core/object/tcp.hh b/src/core/object/tcp.hh
new file mode 100644
index 0000000..bd3167d
--- /dev/null
+++ b/src/core/object/tcp.hh
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.object_h")
+
+typedef struct core_object_tcp {
+ const core_object_t* obj_prev;
+ int32_t obj_type;
+
+ uint16_t sport;
+ uint16_t dport;
+ uint32_t seq;
+ uint32_t ack;
+ uint8_t off : 4;
+ uint8_t x2 : 4;
+ uint8_t flags;
+ uint16_t win;
+ uint16_t sum;
+ uint16_t urp;
+
+ uint8_t opts[64];
+ size_t opts_len;
+} core_object_tcp_t;
+
+core_object_tcp_t* core_object_tcp_copy(const core_object_tcp_t* self);
+void core_object_tcp_free(core_object_tcp_t* self);
diff --git a/src/core/object/tcp.lua b/src/core/object/tcp.lua
new file mode 100644
index 0000000..e1d6dc2
--- /dev/null
+++ b/src/core/object/tcp.lua
@@ -0,0 +1,110 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.object.tcp
+-- A TCP segment header
+--
+-- A TCP segment header.
+-- The data itself is in the
+-- .I dnsjit.core.object.payload
+-- object, which is the next object in the chain after parsing with,
+-- for example,
+-- .IR dnsjit.filter.layer .
+-- .SS Attributes
+-- .TP
+-- sport
+-- Source port.
+-- .TP
+-- dport
+-- Destination port.
+-- .TP
+-- seq
+-- Sequence number.
+-- .TP
+-- ack
+-- Acknowledgement number.
+-- .TP
+-- off
+-- Data offset.
+-- .TP
+-- x2
+-- Unused.
+-- .TP
+-- flags
+-- TCP flags.
+-- .TP
+-- win
+-- Window.
+-- .TP
+-- sum
+-- Checksum.
+-- .TP
+-- urp
+-- Urgent pointer.
+-- .TP
+-- opts
+-- Array of bytes with the TCP options found.
+-- .TP
+-- opts_len
+-- Length of the TCP options.
+module(...,package.seeall)
+
+require("dnsjit.core.object.tcp_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "core_object_tcp_t"
+local core_object_tcp_t
+local Tcp = {}
+
+-- Return the textual type of the object.
+function Tcp:type()
+ return "tcp"
+end
+
+-- Return the previous object.
+function Tcp:prev()
+ return self.obj_prev
+end
+
+-- Cast the object to the underlining object module and return it.
+function Tcp:cast()
+ return self
+end
+
+-- Cast the object to the generic object module and return it.
+function Tcp:uncast()
+ return ffi.cast("core_object_t*", self)
+end
+
+-- Make a copy of the object and return it.
+function Tcp:copy()
+ return C.core_object_tcp_copy(self)
+end
+
+-- Free the object, should only be used on copies or otherwise allocated.
+function Tcp:free()
+ C.core_object_tcp_free(self)
+end
+
+core_object_tcp_t = ffi.metatype(t_name, { __index = Tcp })
+
+-- dnsjit.core.object (3),
+-- dnsjit.core.object.payload (3),
+-- dnsjit.filter.layer (3)
+return Tcp
diff --git a/src/core/object/udp.c b/src/core/object/udp.c
new file mode 100644
index 0000000..3d05002
--- /dev/null
+++ b/src/core/object/udp.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "core/object/udp.h"
+#include "core/assert.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+core_object_udp_t* core_object_udp_copy(const core_object_udp_t* self)
+{
+ core_object_udp_t* copy;
+ glassert_self();
+
+ glfatal_oom(copy = malloc(sizeof(core_object_udp_t)));
+ memcpy(copy, self, sizeof(core_object_udp_t));
+ copy->obj_prev = 0;
+
+ return copy;
+}
+
+void core_object_udp_free(core_object_udp_t* self)
+{
+ glassert_self();
+ free(self);
+}
diff --git a/src/core/object/udp.h b/src/core/object/udp.h
new file mode 100644
index 0000000..147e80b
--- /dev/null
+++ b/src/core/object/udp.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/object.h"
+#include "core/timespec.h"
+
+#ifndef __dnsjit_core_object_udp_h
+#define __dnsjit_core_object_udp_h
+
+#include <stddef.h>
+
+#include "core/object/udp.hh"
+
+#define CORE_OBJECT_UDP_INIT(prev) \
+ { \
+ CORE_OBJECT_INIT(CORE_OBJECT_UDP, prev) \
+ , \
+ 0, 0, 0, 0, \
+ }
+
+#endif
diff --git a/src/core/object/udp.hh b/src/core/object/udp.hh
new file mode 100644
index 0000000..5efbd92
--- /dev/null
+++ b/src/core/object/udp.hh
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.object_h")
+
+typedef struct core_object_udp {
+ const core_object_t* obj_prev;
+ int32_t obj_type;
+
+ uint16_t sport;
+ uint16_t dport;
+ uint16_t ulen;
+ uint16_t sum;
+} core_object_udp_t;
+
+core_object_udp_t* core_object_udp_copy(const core_object_udp_t* self);
+void core_object_udp_free(core_object_udp_t* self);
diff --git a/src/core/object/udp.lua b/src/core/object/udp.lua
new file mode 100644
index 0000000..1652574
--- /dev/null
+++ b/src/core/object/udp.lua
@@ -0,0 +1,86 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.object.udp
+-- A UDP datagram header
+--
+-- A UDP datagram header.
+-- The data itself is in the
+-- .I dnsjit.core.object.payload
+-- object, which is the next object in the chain after parsing with,
+-- for example,
+-- .IR dnsjit.filter.layer .
+-- .SS Attributes
+-- .TP
+-- sport
+-- Source port.
+-- .TP
+-- dport
+-- Destination port.
+-- .TP
+-- ulen
+-- UDP length (as described in the UDP header).
+-- .TP
+-- sum
+-- Checksum.
+module(...,package.seeall)
+
+require("dnsjit.core.object.udp_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "core_object_udp_t"
+local core_object_udp_t
+local Udp = {}
+
+-- Return the textual type of the object.
+function Udp:type()
+ return "udp"
+end
+
+-- Return the previous object.
+function Udp:prev()
+ return self.obj_prev
+end
+
+-- Cast the object to the underlining object module and return it.
+function Udp:cast()
+ return self
+end
+
+-- Cast the object to the generic object module and return it.
+function Udp:uncast()
+ return ffi.cast("core_object_t*", self)
+end
+
+-- Make a copy of the object and return it.
+function Udp:copy()
+ return C.core_object_udp_copy(self)
+end
+
+-- Free the object, should only be used on copies or otherwise allocated.
+function Udp:free()
+ C.core_object_udp_free(self)
+end
+
+core_object_udp_t = ffi.metatype(t_name, { __index = Udp })
+
+-- dnsjit.core.object (3),
+-- dnsjit.core.object.payload (3),
+-- dnsjit.filter.layer (3)
+return Udp
diff --git a/src/core/objects.lua b/src/core/objects.lua
new file mode 100644
index 0000000..ce83cd0
--- /dev/null
+++ b/src/core/objects.lua
@@ -0,0 +1,61 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.objects
+-- Easy way to import all objects
+-- require("dnsjit.core.objects")
+--
+-- Helper module to require all available objects, returns what
+-- .I dnsjit.core.object
+-- returned so that constants like object types can be used.
+module(...,package.seeall)
+
+local object = require("dnsjit.core.object")
+require("dnsjit.core.object.pcap")
+require("dnsjit.core.object.ether")
+require("dnsjit.core.object.null")
+require("dnsjit.core.object.loop")
+require("dnsjit.core.object.linuxsll")
+require("dnsjit.core.object.ieee802")
+require("dnsjit.core.object.gre")
+require("dnsjit.core.object.ip")
+require("dnsjit.core.object.ip6")
+require("dnsjit.core.object.icmp")
+require("dnsjit.core.object.icmp6")
+require("dnsjit.core.object.udp")
+require("dnsjit.core.object.tcp")
+require("dnsjit.core.object.payload")
+require("dnsjit.core.object.dns")
+
+-- dnsjit.core.object (3),
+-- dnsjit.core.object.pcap (3),
+-- dnsjit.core.object.ether (3),
+-- dnsjit.core.object.null (3),
+-- dnsjit.core.object.loop (3),
+-- dnsjit.core.object.linuxsll (3),
+-- dnsjit.core.object.ieee802 (3),
+-- dnsjit.core.object.gre (3),
+-- dnsjit.core.object.ip (3),
+-- dnsjit.core.object.ip6 (3),
+-- dnsjit.core.object.icmp (3),
+-- dnsjit.core.object.icmp6 (3),
+-- dnsjit.core.object.udp (3),
+-- dnsjit.core.object.tcp (3),
+-- dnsjit.core.object.payload (3),
+-- dnsjit.core.object.dns (3)
+return object
diff --git a/src/core/producer.c b/src/core/producer.c
new file mode 100644
index 0000000..8a85c93
--- /dev/null
+++ b/src/core/producer.c
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "core/producer.h"
diff --git a/src/core/producer.h b/src/core/producer.h
new file mode 100644
index 0000000..e78d48b
--- /dev/null
+++ b/src/core/producer.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/object.h"
+
+#ifndef __dnsjit_core_producer_h
+#define __dnsjit_core_producer_h
+
+#include "core/producer.hh"
+
+#endif
diff --git a/src/core/producer.hh b/src/core/producer.hh
new file mode 100644
index 0000000..2d64035
--- /dev/null
+++ b/src/core/producer.hh
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.object_h")
+
+typedef const core_object_t* (*core_producer_t)(void* ctx);
diff --git a/src/core/producer.lua b/src/core/producer.lua
new file mode 100644
index 0000000..42f3e44
--- /dev/null
+++ b/src/core/producer.lua
@@ -0,0 +1,28 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.producer
+-- Producer interfaces
+-- require("dnsjit.core.producer_h")
+--
+-- Producer interfaces are used by input, filter and output modules to pass
+-- objects for processing.
+module(...,package.seeall)
+
+-- dnsjit.core.object (3)
+return
diff --git a/src/core/receiver.c b/src/core/receiver.c
new file mode 100644
index 0000000..f67be0e
--- /dev/null
+++ b/src/core/receiver.c
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "core/receiver.h"
diff --git a/src/core/receiver.h b/src/core/receiver.h
new file mode 100644
index 0000000..f592c0c
--- /dev/null
+++ b/src/core/receiver.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/object.h"
+
+#ifndef __dnsjit_core_receiver_h
+#define __dnsjit_core_receiver_h
+
+#include "core/receiver.hh"
+
+#endif
diff --git a/src/core/receiver.hh b/src/core/receiver.hh
new file mode 100644
index 0000000..316c9be
--- /dev/null
+++ b/src/core/receiver.hh
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.object_h")
+
+typedef void (*core_receiver_t)(void* ctx, const core_object_t* obj);
diff --git a/src/core/receiver.lua b/src/core/receiver.lua
new file mode 100644
index 0000000..02aa39d
--- /dev/null
+++ b/src/core/receiver.lua
@@ -0,0 +1,28 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.receiver
+-- Receiver interfaces
+-- require("dnsjit.core.receiver_h")
+--
+-- Receiver interfaces are used by input, filter and output modules to pass
+-- objects for processing.
+module(...,package.seeall)
+
+-- dnsjit.core.object (3)
+return
diff --git a/src/core/thread.c b/src/core/thread.c
new file mode 100644
index 0000000..189c720
--- /dev/null
+++ b/src/core/thread.c
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "globals.h"
+#include "core/assert.h"
+#include "core/thread.h"
+
+#include <string.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+static core_log_t _log = LOG_T_INIT("core.thread");
+static core_thread_t _defaults = {
+ LOG_T_INIT_OBJ("core.thread"),
+ 0, 0, 0, 0,
+ PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER,
+ 0, 0
+};
+
+core_log_t* core_thread_log()
+{
+ return &_log;
+}
+
+void core_thread_init(core_thread_t* self)
+{
+ mlassert_self();
+
+ *self = _defaults;
+}
+
+void core_thread_destroy(core_thread_t* self)
+{
+ core_thread_item_t* item;
+ mlassert_self();
+
+ free(self->bytecode);
+ while ((item = self->stack)) {
+ self->stack = item->next;
+ free(item);
+ }
+}
+
+static void* _thread(void* vp)
+{
+ core_thread_t* self = (core_thread_t*)vp;
+ lua_State* L;
+ mlassert_self();
+
+ // TODO: move to dnsjit_newstate()
+ L = luaL_newstate();
+ lassert(L, "could not create new Lua state");
+ luaL_openlibs(L);
+ dnsjit_globals(L);
+
+ for (;;) {
+ lua_getfield(L, LUA_GLOBALSINDEX, "require");
+ lua_pushstring(L, "dnsjit.core.thread");
+ if (lua_pcall(L, 1, 1, 0)) {
+ lcritical("%s", lua_tostring(L, -1));
+ break;
+ }
+ lua_getfield(L, -1, "_in_thread");
+ lua_pushlightuserdata(L, (void*)self);
+ lua_pushlstring(L, self->bytecode, self->bytecode_len);
+ if (lua_pcall(L, 2, 0, 0)) {
+ lcritical("%s", lua_tostring(L, -1));
+ }
+ break;
+ }
+
+ lua_close(L);
+ return 0;
+}
+
+int core_thread_start(core_thread_t* self, const char* bytecode, size_t len)
+{
+ int err;
+ mlassert_self();
+
+ if (self->bytecode) {
+ lfatal("bytecode already set");
+ }
+
+ lfatal_oom(self->bytecode = malloc(len));
+ memcpy(self->bytecode, bytecode, len);
+ self->bytecode_len = len;
+
+ if ((err = pthread_create(&self->thr_id, 0, _thread, (void*)self))) {
+ lcritical("pthread_create() error: %s", core_log_errstr(err));
+ return -1;
+ }
+
+ return 0;
+}
+
+int core_thread_stop(core_thread_t* self)
+{
+ int err;
+ mlassert_self();
+
+ if ((err = pthread_join(self->thr_id, 0))) {
+ lcritical("pthread_join() error: %s", core_log_errstr(err));
+ return -1;
+ }
+
+ return 0;
+}
+
+inline static void _push(core_thread_t* self, core_thread_item_t* item)
+{
+ if (pthread_mutex_lock(&self->lock)) {
+ lfatal("mutex lock failed");
+ }
+ if (!self->last) {
+ self->stack = self->last = item;
+ } else {
+ self->last->next = item;
+ self->last = item;
+ }
+ if (pthread_cond_signal(&self->cond)) {
+ lfatal("cond signal failed");
+ }
+ if (pthread_mutex_unlock(&self->lock)) {
+ lfatal("mutex unlock failed");
+ }
+}
+
+void core_thread_push(core_thread_t* self, void* ptr, const char* type, size_t type_len, const char* module, size_t module_len)
+{
+ core_thread_item_t* item;
+ mlassert_self();
+ lassert(ptr, "ptr is nil");
+ lassert(type, "type is nil");
+ lassert(type_len, "type_len is zero");
+ lassert(module, "module is nil");
+ lassert(module_len, "module_len is zero");
+
+ lfatal_oom(item = malloc(sizeof(core_thread_item_t) + type_len + module_len + 2));
+ item->next = 0;
+ item->ptr = ptr;
+ item->type = ((void*)item) + sizeof(core_thread_item_t);
+ memcpy(item->type, type, type_len);
+ item->type[type_len] = 0;
+ item->module = item->type + type_len + 1;
+ memcpy(item->module, module, module_len);
+ item->module[module_len] = 0;
+
+ _push(self, item);
+}
+
+void core_thread_push_string(core_thread_t* self, const char* str, size_t len)
+{
+ core_thread_item_t* item;
+ mlassert_self();
+ lassert(str, "str is nil");
+ lassert(len, "len is zero");
+
+ lfatal_oom(item = malloc(sizeof(core_thread_item_t) + len + 1));
+ item->next = 0;
+ item->ptr = 0;
+ item->str = ((void*)item) + sizeof(core_thread_item_t);
+ memcpy(item->str, str, len);
+ item->str[len] = 0;
+
+ _push(self, item);
+}
+
+void core_thread_push_int64(core_thread_t* self, int64_t i64)
+{
+ core_thread_item_t* item;
+ mlassert_self();
+
+ lfatal_oom(item = malloc(sizeof(core_thread_item_t)));
+ item->next = 0;
+ item->ptr = 0;
+ item->str = 0;
+ item->i64 = i64;
+
+ _push(self, item);
+}
+
+const core_thread_item_t* core_thread_pop(core_thread_t* self)
+{
+ mlassert_self();
+
+ if (pthread_mutex_lock(&self->lock)) {
+ lfatal("mutex lock failed");
+ }
+ if (!self->at) {
+ while (!self->stack) {
+ if (pthread_cond_wait(&self->cond, &self->lock)) {
+ lfatal("cond wait failed");
+ }
+ }
+ self->at = self->stack;
+ } else {
+ while (!self->at->next) {
+ if (pthread_cond_wait(&self->cond, &self->lock)) {
+ lfatal("cond wait failed");
+ }
+ }
+ self->at = self->at->next;
+ }
+ if (pthread_mutex_unlock(&self->lock)) {
+ lfatal("mutex unlock failed");
+ }
+
+ return self->at;
+}
diff --git a/src/core/thread.h b/src/core/thread.h
new file mode 100644
index 0000000..fc047d4
--- /dev/null
+++ b/src/core/thread.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/log.h"
+
+#ifndef __dnsjit_core_thread_h
+#define __dnsjit_core_thread_h
+
+#include <pthread.h>
+#include <unistd.h>
+
+#include "core/thread.hh"
+
+#endif
diff --git a/src/core/thread.hh b/src/core/thread.hh
new file mode 100644
index 0000000..a61f8c9
--- /dev/null
+++ b/src/core/thread.hh
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.compat_h")
+//lua:require("dnsjit.core.log")
+
+typedef struct core_thread_item core_thread_item_t;
+struct core_thread_item {
+ core_thread_item_t* next;
+ void* ptr;
+ char * type, *module;
+
+ char* str;
+ int64_t i64;
+};
+
+typedef struct core_thread {
+ core_log_t _log;
+ pthread_t thr_id;
+ core_thread_item_t * stack, *last;
+ const core_thread_item_t* at;
+
+ pthread_mutex_t lock;
+ pthread_cond_t cond;
+
+ char* bytecode;
+ size_t bytecode_len;
+} core_thread_t;
+
+core_log_t* core_thread_log();
+
+void core_thread_init(core_thread_t* self);
+void core_thread_destroy(core_thread_t* self);
+int core_thread_start(core_thread_t* self, const char* bytecode, size_t len);
+int core_thread_stop(core_thread_t* self);
+void core_thread_push(core_thread_t* self, void* ptr, const char* type, size_t type_len, const char* module, size_t module_len);
+void core_thread_push_string(core_thread_t* self, const char* str, size_t len);
+void core_thread_push_int64(core_thread_t* self, int64_t i64);
+const core_thread_item_t* core_thread_pop(core_thread_t* self);
diff --git a/src/core/thread.lua b/src/core/thread.lua
new file mode 100644
index 0000000..f225726
--- /dev/null
+++ b/src/core/thread.lua
@@ -0,0 +1,141 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.thread
+-- POSIX thread with separate Lua state
+-- local thr = require("dnsjit.core.thread").new()
+-- thr:start(function(thr)
+-- print("Hello from thread")
+-- print("got:", thr:pop(), " = ", thr:pop(3))
+-- end)
+-- thr:push("value from main", 1, 2, 3)
+-- thr:stop()
+--
+-- Start a new POSIX thread with it's own Lua state.
+-- Sharable objects can be passed to the thread by pushing and poping them of
+-- the thread stack.
+-- The Thread object and any other objects passed to the thread needs to be
+-- kept alive as long as the thread is running.
+module(...,package.seeall)
+
+require("dnsjit.core.thread_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "core_thread_t"
+local core_thread_t
+local Thread = {
+ _in_thread = function(thr, bytecode)
+ thr = ffi.cast("core_thread_t*", thr)
+ loadstring(bytecode)(thr)
+ end
+}
+
+-- Create a new Thread object.
+function Thread.new()
+ local self = core_thread_t()
+ C.core_thread_init(self)
+ ffi.gc(self, C.core_thread_destroy)
+ return self
+end
+
+-- Return the Log object to control logging of this instance or module.
+function Thread:log()
+ if self == nil then
+ return C.core_thread_log()
+ end
+ return self._log
+end
+
+-- Start the thread and execute the given function in a separate Lua state,
+-- first argument to the function will be the Thread object that created it.
+-- Returns 0 on success.
+function Thread:start(func)
+ local bc = string.dump(func)
+ return C.core_thread_start(self, bc, #bc)
+end
+
+-- Wait for the thread to return.
+-- Returns 0 on success.
+function Thread:stop()
+ return C.core_thread_stop(self)
+end
+
+-- Push string(s), number(s) or sharable object(s) onto the thread stack so
+-- they can be retrieved inside the thread using
+-- .IR pop() .
+-- The sharable object(s) needs to be kept alive as long as the thread is
+-- running, strings and numbers are copied.
+function Thread:push(...)
+ for _, obj in pairs({...}) do
+ local t = type(obj)
+ if t == "string" then
+ C.core_thread_push_string(self, obj, #obj)
+ elseif t == "number" then
+ C.core_thread_push_int64(self, obj)
+ else
+ local ptr, type, module = obj:share()
+ C.core_thread_push(self, ptr, type, #type, module, #module)
+ end
+ end
+end
+
+-- Pop value(s) off the thread stack, should only be called within the thread.
+-- If
+-- .I num
+-- is not given then one value is poped.
+-- Returns nil if no values are left on the stack.
+function Thread:pop(num)
+ if num == nil or num == 1 then
+ local item = C.core_thread_pop(self)
+ if item == nil then
+ return
+ end
+ if item.ptr == nil then
+ if item.str == nil then
+ return tonumber(item.i64)
+ end
+ return ffi.string(item.str)
+ end
+ require(ffi.string(item.module))
+ return ffi.cast(ffi.string(item.type), item.ptr)
+ end
+
+ local ret = {}
+ for n = 1, num do
+ local item = C.core_thread_pop(self)
+ if item == nil then break end
+
+ if item.ptr == nil then
+ if item.str == nil then
+ table.insert(ret, tonumber(item.i64))
+ else
+ table.insert(ret, ffi.string(item.str))
+ end
+ else
+ require(ffi.string(item.module))
+ table.insert(ret, ffi.cast(ffi.string(item.type), item.ptr))
+ end
+ end
+ return unpack(ret)
+end
+
+core_thread_t = ffi.metatype(t_name, { __index = Thread })
+
+-- dnsjit.core.channel (3)
+return Thread
diff --git a/src/core/timespec.h b/src/core/timespec.h
new file mode 100644
index 0000000..e0aefce
--- /dev/null
+++ b/src/core/timespec.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __dnsjit_core_timespec_h
+#define __dnsjit_core_timespec_h
+
+#include <stdint.h>
+
+#include "core/timespec.hh"
+
+#define CORE_TIMESPEC_INIT \
+ { \
+ 0, 0 \
+ }
+
+#endif
diff --git a/src/core/timespec.hh b/src/core/timespec.hh
new file mode 100644
index 0000000..17a9551
--- /dev/null
+++ b/src/core/timespec.hh
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+typedef struct core_timespec {
+ int64_t sec;
+ int64_t nsec;
+} core_timespec_t;
diff --git a/src/core/timespec.lua b/src/core/timespec.lua
new file mode 100644
index 0000000..0db4dd3
--- /dev/null
+++ b/src/core/timespec.lua
@@ -0,0 +1,32 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.core.receiver
+-- Non-system depended time specification structure definition
+-- typedef struct core_timespec {
+-- uint64_t sec;
+-- uint64_t nsec;
+-- } core_timespec_t;
+-- .SS C
+-- #include "core/timespec.h"
+-- .SS Lua
+-- require("dnsjit.core.timespec_h")
+--
+-- Mainly used in C modules for a system independent time specification
+-- structure that can be passed to Lua.
+module(...,package.seeall)
diff --git a/src/dnsjit.1in b/src/dnsjit.1in
new file mode 100644
index 0000000..ae1ca93
--- /dev/null
+++ b/src/dnsjit.1in
@@ -0,0 +1,144 @@
+.\" Copyright (c) 2018-2021, OARC, Inc.
+.\" All rights reserved.
+.\"
+.\" This file is part of dnsjit.
+.\"
+.\" dnsjit is free software: you can redistribute it and/or modify
+.\" it under the terms of the GNU General Public License as published by
+.\" the Free Software Foundation, either version 3 of the License, or
+.\" (at your option) any later version.
+.\"
+.\" dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+.\"
+.TH dnsjit 1 "@PACKAGE_VERSION@" "dnsjit"
+.SH NAME
+dnsjit \- DNS engine based around LuaJIT for capturing, parsing, replaying and statistics gathering
+.SH SYNOPSIS
+.SS Run a Lua script
+.B dnsjit
+.I file.lua
+.I ...
+.SS Shebang-style
+ #!/usr/bin/env dnsjit
+ ...
+.SH DESCRIPTION
+.B dnsjit
+is a combination of parts taken from
+.BR dsc ,
+.BR dnscap ,
+.BR drool,
+and put together around Lua to create a script-based engine for easy
+capturing, parsing and statistics gathering of DNS message while also
+providing facilities for replaying DNS traffic.
+.LP
+One of the core functionality that
+.B dnsjit
+brings is to tie together C and Lua modules through a receiver/receive
+interface.
+This allows creation of custom chains of functionality to meet various
+requirements.
+Another core functionality is the ability to parse and process DNS messages
+even if the messages are non-compliant with the DNS standards.
+.LP
+.B NOTE
+current implementation is
+.I ALPHA
+which means functionality are not set and may be changed or removed.
+.LP
+.SH MODULE CATEGORIES
+The following Lua module categories exists:
+.TP
+.B dnsjit.core
+Core modules for handling things like logging, DNS messages and
+receiver/receive functionality.
+.TP
+.B dnsjit.lib
+Various Lua libraries or C library bindings.
+.TP
+.B dnsjit.input
+Input modules used to read DNS messages in various ways.
+.TP
+.B dnsjit.filter
+Filter modules to process or manipulate DNS messages.
+.TP
+.B dnsjit.output
+Output modules used to display DNS message, export to various formats or
+replay them against other targets.
+.LP
+See each category's man-page for more information.
+.SH LUA GLOBALS
+The following Lua global variables are defined:
+.TP
+.B DNSJIT_VERSION
+A string with the full version.
+.TP
+.B DNSJIT_MAJOR_VERSION
+An integer with the major version number.
+.TP
+.B DNSJIT_MINOR_VERSION
+An integer with the minor version number.
+.TP
+.B DNSJIT_PATCH_VERSION
+An integer with the patch version number.
+.TP
+.B DNSJIT_BUGREPORT
+A string with the email address to file bug reports to.
+.TP
+.B DNSJIT_URL
+A string with the URL to the repository issue tracker, preferred place to
+file bug reports.
+.SH EXAMPLE
+Following example display the DNS ID found in queries.
+.LP
+ local input = require("dnsjit.input.pcapthread").new()
+ local output = require("dnsjit.filter.lua").new()
+
+ output:func(function(filter, object)
+ local packet = object:cast()
+ local dns = require("dnsjit.core.object.dns").new(packet)
+ dns:parse()
+ print(dns.id)
+ end)
+
+ input:open_offline("file.pcap")
+ input:only_queries(true)
+ input:receiver(output)
+ input:run()
+.LP
+See more examples in the
+.I examples
+directory.
+.SH SEE ALSO
+.BR dnsjit.core (3),
+.BR dnsjit.lib (3),
+.BR dnsjit.input (3),
+.BR dnsjit.filter (3),
+.BR dnsjit.output (3)
+.SH AUTHORS
+Jerry Lundström, DNS-OARC
+.LP
+Maintained by DNS-OARC
+.LP
+.RS
+.I https://www.dns-oarc.net/
+.RE
+.LP
+.SH BUGS
+For issues and feature requests please use:
+.LP
+.RS
+\fI@PACKAGE_URL@\fP
+.RE
+.LP
+For question and help please use:
+.LP
+.RS
+\fI@PACKAGE_BUGREPORT@\fP
+.RE
+.LP
diff --git a/src/dnsjit.c b/src/dnsjit.c
new file mode 100644
index 0000000..0caa202
--- /dev/null
+++ b/src/dnsjit.c
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "globals.h"
+#include "core/log.h"
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include <pthread.h>
+#include <signal.h>
+#include <string.h>
+#include <stdio.h>
+
+static void* _sighthr(void* arg)
+{
+ sigset_t* set = (sigset_t*)arg;
+ int sig = 0, err;
+
+ if ((err = sigwait(set, &sig))) {
+ gldebug("sigwait %d", err);
+ }
+ glfatal("signal %d", sig);
+
+ return 0;
+}
+
+int main(int argc, char* argv[])
+{
+ lua_State* L;
+ int n, err;
+ sigset_t set;
+ pthread_t sighthr;
+
+#ifdef PACKAGE_NAME
+ fprintf(stderr, "<< " PACKAGE_NAME
+#ifdef PACKAGE_VERSION
+ " v" PACKAGE_VERSION
+#endif
+#ifdef PACKAGE_URL
+ " " PACKAGE_URL
+#endif
+ " >>\n");
+#endif
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s <file.lua> ...\n", argv[0]);
+ exit(1);
+ }
+
+ sigfillset(&set);
+ if ((err = pthread_sigmask(SIG_BLOCK, &set, 0))) {
+ glfatal("Unable to set blocked signals with pthread_sigmask()");
+ return 2;
+ }
+
+ sigemptyset(&set);
+ sigaddset(&set, SIGTERM);
+ sigaddset(&set, SIGQUIT);
+ sigaddset(&set, SIGINT);
+
+ if ((err = pthread_create(&sighthr, 0, _sighthr, &set))) {
+ glfatal("Unable to start signal thread with pthread_create()");
+ return 2;
+ }
+
+ L = luaL_newstate();
+ luaL_openlibs(L);
+ dnsjit_globals(L);
+
+ lua_createtable(L, argc, 0);
+ for (n = 0; n < argc; n++) {
+ lua_pushstring(L, argv[n]);
+ lua_rawseti(L, -2, n);
+ }
+ lua_setglobal(L, "arg");
+ if ((err = luaL_loadfile(L, argv[1]))) {
+ switch (err) {
+ case LUA_ERRSYNTAX:
+ glcritical("%s: syntax error during pre-compilation", argv[1]);
+ break;
+ case LUA_ERRMEM:
+ glcritical("%s: memory allocation error", argv[1]);
+ break;
+ case LUA_ERRFILE:
+ glcritical("%s: cannot open/read file", argv[1]);
+ break;
+ default:
+ glcritical("%s: unknown error %d", argv[1], err);
+ break;
+ }
+ return 1;
+ }
+ if (lua_pcall(L, 0, 0, 0)) {
+ glcritical("%s: %s", argv[1], lua_tostring(L, -1));
+ lua_pop(L, 1);
+ return 1;
+ }
+ lua_close(L);
+
+ return 0;
+}
diff --git a/src/filter.lua b/src/filter.lua
new file mode 100644
index 0000000..fbb3736
--- /dev/null
+++ b/src/filter.lua
@@ -0,0 +1,31 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.filter
+-- Filter/processing modules for dnsjit
+--
+-- Filter modules to process or manipulate objects, packets and/or DNS
+-- messages.
+module(...,package.seeall)
+
+-- dnsjit.filter.copy (3),
+-- dnsjit.filter.ipsplit (3),
+-- dnsjit.filter.layer (3),
+-- dnsjit.filter.split (3),
+-- dnsjit.filter.timing (3)
+return
diff --git a/src/filter/copy.c b/src/filter/copy.c
new file mode 100644
index 0000000..04a90ef
--- /dev/null
+++ b/src/filter/copy.c
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2019, CZ.NIC, z.s.p.o.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "filter/copy.h"
+#include "core/assert.h"
+
+static core_log_t _log = LOG_T_INIT("filter.copy");
+static filter_copy_t _defaults = {
+ LOG_T_INIT_OBJ("filter.copy"),
+ 0, 0,
+ 0
+};
+
+core_log_t* filter_copy_log()
+{
+ return &_log;
+}
+
+void filter_copy_init(filter_copy_t* self)
+{
+ mlassert_self();
+
+ *self = _defaults;
+}
+
+void filter_copy_destroy(filter_copy_t* self)
+{
+ mlassert_self();
+}
+
+void filter_copy_set(filter_copy_t* self, int32_t obj_type)
+{
+ mlassert_self();
+
+ switch (obj_type) {
+ case CORE_OBJECT_NONE:
+ self->copy |= 0x1;
+ break;
+ case CORE_OBJECT_PCAP:
+ self->copy |= 0x2;
+ break;
+ case CORE_OBJECT_ETHER:
+ self->copy |= 0x4;
+ break;
+ case CORE_OBJECT_NULL:
+ self->copy |= 0x8;
+ break;
+ case CORE_OBJECT_LOOP:
+ self->copy |= 0x10;
+ break;
+ case CORE_OBJECT_LINUXSLL:
+ self->copy |= 0x20;
+ break;
+ case CORE_OBJECT_IEEE802:
+ self->copy |= 0x40;
+ break;
+ case CORE_OBJECT_GRE:
+ self->copy |= 0x80;
+ break;
+ case CORE_OBJECT_IP:
+ self->copy |= 0x100;
+ break;
+ case CORE_OBJECT_IP6:
+ self->copy |= 0x200;
+ break;
+ case CORE_OBJECT_ICMP:
+ self->copy |= 0x400;
+ break;
+ case CORE_OBJECT_ICMP6:
+ self->copy |= 0x800;
+ break;
+ case CORE_OBJECT_UDP:
+ self->copy |= 0x1000;
+ break;
+ case CORE_OBJECT_TCP:
+ self->copy |= 0x2000;
+ break;
+ case CORE_OBJECT_PAYLOAD:
+ self->copy |= 0x4000;
+ break;
+ case CORE_OBJECT_DNS:
+ self->copy |= 0x8000;
+ break;
+ default:
+ lfatal("unknown type %d", obj_type);
+ }
+}
+
+uint64_t filter_copy_get(filter_copy_t* self, int32_t obj_type)
+{
+ mlassert_self();
+
+ switch (obj_type) {
+ case CORE_OBJECT_NONE:
+ return self->copy & 0x1;
+ case CORE_OBJECT_PCAP:
+ return self->copy & 0x2;
+ case CORE_OBJECT_ETHER:
+ return self->copy & 0x4;
+ case CORE_OBJECT_NULL:
+ return self->copy & 0x8;
+ case CORE_OBJECT_LOOP:
+ return self->copy & 0x10;
+ case CORE_OBJECT_LINUXSLL:
+ return self->copy & 0x20;
+ case CORE_OBJECT_IEEE802:
+ return self->copy & 0x40;
+ case CORE_OBJECT_GRE:
+ return self->copy & 0x80;
+ case CORE_OBJECT_IP:
+ return self->copy & 0x100;
+ case CORE_OBJECT_IP6:
+ return self->copy & 0x200;
+ case CORE_OBJECT_ICMP:
+ return self->copy & 0x400;
+ case CORE_OBJECT_ICMP6:
+ return self->copy & 0x800;
+ case CORE_OBJECT_UDP:
+ return self->copy & 0x1000;
+ case CORE_OBJECT_TCP:
+ return self->copy & 0x2000;
+ case CORE_OBJECT_PAYLOAD:
+ return self->copy & 0x4000;
+ case CORE_OBJECT_DNS:
+ return self->copy & 0x8000;
+ default:
+ lfatal("unknown type %d", obj_type);
+ }
+ return 0;
+}
+
+static void _receive(filter_copy_t* self, const core_object_t* obj)
+{
+ mlassert_self();
+ lassert(obj, "obj is nil");
+
+ core_object_t* outobj = NULL;
+ core_object_t* next = NULL;
+ core_object_t* current = NULL;
+ const core_object_t* srcobj = obj;
+
+ do {
+ if (filter_copy_get(self, srcobj->obj_type)) {
+ next = current;
+ current = core_object_copy(srcobj);
+ if (next == NULL) {
+ next = current;
+ outobj = current;
+ } else {
+ next->obj_prev = current;
+ }
+ }
+ srcobj = srcobj->obj_prev;
+ } while (srcobj != NULL);
+
+ if (outobj == NULL) {
+ lnotice("object discarded (no types to copy)");
+ return;
+ }
+
+ self->recv(self->recv_ctx, outobj);
+}
+
+core_receiver_t filter_copy_receiver(filter_copy_t* self)
+{
+ mlassert_self();
+
+ if (!self->recv) {
+ lfatal("no receiver(s) set");
+ }
+
+ return (core_receiver_t)_receive;
+}
diff --git a/src/filter/copy.h b/src/filter/copy.h
new file mode 100644
index 0000000..96c8db0
--- /dev/null
+++ b/src/filter/copy.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2019, CZ.NIC, z.s.p.o.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/log.h"
+#include "core/object.h"
+#include "core/receiver.h"
+
+#ifndef __dnsjit_filter_copy_h
+#define __dnsjit_filter_copy_h
+
+#include "filter/copy.hh"
+
+#endif
diff --git a/src/filter/copy.hh b/src/filter/copy.hh
new file mode 100644
index 0000000..edf7fc7
--- /dev/null
+++ b/src/filter/copy.hh
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2019, CZ.NIC z.s.p.o.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+
+typedef struct filter_copy {
+ core_log_t _log;
+
+ core_receiver_t recv;
+ void* recv_ctx;
+
+ uint64_t copy;
+} filter_copy_t;
+
+core_log_t* filter_copy_log();
+
+void filter_copy_init(filter_copy_t* self);
+void filter_copy_destroy(filter_copy_t* self);
+void filter_copy_set(filter_copy_t* self, int32_t obj_type);
+uint64_t filter_copy_get(filter_copy_t* self, int32_t obj_type);
+
+core_receiver_t filter_copy_receiver(filter_copy_t* self);
diff --git a/src/filter/copy.lua b/src/filter/copy.lua
new file mode 100644
index 0000000..4be469f
--- /dev/null
+++ b/src/filter/copy.lua
@@ -0,0 +1,74 @@
+-- Copyright (c) 2019 CZ.NIC, z.s.p.o.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.filter.copy
+-- Creates a copy of an object chain with selected object types.
+-- local copy = require("dnsjit.filter.copy").new()
+-- local object = require("dnsjit.core.objects")
+-- copy:obj_type(object.PAYLOAD)
+-- copy:obj_type(object.IP6)
+-- channel:receiver(copy)
+--
+-- Filter to create a copy of the object chain with selected object types.
+-- The user is responsible for manually freeing the created object chain.
+module(...,package.seeall)
+
+require("dnsjit.filter.copy_h")
+local object = require("dnsjit.core.object")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "filter_copy_t"
+local filter_copy_t = ffi.typeof(t_name)
+local Copy = {}
+
+-- Create a new Copy filter.
+function Copy.new()
+ local self = {
+ obj = filter_copy_t(),
+ }
+ C.filter_copy_init(self.obj)
+ ffi.gc(self.obj, C.filter_copy_destroy)
+ return setmetatable(self, { __index = Copy })
+end
+
+-- Return the Log object to control logging of this instance or module.
+function Copy:log()
+ if self == nil then
+ return C.filter_copy_log()
+ end
+ return self.obj._log
+end
+
+-- Set the object type to be copied. Can be called multiple times to copy
+-- multiple object types from the object chain.
+function Copy:obj_type(obj_type)
+ C.filter_copy_set(self.obj, obj_type)
+end
+
+-- Return the C functions and context for receiving objects.
+function Copy:receive()
+ return C.filter_copy_receiver(self.obj), self.obj
+end
+
+-- Set the receiver to pass objects to.
+function Copy:receiver(o)
+ self.obj.recv, self.obj.recv_ctx = o:receive()
+end
+
+return Copy
diff --git a/src/filter/ipsplit.c b/src/filter/ipsplit.c
new file mode 100644
index 0000000..8632e2e
--- /dev/null
+++ b/src/filter/ipsplit.c
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2019-2020, CZ.NIC, z.s.p.o.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "filter/ipsplit.h"
+#include "core/assert.h"
+#include "core/object/ip.h"
+#include "core/object/ip6.h"
+#include "lib/trie.h"
+
+#include <string.h>
+
+typedef struct _filter_ipsplit {
+ filter_ipsplit_t pub;
+
+ trie_t* trie;
+ uint32_t weight_total;
+} _filter_ipsplit_t;
+
+typedef struct _client {
+ /* Receiver-specific client ID (1..N) in host byte order. */
+ /* Client ID starts at 1 to avoid issues with lua. */
+ uint8_t id[4];
+
+ filter_ipsplit_recv_t* recv;
+} _client_t;
+
+#define _self ((_filter_ipsplit_t*)self)
+
+static core_log_t _log = LOG_T_INIT("filter.ipsplit");
+static filter_ipsplit_t _defaults = {
+ LOG_T_INIT_OBJ("filter.ipsplit"),
+ IPSPLIT_MODE_SEQUENTIAL, IPSPLIT_OVERWRITE_NONE,
+ 0,
+ NULL
+};
+
+core_log_t* filter_ipsplit_log()
+{
+ return &_log;
+}
+
+filter_ipsplit_t* filter_ipsplit_new()
+{
+ filter_ipsplit_t* self;
+
+ mlfatal_oom(self = malloc(sizeof(_filter_ipsplit_t)));
+ *self = _defaults;
+ lfatal_oom(_self->trie = trie_create(NULL));
+ _self->weight_total = 0;
+
+ return self;
+}
+
+static int _free_trie_value(trie_val_t* val, void* ctx)
+{
+ free(*val);
+ return 0;
+}
+
+void filter_ipsplit_free(filter_ipsplit_t* self)
+{
+ filter_ipsplit_recv_t* first;
+ filter_ipsplit_recv_t* r;
+ mlassert_self();
+
+ trie_apply(_self->trie, _free_trie_value, NULL);
+ trie_free(_self->trie);
+
+ if (self->recv) {
+ first = self->recv;
+ do {
+ r = self->recv->next;
+ free(self->recv);
+ self->recv = r;
+ } while (self->recv != first);
+ }
+
+ free(self);
+}
+
+void filter_ipsplit_add(filter_ipsplit_t* self, core_receiver_t recv, void* ctx, uint32_t weight)
+{
+ filter_ipsplit_recv_t* r;
+ mlassert_self();
+ lassert(recv, "recv is nil");
+ lassert(weight > 0, "weight must be positive integer");
+
+ _self->weight_total += weight;
+
+ lfatal_oom(r = malloc(sizeof(filter_ipsplit_recv_t)));
+ r->recv = recv;
+ r->ctx = ctx;
+ r->n_clients = 0;
+ r->weight = weight;
+
+ if (!self->recv) {
+ r->next = r;
+ self->recv = r;
+ } else {
+ r->next = self->recv->next;
+ self->recv->next = r;
+ }
+}
+
+/*
+ * Use portable pseudo-random number generator.
+ */
+static uint32_t _rand_val = 1;
+
+static uint32_t _rand()
+{
+ _rand_val = ((_rand_val * 1103515245) + 12345) & 0x7fffffff;
+ return _rand_val;
+}
+
+void filter_ipsplit_srand(uint32_t seed)
+{
+ _rand_val = seed;
+}
+
+static void _assign_client_to_receiver(filter_ipsplit_t* self, _client_t* client)
+{
+ uint32_t id = 0;
+ filter_ipsplit_recv_t* recv = 0;
+
+ switch (self->mode) {
+ case IPSPLIT_MODE_SEQUENTIAL:
+ recv = self->recv;
+ id = ++recv->n_clients;
+ /* When *weight* clients are assigned, switch to next receiver. */
+ if (recv->n_clients % recv->weight == 0)
+ self->recv = recv->next;
+ break;
+ case IPSPLIT_MODE_RANDOM: {
+ /* Get random number from [1, weight_total], then iterate through
+ * receivers until their weights add up to at least this value. */
+ int32_t random = (int32_t)(_rand() % _self->weight_total) + 1;
+ while (random > 0) {
+ random -= self->recv->weight;
+ if (random > 0)
+ self->recv = self->recv->next;
+ }
+ recv = self->recv;
+ id = ++recv->n_clients;
+ break;
+ }
+ default:
+ lfatal("invalid ipsplit mode");
+ }
+
+ client->recv = recv;
+ memcpy(client->id, &id, sizeof(client->id));
+}
+
+/*
+ * Optionally, write client ID into byte 0-3 of src/dst IP address in the packet.
+ *
+ * Client ID is a 4-byte array in host byte order.
+ */
+static void _overwrite(filter_ipsplit_t* self, core_object_t* obj, _client_t* client)
+{
+ mlassert_self();
+ lassert(obj, "invalid object");
+ lassert(client, "invalid client");
+
+ core_object_ip_t* ip;
+ core_object_ip6_t* ip6;
+
+ switch (self->overwrite) {
+ case IPSPLIT_OVERWRITE_NONE:
+ return;
+ case IPSPLIT_OVERWRITE_SRC:
+ if (obj->obj_type == CORE_OBJECT_IP) {
+ ip = (core_object_ip_t*)obj;
+ memcpy(&ip->src, client->id, sizeof(client->id));
+ } else if (obj->obj_type == CORE_OBJECT_IP6) {
+ ip6 = (core_object_ip6_t*)obj;
+ memcpy(&ip6->src, client->id, sizeof(client->id));
+ }
+ break;
+ case IPSPLIT_OVERWRITE_DST:
+ if (obj->obj_type == CORE_OBJECT_IP) {
+ ip = (core_object_ip_t*)obj;
+ memcpy(&ip->dst, client->id, sizeof(client->id));
+ } else if (obj->obj_type == CORE_OBJECT_IP6) {
+ ip6 = (core_object_ip6_t*)obj;
+ memcpy(&ip6->dst, client->id, sizeof(client->id));
+ }
+ break;
+ default:
+ lfatal("invalid overwrite mode");
+ }
+}
+
+static void _receive(filter_ipsplit_t* self, const core_object_t* obj)
+{
+ mlassert_self();
+
+ /* Find ip/ip6 object in chain. */
+ core_object_t* pkt = (core_object_t*)obj;
+ while (pkt != NULL) {
+ if (pkt->obj_type == CORE_OBJECT_IP || pkt->obj_type == CORE_OBJECT_IP6)
+ break;
+ pkt = (core_object_t*)pkt->obj_prev;
+ }
+ if (pkt == NULL) {
+ self->discarded++;
+ lwarning("packet discarded (missing ip/ip6 object)");
+ return;
+ }
+
+ /* Lookup IPv4/IPv6 address in trie (prefix-tree). Inserts new node if not found. */
+ trie_val_t* node = 0;
+ switch (pkt->obj_type) {
+ case CORE_OBJECT_IP: {
+ core_object_ip_t* ip = (core_object_ip_t*)pkt;
+ node = trie_get_ins(_self->trie, ip->src, sizeof(ip->src));
+ break;
+ }
+ case CORE_OBJECT_IP6: {
+ core_object_ip6_t* ip6 = (core_object_ip6_t*)pkt;
+ node = trie_get_ins(_self->trie, ip6->src, sizeof(ip6->src));
+ break;
+ }
+ default:
+ lfatal("unsupported object type");
+ }
+ lassert(node, "trie failure");
+
+ _client_t* client;
+ if (*node == NULL) { /* IP address not found in tree -> create new client. */
+ lfatal_oom(client = malloc(sizeof(_client_t)));
+ *node = (void*)client;
+ _assign_client_to_receiver(self, client);
+ }
+
+ client = (_client_t*)*node;
+ _overwrite(self, pkt, client);
+ client->recv->recv(client->recv->ctx, obj);
+}
+
+core_receiver_t filter_ipsplit_receiver(filter_ipsplit_t* self)
+{
+ mlassert_self();
+
+ if (!self->recv) {
+ lfatal("no receiver(s) set");
+ }
+
+ return (core_receiver_t)_receive;
+}
diff --git a/src/filter/ipsplit.h b/src/filter/ipsplit.h
new file mode 100644
index 0000000..16c932e
--- /dev/null
+++ b/src/filter/ipsplit.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2019-2020 CZ.NIC, z.s.p.o.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/log.h"
+#include "core/receiver.h"
+
+#ifndef __dnsjit_filter_ipsplit_h
+#define __dnsjit_filter_ipsplit_h
+
+#include "filter/ipsplit.hh"
+
+#endif
diff --git a/src/filter/ipsplit.hh b/src/filter/ipsplit.hh
new file mode 100644
index 0000000..c362ac3
--- /dev/null
+++ b/src/filter/ipsplit.hh
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2019-2020, CZ.NIC, z.s.p.o.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+
+typedef struct filter_ipsplit_recv filter_ipsplit_recv_t;
+struct filter_ipsplit_recv {
+ filter_ipsplit_recv_t* next;
+
+ core_receiver_t recv;
+ void* ctx;
+
+ uint32_t n_clients; /* Total number of clients assigned to this receiver. */
+
+ uint32_t weight;
+};
+
+typedef struct filter_ipsplit {
+ core_log_t _log;
+
+ enum {
+ IPSPLIT_MODE_SEQUENTIAL = 0,
+ IPSPLIT_MODE_RANDOM = 1
+ } mode;
+ enum {
+ IPSPLIT_OVERWRITE_NONE = 0,
+ IPSPLIT_OVERWRITE_SRC = 1,
+ IPSPLIT_OVERWRITE_DST = 2
+ } overwrite;
+
+ uint64_t discarded;
+
+ filter_ipsplit_recv_t* recv;
+} filter_ipsplit_t;
+
+core_log_t* filter_ipsplit_log();
+
+filter_ipsplit_t* filter_ipsplit_new();
+void filter_ipsplit_free(filter_ipsplit_t* self);
+void filter_ipsplit_add(filter_ipsplit_t* self, core_receiver_t recv, void* ctx, uint32_t weight);
+void filter_ipsplit_srand(unsigned int seed);
+
+core_receiver_t filter_ipsplit_receiver(filter_ipsplit_t* self);
diff --git a/src/filter/ipsplit.lua b/src/filter/ipsplit.lua
new file mode 100644
index 0000000..cca0249
--- /dev/null
+++ b/src/filter/ipsplit.lua
@@ -0,0 +1,122 @@
+-- Copyright (c) 2019-2020 CZ.NIC, z.s.p.o.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.filter.ipsplit
+-- Pass objects to receivers based on the source IP address
+-- local ipsplit = require("dnsjit.filter.ipsplit").new()
+-- ipsplit.receiver(...)
+-- ipsplit.receiver(...)
+-- ipsplit.receiver(...)
+-- input.receiver(ipsplit)
+--
+-- The filter passes objects to other receivers.
+-- Object chains without IPv4/IPv6 packet are discarded.
+-- Packets which have the same source IP address are considered to be sent
+-- from the same "client".
+-- When the first packet from a client is processed, the client is assigned
+-- to a receiver.
+-- All objects from this client will be passed to the assigned receiver.
+-- The filter can also write a receiver-specific client ID (starting from 1)
+-- to the source or destination IP in the packet.
+module(...,package.seeall)
+
+require("dnsjit.filter.ipsplit_h")
+local bit = require("bit")
+local object = require("dnsjit.core.objects")
+local ffi = require("ffi")
+local C = ffi.C
+
+local IpSplit = {}
+
+-- Create a new IpSplit filter.
+function IpSplit.new()
+ local self = {
+ obj = C.filter_ipsplit_new(),
+ }
+ ffi.gc(self.obj, C.filter_ipsplit_free)
+ return setmetatable(self, { __index = IpSplit })
+end
+
+-- Return the Log object to control logging of this instance or module.
+function IpSplit:log()
+ if self == nil then
+ return C.filter_ipsplit_log()
+ end
+ return self.obj._log
+end
+
+-- Return the C functions and context for receiving objects.
+function IpSplit:receive()
+ local recv = C.filter_ipsplit_receiver(self.obj)
+ return recv, self.obj
+end
+
+-- Set the receiver to pass objects to, this can be called multiple times to
+-- set additional receivers.
+-- The weight parameter can be used to adjust distribution of clients among
+-- receivers.
+-- Weight must be a positive integer (default is 1).
+function IpSplit:receiver(o, weight)
+ local recv, ctx = o:receive()
+ if weight == nil then
+ weight = 1
+ end
+ C.filter_ipsplit_add(self.obj, recv, ctx, weight)
+end
+
+-- Number of input packets discarded due to various reasons.
+-- To investigate causes, run with increased logging level.
+function IpSplit:discarded()
+ return tonumber(self.obj.discarded)
+end
+
+-- Set the client assignment mode to sequential.
+-- Assigns `weight` clients to a receiver before continuing with the next
+-- receiver (default mode).
+function IpSplit:sequential()
+ self.obj.mode = "IPSPLIT_MODE_SEQUENTIAL"
+end
+
+-- Set the client assignment mode to random.
+-- Each client is randomly assigned to a receiver (weight affects the
+-- probability).
+-- The client assignment is stable (and portable) for given seed.
+function IpSplit:random(seed)
+ self.obj.mode = "IPSPLIT_MODE_RANDOM"
+ if seed then
+ C.filter_ipsplit_srand(seed)
+ end
+end
+
+-- Don't overwrite source or destination IP (default).
+function IpSplit:overwrite_none()
+ self.obj.overwrite = "IPSPLIT_OVERWRITE_NONE"
+end
+
+-- Write receiver-specific client ID to bytes 0-3 of source IP (host byte order).
+function IpSplit:overwrite_src()
+ self.obj.overwrite = "IPSPLIT_OVERWRITE_SRC"
+end
+
+-- Write receiver-specific client ID to bytes 0-3 of destination IP (host byte
+-- order).
+function IpSplit:overwrite_dst()
+ self.obj.overwrite = "IPSPLIT_OVERWRITE_DST"
+end
+
+return IpSplit
diff --git a/src/filter/layer.c b/src/filter/layer.c
new file mode 100644
index 0000000..b360ca3
--- /dev/null
+++ b/src/filter/layer.c
@@ -0,0 +1,689 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "filter/layer.h"
+#include "core/assert.h"
+
+#include <string.h>
+#include <pcap/pcap.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#ifdef HAVE_NET_ETHERNET_H
+#include <net/ethernet.h>
+#endif
+#ifdef HAVE_NET_ETHERTYPES_H
+#include <net/ethertypes.h>
+#endif
+#ifdef HAVE_ENDIAN_H
+#include <endian.h>
+#else
+#ifdef HAVE_SYS_ENDIAN_H
+#include <sys/endian.h>
+#else
+#ifdef HAVE_MACHINE_ENDIAN_H
+#include <machine/endian.h>
+#endif
+#endif
+#endif
+#ifdef HAVE_BYTESWAP_H
+#include <byteswap.h>
+#endif
+#ifndef bswap_16
+#ifndef bswap16
+#define bswap_16(x) swap16(x)
+#define bswap_32(x) swap32(x)
+#define bswap_64(x) swap64(x)
+#else
+#define bswap_16(x) bswap16(x)
+#define bswap_32(x) bswap32(x)
+#define bswap_64(x) bswap64(x)
+#endif
+#endif
+
+#define N_IEEE802 3
+
+static core_log_t _log = LOG_T_INIT("filter.layer");
+static filter_layer_t _defaults = {
+ LOG_T_INIT_OBJ("filter.layer"),
+ 0, 0,
+ 0, 0,
+ 0,
+ CORE_OBJECT_NULL_INIT(0),
+ CORE_OBJECT_ETHER_INIT(0),
+ CORE_OBJECT_LOOP_INIT(0),
+ CORE_OBJECT_LINUXSLL_INIT(0),
+ 0, { CORE_OBJECT_IEEE802_INIT(0), CORE_OBJECT_IEEE802_INIT(0), CORE_OBJECT_IEEE802_INIT(0) },
+ CORE_OBJECT_IP_INIT(0),
+ CORE_OBJECT_IP6_INIT(0),
+ CORE_OBJECT_GRE_INIT(0),
+ CORE_OBJECT_ICMP_INIT(0),
+ CORE_OBJECT_ICMP6_INIT(0),
+ CORE_OBJECT_UDP_INIT(0),
+ CORE_OBJECT_TCP_INIT(0),
+ CORE_OBJECT_PAYLOAD_INIT(0)
+};
+
+core_log_t* filter_layer_log()
+{
+ return &_log;
+}
+
+void filter_layer_init(filter_layer_t* self)
+{
+ mlassert_self();
+
+ *self = _defaults;
+}
+
+void filter_layer_destroy(filter_layer_t* self)
+{
+ mlassert_self();
+}
+
+#define need4x2(v1, v2, p, l) \
+ if (l < 1) { \
+ break; \
+ } \
+ v1 = (*p) >> 4; \
+ v2 = (*p) & 0xf; \
+ p += 1; \
+ l -= 1
+
+#define need8(v, p, l) \
+ if (l < 1) { \
+ break; \
+ } \
+ v = *p; \
+ p += 1; \
+ l -= 1
+
+static inline uint16_t _need16(const void* ptr)
+{
+ uint16_t v;
+ memcpy(&v, ptr, sizeof(v));
+ return be16toh(v);
+}
+
+#define need16(v, p, l) \
+ if (l < 2) { \
+ break; \
+ } \
+ v = _need16(p); \
+ p += 2; \
+ l -= 2
+
+#define needr16(v, p, l) \
+ if (l < 2) { \
+ break; \
+ } \
+ v = bswap_16(_need16(p)); \
+ p += 2; \
+ l -= 2
+
+static inline uint32_t _need32(const void* ptr)
+{
+ uint32_t v;
+ memcpy(&v, ptr, sizeof(v));
+ return be32toh(v);
+}
+
+#define need32(v, p, l) \
+ if (l < 4) { \
+ break; \
+ } \
+ v = _need32(p); \
+ p += 4; \
+ l -= 4
+
+#define needr32(v, p, l) \
+ if (l < 4) { \
+ break; \
+ } \
+ v = bswap_32(_need32(p)); \
+ p += 4; \
+ l -= 4
+
+#define needxb(b, x, p, l) \
+ if (l < x) { \
+ break; \
+ } \
+ memcpy(b, p, x); \
+ p += x; \
+ l -= x
+
+#define advancexb(x, p, l) \
+ if (l < x) { \
+ break; \
+ } \
+ p += x; \
+ l -= x
+
+//static int _ip(filter_layer_t* self, const core_object_t* obj, const unsigned char* pkt, size_t len);
+
+static inline int _proto(filter_layer_t* self, uint8_t proto, const core_object_t* obj, const unsigned char* pkt, size_t len)
+{
+ switch (proto) {
+ case IPPROTO_GRE: {
+ core_object_gre_t* gre = &self->gre;
+ gre->obj_prev = obj;
+
+ need16(gre->gre_flags, pkt, len);
+ need16(gre->ether_type, pkt, len);
+
+ /* TODO: Incomplete, check RFC 1701 */
+
+ self->produced = (core_object_t*)gre;
+
+ // if (gre.gre_flags & 0x1) {
+ // need16(gre.checksum, pkt, len);
+ // }
+ // if (gre.gre_flags & 0x4) {
+ // need16(gre.key, pkt, len);
+ // }
+ // if (gre.gre_flags & 0x8) {
+ // need16(gre.sequence, pkt, len);
+ // }
+ //
+ // switch (gre.ether_type) {
+ // case ETHERTYPE_IP:
+ // case ETHERTYPE_IPV6:
+ // return _ip(self, (core_object_t*)gre, pkt, len);
+ //
+ // default:
+ // break;
+ // }
+ break;
+ }
+ case IPPROTO_ICMP: {
+ core_object_icmp_t* icmp = &self->icmp;
+ icmp->obj_prev = obj;
+
+ need8(icmp->type, pkt, len);
+ need8(icmp->code, pkt, len);
+ need16(icmp->cksum, pkt, len);
+
+ self->produced = (core_object_t*)icmp;
+ break;
+ }
+ case IPPROTO_ICMPV6: {
+ core_object_icmp6_t* icmp6 = &self->icmp6;
+ icmp6->obj_prev = obj;
+
+ need8(icmp6->type, pkt, len);
+ need8(icmp6->code, pkt, len);
+ need16(icmp6->cksum, pkt, len);
+
+ self->produced = (core_object_t*)icmp6;
+ break;
+ }
+ case IPPROTO_UDP: {
+ core_object_udp_t* udp = &self->udp;
+ core_object_payload_t* payload = &self->payload;
+ udp->obj_prev = obj;
+
+ need16(udp->sport, pkt, len);
+ need16(udp->dport, pkt, len);
+ need16(udp->ulen, pkt, len);
+ need16(udp->sum, pkt, len);
+
+ payload->obj_prev = (core_object_t*)udp;
+
+ /* Check for padding */
+ if (len > udp->ulen) {
+ payload->padding = len - udp->ulen;
+ payload->len = len - payload->padding;
+ } else {
+ payload->padding = 0;
+ payload->len = len;
+ }
+ payload->payload = (uint8_t*)pkt;
+
+ self->produced = (core_object_t*)payload;
+ break;
+ }
+ case IPPROTO_TCP: {
+ core_object_tcp_t* tcp = &self->tcp;
+ core_object_payload_t* payload = &self->payload;
+ tcp->obj_prev = obj;
+
+ need16(tcp->sport, pkt, len);
+ need16(tcp->dport, pkt, len);
+ need32(tcp->seq, pkt, len);
+ need32(tcp->ack, pkt, len);
+ need4x2(tcp->off, tcp->x2, pkt, len);
+ need8(tcp->flags, pkt, len);
+ need16(tcp->win, pkt, len);
+ need16(tcp->sum, pkt, len);
+ need16(tcp->urp, pkt, len);
+ if (tcp->off > 5) {
+ tcp->opts_len = (tcp->off - 5) * 4;
+ needxb(tcp->opts, tcp->opts_len, pkt, len);
+ } else {
+ tcp->opts_len = 0;
+ }
+
+ payload->obj_prev = (core_object_t*)tcp;
+
+ /* Check for padding */
+ if (obj->obj_type == CORE_OBJECT_IP && len > (((const core_object_ip_t*)obj)->len - (((const core_object_ip_t*)obj)->hl * 4))) {
+ payload->padding = len - (((const core_object_ip_t*)obj)->len - (((const core_object_ip_t*)obj)->hl * 4));
+ payload->len = len - payload->padding;
+ } else if (obj->obj_type == CORE_OBJECT_IP6 && len > ((const core_object_ip6_t*)obj)->plen) {
+ payload->padding = len - ((const core_object_ip6_t*)obj)->plen;
+ payload->len = len - payload->padding;
+ } else {
+ payload->padding = 0;
+ payload->len = len;
+ }
+
+ payload->payload = (uint8_t*)pkt;
+
+ self->produced = (core_object_t*)payload;
+ break;
+ }
+ default:
+ self->produced = obj;
+ break;
+ }
+
+ return 0;
+}
+
+static inline int _ip(filter_layer_t* self, const core_object_t* obj, const unsigned char* pkt, size_t len)
+{
+ if (len) {
+ switch ((*pkt >> 4)) {
+ case 4: {
+ core_object_ip_t* ip = &self->ip;
+
+ ip->obj_prev = obj;
+
+ need4x2(ip->v, ip->hl, pkt, len);
+ need8(ip->tos, pkt, len);
+ need16(ip->len, pkt, len);
+ need16(ip->id, pkt, len);
+ need16(ip->off, pkt, len);
+ need8(ip->ttl, pkt, len);
+ need8(ip->p, pkt, len);
+ need16(ip->sum, pkt, len);
+ needxb(&ip->src, 4, pkt, len);
+ needxb(&ip->dst, 4, pkt, len);
+
+ /* TODO: IPv4 options */
+
+ if (ip->hl < 5)
+ break;
+ if (ip->hl > 5) {
+ advancexb((ip->hl - 5) * 4, pkt, len);
+ }
+
+ /* Check reported length for missing payload */
+ if (ip->len < (ip->hl * 4)) {
+ break;
+ }
+ if (len < (ip->len - (ip->hl * 4))) {
+ break;
+ }
+
+ if (ip->off & 0x2000 || ip->off & 0x1fff) {
+ core_object_payload_t* payload = &self->payload;
+
+ payload->obj_prev = (core_object_t*)ip;
+
+ /* Check for padding */
+ if (len > (ip->len - (ip->hl * 4))) {
+ payload->padding = len - (ip->len - (ip->hl * 4));
+ payload->len = len - payload->padding;
+ } else {
+ payload->padding = 0;
+ payload->len = len;
+ }
+ payload->payload = (uint8_t*)pkt;
+
+ self->produced = (core_object_t*)payload;
+ return 0;
+ }
+
+ return _proto(self, ip->p, (core_object_t*)ip, pkt, len);
+ }
+ case 6: {
+ core_object_ip6_t* ip6 = &self->ip6;
+ struct ip6_ext ext;
+
+ ip6->obj_prev = obj;
+ ip6->is_frag = ip6->have_rtdst = 0;
+
+ need32(ip6->flow, pkt, len);
+ need16(ip6->plen, pkt, len);
+ need8(ip6->nxt, pkt, len);
+ need8(ip6->hlim, pkt, len);
+ needxb(&ip6->src, 16, pkt, len);
+ needxb(&ip6->dst, 16, pkt, len);
+
+ /* Check reported length for missing payload */
+ if (len < ip6->plen) {
+ break;
+ }
+
+ ext.ip6e_nxt = ip6->nxt;
+ ext.ip6e_len = 0;
+ while (ext.ip6e_nxt != IPPROTO_NONE
+ && ext.ip6e_nxt != IPPROTO_GRE
+ && ext.ip6e_nxt != IPPROTO_ICMPV6
+ && ext.ip6e_nxt != IPPROTO_UDP
+ && ext.ip6e_nxt != IPPROTO_TCP) {
+
+ /*
+ * Advance to the start of next header, this may not be needed
+ * if it's the first header or if the header is supported.
+ */
+ if (ext.ip6e_len) {
+ advancexb(ext.ip6e_len * 8, pkt, len);
+ }
+
+ /* TODO: Store IPv6 headers? */
+
+ /* Handle supported headers */
+ if (ext.ip6e_nxt == IPPROTO_FRAGMENT) {
+ if (ip6->is_frag) {
+ return 1;
+ }
+ need8(ext.ip6e_nxt, pkt, len);
+ need8(ext.ip6e_len, pkt, len);
+ if (ext.ip6e_len) {
+ return 1;
+ }
+ need16(ip6->frag_offlg, pkt, len);
+ need32(ip6->frag_ident, pkt, len);
+ ip6->is_frag = 1;
+ } else if (ext.ip6e_nxt == IPPROTO_ROUTING) {
+ struct ip6_rthdr rthdr;
+
+ if (ip6->have_rtdst) {
+ return 1;
+ }
+
+ need8(ext.ip6e_nxt, pkt, len);
+ need8(ext.ip6e_len, pkt, len);
+ need8(rthdr.ip6r_type, pkt, len);
+ need8(rthdr.ip6r_segleft, pkt, len);
+ advancexb(4, pkt, len);
+
+ if (!rthdr.ip6r_type && rthdr.ip6r_segleft) {
+ if (ext.ip6e_len & 1) {
+ return 1;
+ }
+ if (ext.ip6e_len > 2) {
+ advancexb(ext.ip6e_len - 2, pkt, len);
+ }
+ needxb(ip6->rtdst, 16, pkt, len);
+ ip6->have_rtdst = 1;
+ }
+ } else {
+ need8(ext.ip6e_nxt, pkt, len);
+ need8(ext.ip6e_len, pkt, len);
+ advancexb(6, pkt, len);
+ }
+ }
+
+ if (ext.ip6e_nxt == IPPROTO_NONE || ip6->is_frag) {
+ core_object_payload_t* payload = &self->payload;
+
+ payload->obj_prev = (core_object_t*)ip6;
+
+ /* Check for padding */
+ if (len > ip6->plen) {
+ payload->padding = len - ip6->plen;
+ payload->len = len - payload->padding;
+ } else {
+ payload->padding = 0;
+ payload->len = len;
+ }
+ payload->payload = (uint8_t*)pkt;
+
+ self->produced = (core_object_t*)payload;
+ return 0;
+ }
+
+ return _proto(self, ext.ip6e_nxt, (core_object_t*)ip6, pkt, len);
+ }
+ default:
+ break;
+ }
+ }
+
+ self->produced = obj;
+
+ return 0;
+}
+
+static inline int _ieee802(filter_layer_t* self, uint16_t tpid, const core_object_t* obj, const unsigned char* pkt, size_t len)
+{
+ core_object_ieee802_t* ieee802 = &self->ieee802[self->n_ieee802];
+ uint16_t tci;
+
+ ieee802->obj_prev = obj;
+
+ for (;;) {
+ ieee802->tpid = tpid;
+ need16(tci, pkt, len);
+ ieee802->pcp = (tci & 0xe000) >> 13;
+ ieee802->dei = (tci & 0x1000) >> 12;
+ ieee802->vid = tci & 0x0fff;
+ need16(ieee802->ether_type, pkt, len);
+
+ switch (ieee802->ether_type) {
+ case 0x88a8: /* 802.1ad */
+ case 0x9100: /* 802.1 QinQ non-standard */
+ self->n_ieee802++;
+ if (self->n_ieee802 < N_IEEE802) {
+ obj = (const core_object_t*)ieee802;
+ ieee802 = &self->ieee802[self->n_ieee802];
+ ieee802->obj_prev = obj;
+ tpid = ieee802->ether_type;
+ continue;
+ }
+ return 1;
+
+ case ETHERTYPE_IP:
+ case ETHERTYPE_IPV6:
+ return _ip(self, (core_object_t*)ieee802, pkt, len);
+
+ default:
+ break;
+ }
+ break;
+ }
+
+ self->produced = obj;
+
+ return 0;
+}
+
+static inline int _link(filter_layer_t* self, const core_object_pcap_t* pcap)
+{
+ const unsigned char* pkt;
+ size_t len;
+
+ self->n_ieee802 = 0;
+
+ pkt = pcap->bytes;
+ len = pcap->caplen;
+
+ switch (pcap->linktype) {
+ case DLT_NULL: {
+ core_object_null_t* null = &self->null;
+ null->obj_prev = (core_object_t*)pcap;
+
+ if (pcap->is_swapped) {
+ needr32(null->family, pkt, len);
+ } else {
+ need32(null->family, pkt, len);
+ }
+
+ switch (null->family) {
+ case 2:
+ case 24:
+ case 28:
+ case 30:
+ return _ip(self, (core_object_t*)null, pkt, len);
+
+ default:
+ break;
+ }
+ break;
+ }
+ case DLT_EN10MB: {
+ core_object_ether_t* ether = &self->ether;
+ ether->obj_prev = (core_object_t*)pcap;
+
+ needxb(ether->dhost, 6, pkt, len);
+ needxb(ether->shost, 6, pkt, len);
+ need16(ether->type, pkt, len);
+
+ switch (ether->type) {
+ case 0x8100: /* 802.1q */
+ case 0x88a8: /* 802.1ad */
+ case 0x9100: /* 802.1 QinQ non-standard */
+ return _ieee802(self, ether->type, (core_object_t*)ether, pkt, len);
+
+ case ETHERTYPE_IP:
+ case ETHERTYPE_IPV6:
+ return _ip(self, (core_object_t*)ether, pkt, len);
+
+ default:
+ break;
+ }
+ break;
+ }
+ case DLT_LOOP: {
+ core_object_loop_t* loop = &self->loop;
+ loop->obj_prev = (core_object_t*)pcap;
+
+ need32(loop->family, pkt, len);
+
+ switch (loop->family) {
+ case 2:
+ case 24:
+ case 28:
+ case 30:
+ return _ip(self, (core_object_t*)loop, pkt, len);
+
+ default:
+ break;
+ }
+ break;
+ }
+ case DLT_RAW:
+#ifdef DLT_IPV4
+ case DLT_IPV4:
+#endif
+#ifdef DLT_IPV6
+ case DLT_IPV6:
+#endif
+ return _ip(self, (core_object_t*)pcap, pkt, len);
+ case DLT_LINUX_SLL: {
+ core_object_linuxsll_t* linuxsll = &self->linuxsll;
+ linuxsll->obj_prev = (core_object_t*)pcap;
+
+ need16(linuxsll->packet_type, pkt, len);
+ need16(linuxsll->arp_hardware, pkt, len);
+ need16(linuxsll->link_layer_address_length, pkt, len);
+ needxb(linuxsll->link_layer_address, 8, pkt, len);
+ need16(linuxsll->ether_type, pkt, len);
+
+ switch (linuxsll->ether_type) {
+ case 0x8100: /* 802.1q */
+ case 0x88a8: /* 802.1ad */
+ case 0x9100: /* 802.1 QinQ non-standard */
+ return _ieee802(self, linuxsll->ether_type, (core_object_t*)linuxsll, pkt, len);
+
+ case ETHERTYPE_IP:
+ case ETHERTYPE_IPV6:
+ return _ip(self, (core_object_t*)linuxsll, pkt, len);
+
+ default:
+ break;
+ }
+ break;
+ }
+ /* TODO: These might be interesting to implement
+ case DLT_IPNET:
+ case DLT_PKTAP:
+ */
+ default:
+ break;
+ }
+
+ self->produced = (core_object_t*)pcap;
+
+ return 0;
+}
+
+static void _receive(filter_layer_t* self, const core_object_t* obj)
+{
+ mlassert_self();
+ lassert(obj, "obj is nil");
+
+ if (!self->recv) {
+ lfatal("no receiver set");
+ }
+ if (obj->obj_type != CORE_OBJECT_PCAP) {
+ lfatal("obj is not CORE_OBJECT_PCAP");
+ }
+
+ if (!_link(self, (core_object_pcap_t*)obj)) {
+ self->recv(self->ctx, self->produced);
+ }
+}
+
+core_receiver_t filter_layer_receiver()
+{
+ return (core_receiver_t)_receive;
+}
+
+static const core_object_t* _produce(filter_layer_t* self)
+{
+ const core_object_t* obj;
+ mlassert_self();
+
+ obj = self->prod(self->prod_ctx);
+ if (!obj || obj->obj_type != CORE_OBJECT_PCAP || _link(self, (core_object_pcap_t*)obj)) {
+ return 0;
+ }
+
+ return self->produced;
+}
+
+core_producer_t filter_layer_producer(filter_layer_t* self)
+{
+ mlassert_self();
+
+ if (!self->prod) {
+ lfatal("no producer set");
+ }
+
+ return (core_producer_t)_produce;
+}
diff --git a/src/filter/layer.h b/src/filter/layer.h
new file mode 100644
index 0000000..da8671c
--- /dev/null
+++ b/src/filter/layer.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/log.h"
+#include "core/receiver.h"
+#include "core/producer.h"
+#include "core/object/pcap.h"
+#include "core/object/null.h"
+#include "core/object/ether.h"
+#include "core/object/loop.h"
+#include "core/object/linuxsll.h"
+#include "core/object/ieee802.h"
+#include "core/object/ip.h"
+#include "core/object/ip6.h"
+#include "core/object/gre.h"
+#include "core/object/icmp.h"
+#include "core/object/icmp6.h"
+#include "core/object/udp.h"
+#include "core/object/tcp.h"
+#include "core/object/payload.h"
+
+#ifndef __dnsjit_filter_layer_h
+#define __dnsjit_filter_layer_h
+
+#include "filter/layer.hh"
+
+#endif
diff --git a/src/filter/layer.hh b/src/filter/layer.hh
new file mode 100644
index 0000000..f8f260b
--- /dev/null
+++ b/src/filter/layer.hh
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+//lua:require("dnsjit.core.producer_h")
+//lua:require("dnsjit.core.object.pcap_h")
+//lua:require("dnsjit.core.object.null_h")
+//lua:require("dnsjit.core.object.ether_h")
+//lua:require("dnsjit.core.object.loop_h")
+//lua:require("dnsjit.core.object.linuxsll_h")
+//lua:require("dnsjit.core.object.ieee802_h")
+//lua:require("dnsjit.core.object.ip_h")
+//lua:require("dnsjit.core.object.ip6_h")
+//lua:require("dnsjit.core.object.gre_h")
+//lua:require("dnsjit.core.object.icmp_h")
+//lua:require("dnsjit.core.object.icmp6_h")
+//lua:require("dnsjit.core.object.udp_h")
+//lua:require("dnsjit.core.object.tcp_h")
+//lua:require("dnsjit.core.object.payload_h")
+
+typedef struct filter_layer {
+ core_log_t _log;
+ core_receiver_t recv;
+ void* ctx;
+
+ core_producer_t prod;
+ void* prod_ctx;
+
+ const core_object_t* produced;
+ core_object_null_t null;
+ core_object_ether_t ether;
+ core_object_loop_t loop;
+ core_object_linuxsll_t linuxsll;
+ size_t n_ieee802;
+ core_object_ieee802_t ieee802[3]; // N_IEEE802
+ core_object_ip_t ip;
+ core_object_ip6_t ip6;
+ core_object_gre_t gre;
+ core_object_icmp_t icmp;
+ core_object_icmp6_t icmp6;
+ core_object_udp_t udp;
+ core_object_tcp_t tcp;
+ core_object_payload_t payload;
+} filter_layer_t;
+
+core_log_t* filter_layer_log();
+
+void filter_layer_init(filter_layer_t* self);
+void filter_layer_destroy(filter_layer_t* self);
+
+core_receiver_t filter_layer_receiver();
+core_producer_t filter_layer_producer(filter_layer_t* self);
diff --git a/src/filter/layer.lua b/src/filter/layer.lua
new file mode 100644
index 0000000..74c56ba
--- /dev/null
+++ b/src/filter/layer.lua
@@ -0,0 +1,93 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.filter.layer
+-- Parse the ether/IP stack
+-- local filter = require("dnsjit.filter.layer").new()
+--
+-- Parse the ether/IP stack of the received objects and send the top most
+-- object to the receivers.
+-- Objects are chained which each layer in the stack with the top most first.
+-- Currently supports input
+-- .IR dnsjit.core.object.pcap .
+module(...,package.seeall)
+
+require("dnsjit.filter.layer_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "filter_layer_t"
+local filter_layer_t = ffi.typeof(t_name)
+local Layer = {}
+
+-- Create a new Layer filter.
+function Layer.new()
+ local self = {
+ _receiver = nil,
+ obj = filter_layer_t(),
+ }
+ C.filter_layer_init(self.obj)
+ ffi.gc(self.obj, C.filter_layer_destroy)
+ return setmetatable(self, { __index = Layer })
+end
+
+-- Return the Log object to control logging of this instance or module.
+function Layer:log()
+ if self == nil then
+ return C.filter_layer_log()
+ end
+ return self.obj._log
+end
+
+-- Return the C functions and context for receiving objects.
+function Layer:receive()
+ return C.filter_layer_receiver(), self.obj
+end
+
+-- Set the receiver to pass objects to.
+function Layer:receiver(o)
+ self.obj.recv, self.obj.ctx = o:receive()
+ self._receiver = o
+end
+
+-- Return the C functions and context for producing objects.
+function Layer:produce()
+ return C.filter_layer_producer(self.obj), self.obj
+end
+
+-- Set the producer to get objects from.
+function Layer:producer(o)
+ self.obj.prod, self.obj.prod_ctx = o:produce()
+ self._producer = o
+end
+
+-- dnsjit.core.object.pcap (3),
+-- dnsjit.core.object.ether (3),
+-- dnsjit.core.object.null (3),
+-- dnsjit.core.object.loop (3),
+-- dnsjit.core.object.linuxsll (3),
+-- dnsjit.core.object.ieee802 (3),
+-- dnsjit.core.object.gre (3),
+-- dnsjit.core.object.ip (3),
+-- dnsjit.core.object.ip6 (3),
+-- dnsjit.core.object.icmp (3),
+-- dnsjit.core.object.icmp6 (3),
+-- dnsjit.core.object.udp (3),
+-- dnsjit.core.object.tcp (3),
+-- dnsjit.core.object.payload (3)
+return Layer
diff --git a/src/filter/split.c b/src/filter/split.c
new file mode 100644
index 0000000..dccf38a
--- /dev/null
+++ b/src/filter/split.c
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "filter/split.h"
+#include "core/assert.h"
+
+static core_log_t _log = LOG_T_INIT("filter.split");
+static filter_split_t _defaults = {
+ LOG_T_INIT_OBJ("filter.split"),
+ FILTER_SPLIT_MODE_ROUNDROBIN, 0, 0, 0
+};
+
+core_log_t* filter_split_log()
+{
+ return &_log;
+}
+
+void filter_split_init(filter_split_t* self)
+{
+ mlassert_self();
+
+ *self = _defaults;
+}
+
+void filter_split_destroy(filter_split_t* self)
+{
+ filter_split_recv_t* r;
+ mlassert_self();
+
+ if (self->recv_last)
+ self->recv_last->next = 0;
+ while ((r = self->recv_first)) {
+ self->recv_first = r->next;
+ free(r);
+ }
+}
+
+void filter_split_add(filter_split_t* self, core_receiver_t recv, void* ctx)
+{
+ filter_split_recv_t* r;
+ mlassert_self();
+ lassert(recv, "recv is nil");
+
+ lfatal_oom(r = malloc(sizeof(filter_split_recv_t)));
+ r->recv = recv;
+ r->ctx = ctx;
+
+ if (self->recv_last) {
+ self->recv_last->next = r;
+ r->next = self->recv_first;
+ self->recv_first = r;
+ } else {
+ self->recv_first = self->recv = self->recv_last = r;
+ r->next = r;
+ }
+}
+
+static void _roundrobin(filter_split_t* self, const core_object_t* obj)
+{
+ mlassert_self();
+
+ self->recv->recv(self->recv->ctx, obj);
+ self->recv = self->recv->next;
+}
+
+static void _sendall(filter_split_t* self, const core_object_t* obj)
+{
+ filter_split_recv_t* r;
+ mlassert_self();
+
+ for (r = self->recv_first; r; r = r->next) {
+ r->recv(r->ctx, obj);
+ if (r == self->recv_last)
+ break;
+ }
+}
+
+core_receiver_t filter_split_receiver(filter_split_t* self)
+{
+ mlassert_self();
+
+ if (!self->recv) {
+ lfatal("no receiver(s) set");
+ }
+
+ switch (self->mode) {
+ case FILTER_SPLIT_MODE_ROUNDROBIN:
+ return (core_receiver_t)_roundrobin;
+ case FILTER_SPLIT_MODE_SENDALL:
+ return (core_receiver_t)_sendall;
+ default:
+ lfatal("invalid split mode");
+ }
+ return 0;
+}
diff --git a/src/filter/split.h b/src/filter/split.h
new file mode 100644
index 0000000..921b649
--- /dev/null
+++ b/src/filter/split.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/log.h"
+#include "core/receiver.h"
+
+#ifndef __dnsjit_filter_split_h
+#define __dnsjit_filter_split_h
+
+#include "filter/split.hh"
+
+#endif
diff --git a/src/filter/split.hh b/src/filter/split.hh
new file mode 100644
index 0000000..2aba2ae
--- /dev/null
+++ b/src/filter/split.hh
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+
+typedef enum filter_split_mode {
+ FILTER_SPLIT_MODE_ROUNDROBIN,
+ FILTER_SPLIT_MODE_SENDALL
+} filter_split_mode_t;
+
+typedef struct filter_split_recv filter_split_recv_t;
+struct filter_split_recv {
+ filter_split_recv_t* next;
+ core_receiver_t recv;
+ void* ctx;
+};
+
+typedef struct filter_split {
+ core_log_t _log;
+ filter_split_mode_t mode;
+ filter_split_recv_t* recv_first;
+ filter_split_recv_t* recv;
+ filter_split_recv_t* recv_last;
+} filter_split_t;
+
+core_log_t* filter_split_log();
+
+void filter_split_init(filter_split_t* self);
+void filter_split_destroy(filter_split_t* self);
+void filter_split_add(filter_split_t* self, core_receiver_t recv, void* ctx);
+
+core_receiver_t filter_split_receiver(filter_split_t* self);
diff --git a/src/filter/split.lua b/src/filter/split.lua
new file mode 100644
index 0000000..d9aec4f
--- /dev/null
+++ b/src/filter/split.lua
@@ -0,0 +1,80 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.filter.split
+-- Passthrough to other receivers in various ways
+-- local filter = require("dnsjit.filter.split").new()
+-- filter.receiver(...)
+-- filter.receiver(...)
+-- filter.receiver(...)
+-- input.receiver(filter)
+--
+-- Filter to pass objects to others in various ways.
+module(...,package.seeall)
+
+require("dnsjit.filter.split_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "filter_split_t"
+local filter_split_t = ffi.typeof(t_name)
+local Split = {}
+
+-- Create a new Split filter.
+function Split.new()
+ local self = {
+ receivers = {},
+ obj = filter_split_t(),
+ }
+ C.filter_split_init(self.obj)
+ ffi.gc(self.obj, C.filter_split_destroy)
+ return setmetatable(self, { __index = Split })
+end
+
+-- Return the Log object to control logging of this instance or module.
+function Split:log()
+ if self == nil then
+ return C.filter_split_log()
+ end
+ return self.obj._log
+end
+
+-- Set the passthrough mode to round robin (default mode).
+function Split:roundrobin()
+ self.obj.mode = "FILTER_SPLIT_MODE_ROUNDROBIN"
+end
+
+-- Set the passthrough mode to send to all receivers.
+function Split:sendall()
+ self.obj.mode = "FILTER_SPLIT_MODE_SENDALL"
+end
+
+-- Return the C functions and context for receiving objects.
+function Split:receive()
+ return C.filter_split_receiver(self.obj), self.obj
+end
+
+-- Set the receiver to pass objects to, this can be called multiple times to
+-- set addtional receivers.
+function Split:receiver(o)
+ local recv, ctx = o:receive()
+ C.filter_split_add(self.obj, recv, ctx)
+ table.insert(self.receivers, o)
+end
+
+return Split
diff --git a/src/filter/timing.c b/src/filter/timing.c
new file mode 100644
index 0000000..bf4f865
--- /dev/null
+++ b/src/filter/timing.c
@@ -0,0 +1,557 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "filter/timing.h"
+#include "core/assert.h"
+#include "core/timespec.h"
+#include "core/object/pcap.h"
+
+#include <time.h>
+#include <sys/time.h>
+
+#define N1e9 1000000000
+
+typedef struct _filter_timing {
+ filter_timing_t pub;
+
+ struct timespec diff;
+ core_timespec_t last_pkthdr_ts;
+ struct timespec last_ts;
+ struct timespec first_ts;
+ void (*timing_callback)(filter_timing_t*, const core_object_pcap_t*);
+ struct timespec mod_ts;
+ size_t counter;
+} _filter_timing_t;
+
+static core_log_t _log = LOG_T_INIT("filter.timing");
+static filter_timing_t _defaults = {
+ LOG_T_INIT_OBJ("filter.timing"),
+ 0, 0,
+ TIMING_MODE_KEEP, 0, 0, 0, 0, 0.0, 0,
+ 0, 0
+};
+
+#define _self ((_filter_timing_t*)self)
+
+core_log_t* filter_timing_log()
+{
+ return &_log;
+}
+
+static void _keep(filter_timing_t* self, const core_object_pcap_t* pkt)
+{
+#if HAVE_CLOCK_NANOSLEEP
+ struct timespec to = {
+ _self->diff.tv_sec + pkt->ts.sec,
+ _self->diff.tv_nsec + pkt->ts.nsec
+ };
+ int ret = EINTR;
+
+ if (to.tv_nsec >= N1e9) {
+ to.tv_sec += 1;
+ to.tv_nsec -= N1e9;
+ } else if (to.tv_nsec < 0) {
+ to.tv_sec -= 1;
+ to.tv_nsec += N1e9;
+ }
+
+ while (ret) {
+ ldebug("keep mode, sleep to %ld.%09ld", to.tv_sec, to.tv_nsec);
+ ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &to, 0);
+ if (ret && ret != EINTR) {
+ lfatal("clock_nanosleep(%ld.%09ld) %d", to.tv_sec, to.tv_nsec, ret);
+ }
+ }
+#elif HAVE_NANOSLEEP
+ struct timespec diff = {
+ pkt->ts.sec - _self->last_pkthdr_ts.sec,
+ pkt->ts.nsec - _self->last_pkthdr_ts.nsec
+ };
+ int ret = EINTR;
+
+ if (diff.tv_nsec >= N1e9) {
+ diff.tv_sec += 1;
+ diff.tv_nsec -= N1e9;
+ } else if (diff.tv_nsec < 0) {
+ diff.tv_sec -= 1;
+ diff.tv_nsec += N1e9;
+ }
+
+ if (diff.tv_sec > -1 && diff.tv_nsec > -1) {
+ while (ret) {
+ ldebug("keep mode, sleep for %ld.%09ld", diff.tv_sec, diff.tv_nsec);
+ if ((ret = nanosleep(&diff, &diff))) {
+ ret = errno;
+ if (ret != EINTR) {
+ lfatal("nanosleep(%ld.%09ld) %d", diff.tv_sec, diff.tv_nsec, ret);
+ }
+ }
+ }
+ }
+
+ _self->last_pkthdr_ts = pkt->ts;
+#endif
+}
+
+static void _increase(filter_timing_t* self, const core_object_pcap_t* pkt)
+{
+ struct timespec diff = {
+ pkt->ts.sec - _self->last_pkthdr_ts.sec,
+ pkt->ts.nsec - _self->last_pkthdr_ts.nsec
+ };
+ int ret = EINTR;
+
+ if (diff.tv_nsec >= N1e9) {
+ diff.tv_sec += 1;
+ diff.tv_nsec -= N1e9;
+ } else if (diff.tv_nsec < 0) {
+ diff.tv_sec -= 1;
+ diff.tv_nsec += N1e9;
+ }
+
+ diff.tv_sec += _self->mod_ts.tv_sec;
+ diff.tv_nsec += _self->mod_ts.tv_nsec;
+ if (diff.tv_nsec >= N1e9) {
+ diff.tv_sec += 1;
+ diff.tv_nsec -= N1e9;
+ }
+
+ if (diff.tv_sec > -1 && diff.tv_nsec > -1) {
+#if HAVE_CLOCK_NANOSLEEP
+ struct timespec to = {
+ _self->last_ts.tv_sec + diff.tv_sec,
+ _self->last_ts.tv_nsec + diff.tv_nsec
+ };
+
+ if (to.tv_nsec >= N1e9) {
+ to.tv_sec += 1;
+ to.tv_nsec -= N1e9;
+ } else if (to.tv_nsec < 0) {
+ to.tv_sec -= 1;
+ to.tv_nsec += N1e9;
+ }
+
+ while (ret) {
+ ldebug("increase mode, sleep to %ld.%09ld", to.tv_sec, to.tv_nsec);
+ ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &to, 0);
+ if (ret && ret != EINTR) {
+ lfatal("clock_nanosleep(%ld.%09ld) %d", to.tv_sec, to.tv_nsec, ret);
+ }
+ }
+#elif HAVE_NANOSLEEP
+ while (ret) {
+ ldebug("increase mode, sleep for %ld.%09ld", diff.tv_sec, diff.tv_nsec);
+ if ((ret = nanosleep(&diff, &diff))) {
+ ret = errno;
+ if (ret != EINTR) {
+ lfatal("nanosleep(%ld.%09ld) %d", diff.tv_sec, diff.tv_nsec, ret);
+ }
+ }
+ }
+#endif
+ }
+
+ _self->last_pkthdr_ts = pkt->ts;
+
+#if HAVE_CLOCK_NANOSLEEP
+ if (clock_gettime(CLOCK_MONOTONIC, &_self->last_ts)) {
+ lfatal("clock_gettime()");
+ }
+#endif
+}
+
+static void _reduce(filter_timing_t* self, const core_object_pcap_t* pkt)
+{
+ struct timespec diff = {
+ pkt->ts.sec - _self->last_pkthdr_ts.sec,
+ pkt->ts.nsec - _self->last_pkthdr_ts.nsec
+ };
+ int ret = EINTR;
+
+ if (diff.tv_nsec >= N1e9) {
+ diff.tv_sec += 1;
+ diff.tv_nsec -= N1e9;
+ } else if (diff.tv_nsec < 0) {
+ diff.tv_sec -= 1;
+ diff.tv_nsec += N1e9;
+ }
+
+ diff.tv_sec -= _self->mod_ts.tv_sec;
+ diff.tv_nsec -= _self->mod_ts.tv_nsec;
+ if (diff.tv_nsec < 0) {
+ diff.tv_sec -= 1;
+ diff.tv_nsec += N1e9;
+ }
+
+ if (diff.tv_sec > -1 && diff.tv_nsec > -1) {
+#if HAVE_CLOCK_NANOSLEEP
+ struct timespec to = {
+ _self->last_ts.tv_sec + diff.tv_sec,
+ _self->last_ts.tv_nsec + diff.tv_nsec
+ };
+
+ if (to.tv_nsec >= N1e9) {
+ to.tv_sec += 1;
+ to.tv_nsec -= N1e9;
+ } else if (to.tv_nsec < 0) {
+ to.tv_sec -= 1;
+ to.tv_nsec += N1e9;
+ }
+
+ while (ret) {
+ ldebug("reduce mode, sleep to %ld.%09ld", to.tv_sec, to.tv_nsec);
+ ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &to, 0);
+ if (ret && ret != EINTR) {
+ lfatal("clock_nanosleep(%ld.%09ld) %d", to.tv_sec, to.tv_nsec, ret);
+ }
+ }
+#elif HAVE_NANOSLEEP
+ while (ret) {
+ ldebug("reduce mode, sleep for %ld.%09ld", diff.tv_sec, diff.tv_nsec);
+ if ((ret = nanosleep(&diff, &diff))) {
+ ret = errno;
+ if (ret != EINTR) {
+ lfatal("nanosleep(%ld.%09ld) %d", diff.tv_sec, diff.tv_nsec, ret);
+ }
+ }
+ }
+#endif
+ }
+
+ _self->last_pkthdr_ts = pkt->ts;
+
+#if HAVE_CLOCK_NANOSLEEP
+ if (clock_gettime(CLOCK_MONOTONIC, &_self->last_ts)) {
+ lfatal("clock_gettime()");
+ }
+#endif
+}
+
+static void _multiply(filter_timing_t* self, const core_object_pcap_t* pkt)
+{
+ struct timespec diff = {
+ pkt->ts.sec - _self->last_pkthdr_ts.sec,
+ pkt->ts.nsec - _self->last_pkthdr_ts.nsec
+ };
+ int ret = EINTR;
+
+ if (diff.tv_nsec >= N1e9) {
+ diff.tv_sec += 1;
+ diff.tv_nsec -= N1e9;
+ } else if (diff.tv_nsec < 0) {
+ diff.tv_sec -= 1;
+ diff.tv_nsec += N1e9;
+ }
+
+ diff.tv_sec = (time_t)((float)diff.tv_sec * self->mul);
+ diff.tv_nsec = (long)((float)diff.tv_nsec * self->mul);
+ if (diff.tv_nsec >= N1e9) {
+ diff.tv_sec += diff.tv_nsec / N1e9;
+ diff.tv_nsec %= N1e9;
+ }
+
+ if (diff.tv_sec > -1 && diff.tv_nsec > -1) {
+#if HAVE_CLOCK_NANOSLEEP
+ struct timespec to = {
+ _self->last_ts.tv_sec + diff.tv_sec,
+ _self->last_ts.tv_nsec + diff.tv_nsec
+ };
+
+ if (to.tv_nsec >= N1e9) {
+ to.tv_sec += 1;
+ to.tv_nsec -= N1e9;
+ } else if (to.tv_nsec < 0) {
+ to.tv_sec -= 1;
+ to.tv_nsec += N1e9;
+ }
+
+ while (ret) {
+ ldebug("multiply mode, sleep to %ld.%09ld", to.tv_sec, to.tv_nsec);
+ ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &to, 0);
+ if (ret && ret != EINTR) {
+ lfatal("clock_nanosleep(%ld.%09ld) %d", to.tv_sec, to.tv_nsec, ret);
+ }
+ }
+#elif HAVE_NANOSLEEP
+ while (ret) {
+ ldebug("multiply mode, sleep for %ld.%09ld", diff.tv_sec, diff.tv_nsec);
+ if ((ret = nanosleep(&diff, &diff))) {
+ ret = errno;
+ if (ret != EINTR) {
+ lfatal("nanosleep(%ld.%09ld) %d", diff.tv_sec, diff.tv_nsec, ret);
+ }
+ }
+ }
+#endif
+ }
+
+ _self->last_pkthdr_ts = pkt->ts;
+
+#if HAVE_CLOCK_NANOSLEEP
+ if (clock_gettime(CLOCK_MONOTONIC, &_self->last_ts)) {
+ lfatal("clock_gettime()");
+ }
+#endif
+}
+
+static void _fixed(filter_timing_t* self, const core_object_pcap_t* pkt)
+{
+ struct timespec diff = {
+ _self->mod_ts.tv_sec,
+ _self->mod_ts.tv_nsec
+ };
+ int ret = EINTR;
+
+ if (diff.tv_sec > -1 && diff.tv_nsec > -1) {
+#if HAVE_CLOCK_NANOSLEEP
+ struct timespec to = {
+ _self->last_ts.tv_sec + diff.tv_sec,
+ _self->last_ts.tv_nsec + diff.tv_nsec
+ };
+
+ if (to.tv_nsec >= N1e9) {
+ to.tv_sec += 1;
+ to.tv_nsec -= N1e9;
+ } else if (to.tv_nsec < 0) {
+ to.tv_sec -= 1;
+ to.tv_nsec += N1e9;
+ }
+
+ while (ret) {
+ ldebug("fixed mode, sleep to %ld.%09ld", to.tv_sec, to.tv_nsec);
+ ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &to, 0);
+ if (ret && ret != EINTR) {
+ lfatal("clock_nanosleep(%ld.%09ld) %d", to.tv_sec, to.tv_nsec, ret);
+ }
+ }
+#elif HAVE_NANOSLEEP
+ while (ret) {
+ ldebug("fixed mode, sleep for %ld.%09ld", diff.tv_sec, diff.tv_nsec);
+ if ((ret = nanosleep(&diff, &diff))) {
+ ret = errno;
+ if (ret != EINTR) {
+ lfatal("nanosleep(%ld.%09ld) %d", diff.tv_sec, diff.tv_nsec, ret);
+ }
+ }
+ }
+#endif
+ }
+
+ _self->last_pkthdr_ts = pkt->ts;
+
+#if HAVE_CLOCK_NANOSLEEP
+ if (clock_gettime(CLOCK_MONOTONIC, &_self->last_ts)) {
+ lfatal("clock_gettime()");
+ }
+#endif
+}
+
+#if HAVE_CLOCK_NANOSLEEP
+static inline void _timespec_diff(struct timespec* start, struct timespec* stop,
+ struct timespec* result)
+{
+ if ((stop->tv_nsec - start->tv_nsec) < 0) {
+ mlassert(stop->tv_sec > start->tv_sec, "stop time must be after start time");
+ result->tv_sec = stop->tv_sec - start->tv_sec - 1;
+ result->tv_nsec = stop->tv_nsec - start->tv_nsec + 1000000000UL;
+ } else {
+ mlassert(stop->tv_sec >= start->tv_sec, "stop time must be after start time");
+ result->tv_sec = stop->tv_sec - start->tv_sec;
+ result->tv_nsec = stop->tv_nsec - start->tv_nsec;
+ }
+}
+
+static void _realtime(filter_timing_t* self, const core_object_pcap_t* pkt)
+{
+ _self->counter++;
+ if (_self->counter >= self->rt_batch) {
+ struct timespec simulated;
+
+ _self->counter = 0;
+ if (clock_gettime(CLOCK_MONOTONIC, &_self->last_ts)) {
+ lfatal("clock_gettime()");
+ }
+
+ // calculate simulated time from packet offsets
+ simulated.tv_sec = pkt->ts.sec;
+ simulated.tv_nsec = pkt->ts.nsec;
+ _timespec_diff(&_self->mod_ts, &simulated, &simulated);
+
+ // calculate real elapsed time from monotonic clock
+ _timespec_diff(&_self->first_ts, &_self->last_ts, &_self->diff);
+
+ linfo("simulated time: %ld.%09lds; real time: %ld.%09lds",
+ simulated.tv_sec, simulated.tv_nsec, _self->diff.tv_sec, _self->diff.tv_nsec);
+
+ if (simulated.tv_sec > _self->diff.tv_sec
+ || (simulated.tv_sec == _self->diff.tv_sec && simulated.tv_nsec > _self->diff.tv_nsec)) {
+ int ret = EINTR;
+ _timespec_diff(&_self->diff, &simulated, &simulated);
+
+ ldebug("sleeping for %ld.%09lds", simulated.tv_sec, simulated.tv_nsec);
+ while (ret) {
+ ret = clock_nanosleep(CLOCK_MONOTONIC, 0, &simulated, 0);
+ if (ret && ret != EINTR) {
+ lfatal("clock_nanosleep(%ld.%09ld) %d", simulated.tv_sec, simulated.tv_nsec, ret);
+ }
+ }
+ } else {
+ // check that real time didn't drift ahead more than specified drift limit
+ _timespec_diff(&simulated, &_self->diff, &_self->diff);
+ if (_self->diff.tv_sec > (self->rt_drift / N1e9)
+ || (_self->diff.tv_sec == (self->rt_drift / N1e9) && _self->diff.tv_nsec >= (self->rt_drift % N1e9))) {
+ lfatal("aborting, real time drifted ahead of simulated time (%ld.%09lds) by %ld.%09lds",
+ simulated.tv_sec, simulated.tv_nsec, _self->diff.tv_sec, _self->diff.tv_nsec);
+ }
+ }
+ }
+}
+#endif
+
+static void _init(filter_timing_t* self, const core_object_pcap_t* pkt)
+{
+#if HAVE_CLOCK_NANOSLEEP
+ if (clock_gettime(CLOCK_MONOTONIC, &_self->last_ts)) {
+ lfatal("clock_gettime()");
+ }
+ _self->first_ts = _self->last_ts;
+ _self->diff = _self->last_ts;
+ _self->diff.tv_sec -= pkt->ts.sec;
+ _self->diff.tv_nsec -= pkt->ts.nsec;
+ ldebug("init with clock_nanosleep() now is %ld.%09ld, diff of first pkt %ld.%09ld",
+ _self->last_ts.tv_sec, _self->last_ts.tv_nsec,
+ _self->diff.tv_sec, _self->diff.tv_nsec);
+#elif HAVE_NANOSLEEP
+ ldebug("init with nanosleep()");
+#else
+#error "No clock_nanosleep() or nanosleep(), can not continue"
+#endif
+
+ _self->last_pkthdr_ts = pkt->ts;
+
+ switch (self->mode) {
+ case TIMING_MODE_KEEP:
+ ldebug("init mode keep");
+ _self->timing_callback = _keep;
+ break;
+ case TIMING_MODE_INCREASE:
+ _self->timing_callback = _increase;
+ _self->mod_ts.tv_sec = self->inc / N1e9;
+ _self->mod_ts.tv_nsec = self->inc % N1e9;
+ ldebug("init mode increase by %ld.%09ld", _self->mod_ts.tv_sec, _self->mod_ts.tv_nsec);
+ break;
+ case TIMING_MODE_REDUCE:
+ _self->timing_callback = _reduce;
+ _self->mod_ts.tv_sec = self->red / N1e9;
+ _self->mod_ts.tv_nsec = self->red % N1e9;
+ ldebug("init mode reduce by %ld.%09ld", _self->mod_ts.tv_sec, _self->mod_ts.tv_nsec);
+ break;
+ case TIMING_MODE_MULTIPLY:
+ _self->timing_callback = _multiply;
+ ldebug("init mode multiply by %f", self->mul);
+ break;
+ case TIMING_MODE_FIXED:
+ _self->timing_callback = _fixed;
+ _self->mod_ts.tv_sec = self->fixed / N1e9;
+ _self->mod_ts.tv_nsec = self->fixed % N1e9;
+ ldebug("init mode fixed by %ld.%09ld", _self->mod_ts.tv_sec, _self->mod_ts.tv_nsec);
+ break;
+ case TIMING_MODE_REALTIME:
+#if HAVE_CLOCK_NANOSLEEP
+ ldebug("init mode realtime");
+ _self->timing_callback = _realtime;
+ _self->counter = 0;
+ _self->mod_ts.tv_sec = pkt->ts.sec;
+ _self->mod_ts.tv_nsec = pkt->ts.nsec;
+#else
+ lfatal("realtime mode requires clock_nanosleep()");
+#endif
+ break;
+ default:
+ lfatal("invalid timing mode %d", self->mode);
+ }
+}
+
+filter_timing_t* filter_timing_new()
+{
+ filter_timing_t* self;
+ mlfatal_oom(self = malloc(sizeof(_filter_timing_t)));
+ *self = _defaults;
+ _self->timing_callback = _init;
+
+ return self;
+}
+
+void filter_timing_free(filter_timing_t* self)
+{
+ mlassert_self();
+ free(self);
+}
+
+static void _receive(filter_timing_t* self, const core_object_t* obj)
+{
+ mlassert_self();
+ lassert(obj, "obj is nil");
+
+ if (obj->obj_type != CORE_OBJECT_PCAP) {
+ lfatal("obj is not CORE_OBJECT_PCAP");
+ }
+
+ _self->timing_callback(self, (core_object_pcap_t*)obj);
+ self->recv(self->ctx, obj);
+}
+
+core_receiver_t filter_timing_receiver(filter_timing_t* self)
+{
+ mlassert_self();
+
+ if (!self->recv) {
+ lfatal("no receiver set");
+ }
+
+ return (core_receiver_t)_receive;
+}
+
+static const core_object_t* _produce(filter_timing_t* self)
+{
+ const core_object_t* obj;
+ mlassert_self();
+
+ obj = self->prod(self->prod_ctx);
+ if (!obj || obj->obj_type != CORE_OBJECT_PCAP) {
+ return 0;
+ }
+
+ _self->timing_callback(self, (core_object_pcap_t*)obj);
+ return obj;
+}
+
+core_producer_t filter_timing_producer(filter_timing_t* self)
+{
+ mlassert_self();
+
+ if (!self->prod) {
+ lfatal("no producer set");
+ }
+
+ return (core_producer_t)_produce;
+}
diff --git a/src/filter/timing.h b/src/filter/timing.h
new file mode 100644
index 0000000..c00c43d
--- /dev/null
+++ b/src/filter/timing.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/log.h"
+#include "core/receiver.h"
+#include "core/producer.h"
+
+#ifndef __dnsjit_filter_timing_h
+#define __dnsjit_filter_timing_h
+
+#include "filter/timing.hh"
+
+#endif
diff --git a/src/filter/timing.hh b/src/filter/timing.hh
new file mode 100644
index 0000000..8615907
--- /dev/null
+++ b/src/filter/timing.hh
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+//lua:require("dnsjit.core.producer_h")
+//lua:require("dnsjit.core.timespec_h")
+
+typedef struct filter_timing {
+ core_log_t _log;
+ core_receiver_t recv;
+ void* ctx;
+ enum {
+ TIMING_MODE_KEEP = 0,
+ TIMING_MODE_INCREASE = 1,
+ TIMING_MODE_REDUCE = 2,
+ TIMING_MODE_MULTIPLY = 3,
+ TIMING_MODE_FIXED = 4,
+ TIMING_MODE_REALTIME = 5
+ } mode;
+ size_t inc, red, fixed, rt_batch;
+ float mul;
+ uint64_t rt_drift;
+
+ core_producer_t prod;
+ void* prod_ctx;
+} filter_timing_t;
+
+core_log_t* filter_timing_log();
+
+filter_timing_t* filter_timing_new();
+void filter_timing_free(filter_timing_t* self);
+
+core_receiver_t filter_timing_receiver(filter_timing_t* self);
+core_producer_t filter_timing_producer(filter_timing_t* self);
diff --git a/src/filter/timing.lua b/src/filter/timing.lua
new file mode 100644
index 0000000..cab9574
--- /dev/null
+++ b/src/filter/timing.lua
@@ -0,0 +1,123 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.filter.timing
+-- Filter to pass objects to the next receiver based on timing between packets
+-- local filter = require("dnsjit.filter.timing").new()
+-- ...
+-- filter:receiver(...)
+--
+-- Filter to manipulate processing so it simulates the actual timing when
+-- packets arrived or to delay processing.
+module(...,package.seeall)
+
+require("dnsjit.filter.timing_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local Timing = {}
+
+-- Create a new Timing filter.
+function Timing.new()
+ local self = {
+ _receiver = nil,
+ obj = C.filter_timing_new(),
+ }
+ ffi.gc(self.obj, C.filter_timing_free)
+ return setmetatable(self, { __index = Timing })
+end
+
+-- Return the Log object to control logging of this instance or module.
+function Timing:log()
+ if self == nil then
+ return C.filter_timing_log()
+ end
+ return self.obj._log
+end
+
+-- Set the timing mode to keep the timing between packets.
+function Timing:keep()
+ self.obj.mode = "TIMING_MODE_KEEP"
+end
+
+-- Set the timing mode to increase the timing between packets by the given
+-- number of nanoseconds.
+function Timing:increase(ns)
+ self.obj.mode = "TIMING_MODE_INCREASE"
+ self.obj.inc = ns
+end
+
+-- Set the timing mode to reduce the timing between packets by the given
+-- number of nanoseconds.
+function Timing:reduce(ns)
+ self.obj.mode = "TIMING_MODE_REDUCE"
+ self.obj.red = ns
+end
+
+-- Set the timing mode to multiply the timing between packets by the given
+-- factor (float/double).
+function Timing:multiply(factor)
+ self.obj.mode = "TIMING_MODE_MULTIPLY"
+ self.obj.mul = factor
+end
+
+-- Set the timing mode to a fixed number of nanoseconds between packets.
+function Timing:fixed(ns)
+ self.obj.mode = "TIMING_MODE_FIXED"
+ self.obj.fixed = ns
+end
+
+-- Set the timing mode to simulate the timing of packets in realtime.
+-- Packets are processed in batches of given size (default 128) before
+-- adjusting time. Aborts if real time drifts ahead more than given
+-- number of seconds (default 1.0s).
+function Timing:realtime(drift, batch_size)
+ self.obj.mode = "TIMING_MODE_REALTIME"
+ if drift == nil then
+ drift = 1
+ end
+ if batch_size == nil then
+ batch_size = 128
+ end
+ self.obj.rt_batch = batch_size
+ self.obj.rt_drift = math.floor(drift * 1000000000)
+end
+
+-- Return the C functions and context for receiving objects.
+function Timing:receive()
+ return C.filter_timing_receiver(), self.obj
+end
+
+-- Set the receiver to pass objects to.
+function Timing:receiver(o)
+ self.obj.recv, self.obj.ctx = o:receive()
+ self._receiver = o
+end
+
+-- Return the C functions and context for producing objects.
+function Timing:produce()
+ return C.filter_timing_producer(self.obj), self.obj
+end
+
+-- Set the producer to get objects from.
+function Timing:producer(o)
+ self.obj.prod, self.obj.prod_ctx = o:produce()
+ self._producer = o
+end
+
+return Timing
diff --git a/src/gen-compat.lua b/src/gen-compat.lua
new file mode 100644
index 0000000..65c40ec
--- /dev/null
+++ b/src/gen-compat.lua
@@ -0,0 +1,34 @@
+for line in io.lines("config.h") do
+ local n, s = line:match("define SIZEOF_(%S*) (%d+)")
+ if n and s then
+ if n:match("^PTHREAD") or n:match("^CK_") or n:match("^GNUTLS_") then
+ s = math.ceil(s / 8)
+ print("#if !defined(SIZEOF_"..n..") || SIZEOF_"..n.." == 0")
+ print("#error \""..n.." is undefined or zero\"")
+ print("#endif")
+ n = n:lower()
+ print("typedef struct "..n:sub(1,-3).." { uint64_t a["..s.."]; } "..n..";")
+ elseif n:match("^STRUCT") then
+ n = n:match("^STRUCT_(%S*)")
+ if n == "SOCKADDR_STORAGE" or n == "POLLFD" then
+ print("#if !defined(SIZEOF_STRUCT_"..n..") || SIZEOF_STRUCT_"..n.." == 0")
+ print("#error \""..n.." is undefined or zero\"")
+ print("#endif")
+ n = n:lower()
+ print("struct "..n.." { uint8_t a["..s.."]; };")
+ end
+ end
+ end
+end
+code, err = pcall(function()
+ local ffi = require("ffi")
+ ffi.cdef[[
+ ssize_t dummy;
+ ]]
+end)
+if code then
+ print("#include <unistd.h>")
+ print("typedef ssize_t luajit_ssize_t;")
+else
+ print("typedef long luajit_ssize_t;")
+end
diff --git a/src/gen-errno.sh b/src/gen-errno.sh
new file mode 100755
index 0000000..f9a81da
--- /dev/null
+++ b/src/gen-errno.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+includes="/usr/include/errno.h"
+if [ -f /usr/include/sys/errno.h ]; then
+ includes="$includes /usr/include/sys/errno.h"
+fi
+if [ -f /usr/include/asm-generic/errno.h ]; then
+ includes="$includes /usr/include/asm-generic/errno.h"
+fi
+if [ -f /usr/include/asm-generic/errno-base.h ]; then
+ includes="$includes /usr/include/asm-generic/errno-base.h"
+fi
+
+echo 'const char* core_log_errstr(int err)
+{
+ switch (err) {'
+
+egrep -Eh '^#define[ ]+E\w+[ ]+[0-9]+' $includes |
+ grep -v ELAST |
+ awk '{print $2}' |
+ sort -u |
+ awk '{print "#ifdef " $1 "\n case " $1 ":\n return \"" $1 "\";\n#endif"}'
+
+echo ' default:
+ break;
+ }
+ return "UNKNOWN";
+}'
diff --git a/src/gen-makefile.sh b/src/gen-makefile.sh
new file mode 100755
index 0000000..18e48cb
--- /dev/null
+++ b/src/gen-makefile.sh
@@ -0,0 +1,130 @@
+#!/bin/sh
+
+echo '# Copyright (c) 2018-2021, OARC, Inc.
+# All rights reserved.
+#
+# This file is part of dnsjit.
+#
+# dnsjit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
+CLEANFILES = *.gcda *.gcno *.gcov
+
+SUBDIRS = test
+
+AM_CFLAGS = -Werror=attributes \
+ -I$(srcdir) \
+ -I$(top_srcdir) \
+ $(SIMD_FLAGS) $(CPUEXT_FLAGS) \
+ $(PTHREAD_CFLAGS) \
+ $(luajit_CFLAGS) \
+ $(libuv_CFLAGS) \
+ $(libnghttp2_CFLAGS)
+
+EXTRA_DIST = gen-manpage.lua gen-compat.lua gen-errno.sh dnsjit.1in
+
+BUILT_SOURCES = core/compat.hh core/log_errstr.c
+
+bin_PROGRAMS = dnsjit
+
+dnsjit_SOURCES = dnsjit.c globals.c
+dist_dnsjit_SOURCES = core.lua lib.lua input.lua filter.lua globals.h \
+ output.lua
+lua_hobjects = core/compat.luaho
+lua_objects = core.luao lib.luao input.luao filter.luao output.luao
+dnsjit_LDADD = $(PTHREAD_LIBS) $(luajit_LIBS) $(libuv_LIBS) $(libnghttp2_LIBS)
+
+# C source and headers';
+
+echo "dnsjit_SOURCES +=`find core lib input filter output -type f -name '*.c' | sort | while read line; do echo -n " $line"; done`"
+echo "dist_dnsjit_SOURCES +=`find core lib input filter output -type f -name '*.h' | sort | while read line; do echo -n " $line"; done`"
+
+echo '
+# Lua headers'
+echo "dist_dnsjit_SOURCES +=`find core lib input filter output -type f -name '*.hh' | sort | while read line; do echo -n " $line"; done`"
+echo "lua_hobjects +=`find core lib input filter output -type f -name '*.hh' | sed -e 's%.hh%.luaho%g' | sort | while read line; do echo -n " $line"; done`"
+
+echo '
+# Lua sources'
+echo "dist_dnsjit_SOURCES +=`find core lib input filter output -type f -name '*.lua' | sort | while read line; do echo -n " $line"; done`"
+echo "lua_objects +=`find core lib input filter output -type f -name '*.lua' | sed -e 's%.lua%.luao%g' | sort | while read line; do echo -n " $line"; done`"
+
+echo '
+dnsjit_LDFLAGS = -Wl,-E
+dnsjit_LDADD += $(lua_hobjects) $(lua_objects)
+CLEANFILES += $(lua_hobjects) $(lua_objects)
+
+man1_MANS = dnsjit.1
+CLEANFILES += $(man1_MANS)
+
+man3_MANS = dnsjit.core.3 dnsjit.lib.3 dnsjit.input.3 dnsjit.filter.3 dnsjit.output.3';
+echo "man3_MANS +=`find core lib input filter output -type f -name '*.lua' | sed -e 's%.lua%.3%g' | sed -e 's%/%.%g' | sort | while read line; do echo -n " dnsjit.$line"; done`"
+
+echo 'CLEANFILES += *.3in $(man3_MANS)
+
+.lua.luao:
+ @mkdir -p `dirname "$@"`
+ $(LUAJIT) -bg -n "dnsjit.`echo \"$@\" | sed '"'"'s%\..*%%'"'"' | sed '"'"'s%/%.%g'"'"'`" -t o "$<" "$@"
+
+.luah.luaho:
+ @mkdir -p `dirname "$@"`
+ $(LUAJIT) -bg -n "dnsjit.`echo \"$@\" | sed '"'"'s%\..*%%'"'"' | sed '"'"'s%/%.%g'"'"'`_h" -t o "$<" "$@"
+
+.hh.luah:
+ @mkdir -p `dirname "$@"`
+ @echo '"'"'module(...,package.seeall);'"'"' > "$@"
+ @cat "$<" | grep '"'"'^//lua:'"'"' | sed '"'"'s%^//lua:%%'"'"' >> "$@"
+ @echo '"'"'require("ffi").cdef[['"'"' >> "$@"
+ @cat "$<" | grep -v '"'"'^#'"'"' >> "$@"
+ @echo '"'"']]'"'"' >> "$@"
+
+.1in.1:
+ sed -e '"'"'s,[@]PACKAGE_VERSION[@],$(PACKAGE_VERSION),g'"'"' \
+ -e '"'"'s,[@]PACKAGE_URL[@],$(PACKAGE_URL),g'"'"' \
+ -e '"'"'s,[@]PACKAGE_BUGREPORT[@],$(PACKAGE_BUGREPORT),g'"'"' \
+ < "$<" > "$@"
+
+.3in.3:
+ sed -e '"'"'s,[@]PACKAGE_VERSION[@],$(PACKAGE_VERSION),g'"'"' \
+ -e '"'"'s,[@]PACKAGE_URL[@],$(PACKAGE_URL),g'"'"' \
+ -e '"'"'s,[@]PACKAGE_BUGREPORT[@],$(PACKAGE_BUGREPORT),g'"'"' \
+ < "$<" > "$@"
+
+if ENABLE_GCOV
+gcov-local:
+ for src in $(dnsjit_SOURCES); do \
+ gcov -x -l -r -s "$(srcdir)" "$$src"; \
+ done
+endif
+
+core/compat.hh: gen-compat.lua
+ $(LUAJIT) "$(srcdir)/gen-compat.lua" > "$@"
+
+core/log_errstr.c: gen-errno.sh
+ "$(srcdir)/gen-errno.sh" > "$@"
+';
+
+for file in core.lua lib.lua input.lua filter.lua output.lua; do
+ man=`echo "$file"|sed -e 's%.lua%.3%g'|sed -e 's%/%.%g'`
+echo "
+dnsjit.${man}in: $file gen-manpage.lua
+ \$(LUAJIT) \"\$(srcdir)/gen-manpage.lua\" \"\$(srcdir)/$file\" > \"\$@\"";
+done
+
+find core lib input filter output -type f -name '*.lua' | sort | while read file; do
+ man=`echo "$file"|sed -e 's%.lua%.3%g'|sed -e 's%/%.%g'`
+echo "
+dnsjit.${man}in: $file gen-manpage.lua
+ \$(LUAJIT) \"\$(srcdir)/gen-manpage.lua\" \"\$(srcdir)/$file\" > \"\$@\"";
+done
diff --git a/src/gen-manpage.lua b/src/gen-manpage.lua
new file mode 100644
index 0000000..d4d63c4
--- /dev/null
+++ b/src/gen-manpage.lua
@@ -0,0 +1,125 @@
+print[[
+.\" Copyright (c) 2018-2021, OARC, Inc.
+.\" All rights reserved.
+.\"
+.\" This file is part of dnsjit.
+.\"
+.\" dnsjit is free software: you can redistribute it and/or modify
+.\" it under the terms of the GNU General Public License as published by
+.\" the Free Software Foundation, either version 3 of the License, or
+.\" (at your option) any later version.
+.\"
+.\" dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+.\"]]
+
+sh_syn = false
+sh_desc = false
+ss_func = false
+doc = {}
+funcs = {}
+for line in io.lines(arg[1]) do
+ if line:match("^[-][-]") then
+ table.insert(doc, line:sub(4))
+ elseif line:match("^module") then
+ if table.maxn(doc) < 2 then
+ error("Minimum required module doc missing")
+ end
+ print(".TH "..doc[1].." 3 \"@PACKAGE_VERSION@\" \"dnsjit\"")
+ print(".SH NAME")
+ print(doc[1].." \\- "..doc[2])
+ n, line = next(doc, 2)
+ if n and n > 2 then
+ local n2 = n
+ while line and line > "" do
+ if not sh_syn then
+ print(".SH SYNOPSIS")
+ sh_syn = true
+ end
+ if line == "." then
+ line = ""
+ end
+ print(line)
+ n2 = n
+ n, line = next(doc, n)
+ end
+ n, line = next(doc, n)
+ if n and n > n2 then
+ while line and line > "" do
+ if not sh_desc then
+ print(".SH DESCRIPTION")
+ sh_desc = true
+ end
+ if line == "." then
+ line = ""
+ end
+ print(line)
+ n, line = next(doc, n)
+ end
+ end
+ end
+ elseif line:match("^function") then
+ if table.maxn(doc) > 0 then
+ if not ss_func then
+ if not sh_desc then
+ print(".SH DESCRIPTION")
+ sh_desc = true
+ end
+ print(".SS Functions")
+ ss_func = true
+ end
+ print(".TP")
+ local fn,fd = line:match("(%S+)(%(.+)")
+ if fn and fd then
+ print(".BR "..fn.." \""..fd.."\"")
+ else
+ print(".B "..line:sub(10))
+ end
+ for _, line in pairs(doc) do
+ if line == "." then
+ line = ""
+ end
+ print(line)
+ end
+ end
+ elseif line:match("^return") then
+ if table.maxn(doc) > 0 then
+ print(".SH SEE ALSO")
+ for _, line in pairs(doc) do
+ print(".BR "..line)
+ end
+ end
+ else
+ doc = {}
+ end
+end
+
+print[[
+.SH AUTHORS
+Jerry Lundström (DNS-OARC),
+Tomáš Křížek (CZ.NIC)
+.LP
+Maintained by DNS-OARC
+.LP
+.RS
+.I https://www.dns-oarc.net/
+.RE
+.LP
+.SH BUGS
+For issues and feature requests please use:
+.LP
+.RS
+\fI@PACKAGE_URL@\fP
+.RE
+.LP
+For question and help please use:
+.LP
+.RS
+\fI@PACKAGE_BUGREPORT@\fP
+.RE
+.LP]]
diff --git a/src/globals.c b/src/globals.c
new file mode 100644
index 0000000..3f85c51
--- /dev/null
+++ b/src/globals.c
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "globals.h"
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+void dnsjit_globals(lua_State* L)
+{
+#ifdef PACKAGE_VERSION
+ lua_pushstring(L, PACKAGE_VERSION);
+#elif defined(VERSION)
+ lua_pushstring(L, VERSION);
+#else
+#error "No PACKAGE_VERSION or VERSION defined"
+#endif
+ lua_setglobal(L, "DNSJIT_VERSION");
+
+ lua_pushinteger(L, PACKAGE_MAJOR_VERSION);
+ lua_setglobal(L, "DNSJIT_MAJOR_VERSION");
+ lua_pushinteger(L, PACKAGE_MINOR_VERSION);
+ lua_setglobal(L, "DNSJIT_MINOR_VERSION");
+ lua_pushinteger(L, PACKAGE_PATCH_VERSION);
+ lua_setglobal(L, "DNSJIT_PATCH_VERSION");
+
+#ifdef PACKAGE_BUGREPORT
+ lua_pushstring(L, PACKAGE_BUGREPORT);
+#else
+ lua_pushstring(L, "none");
+#endif
+ lua_setglobal(L, "DNSJIT_BUGREPORT");
+
+#ifdef PACKAGE_URL
+ lua_pushstring(L, PACKAGE_URL);
+#else
+ lua_pushstring(L, "none");
+#endif
+ lua_setglobal(L, "DNSJIT_URL");
+}
diff --git a/src/globals.h b/src/globals.h
new file mode 100644
index 0000000..48e7e68
--- /dev/null
+++ b/src/globals.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __dnsjit_globals_h
+#define __dnsjit_globals_h
+
+#include <lua.h>
+
+void dnsjit_globals(lua_State* L);
+
+#endif
diff --git a/src/input.lua b/src/input.lua
new file mode 100644
index 0000000..719f624
--- /dev/null
+++ b/src/input.lua
@@ -0,0 +1,29 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.input
+-- Input modules for dnsjit
+--
+-- Input modules used to read DNS messages in various ways.
+module(...,package.seeall)
+
+-- dnsjit.input.fpcap (3),
+-- dnsjit.input.mmpcap (3),
+-- dnsjit.input.pcap (3),
+-- dnsjit.input.zero (3)
+return
diff --git a/src/input/fpcap.c b/src/input/fpcap.c
new file mode 100644
index 0000000..de054ec
--- /dev/null
+++ b/src/input/fpcap.c
@@ -0,0 +1,338 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "input/fpcap.h"
+#include "core/assert.h"
+#include "core/object/pcap.h"
+
+#include <stdio.h>
+#ifdef HAVE_ENDIAN_H
+#include <endian.h>
+#else
+#ifdef HAVE_SYS_ENDIAN_H
+#include <sys/endian.h>
+#else
+#ifdef HAVE_MACHINE_ENDIAN_H
+#include <machine/endian.h>
+#endif
+#endif
+#endif
+#ifdef HAVE_BYTESWAP_H
+#include <byteswap.h>
+#endif
+#ifndef bswap_16
+#ifndef bswap16
+#define bswap_16(x) swap16(x)
+#define bswap_32(x) swap32(x)
+#define bswap_64(x) swap64(x)
+#else
+#define bswap_16(x) bswap16(x)
+#define bswap_32(x) bswap32(x)
+#define bswap_64(x) bswap64(x)
+#endif
+#endif
+#include <pcap/pcap.h>
+
+#define MAX_SNAPLEN 0x40000
+
+static core_log_t _log = LOG_T_INIT("input.fpcap");
+static input_fpcap_t _defaults = {
+ LOG_T_INIT_OBJ("input.fpcap"),
+ 0, 0,
+ 0, 0, 0,
+ CORE_OBJECT_PCAP_INIT(0),
+ 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0
+};
+
+core_log_t* input_fpcap_log()
+{
+ return &_log;
+}
+
+void input_fpcap_init(input_fpcap_t* self)
+{
+ mlassert_self();
+
+ *self = _defaults;
+}
+
+void input_fpcap_destroy(input_fpcap_t* self)
+{
+ mlassert_self();
+
+ if (!self->extern_file && self->file) {
+ fclose(self->file);
+ }
+ free(self->buf);
+}
+
+static int _open(input_fpcap_t* self)
+{
+ mlassert_self();
+
+ if (fread(&self->magic_number, 1, 4, self->file) != 4
+ || fread(&self->version_major, 1, 2, self->file) != 2
+ || fread(&self->version_minor, 1, 2, self->file) != 2
+ || fread(&self->thiszone, 1, 4, self->file) != 4
+ || fread(&self->sigfigs, 1, 4, self->file) != 4
+ || fread(&self->snaplen, 1, 4, self->file) != 4
+ || fread(&self->network, 1, 4, self->file) != 4) {
+ lcritical("could not read full PCAP header");
+ return -2;
+ }
+ switch (self->magic_number) {
+ case 0x4d3cb2a1:
+ self->is_nanosec = 1;
+ case 0xd4c3b2a1:
+ self->is_swapped = 1;
+ self->version_major = bswap_16(self->version_major);
+ self->version_minor = bswap_16(self->version_minor);
+ self->thiszone = (int32_t)bswap_32((uint32_t)self->thiszone);
+ self->sigfigs = bswap_32(self->sigfigs);
+ self->snaplen = bswap_32(self->snaplen);
+ self->network = bswap_32(self->network);
+ break;
+ case 0xa1b2c3d4:
+ case 0xa1b23c4d:
+ break;
+ default:
+ lcritical("invalid PCAP header");
+ return -2;
+ }
+
+ if (self->snaplen > MAX_SNAPLEN) {
+ lcritical("too large snaplen (%u)", self->snaplen);
+ return -2;
+ }
+
+ if (self->version_major != 2 || self->version_minor != 4) {
+ lcritical("unsupported PCAP version v%u.%u", self->version_major, self->version_minor);
+ return -2;
+ }
+
+ /*
+ * Translation taken from https://github.com/the-tcpdump-group/libpcap/blob/90543970fd5fbed261d3637f5ec4811d7dde4e49/pcap-common.c#L1212 .
+ */
+ switch (self->network) {
+ case 101:
+ self->linktype = DLT_RAW;
+ break;
+#ifdef DLT_FR
+ case 107: /* LINKTYPE_FRELAY */
+ self->linktype = DLT_FR;
+ break;
+#endif
+ case 100: /* LINKTYPE_ATM_RFC1483 */
+ self->linktype = DLT_ATM_RFC1483;
+ break;
+ case 102: /* LINKTYPE_SLIP_BSDOS */
+ self->linktype = DLT_SLIP_BSDOS;
+ break;
+ case 103: /* LINKTYPE_PPP_BSDOS */
+ self->linktype = DLT_PPP_BSDOS;
+ break;
+ case 104: /* LINKTYPE_C_HDLC */
+ self->linktype = DLT_C_HDLC;
+ break;
+ case 106: /* LINKTYPE_ATM_CLIP */
+ self->linktype = DLT_ATM_CLIP;
+ break;
+ case 50: /* LINKTYPE_PPP_HDLC */
+ self->linktype = DLT_PPP_SERIAL;
+ break;
+ case 51: /* LINKTYPE_PPP_ETHER */
+ self->linktype = DLT_PPP_ETHER;
+ break;
+ default:
+ self->linktype = self->network;
+ }
+
+ lfatal_oom(self->buf = malloc(self->snaplen));
+ self->prod_pkt.snaplen = self->snaplen;
+ self->prod_pkt.linktype = self->linktype;
+ self->prod_pkt.bytes = (unsigned char*)self->buf;
+ self->prod_pkt.is_swapped = self->is_swapped;
+
+ ldebug("pcap v%u.%u snaplen:%lu %s", self->version_major, self->version_minor, self->snaplen, self->is_swapped ? " swapped" : "");
+
+ return 0;
+}
+
+int input_fpcap_open(input_fpcap_t* self, const char* file)
+{
+ mlassert_self();
+ lassert(file, "file is nil");
+
+ if (self->file) {
+ lfatal("already opened");
+ }
+
+ if (!(self->file = fopen(file, "rb"))) {
+ lcritical("fopen(%s) error: %s", file, core_log_errstr(errno));
+ return -1;
+ }
+
+ return _open(self);
+}
+
+int input_fpcap_openfp(input_fpcap_t* self, void* fp)
+{
+ mlassert_self();
+
+ if (self->file) {
+ lfatal("already opened");
+ }
+
+ self->file = fp;
+ self->extern_file = 1;
+
+ return _open(self);
+}
+
+int input_fpcap_run(input_fpcap_t* self)
+{
+ struct {
+ uint32_t ts_sec;
+ uint32_t ts_usec;
+ uint32_t incl_len;
+ uint32_t orig_len;
+ } hdr;
+ core_object_pcap_t pkt = CORE_OBJECT_PCAP_INIT(0);
+ int ret;
+ mlassert_self();
+
+ if (!self->file) {
+ lfatal("no PCAP opened");
+ }
+ if (!self->recv) {
+ lfatal("no receiver set");
+ }
+
+ pkt.snaplen = self->snaplen;
+ pkt.linktype = self->linktype;
+ pkt.bytes = (unsigned char*)self->buf;
+ pkt.is_swapped = self->is_swapped;
+
+ while ((ret = fread(&hdr, 1, 16, self->file)) == 16) {
+ if (self->is_swapped) {
+ hdr.ts_sec = bswap_32(hdr.ts_sec);
+ hdr.ts_usec = bswap_32(hdr.ts_usec);
+ hdr.incl_len = bswap_32(hdr.incl_len);
+ hdr.orig_len = bswap_32(hdr.orig_len);
+ }
+ if (hdr.incl_len > self->snaplen) {
+ lwarning("invalid packet length, larger then snaplen");
+ return -1;
+ }
+ if (fread(self->buf, 1, hdr.incl_len, self->file) != hdr.incl_len) {
+ lwarning("could not read all of packet, aborting");
+ return -1;
+ }
+
+ self->pkts++;
+
+ pkt.ts.sec = hdr.ts_sec;
+ if (self->is_nanosec) {
+ pkt.ts.nsec = hdr.ts_usec;
+ } else {
+ pkt.ts.nsec = hdr.ts_usec * 1000;
+ }
+ pkt.caplen = hdr.incl_len;
+ pkt.len = hdr.orig_len;
+
+ self->recv(self->ctx, (core_object_t*)&pkt);
+ }
+ if (ret) {
+ lwarning("could not read next PCAP header, aborting");
+ return -1;
+ }
+
+ return 0;
+}
+
+static const core_object_t* _produce(input_fpcap_t* self)
+{
+ struct {
+ uint32_t ts_sec;
+ uint32_t ts_usec;
+ uint32_t incl_len;
+ uint32_t orig_len;
+ } hdr;
+ int ret;
+ mlassert_self();
+
+ if (self->is_broken) {
+ lwarning("PCAP is broken, will not read next packet");
+ return 0;
+ }
+
+ if ((ret = fread(&hdr, 1, 16, self->file)) != 16) {
+ if (ret) {
+ lwarning("could not read next PCAP header, aborting");
+ self->is_broken = 1;
+ }
+ return 0;
+ }
+
+ if (self->is_swapped) {
+ hdr.ts_sec = bswap_32(hdr.ts_sec);
+ hdr.ts_usec = bswap_32(hdr.ts_usec);
+ hdr.incl_len = bswap_32(hdr.incl_len);
+ hdr.orig_len = bswap_32(hdr.orig_len);
+ }
+ if (hdr.incl_len > self->snaplen) {
+ lwarning("invalid packet length, larger then snaplen");
+ self->is_broken = 1;
+ return 0;
+ }
+ if (fread(self->buf, 1, hdr.incl_len, self->file) != hdr.incl_len) {
+ lwarning("could not read all of packet, aborting");
+ self->is_broken = 1;
+ return 0;
+ }
+
+ self->pkts++;
+
+ self->prod_pkt.ts.sec = hdr.ts_sec;
+ if (self->is_nanosec) {
+ self->prod_pkt.ts.nsec = hdr.ts_usec;
+ } else {
+ self->prod_pkt.ts.nsec = hdr.ts_usec * 1000;
+ }
+ self->prod_pkt.caplen = hdr.incl_len;
+ self->prod_pkt.len = hdr.orig_len;
+
+ return (core_object_t*)&self->prod_pkt;
+}
+
+core_producer_t input_fpcap_producer(input_fpcap_t* self)
+{
+ mlassert_self();
+
+ if (!self->file) {
+ lfatal("no PCAP opened");
+ }
+
+ return (core_producer_t)_produce;
+}
diff --git a/src/input/fpcap.h b/src/input/fpcap.h
new file mode 100644
index 0000000..da3596c
--- /dev/null
+++ b/src/input/fpcap.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/log.h"
+#include "core/receiver.h"
+#include "core/producer.h"
+#include "core/object/pcap.h"
+
+#ifndef __dnsjit_input_fpcap_h
+#define __dnsjit_input_fpcap_h
+
+#include "input/fpcap.hh"
+
+#endif
diff --git a/src/input/fpcap.hh b/src/input/fpcap.hh
new file mode 100644
index 0000000..0ced83c
--- /dev/null
+++ b/src/input/fpcap.hh
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+//lua:require("dnsjit.core.producer_h")
+//lua:require("dnsjit.core.object.pcap_h")
+
+typedef struct input_fpcap {
+ core_log_t _log;
+ core_receiver_t recv;
+ void* ctx;
+
+ uint8_t is_swapped;
+ uint8_t is_nanosec;
+ uint8_t is_broken;
+
+ core_object_pcap_t prod_pkt;
+
+ void* file;
+ int extern_file;
+ size_t pkts;
+ uint8_t* buf;
+ size_t buf_size;
+
+ uint32_t magic_number;
+ uint16_t version_major;
+ uint16_t version_minor;
+ int32_t thiszone;
+ uint32_t sigfigs;
+ uint32_t snaplen;
+ uint32_t network;
+
+ uint32_t linktype;
+} input_fpcap_t;
+
+core_log_t* input_fpcap_log();
+
+void input_fpcap_init(input_fpcap_t* self);
+void input_fpcap_destroy(input_fpcap_t* self);
+int input_fpcap_open(input_fpcap_t* self, const char* file);
+int input_fpcap_openfp(input_fpcap_t* self, void* fp);
+int input_fpcap_run(input_fpcap_t* self);
+
+core_producer_t input_fpcap_producer(input_fpcap_t* self);
diff --git a/src/input/fpcap.lua b/src/input/fpcap.lua
new file mode 100644
index 0000000..2fc2906
--- /dev/null
+++ b/src/input/fpcap.lua
@@ -0,0 +1,131 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.input.fpcap
+-- Read input from a PCAP file using fopen()
+-- local input = require("dnsjit.input.fpcap").new()
+-- input:open("file.pcap")
+-- input:receiver(filter_or_output)
+-- input:run()
+--
+-- Read input from a PCAP file using standard library function
+-- .B fopen()
+-- and parse the PCAP without libpcap.
+-- After opening a file and reading the PCAP header, the attributes are
+-- populated.
+-- .SS Attributes
+-- .TP
+-- is_swapped
+-- Indicate if the byte order in the PCAP is in reverse order of the host.
+-- .TP
+-- is_nanosec
+-- Indicate if the time stamps are in nanoseconds or not.
+-- .TP
+-- magic_number
+-- Magic number.
+-- .TP
+-- version_major
+-- Major version number.
+-- .TP
+-- version_minor
+-- Minor version number.
+-- .TP
+-- thiszone
+-- GMT to local correction.
+-- .TP
+-- sigfigs
+-- Accuracy of timestamps.
+-- .TP
+-- snaplen
+-- Max length of captured packets, in octets.
+-- .TP
+-- network
+-- The link type found in the PCAP header, see https://www.tcpdump.org/linktypes.html .
+-- .TP
+-- linktype
+-- The data link type, mapped from
+-- .IR network .
+module(...,package.seeall)
+
+require("dnsjit.input.fpcap_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "input_fpcap_t"
+local input_fpcap_t = ffi.typeof(t_name)
+local Fpcap = {}
+
+-- Create a new Fpcap input.
+function Fpcap.new()
+ local self = {
+ _receiver = nil,
+ obj = input_fpcap_t(),
+ }
+ C.input_fpcap_init(self.obj)
+ ffi.gc(self.obj, C.input_fpcap_destroy)
+ return setmetatable(self, { __index = Fpcap })
+end
+
+-- Return the Log object to control logging of this instance or module.
+function Fpcap:log()
+ if self == nil then
+ return C.input_fpcap_log()
+ end
+ return self.obj._log
+end
+
+-- Set the receiver to pass objects to.
+function Fpcap:receiver(o)
+ self.obj.recv, self.obj.ctx = o:receive()
+ self._receiver = o
+end
+
+-- Return the C functions and context for producing objects.
+function Fpcap:produce()
+ return C.input_fpcap_producer(self.obj), self.obj
+end
+
+-- Open a PCAP file for processing and read the PCAP header.
+-- Returns 0 on success.
+function Fpcap:open(file)
+ return C.input_fpcap_open(self.obj, file)
+end
+
+-- Open a PCAP file for processing and read the PCAP header using a
+-- file descriptor, for example
+-- .B io.stdin
+-- or with
+-- .BR io.open() .
+-- Will not take ownership of the file descriptor.
+-- Returns 0 on success.
+function Fpcap:openfp(fp)
+ return C.input_fpcap_openfp(self.obj, fp)
+end
+
+-- Start processing packets and send each packet read to the receiver.
+-- Returns 0 if all packets was read successfully.
+function Fpcap:run()
+ return C.input_fpcap_run(self.obj)
+end
+
+-- Return the number of packets seen.
+function Fpcap:packets()
+ return tonumber(self.obj.pkts)
+end
+
+return Fpcap
diff --git a/src/input/mmpcap.c b/src/input/mmpcap.c
new file mode 100644
index 0000000..4f3ee59
--- /dev/null
+++ b/src/input/mmpcap.c
@@ -0,0 +1,335 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "input/mmpcap.h"
+#include "core/assert.h"
+#include "core/object/pcap.h"
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#ifdef HAVE_ENDIAN_H
+#include <endian.h>
+#else
+#ifdef HAVE_SYS_ENDIAN_H
+#include <sys/endian.h>
+#else
+#ifdef HAVE_MACHINE_ENDIAN_H
+#include <machine/endian.h>
+#endif
+#endif
+#endif
+#ifdef HAVE_BYTESWAP_H
+#include <byteswap.h>
+#endif
+#ifndef bswap_16
+#ifndef bswap16
+#define bswap_16(x) swap16(x)
+#define bswap_32(x) swap32(x)
+#define bswap_64(x) swap64(x)
+#else
+#define bswap_16(x) bswap16(x)
+#define bswap_32(x) bswap32(x)
+#define bswap_64(x) bswap64(x)
+#endif
+#endif
+#include <pcap/pcap.h>
+
+static core_log_t _log = LOG_T_INIT("input.mmpcap");
+static input_mmpcap_t _defaults = {
+ LOG_T_INIT_OBJ("input.mmpcap"),
+ 0, 0,
+ 0, 0, 0,
+ CORE_OBJECT_PCAP_INIT(0),
+ -1, 0, 0, 0, MAP_FAILED,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0
+};
+
+core_log_t* input_mmpcap_log()
+{
+ return &_log;
+}
+
+void input_mmpcap_init(input_mmpcap_t* self)
+{
+ mlassert_self();
+
+ *self = _defaults;
+}
+
+void input_mmpcap_destroy(input_mmpcap_t* self)
+{
+ mlassert_self();
+
+ if (self->buf != MAP_FAILED) {
+ munmap(self->buf, self->len);
+ }
+ if (self->fd > -1) {
+ close(self->fd);
+ }
+}
+
+int input_mmpcap_open(input_mmpcap_t* self, const char* file)
+{
+ struct stat sb;
+ mlassert_self();
+ lassert(file, "file is nil");
+
+ if (self->fd != -1) {
+ lfatal("already opened");
+ }
+
+ if ((self->fd = open(file, O_RDONLY)) < 0) {
+ lcritical("open(%s) error %s", file, core_log_errstr(errno));
+ return -1;
+ }
+
+ if (fstat(self->fd, &sb)) {
+ lcritical("stat(%s) error %s", file, core_log_errstr(errno));
+ return -1;
+ }
+ self->len = sb.st_size;
+
+ if ((self->buf = mmap(0, self->len, PROT_READ, MAP_PRIVATE, self->fd, 0)) == MAP_FAILED) {
+ lcritical("mmap(%s) error %s", file, core_log_errstr(errno));
+ return -1;
+ }
+
+ if (self->len < 24) {
+ lcritical("could not read full PCAP header");
+ return -2;
+ }
+ memcpy(&self->magic_number, self->buf, 4);
+ memcpy(&self->version_major, self->buf + 4, 2);
+ memcpy(&self->version_minor, self->buf + 6, 2);
+ memcpy(&self->thiszone, self->buf + 8, 4);
+ memcpy(&self->sigfigs, self->buf + 12, 4);
+ memcpy(&self->snaplen, self->buf + 16, 4);
+ memcpy(&self->network, self->buf + 20, 4);
+ self->at = 24;
+ switch (self->magic_number) {
+ case 0x4d3cb2a1:
+ self->is_nanosec = 1;
+ case 0xd4c3b2a1:
+ self->is_swapped = 1;
+ self->version_major = bswap_16(self->version_major);
+ self->version_minor = bswap_16(self->version_minor);
+ self->thiszone = (int32_t)bswap_32((uint32_t)self->thiszone);
+ self->sigfigs = bswap_32(self->sigfigs);
+ self->snaplen = bswap_32(self->snaplen);
+ self->network = bswap_32(self->network);
+ break;
+ case 0xa1b2c3d4:
+ case 0xa1b23c4d:
+ break;
+ default:
+ lcritical("invalid PCAP header");
+ return -2;
+ }
+
+ if (self->version_major != 2 || self->version_minor != 4) {
+ lcritical("unsupported PCAP version v%u.%u", self->version_major, self->version_minor);
+ return -2;
+ }
+
+ /*
+ * Translation taken from https://github.com/the-tcpdump-group/libpcap/blob/90543970fd5fbed261d3637f5ec4811d7dde4e49/pcap-common.c#L1212 .
+ */
+ switch (self->network) {
+ case 101: /* LINKTYPE_RAW */
+ self->linktype = DLT_RAW;
+ break;
+#ifdef DLT_FR
+ case 107: /* LINKTYPE_FRELAY */
+ self->linktype = DLT_FR;
+ break;
+#endif
+ case 100: /* LINKTYPE_ATM_RFC1483 */
+ self->linktype = DLT_ATM_RFC1483;
+ break;
+ case 102: /* LINKTYPE_SLIP_BSDOS */
+ self->linktype = DLT_SLIP_BSDOS;
+ break;
+ case 103: /* LINKTYPE_PPP_BSDOS */
+ self->linktype = DLT_PPP_BSDOS;
+ break;
+ case 104: /* LINKTYPE_C_HDLC */
+ self->linktype = DLT_C_HDLC;
+ break;
+ case 106: /* LINKTYPE_ATM_CLIP */
+ self->linktype = DLT_ATM_CLIP;
+ break;
+ case 50: /* LINKTYPE_PPP_HDLC */
+ self->linktype = DLT_PPP_SERIAL;
+ break;
+ case 51: /* LINKTYPE_PPP_ETHER */
+ self->linktype = DLT_PPP_ETHER;
+ break;
+ default:
+ self->linktype = self->network;
+ }
+
+ self->prod_pkt.snaplen = self->snaplen;
+ self->prod_pkt.linktype = self->linktype;
+ self->prod_pkt.is_swapped = self->is_swapped;
+
+ ldebug("pcap v%u.%u snaplen:%lu %s", self->version_major, self->version_minor, self->snaplen, self->is_swapped ? " swapped" : "");
+
+ return 0;
+}
+
+int input_mmpcap_run(input_mmpcap_t* self)
+{
+ struct {
+ uint32_t ts_sec;
+ uint32_t ts_usec;
+ uint32_t incl_len;
+ uint32_t orig_len;
+ } hdr;
+ core_object_pcap_t pkt = CORE_OBJECT_PCAP_INIT(0);
+ mlassert_self();
+
+ if (self->buf == MAP_FAILED) {
+ lfatal("no PCAP opened");
+ }
+ if (!self->recv) {
+ lfatal("no receiver set");
+ }
+
+ pkt.snaplen = self->snaplen;
+ pkt.linktype = self->linktype;
+ pkt.is_swapped = self->is_swapped;
+
+ while (self->len - self->at > 16) {
+ memcpy(&hdr, &self->buf[self->at], 16);
+ self->at += 16;
+ if (self->is_swapped) {
+ hdr.ts_sec = bswap_32(hdr.ts_sec);
+ hdr.ts_usec = bswap_32(hdr.ts_usec);
+ hdr.incl_len = bswap_32(hdr.incl_len);
+ hdr.orig_len = bswap_32(hdr.orig_len);
+ }
+ if (hdr.incl_len > self->snaplen) {
+ lwarning("invalid packet length, larger then snaplen");
+ return -1;
+ }
+ if (self->len - self->at < hdr.incl_len) {
+ lwarning("could not read all of packet, aborting");
+ return -1;
+ }
+
+ self->pkts++;
+
+ pkt.ts.sec = hdr.ts_sec;
+ if (self->is_nanosec) {
+ pkt.ts.nsec = hdr.ts_usec;
+ } else {
+ pkt.ts.nsec = hdr.ts_usec * 1000;
+ }
+ pkt.bytes = (unsigned char*)&self->buf[self->at];
+ pkt.caplen = hdr.incl_len;
+ pkt.len = hdr.orig_len;
+
+ self->recv(self->ctx, (core_object_t*)&pkt);
+
+ self->at += hdr.incl_len;
+ }
+ if (self->at < self->len) {
+ lwarning("could not read next PCAP header, aborting");
+ return -1;
+ }
+
+ return 0;
+}
+
+static const core_object_t* _produce(input_mmpcap_t* self)
+{
+ struct {
+ uint32_t ts_sec;
+ uint32_t ts_usec;
+ uint32_t incl_len;
+ uint32_t orig_len;
+ } hdr;
+ mlassert_self();
+
+ if (self->is_broken) {
+ lwarning("PCAP is broken, will not read next packet");
+ return 0;
+ }
+
+ if (self->len - self->at < 16) {
+ if (self->at < self->len) {
+ lwarning("could not read next PCAP header, aborting");
+ self->is_broken = 1;
+ }
+ return 0;
+ }
+
+ memcpy(&hdr, &self->buf[self->at], 16);
+ self->at += 16;
+ if (self->is_swapped) {
+ hdr.ts_sec = bswap_32(hdr.ts_sec);
+ hdr.ts_usec = bswap_32(hdr.ts_usec);
+ hdr.incl_len = bswap_32(hdr.incl_len);
+ hdr.orig_len = bswap_32(hdr.orig_len);
+ }
+ if (hdr.incl_len > self->snaplen) {
+ lwarning("invalid packet length, larger then snaplen");
+ self->is_broken = 1;
+ return 0;
+ }
+ if (self->len - self->at < hdr.incl_len) {
+ lwarning("could not read all of packet, aborting");
+ self->is_broken = 1;
+ return 0;
+ }
+
+ self->pkts++;
+
+ self->prod_pkt.ts.sec = hdr.ts_sec;
+ if (self->is_nanosec) {
+ self->prod_pkt.ts.nsec = hdr.ts_usec;
+ } else {
+ self->prod_pkt.ts.nsec = hdr.ts_usec * 1000;
+ }
+ self->prod_pkt.bytes = (unsigned char*)&self->buf[self->at];
+ self->prod_pkt.caplen = hdr.incl_len;
+ self->prod_pkt.len = hdr.orig_len;
+
+ self->at += hdr.incl_len;
+ return (core_object_t*)&self->prod_pkt;
+}
+
+core_producer_t input_mmpcap_producer(input_mmpcap_t* self)
+{
+ mlassert_self();
+
+ if (self->buf == MAP_FAILED) {
+ lfatal("no PCAP opened");
+ }
+
+ return (core_producer_t)_produce;
+}
diff --git a/src/input/mmpcap.h b/src/input/mmpcap.h
new file mode 100644
index 0000000..0873858
--- /dev/null
+++ b/src/input/mmpcap.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/log.h"
+#include "core/receiver.h"
+#include "core/producer.h"
+#include "core/object/pcap.h"
+
+#ifndef __dnsjit_input_mmpcap_h
+#define __dnsjit_input_mmpcap_h
+
+#include "input/mmpcap.hh"
+
+#endif
diff --git a/src/input/mmpcap.hh b/src/input/mmpcap.hh
new file mode 100644
index 0000000..27ed35e
--- /dev/null
+++ b/src/input/mmpcap.hh
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+//lua:require("dnsjit.core.producer_h")
+//lua:require("dnsjit.core.object.pcap_h")
+
+typedef struct input_mmpcap {
+ core_log_t _log;
+ core_receiver_t recv;
+ void* ctx;
+
+ uint8_t is_swapped;
+ uint8_t is_nanosec;
+ uint8_t is_broken;
+
+ core_object_pcap_t prod_pkt;
+
+ int fd;
+ size_t len, at;
+ size_t pkts;
+ uint8_t* buf;
+
+ uint32_t magic_number;
+ uint16_t version_major;
+ uint16_t version_minor;
+ int32_t thiszone;
+ uint32_t sigfigs;
+ uint32_t snaplen;
+ uint32_t network;
+
+ uint32_t linktype;
+} input_mmpcap_t;
+
+core_log_t* input_mmpcap_log();
+
+void input_mmpcap_init(input_mmpcap_t* self);
+void input_mmpcap_destroy(input_mmpcap_t* self);
+int input_mmpcap_open(input_mmpcap_t* self, const char* file);
+int input_mmpcap_run(input_mmpcap_t* self);
+
+core_producer_t input_mmpcap_producer(input_mmpcap_t* self);
diff --git a/src/input/mmpcap.lua b/src/input/mmpcap.lua
new file mode 100644
index 0000000..8e28e69
--- /dev/null
+++ b/src/input/mmpcap.lua
@@ -0,0 +1,120 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.input.mmpcap
+-- Read input from a PCAP file using mmap()
+-- local input = require("dnsjit.input.fpcap").new()
+-- input:open("file.pcap")
+-- input:receiver(filter_or_output)
+-- input:run()
+--
+-- Read input from a PCAP file by mapping the whole file to memory using
+-- .B mmap()
+-- and parse the PCAP without libpcap.
+-- After opening a file and reading the PCAP header, the attributes are
+-- populated.
+-- .SS Attributes
+-- .TP
+-- is_swapped
+-- Indicate if the byte order in the PCAP is in reverse order of the host.
+-- .TP
+-- is_nanosec
+-- Indicate if the time stamps are in nanoseconds or not.
+-- .TP
+-- magic_number
+-- Magic number.
+-- .TP
+-- version_major
+-- Major version number.
+-- .TP
+-- version_minor
+-- Minor version number.
+-- .TP
+-- thiszone
+-- GMT to local correction.
+-- .TP
+-- sigfigs
+-- Accuracy of timestamps.
+-- .TP
+-- snaplen
+-- Max length of captured packets, in octets.
+-- .TP
+-- network
+-- The link type found in the PCAP header, see https://www.tcpdump.org/linktypes.html .
+-- .TP
+-- linktype
+-- The data link type, mapped from
+-- .IR network .
+module(...,package.seeall)
+
+require("dnsjit.input.mmpcap_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "input_mmpcap_t"
+local input_mmpcap_t = ffi.typeof(t_name)
+local Mmpcap = {}
+
+-- Create a new Mmpcap input.
+function Mmpcap.new()
+ local self = {
+ _receiver = nil,
+ obj = input_mmpcap_t(),
+ }
+ C.input_mmpcap_init(self.obj)
+ ffi.gc(self.obj, C.input_mmpcap_destroy)
+ return setmetatable(self, { __index = Mmpcap })
+end
+
+-- Return the Log object to control logging of this instance or module.
+function Mmpcap:log()
+ if self == nil then
+ return C.input_mmpcap_log()
+ end
+ return self.obj._log
+end
+
+-- Set the receiver to pass objects to.
+function Mmpcap:receiver(o)
+ self.obj.recv, self.obj.ctx = o:receive()
+ self._receiver = o
+end
+
+-- Return the C functions and context for producing objects.
+function Mmpcap:produce()
+ return C.input_mmpcap_producer(self.obj), self.obj
+end
+
+-- Open a PCAP file for processing and read the PCAP header.
+-- Returns 0 on success.
+function Mmpcap:open(file)
+ return C.input_mmpcap_open(self.obj, file)
+end
+
+-- Start processing packets and send each packet read to the receiver.
+-- Returns 0 if all packets was read successfully.
+function Mmpcap:run()
+ return C.input_mmpcap_run(self.obj)
+end
+
+-- Return the number of packets seen.
+function Mmpcap:packets()
+ return tonumber(self.obj.pkts)
+end
+
+return Mmpcap
diff --git a/src/input/pcap.c b/src/input/pcap.c
new file mode 100644
index 0000000..25d6ee3
--- /dev/null
+++ b/src/input/pcap.c
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "input/pcap.h"
+#include "core/assert.h"
+#include "core/object/pcap.h"
+
+static core_log_t _log = LOG_T_INIT("input.pcap");
+static input_pcap_t _defaults = {
+ LOG_T_INIT_OBJ("input.pcap"),
+ 0, 0,
+ 0,
+ CORE_OBJECT_PCAP_INIT(0),
+ 0, 0,
+ 0, 0
+};
+
+core_log_t* input_pcap_log()
+{
+ return &_log;
+}
+
+void input_pcap_init(input_pcap_t* self)
+{
+ mlassert_self();
+
+ *self = _defaults;
+}
+
+void input_pcap_destroy(input_pcap_t* self)
+{
+ mlassert_self();
+
+ if (self->pcap) {
+ pcap_close(self->pcap);
+ }
+}
+
+int input_pcap_create(input_pcap_t* self, const char* source)
+{
+ char errbuf[PCAP_ERRBUF_SIZE] = { 0 };
+ mlassert_self();
+ lassert(source, "source is nil");
+
+ if (self->pcap) {
+ lfatal("already opened");
+ }
+
+ if (!(self->pcap = pcap_create(source, errbuf))) {
+ lcritical("pcap_create(%s) error: %s", source, errbuf);
+ return -1;
+ }
+
+ return 0;
+}
+
+int input_pcap_activate(input_pcap_t* self)
+{
+ int ret;
+
+ mlassert_self();
+ if (!self->pcap) {
+ lfatal("no PCAP opened");
+ }
+
+ if (!(ret = pcap_activate(self->pcap))) {
+ self->snaplen = pcap_snapshot(self->pcap);
+ self->linktype = pcap_datalink(self->pcap);
+ self->is_swapped = 0;
+
+ self->prod_pkt.snaplen = self->snaplen;
+ self->prod_pkt.linktype = self->linktype;
+ self->prod_pkt.is_swapped = self->is_swapped;
+
+ ldebug("pcap v%u.%u snaplen:%lu %s", pcap_major_version(self->pcap), pcap_minor_version(self->pcap), self->snaplen, self->is_swapped ? " swapped" : "");
+ }
+
+ return ret;
+}
+
+int input_pcap_open_offline(input_pcap_t* self, const char* file)
+{
+ char errbuf[PCAP_ERRBUF_SIZE] = { 0 };
+ mlassert_self();
+ lassert(file, "file is nil");
+
+ if (self->pcap) {
+ lfatal("already opened");
+ }
+
+ if (!(self->pcap = pcap_open_offline(file, errbuf))) {
+ lcritical("pcap_open_offline(%s) error: %s", file, errbuf);
+ return -1;
+ }
+
+ self->snaplen = pcap_snapshot(self->pcap);
+ self->linktype = pcap_datalink(self->pcap);
+ self->is_swapped = pcap_is_swapped(self->pcap);
+
+ self->prod_pkt.snaplen = self->snaplen;
+ self->prod_pkt.linktype = self->linktype;
+ self->prod_pkt.is_swapped = self->is_swapped;
+
+ ldebug("pcap v%u.%u snaplen:%lu %s", pcap_major_version(self->pcap), pcap_minor_version(self->pcap), self->snaplen, self->is_swapped ? " swapped" : "");
+
+ return 0;
+}
+
+static void _handler(u_char* user, const struct pcap_pkthdr* h, const u_char* bytes)
+{
+ input_pcap_t* self = (input_pcap_t*)user;
+ core_object_pcap_t pkt = CORE_OBJECT_PCAP_INIT(0);
+ mlassert_self();
+ lassert(h, "pcap_pkthdr is nil");
+ lassert(bytes, "bytes is nil");
+
+ self->pkts++;
+
+ pkt.snaplen = self->snaplen;
+ pkt.linktype = self->linktype;
+ pkt.ts.sec = h->ts.tv_sec;
+ pkt.ts.nsec = h->ts.tv_usec * 1000;
+ pkt.caplen = h->caplen;
+ pkt.len = h->len;
+ pkt.bytes = bytes;
+ pkt.is_swapped = self->is_swapped;
+
+ self->recv(self->ctx, (core_object_t*)&pkt);
+}
+
+int input_pcap_loop(input_pcap_t* self, int cnt)
+{
+ mlassert_self();
+ if (!self->pcap) {
+ lfatal("no PCAP opened");
+ }
+ if (!self->recv) {
+ lfatal("no receiver set");
+ }
+
+ return pcap_loop(self->pcap, cnt, _handler, (void*)self);
+}
+
+int input_pcap_dispatch(input_pcap_t* self, int cnt)
+{
+ mlassert_self();
+ if (!self->pcap) {
+ lfatal("no PCAP opened");
+ }
+ if (!self->recv) {
+ lfatal("no receiver set");
+ }
+
+ return pcap_dispatch(self->pcap, cnt, _handler, (void*)self);
+}
+
+static const core_object_t* _produce(input_pcap_t* self)
+{
+ struct pcap_pkthdr* h;
+ const u_char* bytes;
+ int ret = 0;
+ mlassert_self();
+
+ while (!(ret = pcap_next_ex(self->pcap, &h, &bytes)))
+ ;
+ if (ret == 1 && h && bytes) {
+ self->prod_pkt.ts.sec = h->ts.tv_sec;
+ self->prod_pkt.ts.nsec = h->ts.tv_usec * 1000;
+ self->prod_pkt.caplen = h->caplen;
+ self->prod_pkt.len = h->len;
+ self->prod_pkt.bytes = bytes;
+ return (core_object_t*)&self->prod_pkt;
+ }
+
+ return 0;
+}
+
+core_producer_t input_pcap_producer(input_pcap_t* self)
+{
+ mlassert_self();
+
+ if (!self->pcap) {
+ lfatal("no PCAP opened");
+ }
+
+ return (core_producer_t)_produce;
+}
diff --git a/src/input/pcap.h b/src/input/pcap.h
new file mode 100644
index 0000000..0a53de5
--- /dev/null
+++ b/src/input/pcap.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/log.h"
+#include "core/receiver.h"
+#include "core/producer.h"
+#include "core/object/pcap.h"
+
+#ifndef __dnsjit_input_pcap_h
+#define __dnsjit_input_pcap_h
+
+#include <pcap/pcap.h>
+
+#include "input/pcap.hh"
+
+#endif
diff --git a/src/input/pcap.hh b/src/input/pcap.hh
new file mode 100644
index 0000000..a9e0a41
--- /dev/null
+++ b/src/input/pcap.hh
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if 0
+typedef struct pcap {} pcap_t;
+#endif
+
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+//lua:require("dnsjit.core.producer_h")
+//lua:require("dnsjit.core.object.pcap_h")
+
+typedef struct input_pcap {
+ core_log_t _log;
+ core_receiver_t recv;
+ void* ctx;
+
+ uint8_t is_swapped;
+
+ core_object_pcap_t prod_pkt;
+
+ pcap_t* pcap;
+ size_t pkts;
+
+ size_t snaplen;
+ uint32_t linktype;
+} input_pcap_t;
+
+core_log_t* input_pcap_log();
+
+void input_pcap_init(input_pcap_t* self);
+void input_pcap_destroy(input_pcap_t* self);
+int input_pcap_create(input_pcap_t* self, const char* source);
+int input_pcap_activate(input_pcap_t* self);
+int input_pcap_open_offline(input_pcap_t* self, const char* file);
+int input_pcap_loop(input_pcap_t* self, int cnt);
+int input_pcap_dispatch(input_pcap_t* self, int cnt);
+
+core_producer_t input_pcap_producer(input_pcap_t* self);
diff --git a/src/input/pcap.lua b/src/input/pcap.lua
new file mode 100644
index 0000000..89aabd9
--- /dev/null
+++ b/src/input/pcap.lua
@@ -0,0 +1,130 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.input.pcap
+-- Read input from an interface or PCAP file using libpcap
+-- local input = require("dnsjit.input.pcap").new()
+-- input:open_offline("file.pcap")
+-- input:receiver(filter_or_output)
+-- input:run()
+--
+-- Input module for reading packets from interfaces and PCAP files.
+module(...,package.seeall)
+
+require("dnsjit.input.pcap_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "input_pcap_t"
+local input_pcap_t = ffi.typeof(t_name)
+local Pcap = {}
+
+-- Create a new Pcap input.
+function Pcap.new()
+ local self = {
+ _receiver = nil,
+ obj = input_pcap_t(),
+ }
+ C.input_pcap_init(self.obj)
+ ffi.gc(self.obj, C.input_pcap_destroy)
+ return setmetatable(self, { __index = Pcap })
+end
+
+-- Return the Log object to control logging of this instance or module.
+function Pcap:log()
+ if self == nil then
+ return C.input_pcap_log()
+ end
+ return self.obj._log
+end
+
+-- Set the receiver to pass objects to.
+function Pcap:receiver(o)
+ self.obj.recv, self.obj.ctx = o:receive()
+ self._receiver = o
+end
+
+-- Return the C functions and context for producing objects.
+function Pcap:produce()
+ return C.input_pcap_producer(self.obj), self.obj
+end
+
+-- Open a live packet capture on
+-- .IR source ,
+-- which is an interface name or "any" (Linux) / "all" (BSD).
+-- Must be activated before use.
+function Pcap:create(source)
+ return C.input_pcap_create(self.obj, source)
+end
+
+-- Activate a live packet capture, see
+-- .BR pcap_activate (3pcap)
+-- for more information and possible return values.
+function Pcap:activate()
+ return C.input_pcap_activate(self.obj)
+end
+
+-- Open a PCAP file for processing, see
+-- .BR pcap_open_offline (3pcap)
+-- for more information.
+-- Returns 0 on success.
+function Pcap:open_offline(file)
+ return C.input_pcap_open_offline(self.obj, file)
+end
+
+-- Process packets from a live capture or savefile until
+-- .I cnt
+-- packets are processed, see
+-- .BR pcap_loop (3pcap)
+-- for more information and possible return values.
+function Pcap:loop(cnt)
+ if cnt == nil then
+ cnt = -1
+ end
+ return C.input_pcap_loop(self.obj, cnt)
+end
+
+-- Process packets from a live capture or savefile until
+-- .I cnt
+-- packets are processed, see
+-- .BR pcap_dispatch (3pcap)
+-- for more information and possible return values.
+function Pcap:dispatch(cnt)
+ if cnt == nil then
+ cnt = -1
+ end
+ return C.input_pcap_dispatch(self.obj, cnt)
+end
+
+-- Return the number of packets seen.
+function Pcap:packets()
+ return tonumber(self.obj.pkts)
+end
+
+-- Return the linktype of the opened PCAP.
+function Pcap:linktype()
+ return self.obj.linktype
+end
+
+-- Return the snaplen of the opened PCAP.
+function Pcap:snaplen()
+ return self.obj.snaplen
+end
+
+-- dnsjit.output.pcap (3)
+return Pcap
diff --git a/src/input/zero.c b/src/input/zero.c
new file mode 100644
index 0000000..8bb1cf6
--- /dev/null
+++ b/src/input/zero.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "input/zero.h"
+#include "core/assert.h"
+#include "core/object/null.h"
+
+#include <time.h>
+
+static core_log_t _log = LOG_T_INIT("input.zero");
+static input_zero_t _defaults = {
+ LOG_T_INIT_OBJ("input.zero"),
+ 0,
+ 0,
+};
+
+static core_object_null_t _null = CORE_OBJECT_NULL_INIT(0);
+
+core_log_t* input_zero_log()
+{
+ return &_log;
+}
+
+void input_zero_init(input_zero_t* self)
+{
+ mlassert_self();
+
+ *self = _defaults;
+}
+
+void input_zero_destroy(input_zero_t* self)
+{
+ mlassert_self();
+}
+
+void input_zero_run(input_zero_t* self, uint64_t num)
+{
+ mlassert_self();
+ if (!self->recv) {
+ lfatal("no receiver set");
+ }
+
+ while (num--) {
+ self->recv(self->ctx, (core_object_t*)&_null);
+ }
+}
+
+static const core_object_t* _produce(void* ctx)
+{
+ return (core_object_t*)&_null;
+}
+
+core_producer_t input_zero_producer()
+{
+ return _produce;
+}
diff --git a/src/input/zero.h b/src/input/zero.h
new file mode 100644
index 0000000..882525a
--- /dev/null
+++ b/src/input/zero.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/log.h"
+#include "core/receiver.h"
+#include "core/producer.h"
+
+#ifndef __dnsjit_input_zero_h
+#define __dnsjit_input_zero_h
+
+#include <stdint.h>
+
+#include "input/zero.hh"
+
+#endif
diff --git a/src/input/zero.hh b/src/input/zero.hh
new file mode 100644
index 0000000..9373c90
--- /dev/null
+++ b/src/input/zero.hh
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+//lua:require("dnsjit.core.producer_h")
+
+typedef struct input_zero {
+ core_log_t _log;
+ core_receiver_t recv;
+ void* ctx;
+} input_zero_t;
+
+core_log_t* input_zero_log();
+
+void input_zero_init(input_zero_t* self);
+void input_zero_destroy(input_zero_t* self);
+void input_zero_run(input_zero_t* self, uint64_t num);
+
+core_producer_t input_zero_producer();
diff --git a/src/input/zero.lua b/src/input/zero.lua
new file mode 100644
index 0000000..1907dce
--- /dev/null
+++ b/src/input/zero.lua
@@ -0,0 +1,75 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.input.zero
+-- Generate empty objects (/dev/zero)
+-- local input = require("dnsjit.input.zero").new()
+-- input:receiver(filter_or_output)
+-- input:run(1e6)
+--
+-- Input module for generating empty
+-- .I core.object.null
+-- objects, mostly used for testing.
+module(...,package.seeall)
+
+require("dnsjit.input.zero_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "input_zero_t"
+local input_zero_t = ffi.typeof(t_name)
+local Zero = {}
+
+-- Create a new Zero input.
+function Zero.new()
+ local self = {
+ _receiver = nil,
+ obj = input_zero_t(),
+ }
+ C.input_zero_init(self.obj)
+ ffi.gc(self.obj, C.input_zero_destroy)
+ return setmetatable(self, { __index = Zero })
+end
+
+-- Return the Log object to control logging of this instance or module.
+function Zero:log()
+ if self == nil then
+ return C.input_zero_log()
+ end
+ return self.obj._log
+end
+
+-- Set the receiver to pass objects to.
+function Zero:receiver(o)
+ self.obj.recv, self.obj.ctx = o:receive()
+ self._receiver = o
+end
+
+-- Return the C functions and context for producing objects.
+function Zero:produce()
+ return C.input_zero_producer(), self.obj
+end
+
+-- Generate
+-- .I num
+-- empty objects and send them to the receiver.
+function Zero:run(num)
+ C.input_zero_run(self.obj, num)
+end
+
+return Zero
diff --git a/src/lib.lua b/src/lib.lua
new file mode 100644
index 0000000..d249ea8
--- /dev/null
+++ b/src/lib.lua
@@ -0,0 +1,29 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.lib
+-- Various Lua libraries or C library bindings
+module(...,package.seeall)
+
+-- dnsjit.lib.base64url (3),
+-- dnsjit.lib.clock (3),
+-- dnsjit.lib.getopt (3),
+-- dnsjit.lib.ip (3),
+-- dnsjit.lib.parseconf (3),
+-- dnsjit.lib.trie (3)
+return
diff --git a/src/lib/base64url.c b/src/lib/base64url.c
new file mode 100644
index 0000000..aacd490
--- /dev/null
+++ b/src/lib/base64url.c
@@ -0,0 +1,480 @@
+/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "lib/base64url.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+/*! \brief Maximal length of binary input to Base64url encoding. */
+#define MAX_BIN_DATA_LEN ((INT32_MAX / 4) * 3)
+
+/*! \brief Base64url padding character. */
+static const uint8_t base64url_pad = '\0';
+/*! \brief Base64 alphabet. */
+static const uint8_t base64url_enc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+
+/*! \brief Indicates bad Base64 character. */
+#define KO 255
+/*! \brief Indicates Base64 padding character. */
+#define PD 64
+
+/*! \brief Transformation and validation table for decoding Base64. */
+static const uint8_t base64url_dec[256] = {
+ [0] = PD,
+ [43] = KO,
+ ['V'] = 21,
+ [129] = KO,
+ [172] = KO,
+ [215] = KO,
+ [1] = KO,
+ [44] = KO,
+ ['W'] = 22,
+ [130] = KO,
+ [173] = KO,
+ [216] = KO,
+ [2] = KO,
+ ['-'] = 62,
+ ['X'] = 23,
+ [131] = KO,
+ [174] = KO,
+ [217] = KO,
+ [3] = KO,
+ [46] = KO,
+ ['Y'] = 24,
+ [132] = KO,
+ [175] = KO,
+ [218] = KO,
+ [4] = KO,
+ [47] = KO,
+ ['Z'] = 25,
+ [133] = KO,
+ [176] = KO,
+ [219] = KO,
+ [5] = KO,
+ ['0'] = 52,
+ [91] = KO,
+ [134] = KO,
+ [177] = KO,
+ [220] = KO,
+ [6] = KO,
+ ['1'] = 53,
+ [92] = KO,
+ [135] = KO,
+ [178] = KO,
+ [221] = KO,
+ [7] = KO,
+ ['2'] = 54,
+ [93] = KO,
+ [136] = KO,
+ [179] = KO,
+ [222] = KO,
+ [8] = KO,
+ ['3'] = 55,
+ [94] = KO,
+ [137] = KO,
+ [180] = KO,
+ [223] = KO,
+ [9] = KO,
+ ['4'] = 56,
+ ['_'] = 63,
+ [138] = KO,
+ [181] = KO,
+ [224] = KO,
+ [10] = KO,
+ ['5'] = 57,
+ [96] = KO,
+ [139] = KO,
+ [182] = KO,
+ [225] = KO,
+ [11] = KO,
+ ['6'] = 58,
+ ['a'] = 26,
+ [140] = KO,
+ [183] = KO,
+ [226] = KO,
+ [12] = KO,
+ ['7'] = 59,
+ ['b'] = 27,
+ [141] = KO,
+ [184] = KO,
+ [227] = KO,
+ [13] = KO,
+ ['8'] = 60,
+ ['c'] = 28,
+ [142] = KO,
+ [185] = KO,
+ [228] = KO,
+ [14] = KO,
+ ['9'] = 61,
+ ['d'] = 29,
+ [143] = KO,
+ [186] = KO,
+ [229] = KO,
+ [15] = KO,
+ [58] = KO,
+ ['e'] = 30,
+ [144] = KO,
+ [187] = KO,
+ [230] = KO,
+ [16] = KO,
+ [59] = KO,
+ ['f'] = 31,
+ [145] = KO,
+ [188] = KO,
+ [231] = KO,
+ [17] = KO,
+ [60] = KO,
+ ['g'] = 32,
+ [146] = KO,
+ [189] = KO,
+ [232] = KO,
+ [18] = KO,
+ [61] = KO,
+ ['h'] = 33,
+ [147] = KO,
+ [190] = KO,
+ [233] = KO,
+ [19] = KO,
+ [62] = KO,
+ ['i'] = 34,
+ [148] = KO,
+ [191] = KO,
+ [234] = KO,
+ [20] = KO,
+ [63] = KO,
+ ['j'] = 35,
+ [149] = KO,
+ [192] = KO,
+ [235] = KO,
+ [21] = KO,
+ [64] = KO,
+ ['k'] = 36,
+ [150] = KO,
+ [193] = KO,
+ [236] = KO,
+ [22] = KO,
+ ['A'] = 0,
+ ['l'] = 37,
+ [151] = KO,
+ [194] = KO,
+ [237] = KO,
+ [23] = KO,
+ ['B'] = 1,
+ ['m'] = 38,
+ [152] = KO,
+ [195] = KO,
+ [238] = KO,
+ [24] = KO,
+ ['C'] = 2,
+ ['n'] = 39,
+ [153] = KO,
+ [196] = KO,
+ [239] = KO,
+ [25] = KO,
+ ['D'] = 3,
+ ['o'] = 40,
+ [154] = KO,
+ [197] = KO,
+ [240] = KO,
+ [26] = KO,
+ ['E'] = 4,
+ ['p'] = 41,
+ [155] = KO,
+ [198] = KO,
+ [241] = KO,
+ [27] = KO,
+ ['F'] = 5,
+ ['q'] = 42,
+ [156] = KO,
+ [199] = KO,
+ [242] = KO,
+ [28] = KO,
+ ['G'] = 6,
+ ['r'] = 43,
+ [157] = KO,
+ [200] = KO,
+ [243] = KO,
+ [29] = KO,
+ ['H'] = 7,
+ ['s'] = 44,
+ [158] = KO,
+ [201] = KO,
+ [244] = KO,
+ [30] = KO,
+ ['I'] = 8,
+ ['t'] = 45,
+ [159] = KO,
+ [202] = KO,
+ [245] = KO,
+ [31] = KO,
+ ['J'] = 9,
+ ['u'] = 46,
+ [160] = KO,
+ [203] = KO,
+ [246] = KO,
+ [32] = KO,
+ ['K'] = 10,
+ ['v'] = 47,
+ [161] = KO,
+ [204] = KO,
+ [247] = KO,
+ [33] = KO,
+ ['L'] = 11,
+ ['w'] = 48,
+ [162] = KO,
+ [205] = KO,
+ [248] = KO,
+ [34] = KO,
+ ['M'] = 12,
+ ['x'] = 49,
+ [163] = KO,
+ [206] = KO,
+ [249] = KO,
+ [35] = KO,
+ ['N'] = 13,
+ ['y'] = 50,
+ [164] = KO,
+ [207] = KO,
+ [250] = KO,
+ [36] = KO,
+ ['O'] = 14,
+ ['z'] = 51,
+ [165] = KO,
+ [208] = KO,
+ [251] = KO,
+ [37] = KO,
+ ['P'] = 15,
+ [123] = KO,
+ [166] = KO,
+ [209] = KO,
+ [252] = KO,
+ [38] = KO,
+ ['Q'] = 16,
+ [124] = KO,
+ [167] = KO,
+ [210] = KO,
+ [253] = KO,
+ [39] = KO,
+ ['R'] = 17,
+ [125] = KO,
+ [168] = KO,
+ [211] = KO,
+ [254] = KO,
+ [40] = KO,
+ ['S'] = 18,
+ [126] = KO,
+ [169] = KO,
+ [212] = KO,
+ [255] = KO,
+ [41] = KO,
+ ['T'] = 19,
+ [127] = KO,
+ [170] = KO,
+ [213] = KO,
+ [42] = KO,
+ ['U'] = 20,
+ [128] = KO,
+ [171] = KO,
+ [214] = KO,
+};
+
+int32_t base64url_encode(const uint8_t* in,
+ const uint32_t in_len,
+ uint8_t* out,
+ const uint32_t out_len)
+{
+ // Checking inputs.
+ if (in == NULL || out == NULL) {
+ return -EINVAL;
+ }
+ if (in_len > MAX_BIN_DATA_LEN || out_len < ((in_len + 2) / 3) * 4) {
+ return -ERANGE;
+ }
+
+ uint8_t rest_len = in_len % 3;
+ const uint8_t* stop = in + in_len - rest_len;
+ uint8_t* text = out;
+
+ // Encoding loop takes 3 bytes and creates 4 characters.
+ while (in < stop) {
+ text[0] = base64url_enc[in[0] >> 2];
+ text[1] = base64url_enc[(in[0] & 0x03) << 4 | in[1] >> 4];
+ text[2] = base64url_enc[(in[1] & 0x0F) << 2 | in[2] >> 6];
+ text[3] = base64url_enc[in[2] & 0x3F];
+ text += 4;
+ in += 3;
+ }
+
+ // Processing of padding, if any.
+ switch (rest_len) {
+ case 2:
+ text[0] = base64url_enc[in[0] >> 2];
+ text[1] = base64url_enc[(in[0] & 0x03) << 4 | in[1] >> 4];
+ text[2] = base64url_enc[(in[1] & 0x0F) << 2];
+ text[3] = base64url_pad;
+ text += 3;
+ break;
+ case 1:
+ text[0] = base64url_enc[in[0] >> 2];
+ text[1] = base64url_enc[(in[0] & 0x03) << 4];
+ text[2] = base64url_pad;
+ text[3] = base64url_pad;
+ text += 2;
+ break;
+ }
+ return (text - out);
+}
+
+int32_t base64url_encode_alloc(const uint8_t* in,
+ const uint32_t in_len,
+ uint8_t** out)
+{
+ // Checking inputs.
+ if (out == NULL) {
+ return -EINVAL;
+ }
+ if (in_len > MAX_BIN_DATA_LEN) {
+ return -ERANGE;
+ }
+
+ // Compute output buffer length.
+ uint32_t out_len = ((in_len + 2) / 3) * 4;
+
+ // Allocate output buffer.
+ *out = malloc(out_len);
+ if (*out == NULL) {
+ return -ENOMEM;
+ }
+
+ // Encode data.
+ int32_t ret = base64url_encode(in, in_len, *out, out_len);
+ if (ret < 0) {
+ free(*out);
+ *out = NULL;
+ }
+
+ return ret;
+}
+
+int32_t base64url_decode(const uint8_t* in,
+ const uint32_t in_len,
+ uint8_t* out,
+ const uint32_t out_len)
+{
+ // Checking inputs.
+ if (in == NULL || out == NULL) {
+ return -EINVAL;
+ }
+ if (in_len > INT32_MAX || out_len < ((in_len + 3) / 4) * 3) {
+ return -ERANGE;
+ }
+
+ const uint8_t* stop = in + in_len;
+ uint8_t* bin = out;
+ uint8_t pad_len = 0;
+ uint8_t c1, c2, c3, c4;
+
+ // Decoding loop takes 4 characters and creates 3 bytes.
+ while (in < stop) {
+ // Filling and transforming 4 Base64 chars.
+ c1 = base64url_dec[in[0]];
+ c2 = (in + 1 < stop) ? base64url_dec[in[1]] : PD;
+ c3 = (in + 2 < stop) ? base64url_dec[in[2]] : PD;
+ c4 = (in + 3 < stop) ? base64url_dec[in[3]] : PD;
+
+ // Check 4. char if is bad or padding.
+ if (c4 >= PD) {
+ if (c4 == PD && pad_len == 0) {
+ pad_len = 1;
+ } else {
+ return -1;
+ }
+ }
+
+ // Check 3. char if is bad or padding.
+ if (c3 >= PD) {
+ if (c3 == PD && pad_len == 1) {
+ pad_len = 2;
+ } else {
+ return -1;
+ }
+ }
+
+ // Check 1. and 2. chars if are not padding.
+ if (c2 >= PD || c1 >= PD) {
+ return -1;
+ }
+
+ // Computing of output data based on padding length.
+ switch (pad_len) {
+ case 0:
+ bin[2] = (c3 << 6) + c4;
+ // FALLTHROUGH
+ case 1:
+ bin[1] = (c2 << 4) + (c3 >> 2);
+ // FALLTHROUGH
+ case 2:
+ bin[0] = (c1 << 2) + (c2 >> 4);
+ }
+
+ // Update output end.
+ switch (pad_len) {
+ case 0:
+ bin += 3;
+ break;
+ case 1:
+ bin += 2;
+ break;
+ case 2:
+ bin += 1;
+ break;
+ }
+
+ in += 4;
+ }
+
+ return (bin - out);
+}
+
+int32_t base64url_decode_alloc(const uint8_t* in,
+ const uint32_t in_len,
+ uint8_t** out)
+{
+ // Checking inputs.
+ if (out == NULL) {
+ return -EINVAL;
+ }
+
+ // Compute output buffer length.
+ uint32_t out_len = ((in_len + 3) / 4) * 3;
+
+ // Allocate output buffer.
+ *out = malloc(out_len);
+ if (*out == NULL) {
+ return -ENOMEM;
+ }
+
+ // Decode data.
+ int32_t ret = base64url_decode(in, in_len, *out, out_len);
+ if (ret < 0) {
+ free(*out);
+ *out = NULL;
+ }
+
+ return ret;
+}
diff --git a/src/lib/base64url.h b/src/lib/base64url.h
new file mode 100644
index 0000000..d355598
--- /dev/null
+++ b/src/lib/base64url.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2020, CZ.NIC, z.s.p.o.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+
+#ifndef __dnsjit_lib_base64url_h
+#define __dnsjit_lib_base64url_h
+
+#include "lib/base64url.hh"
+
+#endif
diff --git a/src/lib/base64url.hh b/src/lib/base64url.hh
new file mode 100644
index 0000000..5f19a10
--- /dev/null
+++ b/src/lib/base64url.hh
@@ -0,0 +1,99 @@
+/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ 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, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \brief Base64url implementation (RFC 4648).
+ */
+
+/*!
+ * \brief Encodes binary data using Base64.
+ *
+ * \note Output data buffer contains Base64 text string which isn't
+ * terminated with '\0'!
+ *
+ * \param in Input binary data.
+ * \param in_len Length of input data.
+ * \param out Output data buffer.
+ * \param out_len Size of output buffer.
+ *
+ * \retval >=0 length of output string.
+ * \retval KNOT_E* if error.
+ */
+int32_t base64url_encode(const uint8_t* in,
+ const uint32_t in_len,
+ uint8_t* out,
+ const uint32_t out_len);
+
+/*!
+ * \brief Encodes binary data using Base64 and output stores to own buffer.
+ *
+ * \note Output data buffer contains Base64 text string which isn't
+ * terminated with '\0'!
+ *
+ * \note Output buffer should be deallocated after use.
+ *
+ * \param in Input binary data.
+ * \param in_len Length of input data.
+ * \param out Output data buffer.
+ *
+ * \retval >=0 length of output string.
+ * \retval KNOT_E* if error.
+ */
+int32_t base64url_encode_alloc(const uint8_t* in,
+ const uint32_t in_len,
+ uint8_t** out);
+
+/*!
+ * \brief Decodes text data using Base64.
+ *
+ * \note Input data needn't be terminated with '\0'.
+ *
+ * \note Input data must be continuous Base64 string!
+ *
+ * \param in Input text data.
+ * \param in_len Length of input string.
+ * \param out Output data buffer.
+ * \param out_len Size of output buffer.
+ *
+ * \retval >=0 length of output data.
+ * \retval KNOT_E* if error.
+ */
+int32_t base64url_decode(const uint8_t* in,
+ const uint32_t in_len,
+ uint8_t* out,
+ const uint32_t out_len);
+
+/*!
+ * \brief Decodes text data using Base64 and output stores to own buffer.
+ *
+ * \note Input data needn't be terminated with '\0'.
+ *
+ * \note Input data must be continuous Base64 string!
+ *
+ * \note Output buffer should be deallocated after use.
+ *
+ * \param in Input text data.
+ * \param in_len Length of input string.
+ * \param out Output data buffer.
+ *
+ * \retval >=0 length of output data.
+ * \retval KNOT_E* if error.
+ */
+int32_t base64url_decode_alloc(const uint8_t* in,
+ const uint32_t in_len,
+ uint8_t** out);
+
+/*! @} */
diff --git a/src/lib/base64url.lua b/src/lib/base64url.lua
new file mode 100644
index 0000000..e9966d5
--- /dev/null
+++ b/src/lib/base64url.lua
@@ -0,0 +1,98 @@
+-- Copyright (c) 2020, CZ.NIC, z.s.p.o.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.lib.base64url
+-- Utility library to convert data to base64url format
+-- local base64url = require("dnsjit.lib.base64url")
+-- .SS Encoding and decoding lua strings
+-- local encoded = base64url.encode("abcd")
+-- local decoded = base64url.decode(encoded)
+-- .SS Encoding C byte arrays
+-- local pl -- pl is core.object.payload
+-- local encoded = base64url.encode(pl.payload, pl.len)
+--
+-- Encode and decode data to/from base64url format.
+module(...,package.seeall)
+
+require("dnsjit.lib.base64url_h")
+local ffi = require("ffi")
+local C = ffi.C
+local log = require("dnsjit.core.log")
+local module_log = log.new("lib.base64url")
+
+Base64Url = {}
+
+-- Encode lua string or C byte array to base64url representation.
+-- The input string may contain non-printable characters.
+--
+-- .B data_len
+-- is length of the input data (optional for lua strings, required for
+-- C byte arrays).
+function Base64Url.encode(data, data_len)
+ data_len = tonumber(data_len) -- in case of cdata length
+ if type(data) == "cdata" then
+ if type(data_len) ~= "number" then
+ module_log:fatal("encode: data_len must be specified for cdata")
+ return
+ end
+ elseif type(data) ~= "string" then
+ module_log:fatal("encode: input must be string")
+ return
+ end
+
+ if data_len ~= nil and data_len < 0 then
+ module_log:fatal("encode: data_len must be greater than 0")
+ return
+ end
+
+ local in_len = data_len or string.len(data)
+ local buf_len = math.ceil(4 * in_len / 3) + 2
+ local buf = ffi.new("uint8_t[?]", buf_len)
+ local out_len = ffi.C.base64url_encode(data, in_len, buf, buf_len)
+ if out_len < 0 then
+ module_log:critical("encode: error ("..log.errstr(-out_len)..")")
+ return
+ end
+ return ffi.string(buf, out_len)
+end
+
+-- Decode a base64url encoded lua string.
+-- The output string may contain non-printable characters.
+function Base64Url.decode(data)
+ if type(data) ~= "string" then
+ module_log:fatal("decode: input must be string")
+ return
+ end
+
+ local in_len = string.len(data)
+ local buf_len = math.ceil(3 * in_len / 4) + 1
+ local buf = ffi.new("uint8_t[?]", buf_len)
+ local out_len = ffi.C.base64url_decode(data, in_len, buf, buf_len)
+ if out_len == -34 then -- ERANGE
+ module_log:critical("decode: error "..log.errstr(-out_len).." - invalid character(s) in input string?")
+ return
+ elseif out_len < 0 then
+ module_log:critical("decode: error "..log.errstr(-out_len))
+ return
+ end
+ return ffi.string(buf, out_len)
+end
+
+-- dnsjit.core.object.payload(3)
+-- dnsjit.output.dnssim (3)
+return Base64Url
diff --git a/src/lib/clock.c b/src/lib/clock.c
new file mode 100644
index 0000000..0676648
--- /dev/null
+++ b/src/lib/clock.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "lib/clock.h"
+
+#include <time.h>
+
+core_timespec_t lib_clock_getres(lib_clock_clkid_t clkid)
+{
+ struct timespec ts;
+ core_timespec_t ret = { 0, 0 };
+ clockid_t clk_id;
+
+ switch (clkid) {
+ case LIB_CLOCK_REALTIME:
+ clk_id = CLOCK_REALTIME;
+ break;
+ case LIB_CLOCK_MONOTONIC:
+ clk_id = CLOCK_MONOTONIC;
+ break;
+ default:
+ return ret;
+ }
+
+ if (!clock_getres(clk_id, &ts)) {
+ ret.sec = ts.tv_sec;
+ ret.nsec = ts.tv_nsec;
+ }
+
+ return ret;
+}
+
+core_timespec_t lib_clock_gettime(lib_clock_clkid_t clkid)
+{
+ struct timespec ts;
+ core_timespec_t ret = { 0, 0 };
+ clockid_t clk_id;
+
+ switch (clkid) {
+ case LIB_CLOCK_REALTIME:
+ clk_id = CLOCK_REALTIME;
+ break;
+ case LIB_CLOCK_MONOTONIC:
+ clk_id = CLOCK_MONOTONIC;
+ break;
+ default:
+ return ret;
+ }
+
+ if (!clock_gettime(clk_id, &ts)) {
+ ret.sec = ts.tv_sec;
+ ret.nsec = ts.tv_nsec;
+ }
+
+ return ret;
+}
diff --git a/src/lib/clock.h b/src/lib/clock.h
new file mode 100644
index 0000000..0dc0faa
--- /dev/null
+++ b/src/lib/clock.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/timespec.h"
+
+#ifndef __dnsjit_lib_clock_h
+#define __dnsjit_lib_clock_h
+
+#include "lib/clock.hh"
+
+#endif
diff --git a/src/lib/clock.hh b/src/lib/clock.hh
new file mode 100644
index 0000000..9463815
--- /dev/null
+++ b/src/lib/clock.hh
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.timespec_h")
+
+typedef enum lib_clock_clkid {
+ LIB_CLOCK_REALTIME,
+ LIB_CLOCK_MONOTONIC
+} lib_clock_clkid_t;
+
+core_timespec_t lib_clock_getres(lib_clock_clkid_t clkid);
+core_timespec_t lib_clock_gettime(lib_clock_clkid_t clkid);
diff --git a/src/lib/clock.lua b/src/lib/clock.lua
new file mode 100644
index 0000000..60ee9f6
--- /dev/null
+++ b/src/lib/clock.lua
@@ -0,0 +1,47 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.lib.clock
+-- Clock and time functions
+-- local clock = require("dnsjit.lib.clock")
+-- local sec, nsec = clock.monotonic()
+--
+-- Functions to get the time from system-wide clocks.
+module(...,package.seeall)
+
+require("dnsjit.lib.clock_h")
+local C = require("ffi").C
+
+Clock = {}
+
+-- Return the current seconds and nanoseconds (as a list) from the realtime
+-- clock.
+function Clock.realtime()
+ local ts = C.lib_clock_gettime("LIB_CLOCK_REALTIME")
+ return tonumber(ts.sec), tonumber(ts.nsec)
+end
+
+-- Return the current seconds and nanoseconds (as a list) from the monotonic
+-- clock.
+function Clock.monotonic()
+ local ts = C.lib_clock_gettime("LIB_CLOCK_MONOTONIC")
+ return tonumber(ts.sec), tonumber(ts.nsec)
+end
+
+-- clock_gettime (2)
+return Clock
diff --git a/src/lib/getopt.lua b/src/lib/getopt.lua
new file mode 100644
index 0000000..91ce6cd
--- /dev/null
+++ b/src/lib/getopt.lua
@@ -0,0 +1,365 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.lib.getopt
+-- Parse and handle arguments
+-- local getopt = require("dnsjit.lib.getopt").new({
+-- { "v", "verbose", 0, "Enable verbosity", "?+" },
+-- { nil, "host", "localhost", "Set host", "?" },
+-- { "p", nil, 53, "Set port", "?" },
+-- })
+-- .
+-- local left = getopt:parse()
+-- .
+-- print("host", getopt:val("host"))
+-- print("port", getopt:val("p"))
+--
+-- A "getopt long" implementation to easily handle command line arguments
+-- and display usage.
+-- An option is the short name (one character), long name,
+-- default value (which also defines the type), help text and extensions.
+-- Options are by default required, see extensions to change this.
+-- .LP
+-- The Lua types allowed are
+-- .BR boolean ,
+-- .BR string ,
+-- .BR number .
+-- .LP
+-- The extensions available are:
+-- .TP
+-- .B ?
+-- Make the option optional.
+-- .TP
+-- .B *
+-- For string and number options this make it possible to specified it
+-- multiple times and all values will be returned in a table.
+-- .TP
+-- .B +
+-- For number options this will act as an counter increaser, the value will
+-- be the default value + 1 for each time the option is given.
+-- .LP
+-- Option
+-- .I -h
+-- and
+-- .I --help
+-- are automatically added if the option
+-- .I --help
+-- is not already defined.
+-- .SS Attributes
+-- .TP
+-- left
+-- A table that contains the arguments left after parsing, same as returned by
+-- .IR parse() .
+-- .TP
+-- usage_desc
+-- A string that describes the usage of the program, if not set then the
+-- default will be "
+-- .I "program [options...]"
+-- ".
+module(...,package.seeall)
+
+local log = require("dnsjit.core.log")
+
+local module_log = log.new("lib.getopt")
+Getopt = {}
+
+-- Create a new Getopt object.
+-- .I args
+-- is a table with tables that specifies the options available.
+-- Each entry is unpacked and sent to
+-- .BR Getopt:add() .
+function Getopt.new(args)
+ local self = setmetatable({
+ left = {},
+ usage_desc = nil,
+ _opt = {},
+ _s2l = {},
+ _log = log.new("lib.getopt", module_log),
+ }, { __index = Getopt })
+
+ self._log:debug("new()")
+
+ for k, v in pairs(args) do
+ local short, long, default, help, extensions = unpack(v)
+ self:add(short, long, default, help, extensions)
+ end
+
+ return self
+end
+
+-- Return the Log object to control logging of this instance or module.
+function Getopt:log()
+ if self == nil then
+ return module_log
+ end
+ return self._log
+end
+
+-- Add an option.
+function Getopt:add(short, long, default, help, extensions)
+ local optional = false
+ local multiple = false
+ local counter = false
+ local name = long or short
+
+ if type(name) ~= "string" then
+ error("long|short) need to be a string")
+ elseif name == "" then
+ error("name (long|short) needs to be set")
+ end
+
+ if self._opt[name] then
+ error("option "..name.." alredy exists")
+ elseif short and self._s2l[short] then
+ error("option "..short.." alredy exists")
+ end
+
+ local t = type(default)
+ if t ~= "string" and t ~= "number" and t ~= "boolean" then
+ error("option "..name..": invalid type "..t)
+ end
+
+ if type(extensions) == "string" then
+ local n
+ for n = 1, extensions:len() do
+ local extension = extensions:sub(n, n)
+ if extension == "?" then
+ optional = true
+ elseif extension == "*" then
+ multiple = true
+ elseif extension == "+" then
+ counter = true
+ else
+ error("option "..name..": invalid extension "..extension)
+ end
+ end
+ end
+
+ self._opt[name] = {
+ value = nil,
+ short = short,
+ long = long,
+ type = t,
+ default = default,
+ help = help,
+ optional = optional,
+ multiple = multiple,
+ counter = counter,
+ }
+ if long and short then
+ self._s2l[short] = long
+ elseif short and not long then
+ self._s2l[short] = short
+ end
+
+ if not self._opt["help"] then
+ self._opt["help"] = {
+ short = nil,
+ long = "help",
+ type = "boolean",
+ default = false,
+ help = "Display this help text",
+ optional = true,
+ }
+ if not self._s2l["h"] then
+ self._opt["help"].short = "h"
+ self._s2l["h"] = "help"
+ end
+ end
+end
+
+-- Print the usage.
+function Getopt:usage()
+ if self.usage_desc then
+ print("usage: " .. self.usage_desc)
+ else
+ print("usage: program [options...]")
+ end
+
+ local opts = {}
+ for k, _ in pairs(self._opt) do
+ if k ~= "help" then
+ table.insert(opts, k)
+ end
+ end
+ table.sort(opts)
+ table.insert(opts, "help")
+
+ for _, k in pairs(opts) do
+ local v = self._opt[k]
+ local arg
+ if v.type == "string" then
+ arg = " \""..v.default.."\""
+ elseif v.type == "number" and v.counter == false then
+ arg = " "..v.default
+ else
+ arg = ""
+ end
+ if v.long then
+ print("", (v.short and "-"..v.short or " ").." --"..v.long..arg, v.help)
+ else
+ print("", "-"..v.short..arg, v.help)
+ end
+ end
+end
+
+-- Parse the options.
+-- If
+-- .I args
+-- is not specified or nil then the global
+-- .B arg
+-- is used.
+-- If
+-- .I startn
+-- is given, it will start parsing arguments in the table from that position.
+-- The default position to start at is 2 for
+-- .IR dnsjit ,
+-- see
+-- .BR dnsjit.core (3).
+function Getopt:parse(args, startn)
+ if not args then
+ args = arg
+ end
+
+ local n
+ local opt = nil
+ local left = {}
+ local need_arg = false
+ local stop = false
+ local name
+ for n = startn or 2, table.maxn(args) do
+ if need_arg then
+ if opt.multiple then
+ if opt.value == nil then
+ opt.value = {}
+ end
+ if opt.type == "number" then
+ table.insert(opt.value, tonumber(args[n]))
+ else
+ table.insert(opt.value, args[n])
+ end
+ else
+ if opt.type == "number" then
+ opt.value = tonumber(args[n])
+ else
+ opt.value = args[n]
+ end
+ end
+ need_arg = false
+ elseif stop or args[n] == "-" then
+ table.insert(left, args[n])
+ elseif args[n] == "--" then
+ stop = true
+ elseif args[n]:sub(1, 1) == "-" then
+ if args[n]:sub(1, 2) == "--" then
+ name = args[n]:sub(3)
+ else
+ name = args[n]:sub(2)
+ if name:len() > 1 then
+ local n2, name2
+ for n2 = 1, name:len() - 1 do
+ name2 = name:sub(n2, n2)
+ opt = self._opt[self._s2l[name2]]
+ if not opt then
+ error("unknown option "..name2)
+ end
+ if opt.type == "number" and opt.counter then
+ if opt.value == nil then
+ opt.value = opt.default
+ end
+ opt.value = opt.value + 1
+ elseif opt.type == "boolean" then
+ if opt.value == nil then
+ opt.value = opt.default
+ end
+ if opt.value then
+ opt.value = false
+ else
+ opt.value = true
+ end
+ else
+ error("invalid short option '"..name2.."' in multioption statement")
+ end
+ end
+ name = name:sub(-1)
+ end
+ end
+ if self._s2l[name] then
+ name = self._s2l[name]
+ end
+ if not self._opt[name] then
+ error("unknown option "..name)
+ end
+ opt = self._opt[name]
+ if opt.type == "string" then
+ need_arg = true
+ elseif opt.type == "number" then
+ if opt.counter then
+ if opt.value == nil then
+ opt.value = opt.default
+ end
+ opt.value = opt.value + 1
+ else
+ need_arg = true
+ end
+ elseif opt.type == "boolean" then
+ if opt.value == nil then
+ opt.value = opt.default
+ end
+ if opt.value then
+ opt.value = false
+ else
+ opt.value = true
+ end
+ else
+ error("internal error, invalid option type "..opt.type)
+ end
+ else
+ table.insert(left, args[n])
+ end
+ end
+
+ if need_arg then
+ error("option "..name.." needs argument")
+ end
+
+ for k, v in pairs(self._opt) do
+ if v.optional == false and v.value == nil then
+ error("missing required option "..k.."")
+ end
+ end
+
+ self.left = left
+ return left
+end
+
+-- Return the value of an option.
+function Getopt:val(name)
+ local opt = self._opt[name] or self._opt[self._s2l[name]]
+ if not opt then
+ return
+ end
+ if opt.value == nil then
+ return opt.default
+ else
+ return opt.value
+ end
+end
+
+-- dnsjit.core (3)
+return Getopt
diff --git a/src/lib/ip.lua b/src/lib/ip.lua
new file mode 100644
index 0000000..74dc85a
--- /dev/null
+++ b/src/lib/ip.lua
@@ -0,0 +1,125 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.lib.ip
+-- IP address utility library
+-- local ip = require("dnsjit.lib.ip")
+-- print(ip.ipstring(ipv4_cdata))
+-- print(ip.ip6string(ipv6_cdata), true)
+--
+-- A library to help with various IP address related tasks, such as
+-- printing them.
+module(...,package.seeall)
+
+local ffi = require("ffi")
+
+Ip = {}
+
+-- Return an IPv4 or IPv6 address as a string.
+-- If it's an IPv6 address the optional argument
+-- .I pretty
+-- is true then return an easier to read IPv6 address.
+-- Return an empty string on invalid input.
+function Ip.tostring(ip, pretty)
+ if type(ip) == "cdata" then
+ if ffi.sizeof(ip) == 4 then
+ return Ip.ipstring(ip)
+ elseif ffi.sizeof(ip) == 16 then
+ return Ip.ip6string(ip, pretty)
+ end
+ end
+ return ""
+end
+
+-- Return a IPv4 address as a string.
+-- The input is a 4-byte cdata array.
+function Ip.ipstring(ip)
+ return ip[0] ..".".. ip[1] ..".".. ip[2] ..".".. ip[3]
+end
+
+local function _pretty(ip)
+ local src = {}
+
+ local n, nn
+ nn = 1
+ for n = 0, 15, 2 do
+ if ip[n] ~= 0 then
+ src[nn] = string.format("%x%02x", ip[n], ip[n + 1])
+ elseif ip[n + 1] ~= 0 then
+ src[nn] = string.format("%x", ip[n + 1])
+ else
+ src[nn] = "0"
+ end
+ nn = nn + 1
+ end
+
+ local best_n, best_at, at = 0, 0, 0
+ n = 0
+ for nn = 1, 8 do
+ if src[nn] == "0" then
+ if n == 0 then
+ at = nn
+ end
+ n = n + 1
+ else
+ if n > 0 then
+ if n > best_n then
+ best_n = n
+ best_at = at
+ end
+ n = 0
+ end
+ end
+ end
+ if n > 0 then
+ if n > best_n then
+ best_n = n
+ best_at = at
+ end
+ end
+ if best_n > 1 then
+ for n = 2, best_n do
+ table.remove(src, best_at)
+ end
+ if best_at == 1 or best_at + best_n > 8 then
+ src[best_at] = ":"
+ else
+ src[best_at] = ""
+ end
+ end
+
+ return table.concat(src,":")
+end
+
+-- Return the IPv6 address as a string.
+-- The input is a 16-byte cdata array.
+-- If
+-- .I pretty
+-- is true then return an easier to read IPv6 address.
+function Ip.ip6string(ip6, pretty)
+ if pretty == true then
+ return _pretty(ip6)
+ end
+ return string.format("%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
+ ip6[0], ip6[1], ip6[2], ip6[3], ip6[4], ip6[5], ip6[6], ip6[7],
+ ip6[8], ip6[9], ip6[10], ip6[11], ip6[12], ip6[13], ip6[14], ip6[15])
+end
+
+-- dnsjit.core.object.ip (3),
+-- dnsjit.core.object.ip6 (3)
+return Ip
diff --git a/src/lib/parseconf.lua b/src/lib/parseconf.lua
new file mode 100644
index 0000000..638763b
--- /dev/null
+++ b/src/lib/parseconf.lua
@@ -0,0 +1,181 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.lib.parseconf
+-- Parse simple config files
+-- local conf = require("dnsjit.lib.parseconf").new()
+-- .
+-- conf:func("config_name", function(k,...)
+-- print(k,...)
+-- end)
+-- .
+-- conf:file(file)
+-- .
+-- print(conf:val("another_config_name"))
+--
+-- This module parses simple config files that are based on the config
+-- syntax of DSC, drool and parseconf helper library.
+-- Each config begins with a
+-- .B name
+-- followed by
+-- .B options
+-- and ends with a
+-- .BR ; .
+-- Multiple configs can be given on the same line.
+-- Valid option types are
+-- .IR number ,
+-- .IR float ,
+-- .IR string ,
+-- .IR "quoted string" .
+-- Comments can be added by prefixing the comment with
+-- .BR # .
+-- .SS Example
+-- # Comment
+-- number 12345;
+-- float 123.456;
+-- string string string;
+-- quoted_string "string string string";
+-- multi config; on one line;
+module(...,package.seeall)
+
+local log = require("dnsjit.core.log")
+
+local module_log = log.new("lib.parseconf")
+Parseconf = {}
+
+-- Create a new Parseconf object.
+function Parseconf.new()
+ local self = setmetatable({
+ conf = {},
+ cf = {},
+ _log = log.new("lib.parseconf", module_log),
+ }, { __index = Parseconf })
+
+ self._log:debug("new()")
+
+ return self
+end
+
+-- Return the Log object to control logging of this instance or module.
+function Parseconf:log()
+ if self == nil then
+ return module_log
+ end
+ return self._log
+end
+
+-- Set a function to call when config
+-- .I name
+-- is found.
+function Parseconf:func(name, func)
+ self.cf[name] = func
+end
+
+function Parseconf:part(l, n)
+ local p
+ p = l:match("^(%d+)[%s;]", n)
+ if p then
+ return p, tonumber(p)
+ end
+ p = l:match("^(%d+%.%d+)[%s;]", n)
+ if p then
+ return p, tonumber(p)
+ end
+ p = l:match("^(\"[^\"]+\")[%s;]", n)
+ if p then
+ return p, p:sub(2, -2)
+ end
+ p = l:match("^([^%s;]+)[%s;]", n)
+ if p then
+ return p, p
+ end
+end
+
+function Parseconf:next(l, n)
+ local eol = l:match("^%s*;%s*", n)
+ if eol then
+ return true, eol
+ end
+ local ws = l:match("^%s+", n)
+ if ws then
+ return ws
+ end
+ return false
+end
+
+-- Parse the given file.
+function Parseconf:file(fn)
+ local ln, l
+ ln = 1
+ for l in io.lines(fn) do
+ local c = l:find("#")
+ if c then
+ l = l:sub(1, c - 1)
+ end
+ local e, m = pcall(self.line, self, l)
+ if e == false then
+ error("parse error in "..fn.."["..ln.."]: "..m)
+ end
+ ln = ln + 1
+ end
+end
+
+-- Parse the given line.
+function Parseconf:line(l)
+ local n
+ n = 1
+ while n <= l:len() do
+ local c = nil
+ local va = {}
+ while true do
+ local p, v = self:part(l, n)
+ if not p then
+ error("invalid config at character "..n..": "..l:sub(n))
+ end
+ if not c then
+ c = p
+ else
+ table.insert(va, v)
+ end
+ n = n + p:len()
+ local ws, eol = self:next(l, n)
+ if ws == true then
+ if eol then
+ n = n + eol:len()
+ end
+ break
+ elseif ws == false then
+ error("invalid config at character "..n..": "..l:sub(n))
+ end
+ n = n + ws:len()
+ end
+ if self.cf[c] then
+ self.cf[c](c, unpack(va))
+ else
+ self.conf[c] = va
+ end
+ end
+end
+
+-- Get the value of a config
+-- .IR name .
+function Parseconf:val(name)
+ return self.conf[name]
+end
+
+return Parseconf
diff --git a/src/lib/trie.c b/src/lib/trie.c
new file mode 100644
index 0000000..158c5ca
--- /dev/null
+++ b/src/lib/trie.c
@@ -0,0 +1,923 @@
+/*
+ * Copyright (C) 2016-2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * The code originated from https://github.com/fanf2/qp/blob/master/qp.c
+ * at revision 5f6d93753.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/trie.h"
+
+/*! \brief Error codes used in the library. */
+enum knot_error {
+ KNOT_EOK = 0,
+
+ /* Directly mapped error codes. */
+ KNOT_ENOMEM = -ENOMEM,
+ KNOT_EINVAL = -EINVAL,
+ KNOT_ENOENT = -ENOENT,
+};
+
+#if defined(__i386) || defined(__x86_64) || defined(_M_IX86) \
+ || (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN) \
+ && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+
+/*!
+ * \brief Use a pointer alignment hack to save memory.
+ *
+ * When on, isbranch() relies on the fact that in leaf_t the first pointer
+ * is aligned on multiple of 4 bytes and that the flags bitfield is
+ * overlaid over the lowest two bits of that pointer.
+ * Neither is really guaranteed by the C standards; the second part should
+ * be OK with x86_64 ABI and most likely any other little-endian platform.
+ * It would be possible to manipulate the right bits portably, but it would
+ * complicate the code nontrivially. C++ doesn't even guarantee type-punning.
+ * In debug mode we check this works OK when creating a new trie instance.
+ */
+#define FLAGS_HACK 1
+#else
+#define FLAGS_HACK 0
+#endif
+
+typedef unsigned char byte;
+#ifndef uint
+typedef unsigned int uint;
+#define uint uint
+#endif
+typedef uint bitmap_t; /*! Bit-maps, using the range of 1<<0 to 1<<16 (inclusive). */
+
+typedef struct {
+ uint32_t len; // 32 bits are enough for key lengths; probably even 16 bits would be.
+ uint8_t chars[];
+} tkey_t;
+
+/*! \brief Leaf of trie. */
+typedef struct {
+#if !FLAGS_HACK
+ byte flags;
+#endif
+ tkey_t* key; /*!< The pointer must be aligned to 4-byte multiples! */
+ trie_val_t val;
+} leaf_t;
+
+/*! \brief A trie node is either leaf_t or branch_t. */
+typedef union node node_t;
+
+/*!
+ * \brief Branch node of trie.
+ *
+ * - The flags distinguish whether the node is a leaf_t (0), or a branch
+ * testing the more-important nibble (1) or the less-important one (2).
+ * - It stores the index of the byte that the node tests. The combined
+ * value (index*4 + flags) increases in branch nodes as you go deeper
+ * into the trie. All the keys below a branch are identical up to the
+ * nibble identified by the branch. Indices have to be stored because
+ * we skip any branch nodes that would have a single child.
+ * (Consequently, the skipped parts of key have to be validated in a leaf.)
+ * - The bitmap indicates which subtries are present. The present child nodes
+ * are stored in the twigs array (with no holes between them).
+ * - To simplify storing keys that are prefixes of each other, the end-of-string
+ * position is treated as another nibble value, ordered before all others.
+ * That affects the bitmap and twigs fields.
+ *
+ * \note The branch nodes are never allocated individually, but they are
+ * always part of either the root node or the twigs array of the parent.
+ */
+typedef struct {
+#if FLAGS_HACK
+ uint32_t flags : 2,
+ bitmap : 17; /*!< The first bitmap bit is for end-of-string child. */
+#else
+ byte flags;
+ uint32_t bitmap;
+#endif
+ uint32_t index;
+ node_t* twigs;
+} branch_t;
+
+union node {
+ leaf_t leaf;
+ branch_t branch;
+};
+
+struct trie {
+ node_t root; // undefined when weight == 0, see empty_root()
+ size_t weight;
+ knot_mm_t mm;
+};
+
+/* Included from other files */
+
+/** Readability: avoid const-casts in code. */
+static inline void free_const(const void* what)
+{
+ free((void*)what);
+}
+
+static inline void* mm_alloc(knot_mm_t* mm, size_t size)
+{
+ if (mm)
+ return mm->alloc(mm->ctx, size);
+ else
+ return malloc(size);
+}
+
+static inline void mm_free(knot_mm_t* mm, const void* what)
+{
+ if (mm) {
+ if (mm->free)
+ mm->free((void*)what);
+ } else
+ free_const(what);
+}
+
+static void* mm_malloc(void* ctx, size_t n)
+{
+ (void)ctx;
+ return malloc(n);
+}
+
+static void* mm_realloc(knot_mm_t* mm, void* what, size_t size, size_t prev_size)
+{
+ if (mm) {
+ void* p = mm->alloc(mm->ctx, size);
+ if (p == NULL) {
+ return NULL;
+ } else {
+ if (what) {
+ memcpy(p, what,
+ prev_size < size ? prev_size : size);
+ }
+ mm_free(mm, what);
+ return p;
+ }
+ } else {
+ return realloc(what, size);
+ }
+}
+
+static inline void mm_ctx_init(knot_mm_t* mm)
+{
+ mm->ctx = NULL;
+ mm->alloc = mm_malloc;
+ mm->free = free;
+}
+
+/*! \brief Make the root node empty (debug-only). */
+static inline void empty_root(node_t* root)
+{
+#ifndef NDEBUG
+ *root = (node_t) { .branch = {
+ .flags = 3, // invalid value that fits
+ .bitmap = 0,
+ .index = -1,
+ .twigs = NULL } };
+#endif
+}
+
+/*! \brief Check that unportable code works OK (debug-only). */
+static void assert_portability(void)
+{
+#if FLAGS_HACK
+ assert(((union node) { .leaf = {
+ .key = (tkey_t*)(((uint8_t*)NULL) + 1),
+ .val = NULL } })
+ .branch.flags
+ == 1);
+#endif
+}
+
+/*! \brief Propagate error codes. */
+#define ERR_RETURN(x) \
+ do { \
+ int err_code_ = x; \
+ if (unlikely(err_code_ != KNOT_EOK)) \
+ return err_code_; \
+ } while (false)
+
+/*!
+ * \brief Count the number of set bits.
+ *
+ * \TODO This implementation may be relatively slow on some HW.
+ */
+static uint bitmap_weight(bitmap_t w)
+{
+ assert((w & ~((1 << 17) - 1)) == 0); // using the least-important 17 bits
+ return __builtin_popcount(w);
+}
+
+/*! \brief Only keep the lowest bit in the bitmap (least significant -> twigs[0]). */
+static bitmap_t bitmap_lowest_bit(bitmap_t w)
+{
+ assert((w & ~((1 << 17) - 1)) == 0); // using the least-important 17 bits
+ return 1 << __builtin_ctz(w);
+}
+
+/*! \brief Test flags to determine type of this node. */
+static bool isbranch(const node_t* t)
+{
+ uint f = t->branch.flags;
+ assert(f <= 2);
+ return f != 0;
+}
+
+/*! \brief Make a bitmask for testing a branch bitmap. */
+static bitmap_t nibbit(byte k, uint flags)
+{
+ uint shift = (2 - flags) << 2;
+ uint nibble = (k >> shift) & 0xf;
+ return 1 << (nibble + 1 /*because of prefix keys*/);
+}
+
+/*! \brief Extract a nibble from a key and turn it into a bitmask. */
+static bitmap_t twigbit(const node_t* t, const uint8_t* key, uint32_t len)
+{
+ assert(isbranch(t));
+ uint i = t->branch.index;
+
+ if (i >= len)
+ return 1 << 0; // leaf position
+
+ return nibbit((byte)key[i], t->branch.flags);
+}
+
+/*! \brief Test if a branch node has a child indicated by a bitmask. */
+static bool hastwig(const node_t* t, bitmap_t bit)
+{
+ assert(isbranch(t));
+ return t->branch.bitmap & bit;
+}
+
+/*! \brief Compute offset of an existing child in a branch node. */
+static uint twigoff(const node_t* t, bitmap_t b)
+{
+ assert(isbranch(t));
+ return bitmap_weight(t->branch.bitmap & (b - 1));
+}
+
+/*! \brief Get pointer to a particular child of a branch node. */
+static node_t* twig(node_t* t, uint i)
+{
+ assert(isbranch(t));
+ return &t->branch.twigs[i];
+}
+
+/*!
+ * \brief For a branch nod, compute offset of a child and child count.
+ *
+ * Having this separate might be meaningful for performance optimization.
+ */
+#define TWIGOFFMAX(off, max, t, b) \
+ do { \
+ (off) = twigoff((t), (b)); \
+ (max) = bitmap_weight((t)->branch.bitmap); \
+ } while (0)
+
+/*! \brief Simple string comparator. */
+static int key_cmp(const uint8_t* k1, uint32_t k1_len, const uint8_t* k2, uint32_t k2_len)
+{
+ int ret = memcmp(k1, k2, MIN(k1_len, k2_len));
+ if (ret != 0) {
+ return ret;
+ }
+
+ /* Key string is equal, compare lengths. */
+ if (k1_len == k2_len) {
+ return 0;
+ } else if (k1_len < k2_len) {
+ return -1;
+ } else {
+ return 1;
+ }
+}
+
+trie_t* trie_create(knot_mm_t* mm)
+{
+ assert_portability();
+ trie_t* trie = mm_alloc(mm, sizeof(trie_t));
+ if (trie != NULL) {
+ empty_root(&trie->root);
+ trie->weight = 0;
+ if (mm != NULL)
+ trie->mm = *mm;
+ else
+ mm_ctx_init(&trie->mm);
+ }
+ return trie;
+}
+
+/*! \brief Free anything under the trie node, except for the passed pointer itself. */
+static void clear_trie(node_t* trie, knot_mm_t* mm)
+{
+ if (!isbranch(trie)) {
+ mm_free(mm, trie->leaf.key);
+ } else {
+ branch_t* b = &trie->branch;
+ int len = bitmap_weight(b->bitmap);
+ int i;
+ for (i = 0; i < len; ++i)
+ clear_trie(b->twigs + i, mm);
+ mm_free(mm, b->twigs);
+ }
+}
+
+void trie_free(trie_t* tbl)
+{
+ if (tbl == NULL)
+ return;
+ if (tbl->weight)
+ clear_trie(&tbl->root, &tbl->mm);
+ mm_free(&tbl->mm, tbl);
+}
+
+void trie_clear(trie_t* tbl)
+{
+ assert(tbl);
+ if (!tbl->weight)
+ return;
+ clear_trie(&tbl->root, &tbl->mm);
+ empty_root(&tbl->root);
+ tbl->weight = 0;
+}
+
+size_t trie_weight(const trie_t* tbl)
+{
+ assert(tbl);
+ return tbl->weight;
+}
+
+struct found {
+ leaf_t* l; /**< the found leaf (NULL if not found) */
+ branch_t* p; /**< the leaf's parent (if exists) */
+ bitmap_t b; /**< bit-mask with a single bit marking l under p */
+};
+/** Search trie for an item with the given key (equality only). */
+static struct found find_equal(trie_t* tbl, const uint8_t* key, uint32_t len)
+{
+ assert(tbl);
+ struct found ret0;
+ memset(&ret0, 0, sizeof(ret0));
+ if (!tbl->weight)
+ return ret0;
+ /* Current node and parent while descending (returned values basically). */
+ node_t* t = &tbl->root;
+ branch_t* p = NULL;
+ bitmap_t b = 0;
+ while (isbranch(t)) {
+ __builtin_prefetch(t->branch.twigs);
+ b = twigbit(t, key, len);
+ if (!hastwig(t, b))
+ return ret0;
+ p = &t->branch;
+ t = twig(t, twigoff(t, b));
+ }
+ if (key_cmp(key, len, t->leaf.key->chars, t->leaf.key->len) != 0)
+ return ret0;
+ return (struct found) {
+ .l = &t->leaf,
+ .p = p,
+ .b = b,
+ };
+}
+/** Find item with the first key (lexicographical order). */
+static struct found find_first(trie_t* tbl)
+{
+ assert(tbl);
+ if (!tbl->weight) {
+ struct found ret0;
+ memset(&ret0, 0, sizeof(ret0));
+ return ret0;
+ }
+ /* Current node and parent while descending (returned values basically). */
+ node_t* t = &tbl->root;
+ branch_t* p = NULL;
+ while (isbranch(t)) {
+ p = &t->branch;
+ t = &p->twigs[0];
+ }
+ return (struct found) {
+ .l = &t->leaf,
+ .p = p,
+ .b = p ? bitmap_lowest_bit(p->bitmap) : 0,
+ };
+}
+
+trie_val_t* trie_get_try(trie_t* tbl, const uint8_t* key, uint32_t len)
+{
+ struct found found = find_equal(tbl, key, len);
+ return found.l ? &found.l->val : NULL;
+}
+
+trie_val_t* trie_get_first(trie_t* tbl, uint8_t** key, uint32_t* len)
+{
+ struct found found = find_first(tbl);
+ if (!found.l)
+ return NULL;
+ if (key)
+ *key = found.l->key->chars;
+ if (len)
+ *len = found.l->key->len;
+ return &found.l->val;
+}
+
+/*!
+ * \brief Stack of nodes, storing a path down a trie.
+ *
+ * The structure also serves directly as the public trie_it_t type,
+ * in which case it always points to the current leaf, unless we've finished
+ * (i.e. it->len == 0).
+ */
+typedef struct trie_it {
+ node_t** stack; /*!< The stack; malloc is used directly instead of mm. */
+ uint32_t len; /*!< Current length of the stack. */
+ uint32_t alen; /*!< Allocated/available length of the stack. */
+ /*! \brief Initial storage for \a stack; it should fit in many use cases. */
+ node_t* stack_init[60];
+} nstack_t;
+
+/*! \brief Create a node stack containing just the root (or empty). */
+static void ns_init(nstack_t* ns, trie_t* tbl)
+{
+ assert(tbl);
+ ns->stack = ns->stack_init;
+ ns->alen = sizeof(ns->stack_init) / sizeof(ns->stack_init[0]);
+ if (tbl->weight) {
+ ns->len = 1;
+ ns->stack[0] = &tbl->root;
+ } else {
+ ns->len = 0;
+ }
+}
+
+/*! \brief Free inside of the stack, i.e. not the passed pointer itself. */
+static void ns_cleanup(nstack_t* ns)
+{
+ assert(ns && ns->stack);
+ if (likely(ns->stack == ns->stack_init))
+ return;
+ free(ns->stack);
+#ifndef NDEBUG
+ ns->stack = NULL;
+ ns->alen = 0;
+#endif
+}
+
+/*! \brief Allocate more space for the stack. */
+static int ns_longer_alloc(nstack_t* ns)
+{
+ ns->alen *= 2;
+ size_t new_size = sizeof(nstack_t) + ns->alen * sizeof(node_t*);
+ node_t** st;
+ if (ns->stack == ns->stack_init) {
+ st = malloc(new_size);
+ if (st != NULL)
+ memcpy(st, ns->stack, ns->len * sizeof(node_t*));
+ } else {
+ st = realloc(ns->stack, new_size);
+ }
+ if (st == NULL)
+ return KNOT_ENOMEM;
+ ns->stack = st;
+ return KNOT_EOK;
+}
+
+/*! \brief Ensure the node stack can be extended by one. */
+static inline int ns_longer(nstack_t* ns)
+{
+ // get a longer stack if needed
+ if (likely(ns->len < ns->alen))
+ return KNOT_EOK;
+ return ns_longer_alloc(ns); // hand-split the part suitable for inlining
+}
+
+/*!
+ * \brief Find the "branching point" as if searching for a key.
+ *
+ * The whole path to the point is kept on the passed stack;
+ * always at least the root will remain on the top of it.
+ * Beware: the precise semantics of this function is rather tricky.
+ * The top of the stack will contain: the corresponding leaf if exact match is found;
+ * or the immediate node below a branching-point-on-edge or the branching-point itself.
+ *
+ * \param info Set position of the point of first mismatch (in index and flags).
+ * \param first Set the value of the first non-matching character (from trie),
+ * optionally; end-of-string character has value -256 (that's why it's int).
+ * Note: the character is converted to *unsigned* char (i.e. 0..255),
+ * as that's the ordering used in the trie.
+ *
+ * \return KNOT_EOK or KNOT_ENOMEM.
+ */
+static int ns_find_branch(nstack_t* ns, const uint8_t* key, uint32_t len,
+ branch_t* info, int* first)
+{
+ assert(ns && ns->len && info);
+ // First find some leaf with longest matching prefix.
+ while (isbranch(ns->stack[ns->len - 1])) {
+ ERR_RETURN(ns_longer(ns));
+ node_t* t = ns->stack[ns->len - 1];
+ __builtin_prefetch(t->branch.twigs);
+ bitmap_t b = twigbit(t, key, len);
+ // Even if our key is missing from this branch we need to
+ // keep iterating down to a leaf. It doesn't matter which
+ // twig we choose since the keys are all the same up to this
+ // index. Note that blindly using twigoff(t, b) can cause
+ // an out-of-bounds index if it equals twigmax(t).
+ uint i = hastwig(t, b) ? twigoff(t, b) : 0;
+ ns->stack[ns->len++] = twig(t, i);
+ }
+ tkey_t* lkey = ns->stack[ns->len - 1]->leaf.key;
+ // Find index of the first char that differs.
+ uint32_t index = 0;
+ while (index < MIN(len, lkey->len)) {
+ if (key[index] != lkey->chars[index])
+ break;
+ else
+ ++index;
+ }
+ info->index = index;
+ if (first)
+ *first = lkey->len > index ? (unsigned char)lkey->chars[index] : -256;
+ // Find flags: which half-byte has matched.
+ uint flags;
+ if (index == len && len == lkey->len) { // found equivalent key
+ info->flags = flags = 0;
+ goto success;
+ }
+ if (likely(index < MIN(len, lkey->len))) {
+ byte k2 = (byte)lkey->chars[index];
+ byte k1 = (byte)key[index];
+ flags = ((k1 ^ k2) & 0xf0) ? 1 : 2;
+ } else { // one is prefix of another
+ flags = 1;
+ }
+ info->flags = flags;
+ // now go up the trie from the current leaf
+ branch_t* t;
+ do {
+ if (unlikely(ns->len == 1))
+ goto success; // only the root stays on the stack
+ t = (branch_t*)ns->stack[ns->len - 2];
+ if (t->index < index || (t->index == index && t->flags < flags))
+ goto success;
+ --ns->len;
+ } while (true);
+success:
+#ifndef NDEBUG // invariants on successful return
+ assert(ns->len);
+ if (isbranch(ns->stack[ns->len - 1])) {
+ t = &ns->stack[ns->len - 1]->branch;
+ assert(t->index > index || (t->index == index && t->flags >= flags));
+ }
+ if (ns->len > 1) {
+ t = &ns->stack[ns->len - 2]->branch;
+ assert(t->index < index || (t->index == index && (t->flags < flags || (t->flags == 1 && flags == 0))));
+ }
+#endif
+ return KNOT_EOK;
+}
+
+/*!
+ * \brief Advance the node stack to the last leaf in the subtree.
+ *
+ * \return KNOT_EOK or KNOT_ENOMEM.
+ */
+static int ns_last_leaf(nstack_t* ns)
+{
+ assert(ns);
+ do {
+ ERR_RETURN(ns_longer(ns));
+ node_t* t = ns->stack[ns->len - 1];
+ if (!isbranch(t))
+ return KNOT_EOK;
+ int lasti = bitmap_weight(t->branch.bitmap) - 1;
+ assert(lasti >= 0);
+ ns->stack[ns->len++] = twig(t, lasti);
+ } while (true);
+}
+
+/*!
+ * \brief Advance the node stack to the first leaf in the subtree.
+ *
+ * \return KNOT_EOK or KNOT_ENOMEM.
+ */
+static int ns_first_leaf(nstack_t* ns)
+{
+ assert(ns && ns->len);
+ do {
+ ERR_RETURN(ns_longer(ns));
+ node_t* t = ns->stack[ns->len - 1];
+ if (!isbranch(t))
+ return KNOT_EOK;
+ ns->stack[ns->len++] = twig(t, 0);
+ } while (true);
+}
+
+/*!
+ * \brief Advance the node stack to the leaf that is previous to the current node.
+ *
+ * \note Prefix leaf under the current node DOES count (if present; perhaps questionable).
+ * \return KNOT_EOK on success, KNOT_ENOENT on not-found, or possibly KNOT_ENOMEM.
+ */
+static int ns_prev_leaf(nstack_t* ns)
+{
+ assert(ns && ns->len > 0);
+
+ node_t* t = ns->stack[ns->len - 1];
+ if (hastwig(t, 1 << 0)) { // the prefix leaf
+ t = twig(t, 0);
+ ERR_RETURN(ns_longer(ns));
+ ns->stack[ns->len++] = t;
+ return KNOT_EOK;
+ }
+
+ do {
+ if (ns->len < 2)
+ return KNOT_ENOENT; // root without empty key has no previous leaf
+ t = ns->stack[ns->len - 1];
+ node_t* p = ns->stack[ns->len - 2];
+ int pindex = t - p->branch.twigs; // index in parent via pointer arithmetic
+ assert(pindex >= 0 && pindex <= 16);
+ if (pindex > 0) { // t isn't the first child -> go down the previous one
+ ns->stack[ns->len - 1] = twig(p, pindex - 1);
+ return ns_last_leaf(ns);
+ }
+ // we've got to go up again
+ --ns->len;
+ } while (true);
+}
+
+/*!
+ * \brief Advance the node stack to the leaf that is successor to the current node.
+ *
+ * \note Prefix leaf or anything else under the current node DOES count.
+ * \return KNOT_EOK on success, KNOT_ENOENT on not-found, or possibly KNOT_ENOMEM.
+ */
+static int ns_next_leaf(nstack_t* ns)
+{
+ assert(ns && ns->len > 0);
+
+ node_t* t = ns->stack[ns->len - 1];
+ if (isbranch(t))
+ return ns_first_leaf(ns);
+ do {
+ if (ns->len < 2)
+ return KNOT_ENOENT; // not found, as no more parent is available
+ t = ns->stack[ns->len - 1];
+ node_t* p = ns->stack[ns->len - 2];
+ int pindex = t - p->branch.twigs; // index in parent via pointer arithmetic
+ assert(pindex >= 0 && pindex <= 16);
+ int pcount = bitmap_weight(p->branch.bitmap);
+ if (pindex + 1 < pcount) { // t isn't the last child -> go down the next one
+ ns->stack[ns->len - 1] = twig(p, pindex + 1);
+ return ns_first_leaf(ns);
+ }
+ // we've got to go up again
+ --ns->len;
+ } while (true);
+}
+
+int trie_get_leq(trie_t* tbl, const uint8_t* key, uint32_t len, trie_val_t** val)
+{
+ assert(tbl && val);
+ *val = NULL; // so on failure we can just return;
+ if (tbl->weight == 0)
+ return KNOT_ENOENT;
+ { // Intentionally un-indented; until end of function, to bound cleanup attr.
+ // First find a key with longest-matching prefix
+ __attribute__((cleanup(ns_cleanup)))
+ nstack_t ns_local;
+ ns_init(&ns_local, tbl);
+ nstack_t* ns = &ns_local;
+ branch_t bp;
+ int un_leaf; // first unmatched character in the leaf
+ ERR_RETURN(ns_find_branch(ns, key, len, &bp, &un_leaf));
+ int un_key = bp.index < len ? (unsigned char)key[bp.index] : -256;
+ node_t* t = ns->stack[ns->len - 1];
+ if (bp.flags == 0) { // found exact match
+ *val = &t->leaf.val;
+ return KNOT_EOK;
+ }
+ // Get t: the last node on matching path
+ if (isbranch(t) && t->branch.index == bp.index && t->branch.flags == bp.flags) {
+ // t is OK
+ } else {
+ // the top of the stack was the first unmatched node -> step up
+ if (ns->len == 1) {
+ // root was unmatched already
+ if (un_key < un_leaf)
+ return KNOT_ENOENT;
+ ERR_RETURN(ns_last_leaf(ns));
+ goto success;
+ }
+ --ns->len;
+ t = ns->stack[ns->len - 1];
+ }
+ // Now we re-do the first "non-matching" step in the trie
+ // but try the previous child if key was less (it may not exist)
+ bitmap_t b = twigbit(t, key, len);
+ int i = hastwig(t, b)
+ ? twigoff(t, b) - (un_key < un_leaf)
+ : twigoff(t, b) - 1 /*twigoff returns successor when !hastwig*/;
+ if (i >= 0) {
+ ERR_RETURN(ns_longer(ns));
+ ns->stack[ns->len++] = twig(t, i);
+ ERR_RETURN(ns_last_leaf(ns));
+ } else {
+ ERR_RETURN(ns_prev_leaf(ns));
+ }
+ success:
+ assert(!isbranch(ns->stack[ns->len - 1]));
+ *val = &ns->stack[ns->len - 1]->leaf.val;
+ return 1;
+ }
+}
+
+/*! \brief Initialize a new leaf, copying the key, and returning failure code. */
+static int mk_leaf(node_t* leaf, const uint8_t* key, uint32_t len, knot_mm_t* mm)
+{
+ tkey_t* k = mm_alloc(mm, sizeof(tkey_t) + len);
+#if FLAGS_HACK
+ assert(((uintptr_t)k) % 4 == 0); // we need an aligned pointer
+#endif
+ if (unlikely(!k))
+ return KNOT_ENOMEM;
+ k->len = len;
+ memcpy(k->chars, key, len);
+ leaf->leaf = (leaf_t)
+ {
+#if !FLAGS_HACK
+ .flags = 0,
+#endif
+ .val = NULL,
+ .key = k
+ };
+ return KNOT_EOK;
+}
+
+trie_val_t* trie_get_ins(trie_t* tbl, const uint8_t* key, uint32_t len)
+{
+ assert(tbl);
+ // First leaf in an empty tbl?
+ if (unlikely(!tbl->weight)) {
+ if (unlikely(mk_leaf(&tbl->root, key, len, &tbl->mm)))
+ return NULL;
+ ++tbl->weight;
+ return &tbl->root.leaf.val;
+ }
+ { // Intentionally un-indented; until end of function, to bound cleanup attr.
+ // Find the branching-point
+ __attribute__((cleanup(ns_cleanup)))
+ nstack_t ns_local;
+ ns_init(&ns_local, tbl);
+ nstack_t* ns = &ns_local;
+ branch_t bp; // branch-point: index and flags signifying the longest common prefix
+ int k2; // the first unmatched character in the leaf
+ if (unlikely(ns_find_branch(ns, key, len, &bp, &k2)))
+ return NULL;
+ node_t* t = ns->stack[ns->len - 1];
+ if (bp.flags == 0) // the same key was already present
+ return &t->leaf.val;
+ node_t leaf;
+ if (unlikely(mk_leaf(&leaf, key, len, &tbl->mm)))
+ return NULL;
+
+ if (isbranch(t) && bp.index == t->branch.index && bp.flags == t->branch.flags) {
+ // The node t needs a new leaf child.
+ bitmap_t b1 = twigbit(t, key, len);
+ assert(!hastwig(t, b1));
+ uint s, m;
+ TWIGOFFMAX(s, m, t, b1); // new child position and original child count
+ node_t* twigs = mm_realloc(&tbl->mm, t->branch.twigs,
+ sizeof(node_t) * (m + 1), sizeof(node_t) * m);
+ if (unlikely(!twigs))
+ goto err_leaf;
+ memmove(twigs + s + 1, twigs + s, sizeof(node_t) * (m - s));
+ twigs[s] = leaf;
+ t->branch.twigs = twigs;
+ t->branch.bitmap |= b1;
+ ++tbl->weight;
+ return &twigs[s].leaf.val;
+ } else {
+// We need to insert a new binary branch with leaf at *t.
+// Note: it works the same for the case where we insert above root t.
+#ifndef NDEBUG
+ if (ns->len > 1) {
+ node_t* pt = ns->stack[ns->len - 2];
+ assert(hastwig(pt, twigbit(pt, key, len)));
+ }
+#endif
+ node_t* twigs = mm_alloc(&tbl->mm, sizeof(node_t) * 2);
+ if (unlikely(!twigs))
+ goto err_leaf;
+ node_t t2 = *t; // Save before overwriting t.
+ t->branch.flags = bp.flags;
+ t->branch.index = bp.index;
+ t->branch.twigs = twigs;
+ bitmap_t b1 = twigbit(t, key, len);
+ bitmap_t b2 = unlikely(k2 == -256) ? (1 << 0) : nibbit(k2, bp.flags);
+ t->branch.bitmap = b1 | b2;
+ *twig(t, twigoff(t, b1)) = leaf;
+ *twig(t, twigoff(t, b2)) = t2;
+ ++tbl->weight;
+ return &twig(t, twigoff(t, b1))->leaf.val;
+ };
+ err_leaf:
+ mm_free(&tbl->mm, leaf.leaf.key);
+ return NULL;
+ }
+}
+
+/*! \brief Apply a function to every trie_val_t*, in order; a recursive solution. */
+static int apply_trie(node_t* t, int (*f)(trie_val_t*, void*), void* d)
+{
+ assert(t);
+ if (!isbranch(t))
+ return f(&t->leaf.val, d);
+ int child_count = bitmap_weight(t->branch.bitmap);
+ int i;
+ for (i = 0; i < child_count; ++i)
+ ERR_RETURN(apply_trie(twig(t, i), f, d));
+ return KNOT_EOK;
+}
+
+int trie_apply(trie_t* tbl, int (*f)(trie_val_t*, void*), void* d)
+{
+ assert(tbl && f);
+ if (!tbl->weight)
+ return KNOT_EOK;
+ return apply_trie(&tbl->root, f, d);
+}
+
+/* These are all thin wrappers around static Tns* functions. */
+trie_it_t* trie_it_begin(trie_t* tbl)
+{
+ assert(tbl);
+ trie_it_t* it = malloc(sizeof(nstack_t));
+ if (!it)
+ return NULL;
+ ns_init(it, tbl);
+ if (it->len == 0) // empty tbl
+ return it;
+ if (ns_first_leaf(it)) {
+ ns_cleanup(it);
+ free(it);
+ return NULL;
+ }
+ return it;
+}
+
+void trie_it_next(trie_it_t* it)
+{
+ assert(it && it->len);
+ if (ns_next_leaf(it) != KNOT_EOK)
+ it->len = 0;
+}
+
+bool trie_it_finished(trie_it_t* it)
+{
+ assert(it);
+ return it->len == 0;
+}
+
+void trie_it_free(trie_it_t* it)
+{
+ if (!it)
+ return;
+ ns_cleanup(it);
+ free(it);
+}
+
+const uint8_t* trie_it_key(trie_it_t* it, size_t* len)
+{
+ assert(it && it->len);
+ node_t* t = it->stack[it->len - 1];
+ assert(!isbranch(t));
+ tkey_t* key = t->leaf.key;
+ if (len)
+ *len = key->len;
+ return key->chars;
+}
+
+trie_val_t* trie_it_val(trie_it_t* it)
+{
+ assert(it && it->len);
+ node_t* t = it->stack[it->len - 1];
+ assert(!isbranch(t));
+ return &t->leaf.val;
+}
diff --git a/src/lib/trie.h b/src/lib/trie.h
new file mode 100644
index 0000000..3ce881c
--- /dev/null
+++ b/src/lib/trie.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017-2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifndef __dnsjit_contrib_trie_h
+#define __dnsjit_contrib_trie_h
+
+#include "lib/trie.hh"
+
+#ifndef likely
+/*! \brief Optimize for x to be true value. */
+#define likely(x) __builtin_expect((x), 1)
+#endif
+
+#ifndef unlikely
+/*! \brief Optimize for x to be false value. */
+#define unlikely(x) __builtin_expect((x), 0)
+#endif
+
+#define MIN(a, b) (((a) < (b)) ? (a) : (b)) /** Minimum of two numbers **/
+
+#endif
diff --git a/src/lib/trie.hh b/src/lib/trie.hh
new file mode 100644
index 0000000..60c8f8a
--- /dev/null
+++ b/src/lib/trie.hh
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017-2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Memory allocation function prototypes. */
+typedef void* (*knot_mm_alloc_t)(void* ctx, size_t len);
+typedef void (*knot_mm_free_t)(void* p);
+
+/*! \brief Memory allocation context. */
+typedef struct knot_mm {
+ void* ctx; /* \note Must be first */
+ knot_mm_alloc_t alloc;
+ knot_mm_free_t free;
+} knot_mm_t;
+
+/*!
+ * \brief Native API of QP-tries:
+ *
+ * - keys are uint8_t strings, not necessarily zero-terminated,
+ * the structure copies the contents of the passed keys
+ * - values are void* pointers, typically you get an ephemeral pointer to it
+ * - key lengths are limited by 2^32-1 ATM
+ */
+
+/*! \brief Element value. */
+typedef void* trie_val_t;
+
+/*! \brief Opaque structure holding a QP-trie. */
+typedef struct trie trie_t;
+
+/*! \brief Opaque type for holding a QP-trie iterator. */
+typedef struct trie_it trie_it_t;
+
+/*! \brief Create a trie instance. Pass NULL to use malloc+free. */
+trie_t* trie_create(knot_mm_t* mm);
+
+/*! \brief Free a trie instance. */
+void trie_free(trie_t* tbl);
+
+/*! \brief Clear a trie instance (make it empty). */
+void trie_clear(trie_t* tbl);
+
+/*! \brief Return the number of keys in the trie. */
+size_t trie_weight(const trie_t* tbl);
+
+/*! \brief Search the trie, returning NULL on failure. */
+trie_val_t* trie_get_try(trie_t* tbl, const uint8_t* key, uint32_t len);
+
+/*!
+ * \brief Return pointer to the minimum. Optionally with key and its length. */
+trie_val_t* trie_get_first(trie_t* tbl, uint8_t** key, uint32_t* len);
+
+/*! \brief Search the trie, inserting NULL trie_val_t on failure. */
+trie_val_t* trie_get_ins(trie_t* tbl, const uint8_t* key, uint32_t len);
+
+/*!
+ * \brief Search for less-or-equal element.
+ *
+ * \param tbl Trie.
+ * \param key Searched key.
+ * \param len Key length.
+ * \param val Must be valid; it will be set to NULL if not found or errored.
+ * \return KNOT_EOK for exact match, 1 for previous, KNOT_ENOENT for not-found,
+ * or KNOT_E*.
+ */
+int trie_get_leq(trie_t* tbl, const uint8_t* key, uint32_t len, trie_val_t** val);
+
+/*!
+ * \brief Apply a function to every trie_val_t, in order.
+ *
+ * \param d Parameter passed as the second argument to f().
+ * \return First nonzero from f() or zero (i.e. KNOT_EOK).
+ */
+int trie_apply(trie_t* tbl, int (*f)(trie_val_t*, void*), void* d);
+
+/*! \brief Create a new iterator pointing to the first element (if any). */
+trie_it_t* trie_it_begin(trie_t* tbl);
+
+/*!
+ * \brief Advance the iterator to the next element.
+ *
+ * Iteration is in ascending lexicographical order.
+ * In particular, the empty string would be considered as the very first.
+ *
+ * \note You may not use this function if the trie's key-set has been modified
+ * during the lifetime of the iterator (modifying values only is OK).
+ */
+void trie_it_next(trie_it_t* it);
+
+/*! \brief Test if the iterator has gone past the last element. */
+bool trie_it_finished(trie_it_t* it);
+
+/*! \brief Free any resources of the iterator. It's OK to call it on NULL. */
+void trie_it_free(trie_it_t* it);
+
+/*!
+ * \brief Return pointer to the key of the current element.
+ *
+ * \note The optional len is uint32_t internally but size_t is better for our usage,
+ * as it is without an additional type conversion.
+ */
+const uint8_t* trie_it_key(trie_it_t* it, size_t* len);
+
+/*! \brief Return pointer to the value of the current element (writable). */
+trie_val_t* trie_it_val(trie_it_t* it);
diff --git a/src/lib/trie.lua b/src/lib/trie.lua
new file mode 100644
index 0000000..f7fee03
--- /dev/null
+++ b/src/lib/trie.lua
@@ -0,0 +1,172 @@
+-- Copyright (c) 2020, CZ.NIC, z.s.p.o.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.lib.trie
+-- Prefix-tree data structure which addresses values by strings or byte arrays
+-- .SS Binary-key trie with integer values
+-- local trie = require("dnsjit.lib.trie").new("uint64_t", true, 4)
+-- -- assume we have a bunch of dnsjit.core.object.ip packets to process
+-- for _, pkt in pairs(pkts) do
+-- local node = trie:get_ins(pkt.src)
+-- local value = node:get() -- new nodes' values are initialized to 0
+-- node:set(value + 1)
+-- end
+-- -- iterate over unique IPs and print number of packets received from each
+-- local iter = trie:iter()
+-- local node = iter:node()
+-- local p = require("dnsjit.lib.ip")
+-- while node ~= nil do
+-- local ip_bytes = node:key()
+-- local npkts = tonumber(node:get())
+-- print(ip.tostring(ip_bytes).." sent "..npkts.." packets")
+-- iter:next()
+-- node = iter:node()
+-- end
+-- .SS String-key trie with cdata values
+-- local trie = require("dnsjit.lib.trie").new("core_object_t*")
+-- local obj1 -- assume this contains cdata of type core_object_t*
+-- local node = trie:get_ins("obj1")
+-- node:set(obj1)
+-- node = trie:get_try("obj1")
+-- assert(node:get() == obj1)
+--
+-- Prefix-tree data structure that stores values indexed by strings or byte
+-- arrays, such as IP addresses.
+-- Values of size up to sizeof(size_t) can be stored directly, otherwise
+-- a pointer must be used.
+module(...,package.seeall)
+
+require("dnsjit.lib.trie_h")
+local ffi = require("ffi")
+local C = ffi.C
+local log = require("dnsjit.core.log")
+local module_log = log.new("lib.trie")
+local TrieNode = require("dnsjit.lib.trie.node")
+local TrieIter = require("dnsjit.lib.trie.iter")
+
+Trie = {}
+
+-- Create a new Trie that stores
+-- .I ctype
+-- values as data.
+-- By default, keys are handled as strings.
+-- To use trie with byte arrays, set
+-- .I binary
+-- to true.
+-- Optionally,
+-- .I keylen
+-- may be specified as a default keylen for binary keys.
+-- For string keys, their string length is used by default.
+function Trie.new(ctype, binary, keylen)
+ if ctype == nil then
+ module_log:fatal("missing value ctype")
+ end
+ if ffi.sizeof(ctype) > ffi.sizeof("void *") then
+ module_log:fatal("data type exceeds max size, use a pointer instead")
+ end
+ if keylen ~= nil and not binary then
+ module_log:warning("setting keylen has no effect for string-key tries")
+ end
+
+ local self = setmetatable({
+ obj = C.trie_create(nil),
+ _binary = binary,
+ _keylen = keylen,
+ _ctype = ctype,
+ _log = log.new("lib.trie", module_log),
+ }, { __index = Trie })
+
+ ffi.gc(self.obj, C.trie_free)
+
+ return self
+end
+
+function Trie:_get_keylen(key, keylen)
+ if keylen ~= nil then
+ if type(keylen) == "number" then
+ return keylen
+ else
+ self._log:fatal("keylen must be numeric")
+ end
+ end
+ if not self._binary then
+ if type(key) == "string" then
+ return string.len(key)
+ else
+ self._log:fatal("key must be string when using trie with non-binary keys")
+ end
+ end
+ if not self._keylen or type(self._keylen) ~= "number" then
+ self._log:fatal("default keylen not set or invalid")
+ end
+ return self._keylen
+end
+
+-- Return the Log object to control logging of this instance or module.
+function Trie:log()
+ if self == nil then
+ return module_log
+ end
+ return self._log
+end
+
+-- Clear the trie instance (make it empty).
+function Trie:clear()
+ C.trie_clear(self.obj)
+end
+
+-- Return the number of keys in the trie.
+function Trie:weight()
+ return tonumber(C.trie_weight(self.obj))
+end
+
+-- Search the trie and return nil of failure.
+function Trie:get_try(key, keylen)
+ keylen = self:_get_keylen(key, keylen)
+ local val = C.trie_get_try(self.obj, key, keylen)
+ if val == nil then return nil end
+ val = ffi.cast("trie_val_t *", val)
+ return TrieNode.new(self, val, key, keylen)
+end
+
+-- Search the trie and insert an empty node (with value set to 0) on failure.
+function Trie:get_ins(key, keylen)
+ keylen = self:_get_keylen(key, keylen)
+ local val = C.trie_get_ins(self.obj, key, keylen)
+ val = ffi.cast("trie_val_t *", val)
+ return TrieNode.new(self, val, key, keylen)
+end
+
+-- Return the first node (minimum).
+function Trie:get_first()
+ local key_ptr = ffi.new("uint8_t *[1]")
+ local keylen_ptr = ffi.new("uint32_t[1]")
+ local val = C.trie_get_first(self.obj, key_ptr, keylen_ptr)
+ local keylen = tonumber(keylen_ptr[0])
+ key = key_ptr[0]
+ return TrieNode.new(self, val, key, keylen)
+end
+
+-- Return a trie iterator.
+-- It is only valid as long as the key-set remains unchanged.
+function Trie:iter()
+ return TrieIter.new(self)
+end
+
+-- dnsjit.lib.trie.node (3), dnsjit.lib.trie.iter (3)
+return Trie
diff --git a/src/lib/trie/iter.lua b/src/lib/trie/iter.lua
new file mode 100644
index 0000000..520cc7b
--- /dev/null
+++ b/src/lib/trie/iter.lua
@@ -0,0 +1,93 @@
+-- Copyright (c) 2020, CZ.NIC, z.s.p.o.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.lib.trie.iter
+-- Iterator of the trie.
+-- .SS Iterate over all trie's key-value pairs
+-- local trie = require("dnsjit.lib.trie").new("uint64_t")
+-- local iter = trie:iter()
+-- local node = iter:node()
+-- while node ~= nil do
+-- local key = node:key()
+-- local value = tonumber(node:get())
+-- print(key..": "..value)
+-- iter:next()
+-- node = iter:node()
+-- end
+--
+-- Beware that iterator is only valid as long as the trie's key-set
+-- remains unchanged.
+module(...,package.seeall)
+
+require("dnsjit.lib.trie_h")
+local ffi = require("ffi")
+local C = ffi.C
+local log = require("dnsjit.core.log")
+local module_log = log.new("lib.trie.iter")
+local TrieNode = require("dnsjit.lib.trie.node")
+
+TrieIter = {}
+
+-- Create a new iterator pointing to the first element (if any).
+function TrieIter.new(trie)
+ local self = setmetatable({
+ obj = C.trie_it_begin(trie.obj),
+ _trie = trie,
+ _log = log.new("lib.trie.iter", module_log),
+ }, { __index = TrieIter })
+
+ ffi.gc(self.obj, C.trie_it_free)
+
+ return self
+end
+
+-- Return the Log object to control logging of this instance or module.
+function TrieIter:log()
+ if self == nil then
+ return module_log
+ end
+ return self._log
+end
+
+-- Return the node pointer to by the iterator.
+-- Returns nil when iterator has gone past the last element.
+function TrieIter:node()
+ if C.trie_it_finished(self.obj) then
+ return nil
+ end
+
+ local keylen_ptr = ffi.new("size_t[1]")
+ local key = C.trie_it_key(self.obj, keylen_ptr)
+ local keylen = tonumber(keylen_ptr[0])
+
+ local val = C.trie_it_val(self.obj)
+ return TrieNode.new(self._trie, val, key, keylen)
+end
+
+-- Advance the iterator to the next element.
+--
+-- Iteration is in ascending lexicographical order.
+-- Empty string would be considered as the very first.
+--
+-- You may not use this function if the trie's key-set has been modified during the lifetime of the iterator (modifying only values is OK).
+function TrieIter:next()
+ C.trie_it_next(self.obj)
+end
+
+-- dnsjit.lib.trie (3), dnsjit.lib.trie.node (3)
+return TrieIter
diff --git a/src/lib/trie/node.lua b/src/lib/trie/node.lua
new file mode 100644
index 0000000..7fdc39d
--- /dev/null
+++ b/src/lib/trie/node.lua
@@ -0,0 +1,84 @@
+-- Copyright (c) 2020, CZ.NIC, z.s.p.o.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.lib.trie.node
+-- Node of trie, which contains the value and key.
+-- .SS Set a node's value.
+-- node:set(42)
+-- .SS Get a node's key and value.
+-- local key = node:key()
+-- local val = node:get()
+module(...,package.seeall)
+
+require("dnsjit.lib.trie_h")
+local ffi = require("ffi")
+local C = ffi.C
+local log = require("dnsjit.core.log")
+local module_log = log.new("lib.trie.node")
+
+TrieNode = {}
+
+-- Create a new node object.
+function TrieNode.new(trie, val, key, keylen)
+ local self = setmetatable({
+ _key = key,
+ _keylen = keylen,
+ _val = val,
+ _trie = trie,
+ _log = log.new("lib.trie.node", module_log),
+ }, { __index = TrieNode })
+
+ return self
+end
+
+-- Return key and keylen of this node.
+-- Key is string or byte array if the trie's
+-- .I
+-- binary
+-- setting is set to true.
+function TrieNode:key()
+ if self._trie._binary then
+ local key = ffi.new("uint8_t[?]", self._keylen)
+ ffi.copy(key, self._key, self._keylen)
+ return key, self._keylen
+ else
+ return ffi.string(self._key, self._keylen), self._keylen
+ end
+end
+
+-- Return the Log object to control logging of this instance or module.
+function TrieNode:log()
+ if self == nil then
+ return module_log
+ end
+ return self._log
+end
+
+-- Get the value of this node.
+function TrieNode:get()
+ return ffi.cast(self._trie._ctype, self._val[0])
+end
+
+-- Set the value of this node.
+function TrieNode:set(value)
+ value = ffi.cast('void *', value)
+ self._val[0] = value
+end
+
+-- dnsjit.lib.trie (3)
+return TrieNode
diff --git a/src/output.lua b/src/output.lua
new file mode 100644
index 0000000..26663a2
--- /dev/null
+++ b/src/output.lua
@@ -0,0 +1,34 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.output
+-- Output modules for dnsjit
+--
+-- Output modules used to display DNS message, export to various formats or
+-- replay them against other targets.
+module(...,package.seeall)
+
+-- dnsjit.output.dnscli (3),
+-- dnsjit.output.dnssim (3),
+-- dnsjit.output.null (3),
+-- dnsjit.output.pcap (3),
+-- dnsjit.output.respdiff (3),
+-- dnsjit.output.tcpcli (3),
+-- dnsjit.output.tlscli (3),
+-- dnsjit.output.udpcli (3)
+return
diff --git a/src/output/dnscli.c b/src/output/dnscli.c
new file mode 100644
index 0000000..f7c5b5e
--- /dev/null
+++ b/src/output/dnscli.c
@@ -0,0 +1,888 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "output/dnscli.h"
+#include "core/assert.h"
+#include "core/object/dns.h"
+#include "core/object/payload.h"
+#include "core/object/udp.h"
+#include "core/object/tcp.h"
+
+#include <netdb.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#ifdef HAVE_ENDIAN_H
+#include <endian.h>
+#else
+#ifdef HAVE_SYS_ENDIAN_H
+#include <sys/endian.h>
+#else
+#ifdef HAVE_MACHINE_ENDIAN_H
+#include <machine/endian.h>
+#endif
+#endif
+#endif
+#ifdef HAVE_BYTESWAP_H
+#include <byteswap.h>
+#endif
+#ifndef bswap_16
+#ifndef bswap16
+#define bswap_16(x) swap16(x)
+#define bswap_32(x) swap32(x)
+#define bswap_64(x) swap64(x)
+#else
+#define bswap_16(x) bswap16(x)
+#define bswap_32(x) bswap32(x)
+#define bswap_64(x) bswap64(x)
+#endif
+#endif
+
+static inline uint16_t _need16(const void* ptr)
+{
+ uint16_t v;
+ memcpy(&v, ptr, sizeof(v));
+ return be16toh(v);
+}
+
+static core_log_t _log = LOG_T_INIT("output.dnscli");
+static output_dnscli_t _defaults = {
+ LOG_T_INIT_OBJ("output.dnscli"),
+ OUTPUT_DNSCLI_MODE_NONE,
+ 0, 0, 0, -1, 0, 0,
+ { 0, 0, 0 }, 0,
+ { 0 }, 0,
+ { 0 }, CORE_OBJECT_PAYLOAD_INIT(0), 0, 0, 0, 0, 0,
+ { 0, 0 },
+ 0, 0
+};
+
+core_log_t* output_dnscli_log()
+{
+ return &_log;
+}
+
+void output_dnscli_init(output_dnscli_t* self, output_dnscli_mode_t mode)
+{
+ mlassert_self();
+
+ *self = _defaults;
+ self->mode = mode;
+ self->pkt.payload = self->recvbuf;
+
+ switch (mode & OUTPUT_DNSCLI_MODE_MODES) {
+ case OUTPUT_DNSCLI_MODE_UDP:
+ case OUTPUT_DNSCLI_MODE_TCP:
+ break;
+ case OUTPUT_DNSCLI_MODE_TLS: {
+ int err;
+ if ((err = gnutls_certificate_allocate_credentials(&self->cred)) != GNUTLS_E_SUCCESS) {
+ lfatal("gnutls_certificate_allocate_credentials() error: %s", gnutls_strerror(err));
+ } else if ((err = gnutls_init(&self->session, GNUTLS_CLIENT | ((mode & OUTPUT_DNSCLI_MODE_NONBLOCKING) ? GNUTLS_NONBLOCK : 0))) != GNUTLS_E_SUCCESS) {
+ lfatal("gnutls_init() error: %s", gnutls_strerror(err));
+ } else if ((err = gnutls_set_default_priority(self->session)) != GNUTLS_E_SUCCESS) {
+ lfatal("gnutls_set_default_priority() error: %s", gnutls_strerror(err));
+ } else if ((err = gnutls_credentials_set(self->session, GNUTLS_CRD_CERTIFICATE, self->cred)) != GNUTLS_E_SUCCESS) {
+ lfatal("gnutls_credentials_set() error: %s", gnutls_strerror(err));
+ }
+
+ gnutls_handshake_set_timeout(self->session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
+ break;
+ }
+ default:
+ lfatal("Invalid mode %x", mode);
+ }
+}
+
+void output_dnscli_destroy(output_dnscli_t* self)
+{
+ mlassert_self();
+
+ if (self->fd > -1) {
+ switch (self->mode & OUTPUT_DNSCLI_MODE_MODES) {
+ case OUTPUT_DNSCLI_MODE_UDP:
+ case OUTPUT_DNSCLI_MODE_TCP:
+ shutdown(self->fd, SHUT_RDWR);
+ close(self->fd);
+ break;
+ case OUTPUT_DNSCLI_MODE_TLS:
+ if (self->session) {
+ gnutls_bye(self->session, GNUTLS_SHUT_RDWR);
+ gnutls_deinit(self->session);
+ }
+ shutdown(self->fd, SHUT_RDWR);
+ close(self->fd);
+ if (self->cred) {
+ gnutls_certificate_free_credentials(self->cred);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+int output_dnscli_connect(output_dnscli_t* self, const char* host, const char* port)
+{
+ struct addrinfo* addr;
+ int err;
+ mlassert_self();
+ lassert(host, "host is nil");
+ lassert(port, "port is nil");
+
+ if (self->fd > -1) {
+ lfatal("already connected");
+ }
+
+ if ((err = getaddrinfo(host, port, 0, &addr))) {
+ lcritical("getaddrinfo(%s, %s) error %s", host, port, gai_strerror(err));
+ return -1;
+ }
+ if (!addr) {
+ lcritical("getaddrinfo failed, no address returned");
+ return -1;
+ }
+
+ switch (self->mode & OUTPUT_DNSCLI_MODE_MODES) {
+ case OUTPUT_DNSCLI_MODE_UDP:
+ memcpy(&self->addr, addr->ai_addr, addr->ai_addrlen);
+ self->addr_len = addr->ai_addrlen;
+ freeaddrinfo(addr);
+
+ if ((self->fd = socket(((struct sockaddr*)&self->addr)->sa_family, SOCK_DGRAM, 0)) < 0) {
+ lcritical("socket() error %s", core_log_errstr(errno));
+ return -2;
+ }
+ break;
+ case OUTPUT_DNSCLI_MODE_TCP:
+ case OUTPUT_DNSCLI_MODE_TLS:
+ if ((self->fd = socket(addr->ai_addr->sa_family, SOCK_STREAM, 0)) < 0) {
+ lcritical("socket() error %s", core_log_errstr(errno));
+ freeaddrinfo(addr);
+ return -2;
+ }
+
+ if (connect(self->fd, addr->ai_addr, addr->ai_addrlen)) {
+ lcritical("connect() error %s", core_log_errstr(errno));
+ freeaddrinfo(addr);
+ return -2;
+ }
+
+ freeaddrinfo(addr);
+ break;
+ default:
+ break;
+ }
+
+ switch (self->mode & OUTPUT_DNSCLI_MODE_MODES) {
+ case OUTPUT_DNSCLI_MODE_UDP:
+ case OUTPUT_DNSCLI_MODE_TCP:
+ if (self->mode & OUTPUT_DNSCLI_MODE_NONBLOCKING) {
+ int flags;
+
+ if ((flags = fcntl(self->fd, F_GETFL)) == -1) {
+ lcritical("fcntl(FL_GETFL) error %s", core_log_errstr(errno));
+ return -3;
+ }
+
+ if (fcntl(self->fd, F_SETFL, flags | O_NONBLOCK)) {
+ lcritical("fcntl(FL_SETFL, %x) error %s", flags, core_log_errstr(errno));
+ return -3;
+ }
+ self->nonblocking = 1;
+ }
+ if (self->timeout.sec > 0 || self->timeout.nsec > 0) {
+ self->poll.fd = self->fd;
+ self->poll_timeout = (self->timeout.sec * 1e3) + (self->timeout.nsec / 1e6); //NOSONAR
+ if (!self->poll_timeout) {
+ self->poll_timeout = 1;
+ }
+ }
+ break;
+ case OUTPUT_DNSCLI_MODE_TLS: {
+ unsigned int ms;
+ gnutls_transport_set_int(self->session, self->fd);
+ ms = (self->timeout.sec * 1000) + (self->timeout.nsec / 1000000);
+ if (!ms && self->timeout.nsec) {
+ ms = 1;
+ }
+ gnutls_record_set_timeout(self->session, ms);
+
+ /* Establish TLS */
+ do {
+ err = gnutls_handshake(self->session);
+ } while (err < 0 && gnutls_error_is_fatal(err) == 0);
+ if (err == GNUTLS_E_PREMATURE_TERMINATION) {
+ lcritical("gnutls_handshake() error: %s", gnutls_strerror(err));
+ return -3;
+ } else if (err < 0) {
+ lcritical("gnutls_handshake() failed: %s (%d)\n", gnutls_strerror(err), err);
+ return -3;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ self->conn_ok = 1;
+ return 0;
+}
+
+inline ssize_t _send_udp(output_dnscli_t* self, const uint8_t* payload, size_t len, size_t sent)
+{
+ ssize_t n;
+
+ if (self->poll_timeout) {
+ self->poll.events = POLLOUT;
+ n = poll(&self->poll, 1, self->poll_timeout);
+ if (n != 1 || !(self->poll.revents & POLLOUT)) {
+ if (!n) {
+ self->timeouts++;
+ return -1;
+ }
+ self->errs++;
+ return -2;
+ }
+ }
+ n = sendto(self->fd, payload + sent, len - sent, 0, (struct sockaddr*)&self->addr, self->addr_len);
+ if (n > -1) {
+ return n;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ return -1;
+ default:
+ break;
+ }
+ return -2;
+}
+
+static void _receive_udp(output_dnscli_t* self, const core_object_t* obj)
+{
+ const uint8_t* payload;
+ size_t len, sent = 0;
+ ssize_t n;
+ mlassert_self();
+
+ switch (obj->obj_type) {
+ case CORE_OBJECT_DNS:
+ payload = ((core_object_dns_t*)obj)->payload;
+ len = ((core_object_dns_t*)obj)->len;
+
+ if (((core_object_dns_t*)obj)->includes_dnslen) {
+ if (len < 2) {
+ return;
+ }
+ payload += 2;
+ len -= 2;
+ }
+ break;
+ case CORE_OBJECT_PAYLOAD:
+ payload = ((core_object_payload_t*)obj)->payload;
+ len = ((core_object_payload_t*)obj)->len;
+ break;
+ default:
+ return;
+ }
+
+ for (;;) {
+ n = _send_udp(self, payload, len, sent);
+ if (n > -1) {
+ sent += n;
+ if (sent < len) {
+ continue;
+ }
+ self->pkts++;
+ return;
+ }
+ if (n == -1) {
+ if (self->nonblocking) {
+ // TODO: warn?
+ return;
+ }
+ continue;
+ }
+ break;
+ }
+ self->errs++;
+}
+
+inline ssize_t _send_tcp(output_dnscli_t* self, const uint8_t* payload, size_t len, size_t sent)
+{
+ ssize_t n;
+
+ if (self->poll_timeout) {
+ self->poll.events = POLLOUT;
+ n = poll(&self->poll, 1, self->poll_timeout);
+ if (n != 1 || !(self->poll.revents & POLLOUT)) {
+ if (!n) {
+ self->timeouts++;
+ return -1;
+ }
+ self->errs++;
+ return -2;
+ }
+ }
+ n = sendto(self->fd, payload + sent, len - sent, 0, 0, 0);
+ if (n > -1) {
+ return n;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ return -1;
+ default:
+ break;
+ }
+ return -2;
+}
+
+static void _receive_tcp(output_dnscli_t* self, const core_object_t* obj)
+{
+ const uint8_t* payload;
+ size_t len, sent = 0;
+ ssize_t n;
+ mlassert_self();
+
+ switch (obj->obj_type) {
+ case CORE_OBJECT_DNS:
+ if (!((core_object_dns_t*)obj)->includes_dnslen) {
+ uint16_t dnslen = htons(((core_object_dns_t*)obj)->len);
+ payload = (const uint8_t*)&dnslen;
+ len = sizeof(dnslen);
+
+ for (;;) {
+ n = _send_tcp(self, payload, len, sent);
+ if (n > -1) {
+ sent += n;
+ if (sent < len) {
+ continue;
+ }
+ break;
+ }
+ if (n == -1) {
+ if (self->nonblocking) {
+ // TODO: warn?
+ return;
+ }
+ continue;
+ }
+ self->errs++;
+ return;
+ }
+ sent = 0;
+ }
+ payload = ((core_object_dns_t*)obj)->payload;
+ len = ((core_object_dns_t*)obj)->len;
+ break;
+ case CORE_OBJECT_PAYLOAD:
+ payload = ((core_object_payload_t*)obj)->payload;
+ len = ((core_object_payload_t*)obj)->len;
+ break;
+ default:
+ return;
+ }
+
+ for (;;) {
+ n = _send_tcp(self, payload, len, sent);
+ if (n > -1) {
+ sent += n;
+ if (sent < len) {
+ continue;
+ }
+ self->pkts++;
+ return;
+ }
+ if (n == -1) {
+ if (self->nonblocking) {
+ // TODO: warn?
+ return;
+ }
+ continue;
+ }
+ break;
+ }
+ self->errs++;
+}
+
+inline ssize_t _send_tls(output_dnscli_t* self, const uint8_t* payload, size_t len, size_t sent)
+{
+ ssize_t n;
+
+ n = gnutls_record_send(self->session, payload + sent, len - sent);
+ if (n > -1) {
+ return n;
+ }
+ switch (n) {
+ case GNUTLS_E_AGAIN:
+ case GNUTLS_E_TIMEDOUT:
+ case GNUTLS_E_INTERRUPTED:
+ return -1;
+ default:
+ break;
+ }
+ return -2;
+}
+
+static void _receive_tls(output_dnscli_t* self, const core_object_t* obj)
+{
+ const uint8_t* payload;
+ size_t len, sent = 0;
+ ssize_t n;
+ mlassert_self();
+
+ switch (obj->obj_type) {
+ case CORE_OBJECT_DNS:
+ if (!((core_object_dns_t*)obj)->includes_dnslen) {
+ uint16_t dnslen = htons(((core_object_dns_t*)obj)->len);
+ payload = (const uint8_t*)&dnslen;
+ len = sizeof(dnslen);
+
+ for (;;) {
+ n = _send_tls(self, payload, len, sent);
+ if (n > -1) {
+ sent += n;
+ if (sent < len) {
+ continue;
+ }
+ break;
+ }
+ if (n == -1) {
+ if (self->nonblocking) {
+ // TODO: warn?
+ return;
+ }
+ continue;
+ }
+ self->errs++;
+ return;
+ }
+ sent = 0;
+ }
+ payload = ((core_object_dns_t*)obj)->payload;
+ len = ((core_object_dns_t*)obj)->len;
+ break;
+ case CORE_OBJECT_PAYLOAD:
+ payload = ((core_object_payload_t*)obj)->payload;
+ len = ((core_object_payload_t*)obj)->len;
+ break;
+ default:
+ return;
+ }
+
+ for (;;) {
+ n = _send_tls(self, payload, len, sent);
+ if (n > -1) {
+ sent += n;
+ if (sent < len) {
+ continue;
+ }
+ self->pkts++;
+ return;
+ }
+ if (n == -1) {
+ if (self->nonblocking) {
+ // TODO: warn?
+ return;
+ }
+ continue;
+ }
+ break;
+ }
+ self->errs++;
+}
+
+luajit_ssize_t output_dnscli_send(output_dnscli_t* self, const core_object_t* obj, size_t sent)
+{
+ const uint8_t* payload;
+ size_t len;
+ uint16_t dnslen;
+ mlassert_self();
+
+ switch (self->mode & OUTPUT_DNSCLI_MODE_MODES) {
+ case OUTPUT_DNSCLI_MODE_UDP:
+ switch (obj->obj_type) {
+ case CORE_OBJECT_DNS:
+ payload = ((core_object_dns_t*)obj)->payload;
+ len = ((core_object_dns_t*)obj)->len;
+
+ if (((core_object_dns_t*)obj)->includes_dnslen) {
+ if (len < 2) {
+ return -2;
+ }
+ payload += 2;
+ len -= 2;
+ }
+ break;
+ case CORE_OBJECT_PAYLOAD:
+ payload = ((core_object_payload_t*)obj)->payload;
+ len = ((core_object_payload_t*)obj)->len;
+ break;
+ default:
+ return -2;
+ }
+
+ return _send_udp(self, payload, len, sent);
+
+ case OUTPUT_DNSCLI_MODE_TCP:
+ switch (obj->obj_type) {
+ case CORE_OBJECT_DNS:
+ if (!((core_object_dns_t*)obj)->includes_dnslen) {
+ if (sent < sizeof(dnslen)) {
+ dnslen = htons(((core_object_dns_t*)obj)->len);
+ payload = (const uint8_t*)&dnslen;
+ len = sizeof(dnslen);
+
+ return _send_tcp(self, payload, len, sent);
+ }
+ sent -= sizeof(dnslen);
+ }
+ payload = ((core_object_dns_t*)obj)->payload;
+ len = ((core_object_dns_t*)obj)->len;
+ break;
+ case CORE_OBJECT_PAYLOAD:
+ payload = ((core_object_payload_t*)obj)->payload;
+ len = ((core_object_payload_t*)obj)->len;
+ break;
+ default:
+ return -2;
+ }
+
+ return _send_tcp(self, payload, len, sent);
+
+ case OUTPUT_DNSCLI_MODE_TLS:
+ switch (obj->obj_type) {
+ case CORE_OBJECT_DNS:
+ if (!((core_object_dns_t*)obj)->includes_dnslen) {
+ if (sent < sizeof(dnslen)) {
+ dnslen = htons(((core_object_dns_t*)obj)->len);
+ payload = (const uint8_t*)&dnslen;
+ len = sizeof(dnslen);
+
+ return _send_tls(self, payload, len, sent);
+ }
+ sent -= sizeof(dnslen);
+ }
+ payload = ((core_object_dns_t*)obj)->payload;
+ len = ((core_object_dns_t*)obj)->len;
+ break;
+ case CORE_OBJECT_PAYLOAD:
+ payload = ((core_object_payload_t*)obj)->payload;
+ len = ((core_object_payload_t*)obj)->len;
+ break;
+ default:
+ return -2;
+ }
+
+ return _send_tls(self, payload, len, sent);
+
+ default:
+ break;
+ }
+
+ return -2;
+}
+
+core_receiver_t output_dnscli_receiver(output_dnscli_t* self)
+{
+ mlassert_self();
+
+ if (!self->conn_ok) {
+ lfatal("not connected");
+ }
+
+ switch (self->mode & OUTPUT_DNSCLI_MODE_MODES) {
+ case OUTPUT_DNSCLI_MODE_UDP:
+ return (core_receiver_t)_receive_udp;
+ case OUTPUT_DNSCLI_MODE_TCP:
+ return (core_receiver_t)_receive_tcp;
+ case OUTPUT_DNSCLI_MODE_TLS:
+ return (core_receiver_t)_receive_tls;
+ default:
+ break;
+ }
+
+ lfatal("internal error");
+ return 0;
+}
+
+static const core_object_t* _produce_udp(output_dnscli_t* self)
+{
+ ssize_t n;
+ mlassert_self();
+
+ for (;;) {
+ if (self->poll_timeout) {
+ self->poll.events = POLLIN;
+ n = poll(&self->poll, 1, self->poll_timeout);
+ if (n != 1 || !(self->poll.revents & POLLIN)) {
+ if (!n) {
+ self->timeouts++;
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ } else {
+ self->errs++;
+ }
+ return 0;
+ }
+ }
+ n = recvfrom(self->fd, self->recvbuf, sizeof(self->recvbuf), 0, 0, 0);
+ if (n > -1) {
+ break;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ if (self->nonblocking) {
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ }
+ continue;
+ default:
+ break;
+ }
+ self->errs++;
+ break;
+ }
+
+ if (n < 1) {
+ return 0;
+ }
+
+ self->pkts_recv++;
+ self->pkt.len = n;
+ return (core_object_t*)&self->pkt;
+}
+
+static const core_object_t* _produce_tcp(output_dnscli_t* self)
+{
+ ssize_t n;
+ mlassert_self();
+
+ if (self->have_pkt) {
+ if (self->recv > self->dnslen + sizeof(self->dnslen)) {
+ self->recv -= self->dnslen + sizeof(self->dnslen);
+ memmove(self->recvbuf, self->recvbuf + self->dnslen + sizeof(self->dnslen), self->recv);
+ } else {
+ self->recv = 0;
+ }
+ self->have_pkt = 0;
+ self->have_dnslen = 0;
+ }
+
+ if (!self->have_dnslen && self->recv >= sizeof(self->dnslen)) {
+ self->dnslen = _need16(self->recvbuf);
+ self->have_dnslen = 1;
+ }
+ if (self->have_dnslen && self->recv >= self->dnslen + sizeof(self->dnslen)) {
+ self->pkts_recv++;
+ self->pkt.len = self->dnslen + sizeof(self->dnslen);
+ self->have_pkt = 1;
+ return (core_object_t*)&self->pkt;
+ }
+
+ for (;;) {
+ if (self->poll_timeout) {
+ self->poll.events = POLLIN;
+ n = poll(&self->poll, 1, self->poll_timeout);
+ if (n != 1 || !(self->poll.revents & POLLIN)) {
+ if (!n) {
+ self->timeouts++;
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ } else {
+ self->errs++;
+ }
+ return 0;
+ }
+ }
+ n = recvfrom(self->fd, self->recvbuf + self->recv, sizeof(self->recvbuf) - self->recv, 0, 0, 0);
+ if (n > 0) {
+ self->recv += n;
+
+ if (!self->have_dnslen && self->recv >= sizeof(self->dnslen)) {
+ self->dnslen = _need16(self->recvbuf);
+ self->have_dnslen = 1;
+ }
+ if (self->have_dnslen && self->recv >= self->dnslen + sizeof(self->dnslen)) {
+ self->pkts_recv++;
+ self->pkt.len = self->dnslen + sizeof(self->dnslen);
+ self->have_pkt = 1;
+ return (core_object_t*)&self->pkt;
+ }
+
+ if (self->nonblocking) {
+ break;
+ }
+ continue;
+ }
+ if (!n) {
+ break;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ if (self->nonblocking) {
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ }
+ continue;
+ default:
+ break;
+ }
+ self->errs++;
+ break;
+ }
+
+ if (n < 1) {
+ return 0;
+ }
+
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+}
+
+static const core_object_t* _produce_tls(output_dnscli_t* self)
+{
+ ssize_t n;
+ mlassert_self();
+
+ if (self->have_pkt) {
+ if (self->recv > self->dnslen + sizeof(self->dnslen)) {
+ self->recv -= self->dnslen + sizeof(self->dnslen);
+ memmove(self->recvbuf, self->recvbuf + self->dnslen + sizeof(self->dnslen), self->recv);
+ } else {
+ self->recv = 0;
+ }
+ self->have_pkt = 0;
+ self->have_dnslen = 0;
+ }
+
+ if (!self->have_dnslen && self->recv >= sizeof(self->dnslen)) {
+ self->dnslen = _need16(self->recvbuf);
+ self->have_dnslen = 1;
+ }
+ if (self->have_dnslen && self->recv >= self->dnslen + sizeof(self->dnslen)) {
+ self->pkts_recv++;
+ self->pkt.len = self->dnslen + sizeof(self->dnslen);
+ self->have_pkt = 1;
+ return (core_object_t*)&self->pkt;
+ }
+
+ for (;;) {
+ if (!gnutls_record_check_pending(self->session) && self->poll_timeout) {
+ self->poll.events = POLLIN;
+ n = poll(&self->poll, 1, self->poll_timeout);
+ if (n != 1 || !(self->poll.revents & POLLIN)) {
+ if (!n) {
+ self->timeouts++;
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ } else {
+ self->errs++;
+ }
+ return 0;
+ }
+ }
+ n = gnutls_record_recv(self->session, self->recvbuf + self->recv, sizeof(self->recvbuf) - self->recv);
+ if (n > 0) {
+ self->recv += n;
+
+ if (!self->have_dnslen && self->recv >= sizeof(self->dnslen)) {
+ self->dnslen = _need16(self->recvbuf);
+ self->have_dnslen = 1;
+ }
+ if (self->have_dnslen && self->recv >= self->dnslen + sizeof(self->dnslen)) {
+ self->pkts_recv++;
+ self->pkt.len = self->dnslen + sizeof(self->dnslen);
+ self->have_pkt = 1;
+ return (core_object_t*)&self->pkt;
+ }
+
+ if (self->nonblocking) {
+ break;
+ }
+ continue;
+ }
+ if (!n) {
+ break;
+ }
+ switch (n) {
+ case GNUTLS_E_AGAIN:
+ case GNUTLS_E_TIMEDOUT:
+ case GNUTLS_E_INTERRUPTED:
+ if (self->nonblocking) {
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ }
+ continue;
+ default:
+ break;
+ }
+ self->errs++;
+ break;
+ }
+
+ if (n < 1) {
+ return 0;
+ }
+
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+}
+
+core_producer_t output_dnscli_producer(output_dnscli_t* self)
+{
+ mlassert_self();
+
+ if (!self->conn_ok) {
+ lfatal("not connected");
+ }
+
+ switch (self->mode & OUTPUT_DNSCLI_MODE_MODES) {
+ case OUTPUT_DNSCLI_MODE_UDP:
+ return (core_producer_t)_produce_udp;
+ case OUTPUT_DNSCLI_MODE_TCP:
+ return (core_producer_t)_produce_tcp;
+ case OUTPUT_DNSCLI_MODE_TLS:
+ return (core_producer_t)_produce_tls;
+ default:
+ break;
+ }
+
+ lfatal("internal error");
+ return 0;
+}
diff --git a/src/output/dnscli.h b/src/output/dnscli.h
new file mode 100644
index 0000000..27f2207
--- /dev/null
+++ b/src/output/dnscli.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/log.h"
+#include "core/receiver.h"
+#include "core/producer.h"
+#include "core/object/payload.h"
+#include "core/timespec.h"
+#include "core/compat.h"
+
+#ifndef __dnsjit_output_dnscli_h
+#define __dnsjit_output_dnscli_h
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <gnutls/gnutls.h>
+#include <poll.h>
+
+#include "output/dnscli.hh"
+
+#endif
diff --git a/src/output/dnscli.hh b/src/output/dnscli.hh
new file mode 100644
index 0000000..4126423
--- /dev/null
+++ b/src/output/dnscli.hh
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.compat_h")
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+//lua:require("dnsjit.core.producer_h")
+//lua:require("dnsjit.core.object.payload_h")
+//lua:require("dnsjit.core.timespec_h")
+
+typedef enum output_dnscli_mode {
+ OUTPUT_DNSCLI_MODE_NONE = 0,
+ OUTPUT_DNSCLI_MODE_OPTIONS = 0xf,
+ OUTPUT_DNSCLI_MODE_NONBLOCKING = 0x1,
+ OUTPUT_DNSCLI_MODE_MODES = 0xf0,
+ OUTPUT_DNSCLI_MODE_UDP = 0x10,
+ OUTPUT_DNSCLI_MODE_TCP = 0x20,
+ OUTPUT_DNSCLI_MODE_TLS = 0x30,
+} output_dnscli_mode_t;
+
+typedef struct output_dnscli {
+ core_log_t _log;
+
+ output_dnscli_mode_t mode;
+
+ size_t pkts, errs, timeouts;
+ int fd, nonblocking, conn_ok;
+
+ struct pollfd poll;
+ int poll_timeout;
+
+ struct sockaddr_storage addr;
+ size_t addr_len;
+
+ uint8_t recvbuf[(64 * 1024) + 2];
+ core_object_payload_t pkt;
+ uint16_t dnslen;
+ uint8_t have_dnslen, have_pkt;
+ size_t recv, pkts_recv;
+
+ core_timespec_t timeout;
+
+ gnutls_session_t session;
+ gnutls_certificate_credentials_t cred;
+} output_dnscli_t;
+
+core_log_t* output_dnscli_log();
+
+void output_dnscli_init(output_dnscli_t* self, output_dnscli_mode_t mode);
+void output_dnscli_destroy(output_dnscli_t* self);
+int output_dnscli_connect(output_dnscli_t* self, const char* host, const char* port);
+luajit_ssize_t output_dnscli_send(output_dnscli_t* self, const core_object_t* obj, size_t sent);
+
+core_receiver_t output_dnscli_receiver(output_dnscli_t* self);
+core_producer_t output_dnscli_producer(output_dnscli_t* self);
diff --git a/src/output/dnscli.lua b/src/output/dnscli.lua
new file mode 100644
index 0000000..bfdaf36
--- /dev/null
+++ b/src/output/dnscli.lua
@@ -0,0 +1,187 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.output.dnscli
+-- DNS aware UDP/TCP/TLS client
+-- local dnscli = require("dnsjit.output.dnscli")
+-- .SS UDP Receiver Chain
+-- local output = dnscli.new(dnscli.UDP)
+-- output:connect("127.0.0.1", "53")
+-- input:receiver(output)
+-- .SS TCP Nonblocking
+-- local output = dnscli.new(dnscli.TCP + dnscli.NONBLOCKING)
+-- output:send(object)
+--
+-- The DNS client can a
+-- .I core.object.dns
+-- or a
+-- .I core.object.payload
+-- object via the receiver interface or using
+-- .I send()
+-- and send it as DNS query after which it can receive the response by using
+-- the producer interface.
+-- If the object being sent is a
+-- .I core.object.dns
+-- then it will look at
+-- .I includes_dnslen
+-- attribute and depending on the protocol it will disregard, include or send
+-- the DNS length as an extra packet.
+-- If the object being sent is a
+-- .I core.object.payload
+-- then no special handling will be done and it will be sent as is.
+-- When receiving responses the producer interface will generate
+-- .I core.object.payload
+-- objects which may include the DNS length depending on the protocol used and
+-- must be handled by the caller.
+-- .SS MODES
+-- These transport modes and options are available when creating a new Dnscli
+-- output.
+-- .TP
+-- UDP
+-- Create an output using UDP.
+-- .TP
+-- TCP
+-- Create an output using TCP.
+-- .TP
+-- TLS
+-- Create an output using TCP and encrypt it with TLS.
+-- .TP
+-- NONBLOCKING
+-- Make the client nonblocking, see
+-- .I send()
+-- and
+-- .IR produce() .
+module(...,package.seeall)
+
+require("dnsjit.output.dnscli_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "output_dnscli_t"
+local output_dnscli_t = ffi.typeof(t_name)
+local Dnscli = {
+ NONBLOCKING = 0x1,
+ UDP = 0x10,
+ TCP = 0x20,
+ TLS = 0x30,
+}
+
+-- Create a new Dnscli output.
+function Dnscli.new(mode)
+ local self = {
+ obj = output_dnscli_t(),
+ }
+ C.output_dnscli_init(self.obj, mode)
+ ffi.gc(self.obj, C.output_dnscli_destroy)
+ return setmetatable(self, { __index = Dnscli })
+end
+
+-- Set or return the timeout used for sending and reciving, must be used before
+-- .IR connect() .
+function Dnscli:timeout(seconds, nanoseconds)
+ if seconds == nil and nanoseconds == nil then
+ return self.obj.timeout
+ end
+ if nanoseconds == nil then
+ nanoseconds = 0
+ end
+ self.obj.timeout.sec = seconds
+ self.obj.timeout.nsec = nanoseconds
+end
+
+-- Connect to the
+-- .I host
+-- and
+-- .I port
+-- and return 0 if successful.
+function Dnscli:connect(host, port)
+ return C.output_dnscli_connect(self.obj, host, port)
+end
+
+-- Return if nonblocking mode is on (true) or off (false).
+function Dnscli:nonblocking()
+ if self.obj.nonblocking == 1 then
+ return true
+ end
+ return false
+end
+
+-- Send an object and optionally continue sending after
+-- .I sent
+-- bytes.
+-- Unlike the receive interface this function lets you know if the sending was
+-- successful or not which might be needed on nonblocking connections.
+-- Returns -2 on error, -1 if interrupted, timed out or unable to send due to
+-- nonblocking, or the number of bytes sent.
+-- .B Note
+-- the counters for sent, received, errors and timeouts are not affected by
+-- this function.
+function Dnscli:send(object, sent)
+ if sent == nil then
+ sent = 0
+ end
+ return C.output_dnscli_send(self.obj, object, sent)
+end
+
+-- Return the C functions and context for receiving objects, these objects
+-- will be sent.
+function Dnscli:receive()
+ return C.output_dnscli_receiver(self.obj), self.obj
+end
+
+-- Return the C functions and context for producing objects, these objects
+-- are received.
+-- If nonblocking mode is enabled the producer will return a payload object
+-- with length zero if there was nothing to receive.
+-- If nonblocking mode is disabled the producer will wait for data and if
+-- timed out (see
+-- .IR timeout )
+-- it will return a payload object with length zero.
+-- The producer returns nil on error.
+function Dnscli:produce()
+ return C.output_dnscli_producer(self.obj), self.obj
+end
+
+-- Return the number of "packets" sent, actually the number of completely sent
+-- payloads.
+function Dnscli:packets()
+ return tonumber(self.obj.pkts)
+end
+
+-- Return the number of "packets" received, actually the number of successful
+-- calls to
+-- .IR recvfrom (2)
+-- that returned data.
+function Dnscli:received()
+ return tonumber(self.obj.pkts_recv)
+end
+
+-- Return the number of errors when sending or receiving.
+function Dnscli:errors()
+ return tonumber(self.obj.errs)
+end
+
+-- Return the number of timeouts when sending or receiving.
+function Dnscli:timeouts()
+ return tonumber(self.obj.timeouts)
+end
+
+-- core.object.dns (3),
+-- core.object.payload (3),
+-- core.timespec (3)
+return Dnscli
diff --git a/src/output/dnssim.c b/src/output/dnssim.c
new file mode 100644
index 0000000..acd0a05
--- /dev/null
+++ b/src/output/dnssim.c
@@ -0,0 +1,502 @@
+/*
+ * Copyright (c) 2019-2020, CZ.NIC, z.s.p.o.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "output/dnssim.h"
+#include "output/dnssim/internal.h"
+#include "output/dnssim/ll.h"
+#include "core/assert.h"
+#include "core/object/ip.h"
+#include "core/object/ip6.h"
+
+#include <gnutls/gnutls.h>
+#include <string.h>
+
+static core_log_t _log = LOG_T_INIT("output.dnssim");
+static output_dnssim_t _defaults = { LOG_T_INIT_OBJ("output.dnssim") };
+
+static uint64_t _now_ms()
+{
+#if HAVE_CLOCK_NANOSLEEP
+ struct timespec ts;
+ uint64_t now_ms;
+ if (clock_gettime(CLOCK_REALTIME, &ts)) {
+ mlfatal("clock_gettime()");
+ }
+ now_ms = ts.tv_sec * 1000;
+ now_ms += ts.tv_nsec / 1000000;
+ return now_ms;
+#else
+ mlfatal("clock_gettime() not available");
+ return 0;
+#endif
+}
+
+core_log_t* output_dnssim_log()
+{
+ return &_log;
+}
+
+output_dnssim_t* output_dnssim_new(size_t max_clients)
+{
+ output_dnssim_t* self;
+ int ret, i;
+
+ mlfatal_oom(self = calloc(1, sizeof(_output_dnssim_t)));
+ *self = _defaults;
+ self->handshake_timeout_ms = 5000;
+ self->idle_timeout_ms = 10000;
+ output_dnssim_timeout_ms(self, 2000);
+
+ _self->source = NULL;
+ _self->transport = OUTPUT_DNSSIM_TRANSPORT_UDP_ONLY;
+ _self->h2_zero_out_msgid = false;
+
+ self->max_clients = max_clients;
+ lfatal_oom(_self->client_arr = calloc(max_clients, sizeof(_output_dnssim_client_t)));
+
+ for (i = 0; i < max_clients; ++i) {
+ _self->client_arr[i].dnssim = self;
+ }
+
+ ret = gnutls_certificate_allocate_credentials(&_self->tls_cred);
+ if (ret < 0)
+ lfatal("failed to allocated TLS credentials (%s)", gnutls_strerror(ret));
+
+ ret = uv_loop_init(&_self->loop);
+ if (ret < 0)
+ lfatal("failed to initialize uv_loop (%s)", uv_strerror(ret));
+ ldebug("initialized uv_loop");
+
+ return self;
+}
+
+void output_dnssim_free(output_dnssim_t* self)
+{
+ mlassert_self();
+ int ret, i;
+ _output_dnssim_source_t* source;
+ _output_dnssim_source_t* first = _self->source;
+ output_dnssim_stats_t* stats_prev;
+
+ free(self->stats_sum->latency);
+ free(self->stats_sum);
+ do {
+ stats_prev = self->stats_current->prev;
+ free(self->stats_current->latency);
+ free(self->stats_current);
+ self->stats_current = stats_prev;
+ } while (self->stats_current != NULL);
+
+ if (_self->source != NULL) {
+ // free cilcular linked list
+ do {
+ source = _self->source->next;
+ free(_self->source);
+ _self->source = source;
+ } while (_self->source != first);
+ }
+
+ for (i = 0; i < self->max_clients; ++i) {
+ if (_self->client_arr[i].tls_ticket.size != 0) {
+ gnutls_free(_self->client_arr[i].tls_ticket.data);
+ }
+ }
+ free(_self->client_arr);
+
+ ret = uv_loop_close(&_self->loop);
+ if (ret < 0) {
+ lcritical("failed to close uv_loop (%s)", uv_strerror(ret));
+ } else {
+ ldebug("closed uv_loop");
+ }
+
+ gnutls_certificate_free_credentials(_self->tls_cred);
+ if (_self->tls_priority != NULL) {
+ gnutls_priority_deinit(*_self->tls_priority);
+ free(_self->tls_priority);
+ }
+
+ free(self);
+}
+
+void output_dnssim_log_name(output_dnssim_t* self, const char* name)
+{
+ mlassert_self();
+ lassert(name, "name is nil");
+
+ strncpy(self->_log.name, name, sizeof(self->_log.name) - 1);
+ self->_log.name[sizeof(self->_log.name) - 1] = 0;
+ self->_log.is_obj = false;
+}
+
+static uint32_t _extract_client(const core_object_t* obj)
+{
+ uint32_t client;
+ uint8_t* ip;
+
+ switch (obj->obj_type) {
+ case CORE_OBJECT_IP:
+ ip = ((core_object_ip_t*)obj)->dst;
+ break;
+ case CORE_OBJECT_IP6:
+ ip = ((core_object_ip6_t*)obj)->dst;
+ break;
+ default:
+ return -1;
+ }
+
+ memcpy(&client, ip, sizeof(client));
+ return client;
+}
+
+static void _receive(output_dnssim_t* self, const core_object_t* obj)
+{
+ mlassert_self();
+ core_object_t* current = (core_object_t*)obj;
+ core_object_payload_t* payload;
+ uint32_t client;
+
+ self->processed++;
+
+ /* get payload from packet */
+ for (;;) {
+ if (current->obj_type == CORE_OBJECT_PAYLOAD) {
+ payload = (core_object_payload_t*)current;
+ break;
+ }
+ if (current->obj_prev == NULL) {
+ self->discarded++;
+ lwarning("packet discarded (missing payload object)");
+ return;
+ }
+ current = (core_object_t*)current->obj_prev;
+ }
+
+ /* extract client information from IP/IP6 layer */
+ for (;;) {
+ if (current->obj_type == CORE_OBJECT_IP || current->obj_type == CORE_OBJECT_IP6) {
+ client = _extract_client(current);
+ break;
+ }
+ if (current->obj_prev == NULL) {
+ self->discarded++;
+ lwarning("packet discarded (missing ip/ip6 object)");
+ return;
+ }
+ current = (core_object_t*)current->obj_prev;
+ }
+
+ if (self->free_after_use) {
+ /* free all objects except payload */
+ current = (core_object_t*)obj;
+ core_object_t* parent = current;
+ while (current != NULL) {
+ parent = current;
+ current = (core_object_t*)current->obj_prev;
+ if (parent->obj_type != CORE_OBJECT_PAYLOAD) {
+ core_object_free(parent);
+ }
+ }
+ }
+
+ if (_self->h2_zero_out_msgid) {
+ lassert(_self->transport == OUTPUT_DNSSIM_TRANSPORT_HTTPS2, "must use HTTP/2 to zero-out msgid");
+ if (payload->len < 2) {
+ self->discarded++;
+ lwarning("packet discarded (payload len < 2)");
+ return;
+ }
+ uint8_t* data = (uint8_t*)payload->payload;
+ data[0] = 0x00;
+ data[1] = 0x00;
+ }
+
+ if (client >= self->max_clients) {
+ self->discarded++;
+ lwarning("packet discarded (client exceeded max_clients)");
+ return;
+ }
+
+ ldebug("client(c): %d", client);
+ _output_dnssim_create_request(self, &_self->client_arr[client], payload);
+}
+
+core_receiver_t output_dnssim_receiver()
+{
+ return (core_receiver_t)_receive;
+}
+
+void output_dnssim_set_transport(output_dnssim_t* self, output_dnssim_transport_t tr)
+{
+ mlassert_self();
+
+ switch (tr) {
+ case OUTPUT_DNSSIM_TRANSPORT_UDP_ONLY:
+ lnotice("transport set to UDP (no TCP fallback)");
+ break;
+ case OUTPUT_DNSSIM_TRANSPORT_TCP:
+ lnotice("transport set to TCP");
+ break;
+ case OUTPUT_DNSSIM_TRANSPORT_TLS:
+#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION
+ lnotice("transport set to TLS");
+#else
+ lfatal(DNSSIM_MIN_GNUTLS_ERRORMSG);
+#endif
+ break;
+ case OUTPUT_DNSSIM_TRANSPORT_HTTPS2:
+#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION
+ lnotice("transport set to HTTP/2 over TLS");
+ if (&_self->h2_uri_authority[0])
+ lnotice("set uri authority to: %s", _self->h2_uri_authority);
+#else
+ lfatal(DNSSIM_MIN_GNUTLS_ERRORMSG);
+#endif
+ break;
+ case OUTPUT_DNSSIM_TRANSPORT_UDP:
+ lfatal("UDP transport with TCP fallback is not supported yet.");
+ break;
+ default:
+ lfatal("unknown or unsupported transport");
+ break;
+ }
+
+ _self->transport = tr;
+}
+
+int output_dnssim_target(output_dnssim_t* self, const char* ip, uint16_t port)
+{
+ int ret;
+ mlassert_self();
+ lassert(ip, "ip is nil");
+ lassert(port, "port is nil");
+
+ ret = uv_ip6_addr(ip, port, (struct sockaddr_in6*)&_self->target);
+ if (ret != 0) {
+ ret = uv_ip4_addr(ip, port, (struct sockaddr_in*)&_self->target);
+ if (ret != 0) {
+ lfatal("failed to parse IPv4 or IPv6 from \"%s\"", ip);
+ } else {
+ ret = snprintf(_self->h2_uri_authority, _MAX_URI_LEN, "%s:%d", ip, port);
+ }
+ } else {
+ ret = snprintf(_self->h2_uri_authority, _MAX_URI_LEN, "[%s]:%d", ip, port);
+ }
+
+ if (ret > 0) {
+ if (_self->transport == OUTPUT_DNSSIM_TRANSPORT_HTTPS2)
+ lnotice("set uri authority to: %s", _self->h2_uri_authority);
+ } else {
+ _self->h2_uri_authority[0] = '\0';
+ if (_self->transport == OUTPUT_DNSSIM_TRANSPORT_HTTPS2)
+ lfatal("failed to set authority");
+ }
+
+ lnotice("set target to %s port %d", ip, port);
+ return 0;
+}
+
+int output_dnssim_bind(output_dnssim_t* self, const char* ip)
+{
+ int ret;
+ mlassert_self();
+ lassert(ip, "ip is nil");
+
+ _output_dnssim_source_t* source;
+ lfatal_oom(source = malloc(sizeof(_output_dnssim_source_t)));
+
+ ret = uv_ip6_addr(ip, 0, (struct sockaddr_in6*)&source->addr);
+ if (ret != 0) {
+ ret = uv_ip4_addr(ip, 0, (struct sockaddr_in*)&source->addr);
+ if (ret != 0) {
+ lfatal("failed to parse IPv4 or IPv6 from \"%s\"", ip);
+ }
+ }
+
+ if (_self->source == NULL) {
+ source->next = source;
+ _self->source = source;
+ } else {
+ source->next = _self->source->next;
+ _self->source->next = source;
+ }
+
+ lnotice("bind to source address %s", ip);
+ return 0;
+}
+
+int output_dnssim_tls_priority(output_dnssim_t* self, const char* priority)
+{
+ mlassert_self();
+ lassert(priority, "priority is nil");
+
+ if (_self->tls_priority != NULL) {
+ gnutls_priority_deinit(*_self->tls_priority);
+ free(_self->tls_priority);
+ }
+ lfatal_oom(_self->tls_priority = malloc(sizeof(gnutls_priority_t)));
+
+ int ret = gnutls_priority_init(_self->tls_priority, priority, NULL);
+ if (ret < 0) {
+ lfatal("failed to initialize TLS priority cache: %s", gnutls_strerror(ret));
+ } else {
+ lnotice("GnuTLS priority set: %s", priority);
+ }
+
+ return 0;
+}
+
+int output_dnssim_run_nowait(output_dnssim_t* self)
+{
+ mlassert_self();
+
+ return uv_run(&_self->loop, UV_RUN_NOWAIT);
+}
+
+void output_dnssim_timeout_ms(output_dnssim_t* self, uint64_t timeout_ms)
+{
+ mlassert_self();
+ lassert(timeout_ms > 0, "timeout must be greater than 0");
+
+ if (self->stats_sum != NULL) {
+ free(self->stats_sum->latency);
+ free(self->stats_sum);
+ self->stats_sum = 0;
+ }
+ if (self->stats_current != NULL) {
+ output_dnssim_stats_t* stats_prev;
+ do {
+ stats_prev = self->stats_current->prev;
+ free(self->stats_current->latency);
+ free(self->stats_current);
+ self->stats_current = stats_prev;
+ } while (self->stats_current != NULL);
+ }
+
+ self->timeout_ms = timeout_ms;
+
+ lfatal_oom(self->stats_sum = calloc(1, sizeof(output_dnssim_stats_t)));
+ lfatal_oom(self->stats_sum->latency = calloc(self->timeout_ms + 1, sizeof(uint64_t)));
+
+ lfatal_oom(self->stats_current = calloc(1, sizeof(output_dnssim_stats_t)));
+ lfatal_oom(self->stats_current->latency = calloc(self->timeout_ms + 1, sizeof(uint64_t)));
+
+ self->stats_first = self->stats_current;
+}
+
+void output_dnssim_h2_uri_path(output_dnssim_t* self, const char* uri_path)
+{
+ mlassert_self();
+ lassert(uri_path, "uri_path is nil");
+ lassert(strlen(uri_path) < _MAX_URI_LEN, "uri_path too long");
+
+ strncpy(_self->h2_uri_path, uri_path, _MAX_URI_LEN - 1);
+ _self->h2_uri_path[_MAX_URI_LEN - 1] = 0;
+ lnotice("http2: set uri path to: %s", _self->h2_uri_path);
+}
+
+void output_dnssim_h2_method(output_dnssim_t* self, const char* method)
+{
+ mlassert_self();
+ lassert(method, "method is nil");
+
+ if (strcmp("GET", method) == 0) {
+ _self->h2_method = OUTPUT_DNSSIM_H2_GET;
+ } else if (strcmp("POST", method) == 0) {
+ _self->h2_method = OUTPUT_DNSSIM_H2_POST;
+ } else {
+ lfatal("http2: unsupported method: \"%s\"", method);
+ }
+
+ lnotice("http2: set method to %s", method);
+}
+
+void output_dnssim_h2_zero_out_msgid(output_dnssim_t* self, bool zero_out_msgid)
+{
+ mlassert_self();
+
+ if (zero_out_msgid) {
+ lassert(_self->transport == OUTPUT_DNSSIM_TRANSPORT_HTTPS2, "transport must be set to HTTP/2 to set zero_out_msgid");
+ _self->h2_zero_out_msgid = zero_out_msgid;
+ }
+}
+
+static void _on_stats_timer_tick(uv_timer_t* handle)
+{
+ uint64_t now_ms = _now_ms();
+ output_dnssim_t* self;
+ mlassert(handle, "handle is nil");
+ self = (output_dnssim_t*)handle->data;
+ mlassert_self();
+ lassert(self->stats_sum, "stats_sum is nil");
+ lassert(self->stats_current, "stats_current is nil");
+
+ lnotice("total processed:%10ld; answers:%10ld; discarded:%10ld; ongoing:%10ld",
+ self->processed, self->stats_sum->answers, self->discarded, self->ongoing);
+
+ output_dnssim_stats_t* stats_next;
+ lfatal_oom(stats_next = calloc(1, sizeof(output_dnssim_stats_t)));
+ lfatal_oom(stats_next->latency = calloc(self->timeout_ms + 1, sizeof(uint64_t)));
+
+ self->stats_current->until_ms = now_ms;
+ stats_next->since_ms = now_ms;
+ stats_next->conn_active = self->stats_current->conn_active;
+
+ stats_next->ongoing = self->ongoing;
+ stats_next->prev = self->stats_current;
+ self->stats_current->next = stats_next;
+ self->stats_current = stats_next;
+}
+
+void output_dnssim_stats_collect(output_dnssim_t* self, uint64_t interval_ms)
+{
+ uint64_t now_ms = _now_ms();
+ mlassert_self();
+ lassert(self->stats_sum, "stats_sum is nil");
+ lassert(self->stats_current, "stats_current is nil");
+
+ if (self->stats_interval_ms != 0) {
+ lfatal("statistics collection has already started!");
+ }
+ self->stats_interval_ms = interval_ms;
+
+ self->stats_sum->since_ms = now_ms;
+ self->stats_current->since_ms = now_ms;
+
+ _self->stats_timer.data = (void*)self;
+ uv_timer_init(&_self->loop, &_self->stats_timer);
+ uv_timer_start(&_self->stats_timer, _on_stats_timer_tick, interval_ms, interval_ms);
+}
+
+void output_dnssim_stats_finish(output_dnssim_t* self)
+{
+ uint64_t now_ms = _now_ms();
+ mlassert_self();
+ lassert(self->stats_sum, "stats_sum is nil");
+ lassert(self->stats_current, "stats_current is nil");
+
+ self->stats_sum->until_ms = now_ms;
+ self->stats_current->until_ms = now_ms;
+
+ uv_timer_stop(&_self->stats_timer);
+ uv_close((uv_handle_t*)&_self->stats_timer, NULL);
+}
diff --git a/src/output/dnssim.h b/src/output/dnssim.h
new file mode 100644
index 0000000..f843000
--- /dev/null
+++ b/src/output/dnssim.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2019, CZ.NIC, z.s.p.o.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/log.h"
+#include "core/receiver.h"
+
+#ifndef __dnsjit_output_dnssim_h
+#define __dnsjit_output_dnssim_h
+
+#include <stdbool.h>
+
+#include "output/dnssim.hh"
+
+#endif
diff --git a/src/output/dnssim.hh b/src/output/dnssim.hh
new file mode 100644
index 0000000..a17125d
--- /dev/null
+++ b/src/output/dnssim.hh
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2018-2020, CZ.NIC, z.s.p.o.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+
+typedef enum output_dnssim_transport {
+ OUTPUT_DNSSIM_TRANSPORT_UDP_ONLY,
+ OUTPUT_DNSSIM_TRANSPORT_UDP,
+ OUTPUT_DNSSIM_TRANSPORT_TCP,
+ OUTPUT_DNSSIM_TRANSPORT_TLS,
+ OUTPUT_DNSSIM_TRANSPORT_HTTPS2
+} output_dnssim_transport_t;
+
+typedef enum output_dnssim_h2_method {
+ OUTPUT_DNSSIM_H2_GET,
+ OUTPUT_DNSSIM_H2_POST
+} output_dnssim_h2_method_t;
+
+typedef struct output_dnssim_stats output_dnssim_stats_t;
+struct output_dnssim_stats {
+ output_dnssim_stats_t* prev;
+ output_dnssim_stats_t* next;
+
+ uint64_t* latency;
+
+ uint64_t since_ms;
+ uint64_t until_ms;
+
+ uint64_t requests;
+ uint64_t ongoing;
+ uint64_t answers;
+
+ /* Number of connections that are open at the end of the stats interval. */
+ uint64_t conn_active;
+
+ /* Number of connection handshake attempts during the stats interval. */
+ uint64_t conn_handshakes;
+
+ /* Number of connection that have been resumed with TLS session resumption. */
+ uint64_t conn_resumed;
+
+ /* Number of timed out connection handshakes during the stats interval. */
+ uint64_t conn_handshakes_failed;
+
+ uint64_t rcode_noerror;
+ uint64_t rcode_formerr;
+ uint64_t rcode_servfail;
+ uint64_t rcode_nxdomain;
+ uint64_t rcode_notimp;
+ uint64_t rcode_refused;
+ uint64_t rcode_yxdomain;
+ uint64_t rcode_yxrrset;
+ uint64_t rcode_nxrrset;
+ uint64_t rcode_notauth;
+ uint64_t rcode_notzone;
+ uint64_t rcode_badvers;
+ uint64_t rcode_badkey;
+ uint64_t rcode_badtime;
+ uint64_t rcode_badmode;
+ uint64_t rcode_badname;
+ uint64_t rcode_badalg;
+ uint64_t rcode_badtrunc;
+ uint64_t rcode_badcookie;
+ uint64_t rcode_other;
+};
+
+typedef struct output_dnssim {
+ core_log_t _log;
+
+ uint64_t processed;
+ uint64_t discarded;
+ uint64_t ongoing;
+
+ output_dnssim_stats_t* stats_sum;
+ output_dnssim_stats_t* stats_current;
+ output_dnssim_stats_t* stats_first;
+
+ size_t max_clients;
+ bool free_after_use;
+
+ uint64_t timeout_ms;
+ uint64_t idle_timeout_ms;
+ uint64_t handshake_timeout_ms;
+ uint64_t stats_interval_ms;
+} output_dnssim_t;
+
+core_log_t* output_dnssim_log();
+
+output_dnssim_t* output_dnssim_new(size_t max_clients);
+void output_dnssim_free(output_dnssim_t* self);
+
+void output_dnssim_log_name(output_dnssim_t* self, const char* name);
+void output_dnssim_set_transport(output_dnssim_t* self, output_dnssim_transport_t tr);
+int output_dnssim_target(output_dnssim_t* self, const char* ip, uint16_t port);
+int output_dnssim_bind(output_dnssim_t* self, const char* ip);
+int output_dnssim_tls_priority(output_dnssim_t* self, const char* priority);
+int output_dnssim_run_nowait(output_dnssim_t* self);
+void output_dnssim_timeout_ms(output_dnssim_t* self, uint64_t timeout_ms);
+void output_dnssim_h2_uri_path(output_dnssim_t* self, const char* uri_path);
+void output_dnssim_h2_method(output_dnssim_t* self, const char* method);
+void output_dnssim_h2_zero_out_msgid(output_dnssim_t* self, bool zero_out_msgid);
+void output_dnssim_stats_collect(output_dnssim_t* self, uint64_t interval_ms);
+void output_dnssim_stats_finish(output_dnssim_t* self);
+
+core_receiver_t output_dnssim_receiver();
diff --git a/src/output/dnssim.lua b/src/output/dnssim.lua
new file mode 100644
index 0000000..25193c4
--- /dev/null
+++ b/src/output/dnssim.lua
@@ -0,0 +1,433 @@
+-- Copyright (c) 2018-2021, CZ.NIC, z.s.p.o.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.output.dnssim
+-- Simulate independent DNS clients over various transports
+-- output = require("dnsjit.output.dnssim").new()
+-- .SS Usage
+-- output:udp()
+-- output:target("::1", 53)
+-- recv, rctx = output:receive()
+-- -- pass in objects using recv(rctx, obj)
+-- -- repeatedly call output:run_nowait() until it returns 0
+-- .SS DNS-over-TLS example configuration
+-- output:tls("NORMAL:-VERS-ALL:+VERS-TLS1.3") -- enforce TLS 1.3
+-- .SS DNS-over-HTTPS/2 example configuration
+-- output:https2({ method = "POST", uri_path = "/doh" })
+--
+-- Output module for simulating traffic from huge number of independent,
+-- individual DNS clients.
+-- Uses libuv for asynchronous communication.
+-- There may only be a single DnsSim in a thread.
+-- Use
+-- .I dnsjit.core.thread
+-- to have multiple DnsSim instances.
+-- .P
+-- With proper use of this component, it is possible to simulate hundreds of
+-- thousands of clients when using a high-performance server.
+-- This also applies for state-full transports.
+-- The complete set-up is quite complex and requires other components.
+-- See DNS Shotgun
+-- .RI ( https://gitlab.nic.cz/knot/shotgun )
+-- for dnsjit scripts ready for use for high-performance
+-- benchmarking.
+module(...,package.seeall)
+
+require("dnsjit.output.dnssim_h")
+local bit = require("bit")
+local object = require("dnsjit.core.objects")
+local ffi = require("ffi")
+local C = ffi.C
+
+local DnsSim = {}
+
+local _DNSSIM_VERSION = 20210129
+local _DNSSIM_JSON_VERSION = 20200527
+
+-- Create a new DnsSim output for up to max_clients.
+function DnsSim.new(max_clients)
+ local self = {
+ obj = C.output_dnssim_new(max_clients),
+ max_clients = max_clients,
+ }
+ ffi.gc(self.obj, C.output_dnssim_free)
+ return setmetatable(self, { __index = DnsSim })
+end
+
+local function _check_version(version, req_version)
+ if req_version == nil then
+ return version
+ end
+ local min_version = tonumber(req_version)
+ if min_version == nil then
+ C.output_dnssim_log():fatal("invalid version number: "..req_version)
+ return nil
+ end
+ if version >= min_version then
+ return version
+ end
+ return nil
+end
+
+-- Check that version of dnssim is at minimum the one passed as
+-- .B req_version
+-- and return the actual version number.
+-- Return nil if the condition is not met.
+--
+-- If no
+-- .B req_version
+-- is specified no check is done and only the version number is returned.
+function DnsSim.check_version(req_version)
+ return _check_version(_DNSSIM_VERSION, req_version)
+end
+
+-- Check that version of dnssim's JSON data format is at minimum the one passed as
+-- .B req_version
+-- and return the actual version number.
+-- Return nil if the condition is not met.
+--
+-- If no
+-- .B req_version
+-- is specified no check is done and only the version number is returned.
+function DnsSim.check_json_version(req_version)
+ return _check_version(_DNSSIM_JSON_VERSION, req_version)
+end
+
+-- Return the Log object to control logging of this instance or module.
+-- Optionally, set the instance's log name.
+-- Unique name should be used for each instance.
+function DnsSim:log(name)
+ if self == nil then
+ return C.output_dnssim_log()
+ end
+ if name ~= nil then
+ C.output_dnssim_log_name(self.obj, name)
+ end
+ return self.obj._log
+end
+
+-- Set the target IPv4/IPv6 address where queries will be sent to.
+function DnsSim:target(ip, port)
+ local nport = tonumber(port)
+ if nport == nil then
+ self.obj._log:fatal("invalid port: "..port)
+ return -1
+ end
+ if nport <= 0 or nport > 65535 then
+ self.obj._log:fatal("invalid port number: "..nport)
+ return -1
+ end
+ return C.output_dnssim_target(self.obj, ip, nport)
+end
+
+-- Specify source IPv4/IPv6 address for sending queries.
+-- Can be set multiple times.
+-- Addresses are selected round-robin when sending.
+function DnsSim:bind(ip)
+ return C.output_dnssim_bind(self.obj, ip)
+end
+
+-- Set the preferred transport to UDP.
+--
+-- When the optional argument
+-- .B tcp_fallback
+-- is set to true, individual queries are re-tried over TCP when TC bit is set in the answer.
+-- Defaults to
+-- .B false
+-- (aka only UDP is used).
+function DnsSim:udp(tcp_fallback)
+ if tcp_fallback == true then
+ C.output_dnssim_set_transport(self.obj, C.OUTPUT_DNSSIM_TRANSPORT_UDP)
+ else
+ C.output_dnssim_set_transport(self.obj, C.OUTPUT_DNSSIM_TRANSPORT_UDP_ONLY)
+ end
+end
+
+-- Set the transport to TCP.
+function DnsSim:tcp()
+ C.output_dnssim_set_transport(self.obj, C.OUTPUT_DNSSIM_TRANSPORT_TCP)
+end
+
+-- Set the transport to TLS.
+--
+-- The optional argument
+-- .B tls_priority
+-- is a GnuTLS priority string, which can be used to select TLS versions, cipher suites etc.
+-- For example:
+--
+-- .RB "- """ NORMAL:%NO_TICKETS """"
+-- will use defaults without TLS session resumption.
+--
+-- .RB "- """ SECURE128:-VERS-ALL:+VERS-TLS1.3 """"
+-- will use only TLS 1.3 with 128-bit secure ciphers.
+--
+-- Refer to:
+-- .I https://gnutls.org/manual/html_node/Priority-Strings.html
+function DnsSim:tls(tls_priority)
+ if tls_priority ~= nil then
+ C.output_dnssim_tls_priority(self.obj, tls_priority)
+ end
+ C.output_dnssim_set_transport(self.obj, C.OUTPUT_DNSSIM_TRANSPORT_TLS)
+end
+
+-- Set the transport to HTTP/2 over TLS.
+--
+-- .B http2_options
+-- is a lua table which supports the following keys:
+--
+-- .B method:
+-- .B GET
+-- (default)
+-- or
+-- .B POST
+--
+-- .B uri_path:
+-- where queries will be sent.
+-- Defaults to
+-- .B /dns-query
+--
+-- .B zero_out_msgid:
+-- when
+-- .B true
+-- (default), query ID is always set to 0
+--
+-- See tls() method for
+-- .B tls_priority
+-- documentation.
+function DnsSim:https2(http2_options, tls_priority)
+ if tls_priority ~= nil then
+ C.output_dnssim_tls_priority(self.obj, tls_priority)
+ end
+
+ uri_path = "/dns-query"
+ zero_out_msgid = true
+ method = "GET"
+
+ if http2_options ~= nil then
+ if type(http2_options) ~= "table" then
+ self.obj._log:fatal("http2_options must be a table")
+ else
+ if http2_options["uri_path"] ~= nil then
+ uri_path = http2_options["uri_path"]
+ end
+ if http2_options["zero_out_msgid"] ~= nil and http2_options["zero_out_msgid"] ~= true then
+ zero_out_msgid = false
+ end
+ if http2_options["method"] ~= nil then
+ method = http2_options["method"]
+ end
+ end
+ end
+
+ C.output_dnssim_set_transport(self.obj, C.OUTPUT_DNSSIM_TRANSPORT_HTTPS2)
+ C.output_dnssim_h2_uri_path(self.obj, uri_path)
+ C.output_dnssim_h2_method(self.obj, method)
+ C.output_dnssim_h2_zero_out_msgid(self.obj, zero_out_msgid)
+end
+
+-- Set timeout for the individual requests in seconds (default 2s).
+--
+-- .BR Beware :
+-- increasing this value while the target resolver isn't very responsive
+-- (cold cache, heavy load) may degrade DnsSim's performance and skew
+-- the results.
+function DnsSim:timeout(seconds)
+ if seconds == nil then
+ seconds = 2
+ end
+ timeout_ms = math.floor(seconds * 1000)
+ C.output_dnssim_timeout_ms(self.obj, timeout_ms)
+end
+
+-- Set TCP connection idle timeout for connection reuse according to RFC7766,
+-- Section 6.2.3 (defaults to 10s).
+-- When set to zero, connections are closed immediately after there are no
+-- more pending queries.
+function DnsSim:idle_timeout(seconds)
+ if seconds == nil then
+ seconds = 10
+ end
+ self.obj.idle_timeout_ms = math.floor(seconds * 1000)
+end
+
+-- Set TCP connection handshake timeout (defaults to 5s).
+-- During heavy load, the server may no longer accept new connections.
+-- This parameter ensures such connection attempts are aborted after the
+-- timeout expires.
+function DnsSim:handshake_timeout(seconds)
+ if seconds == nil then
+ seconds = 5
+ end
+ self.obj.handshake_timeout_ms = math.floor(seconds * 1000)
+end
+
+-- Run the libuv loop once without blocking when there is no I/O.
+-- This should be called repeatedly until 0 is returned and no more data
+-- is expected to be received by DnsSim.
+function DnsSim:run_nowait()
+ return C.output_dnssim_run_nowait(self.obj)
+end
+
+-- Set this to true if DnsSim should free the memory of passed-in objects
+-- (useful when using
+-- .I dnsjit.filter.copy
+-- to pass objects from different thread).
+function DnsSim:free_after_use(free_after_use)
+ self.obj.free_after_use = free_after_use
+end
+
+-- Number of input packets discarded due to various reasons.
+-- To investigate causes, run with increased logging level.
+function DnsSim:discarded()
+ return tonumber(self.obj.discarded)
+end
+
+-- Number of valid requests (input packets) processed.
+function DnsSim:requests()
+ return tonumber(self.obj.stats_sum.requests)
+end
+
+-- Number of requests that received an answer
+function DnsSim:answers()
+ return tonumber(self.obj.stats_sum.answers)
+end
+
+-- Number of requests that received a NOERROR response
+function DnsSim:noerror()
+ return tonumber(self.obj.stats_sum.rcode_noerror)
+end
+
+-- Configure statistics to be collected every N seconds.
+function DnsSim:stats_collect(seconds)
+ if seconds == nil then
+ self.obj._log:fatal("number of seconds must be set for stats_collect()")
+ end
+ interval_ms = math.floor(seconds * 1000)
+ C.output_dnssim_stats_collect(self.obj, interval_ms)
+end
+
+-- Stop the collection of statistics.
+function DnsSim:stats_finish()
+ C.output_dnssim_stats_finish(self.obj)
+end
+
+-- Export the results to a JSON file.
+function DnsSim:export(filename)
+ local file = io.open(filename, "w")
+ if file == nil then
+ self.obj._log:fatal("export failed: no filename")
+ return
+ end
+
+ local function write_stats(file, stats)
+ file:write(
+ "{ ",
+ '"since_ms":', tonumber(stats.since_ms), ',',
+ '"until_ms":', tonumber(stats.until_ms), ',',
+ '"requests":', tonumber(stats.requests), ',',
+ '"ongoing":', tonumber(stats.ongoing), ',',
+ '"answers":', tonumber(stats.answers), ',',
+ '"conn_active":', tonumber(stats.conn_active), ',',
+ '"conn_handshakes":', tonumber(stats.conn_handshakes), ',',
+ '"conn_resumed":', tonumber(stats.conn_resumed), ',',
+ '"conn_handshakes_failed":', tonumber(stats.conn_handshakes_failed), ',',
+ '"rcode_noerror":', tonumber(stats.rcode_noerror), ',',
+ '"rcode_formerr":', tonumber(stats.rcode_formerr), ',',
+ '"rcode_servfail":', tonumber(stats.rcode_servfail), ',',
+ '"rcode_nxdomain":', tonumber(stats.rcode_nxdomain), ',',
+ '"rcode_notimp":', tonumber(stats.rcode_notimp), ',',
+ '"rcode_refused":', tonumber(stats.rcode_refused), ',',
+ '"rcode_yxdomain":', tonumber(stats.rcode_yxdomain), ',',
+ '"rcode_yxrrset":', tonumber(stats.rcode_yxrrset), ',',
+ '"rcode_nxrrset":', tonumber(stats.rcode_nxrrset), ',',
+ '"rcode_notauth":', tonumber(stats.rcode_notauth), ',',
+ '"rcode_notzone":', tonumber(stats.rcode_notzone), ',',
+ '"rcode_badvers":', tonumber(stats.rcode_badvers), ',',
+ '"rcode_badkey":', tonumber(stats.rcode_badkey), ',',
+ '"rcode_badtime":', tonumber(stats.rcode_badtime), ',',
+ '"rcode_badmode":', tonumber(stats.rcode_badmode), ',',
+ '"rcode_badname":', tonumber(stats.rcode_badname), ',',
+ '"rcode_badalg":', tonumber(stats.rcode_badalg), ',',
+ '"rcode_badtrunc":', tonumber(stats.rcode_badtrunc), ',',
+ '"rcode_badcookie":', tonumber(stats.rcode_badcookie), ',',
+ '"rcode_other":', tonumber(stats.rcode_other), ',',
+ '"latency":[')
+ file:write(tonumber(stats.latency[0]))
+ for i=1,tonumber(self.obj.timeout_ms) do
+ file:write(',', tonumber(stats.latency[i]))
+ end
+ file:write("]}")
+ end
+
+ file:write(
+ "{ ",
+ '"version":', _DNSSIM_JSON_VERSION, ',',
+ '"merged":false,',
+ '"stats_interval_ms":', tonumber(self.obj.stats_interval_ms), ',',
+ '"timeout_ms":', tonumber(self.obj.timeout_ms), ',',
+ '"idle_timeout_ms":', tonumber(self.obj.idle_timeout_ms), ',',
+ '"handshake_timeout_ms":', tonumber(self.obj.handshake_timeout_ms), ',',
+ '"discarded":', self:discarded(), ',',
+ '"stats_sum":')
+ write_stats(file, self.obj.stats_sum)
+ file:write(
+ ',',
+ '"stats_periodic":[')
+
+ local stats = self.obj.stats_first
+ write_stats(file, stats)
+
+ while (stats.next ~= nil) do
+ stats = stats.next
+ file:write(',')
+ write_stats(file, stats)
+ end
+
+ file:write(']}')
+ file:close()
+ self.obj._log:notice("results exported to "..filename)
+end
+
+-- Return the C function and context for receiving objects.
+-- Only
+-- .I dnsjit.filter.core.object.ip
+-- or
+-- .I dnsjit.filter.core.object.ip6
+-- objects are supported.
+-- The component expects a 32bit integer (in host order) ranging from 0
+-- to max_clients written to first 4 bytes of destination IP.
+-- See
+-- .IR dnsjit.filter.ipsplit .
+function DnsSim:receive()
+ local receive = C.output_dnssim_receiver()
+ return receive, self.obj
+end
+
+-- Deprecated: use udp() instead.
+--
+-- Set the transport to UDP (without any TCP fallback).
+function DnsSim:udp_only()
+ C.output_dnssim_set_transport(self.obj, C.OUTPUT_DNSSIM_TRANSPORT_UDP_ONLY)
+end
+
+-- dnsjit.filter.copy (3),
+-- dnsjit.filter.ipsplit (3),
+-- dnsjit.filter.core.object.ip (3),
+-- dnsjit.filter.core.object.ip6 (3),
+-- https://gitlab.nic.cz/knot/shotgun
+return DnsSim
diff --git a/src/output/dnssim/CHANGELOG.md b/src/output/dnssim/CHANGELOG.md
new file mode 100644
index 0000000..9cbfa55
--- /dev/null
+++ b/src/output/dnssim/CHANGELOG.md
@@ -0,0 +1,16 @@
+dnssim v20210129
+================
+
+- Added DNS-over-HTTPS support with https2()
+- Added IPv4 support
+- Abort operation on insufficient file descriptors
+- Match QUESTION section of received responses
+- Improvements in connection state handling
+- Deprecate udp_only() in favor of udp()
+- Allow setting logger name with log(name)
+- Added check_version() and check_json_version()
+
+dnssim v20200723
+================
+
+- First released dnssim version with UDP, TCP and DoT support
diff --git a/src/output/dnssim/common.c b/src/output/dnssim/common.c
new file mode 100644
index 0000000..e170aec
--- /dev/null
+++ b/src/output/dnssim/common.c
@@ -0,0 +1,384 @@
+/*
+ * Copyright (c) 2019-2020, CZ.NIC, z.s.p.o.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "output/dnssim.h"
+#include "output/dnssim/internal.h"
+#include "output/dnssim/ll.h"
+#include "core/assert.h"
+
+#include <string.h>
+
+#define MAX_LABELS 127
+
+static core_log_t _log = LOG_T_INIT("output.dnssim");
+
+static void _close_request(_output_dnssim_request_t* req);
+
+static void _on_request_timeout(uv_timer_t* handle)
+{
+ _close_request((_output_dnssim_request_t*)handle->data);
+}
+
+static ssize_t parse_qsection(core_object_dns_t* dns)
+{
+ core_object_dns_q_t q;
+ static core_object_dns_label_t labels[MAX_LABELS];
+ const uint8_t* start;
+ int i;
+ int ret;
+
+ if (!dns || !dns->have_qdcount)
+ return -1;
+
+ start = dns->at;
+
+ for (i = 0; i < dns->qdcount; i++) {
+ ret = core_object_dns_parse_q(dns, &q, labels, MAX_LABELS);
+ if (ret < 0)
+ return -1;
+ }
+
+ return (dns->at - start);
+}
+
+int _output_dnssim_answers_request(_output_dnssim_request_t* req, core_object_dns_t* response)
+{
+ const uint8_t* question;
+ ssize_t len;
+
+ if (!response->have_id || !response->have_qdcount)
+ return _ERR_MALFORMED;
+
+ if (req->dns_q->id != response->id)
+ return _ERR_MSGID;
+
+ if (req->dns_q->qdcount != response->qdcount)
+ return _ERR_QUESTION;
+
+ question = response->at;
+ len = parse_qsection(response);
+
+ if (req->question_len != len)
+ return _ERR_QUESTION;
+
+ if (memcmp(req->question, question, len) != 0)
+ return _ERR_QUESTION;
+
+ return 0;
+}
+
+void _output_dnssim_create_request(output_dnssim_t* self, _output_dnssim_client_t* client, core_object_payload_t* payload)
+{
+ int ret;
+ _output_dnssim_request_t* req;
+ mlassert_self();
+ lassert(client, "client is nil");
+ lassert(payload, "payload is nil");
+
+ lfatal_oom(req = calloc(1, sizeof(_output_dnssim_request_t)));
+ req->dnssim = self;
+ req->client = client;
+ req->payload = payload;
+ req->dns_q = core_object_dns_new();
+ req->dns_q->obj_prev = (core_object_t*)req->payload;
+ req->dnssim->ongoing++;
+ req->state = _OUTPUT_DNSSIM_REQ_ONGOING;
+ req->stats = self->stats_current;
+
+ ret = core_object_dns_parse_header(req->dns_q);
+ if (ret != 0) {
+ ldebug("discarded malformed dns query: couldn't parse header");
+ goto failure;
+ }
+
+ req->question = req->dns_q->at;
+ req->question_len = parse_qsection(req->dns_q);
+ if (req->question_len < 0) {
+ ldebug("discarded malformed dns query: invalid question");
+ goto failure;
+ }
+
+ req->dnssim->stats_sum->requests++;
+ req->stats->requests++;
+
+ switch (_self->transport) {
+ case OUTPUT_DNSSIM_TRANSPORT_UDP_ONLY:
+ case OUTPUT_DNSSIM_TRANSPORT_UDP:
+ ret = _output_dnssim_create_query_udp(self, req);
+ break;
+ case OUTPUT_DNSSIM_TRANSPORT_TCP:
+ ret = _output_dnssim_create_query_tcp(self, req);
+ break;
+ case OUTPUT_DNSSIM_TRANSPORT_TLS:
+#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION
+ ret = _output_dnssim_create_query_tls(self, req);
+#else
+ lfatal(DNSSIM_MIN_GNUTLS_ERRORMSG);
+#endif
+ break;
+ case OUTPUT_DNSSIM_TRANSPORT_HTTPS2:
+#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION
+ ret = _output_dnssim_create_query_https2(self, req);
+#else
+ lfatal(DNSSIM_MIN_GNUTLS_ERRORMSG);
+#endif
+ break;
+ default:
+ lfatal("unsupported dnssim transport");
+ break;
+ }
+ if (ret < 0) {
+ goto failure;
+ }
+
+ req->created_at = uv_now(&_self->loop);
+ req->ended_at = req->created_at + self->timeout_ms;
+ lfatal_oom(req->timer = malloc(sizeof(uv_timer_t)));
+ uv_timer_init(&_self->loop, req->timer);
+ req->timer->data = req;
+ uv_timer_start(req->timer, _on_request_timeout, self->timeout_ms, 0);
+
+ return;
+failure:
+ self->discarded++;
+ _close_request(req);
+ return;
+}
+
+/* Bind before connect to be able to send from different source IPs. */
+int _output_dnssim_bind_before_connect(output_dnssim_t* self, uv_handle_t* handle)
+{
+ mlassert_self();
+ lassert(handle, "handle is nil");
+
+ if (_self->source != NULL) {
+ struct sockaddr* addr = (struct sockaddr*)&_self->source->addr;
+ struct sockaddr* dest = (struct sockaddr*)&_self->target;
+ int ret = -1;
+ if (addr->sa_family != dest->sa_family) {
+ lfatal("failed to bind: source/desitnation address family mismatch");
+ }
+ switch (handle->type) {
+ case UV_UDP:
+ ret = uv_udp_bind((uv_udp_t*)handle, addr, 0);
+ break;
+ case UV_TCP:
+ ret = uv_tcp_bind((uv_tcp_t*)handle, addr, 0);
+ break;
+ default:
+ lfatal("failed to bind: unsupported handle type");
+ break;
+ }
+ if (ret < 0) {
+ /* This typically happens when we run out of file descriptors.
+ * Quit to prevent skewed results or unexpected behaviour. */
+ lfatal("failed to bind: %s", uv_strerror(ret));
+ return ret;
+ }
+ _self->source = _self->source->next;
+ }
+ return 0;
+}
+
+void _output_dnssim_maybe_free_request(_output_dnssim_request_t* req)
+{
+ mlassert(req, "req is nil");
+
+ if (req->qry == NULL && req->timer == NULL) {
+ if (req->dnssim->free_after_use) {
+ core_object_payload_free(req->payload);
+ }
+ core_object_dns_free(req->dns_q);
+ free(req);
+ }
+}
+
+static void _close_query(_output_dnssim_query_t* qry)
+{
+ mlassert(qry, "qry is nil");
+
+ switch (qry->transport) {
+ case OUTPUT_DNSSIM_TRANSPORT_UDP:
+ _output_dnssim_close_query_udp((_output_dnssim_query_udp_t*)qry);
+ break;
+ case OUTPUT_DNSSIM_TRANSPORT_TCP:
+ _output_dnssim_close_query_tcp((_output_dnssim_query_tcp_t*)qry);
+ break;
+ case OUTPUT_DNSSIM_TRANSPORT_TLS:
+#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION
+ _output_dnssim_close_query_tls((_output_dnssim_query_tcp_t*)qry);
+#else
+ mlfatal(DNSSIM_MIN_GNUTLS_ERRORMSG);
+#endif
+ break;
+ case OUTPUT_DNSSIM_TRANSPORT_HTTPS2:
+#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION
+ _output_dnssim_close_query_https2((_output_dnssim_query_tcp_t*)qry);
+#else
+ mlfatal(DNSSIM_MIN_GNUTLS_ERRORMSG);
+#endif
+ break;
+ default:
+ mlfatal("invalid query transport");
+ break;
+ }
+}
+
+static void _on_request_timer_closed(uv_handle_t* handle)
+{
+ _output_dnssim_request_t* req = (_output_dnssim_request_t*)handle->data;
+ mlassert(req, "req is nil");
+ free(handle);
+ req->timer = NULL;
+ _output_dnssim_maybe_free_request(req);
+}
+
+static void _close_request(_output_dnssim_request_t* req)
+{
+ if (req == NULL || req->state == _OUTPUT_DNSSIM_REQ_CLOSING)
+ return;
+ mlassert(req->state == _OUTPUT_DNSSIM_REQ_ONGOING, "request to be closed must be ongoing");
+ req->state = _OUTPUT_DNSSIM_REQ_CLOSING;
+ req->dnssim->ongoing--;
+
+ /* Calculate latency. */
+ uint64_t latency;
+ req->ended_at = uv_now(&((_output_dnssim_t*)req->dnssim)->loop);
+ latency = req->ended_at - req->created_at;
+ if (latency > req->dnssim->timeout_ms) {
+ req->ended_at = req->created_at + req->dnssim->timeout_ms;
+ latency = req->dnssim->timeout_ms;
+ }
+ req->stats->latency[latency]++;
+ req->dnssim->stats_sum->latency[latency]++;
+
+ if (req->timer != NULL) {
+ uv_timer_stop(req->timer);
+ uv_close((uv_handle_t*)req->timer, _on_request_timer_closed);
+ }
+
+ /* Finish any queries in flight. */
+ _output_dnssim_query_t* qry = req->qry;
+ if (qry != NULL)
+ _close_query(qry);
+
+ _output_dnssim_maybe_free_request(req);
+}
+
+void _output_dnssim_request_answered(_output_dnssim_request_t* req, core_object_dns_t* msg)
+{
+ mlassert(req, "req is nil");
+ mlassert(msg, "msg is nil");
+
+ req->dnssim->stats_sum->answers++;
+ req->stats->answers++;
+
+ switch (msg->rcode) {
+ case CORE_OBJECT_DNS_RCODE_NOERROR:
+ req->dnssim->stats_sum->rcode_noerror++;
+ req->stats->rcode_noerror++;
+ break;
+ case CORE_OBJECT_DNS_RCODE_FORMERR:
+ req->dnssim->stats_sum->rcode_formerr++;
+ req->stats->rcode_formerr++;
+ break;
+ case CORE_OBJECT_DNS_RCODE_SERVFAIL:
+ req->dnssim->stats_sum->rcode_servfail++;
+ req->stats->rcode_servfail++;
+ break;
+ case CORE_OBJECT_DNS_RCODE_NXDOMAIN:
+ req->dnssim->stats_sum->rcode_nxdomain++;
+ req->stats->rcode_nxdomain++;
+ break;
+ case CORE_OBJECT_DNS_RCODE_NOTIMP:
+ req->dnssim->stats_sum->rcode_notimp++;
+ req->stats->rcode_notimp++;
+ break;
+ case CORE_OBJECT_DNS_RCODE_REFUSED:
+ req->dnssim->stats_sum->rcode_refused++;
+ req->stats->rcode_refused++;
+ break;
+ case CORE_OBJECT_DNS_RCODE_YXDOMAIN:
+ req->dnssim->stats_sum->rcode_yxdomain++;
+ req->stats->rcode_yxdomain++;
+ break;
+ case CORE_OBJECT_DNS_RCODE_YXRRSET:
+ req->dnssim->stats_sum->rcode_yxrrset++;
+ req->stats->rcode_yxrrset++;
+ break;
+ case CORE_OBJECT_DNS_RCODE_NXRRSET:
+ req->dnssim->stats_sum->rcode_nxrrset++;
+ req->stats->rcode_nxrrset++;
+ break;
+ case CORE_OBJECT_DNS_RCODE_NOTAUTH:
+ req->dnssim->stats_sum->rcode_notauth++;
+ req->stats->rcode_notauth++;
+ break;
+ case CORE_OBJECT_DNS_RCODE_NOTZONE:
+ req->dnssim->stats_sum->rcode_notzone++;
+ req->stats->rcode_notzone++;
+ break;
+ case CORE_OBJECT_DNS_RCODE_BADVERS:
+ req->dnssim->stats_sum->rcode_badvers++;
+ req->stats->rcode_badvers++;
+ break;
+ case CORE_OBJECT_DNS_RCODE_BADKEY:
+ req->dnssim->stats_sum->rcode_badkey++;
+ req->stats->rcode_badkey++;
+ break;
+ case CORE_OBJECT_DNS_RCODE_BADTIME:
+ req->dnssim->stats_sum->rcode_badtime++;
+ req->stats->rcode_badtime++;
+ break;
+ case CORE_OBJECT_DNS_RCODE_BADMODE:
+ req->dnssim->stats_sum->rcode_badmode++;
+ req->stats->rcode_badmode++;
+ break;
+ case CORE_OBJECT_DNS_RCODE_BADNAME:
+ req->dnssim->stats_sum->rcode_badname++;
+ req->stats->rcode_badname++;
+ break;
+ case CORE_OBJECT_DNS_RCODE_BADALG:
+ req->dnssim->stats_sum->rcode_badalg++;
+ req->stats->rcode_badalg++;
+ break;
+ case CORE_OBJECT_DNS_RCODE_BADTRUNC:
+ req->dnssim->stats_sum->rcode_badtrunc++;
+ req->stats->rcode_badtrunc++;
+ break;
+ case CORE_OBJECT_DNS_RCODE_BADCOOKIE:
+ req->dnssim->stats_sum->rcode_badcookie++;
+ req->stats->rcode_badcookie++;
+ break;
+ default:
+ req->dnssim->stats_sum->rcode_other++;
+ req->stats->rcode_other++;
+ }
+
+ _close_request(req);
+}
+
+void _output_dnssim_on_uv_alloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf)
+{
+ mlfatal_oom(buf->base = malloc(suggested_size));
+ buf->len = suggested_size;
+}
diff --git a/src/output/dnssim/connection.c b/src/output/dnssim/connection.c
new file mode 100644
index 0000000..eeb1ce8
--- /dev/null
+++ b/src/output/dnssim/connection.c
@@ -0,0 +1,471 @@
+/*
+ * Copyright (c) 2020, CZ.NIC, z.s.p.o.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "output/dnssim.h"
+#include "output/dnssim/internal.h"
+#include "output/dnssim/ll.h"
+#include "core/assert.h"
+
+#include <string.h>
+
+static core_log_t _log = LOG_T_INIT("output.dnssim");
+
+static bool _conn_is_connecting(_output_dnssim_connection_t* conn)
+{
+ return (conn->state >= _OUTPUT_DNSSIM_CONN_TCP_HANDSHAKE && conn->state <= _OUTPUT_DNSSIM_CONN_ACTIVE);
+}
+
+void _output_dnssim_conn_maybe_free(_output_dnssim_connection_t* conn)
+{
+ mlassert(conn, "conn can't be nil");
+ mlassert(conn->client, "conn must belong to a client");
+ if (conn->handle == NULL && conn->handshake_timer == NULL && conn->idle_timer == NULL) {
+ _ll_try_remove(conn->client->conn, conn);
+ if (conn->tls != NULL) {
+ free(conn->tls);
+ conn->tls = NULL;
+ }
+ if (conn->http2 != NULL) {
+ free(conn->http2);
+ conn->http2 = NULL;
+ }
+ free(conn);
+ }
+}
+
+static void _on_handshake_timer_closed(uv_handle_t* handle)
+{
+ _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)handle->data;
+ mlassert(conn, "conn is nil");
+ mlassert(conn->handshake_timer, "conn must have handshake timer when closing it");
+ free(conn->handshake_timer);
+ conn->handshake_timer = NULL;
+ _output_dnssim_conn_maybe_free(conn);
+}
+
+static void _on_idle_timer_closed(uv_handle_t* handle)
+{
+ _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)handle->data;
+ mlassert(conn, "conn is nil");
+ mlassert(conn->idle_timer, "conn must have idle timer when closing it");
+ free(conn->idle_timer);
+ conn->is_idle = false;
+ conn->idle_timer = NULL;
+ _output_dnssim_conn_maybe_free(conn);
+}
+
+void _output_dnssim_conn_close(_output_dnssim_connection_t* conn)
+{
+ mlassert(conn, "conn can't be nil");
+ mlassert(conn->stats, "conn must have stats");
+ mlassert(conn->client, "conn must have client");
+ mlassert(conn->client->dnssim, "client must have dnssim");
+
+ output_dnssim_t* self = conn->client->dnssim;
+
+ switch (conn->state) {
+ case _OUTPUT_DNSSIM_CONN_CLOSING:
+ case _OUTPUT_DNSSIM_CONN_CLOSED:
+ return;
+ case _OUTPUT_DNSSIM_CONN_TCP_HANDSHAKE:
+ case _OUTPUT_DNSSIM_CONN_TLS_HANDSHAKE:
+ conn->stats->conn_handshakes_failed++;
+ self->stats_sum->conn_handshakes_failed++;
+ break;
+ case _OUTPUT_DNSSIM_CONN_ACTIVE:
+ case _OUTPUT_DNSSIM_CONN_CONGESTED:
+ self->stats_current->conn_active--;
+ break;
+ case _OUTPUT_DNSSIM_CONN_INITIALIZED:
+ case _OUTPUT_DNSSIM_CONN_CLOSE_REQUESTED:
+ break;
+ default:
+ lfatal("unknown conn state: %d", conn->state);
+ }
+ if (conn->prevent_close) {
+ lassert(conn->state <= _OUTPUT_DNSSIM_CONN_CLOSE_REQUESTED, "conn already closing");
+ conn->state = _OUTPUT_DNSSIM_CONN_CLOSE_REQUESTED;
+ return;
+ }
+ conn->state = _OUTPUT_DNSSIM_CONN_CLOSING;
+
+ if (conn->handshake_timer != NULL) {
+ uv_timer_stop(conn->handshake_timer);
+ uv_close((uv_handle_t*)conn->handshake_timer, _on_handshake_timer_closed);
+ }
+ if (conn->idle_timer != NULL) {
+ conn->is_idle = false;
+ uv_timer_stop(conn->idle_timer);
+ uv_close((uv_handle_t*)conn->idle_timer, _on_idle_timer_closed);
+ }
+
+ switch (_self->transport) {
+ case OUTPUT_DNSSIM_TRANSPORT_TCP:
+ _output_dnssim_tcp_close(conn);
+ break;
+ case OUTPUT_DNSSIM_TRANSPORT_TLS:
+#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION
+ _output_dnssim_tls_close(conn);
+#else
+ lfatal(DNSSIM_MIN_GNUTLS_ERRORMSG);
+#endif
+ break;
+ case OUTPUT_DNSSIM_TRANSPORT_HTTPS2:
+#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION
+ _output_dnssim_https2_close(conn);
+#else
+ lfatal(DNSSIM_MIN_GNUTLS_ERRORMSG);
+#endif
+ break;
+ default:
+ lfatal("unsupported transport");
+ break;
+ }
+}
+
+/* Close connection or run idle timer when there are no more outstanding queries. */
+void _output_dnssim_conn_idle(_output_dnssim_connection_t* conn)
+{
+ mlassert(conn, "conn can't be nil");
+
+ if (conn->queued == NULL && conn->sent == NULL) {
+ if (conn->idle_timer == NULL)
+ _output_dnssim_conn_close(conn);
+ else if (!conn->is_idle) {
+ conn->is_idle = true;
+ uv_timer_again(conn->idle_timer);
+ }
+ }
+}
+
+static void _send_pending_queries(_output_dnssim_connection_t* conn)
+{
+ _output_dnssim_query_tcp_t* qry;
+ mlassert(conn, "conn is nil");
+ mlassert(conn->client, "conn->client is nil");
+ qry = (_output_dnssim_query_tcp_t*)conn->client->pending;
+
+ while (qry != NULL && conn->state == _OUTPUT_DNSSIM_CONN_ACTIVE) {
+ _output_dnssim_query_tcp_t* next = (_output_dnssim_query_tcp_t*)qry->qry.next;
+ if (qry->qry.state == _OUTPUT_DNSSIM_QUERY_PENDING_WRITE) {
+ switch (qry->qry.transport) {
+ case OUTPUT_DNSSIM_TRANSPORT_TCP:
+ _output_dnssim_tcp_write_query(conn, qry);
+ break;
+ case OUTPUT_DNSSIM_TRANSPORT_TLS:
+#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION
+ _output_dnssim_tls_write_query(conn, qry);
+#else
+ mlfatal(DNSSIM_MIN_GNUTLS_ERRORMSG);
+#endif
+ break;
+ case OUTPUT_DNSSIM_TRANSPORT_HTTPS2:
+#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION
+ _output_dnssim_https2_write_query(conn, qry);
+#else
+ mlfatal(DNSSIM_MIN_GNUTLS_ERRORMSG);
+#endif
+ break;
+ default:
+ mlfatal("unsupported protocol");
+ break;
+ }
+ }
+ qry = next;
+ }
+}
+
+int _output_dnssim_handle_pending_queries(_output_dnssim_client_t* client)
+{
+ int ret = 0;
+ mlassert(client, "client is nil");
+
+ if (client->pending == NULL)
+ return ret;
+
+ output_dnssim_t* self = client->dnssim;
+ mlassert(self, "client must belong to dnssim");
+
+ /* Get active connection or find out whether new connection has to be opened. */
+ bool is_connecting = false;
+ _output_dnssim_connection_t* conn = client->conn;
+ while (conn != NULL) {
+ if (conn->state == _OUTPUT_DNSSIM_CONN_ACTIVE)
+ break;
+ else if (_conn_is_connecting(conn))
+ is_connecting = true;
+ conn = conn->next;
+ }
+
+ if (conn != NULL) { /* Send data right away over active connection. */
+ _send_pending_queries(conn);
+ } else if (!is_connecting) { /* No active or connecting connection -> open a new one. */
+ lfatal_oom(conn = calloc(1, sizeof(_output_dnssim_connection_t)));
+ conn->state = _OUTPUT_DNSSIM_CONN_INITIALIZED;
+ conn->client = client;
+ conn->stats = self->stats_current;
+ if (_self->transport == OUTPUT_DNSSIM_TRANSPORT_TLS) {
+#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION
+ ret = _output_dnssim_tls_init(conn);
+ if (ret < 0) {
+ free(conn);
+ return ret;
+ }
+#else
+ lfatal(DNSSIM_MIN_GNUTLS_ERRORMSG);
+#endif
+ } else if (_self->transport == OUTPUT_DNSSIM_TRANSPORT_HTTPS2) {
+#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION
+ ret = _output_dnssim_https2_init(conn);
+ if (ret < 0) {
+ free(conn);
+ return ret;
+ }
+#else
+ lfatal(DNSSIM_MIN_GNUTLS_ERRORMSG);
+#endif
+ }
+ ret = _output_dnssim_tcp_connect(self, conn);
+ if (ret < 0)
+ return ret;
+ _ll_append(client->conn, conn);
+ } /* Otherwise, pending queries wil be sent after connected callback. */
+
+ return ret;
+}
+
+void _output_dnssim_conn_activate(_output_dnssim_connection_t* conn)
+{
+ mlassert(conn, "conn is nil");
+ mlassert(conn->client, "conn must be associated with a client");
+ mlassert(conn->client->dnssim, "client must be associated with dnssim");
+
+ uv_timer_stop(conn->handshake_timer);
+
+ conn->state = _OUTPUT_DNSSIM_CONN_ACTIVE;
+ conn->client->dnssim->stats_current->conn_active++;
+ conn->read_state = _OUTPUT_DNSSIM_READ_STATE_DNSLEN;
+ conn->dnsbuf_len = 2;
+ conn->dnsbuf_pos = 0;
+ conn->dnsbuf_free_after_use = false;
+
+ _send_pending_queries(conn);
+ _output_dnssim_conn_idle(conn);
+}
+
+int _process_dnsmsg(_output_dnssim_connection_t* conn)
+{
+ mlassert(conn, "conn can't be nil");
+ mlassert(conn->client, "conn must have client");
+ mlassert(conn->client->dnssim, "client must have dnssim");
+
+ output_dnssim_t* self = conn->client->dnssim;
+
+ core_object_payload_t payload = CORE_OBJECT_PAYLOAD_INIT(NULL);
+ core_object_dns_t dns_a = CORE_OBJECT_DNS_INIT(&payload);
+
+ payload.payload = (uint8_t*)conn->dnsbuf_data;
+ payload.len = conn->dnsbuf_len;
+
+ dns_a.obj_prev = (core_object_t*)&payload;
+ int ret = core_object_dns_parse_header(&dns_a);
+ if (ret != 0) {
+ lwarning("tcp response malformed");
+ return _ERR_MALFORMED;
+ }
+ ldebug("tcp recv dnsmsg id: %04x", dns_a.id);
+
+ _output_dnssim_query_t* qry;
+
+ if (_self->transport == OUTPUT_DNSSIM_TRANSPORT_HTTPS2) {
+ lassert(conn->http2, "conn must have http2 ctx");
+ lassert(conn->http2->current_qry, "http2 has no current_qry");
+ lassert(conn->http2->current_qry->qry.req, "current_qry has no req");
+ lassert(conn->http2->current_qry->qry.req->dns_q, "req has no dns_q");
+
+ ret = _output_dnssim_answers_request(conn->http2->current_qry->qry.req, &dns_a);
+ switch (ret) {
+ case 0:
+ _output_dnssim_request_answered(conn->http2->current_qry->qry.req, &dns_a);
+ break;
+ case _ERR_MSGID:
+ lwarning("https2 QID mismatch: request=0x%04x, response=0x%04x",
+ conn->http2->current_qry->qry.req->dns_q->id, dns_a.id);
+ break;
+ case _ERR_QUESTION:
+ default:
+ lwarning("https2 response question mismatch");
+ break;
+ }
+ } else {
+ qry = conn->sent;
+ while (qry != NULL) {
+ if (qry->req->dns_q->id == dns_a.id) {
+ ret = _output_dnssim_answers_request(qry->req, &dns_a);
+ if (ret != 0) {
+ lwarning("response question mismatch");
+ } else {
+ _output_dnssim_request_answered(qry->req, &dns_a);
+ }
+ break;
+ }
+ qry = qry->next;
+ }
+ }
+
+ return 0;
+}
+
+static int _parse_dnsbuf_data(_output_dnssim_connection_t* conn)
+{
+ mlassert(conn, "conn can't be nil");
+ mlassert(conn->dnsbuf_pos == conn->dnsbuf_len, "attempt to parse incomplete dnsbuf_data");
+ int ret = 0;
+
+ switch (conn->read_state) {
+ case _OUTPUT_DNSSIM_READ_STATE_DNSLEN: {
+ uint16_t* p_dnslen = (uint16_t*)conn->dnsbuf_data;
+ conn->dnsbuf_len = ntohs(*p_dnslen);
+ if (conn->dnsbuf_len == 0) {
+ mlwarning("invalid dnslen received: 0");
+ conn->dnsbuf_len = 2;
+ conn->read_state = _OUTPUT_DNSSIM_READ_STATE_DNSLEN;
+ } else if (conn->dnsbuf_len < 12) {
+ mldebug("invalid dnslen received: %d", conn->dnsbuf_len);
+ ret = -1;
+ } else {
+ mldebug("dnslen: %d", conn->dnsbuf_len);
+ conn->read_state = _OUTPUT_DNSSIM_READ_STATE_DNSMSG;
+ }
+ break;
+ }
+ case _OUTPUT_DNSSIM_READ_STATE_DNSMSG:
+ ret = _process_dnsmsg(conn);
+ if (ret) {
+ conn->read_state = _OUTPUT_DNSSIM_READ_STATE_INVALID;
+ } else {
+ conn->dnsbuf_len = 2;
+ conn->read_state = _OUTPUT_DNSSIM_READ_STATE_DNSLEN;
+ }
+ break;
+ default:
+ mlfatal("tcp invalid connection read_state");
+ break;
+ }
+
+ conn->dnsbuf_pos = 0;
+ if (conn->dnsbuf_free_after_use) {
+ conn->dnsbuf_free_after_use = false;
+ free(conn->dnsbuf_data);
+ }
+ conn->dnsbuf_data = NULL;
+
+ return ret;
+}
+
+static unsigned int _read_dns_stream_chunk(_output_dnssim_connection_t* conn, size_t len, const char* data)
+{
+ mlassert(conn, "conn can't be nil");
+ mlassert(data, "data can't be nil");
+ mlassert(len > 0, "no data to read");
+ mlassert((conn->read_state == _OUTPUT_DNSSIM_READ_STATE_DNSLEN || conn->read_state == _OUTPUT_DNSSIM_READ_STATE_DNSMSG),
+ "connection has invalid read_state");
+
+ int ret = 0;
+ unsigned int nread;
+ size_t expected = conn->dnsbuf_len - conn->dnsbuf_pos;
+ mlassert(expected > 0, "no data expected");
+
+ if (conn->dnsbuf_free_after_use == false && expected > len) {
+ /* Start of partial read. */
+ mlassert(conn->dnsbuf_pos == 0, "conn->dnsbuf_pos must be 0 at start of partial read");
+ mlassert(conn->dnsbuf_len > 0, "conn->dnsbuf_len must be set at start of partial read");
+ mlfatal_oom(conn->dnsbuf_data = malloc(conn->dnsbuf_len * sizeof(char)));
+ conn->dnsbuf_free_after_use = true;
+ }
+
+ if (conn->dnsbuf_free_after_use) { /* Partial read is in progress. */
+ char* dest = conn->dnsbuf_data + conn->dnsbuf_pos;
+ if (expected < len)
+ len = expected;
+ memcpy(dest, data, len);
+ conn->dnsbuf_pos += len;
+ nread = len;
+ } else { /* Complete and clean read. */
+ mlassert(expected <= len, "not enough data to perform complete read");
+ conn->dnsbuf_data = (char*)data;
+ conn->dnsbuf_pos = conn->dnsbuf_len;
+ nread = expected;
+ }
+
+ /* If entire dnslen/dnsmsg was read, attempt to parse it. */
+ if (conn->dnsbuf_len == conn->dnsbuf_pos) {
+ ret = _parse_dnsbuf_data(conn);
+ if (ret < 0)
+ return ret;
+ }
+
+ return nread;
+}
+
+void _output_dnssim_read_dns_stream(_output_dnssim_connection_t* conn, size_t len, const char* data)
+{
+ int pos = 0;
+ int chunk = 0;
+ while (pos < len) {
+ chunk = _read_dns_stream_chunk(conn, len - pos, data + pos);
+ if (chunk < 0) {
+ mlwarning("lost orientation in DNS stream, closing");
+ _output_dnssim_conn_close(conn);
+ break;
+ } else {
+ pos += chunk;
+ }
+ }
+ mlassert((pos == len) || (chunk < 0), "dns stream read invalid, pos != len");
+}
+
+void _output_dnssim_read_dnsmsg(_output_dnssim_connection_t* conn, size_t len, const char* data)
+{
+ mlassert(conn, "conn is nil");
+ mlassert(len > 0, "len is zero");
+ mlassert(data, "no data");
+ mlassert(conn->dnsbuf_pos == 0, "dnsbuf not empty");
+ mlassert(conn->dnsbuf_free_after_use == false, "dnsbuf read in progress");
+
+ /* Read dnsmsg of given length from input data. */
+ conn->dnsbuf_len = len;
+ conn->read_state = _OUTPUT_DNSSIM_READ_STATE_DNSMSG;
+ int nread = _read_dns_stream_chunk(conn, len, data);
+
+ if (nread != len) {
+ mlwarning("failed to read received dnsmsg");
+ if (conn->dnsbuf_free_after_use)
+ free(conn->dnsbuf_data);
+ }
+
+ /* Clean state afterwards. */
+ conn->read_state = _OUTPUT_DNSSIM_READ_STATE_DNSLEN;
+ conn->dnsbuf_len = 2;
+ conn->dnsbuf_pos = 0;
+ conn->dnsbuf_free_after_use = false;
+}
diff --git a/src/output/dnssim/https2.c b/src/output/dnssim/https2.c
new file mode 100644
index 0000000..72fcdaf
--- /dev/null
+++ b/src/output/dnssim/https2.c
@@ -0,0 +1,592 @@
+/*
+ * Copyright (c) 2020, CZ.NIC, z.s.p.o.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "output/dnssim.h"
+#include "output/dnssim/internal.h"
+#include "output/dnssim/ll.h"
+#include "core/assert.h"
+#include "lib/base64url.h"
+
+#include <gnutls/gnutls.h>
+#include <string.h>
+
+#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION
+
+#define OUTPUT_DNSSIM_MAKE_NV(NAME, VALUE, VALUELEN) \
+ { \
+ (uint8_t*)NAME, (uint8_t*)VALUE, sizeof(NAME) - 1, VALUELEN, \
+ NGHTTP2_NV_FLAG_NONE \
+ }
+
+#define OUTPUT_DNSSIM_MAKE_NV2(NAME, VALUE) \
+ { \
+ (uint8_t*)NAME, (uint8_t*)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \
+ NGHTTP2_NV_FLAG_NONE \
+ }
+
+#define OUTPUT_DNSSIM_HTTP_GET_TEMPLATE "?dns="
+#define OUTPUT_DNSSIM_HTTP_GET_TEMPLATE_LEN (sizeof(OUTPUT_DNSSIM_HTTP_GET_TEMPLATE) - 1)
+#define OUTPUT_DNSSIM_HTTP2_INITIAL_MAX_CONCURRENT_STREAMS 100
+#define OUTPUT_DNSSIM_HTTP2_DEFAULT_MAX_CONCURRENT_STREAMS 0xffffffffu
+
+static core_log_t _log = LOG_T_INIT("output.dnssim");
+
+static ssize_t _http2_send(nghttp2_session* session, const uint8_t* data, size_t length, int flags, void* user_data)
+{
+ _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)user_data;
+ mlassert(conn, "conn can't be null");
+ mlassert(conn->tls, "conn must have tls ctx");
+ mlassert(conn->tls->session, "conn must have tls session");
+
+ mldebug("http2 (%p): sending data, len=%ld", session, length);
+
+ ssize_t len = 0;
+ if ((len = gnutls_record_send(conn->tls->session, data, length)) < 0) {
+ mlwarning("gnutls_record_send failed: %s", gnutls_strerror(len));
+ len = NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return len;
+}
+
+static ssize_t _http2_on_data_provider_read(nghttp2_session* session, int32_t stream_id, uint8_t* buf, size_t length, uint32_t* data_flags, nghttp2_data_source* source, void* user_data)
+{
+ _output_dnssim_https2_data_provider_t* buffer = source->ptr;
+ mlassert(buffer, "no data provider");
+ mlassert(buffer->len <= MAX_DNSMSG_SIZE, "invalid dnsmsg size: %zu B", buffer->len);
+
+ ssize_t sent = (length < buffer->len) ? length : buffer->len;
+ mlassert(sent >= 0, "negative length of bytes to send");
+
+ memcpy(buf, buffer->buf, sent);
+ buffer->buf += sent;
+ buffer->len -= sent;
+ if (buffer->len == 0)
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+
+ return sent;
+}
+
+static _output_dnssim_query_tcp_t* _http2_get_stream_qry(_output_dnssim_connection_t* conn, int32_t stream_id)
+{
+ mlassert(conn, "conn is nil");
+ mlassert(stream_id >= 0, "invalid stream_id");
+
+ _output_dnssim_query_tcp_t* qry = (_output_dnssim_query_tcp_t*)conn->sent;
+ while (qry != NULL && qry->stream_id != stream_id) {
+ qry = (_output_dnssim_query_tcp_t*)qry->qry.next;
+ }
+
+ return qry;
+}
+
+static int _http2_on_header(nghttp2_session* session, const nghttp2_frame* frame, const uint8_t* name, size_t namelen, const uint8_t* value, size_t valuelen, uint8_t flags, void* user_data)
+{
+ if (frame->hd.type == NGHTTP2_HEADERS && frame->headers.cat == NGHTTP2_HCAT_RESPONSE) {
+ if (namelen == 7 && strncmp((char*)name, ":status", 7) == 0) {
+ if (valuelen != 3 || (value[0] != '1' && value[0] != '2')) {
+ /* When reponse code isn't 1xx or 2xx, close the query.
+ * This will result in request timeout, which currently seems
+ * slightly better than mocking SERVFAIL for statistics. */
+ _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)user_data;
+ mlassert(conn, "conn is nil");
+ _output_dnssim_query_tcp_t* qry = _http2_get_stream_qry(conn, frame->hd.stream_id);
+
+ if (qry != NULL) {
+ _output_dnssim_close_query_https2(qry);
+ mlinfo("http response %s, closing query", value);
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+static int _http2_on_data_recv(nghttp2_session* session, uint8_t flags, int32_t stream_id, const uint8_t* data, size_t len, void* user_data)
+{
+ _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)user_data;
+ mlassert(conn, "conn is nil");
+
+ _output_dnssim_query_tcp_t* qry = _http2_get_stream_qry(conn, stream_id);
+
+ mldebug("http2: data chunk recv, session=%p, len=%d", session, len);
+
+ if (qry) {
+ if (qry->recv_buf_len == 0) {
+ if (len > MAX_DNSMSG_SIZE) {
+ mlwarning("http response exceeded maximum size of dns message");
+ return -1;
+ }
+ mlfatal_oom(qry->recv_buf = malloc(len));
+ memcpy(qry->recv_buf, data, len);
+ qry->recv_buf_len = len;
+ } else {
+ size_t total_len = qry->recv_buf_len + len;
+ if (total_len > MAX_DNSMSG_SIZE) {
+ mlwarning("http response exceeded maximum size of dns message");
+ return -1;
+ }
+ mlfatal_oom(qry->recv_buf = realloc(qry->recv_buf, total_len));
+ memcpy(qry->recv_buf + qry->recv_buf_len, data, len);
+ qry->recv_buf_len = total_len;
+ }
+ } else {
+ mldebug("no query associated with this stream id, ignoring");
+ }
+
+ return 0;
+}
+
+static void _http2_check_max_streams(_output_dnssim_connection_t* conn)
+{
+ mlassert(conn, "conn can't be null");
+ mlassert(conn->http2, "conn must have http2 ctx");
+
+ switch (conn->state) {
+ case _OUTPUT_DNSSIM_CONN_ACTIVE:
+ if (conn->http2->open_streams >= conn->http2->max_concurrent_streams) {
+ mlinfo("http2 (%p): reached maximum number of concurrent streams (%ld)",
+ conn->http2->session, conn->http2->max_concurrent_streams);
+ conn->state = _OUTPUT_DNSSIM_CONN_CONGESTED;
+ }
+ break;
+ case _OUTPUT_DNSSIM_CONN_CONGESTED:
+ if (conn->http2->open_streams < conn->http2->max_concurrent_streams)
+ conn->state = _OUTPUT_DNSSIM_CONN_ACTIVE;
+ break;
+ default:
+ break;
+ }
+}
+
+static int _http2_on_stream_close(nghttp2_session* session, int32_t stream_id, uint32_t error_code, void* user_data)
+{
+ _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)user_data;
+ mlassert(conn, "conn can't be null");
+ mlassert(conn->http2, "conn must have http2 ctx");
+ mlassert(conn->http2->open_streams > 0, "conn has no open streams");
+
+ conn->http2->open_streams--;
+ _http2_check_max_streams(conn);
+ return 0;
+}
+
+static int _http2_on_frame_recv(nghttp2_session* session, const nghttp2_frame* frame, void* user_data)
+{
+ _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)user_data;
+ mlassert(conn, "conn can't be null");
+ mlassert(conn->tls, "conn must have tls ctx");
+ mlassert(conn->tls->session, "conn must have tls session");
+ mlassert(conn->http2, "conn must have http2 ctx");
+
+ switch (frame->hd.type) {
+ case NGHTTP2_DATA:
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ mldebug("http2 (%p): final DATA frame recv", session);
+ _output_dnssim_query_tcp_t* qry = _http2_get_stream_qry(conn, frame->hd.stream_id);
+
+ if (qry != NULL) {
+ conn->http2->current_qry = qry;
+ _output_dnssim_read_dnsmsg(conn, qry->recv_buf_len, (char*)qry->recv_buf);
+ }
+ }
+ break;
+ case NGHTTP2_SETTINGS:
+ if (!conn->http2->remote_settings_received) {
+ /* On the first SETTINGS frame, set concurrent streams to unlimited, same as nghttp2. */
+ conn->http2->remote_settings_received = true;
+ conn->http2->max_concurrent_streams = OUTPUT_DNSSIM_HTTP2_DEFAULT_MAX_CONCURRENT_STREAMS;
+ _http2_check_max_streams(conn);
+ }
+ nghttp2_settings* settings = (nghttp2_settings*)frame;
+ int i;
+ for (i = 0; i < settings->niv; i++) {
+ switch (settings->iv[i].settings_id) {
+ case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
+ conn->http2->max_concurrent_streams = settings->iv[i].value;
+ _http2_check_max_streams(conn);
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+int _output_dnssim_https2_init(_output_dnssim_connection_t* conn)
+{
+ mlassert(conn, "conn is nil");
+ mlassert(conn->tls == NULL, "conn already has tls context");
+ mlassert(conn->http2 == NULL, "conn already has http2 context");
+ mlassert(conn->client, "conn must be associated with a client");
+ mlassert(conn->client->dnssim, "client must have dnssim");
+
+ int ret = -1;
+ nghttp2_session_callbacks* callbacks;
+ nghttp2_option* option;
+ output_dnssim_t* self = conn->client->dnssim;
+
+ /* Initialize TLS session. */
+ ret = _output_dnssim_tls_init(conn);
+ if (ret < 0)
+ return ret;
+
+ /* Configure ALPN to negotiate HTTP/2. */
+ const gnutls_datum_t protos[] = {
+ { (unsigned char*)"h2", 2 }
+ };
+ ret = gnutls_alpn_set_protocols(conn->tls->session, protos, 1, 0);
+ if (ret < 0) {
+ lwarning("failed to set ALPN protocol: %s", gnutls_strerror(ret));
+ return ret;
+ }
+
+ lfatal_oom(conn->http2 = calloc(1, sizeof(_output_dnssim_http2_ctx_t)));
+ conn->http2->max_concurrent_streams = OUTPUT_DNSSIM_HTTP2_INITIAL_MAX_CONCURRENT_STREAMS;
+
+ /* Set up HTTP/2 callbacks and client. */
+ lassert(nghttp2_session_callbacks_new(&callbacks) == 0, "out of memory");
+ nghttp2_session_callbacks_set_send_callback(callbacks, _http2_send);
+ nghttp2_session_callbacks_set_on_header_callback(callbacks, _http2_on_header);
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, _http2_on_data_recv);
+ nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, _http2_on_frame_recv);
+ nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, _http2_on_stream_close);
+
+ lassert(nghttp2_option_new(&option) == 0, "out of memory");
+ nghttp2_option_set_peer_max_concurrent_streams(option, conn->http2->max_concurrent_streams);
+
+ ret = nghttp2_session_client_new2(&conn->http2->session, callbacks, conn, option);
+
+ nghttp2_session_callbacks_del(callbacks);
+ nghttp2_option_del(option);
+
+ if (ret < 0) {
+ free(conn->http2);
+ conn->http2 = NULL;
+ }
+
+ return ret;
+}
+
+int _output_dnssim_https2_setup(_output_dnssim_connection_t* conn)
+{
+ mlassert(conn, "conn is nil");
+ mlassert(conn->tls, "conn must have tls ctx");
+ mlassert(conn->tls->session, "conn must have tls session");
+ mlassert(conn->http2, "conn must have http2 ctx");
+ mlassert(conn->http2->session, "conn must have http2 session");
+
+ int ret = -1;
+
+ /* Check "h2" protocol was negotiated with ALPN. */
+ gnutls_datum_t proto;
+ ret = gnutls_alpn_get_selected_protocol(conn->tls->session, &proto);
+ if (ret < 0) {
+ mlwarning("http2: failed to get negotiated protocol: %s", gnutls_strerror(ret));
+ return ret;
+ }
+ if (proto.size != 2 || memcmp("h2", proto.data, 2) != 0) {
+ mlwarning("http2: protocol is not negotiated");
+ return ret;
+ }
+
+ /* Submit SETTIGNS frame. */
+ static const nghttp2_settings_entry iv[] = {
+ { NGHTTP2_SETTINGS_MAX_FRAME_SIZE, MAX_DNSMSG_SIZE },
+ { NGHTTP2_SETTINGS_ENABLE_PUSH, 0 }, /* Only we can initiate streams. */
+ };
+ ret = nghttp2_submit_settings(conn->http2->session, NGHTTP2_FLAG_NONE, iv, sizeof(iv) / sizeof(*iv));
+ if (ret < 0) {
+ mlwarning("http2: failed to submit SETTINGS: %s", nghttp2_strerror(ret));
+ return ret;
+ }
+
+ ret = 0;
+ return ret;
+}
+
+void _output_dnssim_https2_process_input_data(_output_dnssim_connection_t* conn, size_t len, const char* data)
+{
+ mlassert(conn, "conn is nil");
+ mlassert(conn->http2, "conn must have http2 ctx");
+ mlassert(conn->http2->session, "conn must have http2 session");
+
+ /* Process incoming frames. */
+ ssize_t ret = 0;
+ conn->prevent_close = true;
+ ret = nghttp2_session_mem_recv(conn->http2->session, (uint8_t*)data, len);
+ conn->prevent_close = false;
+ if (ret < 0) {
+ mlwarning("failed nghttp2_session_mem_recv: %s", nghttp2_strerror(ret));
+ _output_dnssim_conn_close(conn);
+ return;
+ } else if (conn->state == _OUTPUT_DNSSIM_CONN_CLOSE_REQUESTED) {
+ _output_dnssim_conn_close(conn);
+ return;
+ }
+ mlassert(ret == len, "nghttp2_session_mem_recv didn't process all data");
+
+ /* Send any frames the read might have triggered. */
+ ret = nghttp2_session_send(conn->http2->session);
+ if (ret < 0) {
+ mlwarning("failed nghttp2_session_send: %s", nghttp2_strerror(ret));
+ _output_dnssim_conn_close(conn);
+ return;
+ }
+}
+
+int _output_dnssim_create_query_https2(output_dnssim_t* self, _output_dnssim_request_t* req)
+{
+ mlassert_self();
+ lassert(req, "req is nil");
+ lassert(req->client, "request must have a client associated with it");
+
+ _output_dnssim_query_tcp_t* qry;
+
+ lfatal_oom(qry = calloc(1, sizeof(_output_dnssim_query_tcp_t)));
+
+ qry->qry.transport = OUTPUT_DNSSIM_TRANSPORT_HTTPS2;
+ qry->qry.req = req;
+ qry->qry.state = _OUTPUT_DNSSIM_QUERY_PENDING_WRITE;
+ qry->stream_id = -1;
+ req->qry = &qry->qry; // TODO change when adding support for multiple Qs for req
+ _ll_append(req->client->pending, &qry->qry);
+
+ return _output_dnssim_handle_pending_queries(req->client);
+}
+
+void _output_dnssim_close_query_https2(_output_dnssim_query_tcp_t* qry)
+{
+ mlassert(qry, "qry can't be null");
+ mlassert(qry->qry.req, "query must be part of a request");
+ _output_dnssim_request_t* req = qry->qry.req;
+ mlassert(req->client, "request must belong to a client");
+
+ _ll_try_remove(req->client->pending, &qry->qry);
+ if (qry->conn) {
+ _output_dnssim_connection_t* conn = qry->conn;
+ _ll_try_remove(conn->sent, &qry->qry);
+ qry->conn = NULL;
+ _output_dnssim_conn_idle(conn);
+ }
+
+ if (qry->recv_buf != NULL)
+ free(qry->recv_buf);
+
+ _ll_remove(req->qry, &qry->qry);
+ free(qry);
+}
+
+void _output_dnssim_https2_close(_output_dnssim_connection_t* conn)
+{
+ mlassert(conn, "conn can't be nil");
+ mlassert(conn->http2, "conn must have http2 ctx");
+
+ nghttp2_session_del(conn->http2->session);
+ _output_dnssim_tls_close(conn);
+}
+
+static int _http2_send_query_get(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry)
+{
+ mlassert(conn, "conn can't be null");
+ mlassert(qry, "qry can't be null");
+ mlassert(qry->qry.req, "req can't be null");
+ mlassert(qry->qry.req->payload, "payload can't be null");
+ mlassert(qry->qry.req->payload->len <= MAX_DNSMSG_SIZE, "payload too big");
+ mlassert(conn->client, "conn must be associated with client");
+ mlassert(conn->client->dnssim, "client must have dnssim");
+
+ output_dnssim_t* self = conn->client->dnssim;
+ core_object_payload_t* content = qry->qry.req->payload;
+
+ const size_t uri_path_len = strlen(_self->h2_uri_path);
+ const size_t path_len = uri_path_len
+ + OUTPUT_DNSSIM_HTTP_GET_TEMPLATE_LEN
+ + (content->len * 4) / 3 + 3 /* upper limit of base64 encoding */
+ + 1; /* terminating null byte */
+ if (path_len >= _MAX_URI_LEN) {
+ self->discarded++;
+ linfo("http2: uri path with query too long, query discarded");
+ return 0;
+ }
+ char path[path_len];
+ memcpy(path, _self->h2_uri_path, uri_path_len);
+ memcpy(&path[uri_path_len], OUTPUT_DNSSIM_HTTP_GET_TEMPLATE, OUTPUT_DNSSIM_HTTP_GET_TEMPLATE_LEN);
+
+ int32_t ret = base64url_encode(content->payload, content->len,
+ (uint8_t*)&path[uri_path_len + OUTPUT_DNSSIM_HTTP_GET_TEMPLATE_LEN],
+ sizeof(path) - uri_path_len - OUTPUT_DNSSIM_HTTP_GET_TEMPLATE_LEN - 1);
+ if (ret < 0) {
+ self->discarded++;
+ linfo("http2: base64url encode of query failed, query discarded");
+ return 0;
+ }
+
+ nghttp2_nv hdrs[] = {
+ OUTPUT_DNSSIM_MAKE_NV2(":method", "GET"),
+ OUTPUT_DNSSIM_MAKE_NV2(":scheme", "https"),
+ OUTPUT_DNSSIM_MAKE_NV(":authority", _self->h2_uri_authority, strlen(_self->h2_uri_authority)),
+ OUTPUT_DNSSIM_MAKE_NV(":path", path, uri_path_len + sizeof(OUTPUT_DNSSIM_HTTP_GET_TEMPLATE) - 1 + ret),
+ OUTPUT_DNSSIM_MAKE_NV2("accept", "application/dns-message"),
+ };
+
+ qry->stream_id = nghttp2_submit_request(conn->http2->session, NULL, hdrs, sizeof(hdrs) / sizeof(nghttp2_nv), NULL, NULL);
+
+ if (qry->stream_id < 0) {
+ mldebug("http2 (%p): failed to submit request: %s", conn->http2->session, nghttp2_strerror(qry->stream_id));
+ return -1;
+ }
+ mldebug("http2 (%p): GET %s", conn->http2->session, path);
+ conn->http2->open_streams++;
+ _http2_check_max_streams(conn);
+
+ ret = nghttp2_session_send(conn->http2->session);
+ if (ret < 0) {
+ mldebug("http2 (%p): failed session send: %s", conn->http2->session, nghttp2_strerror(ret));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int _http2_send_query_post(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry)
+{
+ mlassert(conn, "conn can't be null");
+ mlassert(qry, "qry can't be null");
+ mlassert(qry->qry.req, "req can't be null");
+ mlassert(qry->qry.req->payload, "payload can't be null");
+ mlassert(qry->qry.req->payload->len <= MAX_DNSMSG_SIZE, "payload too big");
+ mlassert(conn->client, "conn must be associated with client");
+ mlassert(conn->client->dnssim, "client must have dnssim");
+
+ output_dnssim_t* self = conn->client->dnssim;
+
+ core_object_payload_t* content = qry->qry.req->payload;
+
+ int window_size = nghttp2_session_get_remote_window_size(conn->http2->session);
+ if (content->len > window_size) {
+ mldebug("http2 (%p): insufficient remote window size, deferring", conn->http2->session);
+ return 0;
+ }
+
+ char content_length[6]; /* max dnslen "65535" */
+ int content_length_len = snprintf(content_length, 6, "%zd", content->len);
+
+ nghttp2_nv hdrs[] = {
+ OUTPUT_DNSSIM_MAKE_NV2(":method", "POST"),
+ OUTPUT_DNSSIM_MAKE_NV2(":scheme", "https"),
+ OUTPUT_DNSSIM_MAKE_NV(":authority", _self->h2_uri_authority, strlen(_self->h2_uri_authority)),
+ OUTPUT_DNSSIM_MAKE_NV(":path", _self->h2_uri_path, strlen(_self->h2_uri_path)),
+ OUTPUT_DNSSIM_MAKE_NV2("accept", "application/dns-message"),
+ OUTPUT_DNSSIM_MAKE_NV2("content-type", "application/dns-message"),
+ OUTPUT_DNSSIM_MAKE_NV("content-length", content_length, content_length_len)
+ };
+
+ _output_dnssim_https2_data_provider_t data = {
+ .buf = content->payload,
+ .len = content->len
+ };
+
+ nghttp2_data_provider data_provider = {
+ .source.ptr = &data,
+ .read_callback = _http2_on_data_provider_read
+ };
+
+ qry->stream_id = nghttp2_submit_request(conn->http2->session, NULL, hdrs, sizeof(hdrs) / sizeof(nghttp2_nv), &data_provider, NULL);
+
+ if (qry->stream_id < 0) {
+ mldebug("http2 (%p): failed to submit request: %s", conn->http2->session, nghttp2_strerror(qry->stream_id));
+ return -1;
+ }
+ mldebug("http2 (%p): POST payload len=%ld", conn->http2->session, content->len);
+ conn->http2->open_streams++;
+ _http2_check_max_streams(conn);
+
+ window_size = nghttp2_session_get_stream_remote_window_size(conn->http2->session, qry->stream_id);
+ mlassert(content->len <= window_size,
+ "unsupported: http2 stream window size (%ld B) is smaller than dns payload (%ld B)",
+ window_size, content->len);
+
+ int ret = nghttp2_session_send(conn->http2->session);
+ if (ret < 0) {
+ mldebug("http2 (%p): failed session send: %s", conn->http2->session, nghttp2_strerror(ret));
+ return -1;
+ }
+
+ return 0;
+}
+
+void _output_dnssim_https2_write_query(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry)
+{
+ mlassert(qry, "qry can't be null");
+ mlassert(qry->qry.state == _OUTPUT_DNSSIM_QUERY_PENDING_WRITE, "qry must be pending write");
+ mlassert(conn, "conn can't be null");
+ mlassert(conn->state == _OUTPUT_DNSSIM_CONN_ACTIVE, "connection state != ACTIVE");
+ mlassert(conn->http2, "conn must have http2 ctx");
+ mlassert(conn->http2->session, "conn must have http2 session");
+ mlassert(conn->client, "conn must be associated with client");
+ mlassert(conn->client->pending, "conn has no pending queries");
+ mlassert(conn->client->dnssim, "client must have dnssim");
+
+ int ret = 0;
+ output_dnssim_t* self = conn->client->dnssim;
+
+ if (!nghttp2_session_check_request_allowed(conn->http2->session)) {
+ mldebug("http2 (%p): request not allowed", conn->http2->session);
+ _output_dnssim_conn_close(conn);
+ return;
+ }
+
+ switch (_self->h2_method) {
+ case OUTPUT_DNSSIM_H2_POST:
+ ret = _http2_send_query_post(conn, qry);
+ break;
+ case OUTPUT_DNSSIM_H2_GET:
+ ret = _http2_send_query_get(conn, qry);
+ break;
+ default:
+ lfatal("http2: unsupported method");
+ }
+
+ if (ret < 0) {
+ _output_dnssim_conn_close(conn);
+ return;
+ }
+
+ qry->conn = conn;
+ _ll_remove(conn->client->pending, &qry->qry);
+ _ll_append(conn->sent, &qry->qry);
+
+ /* Stop idle timer, since there are queries to answer now. */
+ if (conn->idle_timer != NULL) {
+ conn->is_idle = false;
+ uv_timer_stop(conn->idle_timer);
+ }
+
+ qry->qry.state = _OUTPUT_DNSSIM_QUERY_SENT;
+}
+
+#endif
diff --git a/src/output/dnssim/internal.h b/src/output/dnssim/internal.h
new file mode 100644
index 0000000..b9feddf
--- /dev/null
+++ b/src/output/dnssim/internal.h
@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2019-2020, CZ.NIC, z.s.p.o.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __dnsjit_output_dnssim_internal_h
+#define __dnsjit_output_dnssim_internal_h
+
+#include <gnutls/gnutls.h>
+#include <nghttp2/nghttp2.h>
+#include <uv.h>
+#include "core/object/dns.h"
+#include "core/object/payload.h"
+
+#define DNSSIM_MIN_GNUTLS_VERSION 0x030603
+#define DNSSIM_MIN_GNUTLS_ERRORMSG "dnssim tls/https2 transport requires GnuTLS >= 3.6.3"
+
+#define _self ((_output_dnssim_t*)self)
+#define _ERR_MALFORMED -2
+#define _ERR_MSGID -3
+#define _ERR_TC -4
+#define _ERR_QUESTION -5
+
+#define _MAX_URI_LEN 65536
+#define MAX_DNSMSG_SIZE 65535
+#define WIRE_BUF_SIZE (MAX_DNSMSG_SIZE + 2 + 16384) /** max tcplen + 2b tcplen + 16kb tls record */
+
+typedef struct _output_dnssim_request _output_dnssim_request_t;
+typedef struct _output_dnssim_connection _output_dnssim_connection_t;
+typedef struct _output_dnssim_client _output_dnssim_client_t;
+
+/*
+ * Query-related structures.
+ */
+
+typedef struct _output_dnssim_query _output_dnssim_query_t;
+struct _output_dnssim_query {
+ /*
+ * Next query in the list.
+ *
+ * Currently, next is used for TCP clients/connection, which makes it
+ * impossible to use for tracking multiple queries of a single request.
+ *
+ * TODO: refactor the linked lists to allow query to be part of multiple lists
+ */
+ _output_dnssim_query_t* next;
+
+ output_dnssim_transport_t transport;
+ _output_dnssim_request_t* req;
+
+ /* Query state, currently used only for TCP. */
+ enum {
+ _OUTPUT_DNSSIM_QUERY_PENDING_WRITE,
+ _OUTPUT_DNSSIM_QUERY_PENDING_WRITE_CB,
+ _OUTPUT_DNSSIM_QUERY_PENDING_CLOSE,
+ _OUTPUT_DNSSIM_QUERY_WRITE_FAILED,
+ _OUTPUT_DNSSIM_QUERY_SENT,
+ _OUTPUT_DNSSIM_QUERY_ORPHANED
+ } state;
+};
+
+typedef struct _output_dnssim_query_udp _output_dnssim_query_udp_t;
+struct _output_dnssim_query_udp {
+ _output_dnssim_query_t qry;
+
+ uv_udp_t* handle;
+ uv_buf_t buf;
+};
+
+typedef struct _output_dnssim_query_tcp _output_dnssim_query_tcp_t;
+struct _output_dnssim_query_tcp {
+ _output_dnssim_query_t qry;
+
+ /* Connection this query is assigned to. */
+ _output_dnssim_connection_t* conn;
+
+ uv_write_t write_req;
+
+ /* Send buffers for libuv; 0 is for dnslen, 1 is for dnsmsg. */
+ uv_buf_t bufs[2];
+
+ /* HTTP/2 stream id that was used to send this query. */
+ int32_t stream_id;
+
+ /* HTTP/2 expected content length. */
+ int32_t content_len;
+
+ /* Receive buffer (currently used only by HTTP/2). */
+ uint8_t* recv_buf;
+ ssize_t recv_buf_len;
+};
+
+struct _output_dnssim_request {
+ /* List of queries associated with this request. */
+ _output_dnssim_query_t* qry;
+
+ /* Client this request belongs to. */
+ _output_dnssim_client_t* client;
+
+ /* The DNS question to be resolved. */
+ core_object_payload_t* payload;
+ core_object_dns_t* dns_q;
+ const uint8_t* question;
+ ssize_t question_len;
+
+ /* Timestamps for latency calculation. */
+ uint64_t created_at;
+ uint64_t ended_at;
+
+ /* Timer for tracking timeout of the request. */
+ uv_timer_t* timer;
+
+ /* The output component of this request. */
+ output_dnssim_t* dnssim;
+
+ /* State of the request. */
+ enum {
+ _OUTPUT_DNSSIM_REQ_ONGOING,
+ _OUTPUT_DNSSIM_REQ_CLOSING
+ } state;
+
+ /* Statistics interval in which this request is tracked. */
+ output_dnssim_stats_t* stats;
+};
+
+/*
+ * Connection-related structures.
+ */
+
+/* Read-state of connection's data stream. */
+typedef enum _output_dnssim_read_state {
+ _OUTPUT_DNSSIM_READ_STATE_CLEAN,
+ _OUTPUT_DNSSIM_READ_STATE_DNSLEN, /* Expecting bytes of dnslen. */
+ _OUTPUT_DNSSIM_READ_STATE_DNSMSG, /* Expecting bytes of dnsmsg. */
+ _OUTPUT_DNSSIM_READ_STATE_INVALID
+} _output_dnssim_read_state_t;
+
+/* TLS-related data for a single connection. */
+typedef struct _output_dnssim_tls_ctx {
+ gnutls_session_t session;
+ uint8_t* buf;
+ ssize_t buf_len;
+ ssize_t buf_pos;
+ size_t write_queue_size;
+} _output_dnssim_tls_ctx_t;
+
+/* HTTP2 context for a single connection. */
+typedef struct _output_dnssim_http2_ctx {
+ nghttp2_session* session;
+
+ /* Query to which the dnsbuf currently being processed belongs to. */
+ _output_dnssim_query_tcp_t* current_qry;
+
+ /* Maximum number of concurrent and currently open streams. */
+ uint32_t max_concurrent_streams;
+ uint32_t open_streams;
+
+ /* Flag indicating whether we received the peer's initial SETTINGS frame. */
+ bool remote_settings_received;
+} _output_dnssim_http2_ctx_t;
+
+struct _output_dnssim_connection {
+ _output_dnssim_connection_t* next;
+
+ uv_tcp_t* handle;
+
+ /* Timeout timer for establishing the connection. */
+ uv_timer_t* handshake_timer;
+
+ /* Idle timer for connection reuse. rfc7766#section-6.2.3 */
+ uv_timer_t* idle_timer;
+ bool is_idle;
+
+ /* List of queries that have been queued (pending write callback). */
+ _output_dnssim_query_t* queued;
+
+ /* List of queries that have been sent over this connection. */
+ _output_dnssim_query_t* sent;
+
+ /* Client this connection belongs to. */
+ _output_dnssim_client_t* client;
+
+ /* State of the connection.
+ * Numeric ordering of constants is significant and follows the typical connection lifecycle.
+ * Ensure new states are added to a proper place. */
+ enum {
+ _OUTPUT_DNSSIM_CONN_INITIALIZED = 0,
+ _OUTPUT_DNSSIM_CONN_TCP_HANDSHAKE = 10,
+ _OUTPUT_DNSSIM_CONN_TLS_HANDSHAKE = 20,
+ _OUTPUT_DNSSIM_CONN_ACTIVE = 30,
+ _OUTPUT_DNSSIM_CONN_CONGESTED = 35,
+ _OUTPUT_DNSSIM_CONN_CLOSE_REQUESTED = 38,
+ _OUTPUT_DNSSIM_CONN_CLOSING = 40,
+ _OUTPUT_DNSSIM_CONN_CLOSED = 50
+ } state;
+
+ /* State of the data stream read. */
+ _output_dnssim_read_state_t read_state;
+
+ /* Total length of the expected dns data (either 2 for dnslen, or dnslen itself). */
+ size_t dnsbuf_len;
+
+ /* Current position in the receive dns buffer. */
+ size_t dnsbuf_pos;
+
+ /* Receive buffer used for incomplete messages or dnslen. */
+ char* dnsbuf_data;
+ bool dnsbuf_free_after_use;
+
+ /* Statistics interval in which the handshake is tracked. */
+ output_dnssim_stats_t* stats;
+
+ /* TLS-related data. */
+ _output_dnssim_tls_ctx_t* tls;
+
+ /* HTTP/2-related data. */
+ _output_dnssim_http2_ctx_t* http2;
+
+ /* Prevents immediate closure of connection. Instead, connection is moved
+ * to CLOSE_REQUESTED state and setter of this flag is responsible for
+ * closing the connection when clearing this flag. */
+ bool prevent_close;
+};
+
+/*
+ * Client structure.
+ */
+
+struct _output_dnssim_client {
+ /* Dnssim component this client belongs to. */
+ output_dnssim_t* dnssim;
+
+ /* List of connections.
+ * Multiple connections may be used (e.g. some are already closed for writing).
+ */
+ _output_dnssim_connection_t* conn;
+
+ /* List of queries that are pending to be sent over any available connection. */
+ _output_dnssim_query_t* pending;
+
+ /* TLS-ticket for session resumption. */
+ gnutls_datum_t tls_ticket;
+};
+
+/*
+ * DnsSim-related structures.
+ */
+
+typedef struct _output_dnssim_source _output_dnssim_source_t;
+struct _output_dnssim_source {
+ _output_dnssim_source_t* next;
+ struct sockaddr_storage addr;
+};
+
+typedef struct _output_dnssim _output_dnssim_t;
+struct _output_dnssim {
+ output_dnssim_t pub;
+
+ uv_loop_t loop;
+ uv_timer_t stats_timer;
+
+ struct sockaddr_storage target;
+ _output_dnssim_source_t* source;
+ output_dnssim_transport_t transport;
+
+ char h2_uri_authority[_MAX_URI_LEN];
+ char h2_uri_path[_MAX_URI_LEN];
+ bool h2_zero_out_msgid;
+ output_dnssim_h2_method_t h2_method;
+
+ /* Array of clients, mapped by client ID (ranges from 0 to max_clients). */
+ _output_dnssim_client_t* client_arr;
+
+ gnutls_priority_t* tls_priority;
+ gnutls_certificate_credentials_t tls_cred;
+ char wire_buf[WIRE_BUF_SIZE]; /* thread-local buffer for processing tls input */
+};
+
+/* Provides data for HTTP/2 data frames. */
+typedef struct {
+ const uint8_t* buf;
+ size_t len;
+} _output_dnssim_https2_data_provider_t;
+
+/*
+ * Forward function declarations.
+ */
+
+int _output_dnssim_bind_before_connect(output_dnssim_t* self, uv_handle_t* handle);
+int _output_dnssim_create_query_udp(output_dnssim_t* self, _output_dnssim_request_t* req);
+int _output_dnssim_create_query_tcp(output_dnssim_t* self, _output_dnssim_request_t* req);
+void _output_dnssim_close_query_udp(_output_dnssim_query_udp_t* qry);
+void _output_dnssim_close_query_tcp(_output_dnssim_query_tcp_t* qry);
+int _output_dnssim_answers_request(_output_dnssim_request_t* req, core_object_dns_t* response);
+void _output_dnssim_request_answered(_output_dnssim_request_t* req, core_object_dns_t* msg);
+void _output_dnssim_maybe_free_request(_output_dnssim_request_t* req);
+void _output_dnssim_on_uv_alloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf);
+void _output_dnssim_create_request(output_dnssim_t* self, _output_dnssim_client_t* client, core_object_payload_t* payload);
+int _output_dnssim_handle_pending_queries(_output_dnssim_client_t* client);
+int _output_dnssim_tcp_connect(output_dnssim_t* self, _output_dnssim_connection_t* conn);
+void _output_dnssim_tcp_close(_output_dnssim_connection_t* conn);
+void _output_dnssim_tcp_write_query(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry);
+void _output_dnssim_conn_close(_output_dnssim_connection_t* conn);
+void _output_dnssim_conn_idle(_output_dnssim_connection_t* conn);
+int _output_dnssim_handle_pending_queries(_output_dnssim_client_t* client);
+void _output_dnssim_conn_activate(_output_dnssim_connection_t* conn);
+void _output_dnssim_conn_maybe_free(_output_dnssim_connection_t* conn);
+void _output_dnssim_read_dns_stream(_output_dnssim_connection_t* conn, size_t len, const char* data);
+void _output_dnssim_read_dnsmsg(_output_dnssim_connection_t* conn, size_t len, const char* data);
+
+#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION
+int _output_dnssim_create_query_tls(output_dnssim_t* self, _output_dnssim_request_t* req);
+void _output_dnssim_close_query_tls(_output_dnssim_query_tcp_t* qry);
+int _output_dnssim_tls_init(_output_dnssim_connection_t* conn);
+void _output_dnssim_tls_process_input_data(_output_dnssim_connection_t* conn);
+void _output_dnssim_tls_close(_output_dnssim_connection_t* conn);
+void _output_dnssim_tls_write_query(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry);
+
+int _output_dnssim_create_query_https2(output_dnssim_t* self, _output_dnssim_request_t* req);
+void _output_dnssim_close_query_https2(_output_dnssim_query_tcp_t* qry);
+int _output_dnssim_https2_init(_output_dnssim_connection_t* conn);
+int _output_dnssim_https2_setup(_output_dnssim_connection_t* conn);
+void _output_dnssim_https2_process_input_data(_output_dnssim_connection_t* conn, size_t len, const char* data);
+void _output_dnssim_https2_close(_output_dnssim_connection_t* conn);
+void _output_dnssim_https2_write_query(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry);
+#endif
+
+#endif
diff --git a/src/output/dnssim/ll.h b/src/output/dnssim/ll.h
new file mode 100644
index 0000000..8e0b07a
--- /dev/null
+++ b/src/output/dnssim/ll.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2019-2020, CZ.NIC, z.s.p.o.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __dnsjit_output_dnssim_ll_h
+#define __dnsjit_output_dnssim_ll_h
+
+#include "core/assert.h"
+
+/* Utility macros for linked list structures.
+ *
+ * - "list" is the pointer to the first node of the linked list
+ * - "list" can be NULL if there are no nodes
+ * - every node has "next", which points to the next node (can be NULL)
+ */
+
+/* Append a node to the list.
+ *
+ * Only a single node can be appended - node->next must be NULL.
+ */
+#define _ll_append(list, node) \
+ { \
+ glassert((node)->next == NULL, "node->next must be null when appending"); \
+ if ((list) == NULL) \
+ (list) = (node); \
+ else if ((node) != NULL) { \
+ typeof(list) _current = (list); \
+ while (_current->next != NULL) \
+ _current = _current->next; \
+ _current->next = node; \
+ } \
+ }
+
+/* Remove a node from the list.
+ *
+ * In strict mode, the node must be present in the list.
+ */
+#define _ll_remove_template(list, node, strict) \
+ { \
+ if (strict) \
+ glassert((list), "list can't be null when removing nodes"); \
+ if ((list) != NULL && (node) != NULL) { \
+ if ((list) == (node)) { \
+ (list) = (node)->next; \
+ (node)->next = NULL; \
+ } else { \
+ typeof(list) _current = (list); \
+ while (_current != NULL && _current->next != (node)) { \
+ if (strict) \
+ glassert((_current->next), "list doesn't contain the node to be removed"); \
+ _current = _current->next; \
+ } \
+ if (_current != NULL) { \
+ _current->next = (node)->next; \
+ (node)->next = NULL; \
+ } \
+ } \
+ } \
+ }
+
+/* Remove a node from the list. */
+#define _ll_remove(list, node) _ll_remove_template((list), (node), true)
+
+/* Remove a node from the list if it's present. */
+#define _ll_try_remove(list, node) _ll_remove_template((list), (node), false)
+
+#endif
diff --git a/src/output/dnssim/tcp.c b/src/output/dnssim/tcp.c
new file mode 100644
index 0000000..1f2b619
--- /dev/null
+++ b/src/output/dnssim/tcp.c
@@ -0,0 +1,356 @@
+/*
+ * Copyright (c) 2019-2020, CZ.NIC, z.s.p.o.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "output/dnssim.h"
+#include "output/dnssim/internal.h"
+#include "output/dnssim/ll.h"
+#include "core/assert.h"
+
+#include <string.h>
+
+static core_log_t _log = LOG_T_INIT("output.dnssim");
+
+static void _move_queries_to_pending(_output_dnssim_query_tcp_t* qry)
+{
+ _output_dnssim_query_tcp_t* qry_tmp;
+ while (qry != NULL) {
+ mlassert(qry->conn, "query must be associated with conn");
+ mlassert(qry->conn->state == _OUTPUT_DNSSIM_CONN_CLOSED, "conn must be closed");
+ mlassert(qry->conn->client, "conn must be associated with client");
+ qry_tmp = (_output_dnssim_query_tcp_t*)qry->qry.next;
+ qry->qry.next = NULL;
+ _ll_append(qry->conn->client->pending, &qry->qry);
+ qry->conn = NULL;
+ qry->qry.state = _OUTPUT_DNSSIM_QUERY_ORPHANED;
+ qry->stream_id = -1;
+ qry->recv_buf_len = 0;
+ if (qry->recv_buf != NULL) {
+ free(qry->recv_buf);
+ qry->recv_buf = NULL;
+ }
+ qry = qry_tmp;
+ }
+}
+
+static void _on_tcp_closed(uv_handle_t* handle)
+{
+ _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)handle->data;
+ mlassert(conn, "conn is nil");
+ conn->state = _OUTPUT_DNSSIM_CONN_CLOSED;
+
+ /* Orphan any queries that are still unresolved. */
+ _move_queries_to_pending((_output_dnssim_query_tcp_t*)conn->queued);
+ conn->queued = NULL;
+ _move_queries_to_pending((_output_dnssim_query_tcp_t*)conn->sent);
+ conn->sent = NULL;
+
+ /* TODO Improve client re-connect behavior in case the connection fails to
+ * establish. Currently, queries are orphaned and attempted to be re-sent
+ * along with the next query that triggers a new connection.
+ *
+ * Attempting to establish new connection immediately leads to performance
+ * issues if the number of these attempts doesn't have upper limit. */
+ ///* Ensure orhpaned queries are re-sent over a different connection. */
+ //if (_output_dnssim_handle_pending_queries(conn->client) != 0)
+ // mlinfo("tcp: orphaned queries failed to be re-sent");
+
+ mlassert(conn->handle, "conn must have tcp handle when closing it");
+ free(conn->handle);
+ conn->handle = NULL;
+ _output_dnssim_conn_maybe_free(conn);
+}
+
+static void _on_tcp_query_written(uv_write_t* wr_req, int status)
+{
+ _output_dnssim_query_tcp_t* qry = (_output_dnssim_query_tcp_t*)wr_req->data;
+ mlassert(qry, "qry/wr_req->data is nil");
+ mlassert(qry->conn, "query must be associated with connection");
+ _output_dnssim_connection_t* conn = qry->conn;
+
+ free(((_output_dnssim_query_tcp_t*)qry)->bufs[0].base);
+
+ if (qry->qry.state == _OUTPUT_DNSSIM_QUERY_PENDING_CLOSE) {
+ qry->qry.state = status < 0 ? _OUTPUT_DNSSIM_QUERY_WRITE_FAILED : _OUTPUT_DNSSIM_QUERY_SENT;
+ _output_dnssim_request_t* req = qry->qry.req;
+ _output_dnssim_close_query_tcp(qry);
+ _output_dnssim_maybe_free_request(req);
+ qry = NULL;
+ }
+
+ if (status < 0) {
+ if (status != UV_ECANCELED)
+ mlinfo("tcp write failed: %s", uv_strerror(status));
+ if (qry != NULL)
+ qry->qry.state = _OUTPUT_DNSSIM_QUERY_WRITE_FAILED;
+ _output_dnssim_conn_close(conn);
+ return;
+ }
+
+ if (qry == NULL)
+ return;
+
+ /* Mark query as sent and assign it to connection. */
+ mlassert(qry->qry.state == _OUTPUT_DNSSIM_QUERY_PENDING_WRITE_CB, "invalid query state");
+ qry->qry.state = _OUTPUT_DNSSIM_QUERY_SENT;
+ if (qry->conn->state == _OUTPUT_DNSSIM_CONN_ACTIVE) {
+ mlassert(qry->conn->queued, "conn has no queued queries");
+ _ll_remove(qry->conn->queued, &qry->qry);
+ _ll_append(qry->conn->sent, &qry->qry);
+ }
+}
+
+void _output_dnssim_tcp_write_query(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry)
+{
+ mlassert(qry, "qry can't be null");
+ mlassert(qry->qry.state == _OUTPUT_DNSSIM_QUERY_PENDING_WRITE, "qry must be pending write");
+ mlassert(qry->qry.req, "req can't be null");
+ mlassert(qry->qry.req->dns_q, "dns_q can't be null");
+ mlassert(qry->qry.req->dns_q->obj_prev, "payload can't be null");
+ mlassert(conn, "conn can't be null");
+ mlassert(conn->state == _OUTPUT_DNSSIM_CONN_ACTIVE, "connection state != ACTIVE");
+ mlassert(conn->client, "conn must be associated with client");
+ mlassert(conn->client->pending, "conn has no pending queries");
+
+ mldebug("tcp write dnsmsg id: %04x", qry->qry.req->dns_q->id);
+
+ core_object_payload_t* payload = (core_object_payload_t*)qry->qry.req->dns_q->obj_prev;
+ uint16_t* len;
+ mlfatal_oom(len = malloc(sizeof(uint16_t)));
+ *len = htons(payload->len);
+ qry->bufs[0] = uv_buf_init((char*)len, 2);
+ qry->bufs[1] = uv_buf_init((char*)payload->payload, payload->len);
+
+ qry->conn = conn;
+ _ll_remove(conn->client->pending, &qry->qry);
+ _ll_append(conn->queued, &qry->qry);
+
+ /* Stop idle timer, since there are queries to answer now. */
+ if (conn->idle_timer != NULL) {
+ conn->is_idle = false;
+ uv_timer_stop(conn->idle_timer);
+ }
+
+ qry->write_req.data = (void*)qry;
+ uv_write(&qry->write_req, (uv_stream_t*)conn->handle, qry->bufs, 2, _on_tcp_query_written);
+ qry->qry.state = _OUTPUT_DNSSIM_QUERY_PENDING_WRITE_CB;
+}
+
+static void _on_tcp_read(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf)
+{
+ _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)handle->data;
+ output_dnssim_t* self = conn->client->dnssim;
+
+ if (nread > 0) {
+ mldebug("tcp nread: %d", nread);
+ switch (_self->transport) {
+ case OUTPUT_DNSSIM_TRANSPORT_TCP:
+ _output_dnssim_read_dns_stream(conn, nread, buf->base);
+ break;
+ case OUTPUT_DNSSIM_TRANSPORT_TLS:
+ case OUTPUT_DNSSIM_TRANSPORT_HTTPS2:
+#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION
+ mlassert(conn->tls, "con must have tls ctx");
+ conn->tls->buf = (uint8_t*)buf->base;
+ conn->tls->buf_pos = 0;
+ conn->tls->buf_len = nread;
+ _output_dnssim_tls_process_input_data(conn);
+#else
+ mlfatal(DNSSIM_MIN_GNUTLS_ERRORMSG);
+#endif
+ break;
+ default:
+ mlfatal("unsupported transport");
+ break;
+ }
+ } else if (nread < 0) {
+ if (nread != UV_EOF)
+ mlinfo("tcp conn unexpected close: %s", uv_strerror(nread));
+ _output_dnssim_conn_close(conn);
+ }
+
+ if (buf->base != NULL)
+ free(buf->base);
+}
+
+static void _on_tcp_connected(uv_connect_t* conn_req, int status)
+{
+ _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)conn_req->handle->data;
+ mlassert(conn, "conn is nil");
+
+ free(conn_req);
+
+ if (status < 0) {
+ mldebug("tcp connect failed: %s", uv_strerror(status));
+ _output_dnssim_conn_close(conn);
+ return;
+ }
+
+ mlassert(conn->state == _OUTPUT_DNSSIM_CONN_TCP_HANDSHAKE, "connection state != TCP_HANDSHAKE");
+ int ret = uv_read_start((uv_stream_t*)conn->handle, _output_dnssim_on_uv_alloc, _on_tcp_read);
+ if (ret < 0) {
+ mlwarning("tcp uv_read_start() failed: %s", uv_strerror(ret));
+ _output_dnssim_conn_close(conn);
+ return;
+ }
+
+ mldebug("tcp connected");
+ mlassert(conn->client, "conn must be associated with a client");
+ mlassert(conn->client->dnssim, "client must be associated with dnssim");
+ output_dnssim_t* self = conn->client->dnssim;
+ switch (_self->transport) {
+ case OUTPUT_DNSSIM_TRANSPORT_TCP:
+ _output_dnssim_conn_activate(conn);
+ break;
+ case OUTPUT_DNSSIM_TRANSPORT_TLS:
+ case OUTPUT_DNSSIM_TRANSPORT_HTTPS2:
+#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION
+ mldebug("init tls handshake");
+ _output_dnssim_tls_process_input_data(conn); /* Initiate TLS handshake. */
+#else
+ mlfatal(DNSSIM_MIN_GNUTLS_ERRORMSG);
+#endif
+ break;
+ default:
+ lfatal("unsupported transport protocol");
+ break;
+ }
+}
+
+static void _on_connection_timeout(uv_timer_t* handle)
+{
+ _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)handle->data;
+ _output_dnssim_conn_close(conn);
+}
+
+int _output_dnssim_tcp_connect(output_dnssim_t* self, _output_dnssim_connection_t* conn)
+{
+ mlassert_self();
+ lassert(conn, "connection can't be null");
+ lassert(conn->handle == NULL, "connection already has a handle");
+ lassert(conn->handshake_timer == NULL, "connection already has a handshake timer");
+ lassert(conn->idle_timer == NULL, "connection already has idle timer");
+ lassert(conn->state == _OUTPUT_DNSSIM_CONN_INITIALIZED, "connection state != INITIALIZED");
+
+ lfatal_oom(conn->handle = malloc(sizeof(uv_tcp_t)));
+ conn->handle->data = (void*)conn;
+ int ret = uv_tcp_init(&_self->loop, conn->handle);
+ if (ret < 0) {
+ lwarning("failed to init uv_tcp_t");
+ goto failure;
+ }
+
+ ret = _output_dnssim_bind_before_connect(self, (uv_handle_t*)conn->handle);
+ if (ret < 0)
+ goto failure;
+
+ /* Set connection parameters. */
+ ret = uv_tcp_nodelay(conn->handle, 1);
+ if (ret < 0)
+ lwarning("tcp: failed to set TCP_NODELAY: %s", uv_strerror(ret));
+
+ /* Set connection handshake timeout. */
+ lfatal_oom(conn->handshake_timer = malloc(sizeof(uv_timer_t)));
+ uv_timer_init(&_self->loop, conn->handshake_timer);
+ conn->handshake_timer->data = (void*)conn;
+ uv_timer_start(conn->handshake_timer, _on_connection_timeout, self->handshake_timeout_ms, 0);
+
+ /* Set idle connection timer. */
+ if (self->idle_timeout_ms > 0) {
+ lfatal_oom(conn->idle_timer = malloc(sizeof(uv_timer_t)));
+ uv_timer_init(&_self->loop, conn->idle_timer);
+ conn->idle_timer->data = (void*)conn;
+
+ /* Start and stop the timer to set the repeat value without running the timer. */
+ uv_timer_start(conn->idle_timer, _on_connection_timeout, self->idle_timeout_ms, self->idle_timeout_ms);
+ uv_timer_stop(conn->idle_timer);
+ }
+
+ mldebug("tcp connecting");
+ uv_connect_t* conn_req;
+ lfatal_oom(conn_req = malloc(sizeof(uv_connect_t)));
+ ret = uv_tcp_connect(conn_req, conn->handle, (struct sockaddr*)&_self->target, _on_tcp_connected);
+ if (ret < 0)
+ goto failure;
+
+ conn->stats->conn_handshakes++;
+ conn->client->dnssim->stats_sum->conn_handshakes++;
+ conn->state = _OUTPUT_DNSSIM_CONN_TCP_HANDSHAKE;
+ return 0;
+failure:
+ _output_dnssim_conn_close(conn);
+ return ret;
+}
+
+void _output_dnssim_tcp_close(_output_dnssim_connection_t* conn)
+{
+ mlassert(conn, "conn can't be nil");
+
+ if (conn->handle != NULL) {
+ uv_read_stop((uv_stream_t*)conn->handle);
+ uv_close((uv_handle_t*)conn->handle, _on_tcp_closed);
+ }
+}
+
+int _output_dnssim_create_query_tcp(output_dnssim_t* self, _output_dnssim_request_t* req)
+{
+ mlassert_self();
+ lassert(req, "req is nil");
+ lassert(req->client, "request must have a client associated with it");
+
+ _output_dnssim_query_tcp_t* qry;
+
+ lfatal_oom(qry = calloc(1, sizeof(_output_dnssim_query_tcp_t)));
+
+ qry->qry.transport = OUTPUT_DNSSIM_TRANSPORT_TCP;
+ qry->qry.req = req;
+ qry->qry.state = _OUTPUT_DNSSIM_QUERY_PENDING_WRITE;
+ req->qry = &qry->qry; // TODO change when adding support for multiple Qs for req
+ _ll_append(req->client->pending, &qry->qry);
+
+ return _output_dnssim_handle_pending_queries(req->client);
+}
+
+void _output_dnssim_close_query_tcp(_output_dnssim_query_tcp_t* qry)
+{
+ mlassert(qry, "qry can't be null");
+ mlassert(qry->qry.req, "query must be part of a request");
+ _output_dnssim_request_t* req = qry->qry.req;
+ mlassert(req->client, "request must belong to a client");
+
+ if ((qry->qry.state == _OUTPUT_DNSSIM_QUERY_PENDING_WRITE_CB || qry->qry.state == _OUTPUT_DNSSIM_QUERY_PENDING_CLOSE)) {
+ /* Query can't be freed until uv callback is called. */
+ qry->qry.state = _OUTPUT_DNSSIM_QUERY_PENDING_CLOSE;
+ return;
+ }
+
+ _ll_try_remove(req->client->pending, &qry->qry);
+ if (qry->conn) {
+ _output_dnssim_connection_t* conn = qry->conn;
+ _ll_try_remove(conn->queued, &qry->qry); /* edge-case of cancelled queries */
+ _ll_try_remove(conn->sent, &qry->qry);
+ qry->conn = NULL;
+ _output_dnssim_conn_idle(conn);
+ }
+
+ _ll_remove(req->qry, &qry->qry);
+ free(qry);
+}
diff --git a/src/output/dnssim/tls.c b/src/output/dnssim/tls.c
new file mode 100644
index 0000000..e87ca47
--- /dev/null
+++ b/src/output/dnssim/tls.c
@@ -0,0 +1,475 @@
+/*
+ * Copyright (c) 2020, CZ.NIC, z.s.p.o.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "output/dnssim.h"
+#include "output/dnssim/internal.h"
+#include "output/dnssim/ll.h"
+#include "core/assert.h"
+
+#include <gnutls/gnutls.h>
+#include <string.h>
+
+#if GNUTLS_VERSION_NUMBER >= DNSSIM_MIN_GNUTLS_VERSION
+
+#ifndef MIN
+#define MIN(a, b) (((a) < (b)) ? (a) : (b)) /** Minimum of two numbers **/
+#endif
+
+static core_log_t _log = LOG_T_INIT("output.dnssim");
+
+struct async_write_ctx {
+ uv_write_t write_req;
+ _output_dnssim_connection_t* conn;
+ char buf[];
+};
+
+static int _tls_handshake(_output_dnssim_connection_t* conn)
+{
+ mlassert(conn, "conn is nil");
+ mlassert(conn->tls, "conn must have tls context");
+ mlassert(conn->client, "conn must belong to a client");
+ mlassert(conn->state <= _OUTPUT_DNSSIM_CONN_TLS_HANDSHAKE, "conn in invalid state");
+
+ /* Set TLS session resumption ticket if available. */
+ if (conn->state < _OUTPUT_DNSSIM_CONN_TLS_HANDSHAKE && conn->client->tls_ticket.size != 0) {
+ gnutls_datum_t* ticket = &conn->client->tls_ticket;
+ gnutls_session_set_data(conn->tls->session, ticket->data, ticket->size);
+ }
+ conn->state = _OUTPUT_DNSSIM_CONN_TLS_HANDSHAKE;
+
+ return gnutls_handshake(conn->tls->session);
+}
+
+void _output_dnssim_tls_process_input_data(_output_dnssim_connection_t* conn)
+{
+ mlassert(conn, "conn is nil");
+ mlassert(conn->client, "conn must have client");
+ mlassert(conn->client->dnssim, "client must have dnssim");
+ mlassert(conn->tls, "conn must have tls ctx");
+
+ if (conn->state >= _OUTPUT_DNSSIM_CONN_CLOSING)
+ return;
+
+ output_dnssim_t* self = conn->client->dnssim;
+
+ /* Ensure TLS handshake is performed before receiving data.
+ * See https://www.gnutls.org/manual/html_node/TLS-handshake.html */
+ while (conn->state <= _OUTPUT_DNSSIM_CONN_TLS_HANDSHAKE) {
+ int err = _tls_handshake(conn);
+ mldebug("tls handshake returned: %s", gnutls_strerror(err));
+ if (err == GNUTLS_E_SUCCESS) {
+ if (gnutls_session_is_resumed(conn->tls->session))
+ conn->stats->conn_resumed++;
+ if (_self->transport == OUTPUT_DNSSIM_TRANSPORT_HTTPS2) {
+ if (_output_dnssim_https2_setup(conn) < 0) {
+ _output_dnssim_conn_close(conn);
+ return;
+ }
+ }
+ _output_dnssim_conn_activate(conn);
+ break;
+ } else if (err == GNUTLS_E_AGAIN) {
+ return; /* Wait for more data */
+ } else if (err == GNUTLS_E_FATAL_ALERT_RECEIVED) {
+ gnutls_alert_description_t alert = gnutls_alert_get(conn->tls->session);
+ mlwarning("gnutls_handshake failed: %s", gnutls_alert_get_name(alert));
+ _output_dnssim_conn_close(conn);
+ return;
+ } else if (gnutls_error_is_fatal(err)) {
+ mlwarning("gnutls_handshake failed: %s", gnutls_strerror_name(err));
+ _output_dnssim_conn_close(conn);
+ return;
+ }
+ }
+
+ /* See https://gnutls.org/manual/html_node/Data-transfer-and-termination.html#Data-transfer-and-termination */
+ while (true) {
+ /* Connection might have been closed due to an error, don't try to use it. */
+ if (conn->state < _OUTPUT_DNSSIM_CONN_ACTIVE || conn->state >= _OUTPUT_DNSSIM_CONN_CLOSING)
+ return;
+
+ ssize_t count = gnutls_record_recv(conn->tls->session, _self->wire_buf, WIRE_BUF_SIZE);
+ if (count > 0) {
+ switch (_self->transport) {
+ case OUTPUT_DNSSIM_TRANSPORT_TLS:
+ _output_dnssim_read_dns_stream(conn, count, _self->wire_buf);
+ break;
+ case OUTPUT_DNSSIM_TRANSPORT_HTTPS2:
+ _output_dnssim_https2_process_input_data(conn, count, _self->wire_buf);
+ break;
+ default:
+ lfatal("unsupported transport layer");
+ break;
+ }
+ } else if (count == GNUTLS_E_AGAIN) {
+ if (conn->tls->buf_pos == conn->tls->buf_len) {
+ /* See https://www.gnutls.org/manual/html_node/Asynchronous-operation.html */
+ break; /* No more data available in this libuv buffer */
+ }
+ continue;
+ } else if (count == GNUTLS_E_INTERRUPTED) {
+ continue;
+ } else if (count == GNUTLS_E_REHANDSHAKE) {
+ continue; /* Ignore rehandshake request. */
+ } else if (count < 0) {
+ mlwarning("gnutls_record_recv failed: %s", gnutls_strerror_name(count));
+ _output_dnssim_conn_close(conn);
+ return;
+ } else if (count == 0) {
+ break;
+ }
+ }
+ mlassert(conn->tls->buf_len == conn->tls->buf_pos, "tls didn't read the entire buffer");
+}
+
+static ssize_t _tls_pull(gnutls_transport_ptr_t ptr, void* buf, size_t len)
+{
+ _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)ptr;
+ mlassert(conn != NULL, "conn is null");
+ mlassert(conn->tls != NULL, "conn must have tls ctx");
+
+ ssize_t avail = conn->tls->buf_len - conn->tls->buf_pos;
+ if (avail <= 0) {
+ mldebug("tls pull: no more data");
+ errno = EAGAIN;
+ return -1;
+ }
+
+ ssize_t transfer = MIN(avail, len);
+ memcpy(buf, conn->tls->buf + conn->tls->buf_pos, transfer);
+ conn->tls->buf_pos += transfer;
+ return transfer;
+}
+
+static void _tls_on_write_complete(uv_write_t* req, int status)
+{
+ mlassert(req->data != NULL, "uv_write req has no data pointer");
+ struct async_write_ctx* async_ctx = (struct async_write_ctx*)req->data;
+ _output_dnssim_connection_t* conn = async_ctx->conn;
+ mlassert(conn, "conn is nil");
+ mlassert(conn->tls, "conn must have tls ctx");
+ mlassert(conn->tls->write_queue_size > 0, "invalid write_queue_size: %d", conn->tls->write_queue_size);
+ conn->tls->write_queue_size -= 1;
+ free(req->data);
+
+ if (status < 0)
+ _output_dnssim_conn_close(conn);
+}
+
+static ssize_t _tls_vec_push(gnutls_transport_ptr_t ptr, const giovec_t* iov, int iovcnt)
+{
+ _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)ptr;
+ mlassert(conn != NULL, "conn is null");
+ mlassert(conn->tls != NULL, "conn must have tls ctx");
+
+ if (iovcnt == 0)
+ return 0;
+
+ /*
+ * This is a little bit complicated. There are two different writes:
+ * 1. Immediate, these don't need to own the buffered data and return immediately
+ * 2. Asynchronous, these need to own the buffers until the write completes
+ * In order to avoid copying the buffer, an immediate write is tried first if possible.
+ * If it isn't possible to write the data without queueing, an asynchronous write
+ * is created (with copied buffered data).
+ */
+
+ size_t total_len = 0;
+ uv_buf_t uv_buf[iovcnt];
+ int i;
+ for (i = 0; i < iovcnt; ++i) {
+ uv_buf[i].base = iov[i].iov_base;
+ uv_buf[i].len = iov[i].iov_len;
+ total_len += iov[i].iov_len;
+ }
+
+ /* Try to perform the immediate write first to avoid copy */
+ int ret = 0;
+ if (conn->tls->write_queue_size == 0) {
+ ret = uv_try_write((uv_stream_t*)conn->handle, uv_buf, iovcnt);
+ /* from libuv documentation -
+ uv_try_write will return either:
+ > 0: number of bytes written (can be less than the supplied buffer size).
+ < 0: negative error code (UV_EAGAIN is returned if no data can be sent immediately).
+ */
+ if (ret == total_len) {
+ /* All the data were buffered by libuv.
+ * Return. */
+ return ret;
+ }
+
+ if (ret < 0 && ret != UV_EAGAIN) {
+ /* uv_try_write() has returned error code other then UV_EAGAIN.
+ * Return. */
+ errno = EIO;
+ return -1;
+ }
+ /* Since we are here expression below is true
+ * (ret != total_len) && (ret >= 0 || ret == UV_EAGAIN)
+ * or the same
+ * (ret != total_len && ret >= 0) || (ret != total_len && ret == UV_EAGAIN)
+ * i.e. either occurs partial write or UV_EAGAIN.
+ * Proceed and copy data amount to owned memory and perform async write.
+ */
+ if (ret == UV_EAGAIN) {
+ /* No data were buffered, so we must buffer all the data. */
+ ret = 0;
+ }
+ }
+
+ /* Fallback when the queue is full, and it's not possible to do an immediate write */
+ char* p = malloc(sizeof(struct async_write_ctx) + total_len - ret);
+ if (p != NULL) {
+ struct async_write_ctx* async_ctx = (struct async_write_ctx*)p;
+ async_ctx->conn = conn;
+ char* buf = async_ctx->buf;
+ /* Skip data written in the partial write */
+ size_t to_skip = ret;
+ /* Copy the buffer into owned memory */
+ size_t off = 0;
+ int i;
+ for (i = 0; i < iovcnt; ++i) {
+ if (to_skip > 0) {
+ /* Ignore current buffer if it's all skipped */
+ if (to_skip >= uv_buf[i].len) {
+ to_skip -= uv_buf[i].len;
+ continue;
+ }
+ /* Skip only part of the buffer */
+ uv_buf[i].base += to_skip;
+ uv_buf[i].len -= to_skip;
+ to_skip = 0;
+ }
+ memcpy(buf + off, uv_buf[i].base, uv_buf[i].len);
+ off += uv_buf[i].len;
+ }
+ uv_buf[0].base = buf;
+ uv_buf[0].len = off;
+
+ /* Create an asynchronous write request */
+ uv_write_t* write_req = &async_ctx->write_req;
+ memset(write_req, 0, sizeof(uv_write_t));
+ write_req->data = p;
+
+ /* Perform an asynchronous write with a callback */
+ if (uv_write(write_req, (uv_stream_t*)conn->handle, uv_buf, 1, _tls_on_write_complete) == 0) {
+ ret = total_len;
+ conn->tls->write_queue_size += 1;
+ } else {
+ free(p);
+ errno = EIO;
+ ret = -1;
+ }
+ } else {
+ errno = ENOMEM;
+ ret = -1;
+ }
+
+ return ret;
+}
+
+int _tls_pull_timeout(gnutls_transport_ptr_t ptr, unsigned int ms)
+{
+ _output_dnssim_connection_t* conn = (_output_dnssim_connection_t*)ptr;
+ mlassert(conn != NULL, "conn is null");
+ mlassert(conn->tls != NULL, "conn must have tls ctx");
+
+ ssize_t avail = conn->tls->buf_len - conn->tls->buf_pos;
+ if (avail <= 0) {
+ errno = EAGAIN;
+ return -1;
+ }
+ return avail;
+}
+
+int _output_dnssim_tls_init(_output_dnssim_connection_t* conn)
+{
+ mlassert(conn, "conn is nil");
+ mlassert(conn->tls == NULL, "conn already has tls context");
+
+ int ret;
+ mlfatal_oom(conn->tls = malloc(sizeof(_output_dnssim_tls_ctx_t)));
+ conn->tls->buf = NULL;
+ conn->tls->buf_len = 0;
+ conn->tls->buf_pos = 0;
+ conn->tls->write_queue_size = 0;
+
+ ret = gnutls_init(&conn->tls->session, GNUTLS_CLIENT | GNUTLS_NONBLOCK);
+ if (ret < 0) {
+ mldebug("failed gnutls_init() (%s)", gnutls_strerror(ret));
+ free(conn->tls);
+ conn->tls = 0;
+ return ret;
+ }
+
+ output_dnssim_t* self = conn->client->dnssim;
+ if (_self->tls_priority == NULL) {
+ ret = gnutls_set_default_priority(conn->tls->session);
+ if (ret < 0) {
+ mldebug("failed gnutls_set_default_priority() (%s)", gnutls_strerror(ret));
+ gnutls_deinit(conn->tls->session);
+ free(conn->tls);
+ conn->tls = 0;
+ return ret;
+ }
+ } else {
+ ret = gnutls_priority_set(conn->tls->session, *_self->tls_priority);
+ if (ret < 0) {
+ mldebug("failed gnutls_priority_set() (%s)", gnutls_strerror(ret));
+ gnutls_deinit(conn->tls->session);
+ free(conn->tls);
+ conn->tls = 0;
+ return ret;
+ }
+ }
+
+ ret = gnutls_credentials_set(conn->tls->session, GNUTLS_CRD_CERTIFICATE, _self->tls_cred);
+ if (ret < 0) {
+ mldebug("failed gnutls_credentials_set() (%s)", gnutls_strerror(ret));
+ gnutls_deinit(conn->tls->session);
+ free(conn->tls);
+ conn->tls = 0;
+ return ret;
+ }
+
+ gnutls_transport_set_pull_function(conn->tls->session, _tls_pull);
+ gnutls_transport_set_pull_timeout_function(conn->tls->session, _tls_pull_timeout);
+ gnutls_transport_set_vec_push_function(conn->tls->session, _tls_vec_push);
+ gnutls_transport_set_ptr(conn->tls->session, conn);
+
+ return 0;
+}
+
+int _output_dnssim_create_query_tls(output_dnssim_t* self, _output_dnssim_request_t* req)
+{
+ mlassert_self();
+ lassert(req, "req is nil");
+ lassert(req->client, "request must have a client associated with it");
+
+ _output_dnssim_query_tcp_t* qry;
+
+ lfatal_oom(qry = calloc(1, sizeof(_output_dnssim_query_tcp_t)));
+
+ qry->qry.transport = OUTPUT_DNSSIM_TRANSPORT_TLS;
+ qry->qry.req = req;
+ qry->qry.state = _OUTPUT_DNSSIM_QUERY_PENDING_WRITE;
+ req->qry = &qry->qry; // TODO change when adding support for multiple Qs for req
+ _ll_append(req->client->pending, &qry->qry);
+
+ return _output_dnssim_handle_pending_queries(req->client);
+}
+
+void _output_dnssim_close_query_tls(_output_dnssim_query_tcp_t* qry)
+{
+ mlassert(qry, "qry can't be null");
+ mlassert(qry->qry.req, "query must be part of a request");
+ _output_dnssim_request_t* req = qry->qry.req;
+ mlassert(req->client, "request must belong to a client");
+
+ _ll_try_remove(req->client->pending, &qry->qry);
+ if (qry->conn) {
+ _output_dnssim_connection_t* conn = qry->conn;
+ _ll_try_remove(conn->sent, &qry->qry);
+ qry->conn = NULL;
+ _output_dnssim_conn_idle(conn);
+ }
+
+ _ll_remove(req->qry, &qry->qry);
+ free(qry);
+}
+
+void _output_dnssim_tls_close(_output_dnssim_connection_t* conn)
+{
+ mlassert(conn, "conn can't be nil");
+ mlassert(conn->tls, "conn must have tls ctx");
+ mlassert(conn->client, "conn must belong to a client");
+
+ /* Try and get a TLS session ticket for potential resumption. */
+ int ret;
+ if (gnutls_session_get_flags(conn->tls->session) & GNUTLS_SFLAGS_SESSION_TICKET) {
+ if (conn->client->tls_ticket.size != 0) {
+ gnutls_free(conn->client->tls_ticket.data);
+ }
+ ret = gnutls_session_get_data2(conn->tls->session, &conn->client->tls_ticket);
+ if (ret < 0) {
+ mldebug("gnutls_session_get_data2 failed: %s", gnutls_strerror(ret));
+ conn->client->tls_ticket.size = 0;
+ }
+ }
+
+ gnutls_deinit(conn->tls->session);
+ _output_dnssim_tcp_close(conn);
+}
+
+void _output_dnssim_tls_write_query(_output_dnssim_connection_t* conn, _output_dnssim_query_tcp_t* qry)
+{
+ mlassert(qry, "qry can't be null");
+ mlassert(qry->qry.state == _OUTPUT_DNSSIM_QUERY_PENDING_WRITE, "qry must be pending write");
+ mlassert(qry->qry.req, "req can't be null");
+ mlassert(qry->qry.req->dns_q, "dns_q can't be null");
+ mlassert(qry->qry.req->dns_q->obj_prev, "payload can't be null");
+ mlassert(conn, "conn can't be null");
+ mlassert(conn->state == _OUTPUT_DNSSIM_CONN_ACTIVE, "connection state != ACTIVE");
+ mlassert(conn->tls, "conn must have tls ctx");
+ mlassert(conn->client, "conn must be associated with client");
+ mlassert(conn->client->pending, "conn has no pending queries");
+
+ core_object_payload_t* payload = (core_object_payload_t*)qry->qry.req->dns_q->obj_prev;
+ uint16_t len = htons(payload->len);
+
+ gnutls_record_cork(conn->tls->session);
+ ssize_t count = 0;
+ if ((count = gnutls_record_send(conn->tls->session, &len, sizeof(len)) < 0) || (count = gnutls_record_send(conn->tls->session, payload->payload, payload->len) < 0)) {
+ mlwarning("gnutls_record_send failed: %s", gnutls_strerror_name(count));
+ _output_dnssim_conn_close(conn);
+ return;
+ }
+
+ const ssize_t submitted = sizeof(len) + payload->len;
+
+ int ret = gnutls_record_uncork(conn->tls->session, GNUTLS_RECORD_WAIT);
+ if (gnutls_error_is_fatal(ret)) {
+ mlinfo("gnutls_record_uncorck failed: %s", gnutls_strerror_name(ret));
+ _output_dnssim_conn_close(conn);
+ return;
+ }
+
+ if (ret != submitted) {
+ mlwarning("gnutls_record_uncork didn't send all data");
+ _output_dnssim_conn_close(conn);
+ return;
+ }
+
+ qry->conn = conn;
+ _ll_remove(conn->client->pending, &qry->qry);
+ _ll_append(conn->sent, &qry->qry);
+
+ /* Stop idle timer, since there are queries to answer now. */
+ if (conn->idle_timer != NULL) {
+ conn->is_idle = false;
+ uv_timer_stop(conn->idle_timer);
+ }
+
+ qry->qry.state = _OUTPUT_DNSSIM_QUERY_SENT;
+}
+
+#endif
diff --git a/src/output/dnssim/udp.c b/src/output/dnssim/udp.c
new file mode 100644
index 0000000..74f8569
--- /dev/null
+++ b/src/output/dnssim/udp.c
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2019-2020, CZ.NIC, z.s.p.o.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "output/dnssim.h"
+#include "output/dnssim/internal.h"
+#include "output/dnssim/ll.h"
+#include "core/assert.h"
+
+static core_log_t _log = LOG_T_INIT("output.dnssim");
+
+static int _process_udp_response(uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf)
+{
+ _output_dnssim_query_udp_t* qry = (_output_dnssim_query_udp_t*)handle->data;
+ _output_dnssim_request_t* req;
+ core_object_payload_t payload = CORE_OBJECT_PAYLOAD_INIT(NULL);
+ core_object_dns_t dns_a = CORE_OBJECT_DNS_INIT(&payload);
+ mlassert(qry, "qry is nil");
+ mlassert(qry->qry.req, "query must be part of a request");
+ req = qry->qry.req;
+
+ payload.payload = (uint8_t*)buf->base;
+ payload.len = nread;
+
+ dns_a.obj_prev = (core_object_t*)&payload;
+ int ret = core_object_dns_parse_header(&dns_a);
+ if (ret != 0) {
+ mldebug("udp response malformed");
+ return _ERR_MALFORMED;
+ }
+ if (dns_a.id != req->dns_q->id) {
+ mldebug("udp response msgid mismatch %x(q) != %x(a)", req->dns_q->id, dns_a.id);
+ return _ERR_MSGID;
+ }
+ if (dns_a.tc == 1) {
+ mldebug("udp response has TC=1");
+ return _ERR_TC;
+ }
+ ret = _output_dnssim_answers_request(req, &dns_a);
+ if (ret != 0) {
+ mlwarning("udp reponse question mismatch");
+ return _ERR_QUESTION;
+ }
+
+ _output_dnssim_request_answered(req, &dns_a);
+ return 0;
+}
+
+static void _on_udp_query_recv(uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags)
+{
+ if (nread > 0) {
+ mldebug("udp recv: %d", nread);
+
+ // TODO handle TC=1
+ _process_udp_response(handle, nread, buf);
+ }
+
+ if (buf->base != NULL) {
+ free(buf->base);
+ }
+}
+
+static void _on_query_udp_closed(uv_handle_t* handle)
+{
+ _output_dnssim_query_udp_t* qry = (_output_dnssim_query_udp_t*)handle->data;
+ _output_dnssim_request_t* req;
+ mlassert(qry, "qry is nil");
+ mlassert(qry->qry.req, "query must be part of a request");
+ req = qry->qry.req;
+
+ free(qry->handle);
+
+ _ll_remove(req->qry, &qry->qry);
+ free(qry);
+
+ if (req->qry == NULL)
+ _output_dnssim_maybe_free_request(req);
+}
+
+void _output_dnssim_close_query_udp(_output_dnssim_query_udp_t* qry)
+{
+ int ret;
+ mlassert(qry, "qry is nil");
+
+ ret = uv_udp_recv_stop(qry->handle);
+ if (ret < 0) {
+ mldebug("failed uv_udp_recv_stop(): %s", uv_strerror(ret));
+ }
+
+ uv_close((uv_handle_t*)qry->handle, _on_query_udp_closed);
+}
+
+int _output_dnssim_create_query_udp(output_dnssim_t* self, _output_dnssim_request_t* req)
+{
+ int ret;
+ _output_dnssim_query_udp_t* qry;
+ core_object_payload_t* payload;
+ mlassert_self();
+ lassert(req, "req is nil");
+ payload = (core_object_payload_t*)req->dns_q->obj_prev;
+
+ lfatal_oom(qry = calloc(1, sizeof(_output_dnssim_query_udp_t)));
+ lfatal_oom(qry->handle = malloc(sizeof(uv_udp_t)));
+
+ qry->qry.transport = OUTPUT_DNSSIM_TRANSPORT_UDP;
+ qry->qry.req = req;
+ qry->buf = uv_buf_init((char*)payload->payload, payload->len);
+ qry->handle->data = (void*)qry;
+ ret = uv_udp_init(&_self->loop, qry->handle);
+ if (ret < 0) {
+ lwarning("failed to init uv_udp_t");
+ goto failure;
+ }
+ _ll_append(req->qry, &qry->qry);
+
+ ret = _output_dnssim_bind_before_connect(self, (uv_handle_t*)qry->handle);
+ if (ret < 0)
+ return ret;
+
+ ret = uv_udp_try_send(qry->handle, &qry->buf, 1, (struct sockaddr*)&_self->target);
+ if (ret < 0) {
+ lwarning("failed to send udp packet: %s", uv_strerror(ret));
+ return ret;
+ }
+
+ // listen for reply
+ ret = uv_udp_recv_start(qry->handle, _output_dnssim_on_uv_alloc, _on_udp_query_recv);
+ if (ret < 0) {
+ lwarning("failed uv_udp_recv_start(): %s", uv_strerror(ret));
+ return ret;
+ }
+
+ return 0;
+failure:
+ free(qry->handle);
+ free(qry);
+ return ret;
+}
diff --git a/src/output/null.c b/src/output/null.c
new file mode 100644
index 0000000..9360afd
--- /dev/null
+++ b/src/output/null.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "output/null.h"
+#include "core/assert.h"
+#include "core/object/pcap.h"
+
+static core_log_t _log = LOG_T_INIT("output.null");
+static output_null_t _defaults = {
+ LOG_T_INIT_OBJ("output.null"),
+ 0, 0, 0
+};
+
+core_log_t* output_null_log()
+{
+ return &_log;
+}
+
+void output_null_init(output_null_t* self)
+{
+ mlassert_self();
+
+ *self = _defaults;
+}
+
+void output_null_destroy(output_null_t* self)
+{
+ mlassert_self();
+}
+
+static void _receive(output_null_t* self, const core_object_t* obj)
+{
+ mlassert_self();
+
+ self->pkts++;
+}
+
+core_receiver_t output_null_receiver()
+{
+ return (core_receiver_t)_receive;
+}
+
+void output_null_run(output_null_t* self, int64_t num)
+{
+ mlassert_self();
+
+ if (!self->prod) {
+ lfatal("no producer set");
+ }
+
+ if (num > 0) {
+ while (num--) {
+ const core_object_t* obj = self->prod(self->ctx);
+ if (!obj)
+ break;
+
+ self->pkts++;
+ }
+ } else {
+ for (;;) {
+ const core_object_t* obj = self->prod(self->ctx);
+ if (!obj)
+ break;
+
+ self->pkts++;
+ }
+ }
+}
diff --git a/src/output/null.h b/src/output/null.h
new file mode 100644
index 0000000..b8ccf70
--- /dev/null
+++ b/src/output/null.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/log.h"
+#include "core/receiver.h"
+#include "core/producer.h"
+
+#ifndef __dnsjit_output_null_h
+#define __dnsjit_output_null_h
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "output/null.hh"
+
+#endif
diff --git a/src/output/null.hh b/src/output/null.hh
new file mode 100644
index 0000000..3f5cd33
--- /dev/null
+++ b/src/output/null.hh
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+//lua:require("dnsjit.core.producer_h")
+
+typedef struct output_null {
+ core_log_t _log;
+ core_producer_t prod;
+ void* ctx;
+ size_t pkts;
+} output_null_t;
+
+core_log_t* output_null_log();
+void output_null_init(output_null_t* self);
+void output_null_destroy(output_null_t* self);
+void output_null_run(output_null_t* self, int64_t num);
+
+core_receiver_t output_null_receiver();
diff --git a/src/output/null.lua b/src/output/null.lua
new file mode 100644
index 0000000..b634c56
--- /dev/null
+++ b/src/output/null.lua
@@ -0,0 +1,80 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.output.null
+-- Output to nothing (/dev/null)
+-- local output = require("dnsjit.output.null").new()
+--
+-- Output module for those that doesn't really like packets.
+module(...,package.seeall)
+
+require("dnsjit.output.null_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "output_null_t"
+local output_null_t = ffi.typeof(t_name)
+local Null = {}
+
+-- Create a new Null output.
+function Null.new()
+ local self = {
+ _producer = nil,
+ obj = output_null_t(),
+ }
+ C.output_null_init(self.obj)
+ ffi.gc(self.obj, C.output_null_destroy)
+ return setmetatable(self, { __index = Null })
+end
+
+-- Return the Log object to control logging of this instance or module.
+function Null:log()
+ if self == nil then
+ return C.output_null_log()
+ end
+ return self.obj._log
+end
+
+-- Return the C functions and context for receiving objects.
+function Null:receive()
+ return C.output_null_receiver(), self.obj
+end
+
+-- Set the producer to get objects from.
+function Null:producer(o)
+ self.obj.prod, self.obj.ctx = o:produce()
+ self._producer = o
+end
+
+-- Retrieve all objects from the producer, if the optional
+-- .I num
+-- is a positive number then stop after that amount of objects have been
+-- retrieved.
+function Null:run(num)
+ if num == nil then
+ num = -1
+ end
+ C.output_null_run(self.obj, num)
+end
+
+-- Return the number of packets we sent into the void.
+function Null:packets()
+ return tonumber(self.obj.pkts)
+end
+
+return Null
diff --git a/src/output/pcap.c b/src/output/pcap.c
new file mode 100644
index 0000000..2b2aaa8
--- /dev/null
+++ b/src/output/pcap.c
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "output/pcap.h"
+#include "core/assert.h"
+#include "core/object/pcap.h"
+
+static core_log_t _log = LOG_T_INIT("output.pcap");
+static output_pcap_t _defaults = {
+ LOG_T_INIT_OBJ("output.pcap"),
+ 0, 0
+};
+
+core_log_t* output_pcap_log()
+{
+ return &_log;
+}
+
+void output_pcap_init(output_pcap_t* self)
+{
+ mlassert_self();
+
+ *self = _defaults;
+}
+
+void output_pcap_destroy(output_pcap_t* self)
+{
+ mlassert_self();
+}
+
+int output_pcap_open(output_pcap_t* self, const char* file, int linktype, int snaplen)
+{
+ mlassert_self();
+ if (self->dumper) {
+ lfatal("PCAP already opened");
+ }
+
+ if (!(self->pcap = pcap_open_dead(linktype, snaplen))) {
+ lcritical("pcap_open_dead() failed");
+ return -1;
+ }
+
+ if (!(self->dumper = pcap_dump_open(self->pcap, file))) {
+ lcritical("pcap_dump_open() error: %s", pcap_geterr(self->pcap));
+ pcap_close(self->pcap);
+ self->pcap = 0;
+ return -1;
+ }
+
+ return 0;
+}
+
+void output_pcap_close(output_pcap_t* self)
+{
+ mlassert_self();
+ if (self->dumper) {
+ pcap_dump_close(self->dumper);
+ self->dumper = 0;
+ }
+ if (self->pcap) {
+ pcap_close(self->pcap);
+ self->pcap = 0;
+ }
+}
+
+static void _receive(output_pcap_t* self, const core_object_t* obj)
+{
+ struct pcap_pkthdr hdr;
+ mlassert_self();
+
+ while (obj) {
+ if (obj->obj_type == CORE_OBJECT_PCAP) {
+ hdr.ts.tv_sec = ((const core_object_pcap_t*)obj)->ts.sec;
+ hdr.ts.tv_usec = ((const core_object_pcap_t*)obj)->ts.nsec / 1000;
+ hdr.caplen = ((const core_object_pcap_t*)obj)->caplen;
+ hdr.len = ((const core_object_pcap_t*)obj)->len;
+
+ pcap_dump((void*)self->dumper, &hdr, ((const core_object_pcap_t*)obj)->bytes);
+ return;
+ }
+ obj = obj->obj_prev;
+ }
+}
+
+core_receiver_t output_pcap_receiver(output_pcap_t* self)
+{
+ if (!self->dumper) {
+ lfatal("PCAP not opened");
+ }
+
+ return (core_receiver_t)_receive;
+}
diff --git a/src/output/pcap.h b/src/output/pcap.h
new file mode 100644
index 0000000..fccf58d
--- /dev/null
+++ b/src/output/pcap.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/log.h"
+#include "core/receiver.h"
+#include "core/producer.h"
+
+#ifndef __dnsjit_output_pcap_h
+#define __dnsjit_output_pcap_h
+
+#include <pcap/pcap.h>
+
+#include "output/pcap.hh"
+
+#endif
diff --git a/src/output/pcap.hh b/src/output/pcap.hh
new file mode 100644
index 0000000..45f5150
--- /dev/null
+++ b/src/output/pcap.hh
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if 0
+typedef struct pcap_dumper {} pcap_dumper_t;
+#endif
+
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+//lua:require("dnsjit.input.pcap_h")
+
+typedef struct output_pcap {
+ core_log_t _log;
+ pcap_t* pcap;
+ pcap_dumper_t* dumper;
+} output_pcap_t;
+
+core_log_t* output_pcap_log();
+void output_pcap_init(output_pcap_t* self);
+void output_pcap_destroy(output_pcap_t* self);
+int output_pcap_open(output_pcap_t* self, const char* file, int linktype, int snaplen);
+void output_pcap_close(output_pcap_t* self);
+
+core_receiver_t output_pcap_receiver(output_pcap_t* self);
diff --git a/src/output/pcap.lua b/src/output/pcap.lua
new file mode 100644
index 0000000..13f0c29
--- /dev/null
+++ b/src/output/pcap.lua
@@ -0,0 +1,79 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.output.pcap
+-- Output to a PCAP using libpcap
+-- local output = require("dnsjit.output.pcap").new()
+-- output:open("file.pcap")
+-- ...
+-- output:close()
+--
+-- Output module for writing
+-- .I dnsjit.core.object.pcap
+-- objects to a PCAP,
+module(...,package.seeall)
+
+require("dnsjit.output.pcap_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "output_pcap_t"
+local output_pcap_t = ffi.typeof(t_name)
+local Pcap = {}
+
+-- Create a new Pcap output.
+function Pcap.new()
+ local self = {
+ obj = output_pcap_t(),
+ }
+ C.output_pcap_init(self.obj)
+ ffi.gc(self.obj, C.output_pcap_destroy)
+ return setmetatable(self, { __index = Pcap })
+end
+
+-- Return the Log object to control logging of this instance or module.
+function Pcap:log()
+ if self == nil then
+ return C.output_pcap_log()
+ end
+ return self.obj._log
+end
+
+-- Open the PCAP
+-- .I file
+-- to write to using the
+-- .I linktype
+-- and
+-- .IR snaplen .
+-- Returns 0 on success.
+function Pcap:open(file, linktype, snaplen)
+ return C.output_pcap_open(self.obj, file, linktype, snaplen)
+end
+
+-- Close the PCAP.
+function Pcap:close()
+ C.output_pcap_close(self.obj)
+end
+
+-- Return the C functions and context for receiving objects.
+function Pcap:receive()
+ return C.output_pcap_receiver(self.obj), self.obj
+end
+
+-- dnsjit.input.pcap (3)
+return Pcap
diff --git a/src/output/respdiff.c b/src/output/respdiff.c
new file mode 100644
index 0000000..834a264
--- /dev/null
+++ b/src/output/respdiff.c
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "output/respdiff.h"
+#include "core/assert.h"
+#include "core/object/payload.h"
+
+#ifdef HAVE_LMDB_H
+#include <lmdb.h>
+#endif
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <string.h>
+
+static core_log_t _log = LOG_T_INIT("output.respdiff");
+static output_respdiff_t _defaults = {
+ LOG_T_INIT_OBJ("output.respdiff"),
+ 0, 0, 0, 0, 0, 0
+};
+
+core_log_t* output_respdiff_log()
+{
+ return &_log;
+}
+
+void output_respdiff_init(output_respdiff_t* self, const char* path, size_t mapsize)
+{
+ mlassert_self();
+
+ if (!path) {
+ lfatal("path is nil");
+ }
+
+ *self = _defaults;
+
+#ifdef HAVE_LMDB_H
+ if (mkdir(path, 0775) && errno != EEXIST) {
+ lfatal("mkdir(%s) error %s", path, core_log_errstr(errno));
+ }
+ if (mdb_env_create((MDB_env**)&self->env)) {
+ lfatal("mdb_env_create failed");
+ }
+ if (mdb_env_set_mapsize((MDB_env*)self->env, mapsize)) {
+ lfatal("mdb_env_set_mapsize(%lu) failed", mapsize);
+ }
+ if (mdb_env_set_maxdbs((MDB_env*)self->env, 3)) {
+ lfatal("mdb_env_set_maxdbs failed");
+ }
+ if (mdb_env_open((MDB_env*)self->env, path, 0, 0664)) {
+ lfatal("mdb_env_open(%s) failed", path);
+ }
+ if (mdb_txn_begin((MDB_env*)self->env, 0, 0, (MDB_txn**)&self->txn)) {
+ lfatal("mdb_txn_begin failed for queries");
+ }
+ lfatal_oom(self->qdb = calloc(1, sizeof(MDB_dbi)));
+ if (mdb_dbi_open((MDB_txn*)self->txn, "queries", MDB_CREATE, (MDB_dbi*)self->qdb)) {
+ lfatal("mdb_dbi_open failed for queries");
+ }
+ lfatal_oom(self->rdb = calloc(1, sizeof(MDB_dbi)));
+ if (mdb_dbi_open((MDB_txn*)self->txn, "answers", MDB_CREATE, (MDB_dbi*)self->rdb)) {
+ lfatal("mdb_dbi_open failed for responses");
+ }
+ lfatal_oom(self->meta = calloc(1, sizeof(MDB_dbi)));
+ if (mdb_dbi_open((MDB_txn*)self->txn, "meta", MDB_CREATE, (MDB_dbi*)self->meta)) {
+ lfatal("mdb_dbi_open failed for meta");
+ }
+#endif
+}
+
+void output_respdiff_destroy(output_respdiff_t* self)
+{
+ mlassert_self();
+
+#ifdef HAVE_LMDB_H
+ if (self->env) {
+ mdb_env_close((MDB_env*)self->env);
+ }
+ free(self->qdb);
+ free(self->rdb);
+ free(self->meta);
+#endif
+}
+
+#ifdef HAVE_LMDB_H
+static const char* _meta_version = "version";
+static const char* _meta_version_val = "2018-05-21";
+static const char* _meta_servers = "servers";
+static const char* _meta_name0 = "name0";
+static const char* _meta_name1 = "name1";
+static const char* _meta_start_time = "start_time";
+static const char* _meta_end_time = "end_time";
+#endif
+
+void output_respdiff_commit(output_respdiff_t* self, const char* origname, const char* recvname, uint64_t start_time, uint64_t end_time)
+{
+#ifdef HAVE_LMDB_H
+ MDB_val k, v;
+ uint32_t i;
+ int err;
+ mlassert_self();
+ lassert(origname, "origname is nil");
+ lassert(recvname, "recvname is nil");
+
+ k.mv_size = strlen(_meta_version);
+ k.mv_data = (void*)_meta_version;
+ v.mv_size = strlen(_meta_version_val);
+ v.mv_data = (void*)_meta_version_val;
+ if ((err = mdb_put((MDB_txn*)self->txn, (MDB_dbi) * ((MDB_dbi*)self->meta), &k, &v, 0))) {
+ if (err == MDB_MAP_FULL) {
+ lfatal("mdb_put meta.version failed, database is full");
+ } else {
+ lfatal("mdb_put meta.version failed (%d)", err);
+ }
+ }
+
+ k.mv_size = strlen(_meta_servers);
+ k.mv_data = (void*)_meta_servers;
+ i = 2;
+ v.mv_size = 4;
+ v.mv_data = (void*)&i;
+ if ((err = mdb_put((MDB_txn*)self->txn, (MDB_dbi) * ((MDB_dbi*)self->meta), &k, &v, 0))) {
+ if (err == MDB_MAP_FULL) {
+ lfatal("mdb_put meta.servers failed, database is full");
+ } else {
+ lfatal("mdb_put meta.servers failed (%d)", err);
+ }
+ }
+
+ k.mv_size = strlen(_meta_name0);
+ k.mv_data = (void*)_meta_name0;
+ v.mv_size = strlen(origname);
+ v.mv_data = (void*)origname;
+ if ((err = mdb_put((MDB_txn*)self->txn, (MDB_dbi) * ((MDB_dbi*)self->meta), &k, &v, 0))) {
+ if (err == MDB_MAP_FULL) {
+ lfatal("mdb_put meta.name0 failed, database is full");
+ } else {
+ lfatal("mdb_put meta.name0 failed (%d)", err);
+ }
+ }
+
+ k.mv_size = strlen(_meta_name1);
+ k.mv_data = (void*)_meta_name1;
+ v.mv_size = strlen(recvname);
+ v.mv_data = (void*)recvname;
+ if ((err = mdb_put((MDB_txn*)self->txn, (MDB_dbi) * ((MDB_dbi*)self->meta), &k, &v, 0))) {
+ if (err == MDB_MAP_FULL) {
+ lfatal("mdb_put meta.name1 failed, database is full");
+ } else {
+ lfatal("mdb_put meta.name1 failed (%d)", err);
+ }
+ }
+
+ k.mv_size = strlen(_meta_start_time);
+ k.mv_data = (void*)_meta_start_time;
+ i = start_time;
+ v.mv_size = 4;
+ v.mv_data = (void*)&i;
+ if ((err = mdb_put((MDB_txn*)self->txn, (MDB_dbi) * ((MDB_dbi*)self->meta), &k, &v, 0))) {
+ if (err == MDB_MAP_FULL) {
+ lfatal("mdb_put meta.start_time failed, database is full");
+ } else {
+ lfatal("mdb_put meta.start_time failed (%d)", err);
+ }
+ }
+
+ k.mv_size = strlen(_meta_end_time);
+ k.mv_data = (void*)_meta_end_time;
+ i = end_time;
+ v.mv_size = 4;
+ v.mv_data = (void*)&i;
+ if ((err = mdb_put((MDB_txn*)self->txn, (MDB_dbi) * ((MDB_dbi*)self->meta), &k, &v, 0))) {
+ if (err == MDB_MAP_FULL) {
+ lfatal("mdb_put meta.end_time failed, database is full");
+ } else {
+ lfatal("mdb_put meta.end_time failed (%d)", err);
+ }
+ }
+
+ if (self->txn) {
+ if (mdb_txn_commit((MDB_txn*)self->txn)) {
+ lfatal("mdb_txn_commit failed");
+ }
+ self->txn = 0;
+ }
+#endif
+}
+
+#ifdef HAVE_LMDB_H
+static void _receive(output_respdiff_t* self, const core_object_t* obj)
+{
+ const core_object_payload_t *query, *original, *response;
+ MDB_val k, v;
+ uint8_t responses[132096];
+ uint32_t msec;
+ uint16_t dnslen;
+ int err;
+ mlassert_self();
+
+ if (!obj || obj->obj_type != CORE_OBJECT_PAYLOAD) {
+ lfatal("invalid first object");
+ }
+ query = (core_object_payload_t*)obj;
+
+ if (!query->obj_prev || query->obj_prev->obj_type != CORE_OBJECT_PAYLOAD) {
+ lfatal("invalid second object");
+ }
+ original = (core_object_payload_t*)query->obj_prev;
+
+ response = (core_object_payload_t*)original->obj_prev;
+ if (response && response->obj_type != CORE_OBJECT_PAYLOAD) {
+ lfatal("invalid third object");
+ }
+
+ if (12 + original->len + (response ? response->len : 0) > sizeof(responses)) {
+ lfatal("not enough buffer space for responses");
+ }
+
+ self->count++;
+
+ k.mv_size = sizeof(self->id);
+ k.mv_data = (void*)&self->id;
+ v.mv_size = query->len;
+ v.mv_data = (void*)query->payload;
+ if ((err = mdb_put((MDB_txn*)self->txn, (MDB_dbi) * ((MDB_dbi*)self->qdb), &k, &v, 0))) {
+ if (err == MDB_MAP_FULL) {
+ lfatal("mdb_put query failed, database is full");
+ } else {
+ lfatal("mdb_put query failed (%d)", err);
+ }
+ }
+
+ msec = 1; // TODO
+ memcpy(responses, &msec, 4);
+ dnslen = original->len;
+ memcpy(&responses[4], &dnslen, 2);
+ memcpy(&responses[6], original->payload, original->len);
+ if (response) {
+ memcpy(&responses[6 + original->len], &msec, 4);
+ dnslen = response->len;
+ memcpy(&responses[10 + original->len], &dnslen, 2);
+ memcpy(&responses[12 + original->len], response->payload, response->len);
+ } else {
+ msec = 0xffffffff;
+ memcpy(&responses[6 + original->len], &msec, 4);
+ dnslen = 0;
+ memcpy(&responses[10 + original->len], &dnslen, 2);
+ }
+
+ v.mv_size = 12 + original->len + (response ? response->len : 0);
+ v.mv_data = (void*)responses;
+ if ((err = mdb_put((MDB_txn*)self->txn, (MDB_dbi) * ((MDB_dbi*)self->rdb), &k, &v, 0))) {
+ if (err == MDB_MAP_FULL) {
+ lfatal("mdb_put answers failed, database is full");
+ } else {
+ lfatal("mdb_put answers failed (%d)", err);
+ }
+ }
+
+ self->id++;
+}
+
+core_receiver_t output_respdiff_receiver(output_respdiff_t* self)
+{
+ mlassert_self();
+
+ if (!self->txn) {
+ lfatal("no LMDB opened");
+ }
+
+ return (core_receiver_t)_receive;
+}
+#else
+core_receiver_t output_respdiff_receiver(output_respdiff_t* self)
+{
+ mlassert_self();
+ lfatal("no LMDB support");
+ return 0;
+}
+#endif
diff --git a/src/output/respdiff.h b/src/output/respdiff.h
new file mode 100644
index 0000000..f375006
--- /dev/null
+++ b/src/output/respdiff.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/log.h"
+#include "core/receiver.h"
+
+#ifndef __dnsjit_output_respdiff_h
+#define __dnsjit_output_respdiff_h
+
+#include <stdint.h>
+
+#include "output/respdiff.hh"
+
+#endif
diff --git a/src/output/respdiff.hh b/src/output/respdiff.hh
new file mode 100644
index 0000000..44f597e
--- /dev/null
+++ b/src/output/respdiff.hh
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+
+typedef struct output_respdiff {
+ core_log_t _log;
+ void * env, *txn, *qdb, *rdb, *meta;
+ uint32_t id;
+ size_t count;
+} output_respdiff_t;
+
+core_log_t* output_respdiff_log();
+void output_respdiff_init(output_respdiff_t* self, const char* path, size_t mapsize);
+void output_respdiff_destroy(output_respdiff_t* self);
+void output_respdiff_commit(output_respdiff_t* self, const char* origname, const char* recvname, uint64_t start_time, uint64_t end_time);
+
+core_receiver_t output_respdiff_receiver(output_respdiff_t* self);
diff --git a/src/output/respdiff.lua b/src/output/respdiff.lua
new file mode 100644
index 0000000..7d17b4a
--- /dev/null
+++ b/src/output/respdiff.lua
@@ -0,0 +1,94 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.output.respdiff
+-- Output to respdiff LMDB
+-- local output = require("dnsjit.output.respdiff").new("/path/to/lmdb")
+--
+-- Output to an LMDB database (format 2018-05-21) that can be used by respdiff
+-- to compare the responses found in the input data with the responses
+-- received.
+-- The receive function expects to get a chain of 2 or 3
+-- .IR core.object.payload .
+-- For a completed query; The top of the chain is the query, after it the
+-- original response and then the received response.
+-- For a timed out query; The top of the chain is the query, after it the
+-- original response.
+module(...,package.seeall)
+
+require("dnsjit.output.respdiff_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "output_respdiff_t"
+local output_respdiff_t = ffi.typeof(t_name)
+local Respdiff = {}
+
+-- Create a new Respdiff output and created the LMDB database in the directory
+-- .IR path .
+-- The
+-- .I origname
+-- and
+-- .I recvname
+-- are used to populate the meta table, these names should be the same as
+-- what is configured in
+-- .IR respdiff.cfg .
+-- Optional
+-- .I mapsize
+-- can be given to increase the database size beyond the default size of 10MB.
+function Respdiff.new(path, origname, recvname, mapsize)
+ if mapsize == nil then
+ mapsize = 10485760
+ end
+ local self = {
+ obj = output_respdiff_t(),
+ path = path,
+ origname = origname,
+ recvname = recvname,
+ }
+ C.output_respdiff_init(self.obj, path, mapsize)
+ ffi.gc(self.obj, C.output_respdiff_destroy)
+ return setmetatable(self, { __index = Respdiff })
+end
+
+-- Return the Log object to control logging of this instance or module.
+function Respdiff:log()
+ if self == nil then
+ return C.output_respdiff_log()
+ end
+ return self.obj._log
+end
+
+-- Return the C functions and context for receiving objects.
+function Respdiff:receive()
+ return C.output_respdiff_receiver(self.obj), self.obj
+end
+
+-- Commit the LMDB transactions, can not store any more objects after this
+-- call.
+-- The given
+-- .I start_time
+-- and
+-- .I end_time
+-- are used to fill the meta table.
+function Respdiff:commit(start_time, end_time)
+ C.output_respdiff_commit(self.obj, self.origname, self.recvname, start_time, end_time)
+end
+
+-- respdiff " https://gitlab.nic.cz/knot/respdiff"
+return Respdiff
diff --git a/src/output/tcpcli.c b/src/output/tcpcli.c
new file mode 100644
index 0000000..f2b218b
--- /dev/null
+++ b/src/output/tcpcli.c
@@ -0,0 +1,381 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "output/tcpcli.h"
+#include "core/assert.h"
+#include "core/object/dns.h"
+#include "core/object/payload.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <poll.h>
+
+static core_log_t _log = LOG_T_INIT("output.tcpcli");
+static output_tcpcli_t _defaults = {
+ LOG_T_INIT_OBJ("output.tcpcli"),
+ 0, 0, -1,
+ { 0 }, CORE_OBJECT_PAYLOAD_INIT(0),
+ 0, 0, 0, 0,
+ { 5, 0 }, 1
+};
+
+core_log_t* output_tcpcli_log()
+{
+ return &_log;
+}
+
+void output_tcpcli_init(output_tcpcli_t* self)
+{
+ mlassert_self();
+
+ *self = _defaults;
+ self->pkt.payload = self->recvbuf;
+}
+
+void output_tcpcli_destroy(output_tcpcli_t* self)
+{
+ mlassert_self();
+
+ if (self->fd > -1) {
+ shutdown(self->fd, SHUT_RDWR);
+ close(self->fd);
+ }
+}
+
+int output_tcpcli_connect(output_tcpcli_t* self, const char* host, const char* port)
+{
+ struct addrinfo* addr;
+ int err;
+ mlassert_self();
+ lassert(host, "host is nil");
+ lassert(port, "port is nil");
+
+ if (self->fd > -1) {
+ lfatal("already connected");
+ }
+
+ if ((err = getaddrinfo(host, port, 0, &addr))) {
+ lcritical("getaddrinfo(%s, %s) error %s", host, port, gai_strerror(err));
+ return -1;
+ }
+ if (!addr) {
+ lcritical("getaddrinfo failed, no address returned");
+ return -1;
+ }
+
+ if ((self->fd = socket(addr->ai_addr->sa_family, SOCK_STREAM, 0)) < 0) {
+ lcritical("socket() error %s", core_log_errstr(errno));
+ freeaddrinfo(addr);
+ return -2;
+ }
+
+ if (connect(self->fd, addr->ai_addr, addr->ai_addrlen)) {
+ lcritical("connect() error %s", core_log_errstr(errno));
+ freeaddrinfo(addr);
+ return -2;
+ }
+
+ freeaddrinfo(addr);
+ return 0;
+}
+
+int output_tcpcli_nonblocking(output_tcpcli_t* self)
+{
+ int flags;
+ mlassert_self();
+
+ if (self->fd < 0) {
+ lfatal("not connected");
+ }
+
+ flags = fcntl(self->fd, F_GETFL);
+ if (flags != -1) {
+ flags = flags & O_NONBLOCK ? 1 : 0;
+ }
+
+ return flags;
+}
+
+int output_tcpcli_set_nonblocking(output_tcpcli_t* self, int nonblocking)
+{
+ int flags;
+ mlassert_self();
+
+ if (self->fd < 0) {
+ lfatal("not connected");
+ }
+
+ if ((flags = fcntl(self->fd, F_GETFL)) == -1) {
+ lcritical("fcntl(FL_GETFL) error %s", core_log_errstr(errno));
+ return -1;
+ }
+
+ if (nonblocking) {
+ flags |= O_NONBLOCK;
+ self->blocking = 0;
+ } else {
+ flags &= ~O_NONBLOCK;
+ self->blocking = 1;
+ }
+
+ if (fcntl(self->fd, F_SETFL, flags | O_NONBLOCK)) {
+ lcritical("fcntl(FL_SETFL, %x) error %s", flags, core_log_errstr(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static void _receive(output_tcpcli_t* self, const core_object_t* obj)
+{
+ const uint8_t* payload;
+ size_t len, sent;
+ uint16_t dnslen;
+ mlassert_self();
+
+ for (; obj;) {
+ switch (obj->obj_type) {
+ case CORE_OBJECT_DNS:
+ obj = obj->obj_prev;
+ continue;
+ case CORE_OBJECT_PAYLOAD:
+ payload = ((core_object_payload_t*)obj)->payload;
+ len = ((core_object_payload_t*)obj)->len;
+ break;
+ default:
+ return;
+ }
+
+ sent = 0;
+ dnslen = htons(len);
+
+ for (;;) {
+ ssize_t ret = sendto(self->fd, ((uint8_t*)&dnslen) + sent, sizeof(dnslen) - sent, 0, 0, 0);
+ if (ret > -1) {
+ sent += ret;
+ if (sent < sizeof(dnslen))
+ continue;
+
+ sent = 0;
+ for (;;) {
+ ssize_t ret = sendto(self->fd, payload + sent, len - sent, 0, 0, 0);
+ if (ret > -1) {
+ sent += ret;
+ if (sent < len)
+ continue;
+ self->pkts++;
+ return;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ continue;
+ default:
+ break;
+ }
+ break;
+ }
+ self->errs++;
+ return;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ continue;
+ default:
+ break;
+ }
+ break;
+ }
+ self->errs++;
+ break;
+ }
+}
+
+core_receiver_t output_tcpcli_receiver(output_tcpcli_t* self)
+{
+ mlassert_self();
+
+ if (self->fd < 0) {
+ lfatal("not connected");
+ }
+
+ return (core_receiver_t)_receive;
+}
+
+static const core_object_t* _produce(output_tcpcli_t* self)
+{
+ ssize_t n, recv = 0;
+ uint16_t dnslen;
+ struct pollfd p;
+ int to = 0;
+ mlassert_self();
+
+ // Check if last recvfrom() got more then we needed
+ if (!self->have_dnslen && self->recv > self->dnslen) {
+ recv = self->recv - self->dnslen;
+ if (recv < sizeof(dnslen)) {
+ memcpy(((uint8_t*)&dnslen), self->recvbuf + self->dnslen, recv);
+ } else {
+ memcpy(((uint8_t*)&dnslen), self->recvbuf + self->dnslen, sizeof(dnslen));
+
+ if (recv > sizeof(dnslen)) {
+ self->recv = recv - sizeof(dnslen);
+ memmove(self->recvbuf, self->recvbuf + self->dnslen + sizeof(dnslen), self->recv);
+ } else {
+ self->recv = 0;
+ }
+
+ self->dnslen = ntohs(dnslen);
+ self->have_dnslen = 1;
+
+ if (self->recv > self->dnslen) {
+ self->pkts_recv++;
+ self->pkt.len = self->dnslen;
+ self->have_dnslen = 0;
+ return (core_object_t*)&self->pkt;
+ }
+ }
+ }
+
+ if (self->blocking) {
+ p.fd = self->fd;
+ p.events = POLLIN;
+ p.revents = 0;
+ to = (self->timeout.sec * 1e3) + (self->timeout.nsec / 1e6); //NOSONAR
+ if (!to) {
+ to = 1;
+ }
+ }
+
+ if (!self->have_dnslen) {
+ for (;;) {
+ n = poll(&p, 1, to);
+ if (n < 0 || (p.revents & (POLLERR | POLLHUP | POLLNVAL))) {
+ self->errs++;
+ return 0;
+ }
+ if (!n || !(p.revents & POLLIN)) {
+ if (recv) {
+ self->errs++;
+ return 0;
+ }
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ }
+
+ n = recvfrom(self->fd, ((uint8_t*)&dnslen) + recv, sizeof(dnslen) - recv, 0, 0, 0);
+ if (n > 0) {
+ recv += n;
+ if (recv < sizeof(dnslen))
+ continue;
+ break;
+ }
+ if (!n) {
+ break;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ continue;
+ default:
+ break;
+ }
+ self->errs++;
+ break;
+ }
+
+ if (n < 1) {
+ return 0;
+ }
+
+ self->dnslen = ntohs(dnslen);
+ self->have_dnslen = 1;
+ self->recv = 0;
+ }
+
+ for (;;) {
+ n = poll(&p, 1, to);
+ if (n < 0 || (p.revents & (POLLERR | POLLHUP | POLLNVAL))) {
+ self->errs++;
+ return 0;
+ }
+ if (!n || !(p.revents & POLLIN)) {
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ }
+
+ n = recvfrom(self->fd, self->recvbuf + self->recv, sizeof(self->recvbuf) - self->recv, 0, 0, 0);
+ if (n > 0) {
+ self->recv += n;
+ if (self->recv < self->dnslen)
+ continue;
+ break;
+ }
+ if (!n) {
+ break;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ default:
+ break;
+ }
+ self->errs++;
+ break;
+ }
+
+ if (n < 1) {
+ return 0;
+ }
+
+ self->pkts_recv++;
+ self->pkt.len = self->dnslen;
+ self->have_dnslen = 0;
+ return (core_object_t*)&self->pkt;
+}
+
+core_producer_t output_tcpcli_producer(output_tcpcli_t* self)
+{
+ mlassert_self();
+
+ if (self->fd < 0) {
+ lfatal("not connected");
+ }
+
+ return (core_producer_t)_produce;
+}
diff --git a/src/output/tcpcli.h b/src/output/tcpcli.h
new file mode 100644
index 0000000..6eb80f3
--- /dev/null
+++ b/src/output/tcpcli.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/log.h"
+#include "core/receiver.h"
+#include "core/producer.h"
+#include "core/object/payload.h"
+#include "core/timespec.h"
+
+#ifndef __dnsjit_output_tcpcli_h
+#define __dnsjit_output_tcpcli_h
+
+#include "output/tcpcli.hh"
+
+#endif
diff --git a/src/output/tcpcli.hh b/src/output/tcpcli.hh
new file mode 100644
index 0000000..277b0dd
--- /dev/null
+++ b/src/output/tcpcli.hh
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+//lua:require("dnsjit.core.producer_h")
+//lua:require("dnsjit.core.object.payload_h")
+//lua:require("dnsjit.core.timespec_h")
+
+typedef struct output_tcpcli {
+ core_log_t _log;
+ size_t pkts, errs;
+ int fd;
+
+ uint8_t recvbuf[64 * 1024];
+ core_object_payload_t pkt;
+ uint16_t dnslen;
+ uint8_t have_dnslen;
+ size_t recv, pkts_recv;
+
+ core_timespec_t timeout;
+ int8_t blocking;
+} output_tcpcli_t;
+
+core_log_t* output_tcpcli_log();
+
+void output_tcpcli_init(output_tcpcli_t* self);
+void output_tcpcli_destroy(output_tcpcli_t* self);
+int output_tcpcli_connect(output_tcpcli_t* self, const char* host, const char* port);
+int output_tcpcli_nonblocking(output_tcpcli_t* self);
+int output_tcpcli_set_nonblocking(output_tcpcli_t* self, int nonblocking);
+
+core_receiver_t output_tcpcli_receiver(output_tcpcli_t* self);
+core_producer_t output_tcpcli_producer(output_tcpcli_t* self);
diff --git a/src/output/tcpcli.lua b/src/output/tcpcli.lua
new file mode 100644
index 0000000..d57de88
--- /dev/null
+++ b/src/output/tcpcli.lua
@@ -0,0 +1,131 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.output.tcpcli
+-- Simple, length aware, TCP client
+-- local output = require("dnsjit.output.tcpcli").new("127.0.0.1", "53")
+--
+-- Simple TCP client that takes any payload you give it, sends the length of
+-- the payload as an unsigned 16 bit integer and then sends the payload.
+-- When receiving it will first retrieve the length of the payload as an
+-- unsigned 16 bit integer and it will stall until it gets, even if
+-- nonblocking mode is used.
+-- Then it will retrieve at least that amount of bytes, if nonblocking mode
+-- is used here then it will return a payload object with length zero if
+-- there was nothing to receive or if the full payload have not been received
+-- yet.
+-- Additional calls will continue retrieving the payload.
+-- .SS Attributes
+-- .TP
+-- timeout
+-- A
+-- .I core.timespec
+-- that is used when producing objects.
+module(...,package.seeall)
+
+require("dnsjit.output.tcpcli_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "output_tcpcli_t"
+local output_tcpcli_t = ffi.typeof(t_name)
+local Tcpcli = {}
+
+-- Create a new Tcpcli output.
+function Tcpcli.new()
+ local self = {
+ obj = output_tcpcli_t(),
+ }
+ C.output_tcpcli_init(self.obj)
+ ffi.gc(self.obj, C.output_tcpcli_destroy)
+ return setmetatable(self, { __index = Tcpcli })
+end
+
+-- Set the timeout when producing objects.
+function Tcpcli:timeout(seconds, nanoseconds)
+ self.obj.timeout.sec = seconds
+ self.obj.timeout.nsec = nanoseconds
+end
+
+-- Connect to the
+-- .I host
+-- and
+-- .I port
+-- and return 0 if successful.
+function Tcpcli:connect(host, port)
+ return C.output_tcpcli_connect(self.obj, host, port)
+end
+
+-- Enable (true) or disable (false) nonblocking mode and
+-- return 0 if successful, if
+-- .I bool
+-- is not specified then return if nonblocking mode is on (true) or off (false).
+function Tcpcli:nonblocking(bool)
+ if bool == nil then
+ if C.output_tcpcli_nonblocking(self.obj) == 1 then
+ return true
+ end
+ return false
+ elseif bool == true then
+ return C.output_tcpcli_set_nonblocking(self.obj, 1)
+ else
+ return C.output_tcpcli_set_nonblocking(self.obj, 0)
+ end
+end
+
+-- Return the C functions and context for receiving objects, these objects
+-- will be sent.
+function Tcpcli:receive()
+ return C.output_tcpcli_receiver(self.obj), self.obj
+end
+
+-- Return the C functions and context for producing objects, these objects
+-- are received.
+-- If nonblocking mode is enabled the producer will return a payload object
+-- with length zero if there was nothing to receive or if the full payload
+-- have not been received yet.
+-- If nonblocking mode is disabled the producer will wait for data and if
+-- timed out (see
+-- .IR timeout )
+-- it will return a payload object with length zero.
+-- If a timeout happens during during the first stage, getting the length, it
+-- will fail and return nil.
+-- Additional calls will continue retrieving the payload.
+-- The producer returns nil on error.
+function Tcpcli:produce()
+ return C.output_tcpcli_producer(self.obj), self.obj
+end
+
+-- Return the number of "packets" sent, actually the number of completely sent
+-- payloads.
+function Tcpcli:packets()
+ return tonumber(self.obj.pkts)
+end
+
+-- Return the number of "packets" received, actually the number of completely
+-- received DNS messages.
+function Tcpcli:received()
+ return tonumber(self.obj.pkts_recv)
+end
+
+-- Return the number of errors when sending.
+function Tcpcli:errors()
+ return tonumber(self.obj.errs)
+end
+
+return Tcpcli
diff --git a/src/output/tlscli.c b/src/output/tlscli.c
new file mode 100644
index 0000000..8c5947a
--- /dev/null
+++ b/src/output/tlscli.c
@@ -0,0 +1,345 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "output/tlscli.h"
+#include "core/assert.h"
+#include "core/object/dns.h"
+#include "core/object/payload.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <poll.h>
+
+static core_log_t _log = LOG_T_INIT("output.tlscli");
+static output_tlscli_t _defaults = {
+ LOG_T_INIT_OBJ("output.tlscli"),
+ 0, 0, -1, 0,
+ { 0 }, CORE_OBJECT_PAYLOAD_INIT(0),
+ 0, 0, 0, 0,
+ { 5, 0 },
+ 0, 0
+};
+
+core_log_t* output_tlscli_log()
+{
+ return &_log;
+}
+
+void output_tlscli_init(output_tlscli_t* self)
+{
+ int err;
+ mlassert_self();
+
+ *self = _defaults;
+ self->pkt.payload = self->recvbuf;
+
+ gnutls_global_init();
+ if ((err = gnutls_certificate_allocate_credentials(&self->cred)) != GNUTLS_E_SUCCESS) {
+ lfatal("gnutls_certificate_allocate_credentials() error: %s", gnutls_strerror(err));
+ } else if ((err = gnutls_init(&self->session, GNUTLS_CLIENT)) != GNUTLS_E_SUCCESS) {
+ lfatal("gnutls_init() error: %s", gnutls_strerror(err));
+ } else if ((err = gnutls_set_default_priority(self->session)) != GNUTLS_E_SUCCESS) {
+ lfatal("gnutls_set_default_priority() error: %s", gnutls_strerror(err));
+ } else if ((err = gnutls_credentials_set(self->session, GNUTLS_CRD_CERTIFICATE, self->cred)) != GNUTLS_E_SUCCESS) {
+ lfatal("gnutls_credentials_set() error: %s", gnutls_strerror(err));
+ }
+
+ gnutls_handshake_set_timeout(self->session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
+}
+
+void output_tlscli_destroy(output_tlscli_t* self)
+{
+ mlassert_self();
+
+ if (self->fd > -1) {
+ if (self->session) {
+ gnutls_bye(self->session, GNUTLS_SHUT_RDWR);
+ gnutls_deinit(self->session);
+ }
+ shutdown(self->fd, SHUT_RDWR);
+ close(self->fd);
+ if (self->cred) {
+ gnutls_certificate_free_credentials(self->cred);
+ }
+ }
+}
+
+int output_tlscli_connect(output_tlscli_t* self, const char* host, const char* port)
+{
+ struct addrinfo* addr;
+ int err;
+ unsigned int ms;
+ mlassert_self();
+ lassert(host, "host is nil");
+ lassert(port, "port is nil");
+
+ if (self->fd > -1) {
+ lfatal("already connected");
+ }
+ if (self->tls_ok) {
+ lfatal("TLS already established");
+ }
+
+ if ((err = getaddrinfo(host, port, 0, &addr))) {
+ lcritical("getaddrinfo(%s, %s) error %s", host, port, gai_strerror(err));
+ return -1;
+ }
+ if (!addr) {
+ lcritical("getaddrinfo failed, no address returned");
+ return -1;
+ }
+
+ if ((self->fd = socket(addr->ai_addr->sa_family, SOCK_STREAM, 0)) < 0) {
+ lcritical("socket() error %s", core_log_errstr(errno));
+ freeaddrinfo(addr);
+ return -2;
+ }
+
+ if (connect(self->fd, addr->ai_addr, addr->ai_addrlen)) {
+ lcritical("connect() error %s", core_log_errstr(errno));
+ freeaddrinfo(addr);
+ return -2;
+ }
+
+ freeaddrinfo(addr);
+
+ gnutls_transport_set_int(self->session, self->fd);
+ ms = (self->timeout.sec * 1000) + (self->timeout.nsec / 1000000);
+ if (!ms && self->timeout.nsec) {
+ ms = 1;
+ }
+ gnutls_record_set_timeout(self->session, ms);
+
+ /* Establish TLS */
+ do {
+ err = gnutls_handshake(self->session);
+ } while (err < 0 && gnutls_error_is_fatal(err) == 0);
+ if (err == GNUTLS_E_PREMATURE_TERMINATION) {
+ lcritical("gnutls_handshake() error: %s", gnutls_strerror(err));
+ return -3;
+ } else if (err < 0) {
+ lcritical("gnutls_handshake() failed: %s (%d)\n", gnutls_strerror(err), err);
+ return -3;
+ }
+
+ self->tls_ok = 1;
+ return 0;
+}
+
+static void _receive(output_tlscli_t* self, const core_object_t* obj)
+{
+ const uint8_t* payload;
+ size_t len, sent;
+ uint16_t dnslen;
+ ssize_t ret;
+ mlassert_self();
+
+ for (; obj;) {
+ switch (obj->obj_type) {
+ case CORE_OBJECT_DNS:
+ obj = obj->obj_prev;
+ continue;
+ case CORE_OBJECT_PAYLOAD:
+ payload = ((core_object_payload_t*)obj)->payload;
+ len = ((core_object_payload_t*)obj)->len;
+ break;
+ default:
+ return;
+ }
+
+ sent = 0;
+ dnslen = htons(len);
+
+ for (;;) {
+ ret = gnutls_record_send(self->session, ((uint8_t*)&dnslen) + sent, sizeof(dnslen) - sent);
+ if (ret > -1) {
+ sent += ret;
+ if (sent < sizeof(dnslen))
+ continue;
+
+ sent = 0;
+ for (;;) {
+ ret = gnutls_record_send(self->session, payload + sent, len - sent);
+ if (ret > -1) {
+ sent += ret;
+ if (sent < len)
+ continue;
+ self->pkts++;
+ return;
+ }
+ switch (ret) {
+ case GNUTLS_E_AGAIN:
+ case GNUTLS_E_TIMEDOUT:
+ continue;
+ default:
+ break;
+ }
+ break;
+ }
+ self->errs++;
+ return;
+ }
+ switch (ret) {
+ case GNUTLS_E_AGAIN:
+ case GNUTLS_E_TIMEDOUT:
+ continue;
+ default:
+ break;
+ }
+ break;
+ }
+ self->errs++;
+ break;
+ }
+}
+
+core_receiver_t output_tlscli_receiver(output_tlscli_t* self)
+{
+ mlassert_self();
+
+ if (self->fd < 0) {
+ lfatal("not connected");
+ }
+ if (!self->tls_ok) {
+ lfatal("TLS is not established");
+ }
+
+ return (core_receiver_t)_receive;
+}
+
+static const core_object_t* _produce(output_tlscli_t* self)
+{
+ ssize_t n, recv = 0;
+ uint16_t dnslen;
+ mlassert_self();
+
+ // Check if last recvfrom() got more then we needed
+ if (!self->have_dnslen && self->recv > self->dnslen) {
+ recv = self->recv - self->dnslen;
+ if (recv < sizeof(dnslen)) {
+ memcpy(((uint8_t*)&dnslen), self->recvbuf + self->dnslen, recv);
+ } else {
+ memcpy(((uint8_t*)&dnslen), self->recvbuf + self->dnslen, sizeof(dnslen));
+
+ if (recv > sizeof(dnslen)) {
+ self->recv = recv - sizeof(dnslen);
+ memmove(self->recvbuf, self->recvbuf + self->dnslen + sizeof(dnslen), self->recv);
+ } else {
+ self->recv = 0;
+ }
+
+ self->dnslen = ntohs(dnslen);
+ self->have_dnslen = 1;
+
+ if (self->recv > self->dnslen) {
+ self->pkts_recv++;
+ self->pkt.len = self->dnslen;
+ self->have_dnslen = 0;
+ return (core_object_t*)&self->pkt;
+ }
+ }
+ }
+
+ if (!self->have_dnslen) {
+ for (;;) {
+ n = gnutls_record_recv(self->session, ((uint8_t*)&dnslen) + recv, sizeof(dnslen) - recv);
+ if (n > 0) {
+ recv += n;
+ if (recv < sizeof(dnslen))
+ continue;
+ break;
+ }
+ if (!n) {
+ break;
+ }
+ switch (n) {
+ case GNUTLS_E_AGAIN:
+ case GNUTLS_E_TIMEDOUT:
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ default:
+ break;
+ }
+ self->errs++;
+ break;
+ }
+
+ if (n < 1) {
+ return 0;
+ }
+
+ self->dnslen = ntohs(dnslen);
+ self->have_dnslen = 1;
+ self->recv = 0;
+ }
+
+ for (;;) {
+ n = gnutls_record_recv(self->session, self->recvbuf + self->recv, sizeof(self->recvbuf) - self->recv);
+ if (n > 0) {
+ self->recv += n;
+ if (self->recv < self->dnslen)
+ continue;
+ break;
+ }
+ if (!n) {
+ break;
+ }
+ switch (n) {
+ case GNUTLS_E_AGAIN:
+ case GNUTLS_E_TIMEDOUT:
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ default:
+ break;
+ }
+ self->errs++;
+ break;
+ }
+
+ if (n < 1) {
+ return 0;
+ }
+
+ self->pkts_recv++;
+ self->pkt.len = self->dnslen;
+ self->have_dnslen = 0;
+ return (core_object_t*)&self->pkt;
+}
+
+core_producer_t output_tlscli_producer(output_tlscli_t* self)
+{
+ mlassert_self();
+
+ if (self->fd < 0) {
+ lfatal("not connected");
+ }
+ if (!self->tls_ok) {
+ lfatal("TLS is not established");
+ }
+
+ return (core_producer_t)_produce;
+}
diff --git a/src/output/tlscli.h b/src/output/tlscli.h
new file mode 100644
index 0000000..2bdf69f
--- /dev/null
+++ b/src/output/tlscli.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/log.h"
+#include "core/receiver.h"
+#include "core/producer.h"
+#include "core/object/payload.h"
+#include "core/timespec.h"
+
+#ifndef __dnsjit_output_tlscli_h
+#define __dnsjit_output_tlscli_h
+
+#include <gnutls/gnutls.h>
+
+#include "output/tlscli.hh"
+
+#endif
diff --git a/src/output/tlscli.hh b/src/output/tlscli.hh
new file mode 100644
index 0000000..4a0c142
--- /dev/null
+++ b/src/output/tlscli.hh
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.compat_h")
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+//lua:require("dnsjit.core.producer_h")
+//lua:require("dnsjit.core.object.payload_h")
+//lua:require("dnsjit.core.timespec_h")
+
+typedef struct output_tlscli {
+ core_log_t _log;
+ size_t pkts, errs;
+ int fd, tls_ok;
+
+ uint8_t recvbuf[64 * 1024];
+ core_object_payload_t pkt;
+ uint16_t dnslen;
+ uint8_t have_dnslen;
+ size_t recv, pkts_recv;
+
+ core_timespec_t timeout;
+
+ gnutls_session_t session;
+ gnutls_certificate_credentials_t cred;
+} output_tlscli_t;
+
+core_log_t* output_tlscli_log();
+
+void output_tlscli_init(output_tlscli_t* self);
+void output_tlscli_destroy(output_tlscli_t* self);
+int output_tlscli_connect(output_tlscli_t* self, const char* host, const char* port);
+
+core_receiver_t output_tlscli_receiver(output_tlscli_t* self);
+core_producer_t output_tlscli_producer(output_tlscli_t* self);
diff --git a/src/output/tlscli.lua b/src/output/tlscli.lua
new file mode 100644
index 0000000..e37439b
--- /dev/null
+++ b/src/output/tlscli.lua
@@ -0,0 +1,103 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.output.tlscli
+-- Simple TLS client
+-- local output = require("dnsjit.output.tlscli").new("127.0.0.1", "853")
+--
+-- Simple TLS client that attempts to do a TLS handshake (without
+-- certificate verification). It behaves the same way as tcpcli, except all
+-- the data is sent over the encrypted channel.
+-- .SS Attributes
+-- .TP
+-- timeout
+-- A
+-- .I core.timespec
+-- that is used when producing objects.
+module(...,package.seeall)
+
+require("dnsjit.output.tlscli_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "output_tlscli_t"
+local output_tlscli_t = ffi.typeof(t_name)
+local Tlscli = {}
+
+-- Create a new Tlscli output.
+function Tlscli.new()
+ local self = {
+ obj = output_tlscli_t(),
+ }
+ C.output_tlscli_init(self.obj)
+ ffi.gc(self.obj, C.output_tlscli_destroy)
+ return setmetatable(self, { __index = Tlscli })
+end
+
+-- Set the timeout when producing objects.
+function Tlscli:timeout(seconds, nanoseconds)
+ self.obj.timeout.sec = seconds
+ self.obj.timeout.nsec = nanoseconds
+end
+
+-- Connect to the
+-- .I host
+-- and
+-- .I port
+-- , perform a TLS handshake and return 0 if successful.
+function Tlscli:connect(host, port)
+ return C.output_tlscli_connect(self.obj, host, port)
+end
+
+-- Return the C functions and context for receiving objects, these objects
+-- will be sent.
+function Tlscli:receive()
+ return C.output_tlscli_receiver(self.obj), self.obj
+end
+
+-- Return the C functions and context for producing objects, these objects
+-- are received.
+-- The producer will wait for data and if timed out (see
+-- .IR timeout )
+-- it will return a payload object with length zero.
+-- If a timeout happens during during the first stage, getting the length, it
+-- will fail and return nil.
+-- Additional calls will continue retrieving the payload.
+-- The producer returns nil on error.
+function Tlscli:produce()
+ return C.output_tlscli_producer(self.obj), self.obj
+end
+
+-- Return the number of "packets" sent, actually the number of completely sent
+-- payloads.
+function Tlscli:packets()
+ return tonumber(self.obj.pkts)
+end
+
+-- Return the number of "packets" received, actually the number of completely
+-- received DNS messages.
+function Tlscli:received()
+ return tonumber(self.obj.pkts_recv)
+end
+
+-- Return the number of errors when sending.
+function Tlscli:errors()
+ return tonumber(self.obj.errs)
+end
+
+return Tlscli
diff --git a/src/output/udpcli.c b/src/output/udpcli.c
new file mode 100644
index 0000000..b207d9c
--- /dev/null
+++ b/src/output/udpcli.c
@@ -0,0 +1,300 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "output/udpcli.h"
+#include "core/assert.h"
+#include "core/object/dns.h"
+#include "core/object/payload.h"
+
+#include <netdb.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <poll.h>
+
+static core_log_t _log = LOG_T_INIT("output.udpcli");
+static output_udpcli_t _defaults = {
+ LOG_T_INIT_OBJ("output.udpcli"),
+ 0, 0, -1,
+ { 0 }, 0,
+ { 0 }, CORE_OBJECT_PAYLOAD_INIT(0), 0,
+ { 5, 0 }, 1
+};
+
+core_log_t* output_udpcli_log()
+{
+ return &_log;
+}
+
+void output_udpcli_init(output_udpcli_t* self)
+{
+ mlassert_self();
+
+ *self = _defaults;
+ self->pkt.payload = self->recvbuf;
+}
+
+void output_udpcli_destroy(output_udpcli_t* self)
+{
+ mlassert_self();
+
+ if (self->fd > -1) {
+ shutdown(self->fd, SHUT_RDWR);
+ close(self->fd);
+ }
+}
+
+int output_udpcli_connect(output_udpcli_t* self, const char* host, const char* port)
+{
+ struct addrinfo* addr;
+ int err;
+ mlassert_self();
+ lassert(host, "host is nil");
+ lassert(port, "port is nil");
+
+ if (self->fd > -1) {
+ lfatal("already connected");
+ }
+
+ if ((err = getaddrinfo(host, port, 0, &addr))) {
+ lcritical("getaddrinfo(%s, %s) error %s", host, port, gai_strerror(err));
+ return -1;
+ }
+ if (!addr) {
+ lcritical("getaddrinfo failed, no address returned");
+ return -1;
+ }
+
+ memcpy(&self->addr, addr->ai_addr, addr->ai_addrlen);
+ self->addr_len = addr->ai_addrlen;
+ freeaddrinfo(addr);
+
+ if ((self->fd = socket(((struct sockaddr*)&self->addr)->sa_family, SOCK_DGRAM, 0)) < 0) {
+ lcritical("socket() error %s", core_log_errstr(errno));
+ return -2;
+ }
+
+ return 0;
+}
+
+int output_udpcli_nonblocking(output_udpcli_t* self)
+{
+ int flags;
+ mlassert_self();
+
+ if (self->fd < 0) {
+ lfatal("not connected");
+ }
+
+ flags = fcntl(self->fd, F_GETFL);
+ if (flags != -1) {
+ flags = flags & O_NONBLOCK ? 1 : 0;
+ }
+
+ return flags;
+}
+
+int output_udpcli_set_nonblocking(output_udpcli_t* self, int nonblocking)
+{
+ int flags;
+ mlassert_self();
+
+ if (self->fd < 0) {
+ lfatal("not connected");
+ }
+
+ if ((flags = fcntl(self->fd, F_GETFL)) == -1) {
+ lcritical("fcntl(FL_GETFL) error %s", core_log_errstr(errno));
+ return -1;
+ }
+
+ if (nonblocking) {
+ flags |= O_NONBLOCK;
+ self->blocking = 0;
+ } else {
+ flags &= ~O_NONBLOCK;
+ self->blocking = 1;
+ }
+
+ if (fcntl(self->fd, F_SETFL, flags | O_NONBLOCK)) {
+ lcritical("fcntl(FL_SETFL, %x) error %s", flags, core_log_errstr(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static void _receive(output_udpcli_t* self, const core_object_t* obj)
+{
+ const uint8_t* payload;
+ size_t len, sent;
+ mlassert_self();
+
+ for (; obj;) {
+ switch (obj->obj_type) {
+ case CORE_OBJECT_DNS:
+ obj = obj->obj_prev;
+ continue;
+ case CORE_OBJECT_PAYLOAD:
+ payload = ((core_object_payload_t*)obj)->payload;
+ len = ((core_object_payload_t*)obj)->len;
+ break;
+ default:
+ return;
+ }
+
+ sent = 0;
+ for (;;) {
+ ssize_t ret = sendto(self->fd, payload + sent, len - sent, 0, (struct sockaddr*)&self->addr, self->addr_len);
+ if (ret > -1) {
+ sent += ret;
+ if (sent < len)
+ continue;
+ self->pkts++;
+ return;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ continue;
+ default:
+ break;
+ }
+ break;
+ }
+ self->errs++;
+ break;
+ }
+}
+
+core_receiver_t output_udpcli_receiver(output_udpcli_t* self)
+{
+ mlassert_self();
+
+ if (self->fd < 0) {
+ lfatal("not connected");
+ }
+
+ return (core_receiver_t)_receive;
+}
+
+static const core_object_t* _produce(output_udpcli_t* self)
+{
+ ssize_t n;
+ mlassert_self();
+
+ for (;;) {
+ n = recvfrom(self->fd, self->recvbuf, sizeof(self->recvbuf), 0, 0, 0);
+ if (n > -1) {
+ break;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ default:
+ break;
+ }
+ self->errs++;
+ break;
+ }
+
+ if (n < 1) {
+ return 0;
+ }
+
+ self->pkts_recv++;
+ self->pkt.len = n;
+ return (core_object_t*)&self->pkt;
+}
+
+static const core_object_t* _produce_block(output_udpcli_t* self)
+{
+ ssize_t n;
+ struct pollfd p;
+ int to;
+ mlassert_self();
+
+ p.fd = self->fd;
+ p.events = POLLIN;
+ p.revents = 0;
+ to = (self->timeout.sec * 1e3) + (self->timeout.nsec / 1e6); //NOSONAR
+ if (!to) {
+ to = 1;
+ }
+
+ n = poll(&p, 1, to);
+ if (n < 0 || (p.revents & (POLLERR | POLLHUP | POLLNVAL))) {
+ self->errs++;
+ return 0;
+ }
+ if (!n || !(p.revents & POLLIN)) {
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ }
+
+ for (;;) {
+ n = recvfrom(self->fd, self->recvbuf, sizeof(self->recvbuf), 0, 0, 0);
+ if (n > -1) {
+ break;
+ }
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ self->pkt.len = 0;
+ return (core_object_t*)&self->pkt;
+ default:
+ break;
+ }
+ self->errs++;
+ break;
+ }
+
+ if (n < 1) {
+ return 0;
+ }
+
+ self->pkts_recv++;
+ self->pkt.len = n;
+ return (core_object_t*)&self->pkt;
+}
+
+core_producer_t output_udpcli_producer(output_udpcli_t* self)
+{
+ mlassert_self();
+
+ if (self->fd < 0) {
+ lfatal("not connected");
+ }
+
+ if (self->blocking) {
+ return (core_producer_t)_produce_block;
+ }
+ return (core_producer_t)_produce;
+}
diff --git a/src/output/udpcli.h b/src/output/udpcli.h
new file mode 100644
index 0000000..4cb7af2
--- /dev/null
+++ b/src/output/udpcli.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core/log.h"
+#include "core/receiver.h"
+#include "core/producer.h"
+#include "core/object/payload.h"
+#include "core/timespec.h"
+
+#ifndef __dnsjit_output_udpcli_h
+#define __dnsjit_output_udpcli_h
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "output/udpcli.hh"
+
+#endif
diff --git a/src/output/udpcli.hh b/src/output/udpcli.hh
new file mode 100644
index 0000000..084a5b6
--- /dev/null
+++ b/src/output/udpcli.hh
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2021, OARC, Inc.
+ * All rights reserved.
+ *
+ * This file is part of dnsjit.
+ *
+ * dnsjit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+//lua:require("dnsjit.core.compat_h")
+//lua:require("dnsjit.core.log")
+//lua:require("dnsjit.core.receiver_h")
+//lua:require("dnsjit.core.producer_h")
+//lua:require("dnsjit.core.object.payload_h")
+//lua:require("dnsjit.core.timespec_h")
+
+typedef struct output_udpcli {
+ core_log_t _log;
+ size_t pkts, errs;
+ int fd;
+
+ struct sockaddr_storage addr;
+ size_t addr_len;
+
+ uint8_t recvbuf[4 * 1024];
+ core_object_payload_t pkt;
+ size_t pkts_recv;
+
+ core_timespec_t timeout;
+ int8_t blocking;
+} output_udpcli_t;
+
+core_log_t* output_udpcli_log();
+
+void output_udpcli_init(output_udpcli_t* self);
+void output_udpcli_destroy(output_udpcli_t* self);
+int output_udpcli_connect(output_udpcli_t* self, const char* host, const char* port);
+int output_udpcli_nonblocking(output_udpcli_t* self);
+int output_udpcli_set_nonblocking(output_udpcli_t* self, int nonblocking);
+
+core_receiver_t output_udpcli_receiver(output_udpcli_t* self);
+core_producer_t output_udpcli_producer(output_udpcli_t* self);
diff --git a/src/output/udpcli.lua b/src/output/udpcli.lua
new file mode 100644
index 0000000..0584725
--- /dev/null
+++ b/src/output/udpcli.lua
@@ -0,0 +1,121 @@
+-- Copyright (c) 2018-2021, OARC, Inc.
+-- All rights reserved.
+--
+-- This file is part of dnsjit.
+--
+-- dnsjit is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+-- dnsjit.output.udpcli
+-- Simple and dumb UDP DNS client
+-- local output = require("dnsjit.output.udpcli").new("127.0.0.1", "53")
+--
+-- Simple and rather dumb DNS client that takes any payload you give it and
+-- sends the full payload over UDP.
+-- .SS Attributes
+-- .TP
+-- timeout
+-- A
+-- .I core.timespec
+-- that is used when producing objects.
+module(...,package.seeall)
+
+require("dnsjit.output.udpcli_h")
+local ffi = require("ffi")
+local C = ffi.C
+
+local t_name = "output_udpcli_t"
+local output_udpcli_t = ffi.typeof(t_name)
+local Udpcli = {}
+
+-- Create a new Udpcli output.
+function Udpcli.new()
+ local self = {
+ obj = output_udpcli_t(),
+ }
+ C.output_udpcli_init(self.obj)
+ ffi.gc(self.obj, C.output_udpcli_destroy)
+ return setmetatable(self, { __index = Udpcli })
+end
+
+-- Set the timeout when producing objects.
+function Udpcli:timeout(seconds, nanoseconds)
+ self.obj.timeout.sec = seconds
+ self.obj.timeout.nsec = nanoseconds
+end
+
+-- Connect to the
+-- .I host
+-- and
+-- .I port
+-- and return 0 if successful.
+function Udpcli:connect(host, port)
+ return C.output_udpcli_connect(self.obj, host, port)
+end
+
+-- Enable (true) or disable (false) nonblocking mode and
+-- return 0 if successful, if
+-- .I bool
+-- is not specified then return if nonblocking mode is on (true) or off (false).
+function Udpcli:nonblocking(bool)
+ if bool == nil then
+ if C.output_udpcli_nonblocking(self.obj) == 1 then
+ return true
+ end
+ return false
+ elseif bool == true then
+ return C.output_udpcli_set_nonblocking(self.obj, 1)
+ else
+ return C.output_udpcli_set_nonblocking(self.obj, 0)
+ end
+end
+
+-- Return the C functions and context for receiving objects, these objects
+-- will be sent.
+function Udpcli:receive()
+ return C.output_udpcli_receiver(self.obj), self.obj
+end
+
+-- Return the C functions and context for producing objects, these objects
+-- are received.
+-- If nonblocking mode is enabled the producer will return a payload object
+-- with length zero if there was nothing to receive.
+-- If nonblocking mode is disabled the producer will wait for data and if
+-- timed out (see
+-- .IR timeout )
+-- it will return a payload object with length zero.
+-- The producer returns nil on error.
+function Udpcli:produce()
+ return C.output_udpcli_producer(self.obj), self.obj
+end
+
+-- Return the number of "packets" sent, actually the number of completely sent
+-- payloads.
+function Udpcli:packets()
+ return tonumber(self.obj.pkts)
+end
+
+-- Return the number of "packets" received, actually the number of successful
+-- calls to
+-- .IR recvfrom (2)
+-- that returned data.
+function Udpcli:received()
+ return tonumber(self.obj.pkts_recv)
+end
+
+-- Return the number of errors when sending or receiving.
+function Udpcli:errors()
+ return tonumber(self.obj.errs)
+end
+
+return Udpcli
diff --git a/src/test/Makefile.am b/src/test/Makefile.am
new file mode 100644
index 0000000..09a3c41
--- /dev/null
+++ b/src/test/Makefile.am
@@ -0,0 +1,47 @@
+# Copyright (c) 2018-2021, OARC, Inc.
+# All rights reserved.
+#
+# This file is part of dnsjit.
+#
+# dnsjit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
+CLEANFILES = test*.log test*.trs test*.out \
+ *.pcap-dist
+
+TESTS = test1.sh test2.sh test3.sh test4.sh test5.sh test6.sh test-ipsplit.sh \
+ test-trie.sh test-base64url.sh
+
+test1.sh: dns.pcap-dist
+
+test2.sh: dns.pcap-dist
+
+test3.sh: dns.pcap-dist
+
+test4.sh: dns.pcap-dist
+
+test5.sh: dns.pcap-dist
+
+test6.sh: dns.pcap-dist
+
+test-ipsplit.sh: pellets.pcap-dist dns.pcap-dist
+
+test-trie.sh: pellets.pcap-dist dns.pcap-dist
+
+.pcap.pcap-dist:
+ cp "$<" "$@"
+
+EXTRA_DIST = $(TESTS) \
+ dns.pcap pellets.pcap test_ipsplit.lua test_trie.lua test_base64url.lua \
+ test1.gold test2.gold test3.gold test4.gold
diff --git a/src/test/dns.pcap b/src/test/dns.pcap
new file mode 100644
index 0000000..a0e585c
--- /dev/null
+++ b/src/test/dns.pcap
Binary files differ
diff --git a/src/test/pellets.pcap b/src/test/pellets.pcap
new file mode 100644
index 0000000..ef39a7b
--- /dev/null
+++ b/src/test/pellets.pcap
Binary files differ
diff --git a/src/test/test-base64url.sh b/src/test/test-base64url.sh
new file mode 100755
index 0000000..163f847
--- /dev/null
+++ b/src/test/test-base64url.sh
@@ -0,0 +1,20 @@
+#!/bin/sh -e
+# Copyright (c) 2020, CZ.NIC, z.s.p.o.
+# All rights reserved.
+#
+# This file is part of dnsjit.
+#
+# dnsjit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+../dnsjit "$srcdir/test_base64url.lua"
diff --git a/src/test/test-ipsplit.sh b/src/test/test-ipsplit.sh
new file mode 100755
index 0000000..3d118d6
--- /dev/null
+++ b/src/test/test-ipsplit.sh
@@ -0,0 +1,20 @@
+#!/bin/sh -e
+# Copyright (c) 2020, CZ.NIC, z.s.p.o.
+# All rights reserved.
+#
+# This file is part of dnsjit.
+#
+# dnsjit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+../dnsjit "$srcdir/test_ipsplit.lua"
diff --git a/src/test/test-trie.sh b/src/test/test-trie.sh
new file mode 100755
index 0000000..238eea1
--- /dev/null
+++ b/src/test/test-trie.sh
@@ -0,0 +1,20 @@
+#!/bin/sh -e
+# Copyright (c) 2020, CZ.NIC, z.s.p.o.
+# All rights reserved.
+#
+# This file is part of dnsjit.
+#
+# dnsjit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+../dnsjit "$srcdir/test_trie.lua"
diff --git a/src/test/test1.gold b/src/test/test1.gold
new file mode 100644
index 0000000..7ded214
--- /dev/null
+++ b/src/test/test1.gold
@@ -0,0 +1,1493 @@
+udp 172.17.0.10:53199 -> 8.8.8.8:53
+id: 59311
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:53199
+id: 59311
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 44 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157880 <12> <56>ns4.<12>
+ IN NS 157880 <12> <74>ns3.<12>
+ IN NS 157880 <12> <92>ns1.<12>
+ IN NS 157880 <12> <110>ns2.<12>
+additionals: class type ttl labels RR labels
+ IN A 157880 <110>
+ IN A 331882 <92>
+ IN A 157880 <74>
+ IN A 157880 <56>
+udp 172.17.0.10:57822 -> 8.8.8.8:53
+id: 35665
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+udp 8.8.8.8:53 -> 172.17.0.10:57822
+id: 35665
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 2
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+answers: class type ttl labels RR labels
+ IN PTR 72125 <12> <57>dfw06s47-in-f14.<73>1e100.<79>net.
+ IN PTR 72125 <12> <96>dfw06s47-in-f206.<73>
+authorities: class type ttl labels RR labels
+ IN NS 71608 <16> <127>ns2.<131>google.<138>com.
+ IN NS 71608 <16> <155>ns3.<131>
+ IN NS 71608 <16> <173>ns1.<131>
+ IN NS 71608 <16> <191>ns4.<131>
+additionals: class type ttl labels RR labels
+ IN A 331882 <173>
+ IN A 157880 <155>
+ IN A 157880 <191>
+ IN A 157880 <127>
+udp 172.17.0.10:40043 -> 8.8.8.8:53
+id: 5337
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:40043
+id: 5337
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 44 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157880 <12> <56>ns1.<12>
+ IN NS 157880 <12> <74>ns2.<12>
+ IN NS 157880 <12> <92>ns3.<12>
+ IN NS 157880 <12> <110>ns4.<12>
+additionals: class type ttl labels RR labels
+ IN A 157880 <74>
+ IN A 331882 <56>
+ IN A 157880 <92>
+ IN A 157880 <110>
+udp 172.17.0.10:37953 -> 8.8.8.8:53
+id: 22982
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:37953
+id: 22982
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 34 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157870 <12> <56>ns4.<12>
+ IN NS 157870 <12> <74>ns1.<12>
+ IN NS 157870 <12> <92>ns2.<12>
+ IN NS 157870 <12> <110>ns3.<12>
+additionals: class type ttl labels RR labels
+ IN A 157870 <92>
+ IN A 331872 <74>
+ IN A 157870 <110>
+ IN A 157870 <56>
+udp 172.17.0.10:48658 -> 8.8.8.8:53
+id: 18718
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+udp 8.8.8.8:53 -> 172.17.0.10:48658
+id: 18718
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 2
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+answers: class type ttl labels RR labels
+ IN PTR 72115 <12> <57>dfw06s47-in-f206.<74>1e100.<80>net.
+ IN PTR 72115 <12> <97>dfw06s47-in-f14.<74>
+authorities: class type ttl labels RR labels
+ IN NS 71598 <16> <127>ns2.<131>google.<138>com.
+ IN NS 71598 <16> <155>ns3.<131>
+ IN NS 71598 <16> <173>ns4.<131>
+ IN NS 71598 <16> <191>ns1.<131>
+additionals: class type ttl labels RR labels
+ IN A 331872 <191>
+ IN A 157870 <155>
+ IN A 157870 <173>
+ IN A 157870 <127>
+udp 172.17.0.10:40953 -> 8.8.8.8:53
+id: 22531
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:40953
+id: 22531
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 297 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157828 <12> <56>ns2.<12>
+ IN NS 157828 <12> <74>ns4.<12>
+ IN NS 157828 <12> <92>ns1.<12>
+ IN NS 157828 <12> <110>ns3.<12>
+additionals: class type ttl labels RR labels
+ IN A 157828 <56>
+ IN A 331830 <92>
+ IN A 157828 <110>
+ IN A 157828 <74>
+udp 172.17.0.10:45174 -> 8.8.8.8:53
+id: 58510
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:45174
+id: 58510
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 291 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157822 <12> <56>ns2.<12>
+ IN NS 157822 <12> <74>ns3.<12>
+ IN NS 157822 <12> <92>ns1.<12>
+ IN NS 157822 <12> <110>ns4.<12>
+additionals: class type ttl labels RR labels
+ IN A 157822 <56>
+ IN A 331824 <92>
+ IN A 157822 <74>
+ IN A 157822 <110>
+udp 172.17.0.10:33916 -> 8.8.8.8:53
+id: 45248
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+udp 8.8.8.8:53 -> 172.17.0.10:33916
+id: 45248
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 2
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+answers: class type ttl labels RR labels
+ IN PTR 72067 <12> <57>dfw06s47-in-f14.<73>1e100.<79>net.
+ IN PTR 72067 <12> <96>dfw06s47-in-f206.<73>
+authorities: class type ttl labels RR labels
+ IN NS 71550 <16> <127>ns3.<131>google.<138>com.
+ IN NS 71550 <16> <155>ns4.<131>
+ IN NS 71550 <16> <173>ns2.<131>
+ IN NS 71550 <16> <191>ns1.<131>
+additionals: class type ttl labels RR labels
+ IN A 331824 <191>
+ IN A 157822 <127>
+ IN A 157822 <155>
+ IN A 157822 <173>
+udp 172.17.0.10:43559 -> 8.8.8.8:53
+id: 49483
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:43559
+id: 49483
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 285 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157816 <12> <56>ns4.<12>
+ IN NS 157816 <12> <74>ns3.<12>
+ IN NS 157816 <12> <92>ns1.<12>
+ IN NS 157816 <12> <110>ns2.<12>
+additionals: class type ttl labels RR labels
+ IN A 157816 <110>
+ IN A 331818 <92>
+ IN A 157816 <74>
+ IN A 157816 <56>
+udp 172.17.0.10:54859 -> 8.8.8.8:53
+id: 31669
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:54859
+id: 31669
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 283 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157814 <12> <56>ns2.<12>
+ IN NS 157814 <12> <74>ns1.<12>
+ IN NS 157814 <12> <92>ns4.<12>
+ IN NS 157814 <12> <110>ns3.<12>
+additionals: class type ttl labels RR labels
+ IN A 157814 <56>
+ IN A 331816 <74>
+ IN A 157814 <110>
+ IN A 157814 <92>
+udp 172.17.0.10:58176 -> 8.8.8.8:53
+id: 25433
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+udp 8.8.8.8:53 -> 172.17.0.10:58176
+id: 25433
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 2
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+answers: class type ttl labels RR labels
+ IN PTR 72059 <12> <57>dfw06s47-in-f206.<74>1e100.<80>net.
+ IN PTR 72059 <12> <97>dfw06s47-in-f14.<74>
+authorities: class type ttl labels RR labels
+ IN NS 71542 <16> <127>ns4.<131>google.<138>com.
+ IN NS 71542 <16> <155>ns1.<131>
+ IN NS 71542 <16> <173>ns3.<131>
+ IN NS 71542 <16> <191>ns2.<131>
+additionals: class type ttl labels RR labels
+ IN A 331816 <155>
+ IN A 157814 <173>
+ IN A 157814 <127>
+ IN A 157814 <191>
+udp 172.17.0.10:41266 -> 8.8.8.8:53
+id: 63798
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:41266
+id: 63798
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 282 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157813 <12> <56>ns4.<12>
+ IN NS 157813 <12> <74>ns1.<12>
+ IN NS 157813 <12> <92>ns3.<12>
+ IN NS 157813 <12> <110>ns2.<12>
+additionals: class type ttl labels RR labels
+ IN A 157813 <110>
+ IN A 331815 <74>
+ IN A 157813 <92>
+ IN A 157813 <56>
+udp 172.17.0.10:34607 -> 8.8.8.8:53
+id: 8470
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+udp 8.8.8.8:53 -> 172.17.0.10:34607
+id: 8470
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 2
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+answers: class type ttl labels RR labels
+ IN PTR 72058 <12> <57>dfw06s47-in-f14.<73>1e100.<79>net.
+ IN PTR 72058 <12> <96>dfw06s47-in-f206.<73>
+authorities: class type ttl labels RR labels
+ IN NS 71541 <16> <127>ns1.<131>google.<138>com.
+ IN NS 71541 <16> <155>ns2.<131>
+ IN NS 71541 <16> <173>ns4.<131>
+ IN NS 71541 <16> <191>ns3.<131>
+additionals: class type ttl labels RR labels
+ IN A 331815 <127>
+ IN A 157813 <191>
+ IN A 157813 <173>
+ IN A 157813 <155>
+udp 172.17.0.10:60437 -> 8.8.8.8:53
+id: 60258
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:60437
+id: 60258
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 281 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157812 <12> <56>ns3.<12>
+ IN NS 157812 <12> <74>ns2.<12>
+ IN NS 157812 <12> <92>ns4.<12>
+ IN NS 157812 <12> <110>ns1.<12>
+additionals: class type ttl labels RR labels
+ IN A 157812 <74>
+ IN A 331814 <110>
+ IN A 157812 <56>
+ IN A 157812 <92>
+udp 172.17.0.10:37149 -> 8.8.8.8:53
+id: 44985
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+udp 8.8.8.8:53 -> 172.17.0.10:37149
+id: 44985
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 2
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+answers: class type ttl labels RR labels
+ IN PTR 72057 <12> <57>dfw06s47-in-f206.<74>1e100.<80>net.
+ IN PTR 72057 <12> <97>dfw06s47-in-f14.<74>
+authorities: class type ttl labels RR labels
+ IN NS 71540 <16> <127>ns4.<131>google.<138>com.
+ IN NS 71540 <16> <155>ns3.<131>
+ IN NS 71540 <16> <173>ns1.<131>
+ IN NS 71540 <16> <191>ns2.<131>
+additionals: class type ttl labels RR labels
+ IN A 331814 <173>
+ IN A 157812 <155>
+ IN A 157812 <127>
+ IN A 157812 <191>
+udp 172.17.0.10:53820 -> 8.8.8.8:53
+id: 45512
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:53820
+id: 45512
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 280 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157811 <12> <56>ns3.<12>
+ IN NS 157811 <12> <74>ns4.<12>
+ IN NS 157811 <12> <92>ns1.<12>
+ IN NS 157811 <12> <110>ns2.<12>
+additionals: class type ttl labels RR labels
+ IN A 157811 <110>
+ IN A 331813 <92>
+ IN A 157811 <56>
+ IN A 157811 <74>
+udp 172.17.0.10:52368 -> 8.8.8.8:53
+id: 22980
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+udp 8.8.8.8:53 -> 172.17.0.10:52368
+id: 22980
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 2
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+answers: class type ttl labels RR labels
+ IN PTR 72056 <12> <57>dfw06s47-in-f14.<73>1e100.<79>net.
+ IN PTR 72056 <12> <96>dfw06s47-in-f206.<73>
+authorities: class type ttl labels RR labels
+ IN NS 71539 <16> <127>ns2.<131>google.<138>com.
+ IN NS 71539 <16> <155>ns3.<131>
+ IN NS 71539 <16> <173>ns4.<131>
+ IN NS 71539 <16> <191>ns1.<131>
+additionals: class type ttl labels RR labels
+ IN A 331813 <191>
+ IN A 157811 <155>
+ IN A 157811 <173>
+ IN A 157811 <127>
+udp 172.17.0.10:47637 -> 8.8.8.8:53
+id: 1834
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:47637
+id: 1834
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 279 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157810 <12> <56>ns1.<12>
+ IN NS 157810 <12> <74>ns2.<12>
+ IN NS 157810 <12> <92>ns4.<12>
+ IN NS 157810 <12> <110>ns3.<12>
+additionals: class type ttl labels RR labels
+ IN A 157810 <74>
+ IN A 331812 <56>
+ IN A 157810 <110>
+ IN A 157810 <92>
+udp 172.17.0.10:34426 -> 8.8.8.8:53
+id: 25431
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+udp 8.8.8.8:53 -> 172.17.0.10:34426
+id: 25431
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 2
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+answers: class type ttl labels RR labels
+ IN PTR 72055 <12> <57>dfw06s47-in-f206.<74>1e100.<80>net.
+ IN PTR 72055 <12> <97>dfw06s47-in-f14.<74>
+authorities: class type ttl labels RR labels
+ IN NS 71538 <16> <127>ns1.<131>google.<138>com.
+ IN NS 71538 <16> <155>ns4.<131>
+ IN NS 71538 <16> <173>ns3.<131>
+ IN NS 71538 <16> <191>ns2.<131>
+additionals: class type ttl labels RR labels
+ IN A 331812 <127>
+ IN A 157810 <173>
+ IN A 157810 <155>
+ IN A 157810 <191>
+udp 172.17.0.10:41059 -> 8.8.8.8:53
+id: 48432
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:41059
+id: 48432
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 278 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157809 <12> <56>ns3.<12>
+ IN NS 157809 <12> <74>ns4.<12>
+ IN NS 157809 <12> <92>ns2.<12>
+ IN NS 157809 <12> <110>ns1.<12>
+additionals: class type ttl labels RR labels
+ IN A 157809 <92>
+ IN A 331811 <110>
+ IN A 157809 <56>
+ IN A 157809 <74>
+udp 172.17.0.10:51181 -> 8.8.8.8:53
+id: 47411
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+udp 8.8.8.8:53 -> 172.17.0.10:51181
+id: 47411
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 2
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+answers: class type ttl labels RR labels
+ IN PTR 72054 <12> <57>dfw06s47-in-f14.<73>1e100.<79>net.
+ IN PTR 72054 <12> <96>dfw06s47-in-f206.<73>
+authorities: class type ttl labels RR labels
+ IN NS 71537 <16> <127>ns2.<131>google.<138>com.
+ IN NS 71537 <16> <155>ns1.<131>
+ IN NS 71537 <16> <173>ns3.<131>
+ IN NS 71537 <16> <191>ns4.<131>
+additionals: class type ttl labels RR labels
+ IN A 331811 <155>
+ IN A 157809 <173>
+ IN A 157809 <191>
+ IN A 157809 <127>
+udp 172.17.0.10:32976 -> 8.8.8.8:53
+id: 12038
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:32976
+id: 12038
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 277 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157808 <12> <56>ns2.<12>
+ IN NS 157808 <12> <74>ns3.<12>
+ IN NS 157808 <12> <92>ns1.<12>
+ IN NS 157808 <12> <110>ns4.<12>
+additionals: class type ttl labels RR labels
+ IN A 157808 <56>
+ IN A 331810 <92>
+ IN A 157808 <74>
+ IN A 157808 <110>
+udp 172.17.0.10:53467 -> 8.8.8.8:53
+id: 11614
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:53467
+id: 11614
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 275 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157806 <12> <56>ns3.<12>
+ IN NS 157806 <12> <74>ns1.<12>
+ IN NS 157806 <12> <92>ns4.<12>
+ IN NS 157806 <12> <110>ns2.<12>
+additionals: class type ttl labels RR labels
+ IN A 157806 <110>
+ IN A 331808 <74>
+ IN A 157806 <56>
+ IN A 157806 <92>
+udp 172.17.0.10:41532 -> 8.8.8.8:53
+id: 59173
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:41532
+id: 59173
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 273 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157804 <12> <56>ns1.<12>
+ IN NS 157804 <12> <74>ns3.<12>
+ IN NS 157804 <12> <92>ns2.<12>
+ IN NS 157804 <12> <110>ns4.<12>
+additionals: class type ttl labels RR labels
+ IN A 157804 <92>
+ IN A 331806 <56>
+ IN A 157804 <74>
+ IN A 157804 <110>
+udp 172.17.0.10:44982 -> 8.8.8.8:53
+id: 45535
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:44982
+id: 45535
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 271 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157802 <12> <56>ns4.<12>
+ IN NS 157802 <12> <74>ns2.<12>
+ IN NS 157802 <12> <92>ns1.<12>
+ IN NS 157802 <12> <110>ns3.<12>
+additionals: class type ttl labels RR labels
+ IN A 157802 <74>
+ IN A 331804 <92>
+ IN A 157802 <110>
+ IN A 157802 <56>
+udp 172.17.0.10:40224 -> 8.8.8.8:53
+id: 60808
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+udp 8.8.8.8:53 -> 172.17.0.10:40224
+id: 60808
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 2
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+answers: class type ttl labels RR labels
+ IN PTR 72047 <12> <57>dfw06s47-in-f206.<74>1e100.<80>net.
+ IN PTR 72047 <12> <97>dfw06s47-in-f14.<74>
+authorities: class type ttl labels RR labels
+ IN NS 71530 <16> <127>ns3.<131>google.<138>com.
+ IN NS 71530 <16> <155>ns4.<131>
+ IN NS 71530 <16> <173>ns2.<131>
+ IN NS 71530 <16> <191>ns1.<131>
+additionals: class type ttl labels RR labels
+ IN A 331804 <191>
+ IN A 157802 <127>
+ IN A 157802 <155>
+ IN A 157802 <173>
+udp 172.17.0.10:45658 -> 8.8.8.8:53
+id: 64325
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:45658
+id: 64325
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 270 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157801 <12> <56>ns1.<12>
+ IN NS 157801 <12> <74>ns3.<12>
+ IN NS 157801 <12> <92>ns4.<12>
+ IN NS 157801 <12> <110>ns2.<12>
+additionals: class type ttl labels RR labels
+ IN A 157801 <110>
+ IN A 331803 <56>
+ IN A 157801 <74>
+ IN A 157801 <92>
+udp 172.17.0.10:60457 -> 8.8.8.8:53
+id: 25543
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+udp 8.8.8.8:53 -> 172.17.0.10:60457
+id: 25543
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 2
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+answers: class type ttl labels RR labels
+ IN PTR 72046 <12> <57>dfw06s47-in-f14.<73>1e100.<79>net.
+ IN PTR 72046 <12> <96>dfw06s47-in-f206.<73>
+authorities: class type ttl labels RR labels
+ IN NS 71529 <16> <127>ns2.<131>google.<138>com.
+ IN NS 71529 <16> <155>ns3.<131>
+ IN NS 71529 <16> <173>ns4.<131>
+ IN NS 71529 <16> <191>ns1.<131>
+additionals: class type ttl labels RR labels
+ IN A 331803 <191>
+ IN A 157801 <155>
+ IN A 157801 <173>
+ IN A 157801 <127>
+udp 172.17.0.10:59762 -> 8.8.8.8:53
+id: 20736
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:59762
+id: 20736
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 269 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157800 <12> <56>ns3.<12>
+ IN NS 157800 <12> <74>ns1.<12>
+ IN NS 157800 <12> <92>ns4.<12>
+ IN NS 157800 <12> <110>ns2.<12>
+additionals: class type ttl labels RR labels
+ IN A 157800 <110>
+ IN A 331802 <74>
+ IN A 157800 <56>
+ IN A 157800 <92>
+udp 172.17.0.10:56022 -> 8.8.8.8:53
+id: 25911
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+udp 8.8.8.8:53 -> 172.17.0.10:56022
+id: 25911
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 2
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+answers: class type ttl labels RR labels
+ IN PTR 72045 <12> <57>dfw06s47-in-f206.<74>1e100.<80>net.
+ IN PTR 72045 <12> <97>dfw06s47-in-f14.<74>
+authorities: class type ttl labels RR labels
+ IN NS 71528 <16> <127>ns1.<131>google.<138>com.
+ IN NS 71528 <16> <155>ns4.<131>
+ IN NS 71528 <16> <173>ns2.<131>
+ IN NS 71528 <16> <191>ns3.<131>
+additionals: class type ttl labels RR labels
+ IN A 331802 <127>
+ IN A 157800 <191>
+ IN A 157800 <155>
+ IN A 157800 <173>
+udp 172.17.0.10:37669 -> 8.8.8.8:53
+id: 64358
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:37669
+id: 64358
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 268 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157799 <12> <56>ns2.<12>
+ IN NS 157799 <12> <74>ns1.<12>
+ IN NS 157799 <12> <92>ns4.<12>
+ IN NS 157799 <12> <110>ns3.<12>
+additionals: class type ttl labels RR labels
+ IN A 157799 <56>
+ IN A 331801 <74>
+ IN A 157799 <110>
+ IN A 157799 <92>
+udp 172.17.0.10:42978 -> 8.8.8.8:53
+id: 37698
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+udp 8.8.8.8:53 -> 172.17.0.10:42978
+id: 37698
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 2
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+answers: class type ttl labels RR labels
+ IN PTR 72044 <12> <57>dfw06s47-in-f14.<73>1e100.<79>net.
+ IN PTR 72044 <12> <96>dfw06s47-in-f206.<73>
+authorities: class type ttl labels RR labels
+ IN NS 71527 <16> <127>ns1.<131>google.<138>com.
+ IN NS 71527 <16> <155>ns4.<131>
+ IN NS 71527 <16> <173>ns3.<131>
+ IN NS 71527 <16> <191>ns2.<131>
+additionals: class type ttl labels RR labels
+ IN A 331801 <127>
+ IN A 157799 <173>
+ IN A 157799 <155>
+ IN A 157799 <191>
+udp 172.17.0.10:49829 -> 8.8.8.8:53
+id: 54706
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:49829
+id: 54706
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 267 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157798 <12> <56>ns2.<12>
+ IN NS 157798 <12> <74>ns4.<12>
+ IN NS 157798 <12> <92>ns3.<12>
+ IN NS 157798 <12> <110>ns1.<12>
+additionals: class type ttl labels RR labels
+ IN A 157798 <56>
+ IN A 331800 <110>
+ IN A 157798 <92>
+ IN A 157798 <74>
+udp 172.17.0.10:50599 -> 8.8.8.8:53
+id: 32142
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+udp 8.8.8.8:53 -> 172.17.0.10:50599
+id: 32142
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 2
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+answers: class type ttl labels RR labels
+ IN PTR 72043 <12> <57>dfw06s47-in-f206.<74>1e100.<80>net.
+ IN PTR 72043 <12> <97>dfw06s47-in-f14.<74>
+authorities: class type ttl labels RR labels
+ IN NS 71526 <16> <127>ns3.<131>google.<138>com.
+ IN NS 71526 <16> <155>ns1.<131>
+ IN NS 71526 <16> <173>ns2.<131>
+ IN NS 71526 <16> <191>ns4.<131>
+additionals: class type ttl labels RR labels
+ IN A 331800 <155>
+ IN A 157798 <127>
+ IN A 157798 <191>
+ IN A 157798 <173>
+udp 172.17.0.10:44980 -> 8.8.8.8:53
+id: 41808
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:44980
+id: 41808
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 266 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157797 <12> <56>ns2.<12>
+ IN NS 157797 <12> <74>ns4.<12>
+ IN NS 157797 <12> <92>ns1.<12>
+ IN NS 157797 <12> <110>ns3.<12>
+additionals: class type ttl labels RR labels
+ IN A 157797 <56>
+ IN A 331799 <92>
+ IN A 157797 <110>
+ IN A 157797 <74>
+udp 172.17.0.10:60063 -> 8.8.8.8:53
+id: 18886
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+udp 8.8.8.8:53 -> 172.17.0.10:60063
+id: 18886
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 2
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+answers: class type ttl labels RR labels
+ IN PTR 72042 <12> <57>dfw06s47-in-f14.<73>1e100.<79>net.
+ IN PTR 72042 <12> <96>dfw06s47-in-f206.<73>
+authorities: class type ttl labels RR labels
+ IN NS 71525 <16> <127>ns3.<131>google.<138>com.
+ IN NS 71525 <16> <155>ns1.<131>
+ IN NS 71525 <16> <173>ns4.<131>
+ IN NS 71525 <16> <191>ns2.<131>
+additionals: class type ttl labels RR labels
+ IN A 331799 <155>
+ IN A 157797 <127>
+ IN A 157797 <173>
+ IN A 157797 <191>
+udp 172.17.0.10:42042 -> 8.8.8.8:53
+id: 10624
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:42042
+id: 10624
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 265 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157796 <12> <56>ns3.<12>
+ IN NS 157796 <12> <74>ns4.<12>
+ IN NS 157796 <12> <92>ns1.<12>
+ IN NS 157796 <12> <110>ns2.<12>
+additionals: class type ttl labels RR labels
+ IN A 157796 <110>
+ IN A 331798 <92>
+ IN A 157796 <56>
+ IN A 157796 <74>
+udp 172.17.0.10:60469 -> 8.8.8.8:53
+id: 33139
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+udp 8.8.8.8:53 -> 172.17.0.10:60469
+id: 33139
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 2
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+answers: class type ttl labels RR labels
+ IN PTR 72041 <12> <57>dfw06s47-in-f206.<74>1e100.<80>net.
+ IN PTR 72041 <12> <97>dfw06s47-in-f14.<74>
+authorities: class type ttl labels RR labels
+ IN NS 71524 <16> <127>ns2.<131>google.<138>com.
+ IN NS 71524 <16> <155>ns4.<131>
+ IN NS 71524 <16> <173>ns3.<131>
+ IN NS 71524 <16> <191>ns1.<131>
+additionals: class type ttl labels RR labels
+ IN A 331798 <191>
+ IN A 157796 <173>
+ IN A 157796 <155>
+ IN A 157796 <127>
+udp 172.17.0.10:45703 -> 8.8.8.8:53
+id: 61415
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:45703
+id: 61415
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 264 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157795 <12> <56>ns3.<12>
+ IN NS 157795 <12> <74>ns4.<12>
+ IN NS 157795 <12> <92>ns2.<12>
+ IN NS 157795 <12> <110>ns1.<12>
+additionals: class type ttl labels RR labels
+ IN A 157795 <92>
+ IN A 331797 <110>
+ IN A 157795 <56>
+ IN A 157795 <74>
+udp 172.17.0.10:33507 -> 8.8.8.8:53
+id: 59258
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+udp 8.8.8.8:53 -> 172.17.0.10:33507
+id: 59258
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 2
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN PTR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa.
+answers: class type ttl labels RR labels
+ IN PTR 72040 <12> <57>dfw06s47-in-f14.<73>1e100.<79>net.
+ IN PTR 72040 <12> <96>dfw06s47-in-f206.<73>
+authorities: class type ttl labels RR labels
+ IN NS 71523 <16> <127>ns1.<131>google.<138>com.
+ IN NS 71523 <16> <155>ns4.<131>
+ IN NS 71523 <16> <173>ns3.<131>
+ IN NS 71523 <16> <191>ns2.<131>
+additionals: class type ttl labels RR labels
+ IN A 331797 <127>
+ IN A 157795 <173>
+ IN A 157795 <155>
+ IN A 157795 <191>
+udp 172.17.0.10:46798 -> 8.8.8.8:53
+id: 17700
+ qr: 0
+ opcode: QUERY
+ flags: RD
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 0
+ nscount: 0
+ arcount: 0
+questions: class type labels
+ IN A <12>google.<19>com.
+udp 8.8.8.8:53 -> 172.17.0.10:46798
+id: 17700
+ qr: 1
+ opcode: QUERY
+ flags: RD RA
+ rcode: NOERROR
+ qdcount: 1
+ ancount: 1
+ nscount: 4
+ arcount: 4
+questions: class type labels
+ IN A <12>google.<19>com.
+answers: class type ttl labels RR labels
+ IN A 263 <12>
+authorities: class type ttl labels RR labels
+ IN NS 157794 <12> <56>ns1.<12>
+ IN NS 157794 <12> <74>ns4.<12>
+ IN NS 157794 <12> <92>ns3.<12>
+ IN NS 157794 <12> <110>ns2.<12>
+additionals: class type ttl labels RR labels
+ IN A 157794 <110>
+ IN A 331796 <56>
+ IN A 157794 <92>
+ IN A 157794 <74>
diff --git a/src/test/test1.sh b/src/test/test1.sh
new file mode 100755
index 0000000..42bc635
--- /dev/null
+++ b/src/test/test1.sh
@@ -0,0 +1,21 @@
+#!/bin/sh -e
+# Copyright (c) 2018-2021, OARC, Inc.
+# All rights reserved.
+#
+# This file is part of dnsjit.
+#
+# dnsjit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+../dnsjit "$srcdir/../../examples/dumpdns.lua" dns.pcap-dist >test1.out
+diff "$srcdir/test1.gold" test1.out
diff --git a/src/test/test2.gold b/src/test/test2.gold
new file mode 100644
index 0000000..2fb62b0
--- /dev/null
+++ b/src/test/test2.gold
@@ -0,0 +1,42 @@
+src dst id rcode qname qtype
+172.17.0.10 8.8.8.8 59311 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 35665 NOERROR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa. PTR
+172.17.0.10 8.8.8.8 5337 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 22982 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 18718 NOERROR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa. PTR
+172.17.0.10 8.8.8.8 22531 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 58510 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 45248 NOERROR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa. PTR
+172.17.0.10 8.8.8.8 49483 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 31669 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 25433 NOERROR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa. PTR
+172.17.0.10 8.8.8.8 63798 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 8470 NOERROR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa. PTR
+172.17.0.10 8.8.8.8 60258 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 44985 NOERROR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa. PTR
+172.17.0.10 8.8.8.8 45512 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 22980 NOERROR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa. PTR
+172.17.0.10 8.8.8.8 1834 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 25431 NOERROR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa. PTR
+172.17.0.10 8.8.8.8 48432 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 47411 NOERROR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa. PTR
+172.17.0.10 8.8.8.8 12038 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 11614 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 59173 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 45535 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 60808 NOERROR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa. PTR
+172.17.0.10 8.8.8.8 64325 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 25543 NOERROR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa. PTR
+172.17.0.10 8.8.8.8 20736 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 25911 NOERROR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa. PTR
+172.17.0.10 8.8.8.8 64358 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 37698 NOERROR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa. PTR
+172.17.0.10 8.8.8.8 54706 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 32142 NOERROR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa. PTR
+172.17.0.10 8.8.8.8 41808 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 18886 NOERROR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa. PTR
+172.17.0.10 8.8.8.8 10624 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 33139 NOERROR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa. PTR
+172.17.0.10 8.8.8.8 61415 NOERROR <12>google.<19>com. A
+172.17.0.10 8.8.8.8 59258 NOERROR <12>206.<16>218.<20>58.<23>216.<27>in-addr.<35>arpa. PTR
+172.17.0.10 8.8.8.8 17700 NOERROR <12>google.<19>com. A
diff --git a/src/test/test2.sh b/src/test/test2.sh
new file mode 100755
index 0000000..9a58d06
--- /dev/null
+++ b/src/test/test2.sh
@@ -0,0 +1,21 @@
+#!/bin/sh -e
+# Copyright (c) 2018-2021, OARC, Inc.
+# All rights reserved.
+#
+# This file is part of dnsjit.
+#
+# dnsjit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+../dnsjit "$srcdir/../../examples/dumpdns-qr.lua" dns.pcap-dist >test2.out
+diff "$srcdir/test2.gold" test2.out
diff --git a/src/test/test3.gold b/src/test/test3.gold
new file mode 100644
index 0000000..cdbd0bf
--- /dev/null
+++ b/src/test/test3.gold
@@ -0,0 +1,82 @@
+59311 172.17.0.10 -> 8.8.8.8
+59311 8.8.8.8 -> 172.17.0.10
+35665 172.17.0.10 -> 8.8.8.8
+35665 8.8.8.8 -> 172.17.0.10
+5337 172.17.0.10 -> 8.8.8.8
+5337 8.8.8.8 -> 172.17.0.10
+22982 172.17.0.10 -> 8.8.8.8
+22982 8.8.8.8 -> 172.17.0.10
+18718 172.17.0.10 -> 8.8.8.8
+18718 8.8.8.8 -> 172.17.0.10
+22531 172.17.0.10 -> 8.8.8.8
+22531 8.8.8.8 -> 172.17.0.10
+58510 172.17.0.10 -> 8.8.8.8
+58510 8.8.8.8 -> 172.17.0.10
+45248 172.17.0.10 -> 8.8.8.8
+45248 8.8.8.8 -> 172.17.0.10
+49483 172.17.0.10 -> 8.8.8.8
+49483 8.8.8.8 -> 172.17.0.10
+31669 172.17.0.10 -> 8.8.8.8
+31669 8.8.8.8 -> 172.17.0.10
+25433 172.17.0.10 -> 8.8.8.8
+25433 8.8.8.8 -> 172.17.0.10
+63798 172.17.0.10 -> 8.8.8.8
+63798 8.8.8.8 -> 172.17.0.10
+8470 172.17.0.10 -> 8.8.8.8
+8470 8.8.8.8 -> 172.17.0.10
+60258 172.17.0.10 -> 8.8.8.8
+60258 8.8.8.8 -> 172.17.0.10
+44985 172.17.0.10 -> 8.8.8.8
+44985 8.8.8.8 -> 172.17.0.10
+45512 172.17.0.10 -> 8.8.8.8
+45512 8.8.8.8 -> 172.17.0.10
+22980 172.17.0.10 -> 8.8.8.8
+22980 8.8.8.8 -> 172.17.0.10
+1834 172.17.0.10 -> 8.8.8.8
+1834 8.8.8.8 -> 172.17.0.10
+25431 172.17.0.10 -> 8.8.8.8
+25431 8.8.8.8 -> 172.17.0.10
+48432 172.17.0.10 -> 8.8.8.8
+48432 8.8.8.8 -> 172.17.0.10
+47411 172.17.0.10 -> 8.8.8.8
+47411 8.8.8.8 -> 172.17.0.10
+12038 172.17.0.10 -> 8.8.8.8
+12038 8.8.8.8 -> 172.17.0.10
+11614 172.17.0.10 -> 8.8.8.8
+11614 8.8.8.8 -> 172.17.0.10
+59173 172.17.0.10 -> 8.8.8.8
+59173 8.8.8.8 -> 172.17.0.10
+45535 172.17.0.10 -> 8.8.8.8
+45535 8.8.8.8 -> 172.17.0.10
+60808 172.17.0.10 -> 8.8.8.8
+60808 8.8.8.8 -> 172.17.0.10
+64325 172.17.0.10 -> 8.8.8.8
+64325 8.8.8.8 -> 172.17.0.10
+25543 172.17.0.10 -> 8.8.8.8
+25543 8.8.8.8 -> 172.17.0.10
+20736 172.17.0.10 -> 8.8.8.8
+20736 8.8.8.8 -> 172.17.0.10
+25911 172.17.0.10 -> 8.8.8.8
+25911 8.8.8.8 -> 172.17.0.10
+64358 172.17.0.10 -> 8.8.8.8
+64358 8.8.8.8 -> 172.17.0.10
+37698 172.17.0.10 -> 8.8.8.8
+37698 8.8.8.8 -> 172.17.0.10
+54706 172.17.0.10 -> 8.8.8.8
+54706 8.8.8.8 -> 172.17.0.10
+32142 172.17.0.10 -> 8.8.8.8
+32142 8.8.8.8 -> 172.17.0.10
+41808 172.17.0.10 -> 8.8.8.8
+41808 8.8.8.8 -> 172.17.0.10
+18886 172.17.0.10 -> 8.8.8.8
+18886 8.8.8.8 -> 172.17.0.10
+10624 172.17.0.10 -> 8.8.8.8
+10624 8.8.8.8 -> 172.17.0.10
+33139 172.17.0.10 -> 8.8.8.8
+33139 8.8.8.8 -> 172.17.0.10
+61415 172.17.0.10 -> 8.8.8.8
+61415 8.8.8.8 -> 172.17.0.10
+59258 172.17.0.10 -> 8.8.8.8
+59258 8.8.8.8 -> 172.17.0.10
+17700 172.17.0.10 -> 8.8.8.8
+17700 8.8.8.8 -> 172.17.0.10
diff --git a/src/test/test3.sh b/src/test/test3.sh
new file mode 100755
index 0000000..b9a0b64
--- /dev/null
+++ b/src/test/test3.sh
@@ -0,0 +1,22 @@
+#!/bin/sh -e
+# Copyright (c) 2018-2021, OARC, Inc.
+# All rights reserved.
+#
+# This file is part of dnsjit.
+#
+# dnsjit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+../dnsjit "$srcdir/../../examples/filter_rcode.lua" dns.pcap-dist 0 >test3.out
+../dnsjit "$srcdir/../../examples/filter_rcode.lua" dns.pcap-dist 1 >>test3.out
+diff "$srcdir/test3.gold" test3.out
diff --git a/src/test/test4.gold b/src/test/test4.gold
new file mode 100644
index 0000000..f56e464
--- /dev/null
+++ b/src/test/test4.gold
@@ -0,0 +1,82 @@
+59311
+59311
+35665
+35665
+5337
+5337
+22982
+22982
+18718
+18718
+22531
+22531
+58510
+58510
+45248
+45248
+49483
+49483
+31669
+31669
+25433
+25433
+63798
+63798
+8470
+8470
+60258
+60258
+44985
+44985
+45512
+45512
+22980
+22980
+1834
+1834
+25431
+25431
+48432
+48432
+47411
+47411
+12038
+12038
+11614
+11614
+59173
+59173
+45535
+45535
+60808
+60808
+64325
+64325
+25543
+25543
+20736
+20736
+25911
+25911
+64358
+64358
+37698
+37698
+54706
+54706
+32142
+32142
+41808
+41808
+18886
+18886
+10624
+10624
+33139
+33139
+61415
+61415
+59258
+59258
+17700
+17700
diff --git a/src/test/test4.sh b/src/test/test4.sh
new file mode 100755
index 0000000..b2d060d
--- /dev/null
+++ b/src/test/test4.sh
@@ -0,0 +1,21 @@
+#!/bin/sh -e
+# Copyright (c) 2018-2021, OARC, Inc.
+# All rights reserved.
+#
+# This file is part of dnsjit.
+#
+# dnsjit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+../dnsjit "$srcdir/../../examples/readme.lua" dns.pcap-dist >test4.out
+diff "$srcdir/test4.gold" test4.out
diff --git a/src/test/test5.sh b/src/test/test5.sh
new file mode 100755
index 0000000..f766e00
--- /dev/null
+++ b/src/test/test5.sh
@@ -0,0 +1,24 @@
+#!/bin/sh -e
+# Copyright (c) 2018-2021, OARC, Inc.
+# All rights reserved.
+#
+# This file is part of dnsjit.
+#
+# dnsjit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+../dnsjit "$srcdir/../../examples/test_throughput.lua" -vvvvv -t -s 1000000
+../dnsjit "$srcdir/../../examples/test_pcap_read.lua" -vvvvv dns.pcap-dist
+../dnsjit "$srcdir/../../examples/test_pcap_read.lua" -l -vvvvv dns.pcap-dist
+../dnsjit "$srcdir/../../examples/test_pcap_read.lua" -p -vvvvv dns.pcap-dist
+../dnsjit "$srcdir/../../examples/test_pcap_read.lua" -l -p -vvvvv dns.pcap-dist
diff --git a/src/test/test6.sh b/src/test/test6.sh
new file mode 100755
index 0000000..67f8e25
--- /dev/null
+++ b/src/test/test6.sh
@@ -0,0 +1,23 @@
+#!/bin/sh -e
+# Copyright (c) 2018-2021, OARC, Inc.
+# All rights reserved.
+#
+# This file is part of dnsjit.
+#
+# dnsjit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dnsjit 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 dnsjit. If not, see <http://www.gnu.org/licenses/>.
+
+../dnsjit "$srcdir/../../examples/dumpdns.lua" "$srcdir/dns.pcap" > test6-dns.out
+../dnsjit "$srcdir/../../examples/dumpdns2pcap.lua" "$srcdir/dns.pcap" test6-pcap.out
+../dnsjit "$srcdir/../../examples/dumpdns.lua" test6-pcap.out > test6-dns2.out
+diff test6-dns.out test6-dns2.out
diff --git a/src/test/test_base64url.lua b/src/test/test_base64url.lua
new file mode 100644
index 0000000..00aea6a
--- /dev/null
+++ b/src/test/test_base64url.lua
@@ -0,0 +1,24 @@
+base64url = require("dnsjit.lib.base64url")
+ffi = require("ffi")
+
+-- empty string works
+assert(base64url.decode(base64url.encode("")) == "")
+
+-- regular string data
+assert(base64url.encode("abcd") == "YWJjZA")
+assert(base64url.decode(base64url.encode("abcd")) == "abcd")
+
+-- invalid base64 data
+base64url.decode("+")
+
+-- check all symbols - arbitrary binary data
+c_array = ffi.new("uint8_t[?]", 256)
+bin_symbols = {}
+for i = 0, 255 do
+ bin_symbols[i + 1] = string.char(i)
+ c_array[i] = i
+end
+bin_str = table.concat(bin_symbols)
+
+assert(base64url.decode(base64url.encode(bin_str)) == bin_str)
+assert(base64url.encode(c_array, 256) == base64url.encode(bin_str))
diff --git a/src/test/test_ipsplit.lua b/src/test/test_ipsplit.lua
new file mode 100755
index 0000000..43dc8a0
--- /dev/null
+++ b/src/test/test_ipsplit.lua
@@ -0,0 +1,294 @@
+-- Test cases for dnsjit.filter.ipsplit
+-- Some checks that use ip_pkt() assume little-endian machine and will fail otherwise
+local ffi = require("ffi")
+local object = require("dnsjit.core.objects")
+local dns = require("dnsjit.core.object.dns").new()
+
+local function dns_msgid(obj)
+ local obj = ffi.cast("core_object_t*", obj)
+ assert(obj, "obj is nil")
+ local pl = obj:cast()
+ assert(obj:type() == "payload" and pl.len > 0, "obj doesn't have payload")
+ dns.obj_prev = obj
+ dns:parse_header()
+ return dns.id
+end
+
+local function ip_pkt(obj)
+ local obj = ffi.cast("core_object_t*", obj)
+ assert(obj, "obj is nil")
+ local pl = obj:cast()
+ assert(obj:type() == "payload" and pl.len > 0, "obj doesn't have payload")
+
+ local pkt = obj.obj_prev
+ while pkt ~= nil do
+ if pkt.obj_type == object.IP or pkt.obj_type == object.IP6 then
+ return pkt:cast()
+ end
+ pkt = pkt.obj_prev
+ end
+ assert(pkt, "obj has no ip/ip6 layer")
+end
+
+
+-----------------------------------------------------
+-- pellets.pcap: client detection
+--
+-- All packets have IPv6 layer and are expected to
+-- be sucessfully processed by ipsplit filter.
+-- Clients should be identified from source ip.
+-----------------------------------------------------
+local input = require("dnsjit.input.pcap").new()
+local layer = require("dnsjit.filter.layer").new()
+local copy = require("dnsjit.filter.copy").new()
+local ipsplit = require("dnsjit.filter.ipsplit").new()
+local out1 = require("dnsjit.core.channel").new(256)
+local out2 = require("dnsjit.core.channel").new(256)
+
+input:open_offline("pellets.pcap-dist")
+layer:producer(input)
+ipsplit:receiver(out1)
+ipsplit:receiver(out2)
+ipsplit:overwrite_dst()
+copy:obj_type(object.IP)
+copy:obj_type(object.IP6)
+copy:obj_type(object.PAYLOAD)
+copy:receiver(ipsplit)
+
+local prod, pctx = layer:produce()
+local recv, rctx = copy:receive()
+
+-- Process entire PCAP first, channels are large enough to bufer all packets
+while true do
+ local obj = prod(pctx)
+ if obj == nil then break end
+ recv(rctx, obj)
+end
+out1:close()
+out2:close()
+
+assert(ipsplit:discarded() == 0, "some valid packets have been discarded")
+assert(out1:size() == 47, "out1: some IPv6 packets lost by filter")
+assert(out2:size() == 44, "out2: some IPv6 packets lost by filter")
+
+-- out1: test individual packets
+local i = 0
+while true do
+ local obj = out1:get()
+ if obj == nil then break end
+ i = i + 1
+
+ if i == 1 then
+ assert(dns_msgid(obj) == 0x0a31, "pkt 1: client 1, pkt 1 -> out1")
+ assert(ip_pkt(obj):source() == "2001:0db8:beef:feed:0000:0000:0000:0003")
+ assert(ip_pkt(obj):destination() == "0100:0000:0000:0000:0000:0000:0000:0001")
+ end
+ if i == 2 then
+ assert(dns_msgid(obj) == 0xb3e8, "pkt 3: client 3, pkt 1 -> out1")
+ assert(ip_pkt(obj):source() == "2001:0db8:beef:feed:0000:0000:0000:0005")
+ assert(ip_pkt(obj):destination() == "0200:0000:0000:0000:0000:0000:0000:0001")
+ end
+ if i == 3 then
+ assert(dns_msgid(obj) == 0xb3e9, "pkt 4: client 3, pkt 2 -> out1")
+ assert(ip_pkt(obj):source() == "2001:0db8:beef:feed:0000:0000:0000:0005")
+ assert(ip_pkt(obj):destination() == "0200:0000:0000:0000:0000:0000:0000:0001")
+ end
+ if i == 13 then assert(dns_msgid(obj) == 0x4a05, "pkt 16: client 7, pkt 1 -> out1") end
+ if i == 14 then assert(dns_msgid(obj) == 0x4a06, "pkt 17: client 7, pkt 2 -> out1") end
+end
+
+-- out2: test individual packets
+local i = 0
+while true do
+ local obj = out2:get()
+ if obj == nil then break end
+ i = i + 1
+
+ if i == 1 then
+ assert(dns_msgid(obj) == 0xe6bd, "pkt 2: client 2, pkt 1 -> out2")
+ assert(ip_pkt(obj):source() == "2001:0db8:beef:feed:0000:0000:0000:0004")
+ assert(ip_pkt(obj):destination() == "0100:0000:0000:0000:0000:0000:0000:0001")
+ end
+ if i == 4 then assert(dns_msgid(obj) == 0xabfe, "pkt 18: client 8, pkt 1 -> out2") end
+ if i == 5 then assert(dns_msgid(obj) == 0xabff, "pkt 21: client 8, pkt 2 -> out2") end
+end
+
+
+-----------------------------------------------------
+-- pellets.pcap: weighted ipsplit:sequential()
+--
+-- Test sequential client assignment that respects
+-- weight.
+-----------------------------------------------------
+local input = require("dnsjit.input.pcap").new()
+local layer = require("dnsjit.filter.layer").new()
+local copy = require("dnsjit.filter.copy").new()
+local ipsplit = require("dnsjit.filter.ipsplit").new()
+local out1 = require("dnsjit.core.channel").new(256)
+local out2 = require("dnsjit.core.channel").new(256)
+
+input:open_offline("pellets.pcap-dist")
+layer:producer(input)
+ipsplit:receiver(out1, 3)
+ipsplit:receiver(out2, 2)
+copy:obj_type(object.IP)
+copy:obj_type(object.IP6)
+copy:obj_type(object.PAYLOAD)
+copy:receiver(ipsplit)
+
+local prod, pctx = layer:produce()
+local recv, rctx = copy:receive()
+
+-- Process entire PCAP first, channels are large enough to bufer all packets
+while true do
+ local obj = prod(pctx)
+ if obj == nil then break end
+ recv(rctx, obj)
+end
+out1:close()
+out2:close()
+
+assert(ipsplit:discarded() == 0, "some valid packets have been discarded")
+assert(out1:size() + out2:size() == 91, "some IPv6 packets lost by filter")
+
+-- out1: test individual packets
+local i = 0
+while true do
+ local obj = out1:get()
+ if obj == nil then break end
+ i = i + 1
+
+ if i == 1 then
+ assert(dns_msgid(obj) == 0x0a31, "pkt 1: client 1, pkt 1 -> out1")
+ assert(ip_pkt(obj):source() == "2001:0db8:beef:feed:0000:0000:0000:0003")
+ assert(ip_pkt(obj):destination() == "0000:0000:0000:0000:0000:0000:0000:0001")
+ end
+ if i == 2 then assert(dns_msgid(obj) == 0xe6bd, "pkt 2: client 2, pkt 1 -> out1") end
+ if i == 3 then assert(dns_msgid(obj) == 0xb3e8, "pkt 3: client 3, pkt 1 -> out1") end
+ if i == 4 then assert(dns_msgid(obj) == 0xb3e9, "pkt 4: client 3, pkt 2 -> out1") end
+ if i == 5 then assert(dns_msgid(obj) == 0x0a6f, "pkt 9: client 6, pkt 1 -> out1") end
+end
+
+-- out2: test individual packets
+local i = 0
+while true do
+ local obj = out2:get()
+ if obj == nil then break end
+ i = i + 1
+
+ if i == 1 then assert(dns_msgid(obj) == 0xaac6, "pkt 5: client 4, pkt 1 -> out2") end
+ if i == 2 then assert(dns_msgid(obj) == 0xaea6, "pkt 6: client 5, pkt 1 -> out2") end
+ if i == 3 then assert(dns_msgid(obj) == 0xaea7, "pkt 7: client 5, pkt 2 -> out2") end
+end
+
+-----------------------------------------------------
+-- pellets.pcap: weighted ipsplit:random()
+--
+-- Test sequential client assignment that respects
+-- weight.
+-----------------------------------------------------
+local input = require("dnsjit.input.pcap").new()
+local layer = require("dnsjit.filter.layer").new()
+local copy = require("dnsjit.filter.copy").new()
+local ipsplit = require("dnsjit.filter.ipsplit").new()
+local out1 = require("dnsjit.core.channel").new(256)
+local out2 = require("dnsjit.core.channel").new(256)
+
+input:open_offline("pellets.pcap-dist")
+layer:producer(input)
+ipsplit:receiver(out1, 85)
+ipsplit:receiver(out2, 15)
+ipsplit:random()
+copy:obj_type(object.IP)
+copy:obj_type(object.IP6)
+copy:obj_type(object.PAYLOAD)
+copy:receiver(ipsplit)
+
+local prod, pctx = layer:produce()
+local recv, rctx = copy:receive()
+
+-- Process entire PCAP first, channels are large enough to bufer all packets
+while true do
+ local obj = prod(pctx)
+ if obj == nil then break end
+ recv(rctx, obj)
+end
+out1:close()
+out2:close()
+
+assert(ipsplit:discarded() == 0, "some valid packets have been discarded")
+assert(out1:size() == 81, "out1: some IPv6 packets lost by filter")
+assert(out2:size() == 10, "out2: some IPv6 packets lost by filter")
+
+-- out1: test individual packets
+local i = 0
+while true do
+ local obj = out1:get()
+ if obj == nil then break end
+ i = i + 1
+ if i == 1 then assert(dns_msgid(obj) == 0xe6bd, "pkt 1: client 2, pkt 1 -> out1") end
+ if i == 2 then assert(dns_msgid(obj) == 0xb3e8, "pkt 2: client 3, pkt 1 -> out1") end
+ if i == 3 then assert(dns_msgid(obj) == 0xb3e9, "pkt 3: client 3, pkt 2 -> out1") end
+ if i == 5 then assert(dns_msgid(obj) == 0xaea6, "pkt 4: client 5, pkt 1 -> out1") end
+ if i == 29 then assert(dns_msgid(obj) == 0xaeaf, "pkt 29: client 5, pkt 10 -> out1") end
+end
+
+-- out2: test individual packets
+local i = 0
+while true do
+ local obj = out2:get()
+ if obj == nil then break end
+ i = i + 1
+ if i == 1 then assert(dns_msgid(obj) == 0x0a31, "pkt 1: client 1, pkt 1 -> out2") end
+ if i == 2 then assert(dns_msgid(obj) == 0x0a6f, "pkt 2: client 6, pkt 1 -> out2") end
+ if i == 10 then assert(dns_msgid(obj) == 0x0a70, "pkt 10: client 6, pkt 2 -> out2") end
+end
+
+-----------------------------------------------------
+-- Tests with dns.pcap
+--
+-- Packets use IPv4 and not all packets have IP layer
+-----------------------------------------------------
+local input = require("dnsjit.input.pcap").new()
+local layer = require("dnsjit.filter.layer").new()
+local copy = require("dnsjit.filter.copy").new()
+local ipsplit = require("dnsjit.filter.ipsplit").new()
+local out1 = require("dnsjit.core.channel").new(256)
+local out2 = require("dnsjit.core.channel").new(256)
+
+input:open_offline("dns.pcap-dist")
+layer:producer(input)
+ipsplit:receiver(out1)
+ipsplit:receiver(out2)
+ipsplit:overwrite_src()
+copy:obj_type(object.IP)
+copy:obj_type(object.IP6)
+copy:obj_type(object.PAYLOAD)
+copy:receiver(ipsplit)
+
+local prod, pctx = layer:produce()
+local recv, rctx = copy:receive()
+
+-- Process entire PCAP first, channels are large enough to bufer all packets
+while true do
+ local obj = prod(pctx)
+ if obj == nil then break end
+ recv(rctx, obj)
+end
+out1:close()
+out2:close()
+
+assert(out1:size() + out2:size() == 123, "some IPv4 packets lost by filter")
+
+-- out1: test individual packets
+local i = 0
+while true do
+ local obj = out1:get()
+ if obj == nil then break end
+ i = i + 1
+ if i == 1 then
+ assert(dns_msgid(obj) == 0xe7af)
+ assert(ip_pkt(obj):source() == "1.0.0.0")
+ assert(ip_pkt(obj):destination() == "8.8.8.8")
+ end
+end
diff --git a/src/test/test_trie.lua b/src/test/test_trie.lua
new file mode 100755
index 0000000..ddaccdf
--- /dev/null
+++ b/src/test/test_trie.lua
@@ -0,0 +1,134 @@
+-- Test cases for dnsjit.lib.trie
+
+local function key_compare(node1, node2)
+ local key1, keylen1 = node1:key()
+ local key2, keylen2 = node2:key()
+ if keylen1 ~= keylen2 then return false end
+ for i = 0, keylen1 - 1 do
+ if key1[i] ~= key2[i] then return false end
+ end
+ return true
+end
+
+-----------------------------------------------------
+-- binary-key trie with which stores numbers
+-----------------------------------------------------
+local input = require("dnsjit.input.pcap").new()
+local layer = require("dnsjit.filter.layer").new()
+local object = require("dnsjit.core.objects")
+local ip = require("dnsjit.lib.ip")
+local trie = require("dnsjit.lib.trie").new("uint32_t", true, 16)
+
+input:open_offline("pellets.pcap-dist")
+layer:producer(input)
+
+local prod, pctx = layer:produce()
+
+-- fill trie with values
+while true do
+ local obj = prod(pctx)
+ if obj == nil then break end
+ local pkt = obj:cast_to(object.IP6)
+
+ if pkt ~= nil then
+ -- count number of packets per IP
+ local node = trie:get_ins(pkt.src)
+ node:set(node:get() + 1)
+ end
+end
+
+assert(trie:weight() == 29)
+
+-- test iterator and check values
+local iter = trie:iter()
+local node = iter:node()
+local npkts = 0
+
+local i = 0
+while node ~= nil do
+ i = i + 1
+ local ip6str = ip.tostring(node:key())
+ local val = tonumber(node:get())
+ npkts = npkts + val
+
+ if i == 1 then assert(ip6str == "2001:0db8:beef:feed:0000:0000:0000:0003" and val == 1) end
+ if i == 1 then
+ local first = trie:get_first()
+ assert(key_compare(node, first))
+ assert(node:get() == first:get())
+ end
+ if i == 2 then assert(ip6str == "2001:0db8:beef:feed:0000:0000:0000:0004" and val == 1) end
+ if i == 2 then
+ local second = trie:get_try(node:key())
+ assert(key_compare(node, second))
+ assert(node:get() == second:get())
+ end
+ if i == 5 then assert(ip6str == "2001:0db8:beef:feed:0000:0000:0000:0008" and val == 10) end
+ if i == 29 then assert(ip6str == "2001:0db8:beef:feed:0000:0000:0000:0042" and val == 1) end
+
+ iter:next()
+ node = iter:node()
+end
+
+assert(npkts == 91)
+
+trie:clear()
+assert(trie:weight() == 0)
+
+
+-----------------------------------------------------
+-- string-key trie with which stores objects
+-----------------------------------------------------
+local input = require("dnsjit.input.pcap").new()
+local layer = require("dnsjit.filter.layer").new()
+local object = require("dnsjit.core.objects")
+local trie = require("dnsjit.lib.trie").new("core_object_t*")
+
+input:open_offline("dns.pcap-dist")
+layer:producer(input)
+
+local prod, pctx = layer:produce()
+
+-- fill trie with values
+while true do
+ local obj = prod(pctx)
+ if obj == nil then break end
+ local pkt = obj:cast_to(object.IP)
+
+ if pkt ~= nil then
+ local node = trie:get_ins(pkt:source())
+ local pkt2 = node:get()
+ if val ~= nil then
+ val:free()
+ end
+ node:set(pkt:copy():uncast())
+ end
+end
+
+assert(trie:weight() == 3)
+
+local node
+node = trie:get_first()
+assert(node:key() == "172.17.0.10")
+pkt = node:get():cast()
+assert(pkt:source() == "172.17.0.10")
+assert(pkt.id == 0x538b)
+pkt:free()
+
+node = trie:get_try("8.8.8.8")
+assert(node:key() == "8.8.8.8")
+pkt = node:get():cast()
+assert(pkt:source() == "8.8.8.8")
+pkt:free()
+
+node = trie:get_try("216.58.218.206")
+assert(node:key() == "216.58.218.206")
+pkt = node:get():cast()
+assert(pkt:source() == "216.58.218.206")
+pkt:free()
+
+node = trie:get_try("nonexistent")
+assert(node == nil)
+
+trie:clear()
+assert(trie:weight() == 0)