diff options
Diffstat (limited to 'src')
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 Binary files differnew file mode 100644 index 0000000..a0e585c --- /dev/null +++ b/src/test/dns.pcap diff --git a/src/test/pellets.pcap b/src/test/pellets.pcap Binary files differnew file mode 100644 index 0000000..ef39a7b --- /dev/null +++ b/src/test/pellets.pcap 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) |