summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2021-03-13 07:54:12 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2021-03-13 07:54:12 +0000
commit4754ed45b607e82450a5e31fea1da3ba61433b04 (patch)
tree3554490bdc003e6004f605abe41929cdf98b0651 /src
parentInitial commit. (diff)
downloaddnsjit-4754ed45b607e82450a5e31fea1da3ba61433b04.tar.xz
dnsjit-4754ed45b607e82450a5e31fea1da3ba61433b04.zip
Adding upstream version 1.1.0+debian.upstream/1.1.0+debian
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src')
-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
222 files changed, 26437 insertions, 0 deletions
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)