From: Vladislav Odintsov <odiv...@gmail.com> The native OVN DNS support doesn't yet support for PTR DNS requests. This patch adds the support for it. If suppose there is a dns record as - "vm1.ovn.org"="10.0.0.4", then a normal DNS request will query for "vm1.ovn.org" and the reply will be the IP address - 10.0.0.4. PTR DNS request helps in getting the domain name of the IP address. For the above example, the PTR DNS request will have a query name as - "4.0.0.10.in-addr.arpa". And the response will have "vm1.ovn.org". In order to support this feature, this patch expects the CMS to define an another entry in the DNS record as - "4.0.0.10.in-addr.arpa"="vm1.ovn.org".
This makes the job of ovn-controller easier to support this feature. Submitted-at: https://github.com/ovn-org/ovn/pull/74 Signed-off-by: Vladislav Odintsov <odiv...@gmail.com> --- NEWS | 1 + controller/pinctrl.c | 181 +++++++++++++++++++++++++++++++------------ lib/ovn-l7.h | 8 ++ ovn-nb.xml | 6 ++ tests/ovn.at | 83 +++++++++++++++++++- 5 files changed, 229 insertions(+), 50 deletions(-) diff --git a/NEWS b/NEWS index 1d3603269..f96ed73f0 100644 --- a/NEWS +++ b/NEWS @@ -18,6 +18,7 @@ Post-v21.03.0 datapath flows with this field used. - Introduce a new "allow-stateless" ACL verb to always bypass connection tracking. The existing "allow" verb behavior is left intact. + - Added support in native DNS to respond to PTR request types. OVN v21.03.0 - 12 Mar 2021 ------------------------- diff --git a/controller/pinctrl.c b/controller/pinctrl.c index 523a45b9a..01cbbacfd 100644 --- a/controller/pinctrl.c +++ b/controller/pinctrl.c @@ -2698,6 +2698,106 @@ destroy_dns_cache(void) } } +/* Populates dns_answer struct with base data. + * Copy the answer section + * Format of the answer section is + * - NAME -> The domain name + * - TYPE -> 2 octets containing one of the RR type codes + * - CLASS -> 2 octets which specify the class of the data + * in the RDATA field. + * - TTL -> 32 bit unsigned int specifying the time + * interval (in secs) that the resource record + * may be cached before it should be discarded. + * - RDLENGTH -> 16 bit integer specifying the length of the + * RDATA field. + * - RDATA -> a variable length string of octets that + * describes the resource. + */ +static void +dns_build_base_answer( + struct ofpbuf *dns_answer, const uint8_t *in_queryname, + uint16_t query_length, int query_type) +{ + ofpbuf_put(dns_answer, in_queryname, query_length); + put_be16(dns_answer, htons(query_type)); + put_be16(dns_answer, htons(DNS_CLASS_IN)); + put_be32(dns_answer, htonl(DNS_DEFAULT_RR_TTL)); +} + +/* Populates dns_answer struct with a TYPE A answer. */ +static void +dns_build_a_answer( + struct ofpbuf *dns_answer, const uint8_t *in_queryname, + uint16_t query_length, const ovs_be32 addr) +{ + dns_build_base_answer(dns_answer, in_queryname, query_length, + DNS_QUERY_TYPE_A); + put_be16(dns_answer, htons(sizeof(ovs_be32))); + put_be32(dns_answer, addr); +} + +/* Populates dns_answer struct with a TYPE AAAA answer. */ +static void +dns_build_aaaa_answer( + struct ofpbuf *dns_answer, const uint8_t *in_queryname, + uint16_t query_length, const struct in6_addr *addr) +{ + dns_build_base_answer(dns_answer, in_queryname, query_length, + DNS_QUERY_TYPE_AAAA); + put_be16(dns_answer, htons(sizeof(*addr))); + ofpbuf_put(dns_answer, addr, sizeof(*addr)); +} + +/* Populates dns_answer struct with a TYPE PTR answer. */ +static void +dns_build_ptr_answer( + struct ofpbuf *dns_answer, const uint8_t *in_queryname, + uint16_t query_length, const char *answer_data) +{ + char *encoded_answer; + uint16_t encoded_answer_length; + + dns_build_base_answer(dns_answer, in_queryname, query_length, + DNS_QUERY_TYPE_PTR); + + /* Initialize string 2 chars longer than real answer: + * first label length and terminating zero-length label. + * If the answer_data is - vm1tst.ovn.org, it will be encoded as + * - 0010 (Total length which is 16) + * - 06766d31747374 (vm1tst) + * - 036f766e (ovn) + * - 036f7267 (org + * - 00 (zero length field) */ + encoded_answer_length = strlen(answer_data) + 2; + encoded_answer = (char *)xzalloc(encoded_answer_length); + + put_be16(dns_answer, htons(encoded_answer_length)); + uint8_t label_len_index = 0; + uint16_t label_len = 0; + char *encoded_answer_ptr = (char *)encoded_answer + 1; + while (*answer_data) { + if (*answer_data == '.') { + /* Label has ended. Update the length of the label. */ + encoded_answer[label_len_index] = label_len; + label_len_index += (label_len + 1); + label_len = 0; /* Init to 0 for the next label. */ + } else { + *encoded_answer_ptr = *answer_data; + label_len++; + } + encoded_answer_ptr++; + answer_data++; + } + + /* This is required for the last label if it doesn't end with '.' */ + if (label_len) { + encoded_answer[label_len_index] = label_len; + } + + ofpbuf_put(dns_answer, encoded_answer, encoded_answer_length); + free(encoded_answer); +} + /* Called with in the pinctrl_handler thread context. */ static void pinctrl_handle_dns_lookup( @@ -2793,15 +2893,16 @@ pinctrl_handle_dns_lookup( } uint16_t query_type = ntohs(*ALIGNED_CAST(const ovs_be16 *, in_dns_data)); - /* Supported query types - A, AAAA and ANY */ + /* Supported query types - A, AAAA, ANY and PTR */ if (!(query_type == DNS_QUERY_TYPE_A || query_type == DNS_QUERY_TYPE_AAAA - || query_type == DNS_QUERY_TYPE_ANY)) { + || query_type == DNS_QUERY_TYPE_ANY + || query_type == DNS_QUERY_TYPE_PTR)) { ds_destroy(&query_name); goto exit; } uint64_t dp_key = ntohll(pin->flow_metadata.flow.metadata); - const char *answer_ips = NULL; + const char *answer_data = NULL; struct shash_node *iter; SHASH_FOR_EACH (iter, &dns_cache) { struct dns_data *d = iter->data; @@ -2811,75 +2912,57 @@ pinctrl_handle_dns_lookup( * lowercase to perform case insensitive lookup */ char *query_name_lower = str_tolower(ds_cstr(&query_name)); - answer_ips = smap_get(&d->records, query_name_lower); + answer_data = smap_get(&d->records, query_name_lower); free(query_name_lower); - if (answer_ips) { + if (answer_data) { break; } } } - if (answer_ips) { + if (answer_data) { break; } } ds_destroy(&query_name); - if (!answer_ips) { + if (!answer_data) { goto exit; } - struct lport_addresses ip_addrs; - if (!extract_ip_addresses(answer_ips, &ip_addrs)) { - goto exit; - } uint16_t ancount = 0; uint64_t dns_ans_stub[128 / 8]; struct ofpbuf dns_answer = OFPBUF_STUB_INITIALIZER(dns_ans_stub); - if (query_type == DNS_QUERY_TYPE_A || query_type == DNS_QUERY_TYPE_ANY) { - for (size_t i = 0; i < ip_addrs.n_ipv4_addrs; i++) { - /* Copy the answer section */ - /* Format of the answer section is - * - NAME -> The domain name - * - TYPE -> 2 octets containing one of the RR type codes - * - CLASS -> 2 octets which specify the class of the data - * in the RDATA field. - * - TTL -> 32 bit unsigned int specifying the time - * interval (in secs) that the resource record - * may be cached before it should be discarded. - * - RDLENGTH -> 16 bit integer specifying the length of the - * RDATA field. - * - RDATA -> a variable length string of octets that - * describes the resource. In our case it will - * be IP address of the domain name. - */ - ofpbuf_put(&dns_answer, in_queryname, idx); - put_be16(&dns_answer, htons(DNS_QUERY_TYPE_A)); - put_be16(&dns_answer, htons(DNS_CLASS_IN)); - put_be32(&dns_answer, htonl(DNS_DEFAULT_RR_TTL)); - put_be16(&dns_answer, htons(sizeof(ovs_be32))); - put_be32(&dns_answer, ip_addrs.ipv4_addrs[i].addr); - ancount++; + if (query_type == DNS_QUERY_TYPE_PTR) { + dns_build_ptr_answer(&dns_answer, in_queryname, idx, answer_data); + ancount++; + } else { + struct lport_addresses ip_addrs; + if (!extract_ip_addresses(answer_data, &ip_addrs)) { + goto exit; } - } - if (query_type == DNS_QUERY_TYPE_AAAA || - query_type == DNS_QUERY_TYPE_ANY) { - for (size_t i = 0; i < ip_addrs.n_ipv6_addrs; i++) { - ofpbuf_put(&dns_answer, in_queryname, idx); - put_be16(&dns_answer, htons(DNS_QUERY_TYPE_AAAA)); - put_be16(&dns_answer, htons(DNS_CLASS_IN)); - put_be32(&dns_answer, htonl(DNS_DEFAULT_RR_TTL)); - const struct in6_addr *ip6 = &ip_addrs.ipv6_addrs[i].addr; - put_be16(&dns_answer, htons(sizeof *ip6)); - ofpbuf_put(&dns_answer, ip6, sizeof *ip6); - ancount++; + if (query_type == DNS_QUERY_TYPE_A || + query_type == DNS_QUERY_TYPE_ANY) { + for (size_t i = 0; i < ip_addrs.n_ipv4_addrs; i++) { + dns_build_a_answer(&dns_answer, in_queryname, idx, + ip_addrs.ipv4_addrs[i].addr); + ancount++; + } } - } - destroy_lport_addresses(&ip_addrs); + if (query_type == DNS_QUERY_TYPE_AAAA || + query_type == DNS_QUERY_TYPE_ANY) { + for (size_t i = 0; i < ip_addrs.n_ipv6_addrs; i++) { + dns_build_aaaa_answer(&dns_answer, in_queryname, idx, + &ip_addrs.ipv6_addrs[i].addr); + ancount++; + } + } + destroy_lport_addresses(&ip_addrs); + } if (!ancount) { ofpbuf_uninit(&dns_answer); diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h index 40b00643b..5e33d619c 100644 --- a/lib/ovn-l7.h +++ b/lib/ovn-l7.h @@ -45,6 +45,14 @@ struct bfd_msg { }; BUILD_ASSERT_DECL(BFD_PACKET_LEN == sizeof(struct bfd_msg)); +#define DNS_QUERY_TYPE_A 0x01 +#define DNS_QUERY_TYPE_AAAA 0x1c +#define DNS_QUERY_TYPE_ANY 0xff +#define DNS_QUERY_TYPE_PTR 0x0c + +#define DNS_CLASS_IN 0x01 +#define DNS_DEFAULT_RR_TTL 3600 + /* Generic options map which is used to store dhcpv4 opts and dhcpv6 opts. */ struct gen_opts_map { struct hmap_node hmap_node; diff --git a/ovn-nb.xml b/ovn-nb.xml index ed271d8eb..02fd21605 100644 --- a/ovn-nb.xml +++ b/ovn-nb.xml @@ -3673,7 +3673,13 @@ Key-value pair of DNS records with <code>DNS query name</code> as the key and value as a string of IP address(es) separated by comma or space. + For PTR requests, the key-value pair can be + <code>Reverse IPv4 address.in-addr.arpa</code> and the value + <code>DNS domain name</code>. For IPv6 addresses, the key + has to be <code>Reverse IPv6 address.ip6.arpa</code>. + <p><b>Example: </b> "vm1.ovn.org" = "10.0.0.4 aef0::4"</p> + <p><b>Example: </b> "4.0.0.10.in-addr.arpa" = "vm1.ovn.org"</p> </column> <column name="external_ids"> diff --git a/tests/ovn.at b/tests/ovn.at index dcf3e0e09..abd75314b 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -9653,10 +9653,13 @@ ovn-nbctl lsp-set-port-security ls1-lp2 "f0:00:00:00:00:02 10.0.0.6 20.0.0.4" DNS1=`ovn-nbctl create DNS records={}` DNS2=`ovn-nbctl create DNS records={}` +DNS3=`ovn-nbctl create DNS records={}` ovn-nbctl set DNS $DNS1 records:vm1.ovn.org="10.0.0.4 aef0::4" ovn-nbctl set DNS $DNS1 records:vm2.ovn.org="10.0.0.6 20.0.0.4" ovn-nbctl set DNS $DNS2 records:vm3.ovn.org="40.0.0.4" +ovn-nbctl set DNS $DNS3 records:4.0.0.10.in-addr.arpa="vm1.ovn.org" +ovn-nbctl set DNS $DNS3 records:4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.f.e.a.ip6.arpa="vm1.ovn.org" ovn-nbctl set Logical_switch ls1 dns_records="$DNS1" @@ -9759,6 +9762,21 @@ set_dns_params() { vm1_incomplete) # set type to none type='' + ;; + vm1_ipv4_ptr) + # 4.0.0.10.in-addr.arpa + query_name=01340130013002313007696e2d61646472046172706100 + type=000c + # vm1.ovn.org + expected_dns_answer=${query_name}${type}0001${ttl}000d03766d31036f766e036f726700 + ;; + vm1_ipv6_ptr) + # 4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.f.e.a.ip6.arpa + query_name=0134013001300130013001300130013001300130013001300130013001300130013001300130013001300130013001300130013001300130013001660165016103697036046172706100 + type=000c + # vm1.ovn.org + expected_dns_answer=${query_name}${type}0001${ttl}000d03766d31036f766e036f726700 + ;; esac # TTL - 3600 local dns_req_header=010201200001000000000000 @@ -9858,6 +9876,7 @@ reset_pcap_file hv1-vif2 hv1/vif2 rm -f 1.expected rm -f 2.expected + set_dns_params vm1 src_ip=`ip_to_hex 10 0 0 6` dst_ip=`ip_to_hex 10 0 0 1` @@ -9879,8 +9898,8 @@ reset_pcap_file hv1-vif2 hv1/vif2 rm -f 1.expected rm -f 2.expected -# Try vm1 again but an all-caps query name +# Try vm1 again but an all-caps query name set_dns_params VM1 src_ip=`ip_to_hex 10 0 0 6` dst_ip=`ip_to_hex 10 0 0 1` @@ -9902,6 +9921,7 @@ reset_pcap_file hv1-vif2 hv1/vif2 rm -f 1.expected rm -f 2.expected + # Clear the query name options for ls1-lp2 ovn-nbctl --wait=hv remove DNS $DNS1 records vm2.ovn.org @@ -9922,6 +9942,7 @@ reset_pcap_file hv1-vif2 hv1/vif2 rm -f 1.expected rm -f 2.expected + # Clear the query name for ls1-lp1 # Since ls1 has no query names configued, # ovn-northd should not add the DNS flows. @@ -9944,6 +9965,7 @@ reset_pcap_file hv1-vif2 hv1/vif2 rm -f 1.expected rm -f 2.expected + # Test IPv6 (AAAA records) using IPv4 packet. # Add back the DNS options for ls1-lp1. ovn-nbctl --wait=hv set DNS $DNS1 records:vm1.ovn.org="10.0.0.4 aef0::4" @@ -9969,6 +9991,7 @@ reset_pcap_file hv1-vif2 hv1/vif2 rm -f 1.expected rm -f 2.expected + # Test both IPv4 (A) and IPv6 (AAAA records) using IPv4 packet. set_dns_params vm1_ipv4_v6 src_ip=`ip_to_hex 10 0 0 6` @@ -9991,6 +10014,7 @@ reset_pcap_file hv1-vif2 hv1/vif2 rm -f 1.expected rm -f 2.expected + # Invalid type. set_dns_params vm1_invalid_type src_ip=`ip_to_hex 10 0 0 6` @@ -10009,6 +10033,7 @@ reset_pcap_file hv1-vif2 hv1/vif2 rm -f 1.expected rm -f 2.expected + # Incomplete DNS packet. set_dns_params vm1_incomplete src_ip=`ip_to_hex 10 0 0 6` @@ -10027,6 +10052,7 @@ reset_pcap_file hv1-vif2 hv1/vif2 rm -f 1.expected rm -f 2.expected + # Add one more DNS record to the ls1. ovn-nbctl --wait=hv set Logical_switch ls1 dns_records="$DNS1 $DNS2" @@ -10051,6 +10077,7 @@ reset_pcap_file hv1-vif2 hv1/vif2 rm -f 1.expected rm -f 2.expected + # Try DNS query over IPv6 set_dns_params vm1 src_ip=aef00000000000000000000000000004 @@ -10071,6 +10098,60 @@ reset_pcap_file hv1-vif2 hv1/vif2 rm -f 1.expected rm -f 2.expected + +# Add one more DNS record to the ls1. +ovn-nbctl --wait=hv set Logical_switch ls1 dns_records="$DNS1 $DNS2 $DNS3" +echo "*************************" +ovn-sbctl list DNS +echo "*************************" +ovn-nbctl list DNS +echo "*************************" + +# Test PTR record for IPv4 address using IPv4 packet. +set_dns_params vm1_ipv4_ptr +src_ip=`ip_to_hex 10 0 0 4` +dst_ip=`ip_to_hex 10 0 0 1` +dns_reply=1 +test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data + +# NXT_RESUMEs should be 11. +OVS_WAIT_UNTIL([test 11 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets +cat 1.expected | cut -c -48 > expout +AT_CHECK([cat 1.packets | cut -c -48], [0], [expout]) +# Skipping the IPv4 checksum. +cat 1.expected | cut -c 53- > expout +AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout]) + +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +rm -f 1.expected +rm -f 2.expected + + +# Test PTR record for IPv6 address using IPv4 packet. +set_dns_params vm1_ipv6_ptr +src_ip=`ip_to_hex 10 0 0 4` +dst_ip=`ip_to_hex 10 0 0 1` +dns_reply=1 +test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data + +# NXT_RESUMEs should be 12. +OVS_WAIT_UNTIL([test 12 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets +cat 1.expected | cut -c -48 > expout +AT_CHECK([cat 1.packets | cut -c -48], [0], [expout]) +# Skipping the IPv4 checksum. +cat 1.expected | cut -c 53- > expout +AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout]) + +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +rm -f 1.expected +rm -f 2.expected + OVN_CLEANUP([hv1]) AT_CLEANUP -- 2.30.2 _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev