recursor_unittest.cc 18.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Copyright (C) 2010  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.

// $Id$

#include <config.h>

#include <gtest/gtest.h>

#include <asiolink/asiolink.h>

#include <dns/buffer.h>
#include <dns/name.h>
#include <dns/message.h>
#include <dns/messagerenderer.h>
#include <dns/rrclass.h>
#include <dns/rrtype.h>

#include <cc/data.h>
#include <cc/session.h>

33
34
#include <auth/common.h>

35
36
37
38
#include <recurse/recursor.h>

#include <dns/tests/unittest_util.h>

Evan Hunt's avatar
Evan Hunt committed
39
40
#include <auth/tests/mockups.h>

41
42
43
44
45
46
47
48
49
using isc::UnitTestUtil;
using namespace std;
using namespace isc::cc;
using namespace isc::dns;
using namespace isc::data;
using namespace asiolink;

namespace {
const char* const DEFAULT_REMOTE_ADDRESS = "192.0.2.1";
50
const char* const TEST_PORT = "53535";
51

52
53
54
55
56
57
58
59
60
61
62
63
class DummySocket : public IOSocket {
private:
    DummySocket(const DummySocket& source);
    DummySocket& operator=(const DummySocket& source);
public:
    DummySocket(const int protocol) : protocol_(protocol) {}
    virtual int getNative() const { return (-1); }
    virtual int getProtocol() const { return (protocol_); }
private:
    const int protocol_;
};

64
65
class RecursorTest : public ::testing::Test {
protected:
66
    RecursorTest() : ios(*TEST_PORT, true, false, NULL, NULL, NULL),
67
                    request_message(Message::RENDER),
68
                    parse_message(new Message(Message::PARSE)),
69
70
                    default_qid(0x1035), opcode(Opcode(Opcode::QUERY())),
                    qname("www.example.com"),
71
72
73
                    qclass(RRClass::IN()), qtype(RRType::A()),
                    io_message(NULL), endpoint(NULL), request_obuffer(0),
                    request_renderer(request_obuffer),
74
                    response_obuffer(new OutputBuffer(0))
75
76
77
78
79
    {
        vector<pair<string, uint16_t> > upstream;
        upstream.push_back(pair<string, uint16_t>(DEFAULT_REMOTE_ADDRESS, 53));
        server.setForwardAddresses(upstream);
    }
80
81
82
83
84
    ~RecursorTest() {
        delete io_message;
        delete endpoint;
    }
    MockSession notify_session;
Evan Hunt's avatar
Evan Hunt committed
85
    MockServer dnsserv;
86
    IOService ios;
87
88
    Recursor server;
    Message request_message;
89
    MessagePtr parse_message;
90
91
92
93
94
95
    const qid_t default_qid;
    const Opcode opcode;
    const Name qname;
    const RRClass qclass;
    const RRType qtype;
    IOMessage* io_message;
96
    IOSocket* io_sock;
97
98
99
    const IOEndpoint* endpoint;
    OutputBuffer request_obuffer;
    MessageRenderer request_renderer;
100
    OutputBufferPtr response_obuffer;
101
102
103
    vector<uint8_t> data;

