[ovs-dev] [PATCH ovn] controller: Add support for PTR DNS requests.
Numan Siddique
numans at ovn.org
Wed May 19 15:07:53 UTC 2021
On Mon, May 17, 2021 at 10:01 PM <numans at ovn.org> wrote:
>
> From: Vladislav Odintsov <odivlad at 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 at 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 at openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
More information about the dev
mailing list