diff mbox series

[ovs-dev] DNS: allow defining records that owned by OVN only

Message ID 20231001192704.3107450-1-mheib@redhat.com
State Changes Requested
Headers show
Series [ovs-dev] DNS: allow defining records that owned by OVN only | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test fail github build: failed
ovsrobot/github-robot-_ovn-kubernetes fail github build: failed

Commit Message

Mohammad Heib Oct. 1, 2023, 7:27 p.m. UTC
Currently OVN allows users to create DNS records
and define domains within these records.

These domains can be associated with IPV4 or IPv6
or both, when the user creates a domain with both
IPv4 and IPv6 ovn will answer each query for this
domain immediately and everything works as expected.

But if the user only creates a domain with only IPv4
or IPv6 this will cause the DNS queries respond take
longer than the usual since OVN will forward query and
user will keep waiting for an answer until someone else
replies for this query or timeout occur.

The above behavior is a bit problematic if the user knows
that this domain is only configured in OVN we should not
forward queries for this domain to the outside.

This patch adds an option:ovn-owned for the DNS table
that can be set to "true" when creating a DNS.

When setting this option to "true" all the domains within
this table will be treated as local domains only,
and queries for domains within this table that don't have
an accurate IP will be refused immediately to save the time
of waiting for timeout.

Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=1946662
Signed-off-by: Mohammad Heib <mheib@redhat.com>
---
 NEWS                 |  5 ++++
 controller/pinctrl.c | 47 ++++++++++++++++++++++++++++++--
 northd/northd.c      | 11 ++++++++
 ovn-nb.ovsschema     |  9 ++++--
 ovn-nb.xml           | 14 ++++++++++
 ovn-sb.ovsschema     |  9 ++++--
 ovn-sb.xml           |  6 ++++
 tests/ovn.at         | 65 ++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 160 insertions(+), 6 deletions(-)

Comments

Dumitru Ceara Oct. 9, 2023, 2:04 p.m. UTC | #1
On 10/1/23 21:27, Mohammad Heib wrote:
> Currently OVN allows users to create DNS records
> and define domains within these records.
> 
> These domains can be associated with IPV4 or IPv6
> or both, when the user creates a domain with both
> IPv4 and IPv6 ovn will answer each query for this
> domain immediately and everything works as expected.
> 
> But if the user only creates a domain with only IPv4
> or IPv6 this will cause the DNS queries respond take
> longer than the usual since OVN will forward query and
> user will keep waiting for an answer until someone else
> replies for this query or timeout occur.
> 
> The above behavior is a bit problematic if the user knows
> that this domain is only configured in OVN we should not
> forward queries for this domain to the outside.
> 
> This patch adds an option:ovn-owned for the DNS table
> that can be set to "true" when creating a DNS.
> 
> When setting this option to "true" all the domains within
> this table will be treated as local domains only,
> and queries for domains within this table that don't have
> an accurate IP will be refused immediately to save the time
> of waiting for timeout.
> 
> Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=1946662
> Signed-off-by: Mohammad Heib <mheib@redhat.com>
> ---

Hi Mohammad,

Thanks for the patch.  It looks OK overall, I have a few remark inline.

