From 4754ed45b607e82450a5e31fea1da3ba61433b04 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Mar 2021 08:54:12 +0100 Subject: Adding upstream version 1.1.0+debian. Signed-off-by: Daniel Baumann --- .clang-format | 6 + .copr/Makefile | 23 + .github/FUNDING.yml | 1 + .gitignore | 100 +++ .lgtm.yml | 21 + .travis.yml | 29 + CHANGES | 106 +++ LICENSE | 674 +++++++++++++++++ Makefile.am | 31 + README.md | 153 ++++ autogen.sh | 20 + configure.ac | 131 ++++ examples/Makefile.am | 21 + examples/capture.lua | 47 ++ examples/count-pkts-per-ip.lua | 50 ++ examples/dumpdns-qr.lua | 85 +++ examples/dumpdns.lua | 46 ++ examples/dumpdns2pcap.lua | 38 + examples/filter_rcode.lua | 38 + examples/qr-multi-pcap-state.lua | 270 +++++++ examples/readme.lua | 20 + examples/replay.lua | 119 +++ examples/replay_multicli.lua | 192 +++++ examples/respdiff.lua | 160 ++++ examples/test_pcap_read.lua | 149 ++++ examples/test_throughput.lua | 539 ++++++++++++++ fmt.sh | 26 + m4/ax_append_flag.m4 | 50 ++ m4/ax_cflags_warn_all.m4 | 158 ++++ m4/ax_check_compile_flag.m4 | 53 ++ m4/ax_compiler_vendor.m4 | 117 +++ m4/ax_ext.m4 | 328 +++++++++ m4/ax_gcc_x86_avx_xgetbv.m4 | 79 ++ m4/ax_gcc_x86_cpuid.m4 | 89 +++ m4/ax_prepend_flag.m4 | 51 ++ m4/ax_pthread.m4 | 507 +++++++++++++ m4/ax_require_defined.m4 | 37 + m4/dl.sh | 27 + rpm/dnsjit.spec | 168 +++++ src/Makefile.am | 280 +++++++ src/core.lua | 45 ++ src/core/assert.h | 46 ++ src/core/channel.c | 169 +++++ src/core/channel.h | 41 ++ src/core/channel.hh | 49 ++ src/core/channel.lua | 142 ++++ src/core/compat.c | 25 + src/core/compat.h | 32 + src/core/compat.lua | 41 ++ src/core/log.c | 399 ++++++++++ src/core/log.h | 105 +++ src/core/log.hh | 44 ++ src/core/log.lua | 639 ++++++++++++++++ src/core/object.c | 135 ++++ src/core/object.h | 51 ++ src/core/object.hh | 28 + src/core/object.lua | 177 +++++ src/core/object/dns.c | 471 ++++++++++++ src/core/object/dns.h | 184 +++++ src/core/object/dns.hh | 119 +++ src/core/object/dns.lua | 797 ++++++++++++++++++++ src/core/object/dns/label.lua | 118 +++ src/core/object/dns/q.lua | 52 ++ src/core/object/dns/rr.lua | 85 +++ src/core/object/ether.c | 45 ++ src/core/object/ether.h | 38 + src/core/object/ether.hh | 33 + src/core/object/ether.lua | 78 ++ src/core/object/gre.c | 45 ++ src/core/object/gre.h | 38 + src/core/object/gre.hh | 37 + src/core/object/gre.lua | 87 +++ src/core/object/icmp.c | 45 ++ src/core/object/icmp.h | 38 + src/core/object/icmp.hh | 33 + src/core/object/icmp.lua | 78 ++ src/core/object/icmp6.c | 45 ++ src/core/object/icmp6.h | 38 + src/core/object/icmp6.hh | 33 + src/core/object/icmp6.lua | 78 ++ src/core/object/ieee802.c | 45 ++ src/core/object/ieee802.h | 38 + src/core/object/ieee802.hh | 35 + src/core/object/ieee802.lua | 84 +++ src/core/object/ip.c | 45 ++ src/core/object/ip.h | 38 + src/core/object/ip.hh | 40 + src/core/object/ip.lua | 122 ++++ src/core/object/ip6.c | 45 ++ src/core/object/ip6.h | 42 ++ src/core/object/ip6.hh | 42 ++ src/core/object/ip6.lua | 130 ++++ src/core/object/linuxsll.c | 45 ++ src/core/object/linuxsll.h | 38 + src/core/object/linuxsll.hh | 35 + src/core/object/linuxsll.lua | 84 +++ src/core/object/loop.c | 45 ++ src/core/object/loop.h | 38 + src/core/object/loop.hh | 31 + src/core/object/loop.lua | 72 ++ src/core/object/null.c | 45 ++ src/core/object/null.h | 38 + src/core/object/null.hh | 31 + src/core/object/null.lua | 72 ++ src/core/object/payload.c | 50 ++ src/core/object/payload.h | 38 + src/core/object/payload.hh | 32 + src/core/object/payload.lua | 83 +++ src/core/object/pcap.c | 50 ++ src/core/object/pcap.h | 38 + src/core/object/pcap.hh | 38 + src/core/object/pcap.lua | 98 +++ src/core/object/tcp.c | 45 ++ src/core/object/tcp.h | 39 + src/core/object/tcp.hh | 43 ++ src/core/object/tcp.lua | 110 +++ src/core/object/udp.c | 45 ++ src/core/object/udp.h | 38 + src/core/object/udp.hh | 34 + src/core/object/udp.lua | 86 +++ src/core/objects.lua | 61 ++ src/core/producer.c | 23 + src/core/producer.h | 28 + src/core/producer.hh | 23 + src/core/producer.lua | 28 + src/core/receiver.c | 23 + src/core/receiver.h | 28 + src/core/receiver.hh | 23 + src/core/receiver.lua | 28 + src/core/thread.c | 229 ++++++ src/core/thread.h | 31 + src/core/thread.hh | 56 ++ src/core/thread.lua | 141 ++++ src/core/timespec.h | 33 + src/core/timespec.hh | 24 + src/core/timespec.lua | 32 + src/dnsjit.1in | 144 ++++ src/dnsjit.c | 122 ++++ src/filter.lua | 31 + src/filter/copy.c | 192 +++++ src/filter/copy.h | 30 + src/filter/copy.hh | 40 + src/filter/copy.lua | 74 ++ src/filter/ipsplit.c | 270 +++++++ src/filter/ipsplit.h | 29 + src/filter/ipsplit.hh | 61 ++ src/filter/ipsplit.lua | 122 ++++ src/filter/layer.c | 689 ++++++++++++++++++ src/filter/layer.h | 44 ++ src/filter/layer.hh | 70 ++ src/filter/layer.lua | 93 +++ src/filter/split.c | 114 +++ src/filter/split.h | 29 + src/filter/split.hh | 50 ++ src/filter/split.lua | 80 ++ src/filter/timing.c | 557 ++++++++++++++ src/filter/timing.h | 30 + src/filter/timing.hh | 52 ++ src/filter/timing.lua | 123 ++++ src/gen-compat.lua | 34 + src/gen-errno.sh | 28 + src/gen-makefile.sh | 130 ++++ src/gen-manpage.lua | 125 ++++ src/globals.c | 60 ++ src/globals.h | 28 + src/input.lua | 29 + src/input/fpcap.c | 338 +++++++++ src/input/fpcap.h | 31 + src/input/fpcap.hh | 62 ++ src/input/fpcap.lua | 131 ++++ src/input/mmpcap.c | 335 +++++++++ src/input/mmpcap.h | 31 + src/input/mmpcap.hh | 60 ++ src/input/mmpcap.lua | 120 +++ src/input/pcap.c | 206 ++++++ src/input/pcap.h | 33 + src/input/pcap.hh | 56 ++ src/input/pcap.lua | 130 ++++ src/input/zero.c | 75 ++ src/input/zero.h | 32 + src/input/zero.hh | 37 + src/input/zero.lua | 75 ++ src/lib.lua | 29 + src/lib/base64url.c | 480 ++++++++++++ src/lib/base64url.h | 28 + src/lib/base64url.hh | 99 +++ src/lib/base64url.lua | 98 +++ src/lib/clock.c | 75 ++ src/lib/clock.h | 28 + src/lib/clock.hh | 29 + src/lib/clock.lua | 47 ++ src/lib/getopt.lua | 365 ++++++++++ src/lib/ip.lua | 125 ++++ src/lib/parseconf.lua | 181 +++++ src/lib/trie.c | 923 +++++++++++++++++++++++ src/lib/trie.h | 39 + src/lib/trie.hh | 118 +++ src/lib/trie.lua | 172 +++++ src/lib/trie/iter.lua | 93 +++ src/lib/trie/node.lua | 84 +++ src/output.lua | 34 + src/output/dnscli.c | 888 +++++++++++++++++++++++ src/output/dnscli.h | 38 + src/output/dnscli.hh | 72 ++ src/output/dnscli.lua | 187 +++++ src/output/dnssim.c | 502 +++++++++++++ src/output/dnssim.h | 31 + src/output/dnssim.hh | 123 ++++ src/output/dnssim.lua | 433 +++++++++++ src/output/dnssim/CHANGELOG.md | 16 + src/output/dnssim/common.c | 384 ++++++++++ src/output/dnssim/connection.c | 471 ++++++++++++ src/output/dnssim/https2.c | 592 +++++++++++++++ src/output/dnssim/internal.h | 343 +++++++++ src/output/dnssim/ll.h | 83 +++ src/output/dnssim/tcp.c | 356 +++++++++ src/output/dnssim/tls.c | 475 ++++++++++++ src/output/dnssim/udp.c | 156 ++++ src/output/null.c | 87 +++ src/output/null.h | 33 + src/output/null.hh | 37 + src/output/null.lua | 80 ++ src/output/pcap.c | 111 +++ src/output/pcap.h | 32 + src/output/pcap.hh | 41 ++ src/output/pcap.lua | 79 ++ src/output/respdiff.c | 298 ++++++++ src/output/respdiff.h | 31 + src/output/respdiff.hh | 36 + src/output/respdiff.lua | 94 +++ src/output/tcpcli.c | 381 ++++++++++ src/output/tcpcli.h | 32 + src/output/tcpcli.hh | 51 ++ src/output/tcpcli.lua | 131 ++++ src/output/tlscli.c | 345 +++++++++ src/output/tlscli.h | 34 + src/output/tlscli.hh | 52 ++ src/output/tlscli.lua | 103 +++ src/output/udpcli.c | 300 ++++++++ src/output/udpcli.h | 35 + src/output/udpcli.hh | 53 ++ src/output/udpcli.lua | 121 +++ src/test/Makefile.am | 47 ++ src/test/dns.pcap | Bin 0 -> 20228 bytes src/test/pellets.pcap | Bin 0 -> 9177 bytes src/test/test-base64url.sh | 20 + src/test/test-ipsplit.sh | 20 + src/test/test-trie.sh | 20 + src/test/test1.gold | 1493 ++++++++++++++++++++++++++++++++++++++ src/test/test1.sh | 21 + src/test/test2.gold | 42 ++ src/test/test2.sh | 21 + src/test/test3.gold | 82 +++ src/test/test3.sh | 22 + src/test/test4.gold | 82 +++ src/test/test4.sh | 21 + src/test/test5.sh | 24 + src/test/test6.sh | 23 + src/test/test_base64url.lua | 24 + src/test/test_ipsplit.lua | 294 ++++++++ src/test/test_trie.lua | 134 ++++ 261 files changed, 31196 insertions(+) create mode 100644 .clang-format create mode 100644 .copr/Makefile create mode 100644 .github/FUNDING.yml create mode 100644 .gitignore create mode 100644 .lgtm.yml create mode 100644 .travis.yml create mode 100644 CHANGES create mode 100644 LICENSE create mode 100644 Makefile.am create mode 100644 README.md create mode 100755 autogen.sh create mode 100644 configure.ac create mode 100644 examples/Makefile.am create mode 100755 examples/capture.lua create mode 100755 examples/count-pkts-per-ip.lua create mode 100755 examples/dumpdns-qr.lua create mode 100755 examples/dumpdns.lua create mode 100755 examples/dumpdns2pcap.lua create mode 100755 examples/filter_rcode.lua create mode 100755 examples/qr-multi-pcap-state.lua create mode 100755 examples/readme.lua create mode 100755 examples/replay.lua create mode 100755 examples/replay_multicli.lua create mode 100755 examples/respdiff.lua create mode 100755 examples/test_pcap_read.lua create mode 100755 examples/test_throughput.lua create mode 100755 fmt.sh create mode 100644 m4/ax_append_flag.m4 create mode 100644 m4/ax_cflags_warn_all.m4 create mode 100644 m4/ax_check_compile_flag.m4 create mode 100644 m4/ax_compiler_vendor.m4 create mode 100644 m4/ax_ext.m4 create mode 100644 m4/ax_gcc_x86_avx_xgetbv.m4 create mode 100644 m4/ax_gcc_x86_cpuid.m4 create mode 100644 m4/ax_prepend_flag.m4 create mode 100644 m4/ax_pthread.m4 create mode 100644 m4/ax_require_defined.m4 create mode 100755 m4/dl.sh create mode 100644 rpm/dnsjit.spec create mode 100644 src/Makefile.am create mode 100644 src/core.lua create mode 100644 src/core/assert.h create mode 100644 src/core/channel.c create mode 100644 src/core/channel.h create mode 100644 src/core/channel.hh create mode 100644 src/core/channel.lua create mode 100644 src/core/compat.c create mode 100644 src/core/compat.h create mode 100644 src/core/compat.lua create mode 100644 src/core/log.c create mode 100644 src/core/log.h create mode 100644 src/core/log.hh create mode 100644 src/core/log.lua create mode 100644 src/core/object.c create mode 100644 src/core/object.h create mode 100644 src/core/object.hh create mode 100644 src/core/object.lua create mode 100644 src/core/object/dns.c create mode 100644 src/core/object/dns.h create mode 100644 src/core/object/dns.hh create mode 100644 src/core/object/dns.lua create mode 100644 src/core/object/dns/label.lua create mode 100644 src/core/object/dns/q.lua create mode 100644 src/core/object/dns/rr.lua create mode 100644 src/core/object/ether.c create mode 100644 src/core/object/ether.h create mode 100644 src/core/object/ether.hh create mode 100644 src/core/object/ether.lua create mode 100644 src/core/object/gre.c create mode 100644 src/core/object/gre.h create mode 100644 src/core/object/gre.hh create mode 100644 src/core/object/gre.lua create mode 100644 src/core/object/icmp.c create mode 100644 src/core/object/icmp.h create mode 100644 src/core/object/icmp.hh create mode 100644 src/core/object/icmp.lua create mode 100644 src/core/object/icmp6.c create mode 100644 src/core/object/icmp6.h create mode 100644 src/core/object/icmp6.hh create mode 100644 src/core/object/icmp6.lua create mode 100644 src/core/object/ieee802.c create mode 100644 src/core/object/ieee802.h create mode 100644 src/core/object/ieee802.hh create mode 100644 src/core/object/ieee802.lua create mode 100644 src/core/object/ip.c create mode 100644 src/core/object/ip.h create mode 100644 src/core/object/ip.hh create mode 100644 src/core/object/ip.lua create mode 100644 src/core/object/ip6.c create mode 100644 src/core/object/ip6.h create mode 100644 src/core/object/ip6.hh create mode 100644 src/core/object/ip6.lua create mode 100644 src/core/object/linuxsll.c create mode 100644 src/core/object/linuxsll.h create mode 100644 src/core/object/linuxsll.hh create mode 100644 src/core/object/linuxsll.lua create mode 100644 src/core/object/loop.c create mode 100644 src/core/object/loop.h create mode 100644 src/core/object/loop.hh create mode 100644 src/core/object/loop.lua create mode 100644 src/core/object/null.c create mode 100644 src/core/object/null.h create mode 100644 src/core/object/null.hh create mode 100644 src/core/object/null.lua create mode 100644 src/core/object/payload.c create mode 100644 src/core/object/payload.h create mode 100644 src/core/object/payload.hh create mode 100644 src/core/object/payload.lua create mode 100644 src/core/object/pcap.c create mode 100644 src/core/object/pcap.h create mode 100644 src/core/object/pcap.hh create mode 100644 src/core/object/pcap.lua create mode 100644 src/core/object/tcp.c create mode 100644 src/core/object/tcp.h create mode 100644 src/core/object/tcp.hh create mode 100644 src/core/object/tcp.lua create mode 100644 src/core/object/udp.c create mode 100644 src/core/object/udp.h create mode 100644 src/core/object/udp.hh create mode 100644 src/core/object/udp.lua create mode 100644 src/core/objects.lua create mode 100644 src/core/producer.c create mode 100644 src/core/producer.h create mode 100644 src/core/producer.hh create mode 100644 src/core/producer.lua create mode 100644 src/core/receiver.c create mode 100644 src/core/receiver.h create mode 100644 src/core/receiver.hh create mode 100644 src/core/receiver.lua create mode 100644 src/core/thread.c create mode 100644 src/core/thread.h create mode 100644 src/core/thread.hh create mode 100644 src/core/thread.lua create mode 100644 src/core/timespec.h create mode 100644 src/core/timespec.hh create mode 100644 src/core/timespec.lua create mode 100644 src/dnsjit.1in create mode 100644 src/dnsjit.c create mode 100644 src/filter.lua create mode 100644 src/filter/copy.c create mode 100644 src/filter/copy.h create mode 100644 src/filter/copy.hh create mode 100644 src/filter/copy.lua create mode 100644 src/filter/ipsplit.c create mode 100644 src/filter/ipsplit.h create mode 100644 src/filter/ipsplit.hh create mode 100644 src/filter/ipsplit.lua create mode 100644 src/filter/layer.c create mode 100644 src/filter/layer.h create mode 100644 src/filter/layer.hh create mode 100644 src/filter/layer.lua create mode 100644 src/filter/split.c create mode 100644 src/filter/split.h create mode 100644 src/filter/split.hh create mode 100644 src/filter/split.lua create mode 100644 src/filter/timing.c create mode 100644 src/filter/timing.h create mode 100644 src/filter/timing.hh create mode 100644 src/filter/timing.lua create mode 100644 src/gen-compat.lua create mode 100755 src/gen-errno.sh create mode 100755 src/gen-makefile.sh create mode 100644 src/gen-manpage.lua create mode 100644 src/globals.c create mode 100644 src/globals.h create mode 100644 src/input.lua create mode 100644 src/input/fpcap.c create mode 100644 src/input/fpcap.h create mode 100644 src/input/fpcap.hh create mode 100644 src/input/fpcap.lua create mode 100644 src/input/mmpcap.c create mode 100644 src/input/mmpcap.h create mode 100644 src/input/mmpcap.hh create mode 100644 src/input/mmpcap.lua create mode 100644 src/input/pcap.c create mode 100644 src/input/pcap.h create mode 100644 src/input/pcap.hh create mode 100644 src/input/pcap.lua create mode 100644 src/input/zero.c create mode 100644 src/input/zero.h create mode 100644 src/input/zero.hh create mode 100644 src/input/zero.lua create mode 100644 src/lib.lua create mode 100644 src/lib/base64url.c create mode 100644 src/lib/base64url.h create mode 100644 src/lib/base64url.hh create mode 100644 src/lib/base64url.lua create mode 100644 src/lib/clock.c create mode 100644 src/lib/clock.h create mode 100644 src/lib/clock.hh create mode 100644 src/lib/clock.lua create mode 100644 src/lib/getopt.lua create mode 100644 src/lib/ip.lua create mode 100644 src/lib/parseconf.lua create mode 100644 src/lib/trie.c create mode 100644 src/lib/trie.h create mode 100644 src/lib/trie.hh create mode 100644 src/lib/trie.lua create mode 100644 src/lib/trie/iter.lua create mode 100644 src/lib/trie/node.lua create mode 100644 src/output.lua create mode 100644 src/output/dnscli.c create mode 100644 src/output/dnscli.h create mode 100644 src/output/dnscli.hh create mode 100644 src/output/dnscli.lua create mode 100644 src/output/dnssim.c create mode 100644 src/output/dnssim.h create mode 100644 src/output/dnssim.hh create mode 100644 src/output/dnssim.lua create mode 100644 src/output/dnssim/CHANGELOG.md create mode 100644 src/output/dnssim/common.c create mode 100644 src/output/dnssim/connection.c create mode 100644 src/output/dnssim/https2.c create mode 100644 src/output/dnssim/internal.h create mode 100644 src/output/dnssim/ll.h create mode 100644 src/output/dnssim/tcp.c create mode 100644 src/output/dnssim/tls.c create mode 100644 src/output/dnssim/udp.c create mode 100644 src/output/null.c create mode 100644 src/output/null.h create mode 100644 src/output/null.hh create mode 100644 src/output/null.lua create mode 100644 src/output/pcap.c create mode 100644 src/output/pcap.h create mode 100644 src/output/pcap.hh create mode 100644 src/output/pcap.lua create mode 100644 src/output/respdiff.c create mode 100644 src/output/respdiff.h create mode 100644 src/output/respdiff.hh create mode 100644 src/output/respdiff.lua create mode 100644 src/output/tcpcli.c create mode 100644 src/output/tcpcli.h create mode 100644 src/output/tcpcli.hh create mode 100644 src/output/tcpcli.lua create mode 100644 src/output/tlscli.c create mode 100644 src/output/tlscli.h create mode 100644 src/output/tlscli.hh create mode 100644 src/output/tlscli.lua create mode 100644 src/output/udpcli.c create mode 100644 src/output/udpcli.h create mode 100644 src/output/udpcli.hh create mode 100644 src/output/udpcli.lua create mode 100644 src/test/Makefile.am create mode 100644 src/test/dns.pcap create mode 100644 src/test/pellets.pcap create mode 100755 src/test/test-base64url.sh create mode 100755 src/test/test-ipsplit.sh create mode 100755 src/test/test-trie.sh create mode 100644 src/test/test1.gold create mode 100755 src/test/test1.sh create mode 100644 src/test/test2.gold create mode 100755 src/test/test2.sh create mode 100644 src/test/test3.gold create mode 100755 src/test/test3.sh create mode 100644 src/test/test4.gold create mode 100755 src/test/test4.sh create mode 100755 src/test/test5.sh create mode 100755 src/test/test6.sh create mode 100644 src/test/test_base64url.lua create mode 100755 src/test/test_ipsplit.lua create mode 100755 src/test/test_trie.lua diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..1bd4430 --- /dev/null +++ b/.clang-format @@ -0,0 +1,6 @@ +BasedOnStyle: webkit +IndentWidth: 4 +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: true +AlignOperands: true +SortIncludes: false diff --git a/.copr/Makefile b/.copr/Makefile new file mode 100644 index 0000000..29ed0bc --- /dev/null +++ b/.copr/Makefile @@ -0,0 +1,23 @@ +top=.. + +all: srpm + +prereq: $(top)/rpmbuild + rpm -q git rpm-build >/dev/null || dnf -y install git rpm-build + +update-dist-tools: $(top)/dist-tools + ( cd "$(top)/dist-tools" && git pull ) + +$(top)/dist-tools: + git clone https://github.com/jelu/dist-tools.git "$(top)/dist-tools" + +$(top)/rpmbuild: + mkdir -p "$(top)"/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} + +srpm: prereq update-dist-tools + test -f .gitmodules && git submodule update --init || true + echo "$(spec)" | grep -q "develop.spec" && auto_build_number=`date --utc +%s` message="Auto build `date --utc --iso-8601=seconds`" "$(top)/dist-tools/spec-new-changelog-entry" || true + overwrite=yes nosign=yes "$(top)/dist-tools/create-source-packages" rpm + cp ../*.orig.tar.gz "$(top)/rpmbuild/SOURCES/" + echo "$(spec)" | grep -q "develop.spec" && rpmbuild -bs --define "%_topdir $(top)/rpmbuild" --undefine=dist rpm/*.spec || rpmbuild -bs --define "%_topdir $(top)/rpmbuild" --undefine=dist "$(spec)" + cp "$(top)"/rpmbuild/SRPMS/*.src.rpm "$(outdir)" diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..38cc1c4 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: https://www.dns-oarc.net/donate diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..41aaabd --- /dev/null +++ b/.gitignore @@ -0,0 +1,100 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +# Automake +Makefile.in +aclocal.m4 +ar-lib +autom4te.cache +compile +config.guess +config.sub +configure +depcomp +install-sh +ltmain.sh +m4/libtool.m4 +m4/ltoptions.m4 +m4/ltsugar.m4 +m4/ltversion.m4 +m4/lt~obsolete.m4 +missing +config.h.in +config.h.in~ +test-driver + +# Configure +Makefile +config.log +config.status +libtool +.deps +src/config.h +src/stamp-h1 +build +.dirstamp + +# Project specific files +*.luao +*.luaho +src/dnsjit +src/dnsjit.1 +src/dnsjit.*.3in +src/dnsjit.*.3 +src/core/compat.hh +src/core/log_errstr.c +src/test/test-suite.log +src/test/test*.sh.log +src/test/test*.sh.trs +src/test/*.dist +src/test/*-dist diff --git a/.lgtm.yml b/.lgtm.yml new file mode 100644 index 0000000..c79c033 --- /dev/null +++ b/.lgtm.yml @@ -0,0 +1,21 @@ +extraction: + cpp: + prepare: + packages: + - build-essential + - automake + - autoconf + - libtool + - pkg-config + - libluajit-5.1-dev + - libpcap-dev + - luajit + - liblmdb-dev + - libck-dev + - libgnutls28-dev + - libuv1-dev + - libnghttp2-dev + configure: + command: + - ./autogen.sh + - ./configure diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d831324 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,29 @@ +dist: xenial +addons: + apt: + update: true + packages: + - libluajit-5.1-dev + - libpcap-dev + - luajit + - liblmdb-dev + - libck-dev + - libgnutls28-dev + - libuv1-dev + - libnghttp2-dev +language: c +compiler: + - clang + - gcc +install: ./autogen.sh +script: + - ./configure --enable-warn-all + - make dist + - tar zxvf *.tar.gz + - cd dnsjit-[0-9]* + - mkdir build + - cd build + - ../configure --enable-warn-all + - make + - make test + - cat src/test/test*.sh.log diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..448409a --- /dev/null +++ b/CHANGES @@ -0,0 +1,106 @@ +2021-02-03 Jerry Lundström + + Release 1.1.0 + + This releases adds a new module for handling Base64 URLs and new calls + for error handling and opening PCAPs using file descriptors, along with + a bug fix in `lib.getopt` and other changes. + + The `dnssim` module has also gotten its own version and changelog, this + is to prepare it for being moved outside of dnsjit's repository in the + future. + + New modules, calls, features: + - New `lib.base64url`: Utility library to convert data to base64url format + - `core.log`: New call `Log.errstr()`: Convert error number to its text representation + - `input.fpcap`: New call `Fpcap.openfp()`: Open a PCAP file for processing using a file descriptor, for example `io.stdin` + - `output.dnssim`: Support for DNS-over-HTTPS + + Bug fixes: + - `lib.getopt`: Fix bug where `-` and `--` could not be used as arguments to options + + Other changes: + - Fix typo in configure help text + - Add coverage + - `filter.ipsplit`: Extend PRNG modulus to 2^31, new implementation is the same as glibc's `rand()` + - `lib.ip`: Fix typo in documentation + - `output.dnssim`: + - This module now has it's own changelog + - Updated to v20210129 + - Depend on libhttp2 for dnssim DNS-over-HTTPS capabilities + - `output.pcap`: Log libpcap error when failing to open + - SUSE packages now depend on moonjit because of lack of LuaJIT support + + d001ccb m4 + 4b63bce output/dnssim: add changelog + 7355810 output/dnssim: add version checks + 95fa6a9 input pcap/fpcap, getopt + 99c3d9f test/test_ipsplit: update to use new PRNG + 3235b09 filter/ipsplit: extend PRNG modulus to 2^31 + 8ff81a0 fixup! input.fpcap: filename "-" reads from stdin + 63cf0a4 output/dnssim: fix regression in DoH GET + 367d0b8 input.pcap: document stdin feature of open_offline() + 8d94504 input.fpcap: filename "-" reads from stdin + 617058e getopt: accept singleton - also as option value + 7d7f17c output/dnssim: unify failed to bind error messages + bdf1517 output/dnssim: add IPv4 support + 15a21da Sonarcloud + ceeea1d SUSE + 1fc3c82 PR179 + 2f5d38f output/dnssim: allow user-set instance log name + b036c68 Info + 0af1ffb Travis, configure + 49bdc08 output/dnssim: implement udp(tcp_fallback) method + b4f9cf9 man: update gitlab.labs.nic.cz to gitlab.nic.cz + 45b977d output/dnssim: update man page + 4184090 output/dnssim: https2 - fix connection closure issues + 342f33e output/dnssim: https2 - omit closing connection inside callback + 67a76d5 output/dnssim: handle all states when closing connection + 41f04d8 output/dnssim: document importance of conn state enum ordering + 795ab6f output/dnssim: tls - fix handling of CONGESTED connections + 8792b32 output/dnssim: match QUESTION section of received responses + 3a88f5b Coverage + 4f611c8 dnssim + 6e35d5b Compile + 63faa44 README, format code, man-page + 925f85e lib: add missing man reference + 9239087 output/dnssim: fix man formatting + bd7bee5 fix lua log levels + 4083efd output/dnssim: fix doc typo + 24c22b8 lib/base64url: add lua bindings + 69be2a1 core/log: add errstr() utility function + 0c14d74 output/dnssim: improve https2() documentation and behaviour + f74e19c output/pcap: log errors when opening output PCAP + 6fe699a output/dnssim: cleanup and nitpicks + 96db8a9 output/https2: handle max_concurrent_streams similar to nghttp2 + 15ea609 output/dnssim: https2 - ensure uri authority is always set + fad3ed6 output/dnssim: https2 - fix some TODOs + 0bee6d8 output/dnssim: https2 - lua documentation + e83e010 output/dnssim: https2 - implement GET method + b553e0f output/dnssim: https2 - configure method + a431a0d contrib: add base64url functions + c753097 output/dnssim: https2 - set default concurrent stream limit + d49f275 output/dnssim: https2 - track number of open streams + 2f7217f output/dnssim: https2 - improve data send edge cases + c0abebc output/dnssim: https2 - return correct error code on send failure + 5b1f6c3 output/dnssim: conn - avoid assert when tearing down failed connections + 5c42266 output/dnssim: exit when file descriptors run out + 1ab2ab6 output/dnssim: https2 - additional asserts to detect invalid data + 4424eb3 output/dnssim: https2 - check response code + 303f2cd output/dnssim: https2 - improve QID mismatch debug msg + 86e3761 output/dnssim: https2 - bugfixes + 4a52f47 output/dnssim: https2 - use more consistent code style for pointers + c8d853e output/dnssim: conn - fix potential memory leak + 3e6038b output/dnssim: https2 - enable zero-ing out msgid + 712634c output/dnssim: https2 - properly match dnsmsg to query from http request + 5abe943 output/dnssim: https2 - free memory on teardown + 39a9e9e output/dnssim: https2 - initial implementation + 058aee2 output/dnssim: https2 - initialize and setup session + 85eb4a3 output/dnssim: https2 - add libnghttp2 dependency + 6712bd6 output/dnssim: https2 - add skeleton + +2020-07-23 Jerry Lundström + + Release 1.0.0 + + First release of dnsjit. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..6f4de9b --- /dev/null +++ b/Makefile.am @@ -0,0 +1,31 @@ +# Copyright (c) 2018-2021, OARC, Inc. +# All rights reserved. +# +# This file is part of dnsjit. +# +# dnsjit is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# dnsjit is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with dnsjit. If not, see . + +ACLOCAL_AMFLAGS = -I m4 + +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in \ + $(srcdir)/src/config.h.in~ \ + $(srcdir)/configure + +SUBDIRS = src examples + +dist_doc_DATA = CHANGES README.md LICENSE + +EXTRA_DIST = m4 + +test: check diff --git a/README.md b/README.md new file mode 100644 index 0000000..7c6238f --- /dev/null +++ b/README.md @@ -0,0 +1,153 @@ +# Engine for capturing, parsing and replaying DNS + +[![Build Status](https://travis-ci.com/DNS-OARC/dnsjit.svg?branch=develop)](https://travis-ci.com/DNS-OARC/dnsjit) [![Total alerts](https://img.shields.io/lgtm/alerts/g/DNS-OARC/dnsjit.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/DNS-OARC/dnsjit/alerts/) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=dns-oarc%3Adnsjit&metric=bugs)](https://sonarcloud.io/dashboard?id=dns-oarc%3Adnsjit) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=dns-oarc%3Adnsjit&metric=security_rating)](https://sonarcloud.io/dashboard?id=dns-oarc%3Adnsjit) + +**dnsjit** is a combination of parts taken from **dsc**, **dnscap**, **drool**, +and put together around Lua to create a script-based engine for easy +capturing, parsing and statistics gathering of DNS messages while also +providing facilities for replaying DNS traffic. + +One of the core functionality that **dnsjit** brings is to tie together C +and Lua modules through a receiver/producer interface. +This allows creation of custom chains of functionality to meet various +requirements. +Another core functionality is the ability to parse and process DNS messages +even if the messages are non-compliant with the DNS standards. + +The following Lua module categories exists: +- `dnsjit.core`: Core modules for handling things like logging, DNS messages and receiver/receive functionality. +- `dnsjit.lib`: Various Lua libraries or C library bindings. +- `dnsjit.input`: Input modules used to read DNS messages in various ways. +- `dnsjit.filter`: Filter modules to process or manipulate DNS messages. +- `dnsjit.output`: Output modules used to display DNS message, export to various formats or replay them against other targets. + +See each category's man-page for more information. + +More information may be found here: +- https://www.dns-oarc.net/tools/dnsjit + +Issues should be reported here: +- https://github.com/DNS-OARC/dnsjit/issues + +General support and discussion: +- Mattermost: https://chat.dns-oarc.net/community/channels/oarc-software + +## Packages + +https://dev.dns-oarc.net/packages + +Packages for Debian, Ubuntu, EPEL, SLE, openSUSE can be found in the +PRE-RELEASE channel. Some distributions are limited to certain +architectures because of LuaJIT. + +## Dependencies + +- [libluajit](http://luajit.org/) 2.0+ (or compatible alternatives) +- [libpcap](http://www.tcpdump.org/) +- [liblmdb](https://github.com/LMDB/lmdb) +- [libck](https://github.com/concurrencykit/ck) +- [libgnutls](https://www.gnutls.org/) +- [libuv](http://libuv.org/) +- [libnghttp2](https://www.nghttp2.org/) +- [luajit](http://luajit.org/) (for building) +- automake/autoconf/libtool/pkg-config (for building) + +Debian/Ubuntu: `apt-get install libluajit-5.1-dev libpcap-dev luajit liblmdb-dev libck-dev libgnutls28-dev libuv1-dev libnghttp2-dev` + +CentOS: `yum install luajit-devel libpcap-devel lmdb-devel ck-devel gnutls-devel libuv-devel libnghttp2-devel` + +FreeBSD: `pkg install luajit libpcap lmdb gnutls concurrencykit libuv libnghttp2` + +OpenBSD: `pkg_add luajit gnutls libuv nghttp2` + manual install of libpcap, liblmdb and libck + +On some version of SUSE Linux Enterprise moonjit is used as an compatible +alternative to luajit. + +## Build + +```shell +git clone https://github.com/DNS-OARC/dnsjit +cd dnsjit +sh autogen.sh +./configure +make +``` + +## Documentation + +Most documentation exists in man-pages and you do not have to install to +access them, after building you can do: + +```shell +man src/dnsjit.1 +man src/dnsjit.core.3 +man src/dnsjit.lib.3 +man src/dnsjit.input.3 +man src/dnsjit.filter.3 +man src/dnsjit.output.3 +``` + +## Usage + +Run a Lua script: + +```shell +dnsjit file.lua ... +``` + +Shebang-style: +```lua +#!/usr/bin/env dnsjit +... +``` + +## Example + +Following example display the DNS ID found in queries. + +```lua +require("dnsjit.core.objects") +local input = require("dnsjit.input.pcap").new() +local layer = require("dnsjit.filter.layer").new() +local dns = require("dnsjit.core.object.dns").new() + +input:open_offline(arg[2]) +layer:producer(input) +local producer, ctx = layer:produce() + +while true do + local object = producer(ctx) + if object == nil then break end + if object:type() == "payload" then + dns.obj_prev = object + if dns:parse_header() == 0 then + print(dns.id) + end + end +end +``` + +See more examples in the [examples](https://github.com/DNS-OARC/dnsjit/tree/develop/examples) directory. + +## Copyright + +Copyright (c) 2018-2021, OARC, Inc. + +All rights reserved. + +``` +This file is part of dnsjit. + +dnsjit is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +dnsjit is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with dnsjit. If not, see . +``` diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..18096c6 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,20 @@ +#!/bin/sh -e +# Copyright (c) 2018-2021, OARC, Inc. +# All rights reserved. +# +# This file is part of dnsjit. +# +# dnsjit is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# dnsjit is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with dnsjit. If not, see . + +autoreconf --force --install --no-recursive --include=m4 diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..24c6142 --- /dev/null +++ b/configure.ac @@ -0,0 +1,131 @@ +# Copyright (c) 2018-2021, OARC, Inc. +# All rights reserved. +# +# This file is part of dnsjit. +# +# dnsjit is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# dnsjit is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with dnsjit. If not, see . + +AC_PREREQ(2.64) +AC_INIT([dnsjit], [1.1.0], [admin@dns-oarc.net], [dnsjit], [https://github.com/DNS-OARC/dnsjit/issues]) +AC_DEFINE([PACKAGE_MAJOR_VERSION], [1], [Define to the major version of this package.]) +AC_DEFINE([PACKAGE_MINOR_VERSION], [1], [Define to the minor version of this package.]) +AC_DEFINE([PACKAGE_PATCH_VERSION], [0], [Define to the patch version of this package.]) +AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects]) +AC_CONFIG_SRCDIR([src/dnsjit.c]) +AC_CONFIG_HEADER([src/config.h]) +AC_CONFIG_MACRO_DIR([m4]) + +# Checks for programs. +AC_PROG_CC +AM_PROG_CC_C_O +AC_CANONICAL_HOST +m4_ifdef([AM_PROG_AR], [AM_PROG_AR]) +LT_INIT([disable-static]) + +# Check --enable-warn-all +AC_ARG_ENABLE([warn-all], [AS_HELP_STRING([--enable-warn-all], [Enable all compiler warnings])], [AX_CFLAGS_WARN_ALL()]) + +# Check --with-extra-cflags +AC_ARG_WITH([extra-cflags], [AS_HELP_STRING([--with-extra-cflags=CFLAGS], [Add extra CFLAGS])], [ + AC_MSG_NOTICE([appending extra CFLAGS... $withval]) + AS_VAR_APPEND(CFLAGS, [" $withval"]) +]) + +# Check --with-extra-ldflags +AC_ARG_WITH([extra-ldflags], [AS_HELP_STRING([--with-extra-ldflags=LDFLAGS], [Add extra LDFLAGS])], [ + AC_MSG_NOTICE([appending extra LDFLAGS... $withval]) + AS_VAR_APPEND(LDFLAGS, [" $withval"]) +]) + +# Check --enable-gcov +AC_ARG_ENABLE([gcov], [AS_HELP_STRING([--enable-gcov], [Enable coverage testing])], [ + coverage_cflags="--coverage" # ld fails with: -g -O0 -fno-inline -fno-inline-small-functions -fno-default-inline + AC_MSG_NOTICE([enabling coverage testing... $coverage_cflags]) + AS_VAR_APPEND(CFLAGS, [" $coverage_cflags"]) +]) +AM_CONDITIONAL([ENABLE_GCOV], [test "x$enable_gcov" != "xno"]) +AM_EXTRA_RECURSIVE_TARGETS([gcov]) + +# Checks for support. +AC_ARG_ENABLE([cpuext], [AS_HELP_STRING([--enable-cpuext], [check for and enable all available CPU extensions])], [ +case "${enableval}" in + yes) AX_EXT ;; + *) ;; +esac]) +AC_HEADER_TIME +AX_PTHREAD +AC_CHECK_LIB([pcap], [pcap_open_live], [], [AC_MSG_ERROR([libpcap not found])]) +AC_CHECK_HEADER([pcap/pcap.h], [], [AC_MSG_ERROR([libpcap header not found])]) +AC_CHECK_HEADERS([endian.h sys/endian.h machine/endian.h sys/time.h byteswap.h]) +AC_CHECK_FUNCS([pcap_create pcap_set_tstamp_precision pcap_set_immediate_mode]) +AC_CHECK_FUNCS([pcap_set_tstamp_type pcap_setdirection sched_yield]) +AC_CHECK_FUNCS([pcap_open_offline_with_tstamp_precision pcap_activate]) +AC_CHECK_TYPES([pcap_direction_t], [], [], [[#include ]]) +AC_CHECK_HEADERS([net/ethernet.h]) +AC_CHECK_HEADERS([net/ethertypes.h]) +AC_SEARCH_LIBS([clock_gettime],[rt]) +AC_CHECK_FUNCS([clock_nanosleep nanosleep]) +PKG_CHECK_MODULES([luajit], [luajit >= 2],, [AC_MSG_ERROR([luajit v2+ not found])]) +AC_PATH_PROGS([LUAJIT], [luajit luajit51]) +if test "x$ac_cv_path_LUAJIT" = "x"; then + AC_MSG_ERROR([luajit not found]) +fi +AC_CHECK_HEADERS([lmdb.h]) +AC_CHECK_LIB([lmdb], [mdb_env_create]) +PKG_CHECK_MODULES([libuv], [libuv]) +PKG_CHECK_MODULES([libnghttp2], [libnghttp2]) +PKG_CHECK_MODULES([ck], [ck >= 0], [ + AS_VAR_APPEND([CFLAGS], [" $ck_CFLAGS"]) + AS_VAR_APPEND([LIBS], [" $ck_LIBS"]) +], [ + AC_CHECK_HEADERS([ck_ring.h ck_pr.h],, [AC_MSG_ERROR([libck headers not found])]) + AC_CHECK_LIB([ck], [ck_array_init],, [AC_MSG_ERROR([libck not found])]) +]) +AC_CHECK_LIB([gnutls], [gnutls_init],, [AC_MSG_ERROR([libgnutls not found])]) + +# Checks for sizes +AC_CHECK_SIZEOF([void*]) +AC_CHECK_SIZEOF([pthread_t],,[#include ]) +AC_CHECK_SIZEOF([pthread_mutex_t],,[#include ]) +AC_CHECK_SIZEOF([pthread_cond_t],,[#include ]) +AC_CHECK_SIZEOF([struct sockaddr_storage],,[#include +#include ]) +AC_CHECK_SIZEOF([ck_ring_t],,[#if defined(__GNUC__) || defined(__SUNPRO_C) +#include "gcc/ck_cc.h" +#ifdef CK_CC_RESTRICT +#undef CK_CC_RESTRICT +#define CK_CC_RESTRICT __restrict__ +#endif +#endif +#include ]) +AC_CHECK_SIZEOF([ck_ring_buffer_t],,[#if defined(__GNUC__) || defined(__SUNPRO_C) +#include "gcc/ck_cc.h" +#ifdef CK_CC_RESTRICT +#undef CK_CC_RESTRICT +#define CK_CC_RESTRICT __restrict__ +#endif +#endif +#include ]) +AC_CHECK_SIZEOF([gnutls_session_t],,[#include ]) +AC_CHECK_SIZEOF([gnutls_certificate_credentials_t],,[#include ]) +AC_CHECK_SIZEOF([struct pollfd],,[#include ]) + +# Output Makefiles +AC_CONFIG_FILES([ + Makefile + src/Makefile + src/test/Makefile + examples/Makefile +]) +AC_OUTPUT diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100644 index 0000000..96361b9 --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,21 @@ +# Copyright (c) 2018-2021, OARC, Inc. +# All rights reserved. +# +# This file is part of dnsjit. +# +# dnsjit is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# dnsjit is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with dnsjit. If not, see . + +dist_doc_DATA = capture.lua dumpdns2pcap.lua dumpdns.lua dumpdns-qr.lua \ + filter_rcode.lua qr-multi-pcap-state.lua readme.lua replay.lua \ + replay_multicli.lua respdiff.lua test_pcap_read.lua test_throughput.lua diff --git a/examples/capture.lua b/examples/capture.lua new file mode 100755 index 0000000..a4b9e75 --- /dev/null +++ b/examples/capture.lua @@ -0,0 +1,47 @@ +#!/usr/bin/env dnsjit +local interface = arg[2] + +if interface == nil then + print("usage: "..arg[1].." ") + return +end + +local object = require("dnsjit.core.objects") +local input = require("dnsjit.input.pcap").new() +local layer = require("dnsjit.filter.layer").new() +local dns = require("dnsjit.core.object.dns").new() + +input:create(interface) +input:activate() +layer:producer(input) +local producer, ctx = layer:produce() + +while true do + local obj = producer(ctx) + if obj == nil then break end + local pl = obj:cast() + if obj:type() == "payload" and pl.len > 0 then + local transport = obj.obj_prev + while transport ~= nil do + if transport.obj_type == object.IP or transport.obj_type == object.IP6 then + break + end + transport = transport.obj_prev + end + local protocol = obj.obj_prev + while protocol ~= nil do + if protocol.obj_type == object.UDP or protocol.obj_type == object.TCP then + break + end + protocol = protocol.obj_prev + end + + dns.obj_prev = obj + if transport ~= nil and protocol ~= nil then + transport = transport:cast() + protocol = protocol:cast() + print(protocol:type().." "..transport:source()..":"..tonumber(protocol.sport).." -> "..transport:destination()..":"..tonumber(protocol.dport)) + dns:print() + end + end +end diff --git a/examples/count-pkts-per-ip.lua b/examples/count-pkts-per-ip.lua new file mode 100755 index 0000000..9a6908e --- /dev/null +++ b/examples/count-pkts-per-ip.lua @@ -0,0 +1,50 @@ +#!/usr/bin/env dnsjit +-- count-pkts-per-ip.lua: count number of packets received from each IP/IPv6 address + +local input = require("dnsjit.input.pcap").new() +local layer = require("dnsjit.filter.layer").new() +local object = require("dnsjit.core.objects") +local ip = require("dnsjit.lib.ip") +local trie = require("dnsjit.lib.trie").new("uint64_t", true) +local getopt = require("dnsjit.lib.getopt").new({}) + +local pcap = unpack(getopt:parse()) +if pcap == nil then + print("usage: "..arg[1].." ") +end + +-- Set up input +input:open_offline(pcap) +layer:producer(input) +local produce, pctx = layer:produce() + +-- Read input and count packets +while true do + local obj = produce(pctx) + if obj == nil then break end + local pkt = obj:cast_to(object.IP) or obj:cast_to(object.IP6) + + if pkt ~= nil then + local iplen = 4 + if pkt:type() == "ip6" then + iplen = 16 + end + + local node = trie:get_ins(pkt.src, iplen) + node:set(node:get() + 1) + end +end + +-- Print statistics +local iter = trie:iter() +local node = iter:node() + +while node ~= nil do + local npkts = tonumber(node:get()) + local key = node:key() + local ipstr = ip.tostring(key, true) + + print(ipstr.." sent "..npkts.." packets") + iter:next() + node = iter:node() +end diff --git a/examples/dumpdns-qr.lua b/examples/dumpdns-qr.lua new file mode 100755 index 0000000..d726e96 --- /dev/null +++ b/examples/dumpdns-qr.lua @@ -0,0 +1,85 @@ +#!/usr/bin/env dnsjit +local pcap = arg[2] + +if pcap == nil then + print("usage: "..arg[1].." ") + return +end + +local object = require("dnsjit.core.objects") +local input = require("dnsjit.input.pcap").new() +local layer = require("dnsjit.filter.layer").new() +local dns = require("dnsjit.core.object.dns").new() +local label = require("dnsjit.core.object.dns.label") + +local ffi = require("ffi") +local labels = require("dnsjit.core.object.dns.label").new(16) +local q = require("dnsjit.core.object.dns.q").new() + +input:open_offline(pcap) +layer:producer(input) +local producer, ctx = layer:produce() + +local queries = {} +local responses = {} + +while true do + local obj = producer(ctx) + if obj == nil then break end + local pl = obj:cast() + if obj:type() == "payload" and pl.len > 0 then + local transport = obj.obj_prev + while transport ~= nil do + if transport.obj_type == object.IP or transport.obj_type == object.IP6 then + break + end + transport = transport.obj_prev + end + local protocol = obj.obj_prev + while protocol ~= nil do + if protocol.obj_type == object.UDP or protocol.obj_type == object.TCP then + break + end + protocol = protocol.obj_prev + end + + dns.obj_prev = obj + if transport ~= nil and protocol ~= nil and dns:parse_header() == 0 then + transport = transport:cast() + protocol = protocol:cast() + + if dns.qr == 1 then + table.insert(responses, { + src = transport:source(), + sport = protocol.sport, + dst = transport:destination(), + dport = protocol.dport, + id = dns.id, + rcode = dns.rcode_tostring(dns.rcode), + }) + else + if dns.qdcount > 0 and dns:parse_q(q, labels, 16) == 0 then + table.insert(queries, { + src = transport:source(), + sport = protocol.sport, + dst = transport:destination(), + dport = protocol.dport, + id = dns.id, + qname = label.tooffstr(dns, labels, 16), + qtype = dns.type_tostring(q.type) + }) + end + end + end + end +end + +print("src", "dst", "id", "rcode", "qname", "qtype") +local q, r +for _, q in pairs(queries) do + for _, r in pairs(responses) do + if q.id == r.id and q.sport == r.dport and q.dport == r.sport and q.src == r.dst and q.dst == r.src then + print(q.src, q.dst, q.id, r.rcode, q.qname, q.qtype) + end + end +end diff --git a/examples/dumpdns.lua b/examples/dumpdns.lua new file mode 100755 index 0000000..7c6fb8c --- /dev/null +++ b/examples/dumpdns.lua @@ -0,0 +1,46 @@ +#!/usr/bin/env dnsjit +local pcap = arg[2] + +if pcap == nil then + print("usage: "..arg[1].." ") + return +end + +local object = require("dnsjit.core.objects") +local input = require("dnsjit.input.pcap").new() +local layer = require("dnsjit.filter.layer").new() +local dns = require("dnsjit.core.object.dns").new() + +input:open_offline(pcap) +layer:producer(input) +local producer, ctx = layer:produce() + +while true do + local obj = producer(ctx) + if obj == nil then break end + local pl = obj:cast() + if obj:type() == "payload" and pl.len > 0 then + local transport = obj.obj_prev + while transport ~= nil do + if transport.obj_type == object.IP or transport.obj_type == object.IP6 then + break + end + transport = transport.obj_prev + end + local protocol = obj.obj_prev + while protocol ~= nil do + if protocol.obj_type == object.UDP or protocol.obj_type == object.TCP then + break + end + protocol = protocol.obj_prev + end + + dns.obj_prev = obj + if transport ~= nil and protocol ~= nil then + transport = transport:cast() + protocol = protocol:cast() + print(protocol:type().." "..transport:source()..":"..tonumber(protocol.sport).." -> "..transport:destination()..":"..tonumber(protocol.dport)) + dns:print() + end + end +end diff --git a/examples/dumpdns2pcap.lua b/examples/dumpdns2pcap.lua new file mode 100755 index 0000000..08656cc --- /dev/null +++ b/examples/dumpdns2pcap.lua @@ -0,0 +1,38 @@ +#!/usr/bin/env dnsjit +local pcap_in = arg[2] +local pcap_out = arg[3] + +if pcap_in == nil or pcap_out == nil then + print("usage: "..arg[1].." ") + return +end + +local object = require("dnsjit.core.objects") +local input = require("dnsjit.input.pcap").new() +local layer = require("dnsjit.filter.layer").new() +local dns = require("dnsjit.core.object.dns").new() +local output = require("dnsjit.output.pcap").new() + +input:open_offline(pcap_in) +layer:producer(input) +local producer, ctx = layer:produce() + +output:open(pcap_out, input:linktype(), input:snaplen()) +local receiver, rctx = output:receive() + +local n = 0 +while true do + local obj = producer(ctx) + if obj == nil then break end + local pl = obj:cast() + if obj:type() == "payload" and pl.len > 0 then + dns.obj_prev = obj + if dns:parse_header() == 0 then + receiver(rctx, obj) + n = n + 1 + end + end +end + +output:close() +print(n, "DNS packets dumped") diff --git a/examples/filter_rcode.lua b/examples/filter_rcode.lua new file mode 100755 index 0000000..c3f0254 --- /dev/null +++ b/examples/filter_rcode.lua @@ -0,0 +1,38 @@ +#!/usr/bin/env dnsjit +local pcap = arg[2] +local rcode = tonumber(arg[3]) + +if pcap == nil or rcode == nil then + print("usage: "..arg[1].." ") + return +end + +local object = require("dnsjit.core.objects") +local input = require("dnsjit.input.pcap").new() +local layer = require("dnsjit.filter.layer").new() +local dns = require("dnsjit.core.object.dns").new() + +input:open_offline(pcap) +layer:producer(input) +local producer, ctx = layer:produce() + +while true do + local obj = producer(ctx) + if obj == nil then break end + local pl = obj:cast() + if obj:type() == "payload" and pl.len > 0 then + local transport = obj.obj_prev + while transport ~= nil do + if transport.obj_type == object.IP or transport.obj_type == object.IP6 then + break + end + transport = transport.obj_prev + end + + dns.obj_prev = obj + if transport and dns and dns:parse_header() == 0 and dns.have_rcode == 1 and dns.rcode == rcode then + transport = transport:cast() + print(dns.id, transport:source().." -> "..transport:destination()) + end + end +end diff --git a/examples/qr-multi-pcap-state.lua b/examples/qr-multi-pcap-state.lua new file mode 100755 index 0000000..cf2cf0d --- /dev/null +++ b/examples/qr-multi-pcap-state.lua @@ -0,0 +1,270 @@ +#!/usr/bin/env dnsjit +local clock = require("dnsjit.lib.clock") +local log = require("dnsjit.core.log") +local getopt = require("dnsjit.lib.getopt").new({ + { "v", "verbose", 0, "Enable and increase verbosity for each time given", "?+" }, + { "r", "read-state", "", "File to read state from before processing", "?" }, + { "w", "write-state", "", "File to write state to after processing", "?" }, +}) +local pcap = unpack(getopt:parse()) +if getopt:val("help") then + getopt:usage() + return +end +local v = getopt:val("v") +if v > 0 then + log.enable("warning") +end +if v > 1 then + log.enable("notice") +end +if v > 2 then + log.enable("info") +end +if v > 3 then + log.enable("debug") +end + +if pcap == nil then + print("usage: "..arg[1].." ") + return +end + +local object = require("dnsjit.core.objects") +local input = require("dnsjit.input.mmpcap").new() +local layer = require("dnsjit.filter.layer").new() +local dns = require("dnsjit.core.object.dns").new() +local label = require("dnsjit.core.object.dns.label") +local ffi = require("ffi") +local labels = require("dnsjit.core.object.dns.label").new(16) +local q = require("dnsjit.core.object.dns.q").new() +local bit = require("bit") + +hash_u32 = ffi.new("uint32_t[2]") +hash_u32p = ffi.cast("uint32_t*", hash_u32) +function hashkey(dns, transport, protocol) + if transport.obj_type == object.IP then + ffi.copy(hash_u32p, transport.src, 4) + hash_u32[1] = hash_u32[0] + ffi.copy(hash_u32p, transport.dst, 4) + hash_u32[1] = bit.bxor(hash_u32[0], hash_u32[1]) + else + local srcp = ffi.cast("uint8_t*", transport.src) + ffi.copy(hash_u32p, srcp, 4) + hash_u32[1] = hash_u32[0] + ffi.copy(hash_u32p, srcp+4, 4) + hash_u32[1] = bit.bxor(hash_u32[0], hash_u32[1]) + srcp = ffi.cast("uint8_t*", transport.dst) + ffi.copy(hash_u32p, srcp, 4) + hash_u32[1] = bit.bxor(hash_u32[0], hash_u32[1]) + ffi.copy(hash_u32p, srcp+4, 4) + hash_u32[1] = bit.bxor(hash_u32[0], hash_u32[1]) + end + if dns.qr == 1 then + hash_u32[0] = protocol.dport + bit.lshift(protocol.sport, 16) + else + hash_u32[0] = protocol.sport + bit.lshift(protocol.dport, 16) + end + hash_u32[1] = bit.bxor(hash_u32[0], hash_u32[1]) + hash_u32[0] = dns.id + bit.lshift(dns.id, 16) + return bit.bxor(hash_u32[0], hash_u32[1]) +end + +function dump_inflight(hkey, obj) + return string.format("return %d, { qsec = %d, qnsec = %d, rsec = %d, rnsec = %d, src = %q, sport = %d, dst = %q, dport = %d, id = %d, qname = %q, qtype = %q, rcode = %d }", + hkey, + tonumber(obj.qsec), tonumber(obj.qnsec), + tonumber(obj.rsec), tonumber(obj.rnsec), + obj.src, obj.sport, + obj.dst, obj.dport, + obj.id, + obj.qname, obj.qtype, + obj.rcode + ) +end + +function qrout(res) + local tsq = tonumber(res.qsec) + (tonumber(res.qnsec)/1000000000) + local tsr = tonumber(res.rsec) + (tonumber(res.rnsec)/1000000000) + print(tsq, tsr, math.floor(((tsr-tsq)*1000000)+0.5), + res.src, res.dst, res.id, res.rcode, res.qname, res.qtype) +end + +input:open(pcap) +layer:producer(input) +local producer, ctx = layer:produce() + +local inflight = {} + +if getopt:val("read-state") > "" then + local f, _ = io.open(getopt:val("read-state")) + local inflights = 0 + if f ~= nil then + for chunk in f:lines() do + local hkey, query = assert(loadstring(chunk))() + if hkey and query then + if not inflight[hkey] then + inflight[hkey] = { + queries = {}, + size = 0, + } + end + + table.insert(inflight[hkey].queries, query) + inflight[hkey].size = inflight[hkey].size + 1 + inflights = inflights + 1 + end + end + f:close() + print(string.format("== read %d inflight states from %q", inflights, getopt:val("read-state"))) + end +end + +local stat = { + packets = 0, + queries = 0, + responses = 0, + dropped = 0, +} +local start_sec, start_nsec = clock:monotonic() +while true do + local obj = producer(ctx) + if obj == nil then break end + stat.packets = stat.packets + 1 + local pl = obj:cast() + if obj:type() == "payload" and pl.len > 0 then + local protocol = obj.obj_prev + while protocol ~= nil do + if protocol.obj_type == object.UDP or protocol.obj_type == object.TCP then + break + end + protocol = protocol.obj_prev + end + local transport = protocol.obj_prev + while transport ~= nil do + if transport.obj_type == object.IP or transport.obj_type == object.IP6 then + break + end + transport = transport.obj_prev + end + local pcap = transport.obj_prev + while pcap ~= nil do + if pcap.obj_type == object.PCAP then + break + end + pcap = pcap.obj_prev + end + + dns.obj_prev = obj + if pcap ~= nil and transport ~= nil and protocol ~= nil and dns:parse_header() == 0 then + transport = transport:cast() + protocol = protocol:cast() + pcap = pcap:cast() + + local hkey = hashkey(dns, transport, protocol) + + if dns.qr == 1 then + stat.responses = stat.responses + 1 + if inflight[hkey] then + for k, n in pairs(inflight[hkey].queries) do + if n.id == dns.id + and n.sport == protocol.dport + and n.dport == protocol.sport + and n.src == transport:destination() + and n.dst == transport:source() + then + n.rsec = pcap.ts.sec + n.rnsec = pcap.ts.nsec + n.rcode = dns.rcode + qrout(n) + inflight[hkey].queries[k] = nil + inflight[hkey].size = inflight[hkey].size - 1 + if inflight[hkey].size < 1 then + inflight[hkey] = nil + end + break + end + end + else + print("== dropped", + tonumber(pcap.ts.sec) + (tonumber(pcap.ts.nsec) / 1000000000), + transport:source(), + transport:destination(), + dns.id, + label.tooffstr(dns, labels, 16), + dns.type_tostring(q.type) + ) + stat.dropped = stat.dropped + 1 + end + else + stat.queries = stat.queries + 1 + if dns.qdcount > 0 and dns:parse_q(q, labels, 16) == 0 then + if not inflight[hkey] then + inflight[hkey] = { + queries = {}, + size = 0, + } + end + + table.insert(inflight[hkey].queries, { + qsec = pcap.ts.sec, + qnsec = pcap.ts.nsec, + rsec = -1, + rnsec = -1, + src = transport:source(), + sport = protocol.sport, + dst = transport:destination(), + dport = protocol.dport, + id = dns.id, + qname = label.tooffstr(dns, labels, 16), + qtype = dns.type_tostring(q.type), + rcode = -1, + }) + inflight[hkey].size = inflight[hkey].size + 1 + end + end + end + end +end +local end_sec, end_nsec = clock:monotonic() + +local runtime = 0 +if end_sec > start_sec then + runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000) +elseif end_sec == start_sec and end_nsec > start_nsec then + runtime = (end_nsec - start_nsec) / 1000000000 +end + +print("== runtime", runtime) +print("== packets", stat.packets, stat.packets/runtime) +print("== queries", stat.queries, stat.queries/runtime) +print("== responses", stat.responses, stat.responses/runtime) +print("== dropped", stat.dropped, stat.dropped/runtime) + +if getopt:val("write-state") > "" then + local f, _ = io.open(getopt:val("write-state"), "w+") + local inflights = 0 + if f ~= nil then + for hkey, unanswered in pairs(inflight) do + for _, query in pairs(unanswered.queries) do + f:write(dump_inflight(hkey, query), "\n") + inflights = inflights + 1 + end + end + f:close() + print(string.format("== wrote %d inflight states to %q", inflights, getopt:val("write-state"))) + end +else + inflights = 0 + for hkey, unanswered in pairs(inflight) do + inflights = inflights + unanswered.size + end + if inflights > 0 then + print("== inflight queries (tsq, src, dst, id, qname, qtype)") + for hkey, unanswered in pairs(inflight) do + for _, query in pairs(unanswered.queries) do + print(tonumber(query.qsec) + (tonumber(query.qnsec)/1000000000), query.src, query.dst, query.id, query.qname, query.qtype) + end + end + end +end diff --git a/examples/readme.lua b/examples/readme.lua new file mode 100755 index 0000000..dc6de89 --- /dev/null +++ b/examples/readme.lua @@ -0,0 +1,20 @@ +#!/usr/bin/env dnsjit +require("dnsjit.core.objects") +local input = require("dnsjit.input.pcap").new() +local layer = require("dnsjit.filter.layer").new() +local dns = require("dnsjit.core.object.dns").new() + +input:open_offline(arg[2]) +layer:producer(input) +local producer, ctx = layer:produce() + +while true do + local object = producer(ctx) + if object == nil then break end + if object:type() == "payload" then + dns.obj_prev = object + if dns:parse_header() == 0 then + print(dns.id) + end + end +end diff --git a/examples/replay.lua b/examples/replay.lua new file mode 100755 index 0000000..5281133 --- /dev/null +++ b/examples/replay.lua @@ -0,0 +1,119 @@ +#!/usr/bin/env dnsjit +local clock = require("dnsjit.lib.clock") +local log = require("dnsjit.core.log") +local getopt = require("dnsjit.lib.getopt").new({ + { "v", "verbose", 0, "Enable and increase verbosity for each time given", "?+" }, + { "R", "responses", false, "Wait for responses to the queries and print both", "?" }, + { "t", "tcp", false, "Use TCP instead of UDP", "?"}, + { "T", "tls", false, "Use TLS instead of UDP/TCP", "?"}, +}) +local pcap, host, port = unpack(getopt:parse()) +if getopt:val("help") then + getopt:usage() + return +end +local v = getopt:val("v") +if v > 0 then + log.enable("warning") +end +if v > 1 then + log.enable("notice") +end +if v > 2 then + log.enable("info") +end +if v > 3 then + log.enable("debug") +end + +if pcap == nil or host == nil or port == nil then + print("usage: "..arg[1].." ") + return +end + +local ffi = require("ffi") + +require("dnsjit.core.objects") +local input = require("dnsjit.input.mmpcap").new() +local layer = require("dnsjit.filter.layer").new() + +input:open(pcap) +layer:producer(input) + +local query = require("dnsjit.core.object.dns").new() +local response = require("dnsjit.core.object.dns").new() + +local dnscli = require("dnsjit.output.dnscli") +local output +if getopt:val("t") then + output = dnscli.new(dnscli.TCP) + response.includes_dnslen = 1 +elseif getopt:val("T") then + output = dnscli.new(dnscli.TLS) + response.includes_dnslen = 1 +else + output = dnscli.new(dnscli.UDP) +end +output:connect(host, port) + +local printdns = false +if getopt:val("responses") then + printdns = true +end + +local prod, pctx = layer:produce() +local recv, rctx = output:receive() +local oprod, opctx = output:produce() +local start_sec, start_nsec = clock:monotonic() + +while true do + local obj = prod(pctx) + if obj == nil then break end + local pl = obj:cast() + if obj:type() == "payload" and pl.len > 0 then + query.obj_prev = obj + + local trs = pl.obj_prev:cast() + if trs:type() == "tcp" then + query.includes_dnslen = 1 + else + query.includes_dnslen = 0 + end + + if query:parse_header() == 0 and query.qr == 0 then + recv(rctx, query:uncast()) + + if printdns then + print("query:") + query:print() + + local pobj = oprod(opctx) + if pobj == nil then + log.fatal("producer error") + end + local rpl = pobj:cast() + if rpl.len == 0 then + print("timed out") + else + response.obj_prev = pobj + print("response:") + response:print() + end + end + end + end +end + +local end_sec, end_nsec = clock:monotonic() + +local runtime = 0 +if end_sec > start_sec then + runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000) +elseif end_sec == start_sec and end_nsec > start_nsec then + runtime = (end_nsec - start_nsec) / 1000000000 +end + +print("runtime", runtime) +print("packets", input:packets(), input:packets()/runtime, "/pps") +print("queries", output:packets(), output:packets()/runtime, "/qps") +print("errors", output:errors()) diff --git a/examples/replay_multicli.lua b/examples/replay_multicli.lua new file mode 100755 index 0000000..c883aa5 --- /dev/null +++ b/examples/replay_multicli.lua @@ -0,0 +1,192 @@ +#!/usr/bin/env dnsjit +local clock = require("dnsjit.lib.clock") +local log = require("dnsjit.core.log") +local getopt = require("dnsjit.lib.getopt").new({ + { "c", "clients", 10, "Number of clients run", "?" }, + { "v", "verbose", 0, "Enable and increase verbosity for each time given", "?+" }, + { "R", "responses", false, "Wait for responses to the queries and print both", "?" }, + { "t", "tcp", false, "Use TCP instead of UDP", "?"}, + { "T", "tls", false, "Use TLS instead of UDP/TCP", "?"}, +}) +local pcap, host, port = unpack(getopt:parse()) +if getopt:val("help") then + getopt:usage() + return +end +local v = getopt:val("v") +if v > 0 then + log.enable("warning") +end +if v > 1 then + log.enable("notice") +end +if v > 2 then + log.enable("info") +end +if v > 3 then + log.enable("debug") +end + +if pcap == nil or host == nil or port == nil then + print("usage: "..arg[1].." ") + return +end + +local ffi = require("ffi") + +require("dnsjit.core.objects") +local input = require("dnsjit.input.mmpcap").new() +local layer = require("dnsjit.filter.layer").new() + +input:open(pcap) +layer:producer(input) + +local query = require("dnsjit.core.object.dns").new() +local response = require("dnsjit.core.object.dns").new() + +local dnscli = require("dnsjit.output.dnscli") + +local clients = {} +local last_client = nil +local num_clients = getopt:val("c") +for n = 1, num_clients do + local output + if getopt:val("t") then + output = dnscli.new(dnscli.TCP + dnscli.NONBLOCKING) + elseif getopt:val("T") then + output = dnscli.new(dnscli.TLS + dnscli.NONBLOCKING) + else + output = dnscli.new(dnscli.UDP + dnscli.NONBLOCKING) + end + output:connect(host, port) + + local recv, rctx = output:receive() + local prod, pctx = output:produce() + local client = { + output = output, + last = last_client, + busy = false, + recv = recv, rctx = rctx, + prod = prod, pctx = pctx, + id = nil, + done = false, + } + table.insert(clients, client) + last_client = client +end +local output = clients[1] +output.last = last_client + +if getopt:val("t") then + response.includes_dnslen = 1 +elseif getopt:val("T") then + response.includes_dnslen = 1 +end + +local printdns = false +if getopt:val("responses") then + printdns = true +end + +local prod, pctx = layer:produce() +local start_sec, start_nsec = clock:monotonic() + +local done = false +while true do + output = output.last + if printdns and output.busy then + local pobj = output.prod(output.pctx) + if pobj == nil then + log.fatal("producer error") + end + local rpl = pobj:cast() + if rpl.len == 0 then + -- print(output, "busy") + else + response.obj_prev = pobj + if response:parse_header() == 0 and response.qr == 1 and response.id == output.id then + print("response:") + response:print() + output.busy = false + end + end + end + if not output.busy then + while true do + local obj = prod(pctx) + if obj == nil then + done = true + break + end + local pl = obj:cast() + if obj:type() == "payload" and pl.len > 0 then + query.obj_prev = obj + + local trs = pl.obj_prev:cast() + if trs:type() == "tcp" then + query.includes_dnslen = 1 + else + query.includes_dnslen = 0 + end + + if query:parse_header() == 0 and query.qr == 0 then + output.recv(output.rctx, query:uncast()) + if printdns then + print("query:") + query:print() + output.busy = true + output.id = query.id + end + break + end + end + end + end + if done then break end +end + +local queries, timeouts, errors = 0, 0, 0 +done = 0 +while true do + output = output.last + if printdns and output.busy then + local pobj = output.prod(output.pctx) + if pobj == nil then + log.fatal("producer error") + end + local rpl = pobj:cast() + if rpl.len == 0 then + -- print(output, "busy") + else + response.obj_prev = pobj + if response:parse_header() == 0 and response.qr == 1 and response.id == output.id then + print("response:") + response:print() + output.busy = false + end + end + end + if not output.busy and not output.done then + output.done = true + done = done + 1 + queries = queries + output.output:packets() + timeouts = timeouts + output.output:timeouts() + errors = errors + output.output:errors() + end + if done >= num_clients then break end +end + +local end_sec, end_nsec = clock:monotonic() + +local runtime = 0 +if end_sec > start_sec then + runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000) +elseif end_sec == start_sec and end_nsec > start_nsec then + runtime = (end_nsec - start_nsec) / 1000000000 +end + +print("runtime", runtime) +print("packets", input:packets(), input:packets()/runtime, "/pps") +print("queries", queries, queries/runtime, "/qps") +print("timeouts", timeouts) +print("errors", errors) diff --git a/examples/respdiff.lua b/examples/respdiff.lua new file mode 100755 index 0000000..831f349 --- /dev/null +++ b/examples/respdiff.lua @@ -0,0 +1,160 @@ +#!/usr/bin/env dnsjit +local ffi = require("ffi") +local clock = require("dnsjit.lib.clock") +local log = require("dnsjit.core.log") +log.display_file_line(true) +local getopt = require("dnsjit.lib.getopt").new({ + { "v", "verbose", 0, "Enable and increase verbosity for each time given", "?+" }, +}) +local pcap, host, port, path, origname, recvname = unpack(getopt:parse()) +if getopt:val("help") then + getopt:usage() + return +end +local v = getopt:val("v") +if v > 0 then + log.enable("warning") +end +if v > 1 then + log.enable("notice") +end +if v > 2 then + log.enable("info") +end +if v > 3 then + log.enable("debug") +end + +if pcap == nil or host == nil or port == nil or path == nil or origname == nil or recvname == nil then + print("usage: "..arg[1].." ") + return +end + +local object = require("dnsjit.core.objects") +local dns = require("dnsjit.core.object.dns").new() +local input = require("dnsjit.input.mmpcap").new() +input:open(pcap) +local layer = require("dnsjit.filter.layer").new() +layer:producer(input) + +local udpcli, tcpcli +local udprecv, udpctx, tcprecv, tcpctx +local udpprod, tcpprod + +local prod, pctx = layer:produce() +local queries = {} +local clipayload = ffi.new("core_object_payload_t") +clipayload.obj_type = object.PAYLOAD +local cliobject = ffi.cast("core_object_t*", clipayload) + +local respdiff = require("dnsjit.output.respdiff").new(path, origname, recvname) +local resprecv, respctx = respdiff:receive() +local query_payload, original_payload, response_payload = ffi.new("core_object_payload_t"), ffi.new("core_object_payload_t"), ffi.new("core_object_payload_t") +query_payload.obj_type = object.PAYLOAD +original_payload.obj_type = object.PAYLOAD +response_payload.obj_type = object.PAYLOAD +local query_payload_obj = ffi.cast("core_object_t*", query_payload) +query_payload.obj_prev = ffi.cast("core_object_t*", original_payload) +original_payload.obj_prev = ffi.cast("core_object_t*", response_payload) + +local start_sec, start_nsec = clock:realtime() +while true do + local obj = prod(pctx) + if obj == nil then break end + local payload = obj:cast() + if obj:type() == "payload" and payload.len > 0 then + dns.obj_prev = obj + if dns:parse_header() == 0 then + local transport = obj.obj_prev + while transport ~= nil do + if transport.obj_type == object.IP or transport.obj_type == object.IP6 then + break + end + transport = transport.obj_prev + end + local protocol = obj.obj_prev + while protocol ~= nil do + if protocol.obj_type == object.UDP or protocol.obj_type == object.TCP then + break + end + protocol = protocol.obj_prev + end + + if transport ~= nil and protocol ~= nil then + transport = transport:cast() + protocol = protocol:cast() + + if dns.qr == 0 then + local k = string.format("%s %d %s %d", transport:source(), protocol.sport, transport:destination(), protocol.dport) + local q = { + id = dns.id, + proto = protocol:type(), + payload = ffi.new("uint8_t[?]", payload.len), + len = tonumber(payload.len) + } + ffi.copy(q.payload, payload.payload, payload.len) + queries[k] = q + else + local k = string.format("%s %d %s %d", transport:destination(), protocol.dport, transport:source(), protocol.sport) + local q = queries[k] + if q then + queries[k] = nil + clipayload.payload = q.payload + clipayload.len = q.len + + local prod, pctx + + if q.proto == "udp" then + if not udpcli then + udpcli = require("dnsjit.output.udpcli").new() + udpcli:connect(host, port) + udprecv, udpctx = udpcli:receive() + udpprod, _ = udpcli:produce() + end + udprecv(udpctx, cliobject) + prod = udpprod + pctx = udpctx + elseif q.proto == "tcp" then + if not tcpcli then + tcpcli = require("dnsjit.output.tcpcli").new() + tcpcli:connect(host, port) + tcprecv, tcpctx = tcpcli:receive() + tcpprod, _ = tcpcli:produce() + end + tcprecv(tcpctx, cliobject) + prod = tcpprod + pctx = tcpctx + end + + while true do + local response = prod(pctx) + if response == nil then + log.fatal("producer error") + end + local rpl = response:cast() + if rpl.len == 0 then + log.info("timed out") + else + dns.obj_prev = response + if dns:parse_header() == 0 and dns.id == q.id then + query_payload.payload = q.payload + query_payload.len = q.len + original_payload.payload = payload.payload + original_payload.len = payload.len + response_payload.payload = rpl.payload + response_payload.len = rpl.len + + resprecv(respctx, query_payload_obj) + break + end + end + end + end + end + end + end + end +end +local end_sec, end_nsec = clock:realtime() + +respdiff:commit(start_sec, end_sec) diff --git a/examples/test_pcap_read.lua b/examples/test_pcap_read.lua new file mode 100755 index 0000000..7d29c7a --- /dev/null +++ b/examples/test_pcap_read.lua @@ -0,0 +1,149 @@ +#!/usr/bin/env dnsjit +local clock = require("dnsjit.lib.clock") +local log = require("dnsjit.core.log") +local getopt = require("dnsjit.lib.getopt").new({ + { "v", "verbose", 0, "Enable and increase verbosity for each time given", "?+" }, + { "l", "layer", false, "Test also with dnsjit.filter.layer", "?" }, + { "p", "producer", false, "Test with the producer interface rather then receiver interface", "?" }, +}) +local pcap, runs = unpack(getopt:parse()) +if getopt:val("help") then + getopt:usage() + return +end +local v = getopt:val("v") +if v > 0 then + log.enable("warning") +end +if v > 1 then + log.enable("notice") +end +if v > 2 then + log.enable("info") +end +if v > 3 then + log.enable("debug") +end + +if pcap == nil then + print("usage: "..arg[1].." [runs]") + return +end + +inputs = { "fpcap", "mmpcap", "pcap" } +result = {} +results = {} +highest = nil + +if runs == nil then + runs = 10 +else + runs = tonumber(runs) +end + +if getopt:val("p") then + for _, name in pairs(inputs) do + rt = 0.0 + p = 0 + + print("run", name) + for n = 1, runs do + o = require("dnsjit.output.null").new() + i = require("dnsjit.input."..name).new() + + if name == "pcap" then + i:open_offline(pcap) + else + i:open(pcap) + end + + if getopt:val("l") then + f = require("dnsjit.filter.layer").new() + f:producer(i) + o:producer(f) + else + o:producer(i) + end + + ss, sns = clock:monotonic() + o:run() + es, ens = clock:monotonic() + + if es > ss then + rt = rt + ((es - ss) - 1) + ((1000000000 - sns + ens)/1000000000) + elseif es == ss and ens > sns then + rt = rt + (ens - sns) / 1000000000 + end + + p = p + o:packets() + end + + result[name] = { + rt = rt, + p = p + } + if highest == nil or rt > result[highest].rt then + highest = name + end + table.insert(results, name) + end +else + for _, name in pairs(inputs) do + rt = 0.0 + p = 0 + + print("run", name) + for n = 1, runs do + o = require("dnsjit.output.null").new() + i = require("dnsjit.input."..name).new() + + if name == "pcap" then + i:open_offline(pcap) + else + i:open(pcap) + end + + if getopt:val("l") then + f = require("dnsjit.filter.layer").new() + f:receiver(o) + i:receiver(f) + else + i:receiver(o) + end + + ss, sns = clock:monotonic() + if name == "pcap" then + i:dispatch() + else + i:run() + end + es, ens = clock:monotonic() + + if es > ss then + rt = rt + ((es - ss) - 1) + ((1000000000 - sns + ens)/1000000000) + elseif es == ss and ens > sns then + rt = rt + (ens - sns) / 1000000000 + end + + p = p + o:packets() + end + + result[name] = { + rt = rt, + p = p + } + if highest == nil or rt > result[highest].rt then + highest = name + end + table.insert(results, name) + end +end + +print("name", "runtime", "pps", "x", "pkts") +print(highest, result[highest].rt, result[highest].p/result[highest].rt, 1.0, result[highest].p) +for _, name in pairs(results) do + if name ~= highest then + local f = result[name].p / result[highest].p + print(name, result[name].rt, result[name].p/result[name].rt, (result[highest].rt/result[name].rt)*f, result[name].p) + end +end diff --git a/examples/test_throughput.lua b/examples/test_throughput.lua new file mode 100755 index 0000000..b936c41 --- /dev/null +++ b/examples/test_throughput.lua @@ -0,0 +1,539 @@ +#!/usr/bin/env dnsjit +local ffi = require("ffi") +local object = require("dnsjit.core.objects") +local clock = require("dnsjit.lib.clock") +local log = require("dnsjit.core.log") +local getopt = require("dnsjit.lib.getopt").new({ + { "v", "verbose", 0, "Enable and increase verbosity for each time given", "?+" }, + { "s", "split", false, "Test also with dnsjit.filter.split", "?" }, + { "t", "thread", false, "Test also with dnsjit.core.thread using dnsjit.core.channel", "?" }, +}) +local num, runs = unpack(getopt:parse()) +if getopt:val("help") then + getopt:usage() + return +end +local v = getopt:val("v") +if v > 0 then + log.enable("warning") +end +if v > 1 then + log.enable("notice") +end +if v > 2 then + log.enable("info") +end +if v > 3 then + log.enable("debug") +end + +if num == nil then + print("usage: "..arg[1].." [runs]") + return +else + num = tonumber(num) +end + +if runs == nil then + runs = 1 +else + runs = tonumber(runs) +end + +print("zero:receiver() -> null:receive()") +local run +for run = 1, runs do + local i = require("dnsjit.input.zero").new() + local o = require("dnsjit.output.null").new() + + i:receiver(o) + local start_sec, start_nsec = clock:monotonic() + i:run(num) + local end_sec, end_nsec = clock:monotonic() + + local runtime = 0 + if end_sec > start_sec then + runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000) + elseif end_sec == start_sec and end_nsec > start_nsec then + runtime = (end_nsec - start_nsec) / 1000000000 + end + + print(run, "runtime", runtime, num/runtime, "/sec", o:packets()) +end + +print("lua -> null:receive()") +local run +for run = 1, runs do + local o = require("dnsjit.output.null").new() + local recv, rctx = o:receive() + local pkt = ffi.new("core_object_null_t") + pkt.obj_type = object.NULL + local obj = ffi.cast("core_object_t*", pkt) + + local start_sec, start_nsec = clock:monotonic() + for n = 1, num do + recv(rctx, obj) + end + local end_sec, end_nsec = clock:monotonic() + + local runtime = 0 + if end_sec > start_sec then + runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000) + elseif end_sec == start_sec and end_nsec > start_nsec then + runtime = (end_nsec - start_nsec) / 1000000000 + end + + print(run, "runtime", runtime, num/runtime, "/sec", o:packets()) +end + +-- TODO: use core.thread + +print("zero:produce() <- null:producer()") +local run +for run = 1, runs do + local i = require("dnsjit.input.zero").new() + local o = require("dnsjit.output.null").new() + + local start_sec, start_nsec = clock:monotonic() + o:producer(i) + o:run(num) + local end_sec, end_nsec = clock:monotonic() + + local runtime = 0 + if end_sec > start_sec then + runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000) + elseif end_sec == start_sec and end_nsec > start_nsec then + runtime = (end_nsec - start_nsec) / 1000000000 + end + + print(run, "runtime", runtime, num/runtime, "/sec", o:packets()) +end + +print("zero:produce() <- lua") +local run +for run = 1, runs do + local i = require("dnsjit.input.zero").new() + local prod, pctx = i:produce() + + local start_sec, start_nsec = clock:monotonic() + for n = 1, num do + prod(pctx) + end + local end_sec, end_nsec = clock:monotonic() + + local runtime = 0 + if end_sec > start_sec then + runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000) + elseif end_sec == start_sec and end_nsec > start_nsec then + runtime = (end_nsec - start_nsec) / 1000000000 + end + + print(run, "runtime", runtime, num/runtime, "/sec", num) +end + +print("zero:produce() <- lua -> null:receive()") +local run +for run = 1, runs do + local i = require("dnsjit.input.zero").new() + local o = require("dnsjit.output.null").new() + local prod, pctx = i:produce() + local recv, rctx = o:receive() + + local start_sec, start_nsec = clock:monotonic() + for n = 1, num do + recv(rctx, prod(pctx)) + end + local end_sec, end_nsec = clock:monotonic() + + local runtime = 0 + if end_sec > start_sec then + runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000) + elseif end_sec == start_sec and end_nsec > start_nsec then + runtime = (end_nsec - start_nsec) / 1000000000 + end + + print(run, "runtime", runtime, num/runtime, "/sec", num) +end + +if getopt:val("s") then + print("zero:receiver() -> split:receiver() -> null:receive() x1") + local run + for run = 1, runs do + local i = require("dnsjit.input.zero").new() + local s = require("dnsjit.filter.split").new() + local o1 = require("dnsjit.output.null").new() + + s:receiver(o1) + i:receiver(s) + local start_sec, start_nsec = clock:monotonic() + i:run(num) + local end_sec, end_nsec = clock:monotonic() + + local runtime = 0 + if end_sec > start_sec then + runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000) + elseif end_sec == start_sec and end_nsec > start_nsec then + runtime = (end_nsec - start_nsec) / 1000000000 + end + + print(run, "runtime", runtime, num/runtime, "/sec", o1:packets(), o1:packets()) + end + + print("zero:receiver() -> split:receiver() -> null:receive() x2") + local run + for run = 1, runs do + local i = require("dnsjit.input.zero").new() + local s = require("dnsjit.filter.split").new() + local o1 = require("dnsjit.output.null").new() + local o2 = require("dnsjit.output.null").new() + + s:receiver(o1) + s:receiver(o2) + i:receiver(s) + local start_sec, start_nsec = clock:monotonic() + i:run(num) + local end_sec, end_nsec = clock:monotonic() + + local runtime = 0 + if end_sec > start_sec then + runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000) + elseif end_sec == start_sec and end_nsec > start_nsec then + runtime = (end_nsec - start_nsec) / 1000000000 + end + + print(run, "runtime", runtime, num/runtime, "/sec", o1:packets() + o2:packets(), o1:packets(), o2:packets()) + end + + print("zero:receiver() -> split:receiver() -> null:receive() x4") + local run + for run = 1, runs do + local i = require("dnsjit.input.zero").new() + local s = require("dnsjit.filter.split").new() + local o1 = require("dnsjit.output.null").new() + local o2 = require("dnsjit.output.null").new() + local o3 = require("dnsjit.output.null").new() + local o4 = require("dnsjit.output.null").new() + + s:receiver(o1) + s:receiver(o2) + s:receiver(o3) + s:receiver(o4) + i:receiver(s) + local start_sec, start_nsec = clock:monotonic() + i:run(num) + local end_sec, end_nsec = clock:monotonic() + + local runtime = 0 + if end_sec > start_sec then + runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000) + elseif end_sec == start_sec and end_nsec > start_nsec then + runtime = (end_nsec - start_nsec) / 1000000000 + end + + print(run, "runtime", runtime, num/runtime, "/sec", o1:packets() + o2:packets() + o3:packets() + o4:packets(), o1:packets(), o2:packets(), o3:packets(), o4:packets()) + end + + print("zero:receiver() -> lua split table -> null:receive() x4") + local run + for run = 1, runs do + local i = require("dnsjit.input.zero").new() + local o1 = require("dnsjit.output.null").new() + local o2 = require("dnsjit.output.null").new() + local o3 = require("dnsjit.output.null").new() + local o4 = require("dnsjit.output.null").new() + + local prod, pctx = i:produce() + local recv, rctx = {}, {} + + local f, c = o1:receive() + table.insert(recv, f) + table.insert(rctx, c) + f, c = o2:receive() + table.insert(recv, f) + table.insert(rctx, c) + f, c = o3:receive() + table.insert(recv, f) + table.insert(rctx, c) + f, c = o4:receive() + table.insert(recv, f) + table.insert(rctx, c) + + local start_sec, start_nsec = clock:monotonic() + local idx = 1 + for n = 1, num do + local f, c = recv[idx], rctx[idx] + if not f then + idx = 1 + f, c = recv[1], rctx[1] + end + f(c, prod(pctx)) + idx = idx + 1 + end + local end_sec, end_nsec = clock:monotonic() + + local runtime = 0 + if end_sec > start_sec then + runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000) + elseif end_sec == start_sec and end_nsec > start_nsec then + runtime = (end_nsec - start_nsec) / 1000000000 + end + + print(run, "runtime", runtime, num/runtime, "/sec", o1:packets() + o2:packets() + o3:packets() + o4:packets(), o1:packets(), o2:packets(), o3:packets(), o4:packets()) + end + + print("zero:receiver() -> lua split gen code -> null:receive() x4") + local run + for run = 1, runs do + local i = require("dnsjit.input.zero").new() + local o1 = require("dnsjit.output.null").new() + local o2 = require("dnsjit.output.null").new() + local o3 = require("dnsjit.output.null").new() + local o4 = require("dnsjit.output.null").new() + + local prod, pctx = i:produce() + local f1, c1 = o1:receive() + local f2, c2 = o2:receive() + local f3, c3 = o3:receive() + local f4, c4 = o4:receive() + + local code = "return function (num, prod, pctx, f1, c1, f2, c2, f3, c3, f4, c4)\nlocal n = 0\nwhile n < num do\n" + code = code .. "f1(c1,prod(pctx))\n" + code = code .. "n = n + 1\n" + code = code .. "f2(c2,prod(pctx))\n" + code = code .. "n = n + 1\n" + code = code .. "f3(c3,prod(pctx))\n" + code = code .. "n = n + 1\n" + code = code .. "f4(c4,prod(pctx))\n" + code = code .. "n = n + 1\n" + code = code .. "end\n" + code = code .. "end" + local f = assert(loadstring(code))() + + local start_sec, start_nsec = clock:monotonic() + f(num, prod, pctx, f1, c1, f2, c2, f3, c3, f4, c4) + local end_sec, end_nsec = clock:monotonic() + + local runtime = 0 + if end_sec > start_sec then + runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000) + elseif end_sec == start_sec and end_nsec > start_nsec then + runtime = (end_nsec - start_nsec) / 1000000000 + end + + print(run, "runtime", runtime, num/runtime, "/sec", o1:packets() + o2:packets() + o3:packets() + o4:packets(), o1:packets(), o2:packets(), o3:packets(), o4:packets()) + end +end + +if getopt:val("t") then + print("zero:receiver() -> thread lua x1") + local run + for run = 1, runs do + local i = require("dnsjit.input.zero").new() + local c = require("dnsjit.core.channel").new() + local t = require("dnsjit.core.thread").new() + + t:start(function(t) + local c = t:pop() + + while true do + local o = c:get() + if o == nil then break end + end + end) + t:push(c) + + local prod, pctx = i:produce() + local start_sec, start_nsec = clock:monotonic() + for n = 1, num do + c:put(prod(pctx)) + end + c:close() + t:stop() + local end_sec, end_nsec = clock:monotonic() + + local runtime = 0 + if end_sec > start_sec then + runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000) + elseif end_sec == start_sec and end_nsec > start_nsec then + runtime = (end_nsec - start_nsec) / 1000000000 + end + + print(run, "runtime", runtime, num/runtime, "/sec") + end + + print("zero:receiver() -> thread lua x2") + local run + for run = 1, runs do + local i = require("dnsjit.input.zero").new() + local c = require("dnsjit.core.channel").new() + local c2 = require("dnsjit.core.channel").new() + local t = require("dnsjit.core.thread").new() + local t2 = require("dnsjit.core.thread").new() + + local f = function(t) + local c = t:pop() + + while true do + local o = c:get() + if o == nil then break end + end + end + + t:start(f) + t2:start(f) + t:push(c) + t2:push(c2) + + local prod, pctx = i:produce() + local start_sec, start_nsec = clock:monotonic() + for n = 1, num/2 do + c:put(prod(pctx)) + c2:put(prod(pctx)) + end + c:close() + c2:close() + t:stop() + t2:stop() + local end_sec, end_nsec = clock:monotonic() + + local runtime = 0 + if end_sec > start_sec then + runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000) + elseif end_sec == start_sec and end_nsec > start_nsec then + runtime = (end_nsec - start_nsec) / 1000000000 + end + + print(run, "runtime", runtime, num/runtime, "/sec") + end + + print("zero:receiver() -> thread lua x4") + local run + for run = 1, runs do + local i = require("dnsjit.input.zero").new() + local c = require("dnsjit.core.channel").new() + local c2 = require("dnsjit.core.channel").new() + local c3 = require("dnsjit.core.channel").new() + local c4 = require("dnsjit.core.channel").new() + local t = require("dnsjit.core.thread").new() + local t2 = require("dnsjit.core.thread").new() + local t3 = require("dnsjit.core.thread").new() + local t4 = require("dnsjit.core.thread").new() + + local f = function(t) + local c = t:pop() + + while true do + local o = c:get() + if o == nil then break end + end + end + + t:start(f) + t2:start(f) + t3:start(f) + t4:start(f) + t:push(c) + t2:push(c2) + t3:push(c3) + t4:push(c4) + + local prod, pctx = i:produce() + local start_sec, start_nsec = clock:monotonic() + for n = 1, num/4 do + c:put(prod(pctx)) + c2:put(prod(pctx)) + c3:put(prod(pctx)) + c4:put(prod(pctx)) + end + c:close() + c2:close() + c3:close() + c4:close() + t:stop() + t2:stop() + t3:stop() + t4:stop() + local end_sec, end_nsec = clock:monotonic() + + local runtime = 0 + if end_sec > start_sec then + runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000) + elseif end_sec == start_sec and end_nsec > start_nsec then + runtime = (end_nsec - start_nsec) / 1000000000 + end + + print(run, "runtime", runtime, num/runtime, "/sec") + end + + print("zero:receiver() -> thread lua x1 -> null:receiver()") + local run + for run = 1, runs do + local i = require("dnsjit.input.zero").new() + local c = require("dnsjit.core.channel").new() + local t = require("dnsjit.core.thread").new() + + t:start(function(t) + local c = t:pop() + local o = require("dnsjit.output.null").new() + + local recv, rctx = o:receive() + while true do + local obj = c:get() + if obj == nil then break end + recv(rctx, obj) + end + end) + t:push(c) + + local prod, pctx = i:produce() + local start_sec, start_nsec = clock:monotonic() + for n = 1, num do + c:put(prod(pctx)) + end + c:close() + t:stop() + local end_sec, end_nsec = clock:monotonic() + + local runtime = 0 + if end_sec > start_sec then + runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000) + elseif end_sec == start_sec and end_nsec > start_nsec then + runtime = (end_nsec - start_nsec) / 1000000000 + end + + print(run, "runtime", runtime, num/runtime, "/sec") + end + + print("zero:receiver() -> thread x1 -> null:receiver()") + local run + for run = 1, runs do + local i = require("dnsjit.input.zero").new() + local c = require("dnsjit.core.channel").new() + local t = require("dnsjit.core.thread").new() + + t:start(function(t) + local c = t:pop() + local o = require("dnsjit.output.null").new() + + c:receiver(o) + c:run() + end) + t:push(c) + + i:receiver(c) + local start_sec, start_nsec = clock:monotonic() + i:run(num) + c:close() + t:stop() + local end_sec, end_nsec = clock:monotonic() + + local runtime = 0 + if end_sec > start_sec then + runtime = ((end_sec - start_sec) - 1) + ((1000000000 - start_nsec + end_nsec)/1000000000) + elseif end_sec == start_sec and end_nsec > start_nsec then + runtime = (end_nsec - start_nsec) / 1000000000 + end + + print(run, "runtime", runtime, num/runtime, "/sec") + end +end diff --git a/fmt.sh b/fmt.sh new file mode 100755 index 0000000..8585229 --- /dev/null +++ b/fmt.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# Copyright (c) 2018-2021, OARC, Inc. +# All rights reserved. +# +# This file is part of dnsjit. +# +# dnsjit is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# dnsjit is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with dnsjit. If not, see . + +clang-format \ + -style=file \ + -i \ + src/*.c \ + `find src/core src/input src/filter src/output src/lib -name '*.c'` \ + `find src/core src/input src/filter src/output src/lib -name '*.h'` \ + `find src/core src/input src/filter src/output src/lib -name '*.hh'` diff --git a/m4/ax_append_flag.m4 b/m4/ax_append_flag.m4 new file mode 100644 index 0000000..dd6d8b6 --- /dev/null +++ b/m4/ax_append_flag.m4 @@ -0,0 +1,50 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_append_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_APPEND_FLAG(FLAG, [FLAGS-VARIABLE]) +# +# DESCRIPTION +# +# FLAG is appended to the FLAGS-VARIABLE shell variable, with a space +# added in between. +# +# If FLAGS-VARIABLE is not specified, the current language's flags (e.g. +# CFLAGS) is used. FLAGS-VARIABLE is not changed if it already contains +# FLAG. If FLAGS-VARIABLE is unset in the shell, it is set to exactly +# FLAG. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# Copyright (c) 2011 Maarten Bosmans +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 8 + +AC_DEFUN([AX_APPEND_FLAG], +[dnl +AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_SET_IF +AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[FLAGS])]) +AS_VAR_SET_IF(FLAGS,[ + AS_CASE([" AS_VAR_GET(FLAGS) "], + [*" $1 "*], [AC_RUN_LOG([: FLAGS already contains $1])], + [ + AS_VAR_APPEND(FLAGS,[" $1"]) + AC_RUN_LOG([: FLAGS="$FLAGS"]) + ]) + ], + [ + AS_VAR_SET(FLAGS,[$1]) + AC_RUN_LOG([: FLAGS="$FLAGS"]) + ]) +AS_VAR_POPDEF([FLAGS])dnl +])dnl AX_APPEND_FLAG diff --git a/m4/ax_cflags_warn_all.m4 b/m4/ax_cflags_warn_all.m4 new file mode 100644 index 0000000..9235a18 --- /dev/null +++ b/m4/ax_cflags_warn_all.m4 @@ -0,0 +1,158 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_cflags_warn_all.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CFLAGS_WARN_ALL [(shellvar[, default[, action-if-found[, action-if-not-found]]])] +# AX_CXXFLAGS_WARN_ALL [(shellvar[, default[, action-if-found[, action-if-not-found]]])] +# AX_FCFLAGS_WARN_ALL [(shellvar[, default[, action-if-found[, action-if-not-found]]])] +# +# DESCRIPTION +# +# Specify compiler options that enable most reasonable warnings. For the +# GNU Compiler Collection (GCC), for example, it will be "-Wall". The +# result is added to shellvar, one of CFLAGS, CXXFLAGS or FCFLAGS if the +# first parameter is not specified. +# +# Each of these macros accepts the following optional arguments: +# +# - $1 - shellvar +# shell variable to use (CFLAGS, CXXFLAGS or FCFLAGS if not +# specified, depending on macro) +# +# - $2 - default +# value to use for flags if compiler vendor cannot be determined (by +# default, "") +# +# - $3 - action-if-found +# action to take if the compiler vendor has been successfully +# determined (by default, add the appropriate compiler flags to +# shellvar) +# +# - $4 - action-if-not-found +# action to take if the compiler vendor has not been determined or +# is unknown (by default, add the default flags, or "" if not +# specified, to shellvar) +# +# These macros use AX_COMPILER_VENDOR to determine which flags should be +# returned for a given compiler. Not all compilers currently have flags +# defined for them; patches are welcome. If need be, compiler flags may +# be made language-dependent: use a construct like the following: +# +# [vendor_name], [m4_if(_AC_LANG_PREFIX,[C], VAR="--relevant-c-flags",dnl +# m4_if(_AC_LANG_PREFIX,[CXX], VAR="--relevant-c++-flags",dnl +# m4_if(_AC_LANG_PREFIX,[FC], VAR="--relevant-fortran-flags",dnl +# VAR="$2"; FOUND="no")))], +# +# Note: These macros also depend on AX_PREPEND_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# Copyright (c) 2010 Rhys Ulerich +# Copyright (c) 2018 John Zaitseff +# +# 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 . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 25 + +AC_DEFUN([AX_FLAGS_WARN_ALL], [ + AX_REQUIRE_DEFINED([AX_PREPEND_FLAG])dnl + AC_REQUIRE([AX_COMPILER_VENDOR])dnl + + AS_VAR_PUSHDEF([FLAGS], [m4_default($1,_AC_LANG_PREFIX[]FLAGS)])dnl + AS_VAR_PUSHDEF([VAR], [ac_cv_[]_AC_LANG_ABBREV[]flags_warn_all])dnl + AS_VAR_PUSHDEF([FOUND], [ac_save_[]_AC_LANG_ABBREV[]flags_warn_all_found])dnl + + AC_CACHE_CHECK([FLAGS for most reasonable warnings], VAR, [ + VAR="" + FOUND="yes" + dnl Cases are listed in the order found in ax_compiler_vendor.m4 + AS_CASE("$ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor", + [intel], [VAR="-w2"], + [ibm], [VAR="-qsrcmsg -qinfo=all:noppt:noppc:noobs:nocnd"], + [pathscale], [], + [clang], [VAR="-Wall"], + [cray], [VAR="-h msglevel 2"], + [fujitsu], [], + [sdcc], [], + [sx], [VAR="-pvctl[,]fullmsg"], + [portland], [], + [gnu], [VAR="-Wall"], + [sun], [VAR="-v"], + [hp], [VAR="+w1"], + [dec], [VAR="-verbose -w0 -warnprotos"], + [borland], [], + [comeau], [], + [kai], [], + [lcc], [], + [sgi], [VAR="-fullwarn"], + [microsoft], [], + [metrowerks], [], + [watcom], [], + [tcc], [], + [unknown], [ + VAR="$2" + FOUND="no" + ], + [ + AC_MSG_WARN([Unknown compiler vendor returned by [AX_COMPILER_VENDOR]]) + VAR="$2" + FOUND="no" + ] + ) + + AS_IF([test "x$FOUND" = "xyes"], [dnl + m4_default($3, [AS_IF([test "x$VAR" != "x"], [AX_PREPEND_FLAG([$VAR], [FLAGS])])]) + ], [dnl + m4_default($4, [m4_ifval($2, [AX_PREPEND_FLAG([$VAR], [FLAGS])], [true])]) + ])dnl + ])dnl + + AS_VAR_POPDEF([FOUND])dnl + AS_VAR_POPDEF([VAR])dnl + AS_VAR_POPDEF([FLAGS])dnl +])dnl AX_FLAGS_WARN_ALL + +AC_DEFUN([AX_CFLAGS_WARN_ALL], [dnl + AC_LANG_PUSH([C]) + AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4]) + AC_LANG_POP([C]) +])dnl + +AC_DEFUN([AX_CXXFLAGS_WARN_ALL], [dnl + AC_LANG_PUSH([C++]) + AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4]) + AC_LANG_POP([C++]) +])dnl + +AC_DEFUN([AX_FCFLAGS_WARN_ALL], [dnl + AC_LANG_PUSH([Fortran]) + AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4]) + AC_LANG_POP([Fortran]) +])dnl diff --git a/m4/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4 new file mode 100644 index 0000000..bd753b3 --- /dev/null +++ b/m4/ax_check_compile_flag.m4 @@ -0,0 +1,53 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the current language's compiler +# or gives an error. (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the current language's default +# flags (e.g. CFLAGS) when the check is done. The check is thus made with +# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to +# force the compiler to issue an error when a bad flag is given. +# +# INPUT gives an alternative input source to AC_COMPILE_IFELSE. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# Copyright (c) 2011 Maarten Bosmans +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 6 + +AC_DEFUN([AX_CHECK_COMPILE_FLAG], +[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF +AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl +AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ + ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS + _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" + AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) +AS_VAR_IF(CACHEVAR,yes, + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_COMPILE_FLAGS diff --git a/m4/ax_compiler_vendor.m4 b/m4/ax_compiler_vendor.m4 new file mode 100644 index 0000000..f06e865 --- /dev/null +++ b/m4/ax_compiler_vendor.m4 @@ -0,0 +1,117 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_compiler_vendor.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_COMPILER_VENDOR +# +# DESCRIPTION +# +# Determine the vendor of the C, C++ or Fortran compiler. The vendor is +# returned in the cache variable $ax_cv_c_compiler_vendor for C, +# $ax_cv_cxx_compiler_vendor for C++ or $ax_cv_fc_compiler_vendor for +# (modern) Fortran. The value is one of "intel", "ibm", "pathscale", +# "clang" (LLVM), "cray", "fujitsu", "sdcc", "sx", "portland" (PGI), "gnu" +# (GCC), "sun" (Oracle Developer Studio), "hp", "dec", "borland", +# "comeau", "kai", "lcc", "sgi", "microsoft", "metrowerks", "watcom", +# "tcc" (Tiny CC) or "unknown" (if the compiler cannot be determined). +# +# To check for a Fortran compiler, you must first call AC_FC_PP_SRCEXT +# with an appropriate preprocessor-enabled extension. For example: +# +# AC_LANG_PUSH([Fortran]) +# AC_PROG_FC +# AC_FC_PP_SRCEXT([F]) +# AX_COMPILER_VENDOR +# AC_LANG_POP([Fortran]) +# +# LICENSE +# +# Copyright (c) 2008 Steven G. Johnson +# Copyright (c) 2008 Matteo Frigo +# Copyright (c) 2018-19 John Zaitseff +# +# 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 . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 30 + +AC_DEFUN([AX_COMPILER_VENDOR], [dnl + AC_CACHE_CHECK([for _AC_LANG compiler vendor], ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor, [dnl + dnl If you modify this list of vendors, please add similar support + dnl to ax_compiler_version.m4 if at all possible. + dnl + dnl Note: Do NOT check for GCC first since some other compilers + dnl define __GNUC__ to remain compatible with it. Compilers that + dnl are very slow to start (such as Intel) are listed first. + + vendors=" + intel: __ICC,__ECC,__INTEL_COMPILER + ibm: __xlc__,__xlC__,__IBMC__,__IBMCPP__,__ibmxl__ + pathscale: __PATHCC__,__PATHSCALE__ + clang: __clang__ + cray: _CRAYC + fujitsu: __FUJITSU + sdcc: SDCC,__SDCC + sx: _SX + portland: __PGI + gnu: __GNUC__ + sun: __SUNPRO_C,__SUNPRO_CC,__SUNPRO_F90,__SUNPRO_F95 + hp: __HP_cc,__HP_aCC + dec: __DECC,__DECCXX,__DECC_VER,__DECCXX_VER + borland: __BORLANDC__,__CODEGEARC__,__TURBOC__ + comeau: __COMO__ + kai: __KCC + lcc: __LCC__ + sgi: __sgi,sgi + microsoft: _MSC_VER + metrowerks: __MWERKS__ + watcom: __WATCOMC__ + tcc: __TINYC__ + unknown: UNKNOWN + " + for ventest in $vendors; do + case $ventest in + *:) + vendor=$ventest + continue + ;; + *) + vencpp="defined("`echo $ventest | sed 's/,/) || defined(/g'`")" + ;; + esac + + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [[ +#if !($vencpp) + thisisanerror; +#endif + ]])], [break]) + done + + ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor=`echo $vendor | cut -d: -f1` + ]) +])dnl diff --git a/m4/ax_ext.m4 b/m4/ax_ext.m4 new file mode 100644 index 0000000..b95d75a --- /dev/null +++ b/m4/ax_ext.m4 @@ -0,0 +1,328 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_ext.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_EXT +# +# DESCRIPTION +# +# Find supported SIMD extensions by requesting cpuid. When a SIMD +# extension is found, the -m"simdextensionname" is added to SIMD_FLAGS if +# compiler supports it. For example, if "sse2" is available then "-msse2" +# is added to SIMD_FLAGS. +# +# Find other supported CPU extensions by requesting cpuid. When a +# processor extension is found, the -m"extensionname" is added to +# CPUEXT_FLAGS if compiler supports it. For example, if "bmi2" is +# available then "-mbmi2" is added to CPUEXT_FLAGS. +# +# This macro calls: +# +# AC_SUBST(SIMD_FLAGS) +# AC_SUBST(CPUEXT_FLAGS) +# +# And defines: +# +# HAVE_RDRND / HAVE_BMI1 / HAVE_BMI2 / HAVE_ADX / HAVE_MPX +# HAVE_PREFETCHWT1 / HAVE_ABM / HAVE_MMX / HAVE_SSE / HAVE_SSE2 +# HAVE_SSE3 / HAVE_SSSE3 / HAVE_SSE4_1 / HAVE_SSE4_2 / HAVE_SSE4a +# HAVE_SHA / HAVE_AES / HAVE_AVX / HAVE_FMA3 / HAVE_FMA4 / HAVE_XOP +# HAVE_AVX2 / HAVE_AVX512_F / HAVE_AVX512_CD / HAVE_AVX512_PF +# HAVE_AVX512_ER / HAVE_AVX512_VL / HAVE_AVX512_BW / HAVE_AVX512_DQ +# HAVE_AVX512_IFMA / HAVE_AVX512_VBMI / HAVE_ALTIVEC / HAVE_VSX +# +# LICENSE +# +# Copyright (c) 2007 Christophe Tournayre +# Copyright (c) 2013,2015 Michael Petch +# Copyright (c) 2017 Rafael de Lucena Valle +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 18 + +AC_DEFUN([AX_EXT], +[ + AC_REQUIRE([AC_CANONICAL_HOST]) + AC_REQUIRE([AC_PROG_CC]) + + CPUEXT_FLAGS="" + SIMD_FLAGS="" + + case $host_cpu in + powerpc*) + AC_CACHE_CHECK([whether altivec is supported for old distros], [ax_cv_have_altivec_old_ext], + [ + if test `/usr/sbin/sysctl -a 2>/dev/null| grep -c hw.optional.altivec` != 0; then + if test `/usr/sbin/sysctl -n hw.optional.altivec` = 1; then + ax_cv_have_altivec_old_ext=yes + fi + fi + ]) + + if test "$ax_cv_have_altivec_old_ext" = yes; then + AC_DEFINE(HAVE_ALTIVEC,,[Support Altivec instructions]) + AX_CHECK_COMPILE_FLAG(-faltivec, SIMD_FLAGS="$SIMD_FLAGS -faltivec", []) + fi + + AC_CACHE_CHECK([whether altivec is supported], [ax_cv_have_altivec_ext], + [ + if test `LD_SHOW_AUXV=1 /bin/true 2>/dev/null|grep -c altivec` != 0; then + ax_cv_have_altivec_ext=yes + fi + ]) + + if test "$ax_cv_have_altivec_ext" = yes; then + AC_DEFINE(HAVE_ALTIVEC,,[Support Altivec instructions]) + AX_CHECK_COMPILE_FLAG(-maltivec, SIMD_FLAGS="$SIMD_FLAGS -maltivec", []) + fi + + AC_CACHE_CHECK([whether vsx is supported], [ax_cv_have_vsx_ext], + [ + if test `LD_SHOW_AUXV=1 /bin/true 2>/dev/null|grep -c vsx` != 0; then + ax_cv_have_vsx_ext=yes + fi + ]) + + if test "$ax_cv_have_vsx_ext" = yes; then + AC_DEFINE(HAVE_VSX,,[Support VSX instructions]) + AX_CHECK_COMPILE_FLAG(-mvsx, SIMD_FLAGS="$SIMD_FLAGS -mvsx", []) + fi + ;; + + i[[3456]]86*|x86_64*|amd64*) + + AC_REQUIRE([AX_GCC_X86_CPUID]) + AC_REQUIRE([AX_GCC_X86_CPUID_COUNT]) + AC_REQUIRE([AX_GCC_X86_AVX_XGETBV]) + + eax_cpuid0=0 + AX_GCC_X86_CPUID(0x00000000) + if test "$ax_cv_gcc_x86_cpuid_0x00000000" != "unknown"; + then + eax_cpuid0=`echo $ax_cv_gcc_x86_cpuid_0x00000000 | cut -d ":" -f 1` + fi + + eax_cpuid80000000=0 + AX_GCC_X86_CPUID(0x80000000) + if test "$ax_cv_gcc_x86_cpuid_0x80000000" != "unknown"; + then + eax_cpuid80000000=`echo $ax_cv_gcc_x86_cpuid_0x80000000 | cut -d ":" -f 1` + fi + + ecx_cpuid1=0 + edx_cpuid1=0 + if test "$((0x$eax_cpuid0))" -ge 1 ; then + AX_GCC_X86_CPUID(0x00000001) + if test "$ax_cv_gcc_x86_cpuid_0x00000001" != "unknown"; + then + ecx_cpuid1=`echo $ax_cv_gcc_x86_cpuid_0x00000001 | cut -d ":" -f 3` + edx_cpuid1=`echo $ax_cv_gcc_x86_cpuid_0x00000001 | cut -d ":" -f 4` + fi + fi + + ebx_cpuid7=0 + ecx_cpuid7=0 + if test "$((0x$eax_cpuid0))" -ge 7 ; then + AX_GCC_X86_CPUID_COUNT(0x00000007, 0x00) + if test "$ax_cv_gcc_x86_cpuid_0x00000007" != "unknown"; + then + ebx_cpuid7=`echo $ax_cv_gcc_x86_cpuid_0x00000007 | cut -d ":" -f 2` + ecx_cpuid7=`echo $ax_cv_gcc_x86_cpuid_0x00000007 | cut -d ":" -f 3` + fi + fi + + ecx_cpuid80000001=0 + edx_cpuid80000001=0 + if test "$((0x$eax_cpuid80000000))" -ge "$((0x80000001))" ; then + AX_GCC_X86_CPUID(0x80000001) + if test "$ax_cv_gcc_x86_cpuid_0x80000001" != "unknown"; + then + ecx_cpuid80000001=`echo $ax_cv_gcc_x86_cpuid_0x80000001 | cut -d ":" -f 3` + edx_cpuid80000001=`echo $ax_cv_gcc_x86_cpuid_0x80000001 | cut -d ":" -f 4` + fi + fi + + AC_CACHE_VAL([ax_cv_have_mmx_os_support_ext], + [ + ax_cv_have_mmx_os_support_ext=yes + ]) + + ax_cv_have_none_os_support_ext=yes + + AC_CACHE_VAL([ax_cv_have_sse_os_support_ext], + [ + ax_cv_have_sse_os_support_ext=no, + if test "$((0x$edx_cpuid1>>25&0x01))" = 1; then + AC_LANG_PUSH([C]) + AC_RUN_IFELSE([AC_LANG_SOURCE([[ +#include +#include + /* No way at ring1 to ring3 in protected mode to check the CR0 and CR4 + control registers directly. Execute an SSE instruction. + If it raises SIGILL then OS doesn't support SSE based instructions */ + void sig_handler(int signum){ exit(1); } + int main(){ + signal(SIGILL, sig_handler); + /* SSE instruction xorps %xmm0,%xmm0 */ + __asm__ __volatile__ (".byte 0x0f, 0x57, 0xc0"); + return 0; + }]])], + [ax_cv_have_sse_os_support_ext=yes], + [ax_cv_have_sse_os_support_ext=no], + [ax_cv_have_sse_os_support_ext=no]) + AC_LANG_POP([C]) + fi + ]) + + xgetbv_eax=0 + if test "$((0x$ecx_cpuid1>>28&0x01))" = 1; then + AX_GCC_X86_AVX_XGETBV(0x00000000) + + if test x"$ax_cv_gcc_x86_avx_xgetbv_0x00000000" != x"unknown"; then + xgetbv_eax=`echo $ax_cv_gcc_x86_avx_xgetbv_0x00000000 | cut -d ":" -f 1` + fi + + AC_CACHE_VAL([ax_cv_have_avx_os_support_ext], + [ + ax_cv_have_avx_os_support_ext=no + if test "$((0x$ecx_cpuid1>>27&0x01))" = 1; then + if test "$((0x$xgetbv_eax&0x6))" = 6; then + ax_cv_have_avx_os_support_ext=yes + fi + fi + ]) + fi + + AC_CACHE_VAL([ax_cv_have_avx512_os_support_ext], + [ + ax_cv_have_avx512_os_support_ext=no + if test "$ax_cv_have_avx_os_support_ext" = yes; then + if test "$((0x$xgetbv_eax&0xe6))" = "$((0xe6))"; then + ax_cv_have_avx512_os_support_ext=yes + fi + fi + ]) + + for ac_instr_info dnl + in "none;rdrnd;RDRND;ecx_cpuid1,30;-mrdrnd;HAVE_RDRND;CPUEXT_FLAGS" dnl + "none;bmi1;BMI1;ebx_cpuid7,3;-mbmi;HAVE_BMI1;CPUEXT_FLAGS" dnl + "none;bmi2;BMI2;ebx_cpuid7,8;-mbmi2;HAVE_BMI2;CPUEXT_FLAGS" dnl + "none;adx;ADX;ebx_cpuid7,19;-madx;HAVE_ADX;CPUEXT_FLAGS" dnl + "none;mpx;MPX;ebx_cpuid7,14;-mmpx;HAVE_MPX;CPUEXT_FLAGS" dnl + "none;prefetchwt1;PREFETCHWT1;ecx_cpuid7,0;-mprefetchwt1;HAVE_PREFETCHWT1;CPUEXT_FLAGS" dnl + "none;abm;ABM;ecx_cpuid80000001,5;-mabm;HAVE_ABM;CPUEXT_FLAGS" dnl + "mmx;mmx;MMX;edx_cpuid1,23;-mmmx;HAVE_MMX;SIMD_FLAGS" dnl + "sse;sse;SSE;edx_cpuid1,25;-msse;HAVE_SSE;SIMD_FLAGS" dnl + "sse;sse2;SSE2;edx_cpuid1,26;-msse2;HAVE_SSE2;SIMD_FLAGS" dnl + "sse;sse3;SSE3;ecx_cpuid1,1;-msse3;HAVE_SSE3;SIMD_FLAGS" dnl + "sse;ssse3;SSSE3;ecx_cpuid1,9;-mssse3;HAVE_SSSE3;SIMD_FLAGS" dnl + "sse;sse41;SSE4.1;ecx_cpuid1,19;-msse4.1;HAVE_SSE4_1;SIMD_FLAGS" dnl + "sse;sse42;SSE4.2;ecx_cpuid1,20;-msse4.2;HAVE_SSE4_2;SIMD_FLAGS" dnl + "sse;sse4a;SSE4a;ecx_cpuid80000001,6;-msse4a;HAVE_SSE4a;SIMD_FLAGS" dnl + "sse;sha;SHA;ebx_cpuid7,29;-msha;HAVE_SHA;SIMD_FLAGS" dnl + "sse;aes;AES;ecx_cpuid1,25;-maes;HAVE_AES;SIMD_FLAGS" dnl + "avx;avx;AVX;ecx_cpuid1,28;-mavx;HAVE_AVX;SIMD_FLAGS" dnl + "avx;fma3;FMA3;ecx_cpuid1,12;-mfma;HAVE_FMA3;SIMD_FLAGS" dnl + "avx;fma4;FMA4;ecx_cpuid80000001,16;-mfma4;HAVE_FMA4;SIMD_FLAGS" dnl + "avx;xop;XOP;ecx_cpuid80000001,11;-mxop;HAVE_XOP;SIMD_FLAGS" dnl + "avx;avx2;AVX2;ebx_cpuid7,5;-mavx2;HAVE_AVX2;SIMD_FLAGS" dnl + "avx512;avx512f;AVX512-F;ebx_cpuid7,16;-mavx512f;HAVE_AVX512_F;SIMD_FLAGS" dnl + "avx512;avx512cd;AVX512-CD;ebx_cpuid7,28;-mavx512cd;HAVE_AVX512_CD;SIMD_FLAGS" dnl + "avx512;avx512pf;AVX512-PF;ebx_cpuid7,26;-mavx512pf;HAVE_AVX512_PF;SIMD_FLAGS" dnl + "avx512;avx512er;AVX512-ER;ebx_cpuid7,27;-mavx512er;HAVE_AVX512_ER;SIMD_FLAGS" dnl + "avx512;avx512vl;AVX512-VL;ebx_cpuid7,31;-mavx512vl;HAVE_AVX512_VL;SIMD_FLAGS" dnl + "avx512;avx512bw;AVX512-BW;ebx_cpuid7,30;-mavx512bw;HAVE_AVX512_BW;SIMD_FLAGS" dnl + "avx512;avx512dq;AVX512-DQ;ebx_cpuid7,17;-mavx512dq;HAVE_AVX512_DQ;SIMD_FLAGS" dnl + "avx512;avx512ifma;AVX512-IFMA;ebx_cpuid7,21;-mavx512ifma;HAVE_AVX512_IFMA;SIMD_FLAGS" dnl + "avx512;avx512vbmi;AVX512-VBMI;ecx_cpuid7,1;-mavx512vbmi;HAVE_AVX512_VBMI;SIMD_FLAGS" dnl + # + do ac_instr_os_support=$(eval echo \$ax_cv_have_$(echo $ac_instr_info | cut -d ";" -f 1)_os_support_ext) + ac_instr_acvar=$(echo $ac_instr_info | cut -d ";" -f 2) + ac_instr_shortname=$(echo $ac_instr_info | cut -d ";" -f 3) + ac_instr_chk_loc=$(echo $ac_instr_info | cut -d ";" -f 4) + ac_instr_chk_reg=0x$(eval echo \$$(echo $ac_instr_chk_loc | cut -d "," -f 1)) + ac_instr_chk_bit=$(echo $ac_instr_chk_loc | cut -d "," -f 2) + ac_instr_compiler_flags=$(echo $ac_instr_info | cut -d ";" -f 5) + ac_instr_have_define=$(echo $ac_instr_info | cut -d ";" -f 6) + ac_instr_flag_type=$(echo $ac_instr_info | cut -d ";" -f 7) + + AC_CACHE_CHECK([whether ${ac_instr_shortname} is supported by the processor], [ax_cv_have_${ac_instr_acvar}_cpu_ext], + [ + eval ax_cv_have_${ac_instr_acvar}_cpu_ext=no + if test "$((${ac_instr_chk_reg}>>${ac_instr_chk_bit}&0x01))" = 1 ; then + eval ax_cv_have_${ac_instr_acvar}_cpu_ext=yes + fi + ]) + + if test x"$(eval echo \$ax_cv_have_${ac_instr_acvar}_cpu_ext)" = x"yes"; then + AC_CACHE_CHECK([whether ${ac_instr_shortname} is supported by the processor and OS], [ax_cv_have_${ac_instr_acvar}_ext], + [ + eval ax_cv_have_${ac_instr_acvar}_ext=no + if test x"${ac_instr_os_support}" = x"yes"; then + eval ax_cv_have_${ac_instr_acvar}_ext=yes + fi + ]) + + if test "$(eval echo \$ax_cv_have_${ac_instr_acvar}_ext)" = yes; then + AX_CHECK_COMPILE_FLAG(${ac_instr_compiler_flags}, eval ax_cv_support_${ac_instr_acvar}_ext=yes, + eval ax_cv_support_${ac_instr_acvar}_ext=no) + if test x"$(eval echo \$ax_cv_support_${ac_instr_acvar}_ext)" = x"yes"; then + eval ${ac_instr_flag_type}=\"\$${ac_instr_flag_type} ${ac_instr_compiler_flags}\" + AC_DEFINE_UNQUOTED([${ac_instr_have_define}]) + else + AC_MSG_WARN([Your processor and OS supports ${ac_instr_shortname} instructions but not your compiler, can you try another compiler?]) + fi + else + if test x"${ac_instr_os_support}" = x"no"; then + AC_CACHE_VAL(ax_cv_support_${ac_instr_acvar}_ext, eval ax_cv_support_${ac_instr_acvar}_ext=no) + AC_MSG_WARN([Your processor supports ${ac_instr_shortname}, but your OS doesn't]) + fi + fi + else + AC_CACHE_VAL(ax_cv_have_${ac_instr_acvar}_ext, eval ax_cv_have_${ac_instr_acvar}_ext=no) + AC_CACHE_VAL(ax_cv_support_${ac_instr_acvar}_ext, eval ax_cv_support_${ac_instr_acvar}_ext=no) + fi + done + ;; + esac + + AH_TEMPLATE([HAVE_RDRND],[Define to 1 to support Digital Random Number Generator]) + AH_TEMPLATE([HAVE_BMI1],[Define to 1 to support Bit Manipulation Instruction Set 1]) + AH_TEMPLATE([HAVE_BMI2],[Define to 1 to support Bit Manipulation Instruction Set 2]) + AH_TEMPLATE([HAVE_ADX],[Define to 1 to support Multi-Precision Add-Carry Instruction Extensions]) + AH_TEMPLATE([HAVE_MPX],[Define to 1 to support Memory Protection Extensions]) + AH_TEMPLATE([HAVE_PREFETCHWT1],[Define to 1 to support Prefetch Vector Data Into Caches WT1]) + AH_TEMPLATE([HAVE_ABM],[Define to 1 to support Advanced Bit Manipulation]) + AH_TEMPLATE([HAVE_MMX],[Define to 1 to support Multimedia Extensions]) + AH_TEMPLATE([HAVE_SSE],[Define to 1 to support Streaming SIMD Extensions]) + AH_TEMPLATE([HAVE_SSE2],[Define to 1 to support Streaming SIMD Extensions]) + AH_TEMPLATE([HAVE_SSE3],[Define to 1 to support Streaming SIMD Extensions 3]) + AH_TEMPLATE([HAVE_SSSE3],[Define to 1 to support Supplemental Streaming SIMD Extensions 3]) + AH_TEMPLATE([HAVE_SSE4_1],[Define to 1 to support Streaming SIMD Extensions 4.1]) + AH_TEMPLATE([HAVE_SSE4_2],[Define to 1 to support Streaming SIMD Extensions 4.2]) + AH_TEMPLATE([HAVE_SSE4a],[Define to 1 to support AMD Streaming SIMD Extensions 4a]) + AH_TEMPLATE([HAVE_SHA],[Define to 1 to support Secure Hash Algorithm Extension]) + AH_TEMPLATE([HAVE_AES],[Define to 1 to support Advanced Encryption Standard New Instruction Set (AES-NI)]) + AH_TEMPLATE([HAVE_AVX],[Define to 1 to support Advanced Vector Extensions]) + AH_TEMPLATE([HAVE_FMA3],[Define to 1 to support Fused Multiply-Add Extensions 3]) + AH_TEMPLATE([HAVE_FMA4],[Define to 1 to support Fused Multiply-Add Extensions 4]) + AH_TEMPLATE([HAVE_XOP],[Define to 1 to support eXtended Operations Extensions]) + AH_TEMPLATE([HAVE_AVX2],[Define to 1 to support Advanced Vector Extensions 2]) + AH_TEMPLATE([HAVE_AVX512_F],[Define to 1 to support AVX-512 Foundation Extensions]) + AH_TEMPLATE([HAVE_AVX512_CD],[Define to 1 to support AVX-512 Conflict Detection Instructions]) + AH_TEMPLATE([HAVE_AVX512_PF],[Define to 1 to support AVX-512 Conflict Prefetch Instructions]) + AH_TEMPLATE([HAVE_AVX512_ER],[Define to 1 to support AVX-512 Exponential & Reciprocal Instructions]) + AH_TEMPLATE([HAVE_AVX512_VL],[Define to 1 to support AVX-512 Vector Length Extensions]) + AH_TEMPLATE([HAVE_AVX512_BW],[Define to 1 to support AVX-512 Byte and Word Instructions]) + AH_TEMPLATE([HAVE_AVX512_DQ],[Define to 1 to support AVX-512 Doubleword and Quadword Instructions]) + AH_TEMPLATE([HAVE_AVX512_IFMA],[Define to 1 to support AVX-512 Integer Fused Multiply Add Instructions]) + AH_TEMPLATE([HAVE_AVX512_VBMI],[Define to 1 to support AVX-512 Vector Byte Manipulation Instructions]) + AC_SUBST(SIMD_FLAGS) + AC_SUBST(CPUEXT_FLAGS) +]) diff --git a/m4/ax_gcc_x86_avx_xgetbv.m4 b/m4/ax_gcc_x86_avx_xgetbv.m4 new file mode 100644 index 0000000..a57fc5e --- /dev/null +++ b/m4/ax_gcc_x86_avx_xgetbv.m4 @@ -0,0 +1,79 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_gcc_x86_avx_xgetbv.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_GCC_X86_AVX_XGETBV +# +# DESCRIPTION +# +# On later x86 processors with AVX SIMD support, with gcc or a compiler +# that has a compatible syntax for inline assembly instructions, run a +# small program that executes the xgetbv instruction with input OP. This +# can be used to detect if the OS supports AVX instruction usage. +# +# On output, the values of the eax and edx registers are stored as +# hexadecimal strings as "eax:edx" in the cache variable +# ax_cv_gcc_x86_avx_xgetbv. +# +# If the xgetbv instruction fails (because you are running a +# cross-compiler, or because you are not using gcc, or because you are on +# a processor that doesn't have this instruction), +# ax_cv_gcc_x86_avx_xgetbv_OP is set to the string "unknown". +# +# This macro mainly exists to be used in AX_EXT. +# +# LICENSE +# +# Copyright (c) 2013 Michael Petch +# +# 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 . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 3 + +AC_DEFUN([AX_GCC_X86_AVX_XGETBV], +[AC_REQUIRE([AC_PROG_CC]) +AC_LANG_PUSH([C]) +AC_CACHE_CHECK(for x86-AVX xgetbv $1 output, ax_cv_gcc_x86_avx_xgetbv_$1, + [AC_RUN_IFELSE([AC_LANG_PROGRAM([#include ], [ + int op = $1, eax, edx; + FILE *f; + /* Opcodes for xgetbv */ + __asm__ __volatile__ (".byte 0x0f, 0x01, 0xd0" + : "=a" (eax), "=d" (edx) + : "c" (op)); + f = fopen("conftest_xgetbv", "w"); if (!f) return 1; + fprintf(f, "%x:%x\n", eax, edx); + fclose(f); + return 0; +])], + [ax_cv_gcc_x86_avx_xgetbv_$1=`cat conftest_xgetbv`; rm -f conftest_xgetbv], + [ax_cv_gcc_x86_avx_xgetbv_$1=unknown; rm -f conftest_xgetbv], + [ax_cv_gcc_x86_avx_xgetbv_$1=unknown])]) +AC_LANG_POP([C]) +]) diff --git a/m4/ax_gcc_x86_cpuid.m4 b/m4/ax_gcc_x86_cpuid.m4 new file mode 100644 index 0000000..df95465 --- /dev/null +++ b/m4/ax_gcc_x86_cpuid.m4 @@ -0,0 +1,89 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_gcc_x86_cpuid.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_GCC_X86_CPUID(OP) +# AX_GCC_X86_CPUID_COUNT(OP, COUNT) +# +# DESCRIPTION +# +# On Pentium and later x86 processors, with gcc or a compiler that has a +# compatible syntax for inline assembly instructions, run a small program +# that executes the cpuid instruction with input OP. This can be used to +# detect the CPU type. AX_GCC_X86_CPUID_COUNT takes an additional COUNT +# parameter that gets passed into register ECX before calling cpuid. +# +# On output, the values of the eax, ebx, ecx, and edx registers are stored +# as hexadecimal strings as "eax:ebx:ecx:edx" in the cache variable +# ax_cv_gcc_x86_cpuid_OP. +# +# If the cpuid instruction fails (because you are running a +# cross-compiler, or because you are not using gcc, or because you are on +# a processor that doesn't have this instruction), ax_cv_gcc_x86_cpuid_OP +# is set to the string "unknown". +# +# This macro mainly exists to be used in AX_GCC_ARCHFLAG. +# +# LICENSE +# +# Copyright (c) 2008 Steven G. Johnson +# Copyright (c) 2008 Matteo Frigo +# Copyright (c) 2015 Michael Petch +# +# 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 . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 10 + +AC_DEFUN([AX_GCC_X86_CPUID], +[AX_GCC_X86_CPUID_COUNT($1, 0) +]) + +AC_DEFUN([AX_GCC_X86_CPUID_COUNT], +[AC_REQUIRE([AC_PROG_CC]) +AC_LANG_PUSH([C]) +AC_CACHE_CHECK(for x86 cpuid $1 output, ax_cv_gcc_x86_cpuid_$1, + [AC_RUN_IFELSE([AC_LANG_PROGRAM([#include ], [ + int op = $1, level = $2, eax, ebx, ecx, edx; + FILE *f; + __asm__ __volatile__ ("xchg %%ebx, %1\n" + "cpuid\n" + "xchg %%ebx, %1\n" + : "=a" (eax), "=r" (ebx), "=c" (ecx), "=d" (edx) + : "a" (op), "2" (level)); + + f = fopen("conftest_cpuid", "w"); if (!f) return 1; + fprintf(f, "%x:%x:%x:%x\n", eax, ebx, ecx, edx); + fclose(f); + return 0; +])], + [ax_cv_gcc_x86_cpuid_$1=`cat conftest_cpuid`; rm -f conftest_cpuid], + [ax_cv_gcc_x86_cpuid_$1=unknown; rm -f conftest_cpuid], + [ax_cv_gcc_x86_cpuid_$1=unknown])]) +AC_LANG_POP([C]) +]) diff --git a/m4/ax_prepend_flag.m4 b/m4/ax_prepend_flag.m4 new file mode 100644 index 0000000..adac8c5 --- /dev/null +++ b/m4/ax_prepend_flag.m4 @@ -0,0 +1,51 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_prepend_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PREPEND_FLAG(FLAG, [FLAGS-VARIABLE]) +# +# DESCRIPTION +# +# FLAG is added to the front of the FLAGS-VARIABLE shell variable, with a +# space added in between. +# +# If FLAGS-VARIABLE is not specified, the current language's flags (e.g. +# CFLAGS) is used. FLAGS-VARIABLE is not changed if it already contains +# FLAG. If FLAGS-VARIABLE is unset in the shell, it is set to exactly +# FLAG. +# +# NOTE: Implementation based on AX_APPEND_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# Copyright (c) 2011 Maarten Bosmans +# Copyright (c) 2018 John Zaitseff +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 2 + +AC_DEFUN([AX_PREPEND_FLAG], +[dnl +AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_SET_IF +AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[FLAGS])]) +AS_VAR_SET_IF(FLAGS,[ + AS_CASE([" AS_VAR_GET(FLAGS) "], + [*" $1 "*], [AC_RUN_LOG([: FLAGS already contains $1])], + [ + FLAGS="$1 $FLAGS" + AC_RUN_LOG([: FLAGS="$FLAGS"]) + ]) + ], + [ + AS_VAR_SET(FLAGS,[$1]) + AC_RUN_LOG([: FLAGS="$FLAGS"]) + ]) +AS_VAR_POPDEF([FLAGS])dnl +])dnl AX_PREPEND_FLAG diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4 new file mode 100644 index 0000000..1598d07 --- /dev/null +++ b/m4/ax_pthread.m4 @@ -0,0 +1,507 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_pthread.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) +# +# DESCRIPTION +# +# This macro figures out how to build C programs using POSIX threads. It +# sets the PTHREAD_LIBS output variable to the threads library and linker +# flags, and the PTHREAD_CFLAGS output variable to any special C compiler +# flags that are needed. (The user can also force certain compiler +# flags/libs to be tested by setting these environment variables.) +# +# Also sets PTHREAD_CC to any special C compiler that is needed for +# multi-threaded programs (defaults to the value of CC otherwise). (This +# is necessary on AIX to use the special cc_r compiler alias.) +# +# NOTE: You are assumed to not only compile your program with these flags, +# but also to link with them as well. For example, you might link with +# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS +# +# If you are only building threaded programs, you may wish to use these +# variables in your default LIBS, CFLAGS, and CC: +# +# LIBS="$PTHREAD_LIBS $LIBS" +# CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +# CC="$PTHREAD_CC" +# +# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant +# has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to +# that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX). +# +# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the +# PTHREAD_PRIO_INHERIT symbol is defined when compiling with +# PTHREAD_CFLAGS. +# +# ACTION-IF-FOUND is a list of shell commands to run if a threads library +# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it +# is not found. If ACTION-IF-FOUND is not specified, the default action +# will define HAVE_PTHREAD. +# +# Please let the authors know if this macro fails on any platform, or if +# you have any other suggestions or comments. This macro was based on work +# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help +# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by +# Alejandro Forero Cuervo to the autoconf macro repository. We are also +# grateful for the helpful feedback of numerous users. +# +# Updated for Autoconf 2.68 by Daniel Richard G. +# +# LICENSE +# +# Copyright (c) 2008 Steven G. Johnson +# Copyright (c) 2011 Daniel Richard G. +# Copyright (c) 2019 Marc Stevens +# +# 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 . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 27 + +AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) +AC_DEFUN([AX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_REQUIRE([AC_PROG_CC]) +AC_REQUIRE([AC_PROG_SED]) +AC_LANG_PUSH([C]) +ax_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on Tru64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then + ax_pthread_save_CC="$CC" + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"]) + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS]) + AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes]) + AC_MSG_RESULT([$ax_pthread_ok]) + if test "x$ax_pthread_ok" = "xno"; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + CC="$ax_pthread_save_CC" + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items with a "," contain both +# C compiler flags (before ",") and linker flags (after ","). Other items +# starting with a "-" are C compiler flags, and remaining items are +# library names, except for "none" which indicates that we try without +# any flags at all, and "pthread-config" which is a program returning +# the flags for the Pth emulation library. + +ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64 +# (Note: HP C rejects this with "bad form for `-t' option") +# -pthreads: Solaris/gcc (Note: HP C also rejects) +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads and +# -D_REENTRANT too), HP C (must be checked before -lpthread, which +# is present but should not be used directly; and before -mthreads, +# because the compiler interprets this as "-mt" + "-hreads") +# -mthreads: Mingw32/gcc, Lynx/gcc +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case $host_os in + + freebsd*) + + # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) + # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) + + ax_pthread_flags="-kthread lthread $ax_pthread_flags" + ;; + + hpux*) + + # From the cc(1) man page: "[-mt] Sets various -D flags to enable + # multi-threading and also sets -lpthread." + + ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags" + ;; + + openedition*) + + # IBM z/OS requires a feature-test macro to be defined in order to + # enable POSIX threads at all, so give the user a hint if this is + # not set. (We don't define these ourselves, as they can affect + # other portions of the system API in unpredictable ways.) + + AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING], + [ +# if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS) + AX_PTHREAD_ZOS_MISSING +# endif + ], + [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])]) + ;; + + solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (N.B.: The stubs are missing + # pthread_cleanup_push, or rather a function called by this macro, + # so we could check for that, but who knows whether they'll stub + # that too in a future libc.) So we'll check first for the + # standard Solaris way of linking pthreads (-mt -lpthread). + + ax_pthread_flags="-mt,-lpthread pthread $ax_pthread_flags" + ;; +esac + +# Are we compiling with Clang? + +AC_CACHE_CHECK([whether $CC is Clang], + [ax_cv_PTHREAD_CLANG], + [ax_cv_PTHREAD_CLANG=no + # Note that Autoconf sets GCC=yes for Clang as well as GCC + if test "x$GCC" = "xyes"; then + AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG], + [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */ +# if defined(__clang__) && defined(__llvm__) + AX_PTHREAD_CC_IS_CLANG +# endif + ], + [ax_cv_PTHREAD_CLANG=yes]) + fi + ]) +ax_pthread_clang="$ax_cv_PTHREAD_CLANG" + + +# GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) + +# Note that for GCC and Clang -pthread generally implies -lpthread, +# except when -nostdlib is passed. +# This is problematic using libtool to build C++ shared libraries with pthread: +# [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25460 +# [2] https://bugzilla.redhat.com/show_bug.cgi?id=661333 +# [3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=468555 +# To solve this, first try -pthread together with -lpthread for GCC + +AS_IF([test "x$GCC" = "xyes"], + [ax_pthread_flags="-pthread,-lpthread -pthread -pthreads $ax_pthread_flags"]) + +# Clang takes -pthread (never supported any other flag), but we'll try with -lpthread first + +AS_IF([test "x$ax_pthread_clang" = "xyes"], + [ax_pthread_flags="-pthread,-lpthread -pthread"]) + + +# The presence of a feature test macro requesting re-entrant function +# definitions is, on some systems, a strong hint that pthreads support is +# correctly enabled + +case $host_os in + darwin* | hpux* | linux* | osf* | solaris*) + ax_pthread_check_macro="_REENTRANT" + ;; + + aix*) + ax_pthread_check_macro="_THREAD_SAFE" + ;; + + *) + ax_pthread_check_macro="--" + ;; +esac +AS_IF([test "x$ax_pthread_check_macro" = "x--"], + [ax_pthread_check_cond=0], + [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"]) + + +if test "x$ax_pthread_ok" = "xno"; then +for ax_pthread_try_flag in $ax_pthread_flags; do + + case $ax_pthread_try_flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + *,*) + PTHREAD_CFLAGS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\1/"` + PTHREAD_LIBS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\2/"` + AC_MSG_CHECKING([whether pthreads work with "$PTHREAD_CFLAGS" and "$PTHREAD_LIBS"]) + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag]) + PTHREAD_CFLAGS="$ax_pthread_try_flag" + ;; + + pthread-config) + AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) + AS_IF([test "x$ax_pthread_config" = "xno"], [continue]) + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag]) + PTHREAD_LIBS="-l$ax_pthread_try_flag" + ;; + esac + + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include +# if $ax_pthread_check_cond +# error "$ax_pthread_check_macro must be defined" +# endif + static void *some_global = NULL; + static void routine(void *a) + { + /* To avoid any unused-parameter or + unused-but-set-parameter warning. */ + some_global = a; + } + static void *start_routine(void *a) { return a; }], + [pthread_t th; pthread_attr_t attr; + pthread_create(&th, 0, start_routine, 0); + pthread_join(th, 0); + pthread_attr_init(&attr); + pthread_cleanup_push(routine, 0); + pthread_cleanup_pop(0) /* ; */])], + [ax_pthread_ok=yes], + []) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + AC_MSG_RESULT([$ax_pthread_ok]) + AS_IF([test "x$ax_pthread_ok" = "xyes"], [break]) + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + + +# Clang needs special handling, because older versions handle the -pthread +# option in a rather... idiosyncratic way + +if test "x$ax_pthread_clang" = "xyes"; then + + # Clang takes -pthread; it has never supported any other flag + + # (Note 1: This will need to be revisited if a system that Clang + # supports has POSIX threads in a separate library. This tends not + # to be the way of modern systems, but it's conceivable.) + + # (Note 2: On some systems, notably Darwin, -pthread is not needed + # to get POSIX threads support; the API is always present and + # active. We could reasonably leave PTHREAD_CFLAGS empty. But + # -pthread does define _REENTRANT, and while the Darwin headers + # ignore this macro, third-party headers might not.) + + # However, older versions of Clang make a point of warning the user + # that, in an invocation where only linking and no compilation is + # taking place, the -pthread option has no effect ("argument unused + # during compilation"). They expect -pthread to be passed in only + # when source code is being compiled. + # + # Problem is, this is at odds with the way Automake and most other + # C build frameworks function, which is that the same flags used in + # compilation (CFLAGS) are also used in linking. Many systems + # supported by AX_PTHREAD require exactly this for POSIX threads + # support, and in fact it is often not straightforward to specify a + # flag that is used only in the compilation phase and not in + # linking. Such a scenario is extremely rare in practice. + # + # Even though use of the -pthread flag in linking would only print + # a warning, this can be a nuisance for well-run software projects + # that build with -Werror. So if the active version of Clang has + # this misfeature, we search for an option to squash it. + + AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread], + [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG], + [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown + # Create an alternate version of $ac_link that compiles and + # links in two steps (.c -> .o, .o -> exe) instead of one + # (.c -> exe), because the warning occurs only in the second + # step + ax_pthread_save_ac_link="$ac_link" + ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' + ax_pthread_link_step=`$as_echo "$ac_link" | sed "$ax_pthread_sed"` + ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" + ax_pthread_save_CFLAGS="$CFLAGS" + for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do + AS_IF([test "x$ax_pthread_try" = "xunknown"], [break]) + CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS" + ac_link="$ax_pthread_save_ac_link" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], + [ac_link="$ax_pthread_2step_ac_link" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], + [break]) + ]) + done + ac_link="$ax_pthread_save_ac_link" + CFLAGS="$ax_pthread_save_CFLAGS" + AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no]) + ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try" + ]) + + case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in + no | unknown) ;; + *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;; + esac + +fi # $ax_pthread_clang = yes + + + +# Various other checks: +if test "x$ax_pthread_ok" = "xyes"; then + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + AC_CACHE_CHECK([for joinable pthread attribute], + [ax_cv_PTHREAD_JOINABLE_ATTR], + [ax_cv_PTHREAD_JOINABLE_ATTR=unknown + for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], + [int attr = $ax_pthread_attr; return attr /* ; */])], + [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break], + []) + done + ]) + AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \ + test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \ + test "x$ax_pthread_joinable_attr_defined" != "xyes"], + [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], + [$ax_cv_PTHREAD_JOINABLE_ATTR], + [Define to necessary symbol if this constant + uses a non-standard name on your system.]) + ax_pthread_joinable_attr_defined=yes + ]) + + AC_CACHE_CHECK([whether more special flags are required for pthreads], + [ax_cv_PTHREAD_SPECIAL_FLAGS], + [ax_cv_PTHREAD_SPECIAL_FLAGS=no + case $host_os in + solaris*) + ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS" + ;; + esac + ]) + AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \ + test "x$ax_pthread_special_flags_added" != "xyes"], + [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS" + ax_pthread_special_flags_added=yes]) + + AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], + [ax_cv_PTHREAD_PRIO_INHERIT], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[int i = PTHREAD_PRIO_INHERIT; + return i;]])], + [ax_cv_PTHREAD_PRIO_INHERIT=yes], + [ax_cv_PTHREAD_PRIO_INHERIT=no]) + ]) + AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \ + test "x$ax_pthread_prio_inherit_defined" != "xyes"], + [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.]) + ax_pthread_prio_inherit_defined=yes + ]) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + # More AIX lossage: compile with *_r variant + if test "x$GCC" != "xyes"; then + case $host_os in + aix*) + AS_CASE(["x/$CC"], + [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], + [#handle absolute path differently from PATH based program lookup + AS_CASE(["x$CC"], + [x/*], + [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])], + [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])]) + ;; + esac + fi +fi + +test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" + +AC_SUBST([PTHREAD_LIBS]) +AC_SUBST([PTHREAD_CFLAGS]) +AC_SUBST([PTHREAD_CC]) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test "x$ax_pthread_ok" = "xyes"; then + ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) + : +else + ax_pthread_ok=no + $2 +fi +AC_LANG_POP +])dnl AX_PTHREAD diff --git a/m4/ax_require_defined.m4 b/m4/ax_require_defined.m4 new file mode 100644 index 0000000..17c3eab --- /dev/null +++ b/m4/ax_require_defined.m4 @@ -0,0 +1,37 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_require_defined.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_REQUIRE_DEFINED(MACRO) +# +# DESCRIPTION +# +# AX_REQUIRE_DEFINED is a simple helper for making sure other macros have +# been defined and thus are available for use. This avoids random issues +# where a macro isn't expanded. Instead the configure script emits a +# non-fatal: +# +# ./configure: line 1673: AX_CFLAGS_WARN_ALL: command not found +# +# It's like AC_REQUIRE except it doesn't expand the required macro. +# +# Here's an example: +# +# AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG]) +# +# LICENSE +# +# Copyright (c) 2014 Mike Frysinger +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 2 + +AC_DEFUN([AX_REQUIRE_DEFINED], [dnl + m4_ifndef([$1], [m4_fatal([macro ]$1[ is not defined; is a m4 file missing?])]) +])dnl AX_REQUIRE_DEFINED diff --git a/m4/dl.sh b/m4/dl.sh new file mode 100755 index 0000000..08414cf --- /dev/null +++ b/m4/dl.sh @@ -0,0 +1,27 @@ +#!/bin/sh -e +# Copyright (c) 2018-2021, OARC, Inc. +# All rights reserved. +# +# This file is part of dnsjit. +# +# dnsjit is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# dnsjit is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with dnsjit. If not, see . + +m4_files="ax_append_flag.m4 ax_cflags_warn_all.m4 ax_ext.m4 ax_pthread.m4 + ax_require_defined.m4 ax_gcc_x86_avx_xgetbv.m4 ax_gcc_x86_cpuid.m4 + ax_check_compile_flag.m4 ax_prepend_flag.m4 ax_compiler_vendor.m4" + +for ax in $m4_files; do + rm -f "$ax" + wget -O "$ax" "http://git.savannah.gnu.org/gitweb/?p=autoconf-archive.git;a=blob_plain;f=m4/$ax" +done diff --git a/rpm/dnsjit.spec b/rpm/dnsjit.spec new file mode 100644 index 0000000..c2deb72 --- /dev/null +++ b/rpm/dnsjit.spec @@ -0,0 +1,168 @@ +Name: dnsjit +Version: 1.1.0 +Release: 1%{?dist} +Summary: Engine for capturing, parsing and replaying DNS +Group: Productivity/Networking/DNS/Utilities + +License: GPL-3.0-or-later +URL: https://github.com/DNS-OARC/dnsjit +# Source needs to be generated by dist-tools/create-source-packages, see +# https://github.com/jelu/dist-tools +Source0: https://github.com/DNS-OARC/dnsjit/archive/v%{version}.tar.gz?/%{name}_%{version}.orig.tar.gz + +BuildRequires: libpcap-devel +%if 0%{?suse_version} || 0%{?sle_version} +BuildRequires: moonjit-devel >= 2.0.0 +%else +BuildRequires: luajit-devel >= 2.0.0 +%endif +BuildRequires: lmdb-devel +BuildRequires: ck-devel +BuildRequires: gnutls-devel +BuildRequires: libuv-devel +BuildRequires: libnghttp2-devel +BuildRequires: autoconf >= 2.64 +BuildRequires: automake +BuildRequires: libtool + +%description +dnsjit is a combination of parts taken from dsc, dnscap, drool, +and put together around Lua to create a script-based engine for easy +capturing, parsing and statistics gathering of DNS message while also +providing facilities for replaying DNS traffic. + + +%prep +%setup -q -n %{name}_%{version} + + +%build +sh autogen.sh +%configure +make %{?_smp_mflags} + + +%install +rm -rf $RPM_BUILD_ROOT +make install DESTDIR=$RPM_BUILD_ROOT + + +%check +make test + + +%clean +rm -rf $RPM_BUILD_ROOT + + +%files +%defattr(-,root,root) +%{_bindir}/* +%{_datadir}/doc/* +%{_mandir}/man1/* +%{_mandir}/man3/* + + +%changelog +* Wed Feb 03 2021 Jerry Lundström 1.1.0-1 +- Release 1.1.0 + * This releases adds a new module for handling Base64 URLs and new calls + for error handling and opening PCAPs using file descriptors, along with + a bug fix in `lib.getopt` and other changes. + * The `dnssim` module has also gotten its own version and changelog, this + is to prepare it for being moved outside of dnsjit's repository in the + future. + * New modules, calls, features: + - New `lib.base64url`: Utility library to convert data to base64url format + - `core.log`: New call `Log.errstr()`: Convert error number to its text representation + - `input.fpcap`: New call `Fpcap.openfp()`: Open a PCAP file for processing using a file descriptor, for example `io.stdin` + - `output.dnssim`: Support for DNS-over-HTTPS + * Bug fixes: + - `lib.getopt`: Fix bug where `-` and `--` could not be used as arguments to options + * Other changes: + - Fix typo in configure help text + - Add coverage + - `filter.ipsplit`: Extend PRNG modulus to 2^31, new implementation is the same as glibc's `rand()` + - `lib.ip`: Fix typo in documentation + - `output.dnssim`: + - This module now has it's own changelog + - Updated to v20210129 + - Depend on libhttp2 for dnssim DNS-over-HTTPS capabilities + - `output.pcap`: Log libpcap error when failing to open + - SUSE packages now depend on moonjit because of lack of LuaJIT support + * Commits: + d001ccb m4 + 4b63bce output/dnssim: add changelog + 7355810 output/dnssim: add version checks + 95fa6a9 input pcap/fpcap, getopt + 99c3d9f test/test_ipsplit: update to use new PRNG + 3235b09 filter/ipsplit: extend PRNG modulus to 2^31 + 8ff81a0 fixup! input.fpcap: filename "-" reads from stdin + 63cf0a4 output/dnssim: fix regression in DoH GET + 367d0b8 input.pcap: document stdin feature of open_offline() + 8d94504 input.fpcap: filename "-" reads from stdin + 617058e getopt: accept singleton - also as option value + 7d7f17c output/dnssim: unify failed to bind error messages + bdf1517 output/dnssim: add IPv4 support + 15a21da Sonarcloud + ceeea1d SUSE + 1fc3c82 PR179 + 2f5d38f output/dnssim: allow user-set instance log name + b036c68 Info + 0af1ffb Travis, configure + 49bdc08 output/dnssim: implement udp(tcp_fallback) method + b4f9cf9 man: update gitlab.labs.nic.cz to gitlab.nic.cz + 45b977d output/dnssim: update man page + 4184090 output/dnssim: https2 - fix connection closure issues + 342f33e output/dnssim: https2 - omit closing connection inside callback + 67a76d5 output/dnssim: handle all states when closing connection + 41f04d8 output/dnssim: document importance of conn state enum ordering + 795ab6f output/dnssim: tls - fix handling of CONGESTED connections + 8792b32 output/dnssim: match QUESTION section of received responses + 3a88f5b Coverage + 4f611c8 dnssim + 6e35d5b Compile + 63faa44 README, format code, man-page + 925f85e lib: add missing man reference + 9239087 output/dnssim: fix man formatting + bd7bee5 fix lua log levels + 4083efd output/dnssim: fix doc typo + 24c22b8 lib/base64url: add lua bindings + 69be2a1 core/log: add errstr() utility function + 0c14d74 output/dnssim: improve https2() documentation and behaviour + f74e19c output/pcap: log errors when opening output PCAP + 6fe699a output/dnssim: cleanup and nitpicks + 96db8a9 output/https2: handle max_concurrent_streams similar to nghttp2 + 15ea609 output/dnssim: https2 - ensure uri authority is always set + fad3ed6 output/dnssim: https2 - fix some TODOs + 0bee6d8 output/dnssim: https2 - lua documentation + e83e010 output/dnssim: https2 - implement GET method + b553e0f output/dnssim: https2 - configure method + a431a0d contrib: add base64url functions + c753097 output/dnssim: https2 - set default concurrent stream limit + d49f275 output/dnssim: https2 - track number of open streams + 2f7217f output/dnssim: https2 - improve data send edge cases + c0abebc output/dnssim: https2 - return correct error code on send failure + 5b1f6c3 output/dnssim: conn - avoid assert when tearing down failed connections + 5c42266 output/dnssim: exit when file descriptors run out + 1ab2ab6 output/dnssim: https2 - additional asserts to detect invalid data + 4424eb3 output/dnssim: https2 - check response code + 303f2cd output/dnssim: https2 - improve QID mismatch debug msg + 86e3761 output/dnssim: https2 - bugfixes + 4a52f47 output/dnssim: https2 - use more consistent code style for pointers + c8d853e output/dnssim: conn - fix potential memory leak + 3e6038b output/dnssim: https2 - enable zero-ing out msgid + 712634c output/dnssim: https2 - properly match dnsmsg to query from http request + 5abe943 output/dnssim: https2 - free memory on teardown + 39a9e9e output/dnssim: https2 - initial implementation + 058aee2 output/dnssim: https2 - initialize and setup session + 85eb4a3 output/dnssim: https2 - add libnghttp2 dependency + 6712bd6 output/dnssim: https2 - add skeleton +* Thu Jul 23 2020 Jerry Lundström 1.0.0-1 +- Release 1.0.0 +* Tue Jun 04 2019 Jerry Lundström 0.9.8-1 +- Alpha release 0.9.8 +* Fri Jan 25 2019 Jerry Lundström 0.9.7-1 +- Alpha release 0.9.7 +* Wed Aug 01 2018 Jerry Lundström 0.9.6-1 +- Alpha release 0.9.6 diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..96f8b31 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,280 @@ +# Copyright (c) 2018-2021, OARC, Inc. +# All rights reserved. +# +# This file is part of dnsjit. +# +# dnsjit is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# dnsjit is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with dnsjit. If not, see . + +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 . + +-- 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 . + */ + +#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 . + */ + +#include "config.h" + +#include "core/channel.h" +#include "core/assert.h" + +#include + +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 . + */ + +#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 +#include +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include + +#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 . + */ + +#ifndef __dnsjit_core_compat_h +#define __dnsjit_core_compat_h + +#include + +#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 . + +-- 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 . + */ + +#include "config.h" + +#include "core/log.h" + +#include +#include +#include + +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 . + */ + +#ifndef __dnsjit_core_log_h +#define __dnsjit_core_log_h + +#include +#include +#include + +#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 . + */ + +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 . + +-- 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 . + */ + +#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 . + */ + +#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 +#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 . + */ + +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 . + +-- 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 . + */ + +#include "config.h" + +#include "core/object/dns.h" +#include "core/object/payload.h" +#include "core/assert.h" + +#include +#include +#ifdef HAVE_ENDIAN_H +#include +#else +#ifdef HAVE_SYS_ENDIAN_H +#include +#else +#ifdef HAVE_MACHINE_ENDIAN_H +#include +#endif +#endif +#endif +#ifdef HAVE_BYTESWAP_H +#include +#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 . + */ + +#include "core/log.h" +#include "core/object.h" + +#ifndef __dnsjit_core_object_dns_h +#define __dnsjit_core_object_dns_h + +#include +#include + +#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 . + */ + +//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 . + +-- 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 . + +-- 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 "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 . + +-- 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 . + +-- 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 . + */ + +#include "config.h" + +#include "core/object/ether.h" +#include "core/assert.h" + +#include +#include + +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 . + */ + +#include "core/object.h" +#include "core/timespec.h" + +#ifndef __dnsjit_core_object_ether_h +#define __dnsjit_core_object_ether_h + +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "core/object/gre.h" +#include "core/assert.h" + +#include +#include + +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 . + */ + +#include "core/object.h" +#include "core/timespec.h" + +#ifndef __dnsjit_core_object_gre_h +#define __dnsjit_core_object_gre_h + +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "core/object/icmp.h" +#include "core/assert.h" + +#include +#include + +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 . + */ + +#include "core/object.h" +#include "core/timespec.h" + +#ifndef __dnsjit_core_object_icmp_h +#define __dnsjit_core_object_icmp_h + +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "core/object/icmp6.h" +#include "core/assert.h" + +#include +#include + +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 . + */ + +#include "core/object.h" +#include "core/timespec.h" + +#ifndef __dnsjit_core_object_icmp6_h +#define __dnsjit_core_object_icmp6_h + +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "core/object/ieee802.h" +#include "core/assert.h" + +#include +#include + +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 . + */ + +#include "core/object.h" +#include "core/timespec.h" + +#ifndef __dnsjit_core_object_ieee802_h +#define __dnsjit_core_object_ieee802_h + +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "core/object/ip.h" +#include "core/assert.h" + +#include +#include + +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 . + */ + +#include "core/object.h" +#include "core/timespec.h" + +#ifndef __dnsjit_core_object_ip_h +#define __dnsjit_core_object_ip_h + +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "core/object/ip6.h" +#include "core/assert.h" + +#include +#include + +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 . + */ + +#include "core/object.h" +#include "core/timespec.h" + +#ifndef __dnsjit_core_object_ip6_h +#define __dnsjit_core_object_ip6_h + +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "core/object/linuxsll.h" +#include "core/assert.h" + +#include +#include + +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 . + */ + +#include "core/object.h" +#include "core/timespec.h" + +#ifndef __dnsjit_core_object_linuxsll_h +#define __dnsjit_core_object_linuxsll_h + +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "core/object/loop.h" +#include "core/assert.h" + +#include +#include + +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 . + */ + +#include "core/object.h" +#include "core/timespec.h" + +#ifndef __dnsjit_core_object_loop_h +#define __dnsjit_core_object_loop_h + +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "core/object/null.h" +#include "core/assert.h" + +#include +#include + +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 . + */ + +#include "core/object.h" +#include "core/timespec.h" + +#ifndef __dnsjit_core_object_null_h +#define __dnsjit_core_object_null_h + +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "core/object/payload.h" +#include "core/assert.h" + +#include +#include + +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 . + */ + +#include "core/object.h" +#include "core/timespec.h" + +#ifndef __dnsjit_core_object_payload_h +#define __dnsjit_core_object_payload_h + +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "core/object/pcap.h" +#include "core/assert.h" + +#include +#include + +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 . + */ + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "core/object/tcp.h" +#include "core/assert.h" + +#include +#include + +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 . + */ + +#include "core/object.h" +#include "core/timespec.h" + +#ifndef __dnsjit_core_object_tcp_h +#define __dnsjit_core_object_tcp_h + +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "core/object/udp.h" +#include "core/assert.h" + +#include +#include + +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 . + */ + +#include "core/object.h" +#include "core/timespec.h" + +#ifndef __dnsjit_core_object_udp_h +#define __dnsjit_core_object_udp_h + +#include + +#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 . + */ + +//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 . + +-- 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 . + +-- 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 . + */ + +#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 . + */ + +#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 . + */ + +//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 . + +-- 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 . + */ + +#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 . + */ + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "globals.h" +#include "core/assert.h" +#include "core/thread.h" + +#include +#include +#include + +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 . + */ + +#include "core/log.h" + +#ifndef __dnsjit_core_thread_h +#define __dnsjit_core_thread_h + +#include +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#ifndef __dnsjit_core_timespec_h +#define __dnsjit_core_timespec_h + +#include + +#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 . + */ + +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 . + +-- 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 . +.\" +.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 . + */ + +#include "config.h" + +#include "globals.h" +#include "core/log.h" + +#include +#include +#include + +#include +#include +#include +#include + +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 ...\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 . + +-- 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 . + */ + +#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 . + */ + +#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 . + */ + +//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 . + +-- 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 . + */ + +#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 + +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 . + */ + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "filter/layer.h" +#include "core/assert.h" + +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_NET_ETHERNET_H +#include +#endif +#ifdef HAVE_NET_ETHERTYPES_H +#include +#endif +#ifdef HAVE_ENDIAN_H +#include +#else +#ifdef HAVE_SYS_ENDIAN_H +#include +#else +#ifdef HAVE_MACHINE_ENDIAN_H +#include +#endif +#endif +#endif +#ifdef HAVE_BYTESWAP_H +#include +#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 . + */ + +#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 . + */ + +//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 . + +-- 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 . + */ + +#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 . + */ + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "filter/timing.h" +#include "core/assert.h" +#include "core/timespec.h" +#include "core/object/pcap.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +//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 . + +-- 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 ") + 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 . + +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 . +.\"]] + +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 . + */ + +#include "config.h" + +#include "globals.h" + +#include +#include +#include + +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 . + */ + +#ifndef __dnsjit_globals_h +#define __dnsjit_globals_h + +#include + +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 . + +-- 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 . + */ + +#include "config.h" + +#include "input/fpcap.h" +#include "core/assert.h" +#include "core/object/pcap.h" + +#include +#ifdef HAVE_ENDIAN_H +#include +#else +#ifdef HAVE_SYS_ENDIAN_H +#include +#else +#ifdef HAVE_MACHINE_ENDIAN_H +#include +#endif +#endif +#endif +#ifdef HAVE_BYTESWAP_H +#include +#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 + +#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 . + */ + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "input/mmpcap.h" +#include "core/assert.h" +#include "core/object/pcap.h" + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_ENDIAN_H +#include +#else +#ifdef HAVE_SYS_ENDIAN_H +#include +#else +#ifdef HAVE_MACHINE_ENDIAN_H +#include +#endif +#endif +#endif +#ifdef HAVE_BYTESWAP_H +#include +#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 + +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 . + */ + +#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 . + */ + +//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 . + +-- 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 . + */ + +#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 . + */ + +#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 + +#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 . + */ + +#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 . + +-- 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 . + */ + +#include "config.h" + +#include "input/zero.h" +#include "core/assert.h" +#include "core/object/null.h" + +#include + +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 . + */ + +#include "core/log.h" +#include "core/receiver.h" +#include "core/producer.h" + +#ifndef __dnsjit_input_zero_h +#define __dnsjit_input_zero_h + +#include + +#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 . + */ + +//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 . + +-- 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 . + +-- 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. + + 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 . + */ + +#include "lib/base64url.h" + +#include +#include +#include + +/*! \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 . + */ + +#include + +#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. + + 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 . + */ + +/*! + * \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 . + +-- 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 . + */ + +#include "config.h" + +#include "lib/clock.h" + +#include + +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 . + */ + +#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 . + */ + +//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 . + +-- 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 . + +-- 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 . + +-- 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 . + +-- 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. + * + * 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 . + * + * The code originated from https://github.com/fanf2/qp/blob/master/qp.c + * at revision 5f6d93753. + */ + +#include +#include +#include + +#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. + * + * 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 . + */ + +#include +#include +#include + +#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. + * + * 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 . + */ + +/* 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 . + +-- 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 . + +-- 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 . + +-- 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 . + +-- 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 . + */ + +#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 +#include +#include +#include +#ifdef HAVE_ENDIAN_H +#include +#else +#ifdef HAVE_SYS_ENDIAN_H +#include +#else +#ifdef HAVE_MACHINE_ENDIAN_H +#include +#endif +#endif +#endif +#ifdef HAVE_BYTESWAP_H +#include +#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 . + */ + +#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 +#include +#include +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#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 +#include + +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 . + */ + +#include "core/log.h" +#include "core/receiver.h" + +#ifndef __dnsjit_output_dnssim_h +#define __dnsjit_output_dnssim_h + +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "output/dnssim.h" +#include "output/dnssim/internal.h" +#include "output/dnssim/ll.h" +#include "core/assert.h" + +#include + +#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 . + */ + +#include "config.h" + +#include "output/dnssim.h" +#include "output/dnssim/internal.h" +#include "output/dnssim/ll.h" +#include "core/assert.h" + +#include + +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 . + */ + +#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 +#include + +#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 . + */ + +#ifndef __dnsjit_output_dnssim_internal_h +#define __dnsjit_output_dnssim_internal_h + +#include +#include +#include +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include "output/dnssim.h" +#include "output/dnssim/internal.h" +#include "output/dnssim/ll.h" +#include "core/assert.h" + +#include + +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 . + */ + +#include "config.h" + +#include "output/dnssim.h" +#include "output/dnssim/internal.h" +#include "output/dnssim/ll.h" +#include "core/assert.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#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 . + */ + +#include "core/log.h" +#include "core/receiver.h" +#include "core/producer.h" + +#ifndef __dnsjit_output_null_h +#define __dnsjit_output_null_h + +#include +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#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 . + */ + +#include "core/log.h" +#include "core/receiver.h" +#include "core/producer.h" + +#ifndef __dnsjit_output_pcap_h +#define __dnsjit_output_pcap_h + +#include + +#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 . + */ + +#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 . + +-- 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 . + */ + +#include "config.h" + +#include "output/respdiff.h" +#include "core/assert.h" +#include "core/object/payload.h" + +#ifdef HAVE_LMDB_H +#include +#endif +#include +#include +#include + +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 . + */ + +#include "core/log.h" +#include "core/receiver.h" + +#ifndef __dnsjit_output_respdiff_h +#define __dnsjit_output_respdiff_h + +#include + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "output/tcpcli.h" +#include "core/assert.h" +#include "core/object/dns.h" +#include "core/object/payload.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +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 . + */ + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "output/tlscli.h" +#include "core/assert.h" +#include "core/object/dns.h" +#include "core/object/payload.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +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 . + */ + +#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 + +#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 . + */ + +//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 . + +-- 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 . + */ + +#include "config.h" + +#include "output/udpcli.h" +#include "core/assert.h" +#include "core/object/dns.h" +#include "core/object/payload.h" + +#include +#include +#include +#include +#include + +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 . + */ + +#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 +#include + +#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 . + */ + +//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 . + +-- 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 . + +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in +CLEANFILES = test*.log test*.trs test*.out \ + *.pcap-dist + +TESTS = test1.sh test2.sh test3.sh test4.sh test5.sh test6.sh test-ipsplit.sh \ + test-trie.sh test-base64url.sh + +test1.sh: dns.pcap-dist + +test2.sh: dns.pcap-dist + +test3.sh: dns.pcap-dist + +test4.sh: dns.pcap-dist + +test5.sh: dns.pcap-dist + +test6.sh: dns.pcap-dist + +test-ipsplit.sh: pellets.pcap-dist dns.pcap-dist + +test-trie.sh: pellets.pcap-dist dns.pcap-dist + +.pcap.pcap-dist: + cp "$<" "$@" + +EXTRA_DIST = $(TESTS) \ + dns.pcap pellets.pcap test_ipsplit.lua test_trie.lua test_base64url.lua \ + test1.gold test2.gold test3.gold test4.gold diff --git a/src/test/dns.pcap b/src/test/dns.pcap new file mode 100644 index 0000000..a0e585c Binary files /dev/null and b/src/test/dns.pcap differ diff --git a/src/test/pellets.pcap b/src/test/pellets.pcap new file mode 100644 index 0000000..ef39a7b Binary files /dev/null and b/src/test/pellets.pcap differ diff --git a/src/test/test-base64url.sh b/src/test/test-base64url.sh new file mode 100755 index 0000000..163f847 --- /dev/null +++ b/src/test/test-base64url.sh @@ -0,0 +1,20 @@ +#!/bin/sh -e +# Copyright (c) 2020, CZ.NIC, z.s.p.o. +# All rights reserved. +# +# This file is part of dnsjit. +# +# dnsjit is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# dnsjit is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with dnsjit. If not, see . + +../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 . + +../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 . + +../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 . + +../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 . + +../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 . + +../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 . + +../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 . + +../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 . + +../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) -- cgit v1.2.3