    void createDataFromFile(const char* const datafile, int protocol);
Evan Hunt's avatar
Evan Hunt committed
104
    void createRequestPacket(Message& message, int protocol);
105
106
107
108
109
110
111
112
113
114
115
116
117
};

void
RecursorTest::createDataFromFile(const char* const datafile,
                                const int protocol = IPPROTO_UDP)
{
    delete io_message;
    data.clear();

    delete endpoint;
    endpoint = IOEndpoint::create(protocol,
                                  IOAddress(DEFAULT_REMOTE_ADDRESS), 5300);
    UnitTestUtil::readWireData(datafile, data);
118
119
    io_sock = new DummySocket(protocol);
    io_message = new IOMessage(&data[0], data.size(), *io_sock, *endpoint);
120
121
122
}

void
Evan Hunt's avatar
Evan Hunt committed
123
124
RecursorTest::createRequestPacket(Message& message,
                                  const int protocol = IPPROTO_UDP)
125
{
Evan Hunt's avatar
Evan Hunt committed
126
    message.toWire(request_renderer);
127
128

    delete io_message;
129

130
131
    endpoint = IOEndpoint::create(protocol,
                                  IOAddress(DEFAULT_REMOTE_ADDRESS), 5300);
132
    io_sock = new DummySocket(protocol);
133
134
    io_message = new IOMessage(request_renderer.getData(),
                               request_renderer.getLength(),
135
                               *io_sock, *endpoint);
136
137
}

Evan Hunt's avatar
Evan Hunt committed
138
139
140
141
142
143
144
145
146
147
148
// These are flags to indicate whether the corresponding flag bit of the
// DNS header is to be set in the test cases.  (Note that the flag values
// is irrelevant to their wire-format values)
const unsigned int QR_FLAG = 0x1;
const unsigned int AA_FLAG = 0x2;
const unsigned int TC_FLAG = 0x4;
const unsigned int RD_FLAG = 0x8;
const unsigned int RA_FLAG = 0x10;
const unsigned int AD_FLAG = 0x20;
const unsigned int CD_FLAG = 0x40;

149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
void
headerCheck(const Message& message, const qid_t qid, const Rcode& rcode,
            const uint16_t opcodeval, const unsigned int flags,
            const unsigned int qdcount,
            const unsigned int ancount, const unsigned int nscount,
            const unsigned int arcount)
{
    EXPECT_EQ(qid, message.getQid());
    EXPECT_EQ(rcode, message.getRcode());
    EXPECT_EQ(opcodeval, message.getOpcode().getCode());
    EXPECT_EQ((flags & QR_FLAG) != 0, message.getHeaderFlag(MessageFlag::QR()));
    EXPECT_EQ((flags & AA_FLAG) != 0, message.getHeaderFlag(MessageFlag::AA()));
    EXPECT_EQ((flags & TC_FLAG) != 0, message.getHeaderFlag(MessageFlag::TC()));
    EXPECT_EQ((flags & RA_FLAG) != 0, message.getHeaderFlag(MessageFlag::RA()));
    EXPECT_EQ((flags & RD_FLAG) != 0, message.getHeaderFlag(MessageFlag::RD()));
    EXPECT_EQ((flags & AD_FLAG) != 0, message.getHeaderFlag(MessageFlag::AD()));
    EXPECT_EQ((flags & CD_FLAG) != 0, message.getHeaderFlag(MessageFlag::CD()));

    EXPECT_EQ(qdcount, message.getRRCount(Section::QUESTION()));
    EXPECT_EQ(ancount, message.getRRCount(Section::ANSWER()));
    EXPECT_EQ(nscount, message.getRRCount(Section::AUTHORITY()));
    EXPECT_EQ(arcount, message.getRRCount(Section::ADDITIONAL()));
}

// Unsupported requests.  Should result in NOTIMP.
TEST_F(RecursorTest, unsupportedRequest) {
    for (unsigned int i = 0; i < 16; ++i) {
        // set Opcode to 'i', which iterators over all possible codes except
        // the standard query and notify
        if (i == Opcode::QUERY().getCode() ||
            i == Opcode::NOTIFY().getCode()) {
            continue;
        }
        createDataFromFile("simplequery_fromWire");
        data[2] = ((i << 3) & 0xff);

185
        parse_message->clear(Message::PARSE);
186
        server.processMessage(*io_message, parse_message,
Evan Hunt's avatar
Evan Hunt committed
187
188
                              response_obuffer, &dnsserv);
        EXPECT_TRUE(dnsserv.hasAnswer());
189
        headerCheck(*parse_message, default_qid, Rcode::NOTIMP(), i, QR_FLAG,
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
                    0, 0, 0, 0);
    }
}

// Simple API check
TEST_F(RecursorTest, verbose) {
    EXPECT_FALSE(server.getVerbose());
    server.setVerbose(true);
    EXPECT_TRUE(server.getVerbose());
    server.setVerbose(false);
    EXPECT_FALSE(server.getVerbose());
}

// Multiple questions.  Should result in FORMERR.
TEST_F(RecursorTest, multiQuestion) {
    createDataFromFile("multiquestion_fromWire");
Evan Hunt's avatar
Evan Hunt committed
206
207
    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
    EXPECT_TRUE(dnsserv.hasAnswer());
208
    headerCheck(*parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
209
210
                QR_FLAG, 2, 0, 0, 0);

211
    QuestionIterator qit = parse_message->beginQuestion();
212
213
214
215
216
217
218
219
    EXPECT_EQ(Name("example.com"), (*qit)->getName());
    EXPECT_EQ(RRClass::IN(), (*qit)->getClass());
    EXPECT_EQ(RRType::A(), (*qit)->getType());
    ++qit;
    EXPECT_EQ(Name("example.com"), (*qit)->getName());
    EXPECT_EQ(RRClass::IN(), (*qit)->getClass());
    EXPECT_EQ(RRType::AAAA(), (*qit)->getType());
    ++qit;
220
    EXPECT_TRUE(qit == parse_message->endQuestion());
221
222
223
224
225
226
}

// Incoming data doesn't even contain the complete header.  Must be silently
// dropped.
TEST_F(RecursorTest, shortMessage) {
    createDataFromFile("shortmessage_fromWire");
Evan Hunt's avatar
Evan Hunt committed
227
228
    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
    EXPECT_FALSE(dnsserv.hasAnswer());
229
230
231
232
233
234
235
}

// Response messages.  Must be silently dropped, whether it's a valid response
// or malformed or could otherwise cause a protocol error.
TEST_F(RecursorTest, response) {
    // A valid (although unusual) response
    createDataFromFile("simpleresponse_fromWire");
Evan Hunt's avatar
Evan Hunt committed
236
237
    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
    EXPECT_FALSE(dnsserv.hasAnswer());
238
239
240
241

    // A response with a broken question section.  must be dropped rather than
    // returning FORMERR.
    createDataFromFile("shortresponse_fromWire");
Evan Hunt's avatar
Evan Hunt committed
242
243
    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
    EXPECT_FALSE(dnsserv.hasAnswer());
244
245
246

    // A response to iquery.  must be dropped rather than returning NOTIMP.
    createDataFromFile("iqueryresponse_fromWire");
Evan Hunt's avatar
Evan Hunt committed
247
248
    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
    EXPECT_FALSE(dnsserv.hasAnswer());
249
250
251
252
253
}

// Query with a broken question
TEST_F(RecursorTest, shortQuestion) {
    createDataFromFile("shortquestion_fromWire");
Evan Hunt's avatar
Evan Hunt committed
254
255
    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
    EXPECT_TRUE(dnsserv.hasAnswer());
256
257
    // Since the query's question is broken, the question section of the
    // response should be empty.
258
    headerCheck(*parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
259
260
261
262
263
264
                QR_FLAG, 0, 0, 0, 0);
}

// Query with a broken answer section
TEST_F(RecursorTest, shortAnswer) {
    createDataFromFile("shortanswer_fromWire");
Evan Hunt's avatar
Evan Hunt committed
265
266
    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
    EXPECT_TRUE(dnsserv.hasAnswer());
267
268
269

    // This is a bogus query, but question section is valid.  So the response
    // should copy the question section.
270
    headerCheck(*parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
271
272
                QR_FLAG, 1, 0, 0, 0);

273
    QuestionIterator qit = parse_message->beginQuestion();
274
275
276
277
    EXPECT_EQ(Name("example.com"), (*qit)->getName());
    EXPECT_EQ(RRClass::IN(), (*qit)->getClass());
    EXPECT_EQ(RRType::A(), (*qit)->getType());
    ++qit;
278
    EXPECT_TRUE(qit == parse_message->endQuestion());
279
280
281
282
283
}

// Query with unsupported version of EDNS.
TEST_F(RecursorTest, ednsBadVers) {
    createDataFromFile("queryBadEDNS_fromWire");
Evan Hunt's avatar
Evan Hunt committed
284
285
    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
    EXPECT_TRUE(dnsserv.hasAnswer());
286
287
288
289

    // The response must have an EDNS OPT RR in the additional section.
    // Note that the DNSSEC DO bit is cleared even if this bit in the query
    // is set.  This is a limitation of the current implementation.
290
    headerCheck(*parse_message, default_qid, Rcode::BADVERS(), opcode.getCode(),
291
                QR_FLAG, 1, 0, 0, 1);
292
293
    EXPECT_EQ(4096, parse_message->getUDPSize());
    EXPECT_FALSE(parse_message->isDNSSECSupported());
294
295
296
297
}

TEST_F(RecursorTest, AXFROverUDP) {
    // AXFR over UDP is invalid and should result in FORMERR.
Evan Hunt's avatar
Evan Hunt committed
298
299
300
301
302
    UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
                        Name("example.com"), RRClass::IN(), RRType::AXFR());
    createRequestPacket(request_message, IPPROTO_UDP);
    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
    EXPECT_TRUE(dnsserv.hasAnswer());
303
    headerCheck(*parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
304
305
306
307
                QR_FLAG, 1, 0, 0, 0);
}

