diff --git a/.gitignore b/.gitignore index f1f7896..7f5c31b 100644 --- a/.gitignore +++ b/.gitignore @@ -28,9 +28,8 @@ src/stamp-h1 build # Project specific files -src/drool src/drool.1 -src/drool.conf.5 +src/drool-replay.1 src/test/test-suite.log src/test/test*.sh.log src/test/test*.sh.trs diff --git a/.travis.yml b/.travis.yml index 8f4ab54..4b1011b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,13 @@ +addons: + apt: + sources: + - sourceline: 'ppa:dns-oarc/dnsjit-pr' + update: true + packages: + - autoconf + - automake + - dnsjit language: bash -before_install: - - sudo add-apt-repository -y ppa:dns-oarc/dnsjit-pr - - sudo apt-get -qq update - - sudo apt-get install -y autoconf automake dnsjit install: ./autogen.sh script: - ./configure diff --git a/README.md b/README.md index 563be69..f4f5855 100644 --- a/README.md +++ b/README.md @@ -25,38 +25,24 @@ efficacy of subsequent bug fixes. ## Usage example Send all DNS queries twice as fast as found in the PCAP file to localhost -using UDP: +using UDP. ```shell -drool -vv \ - -c 'text:timing multiply 0.5; client_pool target "127.0.0.1" "53"; client_pool sendas udp;' \ - -r file.pcap +drool replay --timing multiply=0.5 --no-tcp file.pcap 127.0.0.1 53 ``` -Only look for DNS queries in TCP traffic and send it to localhost: +Send all DNS queries over TCP to localhost as they were recorded. ```shell -drool -vv \ - -c 'text:filter "tcp"; client_pool target "127.0.0.1" "53";' \ - -r file.pcap +drool replay --timing keep --no-udp file.pcap 127.0.0.1 53 ``` -Listen for DNS queries on eth0 and send them to an (assuming) internal server: +Take all DNS queries found in the PCAP file and send them as fast as possible +over UDP to localhost by ignoring both timings, replies and starting 3 threads +that will simultaneously send queries. ```shell -drool -vv \ - -c 'text:filter "port 53"; client_pool target "172.16.1.2" "53";' \ - -i eth0 -``` - -Take all UDP DNS queries found in the PCAP file and send them as fast as -possible to localhost by ignoring both timings, replies and starting 5 -contexts (threads) that will simultaneously send queries: - -```shell -drool -vv \ - -c 'text:filter "udp"; timing ignore; context client_pools 5; client_pool target "127.0.0.1" "53"; client_pool skip_reply;' \ - -r file.pcap +drool replay --no-tcp --no-responses --threads --udp-threads 3 file.pcap 127.0.0.1 53 ``` ## Dependencies diff --git a/configure.ac b/configure.ac index 5d374d3..14aa959 100644 --- a/configure.ac +++ b/configure.ac @@ -34,9 +34,9 @@ # POSSIBILITY OF SUCH DAMAGE. AC_PREREQ(2.61) -AC_INIT([drool], [1.99.0], [admin@dns-oarc.net], [drool], [https://github.com/DNS-OARC/drool/issues]) +AC_INIT([drool], [1.99.1], [admin@dns-oarc.net], [drool], [https://github.com/DNS-OARC/drool/issues]) AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects]) -AC_CONFIG_SRCDIR([src/main.lua]) +AC_CONFIG_SRCDIR([src/drool.in]) AC_CONFIG_MACRO_DIR([m4]) # Checks for programs. diff --git a/src/Makefile.am b/src/Makefile.am index 57a20ed..89941bd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -34,38 +34,34 @@ # POSSIBILITY OF SUCH DAMAGE. MAINTAINERCLEANFILES = $(srcdir)/Makefile.in -CLEANFILES = drool drool.1 drool.conf.5 +CLEANFILES = drool \ + drool.1 \ + drool-replay.1 SUBDIRS = test EXTRA_DIST = drool.in \ - drool.1.in \ - drool.conf.5.in \ - drool.conf.example + drool.1in \ + drool-replay.1in bin_SCRIPTS = drool -dist_pkgdata_DATA = conf.lua log.lua run.lua main.lua +droollibdir = $(pkglibdir)/drool +dist_droollib_DATA = lib/drool/replay.lua -man1_MANS = drool.1 -man5_MANS = drool.conf.5 +man1_MANS = drool.1 \ + drool-replay.1 drool: drool.in Makefile sed -e 's,[@]PACKAGE_NAME[@],$(PACKAGE_NAME),g' \ -e 's,[@]PACKAGE_VERSION[@],$(PACKAGE_VERSION),g' \ -e 's,[@]PACKAGE_URL[@],$(PACKAGE_URL),g' \ -e 's,[@]PACKAGE_BUGREPORT[@],$(PACKAGE_BUGREPORT),g' \ - -e 's,[@]pkgdatadir[@],$(pkgdatadir),g' \ + -e 's,[@]pkglibdir[@],$(pkglibdir),g' \ < "$(srcdir)/drool.in" > drool chmod +x drool -drool.1: drool.1.in Makefile +.1in.1: sed -e 's,[@]PACKAGE_VERSION[@],$(PACKAGE_VERSION),g' \ -e 's,[@]PACKAGE_URL[@],$(PACKAGE_URL),g' \ -e 's,[@]PACKAGE_BUGREPORT[@],$(PACKAGE_BUGREPORT),g' \ - < "$(srcdir)/drool.1.in" > drool.1 - -drool.conf.5: drool.conf.5.in Makefile - sed -e 's,[@]PACKAGE_VERSION[@],$(PACKAGE_VERSION),g' \ - -e 's,[@]PACKAGE_URL[@],$(PACKAGE_URL),g' \ - -e 's,[@]PACKAGE_BUGREPORT[@],$(PACKAGE_BUGREPORT),g' \ - < "$(srcdir)/drool.conf.5.in" > drool.conf.5 + < "$<" > "$@" diff --git a/src/conf.lua b/src/conf.lua deleted file mode 100644 index b26e10e..0000000 --- a/src/conf.lua +++ /dev/null @@ -1,70 +0,0 @@ -conf:func("log", enable_log) -conf:func("nolog", disable_log) -conf:func("read", function(_, file) - table.insert(files, file) -end) -conf:func("input", function(_, interface) - table.insert(interfaces, interface) -end) -conf:func("filter", function(_, arg1) - bpf = arg1 -end) -conf:func("timing", function(_, mode, arg1) - if mode == "ignore" then - return - elseif mode == "keep" or mode == "best_effort" or mode == "increase" or mode == "reduce" or mode == "multiply" then - timing_arg = arg1 - else - L:fatal("unknown timing mode: %s", mode) - end -end) -conf:func("context", function(_, option, arg1) - if option == "client_pools" then - client_pools = arg1 - else - L:fatal("unknown context option: %s", option) - end -end) -conf:func("client_pool", function(_, option, arg1, arg2) - if option == "target" then - host = arg1 - port = arg2 - elseif option == "max_clients" then - max_clients = arg1 - elseif option == "client_ttl" then - client_ttl = arg1 - elseif option == "skip_reply" then - skip_reply = true - elseif option == "max_reuse_clients" then - max_reuse_clients = arg1 - elseif option == "sendas" then - sendas = arg1 - else - L:fatal("unknown client_pool option: %s", option) - end -end) -conf:func("backend", function(_, section, module) - if section == "input" then - if module == "pcapthread" then - backend.input = "pcapthread" - elseif module == "fpcap" then - backend.input = "fpcap" - elseif module == "mmpcap" then - backend.input = "mmpcap" - elseif module == "pcap" then - backend.input = "pcap" - else - L:fatal("unknown backend input: %s", module) - end - elseif section == "output" then - if module == "cpool" then - backend.output = "cpool" - elseif module == "udpcli" then - backend.output = "udpcli" - else - L:fatal("unknown backend output: %s", module) - end - else - L:fatal("unknown backend section: %s", section) - end -end) diff --git a/src/drool-replay.1in b/src/drool-replay.1in new file mode 100644 index 0000000..56c5fac --- /dev/null +++ b/src/drool-replay.1in @@ -0,0 +1,160 @@ +.\" DNS Reply Tool (drool) +.\" +.\" Copyright (c) 2017-2018, OARC, Inc. +.\" Copyright (c) 2017, Comcast Corporation +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" 3. Neither the name of the copyright holder nor the names of its +.\" contributors may be used to endorse or promote products derived +.\" from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +.\" FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +.\" COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +.\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +.\" CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +.\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.TH drool 1 "@PACKAGE_VERSION@" "DNS Replay Tool" +.SH NAME +drool \- DNS Replay Tool +.SH SYNOPSIS +.B drool replay +[ +.I options +] +.B file +.B host +.B port +.SH DESCRIPTION +\fBdrool\fR can replay DNS traffic from packet capture (PCAP) files and send +it to a specified server, with options such as to manipulate the timing +between packets, as well as loop packets infinitely or for a set number +of iterations. +This tool's goal is to be able to produce a high amount of UDP packets per +second and TCP sessions per second on common hardware. + +The purpose can be to simulate Distributed Denial of Service (DDoS) attacks +on the DNS and measure normal DNS querying. +For example, the tool could enable you to take a snapshot of a DDoS and be +able to replay it later to test if new code or hardening techniques are +useful, safe & effective. +Another example is to be able to replay a packet stream for a bug that is +sequence- and/or timing-related in order to validate the efficacy of +subsequent bug fixes. +.SH OPTIONS +These options are specific for the +.B replay +command, see +.IR drool (1) +for generic options. +.TP +.B \-D +Show DNS queries and responses as processing goes. +.TP +.B \-n \-\-no\-responses +Do not wait for responses before sending next request. +.TP +.B \-\-no\-tcp +Do not use TCP. +.TP +.B \-\-no\-udp +Do not use UDP. +.TP +.B \-T \-\-threads +Use threads. +.TP +.B \-\-tcp\-threads N +Set the number of TCP threads to use, default 2. +.TP +.B \-\-udp\-threads N +Set the number of UDP threads to use, default 4. +.TP +.B \-\-timeout N.N +Set timeout for waiting on responses [seconds.nanoseconds], default 10.0. +.TP +.B \-t \-\-timing mode[=option] +Set the timing mode, see TIMING MODES. +.SH EXAMPLES +.TP +.B drool replay \-\-timing multiply=0.5 \-\-no\-tcp file.pcap 127.0.0.1 53 + +Send all DNS queries twice as fast as found in the PCAP file to localhost +using UDP. +.TP +.B drool replay \-\-timing keep \-\-no\-udp file.pcap 127.0.0.1 53 + +Send all DNS queries over TCP to localhost as they were recorded. +.TP +.B drool replay \-\-no\-tcp \-\-no\-responses \-\-threads \-\-udp\-threads 3 file.pcap 127.0.0.1 53 + +Take all DNS queries found in the PCAP file and send them as fast as possible +over UDP to localhost by ignoring both timings, replies and starting 3 threads +that will simultaneously send queries. +.SH TIMING MODES +.TP +.B ignore +Set the timing mode to ignore all timings and try to send traffic as fast +as possible (default). +.TP +.B keep +Set the timing mode to try and keep up with interval between the traffic +received. +.TP +.B add= +Set the timing mode to add the given nanoseconds to the interval between +the traffic received. +.TP +.B reduce= +Set the timing mode to reduce the interval between the traffic received +with the given nanoseconds. +.TP +.B multiply= +Set the timing mode to multiply the interval between the traffic received, +this can be thought as percent with 1.00 being 100% of the interval, 2.00 +being 200%, 0.10 being 10% and so on. +.TP +.B fixed= +Set the timing between packets to the given nanoseconds. +.SH SEE ALSO +drool(1) +.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/drool.1.in b/src/drool.1.in deleted file mode 100644 index ca10010..0000000 --- a/src/drool.1.in +++ /dev/null @@ -1,308 +0,0 @@ -.\" DNS Reply Tool (drool) -.\" -.\" Copyright (c) 2017-2018, OARC, Inc. -.\" Copyright (c) 2017, Comcast Corporation -.\" All rights reserved. -.\" -.\" Redistribution and use in source and binary forms, with or without -.\" modification, are permitted provided that the following conditions -.\" are met: -.\" -.\" 1. Redistributions of source code must retain the above copyright -.\" notice, this list of conditions and the following disclaimer. -.\" -.\" 2. Redistributions in binary form must reproduce the above copyright -.\" notice, this list of conditions and the following disclaimer in -.\" the documentation and/or other materials provided with the -.\" distribution. -.\" -.\" 3. Neither the name of the copyright holder nor the names of its -.\" contributors may be used to endorse or promote products derived -.\" from this software without specific prior written permission. -.\" -.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -.\" FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -.\" COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -.\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -.\" CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -.\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -.\" POSSIBILITY OF SUCH DAMAGE. -.\" -.TH drool 1 "@PACKAGE_VERSION@" "DNS Replay Tool" -.SH NAME -drool \- DNS Replay Tool -.SH SYNOPSIS -.B drool -[ -.B \-c -[ -.I type -: ] -.I config -] -[ -.B \-l -.I facility -[ : -.I level -] -] -[ -.B \-L -.I facility -[ : -.I level -] -] -[ -.B \-f -.I filter -] -[ -.B \-i -.I interface -] -[ -.B \-r -.I file.pcap -] -[ -.B \-R -.I mode -] -.\" [ -.\" .B \-o -.\" .I interface -.\" ] -.\" [ -.\" .B \-w -.\" .I file.pcap -.\" ] -[ -.B \-nvhV -] -.SH DESCRIPTION -\fBdrool\fR can replay DNS traffic from packet capture (PCAP) files and send -it to a specified server, with options such as to manipulate the timing -between packets, as well as loop packets infinitely or for a set number -of iterations. -This tool's goal is to be able to produce a high amount of UDP packets per -second and TCP sessions per second on common hardware. - -The purpose can be to simulate Distributed Denial of Service (DDoS) attacks -on the DNS and measure normal DNS querying. -For example, the tool could enable you to take a snapshot of a DDoS and be -able to replay it later to test if new code or hardening techniques are -useful, safe & effective. -Another example is to be able to replay a packet stream for a bug that is -sequence- and/or timing-related in order to validate the efficacy of -subsequent bug fixes. - -Most of the functionality of this tool is controlled by the configuration -used, please see -.BR drool.conf (5) -for more information. -.SH DESIGN - - input -> context -+-> client pool -> target - +-> client pool -> target - +-> client pool -> target - -Each file read or interface listen on is a context and runs in its own -thread, parses the input for DNS queries and puts them into a queue within -the context. - -Each context can have one or more client pools. - -Each client pool runs in its own thread, waits for DNS queries on the context -queue and sends them to the target. -.SH CURRENT IMPLEMENTATION LIMITATIONS -See -.BR drool.conf (5) -for the current implementation limitations that exists. -.SH OPTIONS -.TP -\fB\-c\fR [\fItype\fR:]\fIconfig\fR -Specify the configuration to use, if no \fItype\fR is given then -\fIconfig\fR expects to be a file. -Valid types are \fBfile\fR and \fBtext\fR. -Can be given multiple times and will be processed in the given order. -See -.BR drool.conf (5) -for configuration syntax. -.TP -\fB\-l\fR \fIfacility\fR[:\fIlevel\fR] -Enable logging for \fIfacility\fR, optional log \fIlevel\fR can be given -to enable just that. -Can be given multiple times and will be processed in the given order. -See LOGGING for more information. -.TP -\fB\-L\fR \fIfacility\fR[:\fIlevel\fR] -Disable logging for \fIfacility\fR, optional log \fIlevel\fR can be given -to disable just that. -Can be given multiple times and will be processed in the given order. -See LOGGING for more information. -.TP -\fB\-f\fR \fIfilter\fR -Set the Berkeley Packet Filter to use. -.TP -\fB\-i\fR \fIinterface\fR -Capture packets from interface, can be given multiple times. -.TP -\fB\-r\fR \fIfile.pcap\fR -Read packets from PCAP file, can be given multiple times. -.TP -\fB\-R\fR \fImode\fR -Specify the mode for reading PCAP files, see READ MODES for available modes. -.TP -.\" \fB\-o\fR \fIinterface\fR -.\" todo -.\" .TP -.\" \fB\-w\fR \fIfile.pcap\fR -.\" todo -.\" .TP -.B \-n -Dry run mode, do not allocate any outbound sockets or generate any -network traffic. -.TP -.B \-v -Enable verbose, a simple way to enable logging. -Can be given multiple times to increase verbosity level. -.TP -.B \-h -Print help and exit. -.TP -.B \-V -Print version and exit. -.SH LOGGING -Logging is enabled and disabled in the order specified on the command line -which allows for enabling of all logging and disabling of specific, for -example: - - drool -l all -L network:debug - -The following logging facilities exists: -.TP -\fBcore\fR -Log messages about initializing, configuration and start up. -.TP -\fBinput\fR -Log messages about input related tasks. -.TP -\fBprocessing\fR -Log messages about processing related tasks. -.TP -\fBnetwork\fR -Log messages about network related tasks. -.TP -\fBall\fR -Log messages for all facilities, this is only used to configure logging. -.LP -The following logging level exists for all facilities: -.TP -\fBdebug\fR -Log messages about the very inner workings, use with caution since it -generates a lot of messages. -.TP -\fBinfo\fR -Log messages of the informational kind that may not be interesting in normal -operation. -.TP -\fBnotice\fR -Log messages of the informational kind that may be interesting in normal -operation. -.TP -\fBwarning\fR -Log message of the warning kind that indicates possible disruption in -operation. -.TP -\fBcritical\fR -Log messages of the error kind that will most likely result in termination -of operation. -This log level can not be silenced. -.TP -\fBfatal\fR -Log messages of the critical kind that indicates termination of operation. -This log level can not be silenced. -.TP -\fBall\fR -Log messages for all levels, this is only used to configure logging. -.SH READ MODES -.TP -\fBloop\fR -Loop the given file(s) until interrupted. -.TP -\fBiter\fR:\fInumber\fR -Iterate the given file(s) for \fInumber\fR of times. -.SH EXITING -\fBdrool\fR will exit once processing of PCAP files is complete or if -interrupted (CTRL-C or SIGINT). -If any interface is being processed or if loop read mode is being used, -then \fBdrool\fR must be interrupted in order to exit. - -\fBdrool\fR can be forcefully exited by interrupting (CTRL-C or SIGINT) twice. -.SH EXIT VALUES -0 \- no error -.br -1 \- generic error -.br -2 \- unknown or invalid option -.br -3 \- conf file error -.br -4 \- signal setup or handling error -.br -5 \- signal received -.br -6 \- pcap-thread error -.br -7 \- out of memory -.SH EXAMPLES -.TP -\fBdrool \-vv \-c 'text:timing multiply 0.5; client_pool target "127.0.0.1" "53"; client_pool sendas udp;' \-r file.pcap\fR - -Send all DNS queries twice as fast as found in the PCAP file to localhost -using UDP. -.TP -\fBdrool \-vv \-c 'text:filter "tcp"; client_pool target "127.0.0.1" "53";' \-r file.pcap\fR - -Only look for DNS queries in TCP traffic and send it to localhost. -.TP -\fBdrool \-vv \-c 'text:filter "port 53"; client_pool target "172.16.1.2" "53";' \-i eth0\fR - -Listen for DNS queries on eth0 and send them to an (assuming) internal server. -.TP -\fBdrool \-vv \-c 'text:filter "udp"; timing ignore; context client_pools 5; client_pool target "127.0.0.1" "53"; client_pool skip_reply;' \-r file.pcap\fR - -Take all UDP DNS queries found in the PCAP file and send them as fast as -possible to localhost by ignoring both timings, replies and starting 5 -contexts (threads) that will simultaneously send queries. -.SH SEE ALSO -drool.conf(5) -.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/drool.1in b/src/drool.1in new file mode 100644 index 0000000..347d8f0 --- /dev/null +++ b/src/drool.1in @@ -0,0 +1,118 @@ +.\" DNS Reply Tool (drool) +.\" +.\" Copyright (c) 2017-2018, OARC, Inc. +.\" Copyright (c) 2017, Comcast Corporation +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" 3. Neither the name of the copyright holder nor the names of its +.\" contributors may be used to endorse or promote products derived +.\" from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +.\" FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +.\" COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +.\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +.\" CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +.\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.TH drool 1 "@PACKAGE_VERSION@" "DNS Replay Tool" +.SH NAME +drool \- DNS Replay Tool +.SH SYNOPSIS +.B drool command +[ +.I options +] < +.I arguments +> +.SH DESCRIPTION +.B drool +can replay DNS traffic from packet capture (PCAP) files and send +it to a specified server, with options such as to manipulate the timing +between packets, as well as loop packets infinitely or for a set number +of iterations. +This tool's goal is to be able to produce a high amount of UDP packets per +second and TCP sessions per second on common hardware. + +The purpose can be to simulate Distributed Denial of Service (DDoS) attacks +on the DNS and measure normal DNS querying. +For example, the tool could enable you to take a snapshot of a DDoS and be +able to replay it later to test if new code or hardening techniques are +useful, safe & effective. +Another example is to be able to replay a packet stream for a bug that is +sequence- and/or timing-related in order to validate the efficacy of +subsequent bug fixes. +.SH COMMANDS +.B drool +is divided into various commands for different scenarios. +Each command has it's own man-page, may take different arguments and may +have additional options. +.TP +.B replay +Replay DNS from a PCAP file, see +.IR drool-replay (1). +.SH OPTIONS +These options are generic for all +.B drool +commands. +.TP +.B \-\-csv +Output statistics as CSV. +.TP +.B \-\-json +Output statistics as JSON. +.TP +.B \-v +Enable verbose logging, can be given multiple times to increase verbosity level. +.TP +.B \-h +Print help and exit. +.TP +.B \-V +Print version and exit. +.SH EXIT VALUES +0 \- no error +.br +1 \- generic error +.SH SEE ALSO +drool-replay(1) +.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 +.I @PACKAGE_URL@ +.RE +.LP +For question and help please use: +.LP +.RS +.I @PACKAGE_BUGREPORT@ +.RE +.LP diff --git a/src/drool.conf.5.in b/src/drool.conf.5.in deleted file mode 100644 index 93faf28..0000000 --- a/src/drool.conf.5.in +++ /dev/null @@ -1,296 +0,0 @@ -.\" DNS Reply Tool (drool) -.\" -.\" Copyright (c) 2017-2018, OARC, Inc. -.\" Copyright (c) 2017, Comcast Corporation -.\" All rights reserved. -.\" -.\" Redistribution and use in source and binary forms, with or without -.\" modification, are permitted provided that the following conditions -.\" are met: -.\" -.\" 1. Redistributions of source code must retain the above copyright -.\" notice, this list of conditions and the following disclaimer. -.\" -.\" 2. Redistributions in binary form must reproduce the above copyright -.\" notice, this list of conditions and the following disclaimer in -.\" the documentation and/or other materials provided with the -.\" distribution. -.\" -.\" 3. Neither the name of the copyright holder nor the names of its -.\" contributors may be used to endorse or promote products derived -.\" from this software without specific prior written permission. -.\" -.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -.\" FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -.\" COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -.\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -.\" CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -.\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -.\" POSSIBILITY OF SUCH DAMAGE. -.\" -.TH drool.conf 5 "@PACKAGE_VERSION@" "DNS Replay Tool Configuration" -.SH NAME -drool.conf \- Configuration for DNS Replay Tool -.SH DESCRIPTION -With these configuration options for \fIdrool\fR you can control all -aspects of generating/replaying DNS traffic. -Some options are also available as command line options. -.SH CURRENT IMPLEMENTATION LIMITATIONS -These are the current limitations in the implementation and may be changed -or removed in the future: -.TP -- Same configuration for each \fBread\fR and \fBinput\fR -Currently the configuration for \fBfilter\fR, \fBtiming\fR, \fBcontext\fR -and \fBclient_pool\fR affects all \fBread\fR and \fBinput\fR. -In the future this may be split up so that each \fBread\fR and \fBinput\fR -can have different configuration. -.TP -- Only one \fBclient_pool target\fR -Currently there can only be one target for all queries. -In the future this may be change to be able to give multiple targets and -different ways to distribute the queries amongst them. -.TP -- Send from IP/interface -Currently there is no way to specify from where the queries should be sent, -instead it is up to the OS to decide that. -In the future there will be ways to specify this, sending from different -interfaces and/or ranges of IP addresses. -.SH CONFIGURATION -.TP -\fBlog\fR FACILITY [ LEVEL ] ; -Enable logging for \fIfacility\fR, optional log \fIlevel\fR can be given -to enable just that. -Can be given multiple times and will be processed in the given order. -See LOGGING for more information. -.TP -\fBnolog\fR FACILITY [ LEVEL ] ; -Disable logging for \fIfacility\fR, optional log \fIlevel\fR can be -given to disable just that. -Can be given multiple times and will be processed in the given order. -See LOGGING for more information. -.TP -\fBbackend\fR SECTION MODULE ; -Set which backend module to use for each section. -See BACKEND for more information. -.TP -\fBread\fR " FILE " ; -Read packets from PCAP file, can be given multiple times. -Each \fBread\fR counts as a context and will use the given configuration -for starting with \fBcontext\fR. -.TP -\fBinput\fR " INTERFACE " ; -Capture packets from interface, can be given multiple times. -Each \fBinput\fR counts as a context and will use the given configuration -for starting with \fBcontext\fR. -.TP -\fBfilter\fR " FILTER " ; -Set the Berkeley Packet Filter to use, this will be applied on all -\fBread\fR and \fBinput\fR. -.TP -\fBtiming\fR ignore ; -Set the timing mode to ignore all timings and try to send traffic as fast -as possible. -.TP -\fBtiming\fR keep ; -Set the timing mode to try and keep up with interval between the traffic -received, will give a warning if it can't keep up sending it out at the -same rate. -This is the default timing mode if none is specified. -.TP -\fBtiming\fR best_effort ; -This mode is deprecated and is the same as -.IR keep . -Will be removed in future major release. -.TP -\fBtiming\fR add NANOSECONDS ; -Set the timing mode to add the given nanoseconds to the interval between -the traffic received. -.TP -\fBtiming\fR reduce NANOSECONDS ; -Set the timing mode to reduce the interval between the traffic received -with the given nano seconds, will give a warning if it can't keep up -sending it out at the new rate. -.TP -\fBtiming\fR multiply MULTIPLY_AS_FLOAT ; -Set the timing mode to multiply the interval between the traffic received, -this can be thought as percent with 1.00 being 100% of the interval, 2.00 -being 200%, 0.10 being 10% and so on. -Will give a warning if it can't keep up sending it out at the new rate. -.TP -\fBcontext\fR client_pools NUMBER ; -Set the number of client pools to use per context (a context is a \fIread\fR -or \fIinput\fR), default is 1. -Each client pool will spin up a thread for sending and receiving and will use -the given configuration starting with \fBclient_pool\fR. -As each \fIread\fR and \fIinput\fR will each use this amount of client pools -it may be good that the total amount does not exceed the number of CPU cores -available. -.TP -\fBclient_pool\fR target " IP_OR_HOSTNAME " " SERVICE_OR_PORT "; -Set the target of the client pools, where to send the traffic. -.TP -\fBclient_pool\fR max_clients NUMBER ; -Set the maximum clients each client pool can have, default 100. Each client -will use a TCP or UDP port depending on the traffic read/captured, -\fBfilter\fR and \fBclient_pool sendas\fR. -The total amount of ports used will be this value * number of \fIread\fR -and \fIinput\fR * \fBcontext client_pools\fR and it is advised to keep this -within the available ports/file descriptors for the running user. -If used together with backend -.I udpcli -then this will set the number of threads to use and run one client per thread. -.TP -\fBclient_pool\fR client_ttl SECONDS_AS_FLOAT ; -Set the time a client lives for, this is used to timeout clients in case -the target does not answer. -Default value is 0.05. -Specified as a float meaning 0.2 would be 200 milliseconds. -.TP -\fBclient_pool\fR skip_reply ; -Enables a mode where replies are ignored and client is considered successful -after just sending. -.TP -\fBclient_pool\fR max_reuse_clients NUMBER ; -Set the maximum clients to save for reuse, this only applies to protocol that -can be reused. -Default value is 20. -.TP -\fBclient_pool\fR sendas < original | udp | tcp > ; -Set how to send the traffic, default is original which sends the traffic with -the same protocol it was received with. -.\" .TP -.\" \fBwrite\fR " FILE " ; -.\" todo -.\" .TP -.\" \fBoutput\fR " INTERFACE " ; -.\" todo -.SH LOGGING -Logging is enabled and disabled in the order specified in the configuration -which allows for enabling of all logging and disabling of specific, for -example: - - log all; - nolog network debug; - -The following logging facilities exists: -.TP -.B core -Log messages about initializing, configuration and start up. -.TP -.B input -Log messages about input related tasks. -.TP -.B processing -Log messages about processing related tasks. -.TP -.B network -Log messages about network related tasks. -.TP -.B all -Log messages for all facilities, this is only used to configure logging. -.LP -The following logging level exists for all facilities: -.TP -.B debug -Log messages about the very inner workings, use with caution since it -generates a lot of messages. -.TP -.B info -Log messages of the informational kind that may not be interesting in normal -operation. -.TP -.B notice -Log messages of the informational kind that may be interesting in normal -operation. -.TP -.B warning -Log message of the warning kind that indicates possible disruption in -operation. -.TP -.B critical -Log messages of the error kind that will most likely result in termination -of operation. -This log level can not be silenced. -.TP -.B fatal -Log messages of the critical kind that indicates termination of operation. -This log level can not be silenced. -.TP -.B all -Log messages for all levels, this is only used to configure logging. -.SH BACKEND -Following backend modules exists, note that some modules does not work with -each other and configuration options may not be available or mean different -things. -.SS SECTION: input -.TP -.B pcapthread -Default input that uses pcap-thread helper library to read from interfaces -and PCAP files. -.TP -.B fpcap -Read input from a PCAP file using standard library function -.B fopen() -and parse the PCAP without libpcap. -Following configuration options does not work: -.B filter -and -.BR timing . -.TP -.B mmpcap -Read input from a PCAP file by mapping the whole file to memory using -.B mmap() -and parse the PCAP without libpcap. -Following configuration options does not work: -.B filter -and -.BR timing . -.TP -.B pcap -Read input from an interface or PCAP file using libpcap. -Following configuration options does not work: -.B filter -and -.BR timing . -.SS SECTION: output -.TP -.B cpool -Send queries to a target by emulating clients (a client pool). -.TP -.B udpcli -Simple and dumb UDP DNS client. -Following configuration options does not work: -.B context -and all -.B client_pool -but -.BR max_clients . -.SH SEE ALSO -drool(1) -.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/drool.conf.example b/src/drool.conf.example deleted file mode 100644 index ffaf121..0000000 --- a/src/drool.conf.example +++ /dev/null @@ -1,49 +0,0 @@ -log all; -nolog network debug; - -#backend input pcapthread; -#backend input fpcap; -#backend input mmpcap; -#backend input pcap; -#backend output cpool; -#backend output udpcli; - -filter "udp port 53"; - -timing ignore; -#timing keep; -#timing add 100; -#timing reduce 200; -#timing multiply 1.5; - -#context client_pools 1; - -client_pool target "127.0.0.1" "53"; -client_pool max_clients 100; -client_pool client_ttl 0.05; -#client_pool skip_reply; -#client_pool max_reuse_clients 20; -#client_pool sendas tcp; - -# Not working yet: -#write "/path/to/file.pcap"; -#output "eth1"; - -# Experimental / unimplemented / unsupported: -#read once ""; -#read sequential ""; -#read times "" 5; -#read loop ""; -#sequential "/path/to/file1.pcap"; -#sequential "/path/to/file2.pcap"; -#sequential "/path/to/file3.pcap"; -#loop "/path/to/file.pcap"; -#change ip from[:port[-range]] to[:port[-range]]; -#client context; ?? -#client ip ip[:port[-range]] -#client net ip mask [port[-range]]; -#client maximum ; - -# Removed / disabled: -#read "/path/to/file.pcap"; -#input "eth0"; diff --git a/src/drool.in b/src/drool.in index 1ae7d53..c41ee8d 100644 --- a/src/drool.in +++ b/src/drool.in @@ -1,110 +1,144 @@ #!/usr/bin/env dnsjit +-- DNS Reply Tool (drool) +-- +-- Copyright (c) 2017-2018, OARC, Inc. +-- Copyright (c) 2017, Comcast Corporation +-- All rights reserved. +-- +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions +-- are met: +-- +-- 1. Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- +-- 2. Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in +-- the documentation and/or other materials provided with the +-- distribution. +-- +-- 3. Neither the name of the copyright holder nor the names of its +-- contributors may be used to endorse or promote products derived +-- from this software without specific prior written permission. +-- +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +-- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +-- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +-- FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +-- COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +-- INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +-- BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +-- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +-- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +-- ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +-- POSSIBILITY OF SUCH DAMAGE. +package.path = package.path .. ";@pkglibdir@/?.lua" -log = require("dnsjit.core.log") -clock = require("dnsjit.lib.clock") -L = log.new("drool") -conf = require("dnsjit.lib.parseconf").new() -getopt = nil +local clock = require("dnsjit.lib.clock") +local log = require("dnsjit.core.log").new("drool") +local getopt = require("dnsjit.lib.getopt").new({ + { "v", "verbose", 0, "Enable and increase verbosity for each time given", "?+" }, + { nil, "json", false, "Output statistics as JSON", "?" }, + { nil, "csv", false, "Output statistics as CSV", "?" }, +}) +getopt.usage_desc = arg[1] .. " command [options...] " -files = {} -interfaces = {} -bpf = nil -client_pools = nil -host = nil -port = nil -max_clients = nil -client_ttl = nil -skip_reply = nil -max_reuse_clients = nil -sendas = nil -timing_mode = nil -timing_arg = nil -facility_log = { - input = {}, - processing = {}, - network = {}, +local cmds = { + replay = "drool.replay", + mirror = "drool.mirror", + respdiff = "drool.respdiff", } -read_iter = 1 -read_mode = false -program = "@PACKAGE_NAME@" -version = "@PACKAGE_VERSION@" -package_url = "@PACKAGE_URL@" -pkgdatadir = "@pkgdatadir@" -backend = { - input = "pcapthread", - output = "cpool", -} - -io.stderr:write("<< "..program.." v"..version.." "..package_url.." >>\n") -function args() - getopt = require("dnsjit.lib.getopt").new({ - { "c", nil, "", "", "?+" }, - { "l", nil, "", "", "?+" }, - { "L", nil, "", "", "?+" }, - { "f", nil, "", "", "?" }, - { "i", nil, "", "", "?+" }, - { "r", nil, "", "", "?+" }, - { "R", nil, "", "", "?" }, - { "n", nil, "", "", "?" }, - { "v", nil, 0, "", "?+" }, - { "V", nil, false, "", "?" }, - { nil, "pkgdatadir", pkgdatadir, "", "?" }, - }) - arg = getopt:parse() +if not arg[2] or not cmds[arg[2]] then + getopt:parse() - 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") + if getopt:val("help") then + getopt:usage() + os.exit(0) end - if v > 3 then - log.enable("debug") + if arg[2] then + log:critical("Command "..arg[2].." not found") end - if getopt:val("help") then - print("usage: "..program.." [options]") - print[[ + getopt:usage() + os.exit(-1) +end - -c [type:]config - Specify the configuration to use, if no type is given then - config expects to be a file. Valid types are file and text. - Can be given multiple times and will be processed in the - given order. See drool.conf(5) for configuration syntax. - -l facility[:level] - Enable logging for facility, optional log level can be given - to enable just that. Can be given multiple times and will be - processed in the given order. See drool(1) for available - facilities and log levels. - -L facility[:level] - Same as -l but to disable the given facility and log level. - -f filter Set the Berkeley Packet Filter to use. - -i interface Capture packets from interface, can be given multiple times. - -r file.pcap Read packets from PCAP file, can be given multiple times. - -R mode Specify the mode for reading PCAP files, see drool(1) for - available modes. - -n Dry run mode, do not allocate any outbound sockets or - generate any network traffic. - -v Enable verbose, a simple way to enable logging. Can be - given multiple times to increase verbosity level. - -h Print this help and exit - -V Print version and exit -]] - os.exit(1) - elseif getopt:val("V") then - print("drool v"..version) - os.exit(0) - end +local cmd = require(cmds[arg[2]]).new(getopt) +getopt:parse() + +if getopt:val("help") then + getopt:usage() + os.exit(0) end -args() -assert(loadfile(getopt:val("pkgdatadir").."/conf.lua"))() -assert(loadfile(getopt:val("pkgdatadir").."/log.lua"))() -assert(loadfile(getopt:val("pkgdatadir").."/run.lua"))() -assert(loadfile(getopt:val("pkgdatadir").."/main.lua"))() -main() +local v = getopt:val("v") +if v > 0 then + require("dnsjit.core.log").enable("warning") +end +if v > 1 then + require("dnsjit.core.log").enable("notice") +end +if v > 2 then + require("dnsjit.core.log").enable("info") +end +if v > 3 then + require("dnsjit.core.log").enable("debug") +end + +cmd:setup(getopt) +local start_sec, start_nsec = clock:monotonic() +cmd:run() +local end_sec, end_nsec = clock:monotonic() +cmd:finish() +local fin_sec, fin_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 + +local finish = 0 +if fin_sec > end_sec then + finish = ((fin_sec - end_sec) - 1) + ((1000000000 - end_nsec + fin_nsec)/1000000000) +elseif fin_sec == end_sec and fin_nsec > end_nsec then + finish = (fin_nsec - end_nsec) / 1000000000 +end + +if getopt:val("json") then + print("{") + print(" \"runtime\": " .. runtime .. ",") + print(" \"finish\": " .. finish .. ",") + print(" \"packets\": " .. cmd.packets .. ",") + print(" \"queries\": " .. cmd.queries .. ",") + print(" \"sent\": " .. cmd.sent .. ",") + print(" \"received\": " .. cmd.received .. ",") + print(" \"responses\": " .. cmd.responses .. ",") + print(" \"timeouts\": " .. cmd.timeouts .. ",") + print(" \"errors\": " .. cmd.errors) + print("}") +elseif getopt:val("csv") then + print("runtime,finish,packets,queries,sent,received,responses,timeouts,errors") + print(runtime ..",".. + finish ..",".. + cmd.packets ..",".. + cmd.queries ..",".. + cmd.sent ..",".. + cmd.received ..",".. + cmd.responses ..",".. + cmd.timeouts ..",".. + cmd.errors) +else + print("runtime", runtime+finish, "run", runtime, "finish", finish) + print("", "total", "/sec") + print("packets", cmd.packets, cmd.packets/runtime) + print("queries", cmd.queries, cmd.queries/runtime) + print("sent", cmd.sent, cmd.sent/runtime) + print("received", cmd.received, cmd.received/runtime) + print("responses", cmd.responses, cmd.responses/runtime) + print("timeouts", cmd.timeouts) + print("errors", cmd.errors) +end diff --git a/src/lib/drool/replay.lua b/src/lib/drool/replay.lua new file mode 100644 index 0000000..d41eca6 --- /dev/null +++ b/src/lib/drool/replay.lua @@ -0,0 +1,680 @@ +-- DNS Reply Tool (drool) +-- +-- Copyright (c) 2017-2018, OARC, Inc. +-- Copyright (c) 2017, Comcast Corporation +-- All rights reserved. +-- +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions +-- are met: +-- +-- 1. Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- +-- 2. Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in +-- the documentation and/or other materials provided with the +-- distribution. +-- +-- 3. Neither the name of the copyright holder nor the names of its +-- contributors may be used to endorse or promote products derived +-- from this software without specific prior written permission. +-- +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +-- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +-- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +-- FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +-- COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +-- INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +-- BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +-- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +-- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +-- ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +-- POSSIBILITY OF SUCH DAMAGE. + +module(...,package.seeall) + +local ffi = require("ffi") +local C = ffi.C +ffi.cdef[[ +struct replay_stats { + int64_t sent, received, responses, timeouts, errors; +}; +void* malloc(size_t); +void free(void*); +]] + +local object = require("dnsjit.core.objects") +require("dnsjit.core.timespec_h") + +Replay = {} + +function Replay.new(getopt) + local self = setmetatable({ + no_responses = false, + threads = false, + print_dns = false, + packets = 0, + queries = 0, + sent = 0, + received = 0, + responses = 0, + errors = 0, + timeouts = 0, + log = require("dnsjit.core.log").new("replay"), + + _udp_channels = {}, + _tcp_channels = {}, + _threads = {}, + _timeout = 10.0, + _timespec = ffi.new("core_timespec_t"), + }, { __index = Replay }) + + getopt.usage_desc = arg[1] .. " replay [options...] file host port" + getopt:add("t", "timing", "ignore", "Set the timing mode [mode=option], default ignore", "?") + getopt:add("n", "no-responses", false, "Do not wait for responses before sending next request", "?") + getopt:add(nil, "no-udp", false, "Do not use UDP", "?") + getopt:add(nil, "no-tcp", false, "Do not use TCP", "?") + getopt:add("T", "threads", false, "Use threads", "?") + getopt:add(nil, "udp-threads", 4, "Set the number of UDP threads to use, default 4", "?") + getopt:add(nil, "tcp-threads", 2, "Set the number of TCP threads to use, default 2", "?") + getopt:add(nil, "timeout", "10.0", "Set timeout for waiting on responses [seconds.nanoseconds], default 10.0", "?") + getopt:add("D", nil, false, "Show DNS queries and responses as processing goes", "?") + + return self +end + +function Replay:setup(getopt) + local _, file, host, port = unpack(getopt.left) + + if file == nil then + self.log:fatal("no file given") + end + if host == nil then + self.log:fatal("no target host given") + end + if port == nil then + self.log:fatal("no target port given") + end + if getopt:val("no-udp") and getopt:val("no-tcp") then + self.log:fatal("can not disable all transports") + end + self.threads = getopt:val("T") + self._timeout = tonumber(getopt:val("timeout")) + self._timespec.sec = math.floor(self._timeout) + self._timespec.nsec = (self._timeout - math.floor(self._timeout)) * 1000000000 + self.log:info("timeout %d.%09d", self._timespec.sec, self._timespec.nsec) + self.no_responses = getopt:val("n") + self.print_dns = getopt:val("D") + + local input = require("dnsjit.input.mmpcap").new() + if input:open(file) ~= 0 then + self.log:fatal("unable to open file " .. file) + end + + local timing + if getopt:val("t") ~= "ignore" then + timing = require("dnsjit.filter.timing").new() + timing:producer(input) + + local mode, opt = getopt:val("t") + if mode == "keep" then + else + mode, opt = mode:match("^(%w+)=(.*)") + if mode == "inc" or mode == "increase" then + timing:increase(tonumber(opt)) + elseif mode == "red" or mode == "reduce" then + timing:reduce(tonumber(opt)) + elseif mode == "mul" or mode == "multiply" then + timing:multiply(tonumber(opt)) + elseif mode == "fix" or mode == "fixed" then + timing:fixed(tonumber(opt)) + end + end + end + + local layer = require("dnsjit.filter.layer").new() + if timing then + layer:producer(timing) + else + layer:producer(input) + end + + if self.threads then + local stats = require("dnsjit.core.channel").new() + self._stats = stats + + if not getopt:val("no-udp") then + if getopt:val("udp-threads") < 1 then + self.log:fatal("--udp-threads must be 1 or greater") + end + + self.log:info("starting " .. getopt:val("udp-threads") .. " UDP threads") + for n = 1, getopt:val("udp-threads") do + + local chan = require("dnsjit.core.channel").new() + local thr = require("dnsjit.core.thread").new() + + thr:start(function(thr) + local thrid = string.format("udp#%d", thr:pop()) + local log = require("dnsjit.core.log").new(thrid) + require("dnsjit.core.objects") + require("drool.replay_stats") + local ffi = require("ffi") + local host = thr:pop() + local port = thr:pop() + local chan = thr:pop() + local stats = thr:pop() + local resp = thr:pop() + local print_dns, dns = thr:pop() + local to_sec, to_nsec = thr:pop(), thr:pop() + + if print_dns == 1 then + dns = require("dnsjit.core.object.dns").new() + print_dns = function(payload) + dns.obj_prev = payload + dns:print() + end + else + print_dns = nil + end + + local udpcli, urecv, uctx, uprod + udpcli = require("dnsjit.output.udpcli").new() + udpcli:timeout(to_sec, to_nsec) + if udpcli:connect(host, port) ~= 0 then + log:fatal("unable to connect to host " .. host .. " port " .. port) + end + urecv, uctx = udpcli:receive() + uprod = udpcli:produce() + + local stat = ffi.cast("struct replay_stats*", ffi.C.malloc(ffi.sizeof("struct replay_stats"))) + stat.sent = 0 + stat.received = 0 + stat.responses = 0 + stat.timeouts = 0 + stat.errors = 0 + ffi.gc(stat, ffi.C.free) + + local send + if resp == 0 then + send = function(obj) + log:info("sending udp query") + urecv(uctx, obj) + if print_dns then + print_dns(obj) + end + end + else + send = function(obj) + log:info("sending udp query") + urecv(uctx, obj) + if print_dns then + print_dns(obj) + end + + local response = uprod(uctx) + if response == nil then + log:warning("producer error") + return + end + local payload = response:cast() + if payload.len == 0 then + stat.timeouts = stat.timeouts + 1 + log:info("timeout") + return + end + + stat.responses = stat.responses + 1 + log:info("got response") + if print_dns then + print_dns(response) + end + end + end + + local dns = require("dnsjit.core.object.dns").new() + while true do + local obj = chan:get() + if obj == nil then break end + obj = ffi.cast("core_object_t*", obj) + dns.obj_prev = obj + if dns:parse_header() == 0 and dns.qr == 0 then + send(obj) + stat.sent = stat.sent + 1 + end + obj:free() + end + + stat.errors = udpcli:errors() + ffi.gc(stat, nil) + stats:put(stat) + end) + + thr:push(n) + thr:push(host) + thr:push(port) + thr:push(chan) + thr:push(stats) + if self.no_responses then + thr:push(0) + else + thr:push(1) + end + if self.print_dns then + thr:push(1) + else + thr:push(0) + end + thr:push(tonumber(self._timespec.sec)) + thr:push(tonumber(self._timespec.nsec)) + + table.insert(self._udp_channels, chan) + table.insert(self._threads, thr) + self.log:info("UDP thread " .. n .. " started") + end + end + + if not getopt:val("no-tcp") then + if getopt:val("tcp-threads") < 1 then + self.log:fatal("--tcp-threads must be 1 or greater") + end + + self.log:info("starting " .. getopt:val("tcp-threads") .. " TCP threads") + for n = 1, getopt:val("tcp-threads") do + + local chan = require("dnsjit.core.channel").new() + local thr = require("dnsjit.core.thread").new() + + thr:start(function(thr) + local thrid = string.format("tcp#%d", thr:pop()) + local log = require("dnsjit.core.log").new(thrid) + require("dnsjit.core.objects") + require("drool.replay_stats") + local ffi = require("ffi") + local host = thr:pop() + local port = thr:pop() + local chan = thr:pop() + local stats = thr:pop() + local resp = thr:pop() + local print_dns, dns = thr:pop() + local to_sec, to_nsec = thr:pop(), thr:pop() + + if print_dns == 1 then + dns = require("dnsjit.core.object.dns").new() + print_dns = function(payload) + dns.obj_prev = payload + dns:print() + end + else + print_dns = nil + end + + local tcpcli, trecv, tctx, tprod + tcpcli = require("dnsjit.output.tcpcli").new() + tcpcli:timeout(to_sec, to_nsec) + if tcpcli:connect(host, port) ~= 0 then + log:fatal("unable to connect to host " .. host .. " port " .. port) + end + trecv, tctx = tcpcli:receive() + tprod = tcpcli:produce() + + local stat = ffi.cast("struct replay_stats*", ffi.C.malloc(ffi.sizeof("struct replay_stats"))) + stat.sent = 0 + stat.received = 0 + stat.responses = 0 + stat.timeouts = 0 + stat.errors = 0 + ffi.gc(stat, ffi.C.free) + + local send + if resp == 0 then + send = function(obj) + log:info("sending tcp query") + trecv(tctx, obj) + if print_dns then + print_dns(obj) + end + end + else + send = function(obj) + log:info("sending tcp query") + trecv(tctx, obj) + if print_dns then + print_dns(obj) + end + + local response = tprod(tctx) + if response == nil then + log:warning("producer error") + return + end + local payload = response:cast() + if payload.len == 0 then + stat.timeouts = stat.timeouts + 1 + log:info("timeout") + return + end + + stat.responses = stat.responses + 1 + log:info("got response") + if print_dns then + print_dns(response) + end + end + end + + local dns = require("dnsjit.core.object.dns").new() + while true do + local obj = chan:get() + if obj == nil then break end + obj = ffi.cast("core_object_t*", obj) + dns.obj_prev = obj + if dns:parse_header() == 0 and dns.qr == 0 then + send(obj) + stat.sent = stat.sent + 1 + end + obj:free() + end + + ffi.gc(stat, nil) + stats:put(stat) + end) + + thr:push(n) + thr:push(host) + thr:push(port) + thr:push(chan) + thr:push(stats) + if self.no_responses then + thr:push(0) + else + thr:push(1) + end + if self.print_dns then + thr:push(1) + else + thr:push(0) + end + thr:push(tonumber(self._timespec.sec)) + thr:push(tonumber(self._timespec.nsec)) + + table.insert(self._tcp_channels, chan) + table.insert(self._threads, thr) + self.log:info("TCP thread " .. n .. " started") + end + end + else + local udpcli + if not getopt:val("no-udp") then + udpcli = require("dnsjit.output.udpcli").new() + udpcli:timeout(self._timespec.sec, self._timespec.nsec) + if udpcli:connect(host, port) ~= 0 then + self.log:fatal("unable to connect to host " .. host .. " port " .. port .. " with UDP") + end + end + local tcpcli + if not getopt:val("no-tcp") then + tcpcli = require("dnsjit.output.tcpcli").new() + tcpcli:timeout(self._timespec.sec, self._timespec.nsec) + if tcpcli:connect(host, port) ~= 0 then + self.log:fatal("unable to connect to host " .. host .. " port " .. port .. " with TCP") + end + end + + self.udpcli = udpcli + self.tcpcli = tcpcli + end + + self.file = file + self.host = host + self.port = port + self.input = input + self.layer = layer +end + +function Replay:run() + local lprod, lctx = self.layer:produce() + local log, packets, queries, responses, errors, timeouts = self.log, 0, 0, 0, 0, 0 + + if self.threads then + -- TODO: generate code for all udp/tcp channels, see split gen code in test + local udpidx, tcpidx = 1, 1 + + local send, send_udp, send_tcp + send_udp = function(obj) + local chan = self._udp_channels[udpidx] + if not chan then + udpidx = 1 + chan = self._udp_channels[1] + end + chan:put(obj:copy()) + udpidx = udpidx + 1 + end + send_tcp = function(obj) + local chan = self._tcp_channels[tcpidx] + if not chan then + tcpidx = 1 + chan = self._tcp_channels[1] + end + chan:put(obj:copy()) + tcpidx = tcpidx + 1 + end + if self._udp_channels[1] and self._tcp_channels[1] then + send = function(obj) + local protocol = obj.obj_prev + while protocol ~= nil do + if protocol.obj_type == object.UDP then + send_udp(obj) + break + elseif protocol.obj_type == object.TCP then + send_tcp(obj) + break + end + protocol = protocol.obj_prev + end + end + elseif self._udp_channels[1] then + send = send_udp + elseif self._tcp_channels[1] then + send = send_tcp + end + + while true do + local obj = lprod(lctx) + if obj == nil then break end + packets = packets + 1 + local payload = obj:cast() + if obj:type() == "payload" and payload.len > 0 then + send(obj) + end + end + + for _, chan in pairs(self._udp_channels) do + chan:put(nil) + end + for _, chan in pairs(self._tcp_channels) do + chan:put(nil) + end + + self.packets = packets + self.queries = 0 + self.sent = 0 + self.received = 0 + self.responses = 0 + self.errors = 0 + + return + end + + local udpcli, urecv, uctx, uprod = self.udpcli + if udpcli then + urecv, uctx = udpcli:receive() + uprod = udpcli:produce() + end + local tcpcli, trecv, tctx, tprod = self.tcpcli + if tcpcli then + trecv, tctx = tcpcli:receive() + tprod = tcpcli:produce() + end + + local print_dns, dns + if self.print_dns then + dns = require("dnsjit.core.object.dns").new() + print_dns = function(payload) + dns.obj_prev = payload + dns:print() + end + end + local send, send_udp, send_tcp + if self.no_responses then + send_udp = function(obj) + log:info("sending udp query") + urecv(uctx, obj) + if print_dns then + print_dns(obj) + end + end + send_tcp = function(obj) + log:info("sending tcp query") + trecv(tctx, obj) + if print_dns then + print_dns(obj) + end + end + else + send_udp = function(obj) + log:info("sending udp query") + urecv(uctx, obj) + if print_dns then + print_dns(obj) + end + + local response = uprod(uctx) + if response == nil then + log:warning("producer error") + return + end + local payload = response:cast() + if payload.len == 0 then + timeouts = timeouts + 1 + log:info("timeout") + return + end + + responses = responses + 1 + log:info("got response") + if print_dns then + print_dns(response) + end + end + send_tcp = function(obj) + log:info("sending tcp query") + trecv(tctx, obj) + if print_dns then + print_dns(obj) + end + + local response = tprod(tctx) + if response == nil then + log:warning("producer error") + return + end + local payload = response:cast() + if payload.len == 0 then + timeouts = timeouts + 1 + log:info("timeout") + return + end + + responses = responses + 1 + log:info("got response") + if print_dns then + print_dns(response) + end + end + end + if udpcli and tcpcli then + send = function(obj) + local protocol = obj.obj_prev + while protocol ~= nil do + if protocol.obj_type == object.UDP then + send_udp(obj) + break + elseif protocol.obj_type == object.TCP then + send_tcp(obj) + break + end + protocol = protocol.obj_prev + end + end + elseif udpcli then + send = send_udp + elseif tcpcli then + send = send_tcp + end + + local dns = require("dnsjit.core.object.dns").new() + while true do + local obj = lprod(lctx) + if obj == nil then break end + packets = packets + 1 + local payload = obj:cast() + if obj:type() == "payload" and payload.len > 0 then + dns.obj_prev = obj + if dns:parse_header() == 0 and dns.qr == 0 then + queries = queries + 1 + send(obj) + end + end + end + + self.packets = packets + self.queries = queries + self.sent = 0 + if udpcli then + self.sent = self.sent + udpcli:packets() + end + if tcpcli then + self.sent = self.sent + tcpcli:packets() + end + self.responses = responses + self.timeouts = timeouts + self.errors = errors + if udpcli then + self.errors = self.errors + udpcli:errors() + end + if tcpcli then + self.errors = self.errors + tcpcli:errors() + end +end + +function Replay:finish() + if self.threads then + local threads = 0 + for _, thr in pairs(self._threads) do + thr:stop() + threads = threads + 1 + end + + for n = 1, threads do + local stat = ffi.cast("struct replay_stats*", self._stats:get()) + self.queries = self.queries + stat.sent + self.sent = self.sent + stat.sent + self.received = self.received + stat.received + self.responses = self.responses + stat.responses + self.timeouts = self.timeouts + stat.timeouts + self.errors = self.errors + stat.errors + C.free(stat) + end + self.queries = tonumber(self.queries) + self.sent = tonumber(self.sent) + self.received = tonumber(self.received) + self.responses = tonumber(self.responses) + self.timeouts = tonumber(self.timeouts) + self.errors = tonumber(self.errors) + end +end + +return Replay diff --git a/src/log.lua b/src/log.lua deleted file mode 100644 index 41dad60..0000000 --- a/src/log.lua +++ /dev/null @@ -1,197 +0,0 @@ -function enable_log(_, facility, level) - local facility_ok = false - if level == nil then - level = "all" - end - - L:info("enable log facility %s level %s", facility, level) - - if facility == "all" then - if level == "debug" then - log.enable("debug") - elseif level == "info" then - log.enable("info") - elseif level == "notice" then - log.enable("notice") - elseif level == "warning" then - log.enable("warning") - elseif level == "all" then - log.enable("debug") - log.enable("info") - log.enable("notice") - log.enable("warning") - else - L:fatal("invalid log level: %s", level) - end - facility_ok = true - end - - if facility == "core" or facility == "all" then - if level == "debug" then - L:enable("debug") - elseif level == "info" then - L:enable("info") - elseif level == "notice" then - L:enable("notice") - elseif level == "warning" then - L:enable("warning") - elseif level == "all" then - L:enable("debug") - L:enable("info") - L:enable("notice") - L:enable("warning") - else - L:fatal("invalid log level: %s", level) - end - facility_ok = true - end - - if facility == "input" or facility == "all" then - if level == "debug" or level == "info" or level == "notice" or level == "warning" or level == "all" then - table.insert(facility_log["input"], { "enable", level }) - else - L:fatal("invalid log level: %s", level) - end - facility_ok = true - end - - if facility == "processing" or facility == "all" then - if level == "debug" or level == "info" or level == "notice" or level == "warning" or level == "all" then - table.insert(facility_log["processing"], { "enable", level }) - else - L:fatal("invalid log level: %s", level) - end - facility_ok = true - end - - if facility == "network" or facility == "all" then - if level == "debug" or level == "info" or level == "notice" or level == "warning" or level == "all" then - table.insert(facility_log["network"], { "enable", level }) - else - L:fatal("invalid log level: %s", level) - end - facility_ok = true - end - - if not facility_ok then - L:fatal("invalid facility: %s", facility) - end -end - -function disable_log(_, facility, level) - local facility_ok = false - if level == nil then - level = "all" - end - - L:info("disable log facility %s level %s", facility, level) - - if facility == "all" then - if level == "debug" then - log.disable("debug") - elseif level == "info" then - log.disable("info") - elseif level == "notice" then - log.disable("notice") - elseif level == "warning" then - log.disable("warning") - elseif level == "all" then - log.disable("debug") - log.disable("info") - log.disable("notice") - log.disable("warning") - else - L:fatal("invalid log level: %s", level) - end - facility_ok = true - end - - if facility == "core" or facility == "all" then - if level == "debug" then - L:disable("debug") - elseif level == "info" then - L:disable("info") - elseif level == "notice" then - L:disable("notice") - elseif level == "warning" then - L:disable("warning") - elseif level == "all" then - L:disable("debug") - L:disable("info") - L:disable("notice") - L:disable("warning") - else - L:fatal("invalid log level: %s", level) - end - facility_ok = true - end - - if facility == "input" or facility == "all" then - if level == "debug" or level == "info" or level == "notice" or level == "warning" or level == "all" then - table.insert(facility_log["input"], { "disable", level }) - else - L:fatal("invalid log level: %s", level) - end - facility_ok = true - end - - if facility == "processing" or facility == "all" then - if level == "debug" or level == "info" or level == "notice" or level == "warning" or level == "all" then - table.insert(facility_log["processing"], { "disable", level }) - else - L:fatal("invalid log level: %s", level) - end - facility_ok = true - end - - if facility == "network" or facility == "all" then - if level == "debug" or level == "info" or level == "notice" or level == "warning" or level == "all" then - table.insert(facility_log["network"], { "disable", level }) - else - L:fatal("invalid log level: %s", level) - end - facility_ok = true - end - - if not facility_ok then - L:fatal("invalid facility: %s", facility) - end -end - -function set_log(facility, obj) - local v - for _, v in pairs(facility_log[facility]) do - local what, level = unpack(v) - if what == "enable" then - if level == "debug" then - obj:log():enable("debug") - elseif level == "info" then - obj:log():enable("info") - elseif level == "notice" then - obj:log():enable("notice") - elseif level == "warning" then - obj:log():enable("warning") - elseif level == "all" then - obj:log():enable("debug") - obj:log():enable("info") - obj:log():enable("notice") - obj:log():enable("warning") - end - elseif what == "disable" then - if level == "debug" then - obj:log():disable("debug") - elseif level == "info" then - obj:log():disable("info") - elseif level == "notice" then - obj:log():disable("notice") - elseif level == "warning" then - obj:log():disable("warning") - elseif level == "all" then - obj:log():disable("debug") - obj:log():disable("info") - obj:log():disable("notice") - obj:log():disable("warning") - end - end - end -end diff --git a/src/main.lua b/src/main.lua deleted file mode 100644 index a6fd70e..0000000 --- a/src/main.lua +++ /dev/null @@ -1,81 +0,0 @@ -function main() - local k, v, o - - o = getopt:val("c") - if type(o) == "string" then - if o > "" then - o = { o } - else - o = {} - end - end - for k, v in pairs(o) do - local t = v:sub(1, 5) - if t == "text:" then - conf:line(v:sub(6)) - elseif t == "file:" then - conf:file(v:sub(6)) - else - L:fatal("unknown conf type: %s", v:sub(1,4)) - end - end - - o = getopt:val("l") - if type(o) == "string" then - if o > "" then - o = { o } - else - o = {} - end - end - for k, v in pairs(o) do - local n = v:find(":") - print(v, n) - if n == nil then - enable_log(nil, v) - else - enable_log(nil, v:sub(1,n), v:sub(n+1)) - end - end - - o = getopt:val("L") - if type(o) == "string" then - if o > "" then - o = { o } - else - o = {} - end - end - for k, v in pairs(o) do - local n = v:find(":") - if n == nil then - disable_log(nil, v) - else - disable_log(nil, v:sub(1,n), v:sub(n+1)) - end - end - - o = getopt:val("R") - if o > "" then - if o:sub(1,5) == "iter:" then - read_iter = tonumber(o:sub(6)) - elseif o == "loop" then - read_loop = true - else - L:fatal("unknown read mode: %s", o) - end - end - - - if read_loop then - while true do - run() - collectgarbage() - end - else - for iter = 1, read_iter do - run() - collectgarbage() - end - end -end diff --git a/src/run.lua b/src/run.lua deleted file mode 100644 index fd54f48..0000000 --- a/src/run.lua +++ /dev/null @@ -1,230 +0,0 @@ -function run() - local input - - if backend.input == "pcapthread" then - local pcapthread = pcall(function() require("dnsjit.input.pcapthread") end) - if not pcapthread then - pcapthread = require("dnsjit.input.pcap") - else - pcapthread = require("dnsjit.input.pcapthread") - end - input = pcapthread.new() - input:only_queries(true) - elseif backend.input == "fpcap" then - input = require("dnsjit.input.fpcap").new() - elseif backend.input == "mmpcap" then - input = require("dnsjit.input.mmpcap").new() - elseif backend.input == "pcap" then - input = require("dnsjit.input.pcap").new() - end - set_log("input", input) - if backend.input == "fpcap" or backend.input == "mmpcap" then - input:use_shared(true) - end - - o = getopt:val("f") - if o > "" then - bpf = f - end - if bpf and bpf > "" then - if backend.input == "pcapthread" then - input:filter(bpf) - else - L:fatal("input backend "..backend.input.." can not use BPF") - end - end - - o = getopt:val("i") - if type(o) == "string" then - if o > "" then - o = { o } - else - o = {} - end - end - for k, v in pairs(interfaces) do - if backend.input == "pcapthread" then - input:open(v) - else - L:fatal("input backend "..backend.input.." can not open interfaces") - end - end - for k, v in pairs(o) do - if backend.input == "pcapthread" then - input:open(v) - else - L:fatal("input backend "..backend.input.." can not open interfaces") - end - end - - o = getopt:val("r") - if type(o) == "string" then - if o > "" then - o = { o } - else - o = {} - end - end - for k, v in pairs(files) do - if backend.input == "pcapthread" or backend.input == "pcap" then - input:open_offline(v) - elseif backend.input == "fpcap" or backend.input == "mmpcap" then - input:open(v) - else - L:fatal("input backend "..backend.input.." can not open offline files") - end - end - for k, v in pairs(o) do - if backend.input == "pcapthread" or backend.input == "pcap" then - input:open_offline(v) - elseif backend.input == "fpcap" or backend.input == "mmpcap" then - input:open(v) - else - L:fatal("input backend "..backend.input.." can not open offline files") - end - end - - function new_timing() - local timing = require("dnsjit.filter.timing").new() - set_log("processing", timing) - if timing_mode == "keep" or timing_mode == "best_effort" then - timing:keep() - elseif mode == "increase" then - timing:increase(timing_arg) - elseif mode == "reduce" then - timing:reduce(timing_arg) - elseif mode == "multiply" then - timing:multiply(timing_arg) - end - return timing - end - - local start_sec, start_nsec, end_sec, end_nsec, runtime, output_queries - - if backend.output == "cpool" then - local new_output = function() - local output = require("dnsjit.output.cpool").new(host, port) - set_log("network", output) - if max_clients ~= nil then - output:max_clients(max_clients) - end - if client_ttl ~= nil then - output:client_ttl(client_ttl) - end - if skip_reply ~= nil then - output:skip_reply(skip_reply) - end - if max_reuse_clients ~= nil then - output:max_reuse_clients(max_reuse_clients) - end - if sendas ~= nil then - output:sendas(sendas) - end - output:dry_run(getopt:val("n")) - return output - end - - if client_pools == nil or client_pools == 1 then - local output = new_output() - if timing_mode then - local timing = new_timing() - input:receiver(timing) - timing:receiver(output) - else - input:receiver(output) - end - output:start() - start_sec, start_nsec = clock:monotonic() - input:run() - output:stop() - else - local n - local outputs = {} - local roundrobin = require("dnsjit.filter.split").new() - set_log("processing", roundrobin) - for n = 1, client_pools do - local output = new_output() - if timing_mode then - local timing = new_timing() - roundrobin:receiver(timing) - timing:receiver(output) - else - roundrobin:receiver(output) - end - table.insert(outputs, output) - end - input:receiver(roundrobin) - for _, o in pairs(outputs) do - o:start() - end - start_sec, start_nsec = clock:monotonic() - input:run() - for _, o in pairs(outputs) do - o:stop() - end - end - elseif backend.output == "udpcli" then - if max_clients == nil or max_clients == 1 then - local layer = require("dnsjit.filter.layer").new() - set_log("processing", layer) - local output = require("dnsjit.output.udpcli").new(host, port) - set_log("network", output) - - layer:receiver(output) - input:receiver(layer) - start_sec, start_nsec = clock:monotonic() - input:run() - output_queries = output:packets() - else - local layers = {} - local outputs = {} - local thread = require("dnsjit.filter.thread").new() - set_log("processing", thread) - local n - - thread:use_writers(true) - - for n = 1, max_clients do - local layer = require("dnsjit.filter.layer").new() - set_log("processing", layer) - local output = require("dnsjit.output.udpcli").new(host, port) - set_log("network", output) - - layer:receiver(output) - thread:receiver(layer) - table.insert(layers, layer) - table.insert(outputs, output) - end - - input:receiver(thread) - thread:start() - start_sec, start_nsec = clock:monotonic() - input:run() - thread:stop() - output_queries = 0 - for _, output in pairs(outputs) do - output_queries = output_queries + output:packets() - end - end - end - end_sec, end_nsec = clock:monotonic() - - 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") - if backend.input == "pcapthread" then - print("queries", input:queries(), input:queries()/runtime, "/qps") - print("dropped", input:dropped()) - print("ignored", input:ignored()) - print("total", input:queries() + input:dropped() + input:ignored()) - end - if output_queries ~= nil then - print("output", output_queries, output_queries/runtime, "/qps") - end -end diff --git a/src/test/Makefile.am b/src/test/Makefile.am index f7b0412..a9f0f28 100644 --- a/src/test/Makefile.am +++ b/src/test/Makefile.am @@ -37,18 +37,15 @@ MAINTAINERCLEANFILES = $(srcdir)/Makefile.in CLEANFILES = test*.log test*.trs \ *.pcap-dist \ - test4.out test4.out2 + test2.out test2.out2 -TESTS = test1.sh test2.sh test3.sh test4.sh +TESTS = test1.sh test2.sh -test3.sh: dns.pcap-dist 1qtcp.pcap-dist - -test4.sh: dns.pcap-dist 1qtcp.pcap-dist +test2.sh: dns.pcap-dist 1qtcp.pcap-dist .pcap.pcap-dist: cp "$<" "$@" EXTRA_DIST = $(TESTS) \ - test2.conf \ dns.pcap 1qtcp.pcap \ - test4.gold + test2.gold diff --git a/src/test/test1.sh b/src/test/test1.sh index 914cfc8..32fb8f8 100755 --- a/src/test/test1.sh +++ b/src/test/test1.sh @@ -35,4 +35,6 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -! ../drool --pkgdatadir "$srcdir/.." -h +export LUA_PATH="$srcdir/../lib/?.lua" +../drool -h +../drool replay -h diff --git a/src/test/test2.conf b/src/test/test2.conf deleted file mode 100644 index 68c65db..0000000 --- a/src/test/test2.conf +++ /dev/null @@ -1,10 +0,0 @@ -log all; -nolog network debug; - -#read "/path/to/file.pcap"; -input "eth0"; - -filter "udp port 53"; - -#write "/path/to/file.pcap"; -output "eth1"; diff --git a/src/test/test2.gold b/src/test/test2.gold new file mode 100644 index 0000000..c65772d --- /dev/null +++ b/src/test/test2.gold @@ -0,0 +1,28 @@ +packets 133 +queries 41 +sent 41 +received 0 +responses 0 +timeouts 0 +errors 0 +packets 133 +queries 41 +sent 41 +received 0 +responses 0 +timeouts 0 +errors 0 +packets 10 +queries 2 +sent 2 +received 0 +responses 0 +timeouts 0 +errors 0 +packets 10 +queries 2 +sent 2 +received 0 +responses 0 +timeouts 0 +errors 0 diff --git a/src/test/test2.sh b/src/test/test2.sh index 653ad7a..2085f94 100755 --- a/src/test/test2.sh +++ b/src/test/test2.sh @@ -35,4 +35,18 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -! ../drool --pkgdatadir "$srcdir/.." -c "$srcdir/test2.conf" +export LUA_PATH="$srcdir/../lib/?.lua" +../drool -h +../drool replay -h + +if [ -n "$DROOL_TEST_NETWORK" ]; then + rm -f test2.out test2.out2 + for pcap in ./dns.pcap-dist ./1qtcp.pcap-dist; do + ../drool replay -n --no-tcp "$pcap" 127.0.0.1 53 | tail -n 7 >>test2.out + ../drool replay -n --no-tcp "$pcap" ::1 53 | tail -n 7 >>test2.out + done + awk '{print $1 " " $2}' test2.out2 + diff test2.out2 "$srcdir/test2.gold" +else + echo "Not testing network (set DROOL_TEST_NETWORK to enable)" +fi diff --git a/src/test/test3.sh b/src/test/test3.sh deleted file mode 100755 index 928ae69..0000000 --- a/src/test/test3.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/sh -e -# -# DNS Reply Tool (drool) -# -# Copyright (c) 2017-2018, OARC, Inc. -# Copyright (c) 2017, Comcast Corporation -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in -# the documentation and/or other materials provided with the -# distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. - -../drool --pkgdatadir "$srcdir/.." -c "text:client_pool target \"127.0.0.1\" \"53\"; timing ignore;" -n -r ./dns.pcap-dist -../drool --pkgdatadir "$srcdir/.." -c "text:client_pool target \"127.0.0.1\" \"53\"; timing ignore;" -n -r ./1qtcp.pcap-dist diff --git a/src/test/test4.gold b/src/test/test4.gold deleted file mode 100644 index 52ca7d9..0000000 --- a/src/test/test4.gold +++ /dev/null @@ -1,60 +0,0 @@ -packets 82 -queries 41 -dropped 0 -ignored 41 -total 82 -packets 82 -queries 41 -dropped 0 -ignored 41 -total 82 -packets 82 -queries 41 -dropped 0 -ignored 41 -total 82 -packets 82 -queries 41 -dropped 0 -ignored 41 -total 82 -packets 82 -queries 41 -dropped 0 -ignored 41 -total 82 -packets 82 -queries 41 -dropped 0 -ignored 41 -total 82 -packets 10 -queries 1 -dropped 8 -ignored 1 -total 10 -packets 10 -queries 1 -dropped 8 -ignored 1 -total 10 -packets 10 -queries 1 -dropped 8 -ignored 1 -total 10 -packets 10 -queries 1 -dropped 8 -ignored 1 -total 10 -packets 10 -queries 1 -dropped 8 -ignored 1 -total 10 -packets 10 -queries 1 -dropped 8 -ignored 1 -total 10 diff --git a/src/test/test4.sh b/src/test/test4.sh deleted file mode 100755 index dcb696f..0000000 --- a/src/test/test4.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/sh -e -# -# DNS Reply Tool (drool) -# -# Copyright (c) 2017-2018, OARC, Inc. -# Copyright (c) 2017, Comcast Corporation -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in -# the documentation and/or other materials provided with the -# distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. - -if [ -n "$DROOL_TEST_NETWORK" ]; then - rm -f test4.out test4.out2 - for pcap in ./dns.pcap-dist ./1qtcp.pcap-dist; do - ../drool --pkgdatadir "$srcdir/.." -c "text:client_pool target \"127.0.0.1\" \"53\"; timing ignore; client_pool skip_reply;" -vv -r "$pcap" >>test4.out - ../drool --pkgdatadir "$srcdir/.." -c "text:client_pool target \"127.0.0.1\" \"53\"; timing ignore; client_pool skip_reply; client_pool sendas udp;" -vv -r "$pcap" >>test4.out - ../drool --pkgdatadir "$srcdir/.." -c "text:client_pool target \"127.0.0.1\" \"53\"; timing ignore; client_pool skip_reply; client_pool sendas tcp;" -vv -r "$pcap" >>test4.out - ../drool --pkgdatadir "$srcdir/.." -c "text:client_pool target \"::1\" \"53\"; timing ignore; client_pool skip_reply;" -vv -r "$pcap" >>test4.out - ../drool --pkgdatadir "$srcdir/.." -c "text:client_pool target \"::1\" \"53\"; timing ignore; client_pool skip_reply; client_pool sendas udp;" -vv -r "$pcap" >>test4.out - ../drool --pkgdatadir "$srcdir/.." -c "text:client_pool target \"::1\" \"53\"; timing ignore; client_pool skip_reply; client_pool sendas tcp;" -vv -r "$pcap" >>test4.out - done - cut -d" " -f 4- test4.out | grep -v runtime | awk '{print $1 " " $2}' > test4.out2 - diff test4.out2 "$srcdir/test4.gold" -else - echo "Not testing network (set DROOL_TEST_NETWORK to enable)" -fi