From: Vladislav Odintsov <odiv...@gmail.com>

The native OVN DNS support doesn't yet support for PTR DNS requests.
This patch adds the support for it.  If suppose there is a dns record
as - "vm1.ovn.org"="10.0.0.4", then a normal DNS request will query for
"vm1.ovn.org" and the reply will be the IP address - 10.0.0.4.
PTR DNS request helps in getting the domain name of the IP address.
For the above example, the PTR DNS request will have a query name as
- "4.0.0.10.in-addr.arpa".  And the response will have "vm1.ovn.org".
In order to support this feature, this patch expects the CMS to define
an another entry in the DNS record  as - "4.0.0.10.in-addr.arpa"="vm1.ovn.org".

This makes the job of ovn-controller easier to support this feature.

Submitted-at: https://github.com/ovn-org/ovn/pull/74
Signed-off-by: Vladislav Odintsov <odiv...@gmail.com>
---
 NEWS                 |   1 +
 controller/pinctrl.c | 181 +++++++++++++++++++++++++++++++------------
 lib/ovn-l7.h         |   8 ++
 ovn-nb.xml           |   6 ++
 tests/ovn.at         |  83 +++++++++++++++++++-
 5 files changed, 229 insertions(+), 50 deletions(-)

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

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to