TEST_F(RecursorTest, AXFRFail) {
Evan Hunt's avatar
Evan Hunt committed
308
309
310
311
    UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
                                       Name("example.com"), RRClass::IN(),
                                       RRType::AXFR());
    createRequestPacket(request_message, IPPROTO_TCP);
312
    // AXFR is not implemented and should always send NOTIMP.
Evan Hunt's avatar
Evan Hunt committed
313
314
    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
    EXPECT_TRUE(dnsserv.hasAnswer());
315
    headerCheck(*parse_message, default_qid, Rcode::NOTIMP(), opcode.getCode(),
316
317
318
319
320
321
322
323
324
                QR_FLAG, 1, 0, 0, 0);
}

TEST_F(RecursorTest, notifyFail) {
    // Notify should always return NOTAUTH
    request_message.clear(Message::RENDER);
    request_message.setOpcode(Opcode::NOTIFY());
    request_message.setHeaderFlag(MessageFlag::AA());
    request_message.setQid(default_qid);
Evan Hunt's avatar
Evan Hunt committed
325
326
327
328
    request_message.setHeaderFlag(MessageFlag::AA());
    createRequestPacket(request_message, IPPROTO_UDP);
    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
    EXPECT_TRUE(dnsserv.hasAnswer());
329
    headerCheck(*parse_message, default_qid, Rcode::NOTAUTH(),
330
331
332
                Opcode::NOTIFY().getCode(), QR_FLAG, 0, 0, 0, 0);
}

