Commit f2de716a authored by Stephen Morris's avatar Stephen Morris

[trac703] Bad Packet Tool

Sends packets with a mangled flags field to DNS servers and examines
the responses.
parent 583d7973
......@@ -692,6 +692,9 @@ AC_CONFIG_FILES([Makefile
src/lib/server_common/tests/Makefile
tests/Makefile
tests/system/Makefile
tests/tools/Makefile
tests/tools/badpacket/Makefile
tests/tools/badpacket/tests/Makefile
])
AC_OUTPUT([doc/version.ent
src/bin/cfgmgr/b10-cfgmgr.py
......
......@@ -86,6 +86,7 @@ struct IOFetchData {
size_t offset; ///< Offset to receive data
bool stopped; ///< Have we stopped running?
int timeout; ///< Timeout in ms
bool packet; ///< true if packet was supplied
// In case we need to log an error, the origin of the last asynchronous
// I/O is recorded. To save time and simplify the code, this is recorded
......@@ -146,6 +147,7 @@ struct IOFetchData {
offset(0),
stopped(false),
timeout(wait),
packet(false),
origin(ASIO_UNKORIGIN),
staging(),
qid(QidGenerator::getInstance().generateQid())
......@@ -175,6 +177,18 @@ IOFetch::IOFetch(Protocol protocol, IOService& service,
{
}
IOFetch::IOFetch(Protocol protocol, IOService& service,
OutputBufferPtr& outpkt, const IOAddress& address, uint16_t port,
OutputBufferPtr& buff, Callback* cb, int wait)
:
data_(new IOFetchData(protocol, service,
isc::dns::Question(isc::dns::Name("dummy.example.org"), isc::dns::RRClass::IN(), isc::dns::RRType::A()),
address, port, buff, cb, wait))
{
data_->msgbuf = outpkt;
data_->packet = true;
}
// Return protocol in use.
IOFetch::Protocol
......@@ -201,14 +215,22 @@ IOFetch::operator()(asio::error_code ec, size_t length) {
/// This is done in a different scope to allow inline variable
/// declarations.
{
Message msg(Message::RENDER);
msg.setQid(data_->qid);
msg.setOpcode(Opcode::QUERY());
msg.setRcode(Rcode::NOERROR());
msg.setHeaderFlag(Message::HEADERFLAG_RD);
msg.addQuestion(data_->question);
MessageRenderer renderer(*data_->msgbuf);
msg.toWire(renderer);
if (data_->packet) {
// A packet was given, overwrite the QID (which is in the
// first two bytes of the packet).
data_->msgbuf->writeUint16At(data_->qid, 0);
} else {
// A question was given, construct the packet
Message msg(Message::RENDER);
msg.setQid(data_->qid);
msg.setOpcode(Opcode::QUERY());
msg.setRcode(Rcode::NOERROR());
msg.setHeaderFlag(Message::HEADERFLAG_RD);
msg.addQuestion(data_->question);
MessageRenderer renderer(*data_->msgbuf);
msg.toWire(renderer);
}
}
// If we timeout, we stop, which will can cancel outstanding I/Os and
......
......@@ -137,6 +137,32 @@ public:
uint16_t port, isc::dns::OutputBufferPtr& buff, Callback* cb,
int wait = -1);
/// \brief Constructor.
///
/// Creates the object that will handle the upstream fetch.
///
/// TODO: Need to randomise the source port
///
/// \param protocol Fetch protocol, either IOFetch::TCP or IOFetch::UDP
/// \param service I/O Service object to handle the asynchronous
/// operations.
/// \param outpkt Packet to send to upstream server. Note that the
/// QID (first two bytes of the packet) may be altered in the sending.
/// \param buff Output buffer into which the response (in wire format)
/// is written (if a response is received).
/// \param cb Callback object containing the callback to be called
/// when we terminate. The caller is responsible for managing this
/// object and deleting it if necessary.
/// \param address IP address of upstream server
/// \param port Port to which to connect on the upstream server
/// (default = 53)
/// \param wait Timeout for the fetch (in ms). The default value of
/// -1 indicates no timeout.
IOFetch(Protocol protocol, IOService& service,
isc::dns::OutputBufferPtr& outpkt, const IOAddress& address,
uint16_t port, isc::dns::OutputBufferPtr& buff, Callback* cb,
int wait = -1);
/// \brief Return Current Protocol
///
/// \return Protocol associated with this IOFetch object.
......
SUBDIRS = system
SUBDIRS = system tools
SUBDIRS = badpacket
SUBDIRS = . tests
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CXXFLAGS = $(B10_CXXFLAGS)
if USE_STATIC_LINK
AM_LDFLAGS = -static
endif
CLEANFILES = *.gcno *.gcda
noinst_PROGRAMS = badpacket
badpacket_SOURCES = badpacket.cc
badpacket_SOURCES += command_options.cc command_options.h
badpacket_SOURCES += header_flags.h
badpacket_SOURCES += scan.cc scan.h
badpacket_SOURCES += version.h
badpacket_LDADD = $(top_builddir)/src/lib/asiolink/libasiolink.la
badpacket_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
badpacket_LDADD += $(top_builddir)/src/lib/log/liblog.la
badpacket_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
"badpacket" is a tool intended to test that a nameserver can cope with
incorrectly-formatted DNS messages.
This particular incarnation of the tool allows the flags field of a DNS message
(the third and fourth bytes) to be set to any bit combination (including ones
that invalid in a query). As well as setting the bits to a particular
combination, it is possible to specify ranges for bit/field values; when this
is done, the tool will send a set of packets so that each combination of flag
bits is checked.
To illustrate this, consider the following command:
badpacket --address 192.0.2.21 --port 5301 --aa 0-1 --cd 1
--rc 0-2 ftp.example.com
(The command has been split across two lines for clarity.)
The address and port flags are self-evident. The other flags specify settings
for the AA bit (0 and 1), CD bit (always 1) and the RCODE field (0, 1, 2). (The
remaining fields are not specified, so will always be zero.) There are six
combinations of these values, so six packets will sent to the remote server with
the following settings:
AA RCODE CD Rest
0 0 1 0
0 1 1 0
0 2 1 0
1 0 1 0
1 1 1 0
1 2 1 0
Each packet will cause a line to be output to stdout, which will have the
following form:
SUCCESS: (QR:0 OP:0 AA:0 TC:0 RD:0 RA:0 Z:0 AD:0 CD:1 RC:0)
(qr:1 op:0 aa:0 tc:0 rd:0 ra:1 z:0 ad:0 cd:1 rc:0)
(Again the text has been split across two lines for clarity.)
Each lines contains a status (SUCCESS indicates that a response was received,
regardless of the contents of the response), the state of the fields in the
flags word of the packet sent (in upper-case letters) and the state of the
fields in the flags word of the response (in lower-case letters).
TODO: At the moment the tool is limited to just alerting the flags field.
Future work should extend the program to other bad packets. Ideas are:
* Flasify the values in the various count fields
* Add data to sections that should be empty.
* Deliberately mangle the names placed in the message sections (e.g. by altering
the label count fields).
\ No newline at end of file
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <unistd.h>
#include <config.h>
#include "command_options.h"
#include "scan.h"
/// \brief Perform Bad Packet Scan
///
/// Scans the server by sending a sequence of (potentially) bad packets and
/// printing the packet settings and the response received. The principle
/// raison d'etre for this is to check if a bad packet will cause a crash.
///
/// This particular version of the code allows a set of ranges to be set for
/// each field in the "flags" word (the third and fourth bytes) of a DNS
/// message. (Most of the flags are single-bit values, so the range is just 0-1.
/// The OPCODE and RCODE are both four bits wide, so the range is 0-15.) The
/// program then sends packets containing each combination of values.
///
/// TODO: Extend the program to other bad values.
/// Examples of this would be to make the count fields invalid, to add data
/// to sections that should be empty, and to deliberately mangle the names in
/// these sections.
using namespace isc::badpacket;
/// \brief Main Program
int main(int argc, char* argv[]) {
CommandOptions command_line;
command_line.parse(argc, argv);
// Construct the scan object and perform the scan.
Scan scanner;
scanner.scan(command_line);
return 0;
}
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
#include <boost/lexical_cast.hpp>
#include <getopt.h>
#include "exceptions/exceptions.h"
#include "log/strutil.h"
#include "command_options.h"
#include "version.h"
using namespace std;
using namespace isc;
namespace po = boost::program_options;
namespace isc {
namespace badpacket {
/// Parses the command-line options and returns the results in an Options
/// structure. If the
void
CommandOptions::parse(int argc, char* const argv[]) {
// Set up options for processing
const struct option longopts[] = {
{"help", 0, NULL, 'h'}, // Print usage message and exit
{"version", 0, NULL, 'v'}, // Print program version and exit
{"address", 1, NULL, 'a'}, // Specify target server address
{"port", 1, NULL, 'p'}, // Specify target port
{"timeout", 1, NULL, 't'}, // Time to wait before timing out (ms)
{"qr", 1, NULL, 'Q'}, // Query/response flag
{"op", 1, NULL, 'O'}, // Opcode
{"aa", 1, NULL, 'A'}, // Authoritative answer
{"tc", 1, NULL, 'T'}, // Truncated
{"rd", 1, NULL, 'D'}, // recursion Desired
{"ra", 1, NULL, 'R'}, // Recursion available
{"z", 1, NULL, 'Z'}, // Must be Zero (reserved bit)
{"ad", 1, NULL, 'U'}, // aUthenticated data
{"cd", 1, NULL, 'C'}, // Checking disabled
{"rc", 1, NULL, 'E'}, // rEsult code
{NULL, 0, NULL, 0 }
};
const char* shortopts = "hva:p:t:Q:O:A:T:D:R:Z:U:C:E:";
// Set variables to defaults before parsing
reset();
// Process command line
int c; // Option being processed
optind = 0; // Reset parsing
while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) {
switch (c) {
case 'h': // --help
usage();
exit(0);
case 'v': // --version
version();
exit(0);
case 'a': // --address
address_ = optarg;
break;
case 'p': // --port
port_ = boost::lexical_cast<uint16_t>(string(optarg));
break;
case 't': // --timeout
timeout_ = boost::lexical_cast<int>(string(optarg));
break;
case 'Q': // --qr (query/response)
processOptionValue(optarg, values_.qr, 0, 1);
break;
case 'O': // --op (operation code)
processOptionValue(optarg, values_.op, 0, 15);
break;
case 'A': // --aa (authoritative answer)
processOptionValue(optarg, values_.aa, 0, 1);
break;
case 'T': // --tc (truncated)
processOptionValue(optarg, values_.tc, 0, 1);
break;
case 'D': // --rd (recursion desired)
processOptionValue(optarg, values_.rd, 0, 1);
break;
case 'R': // --ra (recursion available)
processOptionValue(optarg, values_.ra, 0, 1);
break;
case 'Z': // --z (zero: reserved bit)
processOptionValue(optarg, values_.z, 0, 1);
break;
case 'U': // --ad (authenticated data)
processOptionValue(optarg, values_.ad, 0, 1);
break;
case 'C': // --cd (checking disabled)
processOptionValue(optarg, values_.cd, 0, 1);
break;
case 'E': // --rc (result code)
processOptionValue(optarg, values_.rc, 0, 15);
break;
default:
isc_throw(isc::InvalidParameter, "Unknown switch");
}
}
// Pick up a parameter if there is one (and ignore excess parameters).
if (optind < argc) {
qname_ = argv[optind++];
}
}
/// \brief Print usage information
void
CommandOptions::usage() {
cout << "Usage: badpacket [options] query\n"
"\n"
"Sends a sequence of packets to the specified nameserver and prints the results.\n"
"The packets are valid query packets but the flags field (third and fourth bytes\n"
"of the packet) can be set to arbitrary values using the command-line switches.\n"
"\n"
"In the following list of command-line switches, '<range>' indicates a range of\n"
"values specified as either <integer> or <integer1>-<integer2> (e.g. both '42'\n"
"and '0-1' would be valid values for range). The program sends a sequence of\n"
"packets that contain all combinations of the flag values. For example,\n"
"specifying:\n"
"\n"
"--tc 0-1 --op 1-4 --aa 1 --rd 0-1\n"
"\n"
"... would send a total of 16 packets which would have all combinations of the\n"
"the tc bit set to 0 and 1, the rd bit set to 0 and 1, and the opcode set to all\n"
"values between 1 and 4. All other flags fields would be zero except for the aa\n"
"bit which would always be 1.\n"
"\n"
"The long form of the option is given. It can also be specified as a single-\n"
"character short-form, which is listed in sqare brackets in the description.\n"
"\n"
"--help [-h] Prints this message and exits.\n"
"--version [-v] Prints the program version number.\n"
"--address <address> [-a] Address of nameserver, which defaults to 127.0.0.1\n"
"--port <port> [-p] Port to which to send query. Defaults to 53.\n"
"--timeout <value> [-t] Timeout value for the query. Specified in ms, it\n"
" defaults to 500ms.\n"
"--qr <range> [-Q] Set query/response bit. Valid <range> is 0-1\n"
"--op <range> [-O] Set opcode. Valid <range> is 0-15\n"
"--aa <range> [-A] Set authoritative answer bit. Valid <range> is 0-1\n"
"--tc <range> [-T] Set truncated bit. Valid <range> is 0-1\n"
"--z <range> [-Z] Set zero (reserved) bit. Valid <range> is 0-1\n"
"--ad <range> [-U] Set authentiacted data bit. Valid <range> is 0-1\n"
"--cd <range> [-C] Set checking disabled bit. Valid <range> is 0-1\n"
"--rc <range> [-E] Set rcode value. Valid <range> is 0-15\n"
"\n"
"query Name to query for. The query is for an 'IN' A record.\n"
" If not given, the name 'www.example.com' is used.\n"
;
}
/// \brief Print version information
void
CommandOptions::version() {
cout << BADPACKET_VERSION << "\n";
}
// Process single flag
void
CommandOptions::processOptionValue(const char* arg, uint32_t* where, uint32_t minval,
uint32_t maxval)
{
// Split the string up into one or two tokens
vector<string> values = isc::strutil::tokens(string(arg), "-");
if ((values.size() < 1) || (values.size() > 2)) {
isc_throw(isc::BadValue, "command argument is '" << arg << "': it must "
"be in the form 'int' or 'int1-int2'");
}
// Convert to uint32.
uint32_t start = boost::lexical_cast<uint32_t>(values[0]);
uint32_t end = start;
if (values.size() == 2) {
end = boost::lexical_cast<uint32_t>(values[1]);
}
if (start > end) {
swap(start, end);
}
// Coerce values into the desired range
where[0] = max(minval, min(start, maxval));
where[1] = min(maxval, max(end, minval));
}
} // namespace badpacket
} // namespace isc
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#ifndef __COMMAND_OPTIONS_H
#define __COMMAND_OPTIONS_H
#include <cstdlib>
#include <stdint.h>
#include <utility>
#include <boost/program_options.hpp>
namespace isc {
namespace badpacket {
/// \brief Command Options
///
/// This class is responsible for parsing the command-line and storing the
/// specified options.
///
/// Each option setting the state of one of the fields in the flags word in the
/// DNS packet can be specified as either:
///
/// - \c --option value
/// - \c --option value1-value2
///
/// Either way, two values are extracted the low value and the high value (in
/// the former case, bost are the same). The values are stored in a
/// "FlagValues" structure, which can be returned on request.
///
/// For simplicity, the class also takes care of the --help and --version flags,
/// each of which will cause a message to be printed to stdout and the program
/// to terminate.
class CommandOptions {
public:
/// \brief Flags Word Values
///
/// Structure holding the values for the flag settings. Each variable in
/// the structure corresponds to one of the fields in the flags word. The
/// variables are two-ewlement arrays: element 0 of the array holds the low
/// value in the range given, and element 1 the high value. If only a
/// single value is given, both elements hold the same value.
struct FlagValues {
uint32_t qr[2]; // QR bit
uint32_t op[2]; // OPCODE field
uint32_t aa[2]; // AA bit
uint32_t tc[2]; // TC bit
uint32_t rd[2]; // RD bit
uint32_t ra[2]; // RA bit
uint32_t z[2]; // Z bit (reserved bit)
uint32_t ad[2]; // AD bit
uint32_t cd[2]; // CD bit
uint32_t rc[2]; // RCODE field
/// \brief Default Constructor
///
/// Sets everything to zero.
FlagValues() {
reset();
}
/// \brief Reset All Values to Zero
void reset() {
qr[0] = qr[1] = 0;
op[0] = op[1] = 0;
aa[0] = aa[1] = 0;
tc[0] = tc[1] = 0;
rd[0] = rd[1] = 0;
ra[0] = ra[1] = 0;
z[0] = z[1] = 0;
ad[0] = ad[1] = 0;
cd[0] = cd[1] = 0;
rc[0] = rc[1] = 0;
}
};
/// \brief CommandOptions Constructor
///
/// Set values to defaults.
CommandOptions() {
reset();
}
/// \brief Return Flags Word Values
///
/// Returns a copy of the flags word structure for use by the caller. This
/// structure holds the flags field settings specified on the command line.
///
/// \return Copy of the values specified on the command line.
FlagValues getFlagValues() const {
return values_;
}
/// \brief Return Target Address
std::string getAddress() const {
return address_;
}
/// \brief Return Target Port
uint16_t getPort() const {
return port_;
}
/// \brief Return Timeout
int getTimeout() const {
return timeout_;
}
/// \brief Return qname
std::string getQname() const {
return qname_;
}
/// \brief Reset To Defaults
void reset() {
values_.reset();
address_ = "127.0.0.1";
port_ = 53;
timeout_ = 500;
qname_ = "www.example.com";
}
/// \brief Parse Command Line
///
/// Parses the command line and stores the selected options. The parsing
/// also handles the --help and --version commands: both of these will cause
/// some text to be printed to stdout, after which exit() is called to
/// terminate the program.
///
/// \param argc Argument count passed to main().
/// \param argv Argument value array passed to main().
void parse(int argc, char* const argv[]);
/// \brief Print Usage Information
void usage();
/// \brief Print Version Information
void version();
// The following are protected to aid testing
protected:
/// \brief Process Option Value
///
/// Processes a specific command-line option, interpreting the value and
/// placing it in the appropriate location. On error a BadValue exception
/// is thrown.
///
/// \param arg flag argument read from the command line
/// \param where Two-element uint32_t array into which the data is put
/// \param minval Minimum allowed value
/// \param maxval Maximum allowed value
void processOptionValue(const char* arg, uint32_t* where, uint32_t minval,
uint32_t maxval);
// Member variables
private:
FlagValues values_