diff mbox series

[ovs-dev] controller: Add support for PTR DNS requests.

Message ID 20210518020104.452472-1-numans@ovn.org
State Accepted
Headers show
Series [ovs-dev] controller: Add support for PTR DNS requests. | expand

Commit Message

Numan Siddique May 18, 2021, 2:01 a.m. UTC
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>
---
 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(-)

Comments

Numan Siddique May 19, 2021, 3:07 p.m. UTC | #1
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 mbox series

Patch

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