333
334
class RecursorConfig : public ::testing::Test {
    public:
335
        IOService service;
336
        Recursor server;
337
338
339
340
341
342
        RecursorConfig() :
            service(NULL, NULL, NULL)
        {
            server.setIOService(service);
        }
        void invalidTest(const string &JOSN);
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
};

TEST_F(RecursorConfig, forwardAddresses) {
    // Default value should be fully recursive
    EXPECT_TRUE(server.getForwardAddresses().empty());
    EXPECT_FALSE(server.isForwarding());

    // Try putting there some addresses
    vector<pair<string, uint16_t> > addresses;
    addresses.push_back(pair<string, uint16_t>(DEFAULT_REMOTE_ADDRESS, 53));
    addresses.push_back(pair<string, uint16_t>("::1", 53));
    server.setForwardAddresses(addresses);
    EXPECT_EQ(2, server.getForwardAddresses().size());
    EXPECT_EQ("::1", server.getForwardAddresses()[1].first);
    EXPECT_TRUE(server.isForwarding());

    // Is it independent from what we do with the vector later?
    addresses.clear();
    EXPECT_EQ(2, server.getForwardAddresses().size());

    // Did it return to fully recursive?
    server.setForwardAddresses(addresses);
    EXPECT_TRUE(server.getForwardAddresses().empty());
    EXPECT_FALSE(server.isForwarding());
}

TEST_F(RecursorConfig, forwardAddressConfig) {
    // Try putting there some address
    ElementPtr config(Element::fromJSON("{"
Michal Vaner's avatar
Michal Vaner committed
372
        "\"forward_addresses/\": ["
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
        "   {"
        "       \"address\": \"192.0.2.1\","
        "       \"port\": 53"
        "   }"
        "]"
        "}"));
    ConstElementPtr result(server.updateConfig(config));
    EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
    EXPECT_TRUE(server.isForwarding());
    ASSERT_EQ(1, server.getForwardAddresses().size());
    EXPECT_EQ("192.0.2.1", server.getForwardAddresses()[0].first);
    EXPECT_EQ(53, server.getForwardAddresses()[0].second);

    // And then remove all addresses
    config = Element::fromJSON("{"
Michal Vaner's avatar
Michal Vaner committed
388
        "\"forward_addresses/\": null"
389
390
391
392
393
394
395
        "}");
    result = server.updateConfig(config);
    EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
    EXPECT_FALSE(server.isForwarding());
    EXPECT_EQ(0, server.getForwardAddresses().size());
}

396
void RecursorConfig::invalidTest(const string &JOSN) {
397
398
399
400
401
402
403
    ElementPtr config(Element::fromJSON(JOSN));
    EXPECT_FALSE(server.updateConfig(config)->equals(
        *isc::config::createAnswer())) << "Accepted config " << JOSN << endl;
}

