[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