From: Petr Spacek Date: Thu, 3 Dec 2015 12:07:41 +0000 (+0100) Subject: Add support for EUI48 and EUI64 RR types X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=db37d614fe07f6a20dc5e032024848daa65ac423;p=thirdparty%2Fdnspython.git Add support for EUI48 and EUI64 RR types --- diff --git a/dns/rdatatype.py b/dns/rdatatype.py index ce496de1..d54b7044 100644 --- a/dns/rdatatype.py +++ b/dns/rdatatype.py @@ -84,6 +84,8 @@ CDS = 59 CDNSKEY = 60 SPF = 99 UNSPEC = 103 +EUI48 = 108 +EUI64 = 109 TKEY = 249 TSIG = 250 IXFR = 251 @@ -150,6 +152,8 @@ _by_text = { 'CDNSKEY' : CDNSKEY, 'SPF' : SPF, 'UNSPEC' : UNSPEC, + 'EUI48': EUI48, + 'EUI64': EUI64, 'TKEY' : TKEY, 'TSIG' : TSIG, 'IXFR' : IXFR, diff --git a/dns/rdtypes/ANY/EUI48.py b/dns/rdtypes/ANY/EUI48.py new file mode 100644 index 00000000..6ddfa6a4 --- /dev/null +++ b/dns/rdtypes/ANY/EUI48.py @@ -0,0 +1,28 @@ +# Copyright (C) 2015 Red Hat, Inc. +# Author: Petr Spacek +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED 'AS IS' AND RED HAT DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +import dns.rdtypes.euibase + + +class EUI48(dns.rdtypes.euibase.EUIBase): + """EUI48 record + + @ivar fingerprint: 48-bit Extended Unique Identifier (EUI-48) + @type fingerprint: string + @see: rfc7043.txt""" + + byte_len = 6 # 0123456789ab (in hex) + text_len = byte_len * 3 - 1 # 01-23-45-67-89-ab diff --git a/dns/rdtypes/ANY/EUI64.py b/dns/rdtypes/ANY/EUI64.py new file mode 100644 index 00000000..41abd7b7 --- /dev/null +++ b/dns/rdtypes/ANY/EUI64.py @@ -0,0 +1,28 @@ +# Copyright (C) 2015 Red Hat, Inc. +# Author: Petr Spacek +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED 'AS IS' AND RED HAT DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +import dns.rdtypes.euibase + + +class EUI64(dns.rdtypes.euibase.EUIBase): + """EUI64 record + + @ivar fingerprint: 64-bit Extended Unique Identifier (EUI-64) + @type fingerprint: string + @see: rfc7043.txt""" + + byte_len = 8 # 0123456789abcdef (in hex) + text_len = byte_len * 3 - 1 # 01-23-45-67-89-ab-cd-ef diff --git a/dns/rdtypes/ANY/__init__.py b/dns/rdtypes/ANY/__init__.py index 2f275175..cb528228 100644 --- a/dns/rdtypes/ANY/__init__.py +++ b/dns/rdtypes/ANY/__init__.py @@ -25,6 +25,8 @@ __all__ = [ 'DNAME', 'DNSKEY', 'DS', + 'EUI48', + 'EUI64', 'GPOS', 'HINFO', 'HIP', diff --git a/dns/rdtypes/__init__.py b/dns/rdtypes/__init__.py index 49db5a37..826efbb6 100644 --- a/dns/rdtypes/__init__.py +++ b/dns/rdtypes/__init__.py @@ -18,6 +18,7 @@ __all__ = [ 'ANY', 'IN', + 'euibase', 'mxbase', 'nsbase', ] diff --git a/dns/rdtypes/euibase.py b/dns/rdtypes/euibase.py new file mode 100644 index 00000000..2af018d4 --- /dev/null +++ b/dns/rdtypes/euibase.py @@ -0,0 +1,71 @@ +# Copyright (C) 2015 Red Hat, Inc. +# Author: Petr Spacek +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED 'AS IS' AND RED HAT DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +import binascii + +import dns.rdata + + +class EUIBase(dns.rdata.Rdata): + """EUIxx record + + @ivar fingerprint: xx-bit Extended Unique Identifier (EUI-xx) + @type fingerprint: string + @see: rfc7043.txt""" + + __slots__ = ['eui'] + # define these in subclasses + # byte_len = 6 # 0123456789ab (in hex) + # text_len = byte_len * 3 - 1 # 01-23-45-67-89-ab + + def __init__(self, rdclass, rdtype, eui): + super(EUIBase, self).__init__(rdclass, rdtype) + if len(eui) != self.byte_len: + raise dns.exception.FormError('EUI%s rdata has to have %s bytes' + % (self.byte_len * 8, self.byte_len)) + self.eui = eui + + def to_text(self, origin=None, relativize=True, **kw): + return dns.rdata._hexify(self.eui, chunksize=2).replace(' ', '-') + + def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True): + text = tok.get_string() + tok.get_eol() + if len(text) != cls.text_len: + raise dns.exception.SyntaxError( + 'Input text must have %s characters' % cls.text_len) + expected_dash_idxs = range(2, cls.byte_len * 3 - 1, 3) + for i in expected_dash_idxs: + if text[i] != '-': + raise dns.exception.SyntaxError('Dash expected at position %s' + % i) + text = text.replace('-', '') + try: + data = binascii.unhexlify(text) + except (ValueError, TypeError) as ex: + raise dns.exception.SyntaxError('Hex decoding error: %s' % str(ex)) + return cls(rdclass, rdtype, data) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress=None, origin=None): + file.write(self.eui) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None): + eui = wire[current:current + rdlen].unwrap() + return cls(rdclass, rdtype, eui) + + from_wire = classmethod(from_wire) diff --git a/tests/test_rdtypeanyeui.py b/tests/test_rdtypeanyeui.py new file mode 100644 index 00000000..800d1035 --- /dev/null +++ b/tests/test_rdtypeanyeui.py @@ -0,0 +1,223 @@ +# Copyright (C) 2015 Red Hat, Inc. +# Author: Petr Spacek +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED 'AS IS' AND RED HAT DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +import unittest +try: + from StringIO import StringIO +except ImportError: + from io import BytesIO as StringIO + +import dns.rrset +import dns.rdtypes.ANY.EUI48 +import dns.exception + + +class RdtypeAnyEUI48TestCase(unittest.TestCase): + def testInstOk(self): + '''Valid binary input.''' + eui = b'\x01\x23\x45\x67\x89\xab' + inst = dns.rdtypes.ANY.EUI48.EUI48(dns.rdataclass.IN, + dns.rdatatype.EUI48, + eui) + self.assertEqual(inst.eui, eui) + + def testInstLength(self): + '''Incorrect input length.''' + eui = b'\x01\x23\x45\x67\x89\xab\xcd' + with self.assertRaises(dns.exception.FormError): + dns.rdtypes.ANY.EUI48.EUI48(dns.rdataclass.IN, + dns.rdatatype.EUI48, + eui) + + def testFromTextOk(self): + '''Valid text input.''' + r1 = dns.rrset.from_text('foo', 300, 'IN', 'EUI48', + '01-23-45-67-89-ab') + eui = b'\x01\x23\x45\x67\x89\xab' + self.assertEqual(r1[0].eui, eui) + + def testFromTextLength(self): + '''Invalid input length.''' + with self.assertRaises(dns.exception.SyntaxError): + dns.rrset.from_text('foo', 300, 'IN', 'EUI48', + '00-01-23-45-67-89-ab') + + def testFromTextDelim(self): + '''Invalid delimiter.''' + with self.assertRaises(dns.exception.SyntaxError): + dns.rrset.from_text('foo', 300, 'IN', 'EUI48', '01_23-45-67-89-ab') + + def testFromTextExtraDash(self): + '''Extra dash instead of hex digit.''' + with self.assertRaises(dns.exception.SyntaxError): + dns.rrset.from_text('foo', 300, 'IN', 'EUI48', '0--23-45-67-89-ab') + + def testFromTextMultipleTokens(self): + '''Invalid input divided to multiple tokens.''' + with self.assertRaises(dns.exception.SyntaxError): + dns.rrset.from_text('foo', 300, 'IN', 'EUI48', '01 23-45-67-89-ab') + + def testFromTextInvalidHex(self): + '''Invalid hexadecimal input.''' + with self.assertRaises(dns.exception.SyntaxError): + dns.rrset.from_text('foo', 300, 'IN', 'EUI48', 'g0-23-45-67-89-ab') + + def testToTextOk(self): + '''Valid text output.''' + eui = b'\x01\x23\x45\x67\x89\xab' + exp_text = '01-23-45-67-89-ab' + inst = dns.rdtypes.ANY.EUI48.EUI48(dns.rdataclass.IN, + dns.rdatatype.EUI48, + eui) + text = inst.to_text() + self.assertEqual(exp_text, text) + + def testToWire(self): + '''Valid wire format.''' + eui = b'\x01\x23\x45\x67\x89\xab' + inst = dns.rdtypes.ANY.EUI48.EUI48(dns.rdataclass.IN, + dns.rdatatype.EUI48, + eui) + buff = StringIO() + inst.to_wire(buff) + self.assertEqual(buff.getvalue(), eui) + + def testFromWireOk(self): + '''Valid wire format.''' + eui = b'\x01\x23\x45\x67\x89\xab' + pad_len = 100 + wire = dns.wiredata.WireData(b'x' * pad_len + eui + b'y' * pad_len * 2) + inst = dns.rdtypes.ANY.EUI48.EUI48.from_wire(dns.rdataclass.IN, + dns.rdatatype.EUI48, + wire, + pad_len, + len(eui)) + self.assertEqual(inst.eui, eui) + + def testFromWireLength(self): + '''Valid wire format.''' + eui = b'\x01\x23\x45\x67\x89' + pad_len = 100 + wire = dns.wiredata.WireData(b'x' * pad_len + eui + b'y' * pad_len * 2) + with self.assertRaises(dns.exception.FormError): + dns.rdtypes.ANY.EUI48.EUI48.from_wire(dns.rdataclass.IN, + dns.rdatatype.EUI48, + wire, + pad_len, + len(eui)) + + +class RdtypeAnyEUI64TestCase(unittest.TestCase): + def testInstOk(self): + '''Valid binary input.''' + eui = b'\x01\x23\x45\x67\x89\xab\xcd\xef' + inst = dns.rdtypes.ANY.EUI64.EUI64(dns.rdataclass.IN, + dns.rdatatype.EUI64, + eui) + self.assertEqual(inst.eui, eui) + + def testInstLength(self): + '''Incorrect input length.''' + eui = b'\x01\x23\x45\x67\x89\xab' + with self.assertRaises(dns.exception.FormError): + dns.rdtypes.ANY.EUI64.EUI64(dns.rdataclass.IN, + dns.rdatatype.EUI64, + eui) + + def testFromTextOk(self): + '''Valid text input.''' + r1 = dns.rrset.from_text('foo', 300, 'IN', 'EUI64', + '01-23-45-67-89-ab-cd-ef') + eui = b'\x01\x23\x45\x67\x89\xab\xcd\xef' + self.assertEqual(r1[0].eui, eui) + + def testFromTextLength(self): + '''Invalid input length.''' + with self.assertRaises(dns.exception.SyntaxError): + dns.rrset.from_text('foo', 300, 'IN', 'EUI64', + '01-23-45-67-89-ab') + + def testFromTextDelim(self): + '''Invalid delimiter.''' + with self.assertRaises(dns.exception.SyntaxError): + dns.rrset.from_text('foo', 300, 'IN', 'EUI64', + '01_23-45-67-89-ab-cd-ef') + + def testFromTextExtraDash(self): + '''Extra dash instead of hex digit.''' + with self.assertRaises(dns.exception.SyntaxError): + dns.rrset.from_text('foo', 300, 'IN', 'EUI64', + '0--23-45-67-89-ab-cd-ef') + + def testFromTextMultipleTokens(self): + '''Invalid input divided to multiple tokens.''' + with self.assertRaises(dns.exception.SyntaxError): + dns.rrset.from_text('foo', 300, 'IN', 'EUI64', + '01 23-45-67-89-ab-cd-ef') + + def testFromTextInvalidHex(self): + '''Invalid hexadecimal input.''' + with self.assertRaises(dns.exception.SyntaxError): + dns.rrset.from_text('foo', 300, 'IN', 'EUI64', + 'g0-23-45-67-89-ab-cd-ef') + + def testToTextOk(self): + '''Valid text output.''' + eui = b'\x01\x23\x45\x67\x89\xab\xcd\xef' + exp_text = '01-23-45-67-89-ab-cd-ef' + inst = dns.rdtypes.ANY.EUI64.EUI64(dns.rdataclass.IN, + dns.rdatatype.EUI64, + eui) + text = inst.to_text() + self.assertEqual(exp_text, text) + + def testToWire(self): + '''Valid wire format.''' + eui = b'\x01\x23\x45\x67\x89\xab\xcd\xef' + inst = dns.rdtypes.ANY.EUI64.EUI64(dns.rdataclass.IN, + dns.rdatatype.EUI64, + eui) + buff = StringIO() + inst.to_wire(buff) + self.assertEqual(buff.getvalue(), eui) + + def testFromWireOk(self): + '''Valid wire format.''' + eui = b'\x01\x23\x45\x67\x89\xab\xcd\xef' + pad_len = 100 + wire = dns.wiredata.WireData(b'x' * pad_len + eui + b'y' * pad_len * 2) + inst = dns.rdtypes.ANY.EUI64.EUI64.from_wire(dns.rdataclass.IN, + dns.rdatatype.EUI64, + wire, + pad_len, + len(eui)) + self.assertEqual(inst.eui, eui) + + def testFromWireLength(self): + '''Valid wire format.''' + eui = b'\x01\x23\x45\x67\x89' + pad_len = 100 + wire = dns.wiredata.WireData(b'x' * pad_len + eui + b'y' * pad_len * 2) + with self.assertRaises(dns.exception.FormError): + dns.rdtypes.ANY.EUI64.EUI64.from_wire(dns.rdataclass.IN, + dns.rdatatype.EUI64, + wire, + pad_len, + len(eui)) + + +if __name__ == '__main__': + unittest.main()