TEST_F(RecursorConfig, invalidForwardAddresses) {
    // Try torturing it with some invalid inputs
404
    invalidTest("{"
Michal Vaner's avatar
Michal Vaner committed
405
        "\"forward_addresses/\": \"error\""
406
        "}");
407
    invalidTest("{"
Michal Vaner's avatar
Michal Vaner committed
408
        "\"forward_addresses/\": [{}]"
409
        "}");
410
    invalidTest("{"
Michal Vaner's avatar
Michal Vaner committed
411
        "\"forward_addresses/\": [{"
412
413
414
        "   \"port\": 1.5,"
        "   \"address\": \"192.0.2.1\""
        "}]}");
415
    invalidTest("{"
Michal Vaner's avatar
Michal Vaner committed
416
        "\"forward_addresses/\": [{"
417
418
419
        "   \"port\": -5,"
        "   \"address\": \"192.0.2.1\""
        "}]}");
420
    invalidTest("{"
Michal Vaner's avatar
Michal Vaner committed
421
        "\"forward_addresses/\": [{"
422
423
424
425
426
        "   \"port\": 53,"
        "   \"address\": \"bad_address\""
        "}]}");
}

427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
TEST_F(RecursorConfig, listenAddresses) {
    // Default value should be fully recursive
    EXPECT_TRUE(server.getListenAddresses().empty());

    // Try putting there some addresses
    vector<pair<string, uint16_t> > addresses;
    addresses.push_back(pair<string, uint16_t>("127.0.0.1", 5300));
    addresses.push_back(pair<string, uint16_t>("::1", 5300));
    server.setListenAddresses(addresses);
    EXPECT_EQ(2, server.getListenAddresses().size());
    EXPECT_EQ("::1", server.getListenAddresses()[1].first);

    // Is it independent from what we do with the vector later?
    addresses.clear();
    EXPECT_EQ(2, server.getListenAddresses().size());

    // Did it return to fully recursive?
    server.setListenAddresses(addresses);
    EXPECT_TRUE(server.getListenAddresses().empty());
}

Michal Vaner's avatar
Michal Vaner committed
448
TEST_F(RecursorConfig, DISABLED_listenAddressConfig) {
449
450
    // Try putting there some address
    ElementPtr config(Element::fromJSON("{"
Michal Vaner's avatar
Michal Vaner committed
451
        "\"listen_on/\": ["
452
453
454
455
456
457
458
459
460
461
462
463
464
465
        "   {"
        "       \"address\": \"127.0.0.1\","
        "       \"port\": 5300"
        "   }"
        "]"
        "}"));
    ConstElementPtr result(server.updateConfig(config));
    EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
    ASSERT_EQ(1, server.getListenAddresses().size());
    EXPECT_EQ("127.0.0.1", server.getListenAddresses()[0].first);
    EXPECT_EQ(5300, server.getListenAddresses()[0].second);

    // As this is example address, the machine should not have it on
    // any interface
Michal Vaner's avatar
Michal Vaner committed
466
467
468
    // FIXME: This test aborts, because it tries to rollback and
    //     it is impossible, since the sockets are not closed.
    //     Once #388 is solved, enable this test.
469
    config = Element::fromJSON("{"
Michal Vaner's avatar
Michal Vaner committed
470
        "\"listen_on/\": ["
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
        "   {"
        "       \"address\": \"192.0.2.0\","
        "       \"port\": 5300"
        "   }"
        "]"
        "}");
    result = server.updateConfig(config);
    EXPECT_FALSE(result->equals(*isc::config::createAnswer()));
    ASSERT_EQ(1, server.getListenAddresses().size());
    EXPECT_EQ("127.0.0.1", server.getListenAddresses()[0].first);
    EXPECT_EQ(5300, server.getListenAddresses()[0].second);
}

TEST_F(RecursorConfig, invalidListenAddresses) {
    // Try torturing it with some invalid inputs
    invalidTest("{"
Michal Vaner's avatar
Michal Vaner committed
487
        "\"listen_on/\": \"error\""
488
489
        "}");
    invalidTest("{"
Michal Vaner's avatar
Michal Vaner committed
490
        "\"listen_on/\": [{}]"
491
492
        "}");
    invalidTest("{"
Michal Vaner's avatar
Michal Vaner committed
493
        "\"listen_on/\": [{"
494
495
496
497
        "   \"port\": 1.5,"
        "   \"address\": \"192.0.2.1\""
        "}]}");
    invalidTest("{"
Michal Vaner's avatar
Michal Vaner committed
498
        "\"listen_on/\": [{"
499
500
501
502
        "   \"port\": -5,"
        "   \"address\": \"192.0.2.1\""
        "}]}");
    invalidTest("{"
Michal Vaner's avatar
Michal Vaner committed
503
        "\"listen_on/\": [{"
504
505
506
507
508
        "   \"port\": 53,"
        "   \"address\": \"bad_address\""
        "}]}");
}

509
}