From: Nicko Dehaine Date: Sat, 14 Dec 2019 00:06:03 +0000 (-0800) Subject: Support for APL Records X-Git-Tag: rec-4.4.0-beta1~4^2~33 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=66eb5eb73e0d0682bbc6052f85cf399f8c0f1fb8;p=thirdparty%2Fpdns.git Support for APL Records This patch adds support for APL records (rfc3123) - Tested with the BIND and SQLite3 backends - Tested IPv4 and IPv6 - Tested Dynamic updates - Tested negation flag - Closes #8379 The patch includes a documentation update (need to set release version once accepted) and unit tests. Contributors: nicko@threatstop.com - Nicolas Dehaine dshabanov@threatstop.com - Dmitry Shabanov I (nicko@threatstop.com) agree that this code can become GPLv2 licensed. --- diff --git a/docs/appendices/types.rst b/docs/appendices/types.rst index f22552b749..e7dd3607e9 100644 --- a/docs/appendices/types.rst +++ b/docs/appendices/types.rst @@ -57,6 +57,17 @@ records. .. _types-caa: +APL +----- + +.. versionadded:: tbd + +The APL record, +specified in :rfc:`3123`, is used +to specify a DNS RR type "APL" for address prefix lists. + +.. _types-caa: + CAA --- diff --git a/pdns/dnsrecords.cc b/pdns/dnsrecords.cc index 5d85ed1d03..bf44f6e772 100644 --- a/pdns/dnsrecords.cc +++ b/pdns/dnsrecords.cc @@ -489,6 +489,242 @@ string EUI64RecordContent::getZoneRepresentation(bool noDot) const /* EUI64 end */ +/* APL start */ +/* https://tools.ietf.org/html/rfc3123 */ +void APLRecordContent::report(void) +{ + regist(1, QType::APL, &make, &make, "APL"); +} +// Parse incoming packets (e.g. nsupdate) +std::shared_ptr APLRecordContent::make(const DNSRecord &dr, PacketReader& pr) { + uint8_t temp; + + if(dr.d_clen < 5 or dr.d_clen > 20) + throw MOADNSException("Wrong size for APL record"); + + auto ret=std::make_shared(); + pr.xfr16BitInt(ret->d_family); + pr.xfr8BitInt(ret->d_prefix); + pr.xfr8BitInt(temp); + ret->d_n = (temp & 128) >> 7; + ret->d_afdlength = temp & 127; + + if (ret->d_family == APL_FAMILY_IPV4) { // IPv4 + if (ret->d_afdlength > 4) { + throw MOADNSException("Invalid IP length for IPv4 APL"); + } + bzero(ret->d_ip4, sizeof(ret->d_ip4)); + for (int i=0; i < 4; i++) { + if (i < ret->d_afdlength) + pr.xfr8BitInt(ret->d_ip4[i]); + } + } else if (ret->d_family == APL_FAMILY_IPV6) { + if (ret->d_afdlength > 16) { + throw MOADNSException("Invalid IP length for IPv6 APL"); + } + bzero(ret->d_ip6, sizeof(ret->d_ip6)); + for (int i=0; i< 16; i++) { + if (i < ret->d_afdlength) + pr.xfr8BitInt(ret->d_ip6[i]); + } + } else + throw MOADNSException("Unknown family for APL record"); + + return ret; +} + +// Parse backend record +std::shared_ptr APLRecordContent::make(const string& zone) { + string record; + + auto ret=std::make_shared(); + + // Strip the optional leading ! (negate) + if (zone[0] == '!') { + ret->d_n = 1; + record = zone.substr(1, zone.length()-1); + } else { + ret->d_n = 0; + record = zone; + } + + if (record.find("1:", 0) == 0) { // IPv4 + unsigned int prefix; + uint32_t v4ip; + string ipstr, subnetstr; + int subnet_pos; + ComboAddress ca; + int bytes; + + ret->d_family = APL_FAMILY_IPV4; + + // Find netmask + subnet_pos = record.rfind("/"); + + if (subnet_pos < 0) { + throw MOADNSException("Asked to decode '"+zone+"' as an APL record, but missing subnet mask"); + } + + // Read IPv4 string into a ComboAddress + ipstr = record.substr(2, subnet_pos - 2); + subnetstr = record.substr(subnet_pos + 1, record.length() - subnet_pos - 1); + ca = makeComboAddress(ipstr); + v4ip = ntohl(ca.sin4.sin_addr.s_addr); + if (ca.sin4.sin_family != AF_INET) { // ComboAddress will match v6 IPs, which is not valid here + throw MOADNSException("Asked to decode '"+zone+"' as an APL record, but found invalid v4 IP address"); + } + // Section 4.1 of RFC 3123 (don't send trailing "0" bytes) + // Copy data; using array of bytes since we might end up truncating them in the packet + bzero(ret->d_ip4, sizeof(ret->d_ip4)); + for (int i=0; i<4; i++) { + ret->d_ip4[3-i] = (v4ip & 255); + v4ip = v4ip >> 8; + } + // Remove trailing "0" bytes from packet and calculate length + bytes = 4; // Start by assuming we'll send 4 bytes + v4ip = ntohl(ca.sin4.sin_addr.s_addr); + for (int i=0; i<4; i++) { + if ((v4ip & 255) == 0) { + // trailing 0 byte, reduce length + bytes--; + } else + { + // Found non-0 byte, stop trimming + break; + } + v4ip = v4ip >> 8; + } + + // Prefix length + if (sscanf(subnetstr.c_str(), "%u", &prefix) == 1) { + ret->d_prefix=prefix; + } else + throw MOADNSException("Asked to decode '"+zone+"' as an IPv4 APL record, but found invalid prefix string "+subnetstr); + if (prefix > 32) + throw MOADNSException("Asked to decode '"+zone+"' as an IPv4 APL record, but found invalid prefix value "+std::to_string(prefix)); + ret->d_afdlength = bytes; + + } else if (record.find("2:", 0) == 0) { // IPv6 + unsigned int prefix; + int subnet_pos; + string ipstr, subnetstr; + ComboAddress ca; + int bytes; + + ret->d_family = APL_FAMILY_IPV6; + + // Find Netmask + subnet_pos = record.rfind("/"); + if (subnet_pos < 0) { + throw MOADNSException("Asked to decode '"+zone+"' as an APL record, but missing subnet mask"); + } + + // Parse IPv6 string into ComboAddress + ipstr = record.substr(2, subnet_pos - 2); + subnetstr = record.substr(subnet_pos + 1, record.length() - subnet_pos - 1); + ca = makeComboAddress(ipstr); + + // Section 4.2 of RFC 3123 (don't send trailing "0" bytes) + // Remove trailing "0" bytes from packet and reduce length + bytes = 16; // Start by assuming we'll send 16 bytes + for (int i=15; i>=0; i--) { + if (ca.sin6.sin6_addr.s6_addr[i] == 0) { + // trailing 0 byte, calculate length + bytes--; + } else { + // Found non-0 byte, stop trimming + break; + } + } + bzero(ret->d_ip6, sizeof(ret->d_ip6)); + for (int i=0; id_ip6[i] = ca.sin6.sin6_addr.s6_addr[i]; + } + ret->d_afdlength = bytes; + + // Prefix length + prefix = 0; + if (sscanf(subnetstr.c_str(), "%u", &prefix) == 1) { + ret->d_prefix=prefix; + } else + throw MOADNSException("Asked to decode '"+zone+"' as an IPv6 APL record, but found invalid prefix string "+subnetstr); + if (prefix > 32) + throw MOADNSException("Asked to decode '"+zone+"' as an IPv6 APL record, but found invalid prefix value "+subnetstr); + } else { + throw MOADNSException("Asked to encode '"+zone+"' as an IPv6 APL record but got unknown Address Family"); + } + + return ret; +} + +// DNSRecord to Packet conversion +void APLRecordContent::toPacket(DNSPacketWriter& pw) { + + pw.xfr16BitInt(d_family); + pw.xfr8BitInt(d_prefix); + pw.xfr8BitInt((d_n << 7) + d_afdlength); + if (d_family == APL_FAMILY_IPV4) { + for (int i=0; i make(const DNSRecord &dr, PacketReader& pr); + static std::shared_ptr make(const string& zone); // FIXME400: DNSName& zone? + string getZoneRepresentation(bool noDot=false) const override; + void toPacket(DNSPacketWriter& pw) override; + uint16_t getType() const override { return QType::APL; } +private: + uint16_t d_family; + uint8_t d_prefix; + unsigned int d_n : 1; + unsigned int d_afdlength : 7; + uint8_t d_ip4[4]; + unsigned char d_ip6[16]; +}; + + class TKEYRecordContent : public DNSRecordContent { public: diff --git a/pdns/qtype.hh b/pdns/qtype.hh index 03b6c8a5c7..5c9b80cf46 100644 --- a/pdns/qtype.hh +++ b/pdns/qtype.hh @@ -95,6 +95,7 @@ public: A6=38, DNAME=39, OPT=41, + APL=42, DS=43, SSHFP=44, IPSECKEY=45, @@ -194,6 +195,7 @@ private: qtype_insert("A6", 38); qtype_insert("DNAME", 39); qtype_insert("OPT", 41); + qtype_insert("APL", 42); qtype_insert("DS", 43); qtype_insert("SSHFP", 44); qtype_insert("IPSECKEY", 45); diff --git a/pdns/test-dnsrecords_cc.cc b/pdns/test-dnsrecords_cc.cc index 441f6464d0..4cf81d427b 100644 --- a/pdns/test-dnsrecords_cc.cc +++ b/pdns/test-dnsrecords_cc.cc @@ -132,6 +132,17 @@ BOOST_AUTO_TEST_CASE(test_record_types) { // X.509 as per PKIX (CASE_S(QType::CERT, "1 0 0 MIIB9DCCAV2gAwIBAgIJAKxUfFVXhw7HMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNVBAMMCHJlYy50ZXN0MB4XDTEzMDUxMjE5NDgwOVoXDTEzMDYxMTE5NDgwOVowEzERMA8GA1UEAwwIcmVjLnRlc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANKCu5aN/ewOXRPfzAo27XMXhYFCThCjfInTAUIEkzs6jBFZ/eyyIa/kFoiD0tAKwfFfykYU+9XgXeLjetD7rYt3SN3bzzCznoBGbGHHM0Fecrn0LV+tC/NfBB61Yx7e0AMUxmxIeLNRQW5ca5CW8qcIiiQ4fl0BScUjc5+E9QLHAgMBAAGjUDBOMB0GA1UdDgQWBBRzcVu/2bwrgkES+FhYbxZqr7mUgjAfBgNVHSMEGDAWgBRzcVu/2bwrgkES+FhYbxZqr7mUgjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAFVQ8dZBOasOhsWzA/xpAV0WdsqVkxBxrkGIRlbHHBFqOBOOz2MFSzUNx4mDy0qDKI28gcWmWaVsxoQ9VFLD6YRJuUoM8MDNcZDJbKpfDumjvvfnUAK+SiM2c4Ur3xpf0wanCA60/q2bOtFiB0tfAH6RVuIgMC3qjHAIaKEld+fE", "\x00\x01\x00\x00\x00\x30\x82\x01\xf4\x30\x82\x01\x5d\xa0\x03\x02\x01\x02\x02\x09\x00\xac\x54\x7c\x55\x57\x87\x0e\xc7\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x1e\x17\x0d\x31\x33\x30\x35\x31\x32\x31\x39\x34\x38\x30\x39\x5a\x17\x0d\x31\x33\x30\x36\x31\x31\x31\x39\x34\x38\x30\x39\x5a\x30\x13\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x72\x65\x63\x2e\x74\x65\x73\x74\x30\x81\x9f\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x81\x8d\x00\x30\x81\x89\x02\x81\x81\x00\xd2\x82\xbb\x96\x8d\xfd\xec\x0e\x5d\x13\xdf\xcc\x0a\x36\xed\x73\x17\x85\x81\x42\x4e\x10\xa3\x7c\x89\xd3\x01\x42\x04\x93\x3b\x3a\x8c\x11\x59\xfd\xec\xb2\x21\xaf\xe4\x16\x88\x83\xd2\xd0\x0a\xc1\xf1\x5f\xca\x46\x14\xfb\xd5\xe0\x5d\xe2\xe3\x7a\xd0\xfb\xad\x8b\x77\x48\xdd\xdb\xcf\x30\xb3\x9e\x80\x46\x6c\x61\xc7\x33\x41\x5e\x72\xb9\xf4\x2d\x5f\xad\x0b\xf3\x5f\x04\x1e\xb5\x63\x1e\xde\xd0\x03\x14\xc6\x6c\x48\x78\xb3\x51\x41\x6e\x5c\x6b\x90\x96\xf2\xa7\x08\x8a\x24\x38\x7e\x5d\x01\x49\xc5\x23\x73\x9f\x84\xf5\x02\xc7\x02\x03\x01\x00\x01\xa3\x50\x30\x4e\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x73\x71\x5b\xbf\xd9\xbc\x2b\x82\x41\x12\xf8\x58\x58\x6f\x16\x6a\xaf\xb9\x94\x82\x30\x0c\x06\x03\x55\x1d\x13\x04\x05\x30\x03\x01\x01\xff\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05\x05\x00\x03\x81\x81\x00\x55\x50\xf1\xd6\x41\x39\xab\x0e\x86\xc5\xb3\x03\xfc\x69\x01\x5d\x16\x76\xca\x95\x93\x10\x71\xae\x41\x88\x46\x56\xc7\x1c\x11\x6a\x38\x13\x8e\xcf\x63\x05\x4b\x35\x0d\xc7\x89\x83\xcb\x4a\x83\x28\x8d\xbc\x81\xc5\xa6\x59\xa5\x6c\xc6\x84\x3d\x54\x52\xc3\xe9\x84\x49\xb9\x4a\x0c\xf0\xc0\xcd\x71\x90\xc9\x6c\xaa\x5f\x0e\xe9\xa3\xbe\xf7\xe7\x50\x02\xbe\x4a\x23\x36\x73\x85\x2b\xdf\x1a\x5f\xd3\x06\xa7\x08\x0e\xb4\xfe\xad\x9b\x3a\xd1\x62\x07\x4b\x5f\x00\x7e\x91\x56\xe2\x20\x30\x2d\xea\x8c\x70\x08\x68\xa1\x25\x77\xe7\xc4")) + (CASE_S(QType::APL,"1:10.1.1.1/32", "\x00\x01\x20\x04\x0a\x01\x01\x01")) + (CASE_S(QType::APL,"1:10.1.1.0/24", "\x00\x01\x18\x03\x0a\x01\x01")) + (CASE_S(QType::APL,"1:60.0.0.0/8", "\x00\x01\x08\x01\x3c")) + (CASE_S(QType::APL,"1:10.1.1.1/32", "\x00\x01\x20\x04\x0a\x01\x01\x01")) + (CASE_S(QType::APL,"!1:10.1.1.1/32", "\x00\x01\x20\x84\x0a\x01\x01\x01")) + (CASE_S(QType::APL,"1:255.255.255.255/32", "\x00\x01\x20\x04\xff\xff\xff\xff")) + (CASE_S(QType::APL,"2:fe80::/8", "\x00\x02\x08\x02\xfe\x80")) + (CASE_S(QType::APL,"2:2001::1/32", "\x00\x02\x20\x10\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")) + (CASE_S(QType::APL,"!2:2001::1/32", "\x00\x02\x20\x90\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")) + (CASE_S(QType::APL,"2:fe80:1234:5678:9910:8bc:3359:b2e8:720e/32", "\x00\x02\x20\x10\xfe\x80\x12\x34\x56\x78\x99\x10\x08\xbc\x33\x59\xb2\xe8\x72\x0e")) + (CASE_S(QType::APL,"2:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/32","\x00\x02\x20\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff")) (CASE_L(QType::DS, "20642 8 2 04443ABE7E94C3985196BEAE5D548C727B044DDA5151E60D7CD76A9F D931D00E", "20642 8 2 04443abe7e94c3985196beae5d548c727b044dda5151e60d7cd76a9fd931d00e", "\x50\xa2\x08\x02\x04\x44\x3a\xbe\x7e\x94\xc3\x98\x51\x96\xbe\xae\x5d\x54\x8c\x72\x7b\x04\x4d\xda\x51\x51\xe6\x0d\x7c\xd7\x6a\x9f\xd9\x31\xd0\x0e")) (CASE_S(QType::SSHFP, "1 1 aa65e3415a50d9b3519c2b17aceb815fc2538d88", "\x01\x01\xaa\x65\xe3\x41\x5a\x50\xd9\xb3\x51\x9c\x2b\x17\xac\xeb\x81\x5f\xc2\x53\x8d\x88")) (CASE_L(QType::SSHFP, "1 1 aa65e3415a50d9b3519c2b17aceb815fc253 8d88", "1 1 aa65e3415a50d9b3519c2b17aceb815fc2538d88", "\x01\x01\xaa\x65\xe3\x41\x5a\x50\xd9\xb3\x51\x9c\x2b\x17\xac\xeb\x81\x5f\xc2\x53\x8d\x88"))