1# coding: utf-8
2"""Tests for the eventlet.support.greendns module"""
3
4import os
5import socket
6import tempfile
7import time
8from dns.resolver import NoAnswer, Answer, Resolver
9
10from eventlet.support import greendns
11from eventlet.support.greendns import dns
12import tests
13import tests.mock
14
15
16def _make_host_resolver():
17    """Returns a HostResolver instance
18
19    The hosts file will be empty but accessible as a py.path.local
20    instance using the ``hosts`` attribute.
21    """
22    hosts = tempfile.NamedTemporaryFile()
23    hr = greendns.HostsResolver(fname=hosts.name)
24    hr.hosts = hosts
25    hr._last_stat = 0
26    return hr
27
28
29class TestHostsResolver(tests.LimitedTestCase):
30
31    def test_default_fname(self):
32        hr = greendns.HostsResolver()
33        assert os.path.exists(hr.fname)
34
35    def test_readlines_lines(self):
36        hr = _make_host_resolver()
37        hr.hosts.write(b'line0\n')
38        hr.hosts.flush()
39        assert list(hr._readlines()) == ['line0']
40        hr._last_stat = 0
41        hr.hosts.write(b'line1\n')
42        hr.hosts.flush()
43        assert list(hr._readlines()) == ['line0', 'line1']
44        # Test reading of varied newline styles
45        hr._last_stat = 0
46        hr.hosts.seek(0)
47        hr.hosts.truncate()
48        hr.hosts.write(b'\naa\r\nbb\r  cc  \n\n\tdd ee')
49        hr.hosts.flush()
50        assert list(hr._readlines()) == ['aa', 'bb', 'cc', 'dd ee']
51        # Test comments, including inline comments
52        hr._last_stat = 0
53        hr.hosts.seek(0)
54        hr.hosts.truncate()
55        hr.hosts.write(b'''\
56# First couple lines
57# are comments.
58line1
59#comment
60line2 # inline comment
61''')
62        hr.hosts.flush()
63        assert list(hr._readlines()) == ['line1', 'line2']
64
65    def test_readlines_missing_file(self):
66        hr = _make_host_resolver()
67        hr.hosts.close()
68        hr._last_stat = 0
69        assert list(hr._readlines()) == []
70
71    def test_load_no_contents(self):
72        hr = _make_host_resolver()
73        hr._load()
74        assert not hr._v4
75        assert not hr._v6
76        assert not hr._aliases
77
78    def test_load_v4_v6_cname_aliases(self):
79        hr = _make_host_resolver()
80        hr.hosts.write(b'1.2.3.4 v4.example.com v4\n'
81                       b'dead:beef::1 v6.example.com v6\n')
82        hr.hosts.flush()
83        hr._load()
84        assert hr._v4 == {'v4.example.com': '1.2.3.4', 'v4': '1.2.3.4'}
85        assert hr._v6 == {'v6.example.com': 'dead:beef::1',
86                          'v6': 'dead:beef::1'}
87        assert hr._aliases == {'v4': 'v4.example.com',
88                               'v6': 'v6.example.com'}
89
90    def test_load_v6_link_local(self):
91        hr = _make_host_resolver()
92        hr.hosts.write(b'fe80:: foo\n'
93                       b'fe80:dead:beef::1 bar\n')
94        hr.hosts.flush()
95        hr._load()
96        assert not hr._v4
97        assert not hr._v6
98
99    def test_query_A(self):
100        hr = _make_host_resolver()
101        hr._v4 = {'v4.example.com': '1.2.3.4'}
102        ans = hr.query('v4.example.com')
103        assert ans[0].address == '1.2.3.4'
104
105    def test_query_ans_types(self):
106        # This assumes test_query_A above succeeds
107        hr = _make_host_resolver()
108        hr._v4 = {'v4.example.com': '1.2.3.4'}
109        hr._last_stat = time.time()
110        ans = hr.query('v4.example.com')
111        assert isinstance(ans, greendns.dns.resolver.Answer)
112        assert ans.response is None
113        assert ans.qname == dns.name.from_text('v4.example.com')
114        assert ans.rdtype == dns.rdatatype.A
115        assert ans.rdclass == dns.rdataclass.IN
116        assert ans.canonical_name == dns.name.from_text('v4.example.com')
117        assert ans.expiration
118        assert isinstance(ans.rrset, dns.rrset.RRset)
119        assert ans.rrset.rdtype == dns.rdatatype.A
120        assert ans.rrset.rdclass == dns.rdataclass.IN
121        ttl = greendns.HOSTS_TTL
122        assert ttl - 1 <= ans.rrset.ttl <= ttl + 1
123        rr = ans.rrset[0]
124        assert isinstance(rr, greendns.dns.rdtypes.IN.A.A)
125        assert rr.rdtype == dns.rdatatype.A
126        assert rr.rdclass == dns.rdataclass.IN
127        assert rr.address == '1.2.3.4'
128
129    def test_query_AAAA(self):
130        hr = _make_host_resolver()
131        hr._v6 = {'v6.example.com': 'dead:beef::1'}
132        ans = hr.query('v6.example.com', dns.rdatatype.AAAA)
133        assert ans[0].address == 'dead:beef::1'
134
135    def test_query_unknown_raises(self):
136        hr = _make_host_resolver()
137        with tests.assert_raises(greendns.dns.resolver.NoAnswer):
138            hr.query('example.com')
139
140    def test_query_unknown_no_raise(self):
141        hr = _make_host_resolver()
142        ans = hr.query('example.com', raise_on_no_answer=False)
143        assert isinstance(ans, greendns.dns.resolver.Answer)
144        assert ans.response is None
145        assert ans.qname == dns.name.from_text('example.com')
146        assert ans.rdtype == dns.rdatatype.A
147        assert ans.rdclass == dns.rdataclass.IN
148        assert ans.canonical_name == dns.name.from_text('example.com')
149        assert ans.expiration
150        assert isinstance(ans.rrset, greendns.dns.rrset.RRset)
151        assert ans.rrset.rdtype == dns.rdatatype.A
152        assert ans.rrset.rdclass == dns.rdataclass.IN
153        assert len(ans.rrset) == 0
154
155    def test_query_CNAME(self):
156        hr = _make_host_resolver()
157        hr._aliases = {'host': 'host.example.com'}
158        ans = hr.query('host', dns.rdatatype.CNAME)
159        assert ans[0].target == dns.name.from_text('host.example.com')
160        assert str(ans[0].target) == 'host.example.com.'
161
162    def test_query_unknown_type(self):
163        hr = _make_host_resolver()
164        with tests.assert_raises(greendns.dns.resolver.NoAnswer):
165            hr.query('example.com', dns.rdatatype.MX)
166
167    def test_getaliases(self):
168        hr = _make_host_resolver()
169        hr._aliases = {'host': 'host.example.com',
170                       'localhost': 'host.example.com'}
171        res = set(hr.getaliases('host'))
172        assert res == set(['host.example.com', 'localhost'])
173
174    def test_getaliases_unknown(self):
175        hr = _make_host_resolver()
176        assert hr.getaliases('host.example.com') == []
177
178    def test_getaliases_fqdn(self):
179        hr = _make_host_resolver()
180        hr._aliases = {'host': 'host.example.com'}
181        res = set(hr.getaliases('host.example.com'))
182        assert res == set(['host'])
183
184    def test_hosts_case_insensitive(self):
185        name = 'example.com'
186        hr = _make_host_resolver()
187        hr.hosts.write(b'1.2.3.4 ExAmPlE.CoM\n')
188        hr.hosts.flush()
189        hr._load()
190
191        ans = hr.query(name)
192        rr = ans.rrset[0]
193        assert isinstance(rr, greendns.dns.rdtypes.IN.A.A)
194        assert rr.rdtype == dns.rdatatype.A
195        assert rr.rdclass == dns.rdataclass.IN
196        assert rr.address == '1.2.3.4'
197
198
199def _make_mock_base_resolver():
200    """A mocked base resolver class"""
201    class RR(object):
202        pass
203
204    class Resolver(object):
205        aliases = ['cname.example.com']
206        raises = None
207        rr = RR()
208        rr6 = RR()
209
210        def query(self, *args, **kwargs):
211            self.args = args
212            self.kwargs = kwargs
213            if self.raises:
214                raise self.raises()
215            if hasattr(self, 'rrset'):
216                rrset = self.rrset
217            else:
218                if self.rr6 and self.args[1] == dns.rdatatype.AAAA:
219                    rrset = [self.rr6]
220                else:
221                    rrset = [self.rr]
222            return greendns.HostsAnswer('foo', 1, 1, rrset, False)
223
224        def getaliases(self, *args, **kwargs):
225            return self.aliases
226
227    return Resolver
228
229
230class TestUdp(tests.LimitedTestCase):
231
232    def setUp(self):
233        # Store this so we can reuse it for each test
234        self.query = greendns.dns.message.Message()
235        self.query.flags = greendns.dns.flags.QR
236        self.query_wire = self.query.to_wire()
237        super(TestUdp, self).setUp()
238
239    def test_udp_ipv4(self):
240        with tests.mock.patch('eventlet.support.greendns.socket.socket.recvfrom',
241                              return_value=(self.query_wire,
242                                            ('127.0.0.1', 53))):
243            greendns.udp(self.query, '127.0.0.1')
244
245    def test_udp_ipv4_timeout(self):
246        with tests.mock.patch('eventlet.support.greendns.socket.socket.recvfrom',
247                              side_effect=socket.timeout):
248            with tests.assert_raises(dns.exception.Timeout):
249                greendns.udp(self.query, '127.0.0.1', timeout=0.1)
250
251    def test_udp_ipv4_wrong_addr_ignore(self):
252        with tests.mock.patch('eventlet.support.greendns.socket.socket.recvfrom',
253                              side_effect=socket.timeout):
254            with tests.assert_raises(dns.exception.Timeout):
255                greendns.udp(self.query, '127.0.0.1', timeout=0.1, ignore_unexpected=True)
256
257    def test_udp_ipv4_wrong_addr(self):
258        with tests.mock.patch('eventlet.support.greendns.socket.socket.recvfrom',
259                              return_value=(self.query_wire,
260                                            ('127.0.0.2', 53))):
261            with tests.assert_raises(dns.query.UnexpectedSource):
262                greendns.udp(self.query, '127.0.0.1')
263
264    def test_udp_ipv6(self):
265        with tests.mock.patch('eventlet.support.greendns.socket.socket.recvfrom',
266                              return_value=(self.query_wire,
267                                            ('::1', 53, 0, 0))):
268            greendns.udp(self.query, '::1')
269
270    def test_udp_ipv6_timeout(self):
271        with tests.mock.patch('eventlet.support.greendns.socket.socket.recvfrom',
272                              side_effect=socket.timeout):
273            with tests.assert_raises(dns.exception.Timeout):
274                greendns.udp(self.query, '::1', timeout=0.1)
275
276    def test_udp_ipv6_addr_zeroes(self):
277        with tests.mock.patch('eventlet.support.greendns.socket.socket.recvfrom',
278                              return_value=(self.query_wire,
279                                            ('0:00:0000::1', 53, 0, 0))):
280            greendns.udp(self.query, '::1')
281
282    def test_udp_ipv6_wrong_addr_ignore(self):
283        with tests.mock.patch('eventlet.support.greendns.socket.socket.recvfrom',
284                              side_effect=socket.timeout):
285            with tests.assert_raises(dns.exception.Timeout):
286                greendns.udp(self.query, '::1', timeout=0.1, ignore_unexpected=True)
287
288    def test_udp_ipv6_wrong_addr(self):
289        with tests.mock.patch('eventlet.support.greendns.socket.socket.recvfrom',
290                              return_value=(self.query_wire,
291                                            ('ffff:0000::1', 53, 0, 0))):
292            with tests.assert_raises(dns.query.UnexpectedSource):
293                greendns.udp(self.query, '::1')
294
295
296class TestProxyResolver(tests.LimitedTestCase):
297
298    def test_clear(self):
299        rp = greendns.ResolverProxy()
300        assert rp._cached_resolver is None
301        resolver = rp._resolver
302        assert resolver is not None
303        rp.clear()
304        assert rp._resolver is not None
305        assert rp._resolver != resolver
306
307    def _make_mock_hostsresolver(self):
308        """A mocked HostsResolver"""
309        base_resolver = _make_mock_base_resolver()
310        base_resolver.rr.address = '1.2.3.4'
311        return base_resolver()
312
313    def _make_mock_resolver(self):
314        """A mocked Resolver"""
315        base_resolver = _make_mock_base_resolver()
316        base_resolver.rr.address = '5.6.7.8'
317        return base_resolver()
318
319    def test_hosts(self):
320        hostsres = self._make_mock_hostsresolver()
321        rp = greendns.ResolverProxy(hostsres)
322        ans = rp.query('host.example.com')
323        assert ans[0].address == '1.2.3.4'
324
325    def test_hosts_noanswer(self):
326        hostsres = self._make_mock_hostsresolver()
327        res = self._make_mock_resolver()
328        rp = greendns.ResolverProxy(hostsres)
329        rp._resolver = res
330        hostsres.raises = greendns.dns.resolver.NoAnswer
331        ans = rp.query('host.example.com')
332        assert ans[0].address == '5.6.7.8'
333
334    def test_resolver(self):
335        res = self._make_mock_resolver()
336        rp = greendns.ResolverProxy()
337        rp._resolver = res
338        ans = rp.query('host.example.com')
339        assert ans[0].address == '5.6.7.8'
340
341    def test_noanswer(self):
342        res = self._make_mock_resolver()
343        rp = greendns.ResolverProxy()
344        rp._resolver = res
345        res.raises = greendns.dns.resolver.NoAnswer
346        with tests.assert_raises(greendns.dns.resolver.NoAnswer):
347            rp.query('host.example.com')
348
349    def test_nxdomain(self):
350        res = self._make_mock_resolver()
351        rp = greendns.ResolverProxy()
352        rp._resolver = res
353        res.raises = greendns.dns.resolver.NXDOMAIN
354        with tests.assert_raises(greendns.dns.resolver.NXDOMAIN):
355            rp.query('host.example.com')
356
357    def test_noanswer_hosts(self):
358        hostsres = self._make_mock_hostsresolver()
359        res = self._make_mock_resolver()
360        rp = greendns.ResolverProxy(hostsres)
361        rp._resolver = res
362        hostsres.raises = greendns.dns.resolver.NoAnswer
363        res.raises = greendns.dns.resolver.NoAnswer
364        with tests.assert_raises(greendns.dns.resolver.NoAnswer):
365            rp.query('host.example.com')
366
367    def _make_mock_resolver_aliases(self):
368
369        class RR(object):
370            target = 'host.example.com'
371
372        class Resolver(object):
373            call_count = 0
374            exc_type = greendns.dns.resolver.NoAnswer
375
376            def query(self, *args, **kwargs):
377                self.args = args
378                self.kwargs = kwargs
379                self.call_count += 1
380                if self.call_count < 2:
381                    return greendns.HostsAnswer(args[0], 1, 5, [RR()], False)
382                else:
383                    raise self.exc_type()
384
385        return Resolver()
386
387    def test_getaliases(self):
388        aliases_res = self._make_mock_resolver_aliases()
389        rp = greendns.ResolverProxy()
390        rp._resolver = aliases_res
391        aliases = set(rp.getaliases('alias.example.com'))
392        assert aliases == set(['host.example.com'])
393
394    def test_getaliases_fqdn(self):
395        aliases_res = self._make_mock_resolver_aliases()
396        rp = greendns.ResolverProxy()
397        rp._resolver = aliases_res
398        rp._resolver.call_count = 1
399        assert rp.getaliases('host.example.com') == []
400
401    def test_getaliases_nxdomain(self):
402        aliases_res = self._make_mock_resolver_aliases()
403        rp = greendns.ResolverProxy()
404        rp._resolver = aliases_res
405        rp._resolver.call_count = 1
406        rp._resolver.exc_type = greendns.dns.resolver.NXDOMAIN
407        assert rp.getaliases('host.example.com') == []
408
409
410class TestResolve(tests.LimitedTestCase):
411
412    def setUp(self):
413        base_resolver = _make_mock_base_resolver()
414        base_resolver.rr.address = '1.2.3.4'
415        self._old_resolver = greendns.resolver
416        greendns.resolver = base_resolver()
417
418    def tearDown(self):
419        greendns.resolver = self._old_resolver
420
421    def test_A(self):
422        ans = greendns.resolve('host.example.com', socket.AF_INET)
423        assert ans[0].address == '1.2.3.4'
424        assert greendns.resolver.args == ('host.example.com', dns.rdatatype.A)
425
426    def test_AAAA(self):
427        greendns.resolver.rr6.address = 'dead:beef::1'
428        ans = greendns.resolve('host.example.com', socket.AF_INET6)
429        assert ans[0].address == 'dead:beef::1'
430        assert greendns.resolver.args == ('host.example.com', dns.rdatatype.AAAA)
431
432    def test_unknown_rdtype(self):
433        with tests.assert_raises(socket.gaierror):
434            greendns.resolve('host.example.com', socket.AF_INET6 + 1)
435
436    def test_timeout(self):
437        greendns.resolver.raises = greendns.dns.exception.Timeout
438        with tests.assert_raises(socket.gaierror):
439            greendns.resolve('host.example.com')
440
441    def test_exc(self):
442        greendns.resolver.raises = greendns.dns.exception.DNSException
443        with tests.assert_raises(socket.gaierror):
444            greendns.resolve('host.example.com')
445
446    def test_noraise_noanswer(self):
447        greendns.resolver.rrset = None
448        ans = greendns.resolve('example.com', raises=False)
449        assert not ans.rrset
450
451    def test_noraise_nxdomain(self):
452        greendns.resolver.raises = greendns.dns.resolver.NXDOMAIN
453        ans = greendns.resolve('example.com', raises=False)
454        assert not ans.rrset
455
456
457class TestResolveCname(tests.LimitedTestCase):
458
459    def setUp(self):
460        base_resolver = _make_mock_base_resolver()
461        base_resolver.rr.target = 'cname.example.com'
462        self._old_resolver = greendns.resolver
463        greendns.resolver = base_resolver()
464
465    def tearDown(self):
466        greendns.resolver = self._old_resolver
467
468    def test_success(self):
469        cname = greendns.resolve_cname('alias.example.com')
470        assert cname == 'cname.example.com'
471
472    def test_timeout(self):
473        greendns.resolver.raises = greendns.dns.exception.Timeout
474        with tests.assert_raises(socket.gaierror):
475            greendns.resolve_cname('alias.example.com')
476
477    def test_nodata(self):
478        greendns.resolver.raises = greendns.dns.exception.DNSException
479        with tests.assert_raises(socket.gaierror):
480            greendns.resolve_cname('alias.example.com')
481
482    def test_no_answer(self):
483        greendns.resolver.raises = greendns.dns.resolver.NoAnswer
484        assert greendns.resolve_cname('host.example.com') == 'host.example.com'
485
486
487def _make_mock_resolve():
488    """A stubbed out resolve function
489
490    This monkeypatches the greendns.resolve() function with a mock.
491    You must give it answers by calling .add().
492    """
493
494    class MockAnswer(list):
495        pass
496
497    class MockResolve(object):
498
499        def __init__(self):
500            self.answers = {}
501
502        def __call__(self, name, family=socket.AF_INET, raises=True,
503                     _proxy=None, use_network=True):
504            qname = dns.name.from_text(name)
505            try:
506                rrset = self.answers[name][family]
507            except KeyError:
508                if raises:
509                    raise greendns.dns.resolver.NoAnswer()
510                rrset = dns.rrset.RRset(qname, 1, 1)
511            ans = MockAnswer()
512            ans.qname = qname
513            ans.rrset = rrset
514            ans.extend(rrset.items)
515            return ans
516
517        def add(self, name, addr):
518            """Add an address to a name and family"""
519            try:
520                rdata = dns.rdtypes.IN.A.A(dns.rdataclass.IN,
521                                           dns.rdatatype.A, addr)
522                family = socket.AF_INET
523            except (socket.error, dns.exception.SyntaxError):
524                rdata = dns.rdtypes.IN.AAAA.AAAA(dns.rdataclass.IN,
525                                                 dns.rdatatype.AAAA, addr)
526                family = socket.AF_INET6
527            family_dict = self.answers.setdefault(name, {})
528            rrset = family_dict.get(family)
529            if not rrset:
530                family_dict[family] = rrset = dns.rrset.RRset(
531                    dns.name.from_text(name), rdata.rdclass, rdata.rdtype)
532            rrset.add(rdata)
533
534    resolve = MockResolve()
535    return resolve
536
537
538class TestGetaddrinfo(tests.LimitedTestCase):
539
540    def _make_mock_resolve_cname(self):
541        """A stubbed out cname function"""
542
543        class ResolveCname(object):
544            qname = None
545            cname = 'cname.example.com'
546
547            def __call__(self, host):
548                self.qname = host
549                return self.cname
550
551        resolve_cname = ResolveCname()
552        return resolve_cname
553
554    def setUp(self):
555        self._old_resolve = greendns.resolve
556        self._old_resolve_cname = greendns.resolve_cname
557        self._old_orig_getaddrinfo = greendns.socket.getaddrinfo
558
559    def tearDown(self):
560        greendns.resolve = self._old_resolve
561        greendns.resolve_cname = self._old_resolve_cname
562        greendns.socket.getaddrinfo = self._old_orig_getaddrinfo
563
564    def test_getaddrinfo(self):
565        greendns.resolve = _make_mock_resolve()
566        greendns.resolve.add('example.com', '127.0.0.2')
567        greendns.resolve.add('example.com', '::1')
568        res = greendns.getaddrinfo('example.com', 'domain')
569        addr = ('127.0.0.2', 53)
570        tcp = (socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, addr)
571        udp = (socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP, addr)
572        addr = ('::1', 53, 0, 0)
573        tcp6 = (socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, addr)
574        udp6 = (socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP, addr)
575        filt_res = [ai[:3] + (ai[4],) for ai in res]
576        assert tcp in filt_res
577        assert udp in filt_res
578        assert tcp6 in filt_res
579        assert udp6 in filt_res
580
581    def test_getaddrinfo_idn(self):
582        greendns.resolve = _make_mock_resolve()
583        idn_name = u'евентлет.com'
584        greendns.resolve.add(idn_name.encode('idna').decode('ascii'), '127.0.0.2')
585        res = greendns.getaddrinfo(idn_name, 'domain')
586        addr = ('127.0.0.2', 53)
587        tcp = (socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, addr)
588        udp = (socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP, addr)
589        filt_res = [ai[:3] + (ai[4],) for ai in res]
590        assert tcp in filt_res
591        assert udp in filt_res
592
593    def test_getaddrinfo_inet(self):
594        greendns.resolve = _make_mock_resolve()
595        greendns.resolve.add('example.com', '127.0.0.2')
596        res = greendns.getaddrinfo('example.com', 'domain', socket.AF_INET)
597        addr = ('127.0.0.2', 53)
598        tcp = (socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, addr)
599        udp = (socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP, addr)
600        assert tcp in [ai[:3] + (ai[4],) for ai in res]
601        assert udp in [ai[:3] + (ai[4],) for ai in res]
602
603    def test_getaddrinfo_inet6(self):
604        greendns.resolve = _make_mock_resolve()
605        greendns.resolve.add('example.com', '::1')
606        res = greendns.getaddrinfo('example.com', 'domain', socket.AF_INET6)
607        addr = ('::1', 53, 0, 0)
608        tcp = (socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, addr)
609        udp = (socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP, addr)
610        assert tcp in [ai[:3] + (ai[4],) for ai in res]
611        assert udp in [ai[:3] + (ai[4],) for ai in res]
612
613    def test_getaddrinfo_only_a_ans(self):
614        greendns.resolve = _make_mock_resolve()
615        greendns.resolve.add('example.com', '1.2.3.4')
616        res = greendns.getaddrinfo('example.com', 0)
617        addr = [('1.2.3.4', 0)] * len(res)
618        assert addr == [ai[-1] for ai in res]
619
620    def test_getaddrinfo_only_aaaa_ans(self):
621        greendns.resolve = _make_mock_resolve()
622        greendns.resolve.add('example.com', 'dead:beef::1')
623        res = greendns.getaddrinfo('example.com', 0)
624        addr = [('dead:beef::1', 0, 0, 0)] * len(res)
625        assert addr == [ai[-1] for ai in res]
626
627    def test_getaddrinfo_hosts_only_ans_with_timeout(self):
628        def clear_raises(res_self):
629            res_self.raises = None
630            return greendns.dns.resolver.NoAnswer()
631
632        hostsres = _make_mock_base_resolver()
633        hostsres.raises = clear_raises
634        hostsres.rr.address = '1.2.3.4'
635        greendns.resolver = greendns.ResolverProxy(hostsres())
636        res = _make_mock_base_resolver()
637        res.raises = greendns.dns.exception.Timeout
638        greendns.resolver._resolver = res()
639
640        result = greendns.getaddrinfo('example.com', 0, 0)
641        addr = [('1.2.3.4', 0)] * len(result)
642        assert addr == [ai[-1] for ai in result]
643
644    def test_getaddrinfo_hosts_only_ans_with_error(self):
645        def clear_raises(res_self):
646            res_self.raises = None
647            return greendns.dns.resolver.NoAnswer()
648
649        hostsres = _make_mock_base_resolver()
650        hostsres.raises = clear_raises
651        hostsres.rr.address = '1.2.3.4'
652        greendns.resolver = greendns.ResolverProxy(hostsres())
653        res = _make_mock_base_resolver()
654        res.raises = greendns.dns.exception.DNSException
655        greendns.resolver._resolver = res()
656
657        result = greendns.getaddrinfo('example.com', 0, 0)
658        addr = [('1.2.3.4', 0)] * len(result)
659        assert addr == [ai[-1] for ai in result]
660
661    def test_getaddrinfo_hosts_only_timeout(self):
662        hostsres = _make_mock_base_resolver()
663        hostsres.raises = greendns.dns.resolver.NoAnswer
664        greendns.resolver = greendns.ResolverProxy(hostsres())
665        res = _make_mock_base_resolver()
666        res.raises = greendns.dns.exception.Timeout
667        greendns.resolver._resolver = res()
668
669        with tests.assert_raises(socket.gaierror):
670            greendns.getaddrinfo('example.com', 0, 0)
671
672    def test_getaddrinfo_hosts_only_dns_error(self):
673        hostsres = _make_mock_base_resolver()
674        hostsres.raises = greendns.dns.resolver.NoAnswer
675        greendns.resolver = greendns.ResolverProxy(hostsres())
676        res = _make_mock_base_resolver()
677        res.raises = greendns.dns.exception.DNSException
678        greendns.resolver._resolver = res()
679
680        with tests.assert_raises(socket.gaierror):
681            greendns.getaddrinfo('example.com', 0, 0)
682
683    def test_canonname(self):
684        greendns.resolve = _make_mock_resolve()
685        greendns.resolve.add('host.example.com', '1.2.3.4')
686        greendns.resolve_cname = self._make_mock_resolve_cname()
687        res = greendns.getaddrinfo('host.example.com', 0,
688                                   0, 0, 0, socket.AI_CANONNAME)
689        assert res[0][3] == 'cname.example.com'
690
691    def test_host_none(self):
692        res = greendns.getaddrinfo(None, 80)
693        for addr in set(ai[-1] for ai in res):
694            assert addr in [('127.0.0.1', 80), ('::1', 80, 0, 0)]
695
696    def test_host_none_passive(self):
697        res = greendns.getaddrinfo(None, 80, 0, 0, 0, socket.AI_PASSIVE)
698        for addr in set(ai[-1] for ai in res):
699            assert addr in [('0.0.0.0', 80), ('::', 80, 0, 0)]
700
701    def test_v4mapped(self):
702        greendns.resolve = _make_mock_resolve()
703        greendns.resolve.add('example.com', '1.2.3.4')
704        res = greendns.getaddrinfo('example.com', 80,
705                                   socket.AF_INET6, 0, 0, socket.AI_V4MAPPED)
706        addrs = set(ai[-1] for ai in res)
707        assert addrs == set([('::ffff:1.2.3.4', 80, 0, 0)])
708
709    def test_v4mapped_all(self):
710        greendns.resolve = _make_mock_resolve()
711        greendns.resolve.add('example.com', '1.2.3.4')
712        greendns.resolve.add('example.com', 'dead:beef::1')
713        res = greendns.getaddrinfo('example.com', 80, socket.AF_INET6, 0, 0,
714                                   socket.AI_V4MAPPED | socket.AI_ALL)
715        addrs = set(ai[-1] for ai in res)
716        for addr in addrs:
717            assert addr in [('::ffff:1.2.3.4', 80, 0, 0),
718                            ('dead:beef::1', 80, 0, 0)]
719
720    def test_numericserv(self):
721        greendns.resolve = _make_mock_resolve()
722        greendns.resolve.add('example.com', '1.2.3.4')
723        with tests.assert_raises(socket.gaierror):
724            greendns.getaddrinfo('example.com', 'www', 0, 0, 0, socket.AI_NUMERICSERV)
725
726    def test_numerichost(self):
727        greendns.resolve = _make_mock_resolve()
728        greendns.resolve.add('example.com', '1.2.3.4')
729        with tests.assert_raises(socket.gaierror):
730            greendns.getaddrinfo('example.com', 80, 0, 0, 0, socket.AI_NUMERICHOST)
731
732    def test_noport(self):
733        greendns.resolve = _make_mock_resolve()
734        greendns.resolve.add('example.com', '1.2.3.4')
735        ai = greendns.getaddrinfo('example.com', None)
736        assert ai[0][-1][1] == 0
737
738    def test_AI_ADDRCONFIG(self):
739        # When the users sets AI_ADDRCONFIG but only has an IPv4
740        # address configured we will iterate over the results, but the
741        # call for the IPv6 address will fail rather then return an
742        # empty list.  In that case we should catch the exception and
743        # only return the ones which worked.
744        def getaddrinfo(addr, port, family, socktype, proto, aiflags):
745            if addr == '127.0.0.1':
746                return [(socket.AF_INET, 1, 0, '', ('127.0.0.1', 0))]
747            elif addr == '::1' and aiflags & socket.AI_ADDRCONFIG:
748                raise socket.error(socket.EAI_ADDRFAMILY,
749                                   'Address family for hostname not supported')
750            elif addr == '::1' and not aiflags & socket.AI_ADDRCONFIG:
751                return [(socket.AF_INET6, 1, 0, '', ('::1', 0, 0, 0))]
752        greendns.socket.getaddrinfo = getaddrinfo
753        greendns.resolve = _make_mock_resolve()
754        greendns.resolve.add('localhost', '127.0.0.1')
755        greendns.resolve.add('localhost', '::1')
756        res = greendns.getaddrinfo('localhost', None,
757                                   0, 0, 0, socket.AI_ADDRCONFIG)
758        assert res == [(socket.AF_INET, 1, 0, '', ('127.0.0.1', 0))]
759
760    def test_AI_ADDRCONFIG_noaddr(self):
761        # If AI_ADDRCONFIG is used but there is no address we need to
762        # get an exception, not an empty list.
763        def getaddrinfo(addr, port, family, socktype, proto, aiflags):
764            raise socket.error(socket.EAI_ADDRFAMILY,
765                               'Address family for hostname not supported')
766        greendns.socket.getaddrinfo = getaddrinfo
767        greendns.resolve = _make_mock_resolve()
768        try:
769            greendns.getaddrinfo('::1', None, 0, 0, 0, socket.AI_ADDRCONFIG)
770        except socket.error as e:
771            assert e.errno == socket.EAI_ADDRFAMILY
772
773
774class TestIsIpAddr(tests.LimitedTestCase):
775
776    def test_isv4(self):
777        assert greendns.is_ipv4_addr('1.2.3.4')
778
779    def test_isv4_false(self):
780        assert not greendns.is_ipv4_addr('260.0.0.0')
781
782    def test_isv6(self):
783        assert greendns.is_ipv6_addr('dead:beef::1')
784
785    def test_isv6_invalid(self):
786        assert not greendns.is_ipv6_addr('foobar::1')
787
788    def test_v4(self):
789        assert greendns.is_ip_addr('1.2.3.4')
790
791    def test_v4_illegal(self):
792        assert not greendns.is_ip_addr('300.0.0.1')
793
794    def test_v6_addr(self):
795        assert greendns.is_ip_addr('::1')
796
797    def test_isv4_none(self):
798        assert not greendns.is_ipv4_addr(None)
799
800    def test_isv6_none(self):
801        assert not greendns.is_ipv6_addr(None)
802
803    def test_none(self):
804        assert not greendns.is_ip_addr(None)
805
806
807class TestGethostbyname(tests.LimitedTestCase):
808
809    def setUp(self):
810        self._old_resolve = greendns.resolve
811        greendns.resolve = _make_mock_resolve()
812
813    def tearDown(self):
814        greendns.resolve = self._old_resolve
815
816    def test_ipaddr(self):
817        assert greendns.gethostbyname('1.2.3.4') == '1.2.3.4'
818
819    def test_name(self):
820        greendns.resolve.add('host.example.com', '1.2.3.4')
821        assert greendns.gethostbyname('host.example.com') == '1.2.3.4'
822
823
824class TestGetaliases(tests.LimitedTestCase):
825
826    def _make_mock_resolver(self):
827        base_resolver = _make_mock_base_resolver()
828        resolver = base_resolver()
829        resolver.aliases = ['cname.example.com']
830        return resolver
831
832    def setUp(self):
833        self._old_resolver = greendns.resolver
834        greendns.resolver = self._make_mock_resolver()
835
836    def tearDown(self):
837        greendns.resolver = self._old_resolver
838
839    def test_getaliases(self):
840        assert greendns.getaliases('host.example.com') == ['cname.example.com']
841
842
843class TestGethostbyname_ex(tests.LimitedTestCase):
844
845    def _make_mock_getaliases(self):
846
847        class GetAliases(object):
848            aliases = ['cname.example.com']
849
850            def __call__(self, *args, **kwargs):
851                return self.aliases
852
853        getaliases = GetAliases()
854        return getaliases
855
856    def setUp(self):
857        self._old_resolve = greendns.resolve
858        greendns.resolve = _make_mock_resolve()
859        self._old_getaliases = greendns.getaliases
860
861    def tearDown(self):
862        greendns.resolve = self._old_resolve
863        greendns.getaliases = self._old_getaliases
864
865    def test_ipaddr(self):
866        res = greendns.gethostbyname_ex('1.2.3.4')
867        assert res == ('1.2.3.4', [], ['1.2.3.4'])
868
869    def test_name(self):
870        greendns.resolve.add('host.example.com', '1.2.3.4')
871        greendns.getaliases = self._make_mock_getaliases()
872        greendns.getaliases.aliases = []
873        res = greendns.gethostbyname_ex('host.example.com')
874        assert res == ('host.example.com', [], ['1.2.3.4'])
875
876    def test_multiple_addrs(self):
877        greendns.resolve.add('host.example.com', '1.2.3.4')
878        greendns.resolve.add('host.example.com', '1.2.3.5')
879        greendns.getaliases = self._make_mock_getaliases()
880        greendns.getaliases.aliases = []
881        res = greendns.gethostbyname_ex('host.example.com')
882        assert res == ('host.example.com', [], ['1.2.3.4', '1.2.3.5'])
883
884
885class TinyDNSTests(tests.LimitedTestCase):
886
887    def test_raise_dns_tcp(self):
888        # https://github.com/eventlet/eventlet/issues/499
889        # None means we don't want the server to find the IP
890        with tests.dns_tcp_server(None) as dnsaddr:
891            resolver = Resolver()
892            resolver.nameservers = [dnsaddr[0]]
893            resolver.nameserver_ports[dnsaddr[0]] = dnsaddr[1]
894
895            with self.assertRaises(NoAnswer):
896                resolver.query('host.example.com', 'a', tcp=True)
897
898    def test_noraise_dns_tcp(self):
899        # https://github.com/eventlet/eventlet/issues/499
900        expected_ip = "192.168.1.1"
901        with tests.dns_tcp_server(expected_ip) as dnsaddr:
902            resolver = Resolver()
903            resolver.nameservers = [dnsaddr[0]]
904            resolver.nameserver_ports[dnsaddr[0]] = dnsaddr[1]
905            response = resolver.query('host.example.com', 'a', tcp=True)
906            self.assertIsInstance(response, Answer)
907            self.assertEqual(list(response.rrset.items)[0].address, expected_ip)
908
909
910def test_reverse_name():
911    tests.run_isolated('greendns_from_address_203.py')
912
913
914def test_proxy_resolve_unqualified():
915    # https://github.com/eventlet/eventlet/issues/363
916    rp = greendns.ResolverProxy(filename=None)
917    rp._resolver.search.append(dns.name.from_text('example.com'))
918    with tests.mock.patch('dns.resolver.Resolver.query', side_effect=dns.resolver.NoAnswer) as m:
919        try:
920            rp.query('machine')
921            assert False, 'Expected NoAnswer exception'
922        except dns.resolver.NoAnswer:
923            pass
924        assert any(call[0][0] == dns.name.from_text('machine') for call in m.call_args_list)
925        assert any(call[0][0] == dns.name.from_text('machine.') for call in m.call_args_list)
926
927
928def test_hosts_priority():
929    name = 'example.com'
930    addr_from_ns = '1.0.2.0'
931
932    hr = _make_host_resolver()
933    rp = greendns.ResolverProxy(hosts_resolver=hr, filename=None)
934    base = _make_mock_base_resolver()
935    base.rr.address = addr_from_ns
936    rp._resolver = base()
937
938    # Default behavior
939    rrns = greendns.resolve(name, _proxy=rp).rrset[0]
940    assert rrns.address == addr_from_ns
941
942    # Hosts result must shadow that from nameservers
943    hr.hosts.write(b'1.2.3.4 example.com\ndead:beef::1 example.com\n')
944    hr.hosts.flush()
945    hr._load()
946    rrs4 = greendns.resolve(name, family=socket.AF_INET, _proxy=rp).rrset
947    assert len(rrs4) == 1
948    assert rrs4[0].address == '1.2.3.4', rrs4[0].address
949    rrs6 = greendns.resolve(name, family=socket.AF_INET6, _proxy=rp).rrset
950    assert len(rrs6) == 1
951    assert rrs6[0].address == 'dead:beef::1', rrs6[0].address
952
953
954def test_hosts_no_network():
955
956    name = 'example.com'
957    addr_from_ns = '1.0.2.0'
958    addr6_from_ns = 'dead:beef::1'
959
960    hr = _make_host_resolver()
961    rp = greendns.ResolverProxy(hosts_resolver=hr, filename=None)
962    base = _make_mock_base_resolver()
963    base.rr.address = addr_from_ns
964    base.rr6.address = addr6_from_ns
965    rp._resolver = base()
966
967    with tests.mock.patch.object(greendns, 'resolver',
968                                 new_callable=tests.mock.PropertyMock(return_value=rp)):
969        res = greendns.getaddrinfo('example.com', 'domain', socket.AF_UNSPEC)
970        # Default behavior
971        addr = (addr_from_ns, 53)
972        tcp = (socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, addr)
973        udp = (socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP, addr)
974        addr = (addr6_from_ns, 53, 0, 0)
975        tcp6 = (socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, addr)
976        udp6 = (socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP, addr)
977        filt_res = [ai[:3] + (ai[4],) for ai in res]
978        assert tcp in filt_res
979        assert udp in filt_res
980        assert tcp6 in filt_res
981        assert udp6 in filt_res
982
983        # Hosts result must shadow that from nameservers
984        hr = _make_host_resolver()
985        hr.hosts.write(b'1.2.3.4 example.com')
986        hr.hosts.flush()
987        hr._load()
988        greendns.resolver._hosts = hr
989
990        res = greendns.getaddrinfo('example.com', 'domain', socket.AF_UNSPEC)
991        filt_res = [ai[:3] + (ai[4],) for ai in res]
992
993        # Make sure that only IPv4 entry from hosts is present.
994        assert tcp not in filt_res
995        assert udp not in filt_res
996        assert tcp6 not in filt_res
997        assert udp6 not in filt_res
998
999        addr = ('1.2.3.4', 53)
1000        tcp = (socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, addr)
1001        udp = (socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP, addr)
1002        assert tcp in filt_res
1003        assert udp in filt_res
1004
1005
1006def test_import_rdtypes_then_eventlet():
1007    # https://github.com/eventlet/eventlet/issues/479
1008    tests.run_isolated('greendns_import_rdtypes_then_eventlet.py')
1009