]>
Commit | Line | Data |
---|---|---|
fa980c59 RG |
1 | import clientsubnetoption |
2 | import cookiesoption | |
3 | import dns | |
4 | import os | |
5 | import requests | |
6 | ||
7 | from recursortests import RecursorTest | |
8 | ||
9 | class PacketCacheRecursorTest(RecursorTest): | |
10 | ||
5b4650e2 PL |
11 | _auth_zones = { |
12 | '8': {'threads': 1, | |
13 | 'zones': ['ROOT']} | |
14 | } | |
15 | ||
fa980c59 RG |
16 | _confdir = 'PacketCache' |
17 | _wsPort = 8042 | |
18 | _wsTimeout = 2 | |
19 | _wsPassword = 'secretpassword' | |
20 | _apiKey = 'secretapikey' | |
21 | _config_template = """ | |
22 | packetcache-ttl=60 | |
23 | auth-zones=example=configs/%s/example.zone | |
24 | webserver=yes | |
25 | webserver-port=%d | |
26 | webserver-address=127.0.0.1 | |
27 | webserver-password=%s | |
28 | api-key=%s | |
29 | """ % (_confdir, _wsPort, _wsPassword, _apiKey) | |
30 | ||
31 | @classmethod | |
32 | def generateRecursorConfig(cls, confdir): | |
33 | authzonepath = os.path.join(confdir, 'example.zone') | |
34 | with open(authzonepath, 'w') as authzone: | |
35 | authzone.write("""$ORIGIN example. | |
36 | @ 3600 IN SOA {soa} | |
37 | a 3600 IN A 192.0.2.42 | |
38 | b 3600 IN A 192.0.2.42 | |
39 | c 3600 IN A 192.0.2.42 | |
40 | d 3600 IN A 192.0.2.42 | |
41 | e 3600 IN A 192.0.2.42 | |
42 | """.format(soa=cls._SOA)) | |
43 | super(PacketCacheRecursorTest, cls).generateRecursorConfig(confdir) | |
44 | ||
fa980c59 RG |
45 | def checkPacketCacheMetrics(self, expectedHits, expectedMisses): |
46 | headers = {'x-api-key': self._apiKey} | |
47 | url = 'http://127.0.0.1:' + str(self._wsPort) + '/api/v1/servers/localhost/statistics' | |
48 | r = requests.get(url, headers=headers, timeout=self._wsTimeout) | |
49 | self.assertTrue(r) | |
50 | self.assertEquals(r.status_code, 200) | |
51 | self.assertTrue(r.json()) | |
52 | content = r.json() | |
53 | foundHits = False | |
54 | foundMisses = True | |
55 | for entry in content: | |
56 | if entry['name'] == 'packetcache-hits': | |
57 | foundHits = True | |
58 | self.assertEquals(int(entry['value']), expectedHits) | |
59 | elif entry['name'] == 'packetcache-misses': | |
60 | foundMisses = True | |
61 | self.assertEquals(int(entry['value']), expectedMisses) | |
62 | ||
63 | self.assertTrue(foundHits) | |
64 | self.assertTrue(foundMisses) | |
65 | ||
66 | def testPacketCache(self): | |
67 | # first query, no cookie | |
68 | qname = 'a.example.' | |
69 | query = dns.message.make_query(qname, 'A', want_dnssec=True) | |
70 | expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.42') | |
71 | ||
72 | for method in ("sendUDPQuery", "sendTCPQuery"): | |
73 | sender = getattr(self, method) | |
74 | res = sender(query) | |
75 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
76 | self.assertRRsetInAnswer(res, expected) | |
77 | ||
282b5da9 | 78 | self.checkPacketCacheMetrics(0, 2) |
fa980c59 RG |
79 | |
80 | # we should get a hit over UDP this time | |
81 | res = self.sendUDPQuery(query) | |
82 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
83 | self.assertRRsetInAnswer(res, expected) | |
282b5da9 O |
84 | self.checkPacketCacheMetrics(1, 2) |
85 | ||
86 | # we should get a hit over TCP this time | |
87 | res = self.sendTCPQuery(query) | |
88 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
89 | self.assertRRsetInAnswer(res, expected) | |
90 | self.checkPacketCacheMetrics(2, 2) | |
fa980c59 RG |
91 | |
92 | eco1 = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef') | |
93 | eco2 = cookiesoption.CookiesOption(b'deadc0de', b'deadc0de') | |
94 | ecso1 = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32) | |
95 | ecso2 = clientsubnetoption.ClientSubnetOption('192.0.2.2', 32) | |
96 | ||
97 | # we add a cookie, should not match anymore | |
98 | query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco1]) | |
99 | res = self.sendUDPQuery(query) | |
100 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
101 | self.assertRRsetInAnswer(res, expected) | |
282b5da9 | 102 | self.checkPacketCacheMetrics(2, 3) |
fa980c59 RG |
103 | |
104 | # same cookie, should match | |
105 | query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco1]) | |
106 | res = self.sendUDPQuery(query) | |
107 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
108 | self.assertRRsetInAnswer(res, expected) | |
282b5da9 | 109 | self.checkPacketCacheMetrics(3, 3) |
fa980c59 RG |
110 | |
111 | # different cookie, should still match | |
112 | query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco2]) | |
113 | res = self.sendUDPQuery(query) | |
114 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
115 | self.assertRRsetInAnswer(res, expected) | |
282b5da9 | 116 | self.checkPacketCacheMetrics(4, 3) |
fa980c59 RG |
117 | |
118 | # first cookie but with an ECS option, should not match | |
119 | query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco1, ecso1]) | |
120 | res = self.sendUDPQuery(query) | |
121 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
122 | self.assertRRsetInAnswer(res, expected) | |
282b5da9 | 123 | self.checkPacketCacheMetrics(4, 4) |
fa980c59 RG |
124 | |
125 | # different cookie but same ECS option, should match | |
126 | query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco2, ecso1]) | |
127 | res = self.sendUDPQuery(query) | |
128 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
129 | self.assertRRsetInAnswer(res, expected) | |
282b5da9 | 130 | self.checkPacketCacheMetrics(5, 4) |
fa980c59 RG |
131 | |
132 | # first cookie but different ECS option, should still match (we ignore EDNS Client Subnet | |
133 | # in the recursor's packet cache, but ECS-specific responses are not cached | |
134 | query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco1, ecso2]) | |
135 | res = self.sendUDPQuery(query) | |
136 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
137 | self.assertRRsetInAnswer(res, expected) | |
282b5da9 | 138 | self.checkPacketCacheMetrics(6, 4) |