]>
Commit | Line | Data |
---|---|---|
fa980c59 RG |
1 | import clientsubnetoption |
2 | import cookiesoption | |
3 | import dns | |
4 | import os | |
5 | import requests | |
628fcdb9 | 6 | import subprocess |
fa980c59 RG |
7 | |
8 | from recursortests import RecursorTest | |
9 | ||
10 | class PacketCacheRecursorTest(RecursorTest): | |
11 | ||
5b4650e2 PL |
12 | _auth_zones = { |
13 | '8': {'threads': 1, | |
14 | 'zones': ['ROOT']} | |
15 | } | |
16 | ||
fa980c59 RG |
17 | _confdir = 'PacketCache' |
18 | _wsPort = 8042 | |
19 | _wsTimeout = 2 | |
20 | _wsPassword = 'secretpassword' | |
21 | _apiKey = 'secretapikey' | |
22 | _config_template = """ | |
628fcdb9 O |
23 | packetcache-ttl=10 |
24 | packetcache-servfail-ttl=5 | |
fa980c59 RG |
25 | auth-zones=example=configs/%s/example.zone |
26 | webserver=yes | |
27 | webserver-port=%d | |
28 | webserver-address=127.0.0.1 | |
29 | webserver-password=%s | |
30 | api-key=%s | |
31 | """ % (_confdir, _wsPort, _wsPassword, _apiKey) | |
32 | ||
33 | @classmethod | |
34 | def generateRecursorConfig(cls, confdir): | |
35 | authzonepath = os.path.join(confdir, 'example.zone') | |
36 | with open(authzonepath, 'w') as authzone: | |
37 | authzone.write("""$ORIGIN example. | |
38 | @ 3600 IN SOA {soa} | |
39 | a 3600 IN A 192.0.2.42 | |
40 | b 3600 IN A 192.0.2.42 | |
41 | c 3600 IN A 192.0.2.42 | |
42 | d 3600 IN A 192.0.2.42 | |
43 | e 3600 IN A 192.0.2.42 | |
628fcdb9 | 44 | f 3600 IN CNAME f ; CNAME loop: dirty trick to get a ServFail in an authzone |
fa980c59 RG |
45 | """.format(soa=cls._SOA)) |
46 | super(PacketCacheRecursorTest, cls).generateRecursorConfig(confdir) | |
47 | ||
fa980c59 RG |
48 | def checkPacketCacheMetrics(self, expectedHits, expectedMisses): |
49 | headers = {'x-api-key': self._apiKey} | |
50 | url = 'http://127.0.0.1:' + str(self._wsPort) + '/api/v1/servers/localhost/statistics' | |
51 | r = requests.get(url, headers=headers, timeout=self._wsTimeout) | |
52 | self.assertTrue(r) | |
4bfebc93 | 53 | self.assertEqual(r.status_code, 200) |
fa980c59 RG |
54 | self.assertTrue(r.json()) |
55 | content = r.json() | |
56 | foundHits = False | |
57 | foundMisses = True | |
58 | for entry in content: | |
59 | if entry['name'] == 'packetcache-hits': | |
60 | foundHits = True | |
4bfebc93 | 61 | self.assertEqual(int(entry['value']), expectedHits) |
fa980c59 RG |
62 | elif entry['name'] == 'packetcache-misses': |
63 | foundMisses = True | |
4bfebc93 | 64 | self.assertEqual(int(entry['value']), expectedMisses) |
fa980c59 RG |
65 | |
66 | self.assertTrue(foundHits) | |
67 | self.assertTrue(foundMisses) | |
68 | ||
69 | def testPacketCache(self): | |
34e6e52f | 70 | self.waitForTCPSocket("127.0.0.1", self._wsPort) |
fa980c59 RG |
71 | # first query, no cookie |
72 | qname = 'a.example.' | |
73 | query = dns.message.make_query(qname, 'A', want_dnssec=True) | |
74 | expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.42') | |
75 | ||
76 | for method in ("sendUDPQuery", "sendTCPQuery"): | |
77 | sender = getattr(self, method) | |
78 | res = sender(query) | |
79 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
80 | self.assertRRsetInAnswer(res, expected) | |
81 | ||
282b5da9 | 82 | self.checkPacketCacheMetrics(0, 2) |
fa980c59 RG |
83 | |
84 | # we should get a hit over UDP this time | |
85 | res = self.sendUDPQuery(query) | |
86 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
87 | self.assertRRsetInAnswer(res, expected) | |
282b5da9 O |
88 | self.checkPacketCacheMetrics(1, 2) |
89 | ||
90 | # we should get a hit over TCP this time | |
91 | res = self.sendTCPQuery(query) | |
92 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
93 | self.assertRRsetInAnswer(res, expected) | |
94 | self.checkPacketCacheMetrics(2, 2) | |
fa980c59 RG |
95 | |
96 | eco1 = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef') | |
97 | eco2 = cookiesoption.CookiesOption(b'deadc0de', b'deadc0de') | |
98 | ecso1 = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32) | |
99 | ecso2 = clientsubnetoption.ClientSubnetOption('192.0.2.2', 32) | |
100 | ||
101 | # we add a cookie, should not match anymore | |
102 | query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco1]) | |
103 | res = self.sendUDPQuery(query) | |
104 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
105 | self.assertRRsetInAnswer(res, expected) | |
282b5da9 | 106 | self.checkPacketCacheMetrics(2, 3) |
fa980c59 RG |
107 | |
108 | # same cookie, should match | |
109 | query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco1]) | |
110 | res = self.sendUDPQuery(query) | |
111 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
112 | self.assertRRsetInAnswer(res, expected) | |
282b5da9 | 113 | self.checkPacketCacheMetrics(3, 3) |
fa980c59 RG |
114 | |
115 | # different cookie, should still match | |
116 | query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco2]) | |
117 | res = self.sendUDPQuery(query) | |
118 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
119 | self.assertRRsetInAnswer(res, expected) | |
282b5da9 | 120 | self.checkPacketCacheMetrics(4, 3) |
fa980c59 RG |
121 | |
122 | # first cookie but with an ECS option, should not match | |
123 | query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco1, ecso1]) | |
124 | res = self.sendUDPQuery(query) | |
125 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
126 | self.assertRRsetInAnswer(res, expected) | |
282b5da9 | 127 | self.checkPacketCacheMetrics(4, 4) |
fa980c59 RG |
128 | |
129 | # different cookie but same ECS option, should match | |
130 | query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco2, ecso1]) | |
131 | res = self.sendUDPQuery(query) | |
132 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
133 | self.assertRRsetInAnswer(res, expected) | |
282b5da9 | 134 | self.checkPacketCacheMetrics(5, 4) |
fa980c59 RG |
135 | |
136 | # first cookie but different ECS option, should still match (we ignore EDNS Client Subnet | |
137 | # in the recursor's packet cache, but ECS-specific responses are not cached | |
138 | query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco1, ecso2]) | |
139 | res = self.sendUDPQuery(query) | |
140 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
141 | self.assertRRsetInAnswer(res, expected) | |
282b5da9 | 142 | self.checkPacketCacheMetrics(6, 4) |
628fcdb9 O |
143 | |
144 | # NXDomain should get default packetcache TTL (10) | |
145 | query = dns.message.make_query('nxdomain.example.', 'A', want_dnssec=True) | |
146 | res = self.sendUDPQuery(query) | |
147 | self.assertRcodeEqual(res, dns.rcode.NXDOMAIN) | |
148 | self.checkPacketCacheMetrics(6, 5) | |
149 | ||
150 | # NoData should get default packetcache TTL (10) | |
151 | query = dns.message.make_query('a.example.', 'AAAA', want_dnssec=True) | |
152 | res = self.sendUDPQuery(query) | |
153 | self.assertRcodeEqual(res, dns.rcode.NOERROR) | |
154 | self.checkPacketCacheMetrics(6, 6) | |
155 | ||
156 | # ServFail should get ServFail TTL (5) | |
157 | query = dns.message.make_query('f.example.', 'A', want_dnssec=True) | |
158 | res = self.sendUDPQuery(query) | |
159 | self.assertRcodeEqual(res, dns.rcode.SERVFAIL) | |
160 | self.checkPacketCacheMetrics(6, 7) | |
161 | ||
a4a0c6a8 | 162 | # We peek into the cache to check TTLs and allow TTLs to be one lower than inserted since the clock might have ticked |
628fcdb9 O |
163 | rec_controlCmd = [os.environ['RECCONTROL'], |
164 | '--config-dir=%s' % 'configs/' + self._confdir, | |
165 | 'dump-cache', '-'] | |
166 | try: | |
167 | ret = subprocess.check_output(rec_controlCmd, stderr=subprocess.STDOUT) | |
168 | self.assertTrue((b"a.example. 10 A ; tag 0 udp\n" in ret) or (b"a.example. 9 A ; tag 0 udp\n" in ret)) | |
169 | self.assertTrue((b"nxdomain.example. 10 A ; tag 0 udp\n" in ret) or (b"nxdomain.example. 9 A ; tag 0 udp\n" in ret)) | |
170 | self.assertTrue((b"a.example. 10 AAAA ; tag 0 udp\n" in ret) or (b"a.example. 9 AAAA ; tag 0 udp\n" in ret)) | |
171 | self.assertTrue((b"f.example. 5 A ; tag 0 udp\n" in ret) or (b"f.example. 4 A ; tag 0 udp\n" in ret)) | |
172 | ||
173 | except subprocess.CalledProcessError as e: | |
174 | print(e.output) | |
175 | raise | |
176 |