]> gitweb.michael.orlitzky.com - mjo-overlay.git/blob - net-dns/rbldnsd/files/rbldnsd-0.997a-robust-ipv6-test-support.patch
Add rbldnsd in preparation for fixing two bugs.
[mjo-overlay.git] / net-dns / rbldnsd / files / rbldnsd-0.997a-robust-ipv6-test-support.patch
1 diff --git a/NEWS b/NEWS
2 index 8d8bdd9..4d8c01d 100644
3 --- a/NEWS
4 +++ b/NEWS
5 @@ -1,6 +1,19 @@
6 This file describes user-visible changes in rbldnsd.
7 Newer news is at the top.
8
9 +Next release
10 +
11 + - fix tests for systems without ipv6 support, or when ipv6 is
12 + disabled in rbldnsd at compile-time
13 +
14 + - fix tests for API change in pydns >= 2.3.6
15 +
16 + - It is no longer an error to request binding to a particular
17 + address/port more than once. (The subsequent requests are simply
18 + ignored.) (This avoids confusion on certain systems/configurations
19 + where gethostbyname("localhost") can return 127.0.0.1 multiple
20 + times.)
21 +
22 0.997a (23 Jul 2013)
23
24 - minor fixes/changes in packaging, no code changes.
25 diff --git a/rbldnsd.c b/rbldnsd.c
26 index abf1d01..8322bdd 100644
27 --- a/rbldnsd.c
28 +++ b/rbldnsd.c
29 @@ -203,10 +203,79 @@ static volatile int signalled;
30 #define SIGNALLED_ZSTATS 0x10
31 #define SIGNALLED_TERM 0x20
32
33 +static inline int sockaddr_in_equal(const struct sockaddr_in *addr1,
34 + const struct sockaddr_in *addr2)
35 +{
36 + return (addr1->sin_port == addr2->sin_port
37 + && addr1->sin_addr.s_addr == addr2->sin_addr.s_addr);
38 +}
39 +
40 +#ifndef NO_IPv6
41 +static inline int sockaddr_in6_equal(const struct sockaddr_in6 *addr1,
42 + const struct sockaddr_in6 *addr2)
43 +{
44 + if (memcmp(addr1->sin6_addr.s6_addr, addr2->sin6_addr.s6_addr, 16) != 0)
45 + return 0;
46 + return (addr1->sin6_port == addr2->sin6_port
47 + && addr1->sin6_flowinfo == addr2->sin6_flowinfo
48 + && addr1->sin6_scope_id == addr2->sin6_scope_id);
49 +}
50 +#endif
51 +
52 +static inline int sockaddr_equal(const struct sockaddr *addr1,
53 + const struct sockaddr *addr2)
54 +{
55 + if (addr1->sa_family != addr2->sa_family)
56 + return 0;
57 + switch (addr1->sa_family) {
58 + case AF_INET:
59 + return sockaddr_in_equal((const struct sockaddr_in *)addr1,
60 + (const struct sockaddr_in *)addr2);
61 +#ifndef NO_IPv6
62 + return sockaddr_in6_equal((const struct sockaddr_in6 *)addr1,
63 + (const struct sockaddr_in6 *)addr2);
64 +#endif
65 + default:
66 + error(0, "unknown address family (%d)", addr1->sa_family);
67 + }
68 +}
69 +
70 +/* already_bound(addr, addrlen)
71 + *
72 + * Determine whether we've already bound to a particular address.
73 + * This is here mostly to deal with the fact that on certain systems,
74 + * gethostbyname()/getaddrinfo() can return a duplicate 127.0.0.1
75 + * for 'localhost'. See
76 + * - http://sourceware.org/bugzilla/show_bug.cgi?id=4980
77 + * - https://bugzilla.redhat.com/show_bug.cgi?id=496300
78 + */
79 +static int already_bound(const struct sockaddr *addr, socklen_t addrlen) {
80 +#ifdef NO_IPv6
81 + struct sockaddr_in addr_buf;
82 +#else
83 + struct sockaddr_in6 addr_buf;
84 +#endif
85 + struct sockaddr *boundaddr = (struct sockaddr *)&addr_buf;
86 + socklen_t buflen;
87 + int i;
88 +
89 + for (i = 0; i < numsock; i++) {
90 + buflen = sizeof(addr_buf);
91 + if (getsockname(sock[i], boundaddr, &buflen) < 0)
92 + error(errno, "getsockname failed");
93 + if (buflen == addrlen && sockaddr_equal(boundaddr, addr))
94 + return 1;
95 + }
96 + return 0;
97 +}
98 +
99 #ifdef NO_IPv6
100 static void newsocket(struct sockaddr_in *sin) {
101 int fd;
102 const char *host = ip4atos(ntohl(sin->sin_addr.s_addr));
103 +
104 + if (already_bound((struct sockaddr *)sin, sizeof(*sin)))
105 + return;
106 if (numsock >= MAXSOCK)
107 error(0, "too many listening sockets (%d max)", MAXSOCK);
108 fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
109 @@ -223,6 +292,8 @@ static int newsocket(struct addrinfo *ai) {
110 int fd;
111 char host[NI_MAXHOST], serv[NI_MAXSERV];
112
113 + if (already_bound(ai->ai_addr, ai->ai_addrlen))
114 + return 1;
115 if (numsock >= MAXSOCK)
116 error(0, "too many listening sockets (%d max)", MAXSOCK);
117 fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
118 diff --git a/rbldnsd.py b/rbldnsd.py
119 index 9300ef2..4b78dee 100644
120 --- a/rbldnsd.py
121 +++ b/rbldnsd.py
122 @@ -2,6 +2,7 @@
123
124
125 """
126 +import errno
127 from itertools import count
128 import subprocess
129 from tempfile import NamedTemporaryFile, TemporaryFile
130 @@ -12,6 +13,14 @@ try:
131 import DNS
132 except ImportError:
133 raise RuntimeError("The pydns library is not installed")
134 +try:
135 + from DNS import SocketError as DNS_SocketError
136 +except ImportError:
137 + class DNS_SocketError(Exception):
138 + """ Dummy, never raised.
139 +
140 + (Older versions of pydns before 2.3.6 do not raise SocketError.)
141 + """
142
143 DUMMY_ZONE_HEADER = """
144 $SOA 0 example.org. hostmaster.example.com. 0 1h 1h 2d 1h
145 @@ -113,7 +122,6 @@ class Rbldnsd(object):
146 stderr=self.stderr)
147
148 # wait for rbldnsd to start responding
149 - time.sleep(0.1)
150 for retry in count():
151 if daemon.poll() is not None:
152 raise DaemonError(
153 @@ -124,12 +132,18 @@ class Rbldnsd(object):
154 break
155 except QueryRefused:
156 break
157 + except DNS_SocketError as ex:
158 + # pydns >= 2.3.6
159 + wrapped_error = ex.args[0]
160 + if wrapped_error.errno != errno.ECONNREFUSED:
161 + raise
162 except DNS.DNSError as ex:
163 + # pydns < 2.3.6
164 if str(ex) != 'no working nameservers found':
165 raise
166 - elif retries > 10:
167 - raise DaemonError(
168 - "rbldnsd does not seem to be responding")
169 + if retry > 10:
170 + raise DaemonError("rbldnsd does not seem to be responding")
171 + time.sleep(0.1)
172
173 def _stop_daemon(self):
174 daemon = self._daemon
175 @@ -150,6 +164,22 @@ class Rbldnsd(object):
176 raise DaemonError("rbldnsd exited with code %d"
177 % daemon.returncode)
178
179 + @property
180 + def no_ipv6(self):
181 + """ Was rbldnsd compiled with -DNO_IPv6?
182 + """
183 + # If rbldnsd was compiled with -DNO_IPv6, the (therefore
184 + # unsupported) '-6' command-line switch will not be described
185 + # in the help message
186 + cmd = [self.daemon_bin, '-h']
187 + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
188 + help_message = proc.stdout.readlines()
189 + if proc.wait() != 0:
190 + raise subprocess.CalledProcessError(proc.returncode, cmd)
191 + return not any(line.lstrip().startswith('-6 ')
192 + for line in help_message)
193 +
194 +
195 class TestRbldnsd(unittest.TestCase):
196 def test(self):
197 rbldnsd = Rbldnsd()
198 diff --git a/test_acl.py b/test_acl.py
199 index d93ca0a..10bed1c 100644
200 --- a/test_acl.py
201 +++ b/test_acl.py
202 @@ -1,5 +1,8 @@
203 """ Tests for the acl dataset
204 """
205 +from functools import wraps
206 +import socket
207 +import sys
208 from tempfile import NamedTemporaryFile
209 import unittest
210
211 @@ -9,6 +12,35 @@ __all__ = [
212 'TestAclDataset',
213 ]
214
215 +try:
216 + from unittest import skipIf
217 +except ImportError:
218 + # hokey replacement (for python <= 2.6)
219 + def skipIf(condition, reason):
220 + if condition:
221 + def decorate(f):
222 + @wraps(f)
223 + def skipped(*args, **kw):
224 + sys.stderr.write("skipped test: %s " % reason)
225 + return skipped
226 + return decorate
227 + else:
228 + return lambda f: f
229 +
230 +def _have_ipv6():
231 + # Check for IPv6 support
232 + if not getattr(socket, 'has_ipv6', False):
233 + return False # no python support for ipv6
234 + elif Rbldnsd().no_ipv6:
235 + return False # rbldnsd compiled with -DNO_IPv6
236 + try:
237 + socket.socket(socket.AF_INET6, socket.SOCK_DGRAM).close()
238 + except socket.error:
239 + return False # no kernel (or libc) support for ipv6?
240 + return True
241 +
242 +no_ipv6 = not _have_ipv6()
243 +
244 def daemon(acl, addr='localhost'):
245 """ Create an Rbldnsd instance with given ACL
246 """
247 @@ -33,11 +65,13 @@ class TestAclDataset(unittest.TestCase):
248 addr='127.0.0.1') as dnsd:
249 self.assertEqual(dnsd.query('test.example.com'), 'Success')
250
251 + @skipIf(no_ipv6, "IPv6 unsupported")
252 def test_refuse_ipv6(self):
253 with daemon(acl=["::1 :refuse"],
254 addr='::1') as dnsd:
255 self.assertRaises(QueryRefused, dnsd.query, 'test.example.com')
256
257 + @skipIf(no_ipv6, "IPv6 unsupported")
258 def test_pass_ipv6(self):
259 with daemon(acl=[ "0/0 :refuse",
260 "0::1 :pass" ],
261 diff --git a/test_ip4trie.py b/test_ip4trie.py
262 index fe9e78f..2cce09b 100644
263 --- a/test_ip4trie.py
264 +++ b/test_ip4trie.py
265 @@ -9,7 +9,7 @@ __all__ = [
266 ]
267
268 def ip4trie(zone_data):
269 - """ Run rbldnsd with an ip6trie dataset
270 + """ Run rbldnsd with an ip4trie dataset
271 """
272 dnsd = Rbldnsd()
273 dnsd.add_dataset('ip4trie', ZoneFile(zone_data))
274 diff --git a/test_ip6trie.py b/test_ip6trie.py
275 index d3600db..377c5dd 100644
276 --- a/test_ip6trie.py
277 +++ b/test_ip6trie.py
278 @@ -15,15 +15,6 @@ def ip6trie(zone_data):
279 dnsd.add_dataset('ip6trie', ZoneFile(zone_data))
280 return dnsd
281
282 -def rfc3152(ip6addr, domain='example.com'):
283 - from socket import inet_pton, AF_INET6
284 - from struct import unpack
285 -
286 - bytes = unpack("16B", inet_pton(AF_INET6, ip6addr))
287 - nibbles = '.'.join("%x.%x" % (byte & 0xf, (byte >> 4) & 0xf)
288 - for byte in reversed(bytes))
289 - return "%s.%s" % (nibbles, domain)
290 -
291 class TestIp6TrieDataset(unittest.TestCase):
292 def test_exclusion(self):
293 with ip6trie(["dead::/16 listed",
294 @@ -31,5 +22,35 @@ class TestIp6TrieDataset(unittest.TestCase):
295 self.assertEqual(dnsd.query(rfc3152("dead::beef")), None)
296 self.assertEqual(dnsd.query(rfc3152("dead::beee")), "listed")
297
298 +
299 +def rfc3152(ip6addr, domain='example.com'):
300 + return "%s.%s" % ('.'.join(reversed(_to_nibbles(ip6addr))), domain)
301 +
302 +def _to_nibbles(ip6addr):
303 + """ Convert ip6 address (in rfc4291 notation) to a sequence of nibbles
304 +
305 + NB: We avoid the use of socket.inet_pton(AF_INET6, ip6addr) here
306 + because it fails (with 'error: can't use AF_INET6, IPv6 is
307 + disabled') when python has been compiled without IPv6 support. See
308 + http://www.corpit.ru/pipermail/rbldnsd/2013q3/001181.html
309 +
310 + """
311 + def _split_words(addr):
312 + return [ int(w, 16) for w in addr.split(':') ] if addr else []
313 +
314 + if '::' in ip6addr:
315 + head, tail = [ _split_words(s) for s in ip6addr.split('::', 1) ]
316 + nzeros = 8 - len(head) - len(tail)
317 + assert nzeros >= 0
318 + words = head + [ 0 ] * nzeros + tail
319 + else:
320 + words = _split_words(ip6addr)
321 +
322 + assert len(words) == 8
323 + for word in words:
324 + assert 0 <= word <= 0xffff
325 +
326 + return ''.join("%04x" % word for word in words)
327 +
328 if __name__ == '__main__':
329 unittest.main()