Message ID | 20210518020104.452472-1-numans@ovn.org |
---|---|
State | Accepted |
Headers | show |
Series | [ovs-dev] controller: Add support for PTR DNS requests. | expand |
On Mon, May 17, 2021 at 10:01 PM <numans@ovn.org> wrote: > > From: Vladislav Odintsov <odivlad@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 <odivlad@gmail.com> Hi Vladislav, Thanks for adding this feature. I applied this patch to master. Sorry that the PR was up for a long time. Regards Numan > --- > 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 > dev@openvswitch.org > https://mail.openvswitch.org/mailman/listinfo/ovs-dev >
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