>  NEWS                 |  5 ++++
>  controller/pinctrl.c | 47 ++++++++++++++++++++++++++++++--
>  northd/northd.c      | 11 ++++++++
>  ovn-nb.ovsschema     |  9 ++++--
>  ovn-nb.xml           | 14 ++++++++++
>  ovn-sb.ovsschema     |  9 ++++--
>  ovn-sb.xml           |  6 ++++
>  tests/ovn.at         | 65 ++++++++++++++++++++++++++++++++++++++++++++
>  8 files changed, 160 insertions(+), 6 deletions(-)
> 
> diff --git a/NEWS b/NEWS
> index 425dfe0a8..465649430 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -1,5 +1,10 @@
>  Post v23.09.0
>  -------------
> +  - DNS now have an "options" column for configuration of extra options.
> +  - A new DNS option "ovn-owned" has been added to allow defining domains
> +    that are owned only by ovn, queries for that domain will not be processed
> +    externally.
> +
>  
>  OVN v23.09.0 - 15 Sep 2023
>  --------------------------
> diff --git a/controller/pinctrl.c b/controller/pinctrl.c
> index ff5a3444c..e1e558595 100644
> --- a/controller/pinctrl.c
> +++ b/controller/pinctrl.c
> @@ -179,6 +179,7 @@ struct pinctrl {
>      struct latch pinctrl_thread_exit;
>      bool mac_binding_can_timestamp;
>      bool fdb_can_timestamp;
> +    bool dns_can_ovn_owned;

I think I'd call this "dns_supports_ovn_owned".

>  };
>  
>  static struct pinctrl pinctrl;
> @@ -2709,6 +2710,7 @@ struct dns_data {
>      uint64_t *dps;
>      size_t n_dps;
>      struct smap records;
> +    struct smap options;
>      bool delete;
>  };
>  
> @@ -2737,6 +2739,7 @@ sync_dns_cache(const struct sbrec_dns_table *dns_table)
>          if (!dns_data) {
>              dns_data = xmalloc(sizeof *dns_data);
>              smap_init(&dns_data->records);
> +            smap_init(&dns_data->options);
>              shash_add(&dns_cache, dns_id, dns_data);
>              dns_data->n_dps = 0;
>              dns_data->dps = NULL;
> @@ -2751,6 +2754,12 @@ sync_dns_cache(const struct sbrec_dns_table *dns_table)
>              smap_clone(&dns_data->records, &sbrec_dns->records);
>          }
>  
> +        if (pinctrl.dns_can_ovn_owned
> +            && !smap_equal(&dns_data->options, &sbrec_dns->options)) {
> +            smap_destroy(&dns_data->options);
> +            smap_clone(&dns_data->options, &sbrec_dns->options);
> +        }
> +
>          dns_data->n_dps = sbrec_dns->n_datapaths;
>          dns_data->dps = xcalloc(dns_data->n_dps, sizeof(uint64_t));
>          for (size_t i = 0; i < sbrec_dns->n_datapaths; i++) {
> @@ -2763,6 +2772,7 @@ sync_dns_cache(const struct sbrec_dns_table *dns_table)
>          if (d->delete) {
>              shash_delete(&dns_cache, iter);
>              smap_destroy(&d->records);
> +            smap_destroy(&d->options);
>              free(d->dps);
>              free(d);
>          }
> @@ -2777,6 +2787,7 @@ destroy_dns_cache(void)
>          struct dns_data *d = iter->data;
>          shash_delete(&dns_cache, iter);
>          smap_destroy(&d->records);
> +        smap_destroy(&d->options);
>          free(d->dps);
>          free(d);
>      }
> @@ -2850,6 +2861,8 @@ dns_build_ptr_answer(
>      free(encoded);
>  }
>  
> +#define DNS_RCODE_SERVER_REFUSE 0x5
> +
>  /* Called with in the pinctrl_handler thread context. */
>  static void
>  pinctrl_handle_dns_lookup(
> @@ -2863,6 +2876,7 @@ pinctrl_handle_dns_lookup(
>      enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
>      struct dp_packet *pkt_out_ptr = NULL;
>      uint32_t success = 0;
> +    bool send_query_rejection = false;

Nit: I think I'd rename this to 'send_refuse'.
Nit: reverse xmas tree.

>  
>      /* Parse result field. */
>      const struct mf_field *f;
> @@ -2962,6 +2976,7 @@ pinctrl_handle_dns_lookup(
>  
>      uint64_t dp_key = ntohll(pin->flow_metadata.flow.metadata);
>      const char *answer_data = NULL;
> +    bool ovn_owned = false;
>      struct shash_node *iter;
>      SHASH_FOR_EACH (iter, &dns_cache) {
>          struct dns_data *d = iter->data;
> @@ -2974,6 +2989,7 @@ pinctrl_handle_dns_lookup(
>                  answer_data = smap_get(&d->records, query_name_lower);
>                  free(query_name_lower);
>                  if (answer_data) {
> +                    ovn_owned = smap_get_bool(&d->options, "ovn-owned", false);
>                      break;
>                  }
>              }
> @@ -3020,10 +3036,22 @@ pinctrl_handle_dns_lookup(
>                  ancount++;
>              }
>          }
> +
> +        /* DNS is configured with a record for this domain with
> +         * an IPv4/IPV6 only, so instead of ignoring this A/AAAA query,
> +         * we can reply with  RCODE = 5 (server refuses) and that
> +         * will speed up the DNS process by not letting the customer
> +         * wait for a timeout.
> +         */
> +        if (ovn_owned && (query_type == DNS_QUERY_TYPE_AAAA ||
> +            query_type == DNS_QUERY_TYPE_A) && !ancount) {
> +            send_query_rejection = true;
> +        }
> +
>          destroy_lport_addresses(&ip_addrs);
>      }
>  
> -    if (!ancount) {
> +    if (!ancount && !send_query_rejection) {
>          ofpbuf_uninit(&dns_answer);
>          goto exit;
>      }
> @@ -3058,13 +3086,19 @@ pinctrl_handle_dns_lookup(
>  
>      /* Set the answer RRs. */
>      out_dns_header->ancount = htons(ancount);
> +    if (send_query_rejection) {
> +        /* set RCODE = 5 (server refuses). */
> +        out_dns_header->hi_flag |= DNS_RCODE_SERVER_REFUSE;
> +    }
>      out_dns_header->arcount = 0;
>  
>      /* Copy the Query section. */
>      dp_packet_put(&pkt_out, dp_packet_data(pkt_in), dp_packet_size(pkt_in));
>  
>      /* Copy the answer sections. */
> -    dp_packet_put(&pkt_out, dns_answer.data, dns_answer.size);
> +    if (!send_query_rejection) {

We don't really need the "if", right?

"dp_packet_put(&packet_out, foo, 0)" basically a no-op.

> +        dp_packet_put(&pkt_out, dns_answer.data, dns_answer.size);
> +    }
>      ofpbuf_uninit(&dns_answer);
>  
>      out_udp->udp_len = htons(new_l4_size);
> @@ -3521,6 +3555,15 @@ pinctrl_update(const struct ovsdb_idl *idl, const char *br_int_name)
>          notify_pinctrl_handler();
>      }
>  
> +    bool dns_can_ovn_owned = sbrec_server_has_dns_table_col_options(idl);
> +    if (dns_can_ovn_owned != pinctrl.dns_can_ovn_owned) {
> +        pinctrl.dns_can_ovn_owned = dns_can_ovn_owned;
> +
> +        /* Notify pinctrl_handler that fdb timestamp column
> +       * availability has changed. */
> +        notify_pinctrl_handler();
> +    }
> +
>      ovs_mutex_unlock(&pinctrl_mutex);
>  }
>  
> diff --git a/northd/northd.c b/northd/northd.c
> index 528027d07..70a2922f2 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -17196,6 +17196,17 @@ sync_dns_entries(struct ovsdb_idl_txn *ovnsb_txn,
>              free(dns_id);
>          }
>  
> +
> +        /* Copy DNS options to SB*/
> +        struct smap options;
> +        smap_clone(&options, &dns_info->sb_dns->options);
> +
> +        bool ovn_owned = smap_get_bool(&dns_info->nb_dns->options,
> +                                       "ovn-owned", false);
> +        smap_replace(&options, "ovn-owned",
> +                 ovn_owned? "true" : "false");
> +        sbrec_dns_set_options(dns_info->sb_dns, &options);
> +

We leak "options" here, CI caught this:

https://github.com/ovsrobot/ovn/actions/runs/6372957589/job/17296298390#step:8:5644

>          /* Set the datapaths and records. If nothing has changed, then
>           * this will be a no-op.
>           */
> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
> index e103360ec..b2e0993e0 100644
> --- a/ovn-nb.ovsschema
> +++ b/ovn-nb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Northbound",
> -    "version": "7.1.0",
> -    "cksum": "217362582 33949",
> +    "version": "7.2.0",
> +    "cksum": "1069338687 34162",
>      "tables": {
>          "NB_Global": {
>              "columns": {
> @@ -560,6 +560,11 @@
>                                       "value": "string",
>                                       "min": 0,
>                                       "max": "unlimited"}},
> +                "options": {
> +                    "type": {"key": "string",
> +                             "value": "string",
> +                             "min": 0,
> +                             "max": "unlimited"}},
>                  "external_ids": {"type": {"key": "string",
>                                   "value": "string",
>                                   "min": 0,
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index 1de0c3041..4aa8107a5 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -4542,6 +4542,20 @@ or
>        <p><b>Example: </b> "4.0.0.10.in-addr.arpa" = "vm1.ovn.org"</p>
>      </column>
>  
> +    <column name="options" key="ovn-owned">
> +      If set to true, then the OVN will be the main responsible for
> +      <code>DNS Records</code> within this Table.
> +
> +      <p> A <code>DNS Table</code> with this option set to <code>true</code>
> +          can be created for domains that the user needs to configure
> +          locally and don't care about IPv6 only interested in IPv4 or
> +          vice versa.
> +
> +           This will let ovn send IPv4 DNS reply and reject/ignore IPv6
> +           queries to save the waiting for a timeout on those uninteresting
> +           queries.</p>
> +    </column>
> +
>      <column name="external_ids">
>        See <em>External IDs</em> at the beginning of this document.
>      </column>
> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
> index 7b20aa21b..b5a16d231 100644
> --- a/ovn-sb.ovsschema
> +++ b/ovn-sb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Southbound",
> -    "version": "20.27.4",
> -    "cksum": "3194181501 30531",
> +    "version": "20.28.0",
> +    "cksum": "160983021 30744",
>      "tables": {
>          "SB_Global": {
>              "columns": {
> @@ -363,6 +363,11 @@
>                                                 "refTable": "Datapath_Binding"},
>                                         "min": 1,
>                                         "max": "unlimited"}},
> +                "options": {
> +                    "type": {"key": "string",
> +                             "value": "string",
> +                             "min": 0,
> +                             "max": "unlimited"}},
>                  "external_ids": {"type": {"key": "string",
>                                            "value": "string",
>                                            "min": 0,
> diff --git a/ovn-sb.xml b/ovn-sb.xml
> index 46aedf973..55fcf59b2 100644
> --- a/ovn-sb.xml
> +++ b/ovn-sb.xml
> @@ -4500,6 +4500,12 @@ tcp.flags = RST;
>        in this column.
>      </column>
>  
> +    <column name="options" key="ovn-owned">
> +      This column indicates that all the <code>Domains</code> in this table
> +      are owned by OVN, and all <code>DNS queries</code> for those domains
> +      will be answered locally by either an IP address or
> +      <code>DNS rejection</code>.
> +</column>
>      <group title="Common Columns">
>        <column name="external_ids">
>          See <em>External IDs</em> at the beginning of this document.
> diff --git a/tests/ovn.at b/tests/ovn.at
> index dfe535f36..88357fd33 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -11302,6 +11302,71 @@ reset_pcap_file hv1-vif2 hv1/vif2
>  rm -f 1.expected
>  rm -f 2.expected
>  
> +# send AAAA query for a server known domain that don't have
> +# any IPV6 address associated with this domain, and expected
> +# server refused DNS reply to save the sender time of waiting for timeout.
> +AS_BOX([Test IPv6 (AAAA records) NO timeout.])
> +# Add back the DNS options for ls1-lp1 without ipv6.
> +check ovn-nbctl remove DNS $DNS1 records vm1.ovn.org
> +check ovn-nbctl set DNS $DNS1 records:vm1.ovn.org="10.0.0.4"
> +check ovn-nbctl --wait=hv set DNS $DNS1 options:ovn-owned=true
> +ovn-sbctl list DNS > dns6
> +AT_CAPTURE_FILE([dns6])
> +ovn-sbctl dump-flows > sbflows6
> +AT_CAPTURE_FILE([sbflows6])
> +
> +set_dns_params vm1_ipv6_only
> +src_ip=`ip_to_hex 10 0 0 6`
> +dst_ip=`ip_to_hex 10 0 0 1`
> +dns_reply=1
> +test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data
> +
> +# NXT_RESUMEs should be 13.
> +OVS_WAIT_UNTIL([test 13 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> +
> +$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
> +# dns hdr with server refuse RCODE
> +echo "01028125"  > expout
> +#only check for the DNS HDR flags since we are not getting any DNS answer
> +AT_CHECK([cat 2.packets | cut -c -92 | cut -c 85-], [0], [expout])
> +
> +reset_pcap_file hv1-vif1 hv1/vif1
> +reset_pcap_file hv1-vif2 hv1/vif2
> +rm -f 1.expected
> +rm -f 2.expected
> +
> +# send A query for a server known domain that don't have
> +# any IPv4 address associated with this domain, and expected
> +# server refused DNS reply to save the sender time of waiting for timeout.
> +AS_BOX([Test IPv4 (A records) NO timeout.])
> +# Add back the DNS options for ls1-lp1 without ipv4.
> +check ovn-nbctl remove DNS $DNS1 records vm1.ovn.org
> +check ovn-nbctl set DNS $DNS1 records:vm1.ovn.org="aef0::4"
> +check ovn-nbctl --wait=hv set DNS $DNS1 options:ovn-owned=true
> +ovn-sbctl list DNS > dns7
> +AT_CAPTURE_FILE([dns7])
> +ovn-sbctl dump-flows > sbflows7
> +AT_CAPTURE_FILE([sbflows7])
> +
> +set_dns_params vm1
> +src_ip=`ip_to_hex 10 0 0 6`
> +dst_ip=`ip_to_hex 10 0 0 1`
> +dns_reply=1
> +test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data
> +
> +# NXT_RESUMEs should be 14.
> +OVS_WAIT_UNTIL([test 14 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> +
> +$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
> +# dns hdr with server refuse RCODE
> +echo "01028125"  > expout
> +#only check for the DNS HDR flags since we are not getting any DNS answer
> +AT_CHECK([cat 2.packets | cut -c -92 | cut -c 85-], [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

Regards,
Dumitru
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index 425dfe0a8..465649430 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,10 @@ 
 Post v23.09.0
 -------------
+  - DNS now have an "options" column for configuration of extra options.
+  - A new DNS option "ovn-owned" has been added to allow defining domains
+    that are owned only by ovn, queries for that domain will not be processed
+    externally.
+
 
 OVN v23.09.0 - 15 Sep 2023
 --------------------------
diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index ff5a3444c..e1e558595 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -179,6 +179,7 @@  struct pinctrl {
     struct latch pinctrl_thread_exit;
     bool mac_binding_can_timestamp;
     bool fdb_can_timestamp;
+    bool dns_can_ovn_owned;
 };
 
 static struct pinctrl pinctrl;
@@ -2709,6 +2710,7 @@  struct dns_data {
     uint64_t *dps;
     size_t n_dps;
     struct smap records;
+    struct smap options;
     bool delete;
 };
 
@@ -2737,6 +2739,7 @@  sync_dns_cache(const struct sbrec_dns_table *dns_table)
         if (!dns_data) {
             dns_data = xmalloc(sizeof *dns_data);
             smap_init(&dns_data->records);
+            smap_init(&dns_data->options);
             shash_add(&dns_cache, dns_id, dns_data);
             dns_data->n_dps = 0;
             dns_data->dps = NULL;
@@ -2751,6 +2754,12 @@  sync_dns_cache(const struct sbrec_dns_table *dns_table)
             smap_clone(&dns_data->records, &sbrec_dns->records);
         }
 
+        if (pinctrl.dns_can_ovn_owned
+            && !smap_equal(&dns_data->options, &sbrec_dns->options)) {
+            smap_destroy(&dns_data->options);
+            smap_clone(&dns_data->options, &sbrec_dns->options);
+        }
+
         dns_data->n_dps = sbrec_dns->n_datapaths;
         dns_data->dps = xcalloc(dns_data->n_dps, sizeof(uint64_t));
         for (size_t i = 0; i < sbrec_dns->n_datapaths; i++) {
@@ -2763,6 +2772,7 @@  sync_dns_cache(const struct sbrec_dns_table *dns_table)
         if (d->delete) {
             shash_delete(&dns_cache, iter);
             smap_destroy(&d->records);
+            smap_destroy(&d->options);
             free(d->dps);
             free(d);
         }
@@ -2777,6 +2787,7 @@  destroy_dns_cache(void)
         struct dns_data *d = iter->data;
         shash_delete(&dns_cache, iter);
         smap_destroy(&d->records);
+        smap_destroy(&d->options);
         free(d->dps);
         free(d);
     }
@@ -2850,6 +2861,8 @@  dns_build_ptr_answer(
     free(encoded);
 }
 
+#define DNS_RCODE_SERVER_REFUSE 0x5
+
 /* Called with in the pinctrl_handler thread context. */
 static void
 pinctrl_handle_dns_lookup(
@@ -2863,6 +2876,7 @@  pinctrl_handle_dns_lookup(
     enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
     struct dp_packet *pkt_out_ptr = NULL;
     uint32_t success = 0;
+    bool send_query_rejection = false;
 
     /* Parse result field. */
     const struct mf_field *f;
@@ -2962,6 +2976,7 @@  pinctrl_handle_dns_lookup(
 
     uint64_t dp_key = ntohll(pin->flow_metadata.flow.metadata);
     const char *answer_data = NULL;
+    bool ovn_owned = false;
     struct shash_node *iter;
     SHASH_FOR_EACH (iter, &dns_cache) {
         struct dns_data *d = iter->data;
@@ -2974,6 +2989,7 @@  pinctrl_handle_dns_lookup(
                 answer_data = smap_get(&d->records, query_name_lower);
                 free(query_name_lower);
                 if (answer_data) {
+                    ovn_owned = smap_get_bool(&d->options, "ovn-owned", false);
                     break;
                 }
             }
@@ -3020,10 +3036,22 @@  pinctrl_handle_dns_lookup(
                 ancount++;
             }
         }
+
+        /* DNS is configured with a record for this domain with
+         * an IPv4/IPV6 only, so instead of ignoring this A/AAAA query,
+         * we can reply with  RCODE = 5 (server refuses) and that
+         * will speed up the DNS process by not letting the customer
+         * wait for a timeout.
+         */
+        if (ovn_owned && (query_type == DNS_QUERY_TYPE_AAAA ||
+            query_type == DNS_QUERY_TYPE_A) && !ancount) {
+            send_query_rejection = true;
+        }
+
         destroy_lport_addresses(&ip_addrs);
     }
 
-    if (!ancount) {
+    if (!ancount && !send_query_rejection) {
         ofpbuf_uninit(&dns_answer);
         goto exit;
     }
@@ -3058,13 +3086,19 @@  pinctrl_handle_dns_lookup(
 
     /* Set the answer RRs. */
     out_dns_header->ancount = htons(ancount);
+    if (send_query_rejection) {
+        /* set RCODE = 5 (server refuses). */
+        out_dns_header->hi_flag |= DNS_RCODE_SERVER_REFUSE;
+    }
     out_dns_header->arcount = 0;
 
     /* Copy the Query section. */
     dp_packet_put(&pkt_out, dp_packet_data(pkt_in), dp_packet_size(pkt_in));
 
     /* Copy the answer sections. */
-    dp_packet_put(&pkt_out, dns_answer.data, dns_answer.size);
+    if (!send_query_rejection) {
+        dp_packet_put(&pkt_out, dns_answer.data, dns_answer.size);
+    }
     ofpbuf_uninit(&dns_answer);
 
     out_udp->udp_len = htons(new_l4_size);
@@ -3521,6 +3555,15 @@  pinctrl_update(const struct ovsdb_idl *idl, const char *br_int_name)
         notify_pinctrl_handler();
     }
 
+    bool dns_can_ovn_owned = sbrec_server_has_dns_table_col_options(idl);
+    if (dns_can_ovn_owned != pinctrl.dns_can_ovn_owned) {
+        pinctrl.dns_can_ovn_owned = dns_can_ovn_owned;
+
+        /* Notify pinctrl_handler that fdb timestamp column
+       * availability has changed. */
+        notify_pinctrl_handler();
+    }
+
     ovs_mutex_unlock(&pinctrl_mutex);
 }
 
diff --git a/northd/northd.c b/northd/northd.c
index 528027d07..70a2922f2 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -17196,6 +17196,17 @@  sync_dns_entries(struct ovsdb_idl_txn *ovnsb_txn,
             free(dns_id);
         }
 
+
+        /* Copy DNS options to SB*/
+        struct smap options;
+        smap_clone(&options, &dns_info->sb_dns->options);
+
+        bool ovn_owned = smap_get_bool(&dns_info->nb_dns->options,
+                                       "ovn-owned", false);
+        smap_replace(&options, "ovn-owned",
+                 ovn_owned? "true" : "false");
+        sbrec_dns_set_options(dns_info->sb_dns, &options);
+
         /* Set the datapaths and records. If nothing has changed, then
          * this will be a no-op.
          */
diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
index e103360ec..b2e0993e0 100644
--- a/ovn-nb.ovsschema
+++ b/ovn-nb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Northbound",
-    "version": "7.1.0",
-    "cksum": "217362582 33949",
+    "version": "7.2.0",
+    "cksum": "1069338687 34162",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -560,6 +560,11 @@ 
                                      "value": "string",
                                      "min": 0,
                                      "max": "unlimited"}},
+                "options": {
+                    "type": {"key": "string",
+                             "value": "string",
+                             "min": 0,
+                             "max": "unlimited"}},
                 "external_ids": {"type": {"key": "string",
                                  "value": "string",
                                  "min": 0,
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 1de0c3041..4aa8107a5 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -4542,6 +4542,20 @@  or
       <p><b>Example: </b> "4.0.0.10.in-addr.arpa" = "vm1.ovn.org"</p>
     </column>
 
+    <column name="options" key="ovn-owned">
+      If set to true, then the OVN will be the main responsible for
+      <code>DNS Records</code> within this Table.
+
+      <p> A <code>DNS Table</code> with this option set to <code>true</code>
+          can be created for domains that the user needs to configure
+          locally and don't care about IPv6 only interested in IPv4 or
+          vice versa.
+
+           This will let ovn send IPv4 DNS reply and reject/ignore IPv6
+           queries to save the waiting for a timeout on those uninteresting
+           queries.</p>
+    </column>
+
     <column name="external_ids">
       See <em>External IDs</em> at the beginning of this document.
     </column>
diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
index 7b20aa21b..b5a16d231 100644
--- a/ovn-sb.ovsschema
+++ b/ovn-sb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Southbound",
-    "version": "20.27.4",
-    "cksum": "3194181501 30531",
+    "version": "20.28.0",
+    "cksum": "160983021 30744",
     "tables": {
         "SB_Global": {
             "columns": {
@@ -363,6 +363,11 @@ 
                                                "refTable": "Datapath_Binding"},
                                        "min": 1,
                                        "max": "unlimited"}},
+                "options": {
+                    "type": {"key": "string",
+                             "value": "string",
+                             "min": 0,
+                             "max": "unlimited"}},
                 "external_ids": {"type": {"key": "string",
                                           "value": "string",
                                           "min": 0,
diff --git a/ovn-sb.xml b/ovn-sb.xml
index 46aedf973..55fcf59b2 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -4500,6 +4500,12 @@  tcp.flags = RST;
       in this column.
     </column>
 
+    <column name="options" key="ovn-owned">
+      This column indicates that all the <code>Domains</code> in this table
+      are owned by OVN, and all <code>DNS queries</code> for those domains
+      will be answered locally by either an IP address or
+      <code>DNS rejection</code>.
+</column>
     <group title="Common Columns">
       <column name="external_ids">
         See <em>External IDs</em> at the beginning of this document.
diff --git a/tests/ovn.at b/tests/ovn.at
index dfe535f36..88357fd33 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -11302,6 +11302,71 @@  reset_pcap_file hv1-vif2 hv1/vif2
 rm -f 1.expected
 rm -f 2.expected
 
+# send AAAA query for a server known domain that don't have
+# any IPV6 address associated with this domain, and expected
+# server refused DNS reply to save the sender time of waiting for timeout.
+AS_BOX([Test IPv6 (AAAA records) NO timeout.])
+# Add back the DNS options for ls1-lp1 without ipv6.
+check ovn-nbctl remove DNS $DNS1 records vm1.ovn.org
+check ovn-nbctl set DNS $DNS1 records:vm1.ovn.org="10.0.0.4"
+check ovn-nbctl --wait=hv set DNS $DNS1 options:ovn-owned=true
+ovn-sbctl list DNS > dns6
+AT_CAPTURE_FILE([dns6])
+ovn-sbctl dump-flows > sbflows6
+AT_CAPTURE_FILE([sbflows6])
+
+set_dns_params vm1_ipv6_only
+src_ip=`ip_to_hex 10 0 0 6`
+dst_ip=`ip_to_hex 10 0 0 1`
+dns_reply=1
+test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data
+
+# NXT_RESUMEs should be 13.
+OVS_WAIT_UNTIL([test 13 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
+# dns hdr with server refuse RCODE
+echo "01028125"  > expout
+#only check for the DNS HDR flags since we are not getting any DNS answer
+AT_CHECK([cat 2.packets | cut -c -92 | cut -c 85-], [0], [expout])
+
+reset_pcap_file hv1-vif1 hv1/vif1
+reset_pcap_file hv1-vif2 hv1/vif2
+rm -f 1.expected
+rm -f 2.expected
+
+# send A query for a server known domain that don't have
+# any IPv4 address associated with this domain, and expected
+# server refused DNS reply to save the sender time of waiting for timeout.
+AS_BOX([Test IPv4 (A records) NO timeout.])
+# Add back the DNS options for ls1-lp1 without ipv4.
+check ovn-nbctl remove DNS $DNS1 records vm1.ovn.org
+check ovn-nbctl set DNS $DNS1 records:vm1.ovn.org="aef0::4"
+check ovn-nbctl --wait=hv set DNS $DNS1 options:ovn-owned=true
+ovn-sbctl list DNS > dns7
+AT_CAPTURE_FILE([dns7])
+ovn-sbctl dump-flows > sbflows7
+AT_CAPTURE_FILE([sbflows7])
+
+set_dns_params vm1
+src_ip=`ip_to_hex 10 0 0 6`
+dst_ip=`ip_to_hex 10 0 0 1`
+dns_reply=1
+test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data
+
+# NXT_RESUMEs should be 14.
+OVS_WAIT_UNTIL([test 14 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
+# dns hdr with server refuse RCODE
+echo "01028125"  > expout
+#only check for the DNS HDR flags since we are not getting any DNS answer
+AT_CHECK([cat 2.packets | cut -c -92 | cut -c 85-], [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