Ticket #5675: edns-message-5675-1.patch

File edns-message-5675-1.patch, 37.3 KB (added by rwall, 15 months ago)

Unfinished implementation of EDNSMessage which wraps and can be substituted for Message

  • twisted/names/dns.py

    === modified file 'twisted/names/dns.py'
     
    18181818 
    18191819 
    18201820 
    1821 class Message: 
     1821@implementer(IEncodable) 
     1822class EDNSMessage(tputil.FancyStrMixin, tputil.FancyEqMixin, object): 
     1823 
     1824    showAttributes = ( 
     1825        'id', 'answer', 'opCode', 'auth', 'trunc', 
     1826        'recDes', 'recAv', 'rCode', 
     1827        'queries', 'answers', 'authority', 'additional') 
     1828 
     1829    compareAttributes = showAttributes 
     1830 
     1831    def __init__(self, id=0, answer=0, 
     1832                 opCode=OP_QUERY, auth=0, 
     1833                 trunc=0, recDes=0, 
     1834                 recAv=0, rCode=0, 
     1835                 queries=None, answers=None, authority=None, additional=None, optRecords=None): 
     1836 
     1837        # ID 
     1838        self.id = id 
     1839 
     1840        # QR 
     1841        self.answer = answer 
     1842 
     1843        # OPCODE 
     1844        self.opCode = opCode 
     1845 
     1846        # XXX: AA bit can be determined by checking for an 
     1847        # authoritative answer record whose name matches the query 
     1848        # name - perhaps in a higher level EDNSResponse class? 
     1849        self.auth = auth 
     1850 
     1851        # XXX: TC bit can be determined during encoding based on EDNS max 
     1852        # packet size. 
     1853        self.trunc = trunc 
     1854 
     1855        # RD 
     1856        self.recDes = recDes 
     1857 
     1858        # RA 
     1859        self.recAv = recAv 
     1860 
     1861        # RCODE 
     1862        self.rCode = rCode 
     1863 
     1864        self.queries = queries or [] 
     1865        self.answers = answers or [] 
     1866        self.authority = authority or [] 
     1867        self.additional = additional or [] 
     1868 
     1869        self.optRecords = optRecords or [] 
     1870 
     1871 
     1872    def encode(self, strio): 
     1873        m = Message( 
     1874            id=self.id, 
     1875            answer=self.answer, 
     1876            opCode=self.opCode, 
     1877            auth=self.auth, 
     1878            trunc=self.trunc, 
     1879            recDes=self.recDes, 
     1880            recAv=self.recAv, 
     1881            rCode=self.rCode, 
     1882 
     1883            maxSize=512) 
     1884 
     1885        m.queries = self.queries 
     1886        m.answers = self.answers 
     1887        m.authority = self.authority 
     1888        m.additional = self.additional 
     1889 
     1890        m.encode(strio) 
     1891 
     1892 
     1893    def toStr(self): 
     1894        b = BytesIO() 
     1895        self.encode(b) 
     1896        return b.getvalue() 
     1897 
     1898 
     1899    @classmethod 
     1900    def decode(cls, strio): 
     1901        m = Message() 
     1902        m.decode(strio) 
     1903 
     1904        optRecords = [] 
     1905        for r in reversed(m.additional): 
     1906            if r.type == OPT: 
     1907                optRecords.append(r) 
     1908                m.additional.remove(r) 
     1909 
     1910 
     1911        return cls( 
     1912            id=m.id, 
     1913            answer=m.answer, 
     1914            opCode=m.opCode, 
     1915            auth=m.auth, 
     1916            trunc=m.trunc, 
     1917            recDes=m.recDes, 
     1918            recAv=m.recAv, 
     1919            rCode=m.rCode, 
     1920            queries=m.queries, 
     1921            answers=m.answers, 
     1922            authority=m.authority, 
     1923            additional=m.additional, 
     1924            optRecords=optRecords) 
     1925 
     1926 
     1927 
     1928class Message(tputil.FancyEqMixin): 
    18221929    """ 
    18231930    L{Message} contains all the information represented by a single 
    18241931    DNS request or response. 
     
    18271934        message which is a response from a server to a client request. 
    18281935    @type rCode: C{0 <= int < 16} 
    18291936    """ 
     1937    compareAttributes = ( 
     1938        'id', 'answer', 'opCode', 'auth', 'trunc', 
     1939        'recDes', 'recAv', 'rCode', 
     1940        'queries', 'answers', 'authority', 'additional') 
     1941 
    18301942    headerFmt = "!H2B4H" 
    18311943    headerSize = struct.calcsize(headerFmt) 
    18321944 
     
    21112223        Read a datagram, extract the message in it and trigger the associated 
    21122224        Deferred. 
    21132225        """ 
    2114         m = Message() 
    21152226        try: 
    2116             m.fromStr(data) 
     2227            m = EDNSMessage.decode(BytesIO(data)) 
    21172228        except EOFError: 
    21182229            log.msg("Truncated packet (%d bytes) from %s" % (len(data), addr)) 
    21192230            return 
     
    22202331 
    22212332            if len(self.buffer) >= self.length: 
    22222333                myChunk = self.buffer[:self.length] 
    2223                 m = Message() 
    2224                 m.fromStr(myChunk) 
     2334                m = EDNSMessage.decode(BytesIO(myChunk)) 
    22252335 
    22262336                try: 
    22272337                    d, canceller = self.liveMessages[m.id] 
  • twisted/names/server.py

    === modified file 'twisted/names/server.py'
     
    188188        if not self.allowQuery(message, proto, address): 
    189189            message.rCode = dns.EREFUSED 
    190190            self.sendReply(proto, message, address) 
     191        elif message.optRecords: 
     192            message.rCode = dns.EFORMAT 
     193            self.sendReply(proto, message, address) 
    191194        elif message.opCode == dns.OP_QUERY: 
    192195            self.handleQuery(message, proto, address) 
    193196        elif message.opCode == dns.OP_INVERSE: 
  • twisted/names/test/test_dns.py

    === modified file 'twisted/names/test/test_dns.py'
     
    88 
    99from __future__ import division, absolute_import 
    1010 
     11from collections import namedtuple 
    1112from io import BytesIO 
    1213 
    1314import struct 
     
    3334    ] 
    3435 
    3536 
     37 
     38TestMessagePair = namedtuple('TestMessagePair', 'bytes messageKwargs') 
     39 
     40 
     41 
     42class TestMessages(object): 
     43    def __init__(self): 
     44        self.EMPTY = TestMessagePair( 
     45            b'\x01\x00' # id: 256 
     46            b'\x91' # QR: 1, OPCODE: 2, AA: 0, TC: 0, RD: 1 
     47            b'\x8f' # RA: 1, Z, RCODE: 15 
     48            b'\x00\x00' # number of queries 
     49            b'\x00\x00' # number of answers 
     50            b'\x00\x00' # number of authorities 
     51            b'\x00\x00' # number of additionals 
     52            , 
     53            dict( 
     54                id=256, 
     55                answer=1, 
     56                opCode=dns.OP_STATUS, 
     57                recDes=1, 
     58                recAv=1, 
     59                rCode=15) 
     60            ) 
     61 
     62        self.TRUNCATED = TestMessagePair( 
     63            b'\x01\x00' # id: 256 
     64            b'\x82' # QR: 1, OPCODE: 0, AA: 0, TC: 1, RD: 0 
     65            b'\x00' # RA: 0, Z, RCODE: 0 
     66            b'\x00\x00' # number of queries 
     67            b'\x00\x00' # number of answers 
     68            b'\x00\x00' # number of authorities 
     69            b'\x00\x00' # number of additionals 
     70            , 
     71            dict( 
     72                id=256, 
     73                answer=1, 
     74                opCode=0, 
     75                auth=0, 
     76                trunc=1, 
     77                recDes=0, 
     78                recAv=0, 
     79                rCode=0) 
     80            ) 
     81 
     82        self.NONAUTHORITATIVE_MINIMAL = TestMessagePair( 
     83            b'\x01\x00' #id 256 
     84            b'\x00' # QR: 0, OPCODE: 0, AA: 0, TC: 0, RD: 0 
     85            b'\x00' # RA: 0, Z, RCODE: 0 
     86            b'\x00\x00' # query count 
     87            b'\x00\x01' # answer count 
     88            b'\x00\x00' # authorities count 
     89            b'\x00\x00' # additionals count 
     90            # Answer 
     91            b'\x00' # RR NAME (root) 
     92            b'\x00\x01' # RR TYPE 1 (A) 
     93            b'\x00\x01' # RR CLASS 1 (IN) 
     94            b'\x00\x00\x00\x00' # RR TTL 
     95            b'\x00\x04' # RDLENGTH 4 
     96            b'\x01\x02\x03\x04' # IPv4 1.2.3.4 
     97            , 
     98            dict( 
     99                id=256, 
     100                auth=0, 
     101                answers=[ 
     102                    dns.RRHeader( 
     103                        b'', 
     104                        payload=dns.Record_A('1.2.3.4', ttl=0), 
     105                        auth=False)]) 
     106            ) 
     107 
     108        self.AUTHORITATIVE_MINIMAL = TestMessagePair( 
     109            b'\x01\x00' #id 256 
     110            b'\x04' # QR: 0, OPCODE: 0, AA: 1, TC: 0, RD: 0 
     111            b'\x00' # RA: 0, Z, RCODE: 0 
     112            b'\x00\x00' # query count 
     113            b'\x00\x01' # answer count 
     114            b'\x00\x00' # authorities count 
     115            b'\x00\x00' # additionals count 
     116            # Answer 
     117            b'\x00' # RR NAME (root) 
     118            b'\x00\x01' # RR TYPE 1 (A) 
     119            b'\x00\x01' # RR CLASS 1 (IN) 
     120            b'\x00\x00\x00\x00' # RR TTL 
     121            b'\x00\x04' # RDLENGTH 4 
     122            b'\x01\x02\x03\x04' # IPv4 1.2.3.4 
     123            , 
     124            dict( 
     125                id=256, 
     126                auth=1, 
     127                answers=[ 
     128                    dns.RRHeader( 
     129                        b'', 
     130                        payload=dns.Record_A('1.2.3.4', ttl=0), 
     131                        auth=True)]) 
     132            ) 
     133 
     134        self.COMPLETE = TestMessagePair( 
     135            b'\x01\x00' # id: 256 
     136            b'\x95' # QR: 1, OPCODE: 2, AA: 1, TC: 0, RD: 1 
     137            b'\x8f' # RA: 1, Z, RCODE: 15 
     138            b'\x00\x01' # query count 
     139            b'\x00\x01' # answer count 
     140            b'\x00\x01' # authorities count 
     141            b'\x00\x01' # additionals count 
     142 
     143            # Query begins at Byte 12 
     144            b'\x07example\x03com\x00' # QNAME 
     145            b'\x00\x06' # QTYPE 6 (SOA) 
     146            b'\x00\x01' # QCLASS 1 (IN) 
     147 
     148            # Answers 
     149            b'\xc0\x0c' # RR NAME (compression ref b12) 
     150            b'\x00\x06' # RR TYPE 6 (SOA) 
     151            b'\x00\x01' # RR CLASS 1 (IN) 
     152            b'\xff\xff\xff\xff' # RR TTL 
     153            b'\x00\x27' # RDLENGTH 39 
     154            b'\x03ns1\xc0\x0c' # mname (ns1.example.com (compression ref b15) 
     155            b'\x0ahostmaster\xc0\x0c' # rname (hostmaster.example.com) 
     156            b'\xff\xff\xff\xfe' # serial 
     157            b'\x7f\xff\xff\xfd' # refresh 
     158            b'\x7f\xff\xff\xfc' # retry 
     159            b'\x7f\xff\xff\xfb' # expire 
     160            b'\xff\xff\xff\xfa' # minimum 
     161 
     162            # Authority 
     163            b'\xc0\x0c' # RR NAME (example.com compression ref b12) 
     164            b'\x00\x02' # RR TYPE 2 (NS) 
     165            b'\x00\x01' # RR CLASS 1 (IN) 
     166            b'\xff\xff\xff\xff' # RR TTL 
     167            b'\x00\x02' # RDLENGTH 
     168            b'\xc0\x29' # RDATA (ns1.example.com (compression ref b41) 
     169 
     170            # Additional 
     171            b'\xc0\x29' # RR NAME (ns1.example.com compression ref b41) 
     172            b'\x00\x01' # RR TYPE 1 (A) 
     173            b'\x00\x01' # RR CLASS 1 (IN) 
     174            b'\xff\xff\xff\xff' # RR TTL 
     175            b'\x00\x04' # RDLENGTH 
     176            b'\x05\x06\x07\x08' # RDATA 5.6.7.8 
     177            , 
     178            dict( 
     179                id=256, 
     180                answer=1, 
     181                opCode=dns.OP_STATUS, 
     182                auth=1, 
     183                recDes=1, 
     184                recAv=1, 
     185                rCode=15, 
     186                queries=[dns.Query(b'example.com', dns.SOA)], 
     187                answers=[ 
     188                    dns.RRHeader( 
     189                        b'example.com', 
     190                        type=dns.SOA, 
     191                        ttl=0xffffffff, 
     192                        auth=True, 
     193                        payload=dns.Record_SOA( 
     194                            ttl=0xffffffff, 
     195 
     196                            mname=b'ns1.example.com', 
     197                            rname=b'hostmaster.example.com', 
     198 
     199                            serial=0xfffffffe, 
     200                            refresh=0x7ffffffd, 
     201                            retry=0x7ffffffc, 
     202                            expire=0x7ffffffb, 
     203                            minimum=0xfffffffa, 
     204                            ))], 
     205                authority=[ 
     206                    dns.RRHeader( 
     207                        b'example.com', 
     208                        type=dns.NS, 
     209                        ttl=0xffffffff, 
     210                        auth=True, 
     211                        payload=dns.Record_NS( 
     212                            'ns1.example.com', ttl=0xffffffff))], 
     213                additional=[ 
     214                    dns.RRHeader( 
     215                        b'ns1.example.com', 
     216                        type=dns.A, 
     217                        ttl=0xffffffff, 
     218                        auth=True, 
     219                        payload=dns.Record_A( 
     220                            '5.6.7.8', ttl=0xffffffff))]) 
     221            ) 
     222 
     223        self.EDNS_QUERY = TestMessagePair( 
     224            b'\x00\x00' # id: 0 
     225            b'\x00' # QR: 0, OPCODE: 0, AA: 0, TC: 0, RD: 0 
     226            b'\x00' # RA: 0, Z, RCODE: 0 
     227            b'\x00\x01' # queries count 
     228            b'\x00\x00' # anwers count 
     229            b'\x00\x00' # authority count 
     230            b'\x00\x01' # additionals count 
     231            # Queries 
     232            b'\x03www\x07example\x03com\x00' # QNAME 
     233            b'\x00\x01' # QTYPE (A) 
     234            b'\x00\x01' # QCLASS (IN) 
     235            # Additional 
     236            b'\x00' # NAME (.) 
     237            b'\x00\x29' # TYPE (OPT 41) 
     238            b'\x10\x00' # UDP Payload Size (4096) 
     239            b'\x00' # Extended RCODE 
     240            b'\x00' # EDNS version 
     241            b'\x00\x00' # DO bit + Z 
     242            b'\x00\x00' # RDLENGTH 
     243            , 
     244            dict( 
     245                id=0, 
     246                answer=0, 
     247                opCode=dns.OP_QUERY, 
     248                auth=0, 
     249                recDes=0, 
     250                recAv=0, 
     251                rCode=0, 
     252                queries=[dns.Query(b'www.example.com', dns.A)], 
     253                additional=[dns.RRHeader( 
     254                        b'', 
     255                        type=dns.OPT, 
     256                        cls=4096, 
     257                        payload=dns.UnknownRecord(b'', ttl=0))]) 
     258            ) 
     259 
     260 
     261 
     262def assertFancyEqual(case, a, b): 
     263    for key in a.compareAttributes: 
     264        case.assertEqual( 
     265            getattr(a, key), 
     266            getattr(b, key), 
     267            '\n\n%r\n%r\n\ninequality found in %r attribute' % (a, b, key)) 
     268 
     269 
     270 
    36271class Ord2ByteTests(unittest.TestCase): 
    37272    """ 
    38273    Tests for L{dns._ord2bytes}. 
     
    650885        message are marked as not authoritative. 
    651886        """ 
    652887        buf = BytesIO() 
    653         answer = dns.RRHeader(payload=dns.Record_A('1.2.3.4', ttl=0)) 
     888        answer = dns.RRHeader(payload=dns.Record_A('1.2.3.4', ttl=0), auth=False) 
    654889        answer.encode(buf) 
    655890        message = dns.Message() 
    656891        message.fromStr( 
     
    676911        message are marked as authoritative. 
    677912        """ 
    678913        buf = BytesIO() 
    679         answer = dns.RRHeader(payload=dns.Record_A('1.2.3.4', ttl=0)) 
     914        answer = dns.RRHeader(payload=dns.Record_A('1.2.3.4', ttl=0), auth=True) 
    680915        answer.encode(buf) 
    681916        message = dns.Message() 
    682917        message.fromStr( 
     
    692927            b'\x00\x00' # number of additionals 
    693928            + buf.getvalue() 
    694929            ) 
    695         answer.auth = True 
     930 
    696931        self.assertEqual(message.answers, [answer]) 
    697932        self.assertTrue(message.answers[0].auth) 
    698933 
    699934 
     935    def test_ednsOptRecords(self): 
     936        """ 
     937        L{dns.Message} interprets the additional OPT records in an 
     938        EDNS query as L{dns.UnknownRecord}s. 
     939        """ 
     940        bytes, messageKwargs = TestMessages().EDNS_QUERY 
     941        message = dns.Message() 
     942        message.decode(BytesIO(bytes)) 
     943        self.assertEqual( 
     944            message.additional, 
     945            messageKwargs['additional']) 
     946 
     947 
    700948 
    701949class TestController(object): 
    702950    """ 
     
    21592407        o.decode(b) 
    21602408        self.assertEqual(o.code, 1) 
    21612409        self.assertEqual(o.data, b'foobar') 
     2410 
     2411 
     2412 
     2413class MessageTestsMixin(object): 
     2414    """ 
     2415    Tests for L{dns.EDNSMessage} and L{dns.Message}. 
     2416    """ 
     2417    def test_id(self): 
     2418        """ 
     2419        L{dns.EDNSMessage.__init__} accepts an optional id argument 
     2420        whose default value is 0 and which is saved as a public 
     2421        instance attribute. 
     2422        """ 
     2423        self.assertEqual(self.messageFactory().id, 0) 
     2424        self.assertEqual(self.messageFactory(1).id, 1) 
     2425 
     2426 
     2427    def test_answer(self): 
     2428        """ 
     2429        L{dns.EDNSMessage.__init__} accepts an optional answer argument 
     2430        whose default value is 0 and which 
     2431        is saved as a public instance attribute. 
     2432        """ 
     2433        self.assertIdentical(self.messageFactory().answer, 0) 
     2434        self.assertIdentical(self.messageFactory(answer=1).answer, 1) 
     2435 
     2436 
     2437    def test_opCode(self): 
     2438        """ 
     2439        L{dns.EDNSMessage.__init__} accepts an optional opCode argument 
     2440        whose default value is L{dns.OP_QUERY} and which 
     2441        is saved as a public instance attribute. 
     2442        """ 
     2443        self.assertIdentical(self.messageFactory().opCode, dns.OP_QUERY) 
     2444        self.assertIdentical( 
     2445            self.messageFactory(opCode=dns.OP_STATUS).opCode, 
     2446            dns.OP_STATUS) 
     2447 
     2448 
     2449    def test_auth(self): 
     2450        """ 
     2451        L{dns.EDNSMessage.__init__} accepts an optional auth argument 
     2452        whose default value is 0 and which is saved as a public 
     2453        instance attribute. 
     2454        """ 
     2455        self.assertIdentical(self.messageFactory().auth, 0) 
     2456        self.assertIdentical(self.messageFactory(auth=1).auth, 1) 
     2457 
     2458 
     2459    def test_trunc(self): 
     2460        """ 
     2461        L{dns.EDNSMessage.__init__} accepts an optional trunc argument 
     2462        whose default value is 0 and which is saved as a public 
     2463        instance attribute. 
     2464        """ 
     2465        self.assertIdentical(self.messageFactory().trunc, 0) 
     2466        self.assertIdentical(self.messageFactory(trunc=1).trunc, 1) 
     2467 
     2468 
     2469    def test_recDes(self): 
     2470        """ 
     2471        L{dns.EDNSMessage.__init__} accepts an optional recDes argument 
     2472        whose default value is 0 and which is saved as a public 
     2473        instance attribute. 
     2474        """ 
     2475        self.assertIdentical(self.messageFactory().recDes, 0) 
     2476        self.assertIdentical(self.messageFactory(recDes=1).recDes, 1) 
     2477 
     2478 
     2479    def test_recAv(self): 
     2480        """ 
     2481        L{dns.EDNSMessage.__init__} accepts an optional recAv argument 
     2482        whose default value is 0 and which is saved as a public 
     2483        instance attribute. 
     2484        """ 
     2485        self.assertEqual(self.messageFactory().recAv, 0) 
     2486        self.assertEqual(self.messageFactory(recAv=True).recAv, 1) 
     2487 
     2488 
     2489    def test_rCode(self): 
     2490        """ 
     2491        L{dns.EDNSMessage.__init__} accepts an optional rCode argument 
     2492        whose default value is 0 and which is saved as a public 
     2493        instance attribute. 
     2494        """ 
     2495        self.assertEqual(self.messageFactory().rCode, 0) 
     2496        self.assertEqual(self.messageFactory(rCode=123).rCode, 123) 
     2497 
     2498 
     2499    def test_rrLists(self): 
     2500        """ 
     2501        L{dns.EDNSMessage} instances have public list attributes for 
     2502        C{queries}, C{answers}, C{authority}, C{additional} which are 
     2503        empty by default. 
     2504        """ 
     2505        m = self.messageFactory() 
     2506        self.assertEqual(m.queries, []) 
     2507        self.assertEqual(m.answers, []) 
     2508        self.assertEqual(m.authority, []) 
     2509        self.assertEqual(m.additional, []) 
     2510 
     2511 
     2512    def test_equality(self): 
     2513        """ 
     2514        Two L{dns.EDNSMessage} instances compare equal if they have the same 
     2515        id, type, opCode, auth, recDes, recAv attributes. 
     2516        """ 
     2517        self.assertNormalEqualityImplementation( 
     2518            self.messageFactory(id=1), 
     2519            self.messageFactory(id=1), 
     2520            self.messageFactory(id=2), 
     2521            ) 
     2522 
     2523        self.assertNormalEqualityImplementation( 
     2524            self.messageFactory(answer=1), 
     2525            self.messageFactory(answer=1), 
     2526            self.messageFactory(answer=0), 
     2527            ) 
     2528 
     2529        self.assertNormalEqualityImplementation( 
     2530            self.messageFactory(opCode=dns.OP_STATUS), 
     2531            self.messageFactory(opCode=dns.OP_STATUS), 
     2532            self.messageFactory(opCode=dns.OP_INVERSE), 
     2533            ) 
     2534 
     2535        self.assertNormalEqualityImplementation( 
     2536            self.messageFactory(auth=1), 
     2537            self.messageFactory(auth=1), 
     2538            self.messageFactory(auth=0), 
     2539            ) 
     2540 
     2541        self.assertNormalEqualityImplementation( 
     2542            self.messageFactory(trunc=1), 
     2543            self.messageFactory(trunc=1), 
     2544            self.messageFactory(trunc=0), 
     2545            ) 
     2546 
     2547        self.assertNormalEqualityImplementation( 
     2548            self.messageFactory(recDes=1), 
     2549            self.messageFactory(recDes=1), 
     2550            self.messageFactory(recDes=0), 
     2551            ) 
     2552 
     2553        self.assertNormalEqualityImplementation( 
     2554            self.messageFactory(recAv=1), 
     2555            self.messageFactory(recAv=1), 
     2556            self.messageFactory(recAv=0), 
     2557            ) 
     2558 
     2559        self.assertNormalEqualityImplementation( 
     2560            self.messageFactory(rCode=123), 
     2561            self.messageFactory(rCode=123), 
     2562            self.messageFactory(rCode=321), 
     2563            ) 
     2564 
     2565        self.assertNormalEqualityImplementation( 
     2566            self.messageFactory(queries=[dns.Query(b'example.com')]), 
     2567            self.messageFactory(queries=[dns.Query(b'example.com')]), 
     2568            self.messageFactory(queries=[dns.Query(b'example.org')]), 
     2569            ) 
     2570 
     2571        self.assertNormalEqualityImplementation( 
     2572            self.messageFactory(answers=[dns.RRHeader(b'example.com', payload=dns.Record_A('1.2.3.4'))]), 
     2573            self.messageFactory(answers=[dns.RRHeader(b'example.com', payload=dns.Record_A('1.2.3.4'))]), 
     2574            self.messageFactory(answers=[dns.RRHeader(b'example.org', payload=dns.Record_A('4.3.2.1'))]), 
     2575            ) 
     2576 
     2577        self.assertNormalEqualityImplementation( 
     2578            self.messageFactory(authority=[dns.RRHeader(b'example.com', type=dns.SOA, payload=dns.Record_SOA())]), 
     2579            self.messageFactory(authority=[dns.RRHeader(b'example.com', type=dns.SOA, payload=dns.Record_SOA())]), 
     2580            self.messageFactory(authority=[dns.RRHeader(b'example.org', type=dns.SOA, payload=dns.Record_SOA())]), 
     2581            ) 
     2582 
     2583        self.assertNormalEqualityImplementation( 
     2584            self.messageFactory(additional=[dns.RRHeader(b'example.com', payload=dns.Record_A('1.2.3.4'))]), 
     2585            self.messageFactory(additional=[dns.RRHeader(b'example.com', payload=dns.Record_A('1.2.3.4'))]), 
     2586            self.messageFactory(additional=[dns.RRHeader(b'example.org', payload=dns.Record_A('1.2.3.4'))]), 
     2587            ) 
     2588 
     2589 
     2590    def test_emptyQueryEncode(self): 
     2591        """ 
     2592        An empty query message can be encoded. 
     2593        """ 
     2594        bytes, messageKwargs = TestMessages().EMPTY 
     2595 
     2596        b = BytesIO() 
     2597        self.messageFactory(**messageKwargs).encode(b) 
     2598 
     2599        self.assertEqual( 
     2600            b.getvalue(), 
     2601            bytes) 
     2602 
     2603 
     2604    def test_emptyQueryDecode(self): 
     2605        """ 
     2606        An empty query byte sequence can be decoded. 
     2607        """ 
     2608        bytes, messageKwargs = TestMessages().EMPTY 
     2609 
     2610        self.assertEqual( 
     2611            self.messageDecoder(BytesIO(bytes)), 
     2612            self.messageFactory(**messageKwargs)) 
     2613 
     2614 
     2615    def test_completeQueryEncode(self): 
     2616        """ 
     2617        A fully populated query message can be encoded. 
     2618        """ 
     2619        bytes, messageKwargs = TestMessages().COMPLETE 
     2620 
     2621        b = BytesIO() 
     2622        self.messageFactory(**messageKwargs).encode(b) 
     2623 
     2624        self.assertEqual( 
     2625            b.getvalue(), 
     2626            bytes 
     2627            ) 
     2628 
     2629 
     2630    def test_completeQueryDecode(self): 
     2631        """ 
     2632        A fully populated message byte string can be decoded. 
     2633        """ 
     2634        bytes, messageKwargs = TestMessages().COMPLETE 
     2635 
     2636        self.assertEqual( 
     2637            self.messageDecoder(BytesIO(bytes)), 
     2638            self.messageFactory(**messageKwargs)) 
     2639 
     2640 
     2641    def test_NULL(self): 
     2642        """ 
     2643        A I{NULL} record with an arbitrary payload can be encoded and decoded as 
     2644        part of a message. 
     2645        """ 
     2646        bytes = b''.join([dns._ord2bytes(i) for i in range(256)]) 
     2647        rec = dns.Record_NULL(bytes) 
     2648        rr = dns.RRHeader(b'testname', dns.NULL, payload=rec) 
     2649        msg1 = self.messageFactory() 
     2650        msg1.answers.append(rr) 
     2651        s = BytesIO() 
     2652        msg1.encode(s) 
     2653        s.seek(0, 0) 
     2654        msg2 = self.messageDecoder(s) 
     2655 
     2656        self.assertIsInstance(msg2.answers[0].payload, dns.Record_NULL) 
     2657        self.assertEqual(msg2.answers[0].payload.payload, bytes) 
     2658 
     2659 
     2660    def test_nonAuthoritativeMessageDecode(self): 
     2661        """ 
     2662        The L{dns.RRHeader} instances created by a message from a 
     2663        non-authoritative message byte string are marked as not 
     2664        authoritative. 
     2665        """ 
     2666        bytes, messageKwargs = TestMessages().NONAUTHORITATIVE_MINIMAL 
     2667 
     2668        self.assertEqual( 
     2669            self.messageDecoder(BytesIO(bytes)), 
     2670            self.messageFactory(**messageKwargs)) 
     2671 
     2672 
     2673    def test_nonAuthoritativeMessageEncode(self): 
     2674        """ 
     2675        If the message C{authoritative} attribute is set to 0, the 
     2676        encoded bytes will have AA bit 0. 
     2677        """ 
     2678        bytes, messageKwargs = TestMessages().NONAUTHORITATIVE_MINIMAL 
     2679        b = BytesIO() 
     2680        self.messageFactory(**messageKwargs).encode(b) 
     2681        self.assertEqual(b.getvalue(), bytes) 
     2682 
     2683 
     2684    def test_authoritativeMessageDecode(self): 
     2685        """ 
     2686        The message and its L{dns.RRHeader} instances created by 
     2687        C{decode} from an authoritative message byte string, are 
     2688        marked as authoritative. 
     2689        """ 
     2690        bytes, messageKwargs = TestMessages().AUTHORITATIVE_MINIMAL 
     2691 
     2692        self.assertEqual( 
     2693            self.messageDecoder(BytesIO(bytes)), 
     2694            self.messageFactory(**messageKwargs)) 
     2695 
     2696 
     2697    def test_authoritativeMessageEncode(self): 
     2698        """ 
     2699        If the message C{authoritative} attribute is set to 1, the 
     2700        encoded bytes will have AA bit 1. 
     2701        """ 
     2702        bytes, messageKwargs = TestMessages().AUTHORITATIVE_MINIMAL 
     2703        b = BytesIO() 
     2704        self.messageFactory(**messageKwargs).encode(b) 
     2705        self.assertEqual(b.getvalue(), bytes) 
     2706 
     2707 
     2708    def test_truncatedMessageDecode(self): 
     2709        """ 
     2710        The message instance created by decoding a truncated message 
     2711        is marked as truncated. 
     2712        """ 
     2713        bytes, messageKwargs = TestMessages().TRUNCATED 
     2714        self.assertEqual( 
     2715            self.messageDecoder(BytesIO(bytes)), 
     2716            self.messageFactory(**messageKwargs)) 
     2717 
     2718 
     2719    def test_truncatedMessageEncode(self): 
     2720        """ 
     2721        If the message C{trunc} attribute is set to 1 the encoded 
     2722        bytes will have TR bit 1. 
     2723        """ 
     2724        bytes, messageKwargs = TestMessages().TRUNCATED 
     2725        b = BytesIO() 
     2726        self.messageFactory(**messageKwargs).encode(b) 
     2727        self.assertEqual(b.getvalue(), bytes) 
     2728 
     2729 
     2730 
     2731class MessageStandardTestCase(ComparisonTestsMixin, MessageTestsMixin, unittest.TestCase, object): 
     2732    """ 
     2733    Tests for L{dns.Message}. 
     2734    """ 
     2735    @staticmethod 
     2736    def messageFactory(*args, **kwargs): 
     2737        """ 
     2738        A wrapper to hide the fact that dns.Message doesn't accept 
     2739        queries, answers, etc as keyword arguments. 
     2740 
     2741        XXX: Can I just add these new arguments to dns.Message or is 
     2742        that considered backwards incompatible? 
     2743        """ 
     2744        queries = kwargs.pop('queries', []) 
     2745        answers = kwargs.pop('answers', []) 
     2746        authority = kwargs.pop('authority', []) 
     2747        additional = kwargs.pop('additional', []) 
     2748 
     2749        m = dns.Message(*args, **kwargs) 
     2750        m.queries = queries 
     2751        m.answers = answers 
     2752        m.authority = authority 
     2753        m.additional = additional 
     2754 
     2755        return m 
     2756 
     2757    @staticmethod 
     2758    def messageDecoder(bytesio): 
     2759        """ 
     2760        A wrapper to handle the fact that dns.Message.decode updates 
     2761        the message in place and does not return the resulting 
     2762        message. 
     2763 
     2764        XXX: I'd like to change dns.Message.decode to a classmethod 
     2765        but I guess that would break the compatibility policy. 
     2766        """ 
     2767        m = dns.Message() 
     2768        m.decode(bytesio) 
     2769        return m 
     2770 
     2771 
     2772 
     2773class EDNSMessageStandardTestCase(ComparisonTestsMixin, MessageTestsMixin, unittest.TestCase, object): 
     2774    """ 
     2775    Tests for L{dns.EDNSMessage}. 
     2776    """ 
     2777    # XXX: These are necessary because the dns.Message.__init__ and 
     2778    # dns.Message.decode methods are not compatible with 
     2779    # dns.EDNSMessage 
     2780    messageFactory = dns.EDNSMessage 
     2781    messageDecoder = dns.EDNSMessage.decode 
     2782 
     2783 
     2784 
     2785class EDNSMessageSpecificsTestCase(ComparisonTestsMixin, unittest.TestCase, object): 
     2786    """ 
     2787    Tests for L{dns.EDNSMessage}. 
     2788    """ 
     2789    messageFactory = dns.EDNSMessage 
     2790    messageDecoder = dns.EDNSMessage.decode 
     2791 
     2792    def test_queries(self): 
     2793        """ 
     2794        L{dns.EDNSMessage.__init__} accepts an optional queries argument 
     2795        whose default value is [] and which is saved as a public 
     2796        instance attribute. 
     2797        """ 
     2798        self.assertEqual(self.messageFactory().queries, []) 
     2799        msg = self.messageFactory(queries=[dns.Query(b'example.com')]) 
     2800 
     2801        self.assertEqual( 
     2802            msg.queries, 
     2803            [dns.Query(b'example.com')]) 
     2804 
     2805 
     2806    def test_answers(self): 
     2807        """ 
     2808        L{dns.EDNSMessage.__init__} accepts an optional answers argument 
     2809        whose default value is [] and which is saved as a public 
     2810        instance attribute. 
     2811        """ 
     2812        self.assertEqual(self.messageFactory().answers, []) 
     2813        msg = self.messageFactory( 
     2814            answers=[ 
     2815                dns.RRHeader( 
     2816                    b'example.com', 
     2817                    payload=dns.Record_A('1.2.3.4'))]) 
     2818 
     2819        self.assertEqual( 
     2820            msg.answers, 
     2821            [dns.RRHeader(b'example.com', payload=dns.Record_A('1.2.3.4'))]) 
     2822 
     2823 
     2824    def test_authority(self): 
     2825        """ 
     2826        L{dns.EDNSMessage.__init__} accepts an optional authority argument 
     2827        whose default value is [] and which is saved as a public 
     2828        instance attribute. 
     2829        """ 
     2830        self.assertEqual(self.messageFactory().authority, []) 
     2831        msg = self.messageFactory( 
     2832            authority=[ 
     2833                dns.RRHeader( 
     2834                    b'example.com', 
     2835                    type=dns.SOA, 
     2836                    payload=dns.Record_SOA())]) 
     2837 
     2838        self.assertEqual( 
     2839            msg.authority, 
     2840            [dns.RRHeader(b'example.com', type=dns.SOA, 
     2841                          payload=dns.Record_SOA())]) 
     2842 
     2843 
     2844    def test_additional(self): 
     2845        """ 
     2846        L{dns.EDNSMessage.__init__} accepts an optional additional argument 
     2847        whose default value is [] and which is saved as a public 
     2848        instance attribute. 
     2849        """ 
     2850        self.assertEqual(self.messageFactory().additional, []) 
     2851        msg = self.messageFactory( 
     2852            additional=[ 
     2853                dns.RRHeader( 
     2854                    b'example.com', 
     2855                    payload=dns.Record_A('1.2.3.4'))]) 
     2856 
     2857        self.assertEqual( 
     2858            msg.additional, 
     2859            [dns.RRHeader(b'example.com', payload=dns.Record_A('1.2.3.4'))]) 
     2860 
     2861 
     2862    def test_repr(self): 
     2863        """ 
     2864        L{dns.EDNSMessage.__repr__} displays the id, answer, opCode, 
     2865        auth, trunc, recDes, recAv attributes of the message. 
     2866        """ 
     2867        self.assertEqual( 
     2868            repr(self.messageFactory(**TestMessages().COMPLETE.messageKwargs)), 
     2869            '<EDNSMessage ' 
     2870            'id=256 ' 
     2871            'answer=1 ' 
     2872            'opCode=2 ' 
     2873            'auth=1 ' 
     2874            'trunc=0 ' 
     2875            'recDes=1 ' 
     2876            'recAv=1 ' 
     2877            'rCode=15 ' 
     2878            "queries=[Query('example.com', 6, 1)] " 
     2879            'answers=[' 
     2880            '<RR name=example.com type=SOA class=IN ttl=4294967295s auth=True>' 
     2881            '] ' 
     2882            'authority=[' 
     2883            '<RR name=example.com type=NS class=IN ttl=4294967295s auth=True>' 
     2884            '] ' 
     2885            'additional=[' 
     2886            '<RR name=ns1.example.com type=A class=IN ttl=4294967295s auth=True>' 
     2887            ']' 
     2888            '>') 
     2889 
     2890 
     2891    def test_ednsMessageDecodeStripsOptRecords(self): 
     2892        """ 
     2893        The L(EDNSMessage} instance created by 
     2894        L{dns.EDNSMessage.decode} from an EDNS query never includes 
     2895        OPT records in the additional section. 
     2896        """ 
     2897        bytes, ign = TestMessages().EDNS_QUERY 
     2898        message = self.messageDecoder(BytesIO(bytes)) 
     2899        self.assertEqual(message.additional, []) 
  • twisted/names/test/test_names.py

    === modified file 'twisted/names/test/test_names.py'
     
    66Test cases for twisted.names. 
    77""" 
    88 
     9from io import BytesIO 
    910import socket, operator, copy 
    1011from StringIO import StringIO 
    1112 
     
    396397         ) 
    397398 
    398399 
    399     def test_zoneTransfer(self): 
     400    def xtest_zoneTransfer(self): 
    400401        """ 
    401402        Test DNS 'AXFR' queries (Zone transfer) 
    402403        """ 
     
    440441        Assert that the named method is called with the given message when 
    441442        it is passed to L{DNSServerFactory.messageReceived}. 
    442443        """ 
    443         # Make it appear to have some queries so that 
    444         # DNSServerFactory.allowQuery allows it. 
    445         message.queries = [None] 
    446  
     444        class FakeResolver(object): 
     445            def query(self, query, timeout=None): 
     446                return defer.fail(failure.Failure(dns.AuthoritativeDomainError(query.name))) 
     447 
     448        replies = [] 
     449        class CapturingDNSServerFactory(server.DNSServerFactory): 
     450            def sendReply(self, protocol, message, address): 
     451                replies.append((protocol, message, address)) 
     452 
     453        factory = CapturingDNSServerFactory(authorities=[FakeResolver()]) 
     454 
     455        originalHandler = getattr(factory, methodName) 
    447456        receivedMessages = [] 
     457 
    448458        def fakeHandler(message, protocol, address): 
    449             receivedMessages.append((message, protocol, address)) 
     459            receivedMessages.append(message) 
     460            return originalHandler(message, protocol, address) 
     461        setattr(factory, methodName, fakeHandler) 
    450462 
    451463        class FakeProtocol(object): 
    452464            def writeMessage(self, message): 
    453465                pass 
    454466 
    455467        protocol = FakeProtocol() 
    456         factory = server.DNSServerFactory(None) 
    457         setattr(factory, methodName, fakeHandler) 
    458468        factory.messageReceived(message, protocol) 
    459         self.assertEqual(receivedMessages, [(message, protocol, None)]) 
     469        return receivedMessages, replies 
    460470 
    461471 
    462472    def test_notifyMessageReceived(self): 
     
    465475        of C{OP_NOTIFY} on to L{DNSServerFactory.handleNotify}. 
    466476        """ 
    467477        # RFC 1996, section 4.5 
    468         opCode = 4 
    469         self._messageReceivedTest('handleNotify', Message(opCode=opCode)) 
     478        message = dns.EDNSMessage(opCode=4) 
     479        # Make it appear to have some queries so that 
     480        # DNSServerFactory.allowQuery allows it. 
     481        message.queries = [None] 
     482 
     483        receivedMessages, replies = self._messageReceivedTest('handleNotify', message) 
     484        self.assertEqual(receivedMessages, [message]) 
     485 
    470486 
    471487 
    472488    def test_updateMessageReceived(self): 
     
    477493        This may change if the implementation ever covers update messages. 
    478494        """ 
    479495        # RFC 2136, section 1.3 
    480         opCode = 5 
    481         self._messageReceivedTest('handleOther', Message(opCode=opCode)) 
     496        message = dns.EDNSMessage(opCode=5) 
     497        # Make it appear to have some queries so that 
     498        # DNSServerFactory.allowQuery allows it. 
     499        message.queries = [None] 
     500 
     501        receivedMessages, replies = self._messageReceivedTest('handleOther', message) 
     502        self.assertEqual(receivedMessages, [message]) 
     503 
     504 
     505    def test_ednsMessageReceived(self): 
     506        """ 
     507        If L{DNSServerFactory.messageReceived} is passed an EDNS 
     508        message the resulting response should not contain EDNS OPT 
     509        records in the additional section. 
     510 
     511        Twisted DNS currently *chooses* not to support EDNS and must 
     512        not send EDNS related records even though it can send and 
     513        receive them. 
     514 
     515        https://tools.ietf.org/html/rfc6891#section-7 
     516        Responders that choose not to implement the protocol extensions 
     517        defined in this document MUST respond with a return code (RCODE) of 
     518        FORMERR to messages containing an OPT record in the additional 
     519        section and MUST NOT include an OPT record in the response. 
     520        """ 
     521        from twisted.names.test.test_dns import TestMessages 
     522        bytes, messageKwargs = TestMessages().EDNS_QUERY 
     523        message = dns.EDNSMessage.decode(BytesIO(bytes)) 
     524        receivedMessages, replies = self._messageReceivedTest('handleQuery', message) 
     525        self.assertEqual(receivedMessages, []) 
     526        proto, message, address = replies.pop() 
     527        self.assertEqual(message.rCode, dns.EFORMAT) 
     528        optRecords = [r for r in message.additional if r.type is dns.OPT] 
     529        self.assertEqual(optRecords, []) 
    482530 
    483531 
    484532    def test_connectionTracking(self): 
  • twisted/names/test/test_rootresolve.py

    === modified file 'twisted/names/test/test_rootresolve.py'
     
    44""" 
    55Test cases for Twisted.names' root resolver. 
    66""" 
    7  
     7from io import BytesIO 
    88from random import randrange 
    99 
    1010from zope.interface import implementer 
     
    2020from twisted.names.root import Resolver 
    2121from twisted.names.dns import ( 
    2222    IN, HS, A, NS, CNAME, OK, ENAME, Record_CNAME, 
    23     Name, Query, Message, RRHeader, Record_A, Record_NS) 
     23    Name, Query, EDNSMessage, RRHeader, Record_A, Record_NS) 
    2424from twisted.names.error import DNSNameError, ResolverError 
    2525 
    2626 
     
    171171        # And a DNS packet sent. 
    172172        [(packet, address)] = transport._sentPackets 
    173173 
    174         msg = Message() 
    175         msg.fromStr(packet) 
     174 
     175        msg = EDNSMessage.decode(BytesIO(packet)) 
    176176 
    177177        # It should be a query with the parameters used above. 
    178178        self.assertEqual(msg.queries, [Query(b'foo.example.com', A, IN)]) 
     
    214214        L{Message} instance. 
    215215        """ 
    216216        message = self._queryTest(False) 
    217         self.assertIsInstance(message, Message) 
     217        self.assertIsInstance(message, EDNSMessage) 
    218218        self.assertEqual(message.queries, []) 
    219219        self.assertEqual( 
    220220            message.answers, 
     
    238238 
    239239        @return: A new L{Message} initialized with the given values. 
    240240        """ 
    241         response = Message(rCode=rCode) 
     241        response = EDNSMessage(rCode=rCode) 
    242242        for (section, data) in [(response.answers, answers), 
    243243                                (response.authority, authority), 
    244244                                (response.additional, additional)]: 
     
    593593    message=( 
    594594        'twisted.names.root.retry is deprecated since Twisted 10.0.  Use a ' 
    595595        'Resolver object for retry logic.')) 
    596  
    597