[ovs-dev,v5,3/3] ovn-northd: Add logical flows to support native DNS

Submitted by Numan Siddique on April 17, 2017, 3:13 p.m.

Details

Message ID 20170417151321.1467-1-nusiddiq@redhat.com
State New
Delegated to: Guru Shetty
Headers show

Commit Message

Numan Siddique April 17, 2017, 3:13 p.m.
From: Numan Siddique <nusiddiq@redhat.com>

OVN implements native DNS resolution which can be used to resolve the
internal DNS names belonging to a logical datapath.

To support this, a new table 'DNS' is added in the NB DB. A new column
'dns_records' is added in 'Logical_Switch' table which references to the
'DNS' table.

Following flows are added for each logical switch if configured with
DNS records in the 'dns_records' column
 - A logical flow in DNS_LOOKUP stage which uses the action 'dns_lookup'
   to transform the DNS query to DNS reply packet and advances
   to the next stage - DNS_RESPONSE.

 - A logical flow in DNS_RESPONSE stage which implements the DNS responder
   by sending the DNS reply from previous stage back to the inport.

Signed-off-by: Numan Siddique <nusiddiq@redhat.com>
---
 ovn/northd/ovn-northd.8.xml |  85 +++++++++-
 ovn/northd/ovn-northd.c     | 183 ++++++++++++++++++++-
 ovn/ovn-nb.ovsschema        |  20 ++-
 ovn/ovn-nb.xml              |  27 +++-
 ovn/utilities/ovn-nbctl.c   |   3 +
 tests/ovn.at                | 377 ++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 682 insertions(+), 13 deletions(-)

Comments

Guru Shetty April 21, 2017, 10:23 p.m.
On 17 April 2017 at 08:13, <nusiddiq@redhat.com> wrote:

> From: Numan Siddique <nusiddiq@redhat.com>
>
> OVN implements native DNS resolution which can be used to resolve the
> internal DNS names belonging to a logical datapath.
>
> To support this, a new table 'DNS' is added in the NB DB. A new column
> 'dns_records' is added in 'Logical_Switch' table which references to the
> 'DNS' table.
>
> Following flows are added for each logical switch if configured with
> DNS records in the 'dns_records' column
>  - A logical flow in DNS_LOOKUP stage which uses the action 'dns_lookup'
>    to transform the DNS query to DNS reply packet and advances
>    to the next stage - DNS_RESPONSE.
>
>  - A logical flow in DNS_RESPONSE stage which implements the DNS responder
>    by sending the DNS reply from previous stage back to the inport.
>
> Signed-off-by: Numan Siddique <nusiddiq@redhat.com>
>

Acked-by: Gurucharan Shetty <guru@ovn.org>

A few comments below.


> ---
>  ovn/northd/ovn-northd.8.xml |  85 +++++++++-
>  ovn/northd/ovn-northd.c     | 183 ++++++++++++++++++++-
>  ovn/ovn-nb.ovsschema        |  20 ++-
>  ovn/ovn-nb.xml              |  27 +++-
>  ovn/utilities/ovn-nbctl.c   |   3 +
>  tests/ovn.at                | 377 ++++++++++++++++++++++++++++++
> ++++++++++++++
>  6 files changed, 682 insertions(+), 13 deletions(-)
>
> diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
> index ab8fd88..b7e2325 100644
> --- a/ovn/northd/ovn-northd.8.xml
> +++ b/ovn/northd/ovn-northd.8.xml
> @@ -724,7 +724,71 @@ output;
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 13 Destination Lookup</h3>
> +    <h3>Ingress Table 13 DNS Lookup</h3>
> +
> +    <p>
> +      This table looks up and resolves the DNS names of the logical ports
> +      if configured with the host names.
>

I think the above should say something like:
This table looks up and resolves the DNS names to the corresponding
configured IP address.



> +    </p>
> +
> +    <ul>
> +      <li>
> +        <p>
> +          A priority-100 logical flow for each logical switch datapath
> +          if it is configured with DNS records, which matches the IPv4
> and IPv6
> +          packets with <code>udp.dst</code> = 53 and applies the action
> +          <code>dns_lookup</code> and advances the packet to the next
> table.
> +        </p>
> +
> +        <pre>
> +reg0[4] = dns_lookup(); next;
> +        </pre>
> +
> +        <p>
> +          For valid DNS packets, this transforms the packet into a DNS
> +          reply if the DNS name can be resolved, and stores 1 into
> reg0[4].
> +          For failed DNS resolution or other kinds of packets, it just
> stores
> +          0 into reg0[4]. Either way, it continues to the next table.
> +        </p>
> +      </li>
> +    </ul>
> +
> +    <h3>Ingress Table 14 DNS Responses</h3>
> +
> +    <p>
> +      This table implements DNS responder for the DNS replies generated by
> +      the previous table.
> +    </p>
> +
> +    <ul>
> +      <li>
> +        <p>
> +          A priority-100 logical flow for each logical switch datapath
> +          if it is configured with DNS records, which matches the IPv4
> and IPv6
> +          packets with <code>udp.dst = 53 &amp;&amp; reg0[4] == 1</code>
> +          and responds back to the <code>inport</code> after applying
> these
> +          actions.  If <code>reg0[4]</code> is set to 1, it means that the
> +          action <code>dns_lookup</code> was successful.
> +        </p>
> +
> +        <pre>
> +eth.dst &lt;-&gt; eth.src;
> +ip4.src &lt;-&gt; ip4.dst;
> +udp.dst = udp.src;
> +udp.src = 53;
> +outport = <var>P</var>;
> +flags.loopback = 1;
> +output;
> +        </pre>
> +
> +        <p>
> +          (This terminates ingress packet processing; the packet does not
> go
> +           to the next ingress table.)
> +        </p>
> +      </li>
> +    </ul>
> +
> +    <h3>Ingress Table 15 Destination Lookup</h3>
>
>      <p>
>        This table implements switching behavior.  It contains these logical
> @@ -834,11 +898,22 @@ output;
>      </p>
>
>      <p>
> -      Also a priority 34000 logical flow is added for each logical port
> which
> -      has DHCPv4 options defined to allow the DHCPv4 reply packet and
> which has
> -      DHCPv6 options defined to allow the DHCPv6 reply packet from the
> -      <code>Ingress Table 12: DHCP responses</code>.
> +      Also the following flows are added.
>      </p>
> +    <ul>
> +      <li>
> +        A priority 34000 logical flow is added for each logical port which
> +        has DHCPv4 options defined to allow the DHCPv4 reply packet and
> which has
> +        DHCPv6 options defined to allow the DHCPv6 reply packet from the
> +        <code>Ingress Table 12: DHCP responses</code>.
> +      </li>
> +
> +      <li>
> +        A priority 34000 logical flow is added for each logical switch
> datapath
> +        if it is configured with DNS records, which allows the DNS reply
> packet
> +        from the <code>Ingress Table 14:DNS responses</code>.
>
Usually we mention the match and actions that are added, right?


> +      </li>
> +    </ul>
>
>      <h3>Egress Table 7: Egress Port Security - IP</h3>
>
> diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
> index 027e5a1..226d05c 100644
> --- a/ovn/northd/ovn-northd.c
> +++ b/ovn/northd/ovn-northd.c
> @@ -112,7 +112,9 @@ enum ovn_stage {
>      PIPELINE_STAGE(SWITCH, IN,  ARP_ND_RSP,    10, "ls_in_arp_rsp")
>  \
>      PIPELINE_STAGE(SWITCH, IN,  DHCP_OPTIONS,  11, "ls_in_dhcp_options")
> \
>      PIPELINE_STAGE(SWITCH, IN,  DHCP_RESPONSE, 12, "ls_in_dhcp_response")
> \
> -    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       13, "ls_in_l2_lkup")
>  \
> +    PIPELINE_STAGE(SWITCH, IN,  DNS_LOOKUP,      13, "ls_in_dns_lookup") \
> +    PIPELINE_STAGE(SWITCH, IN,  DNS_RESPONSE,  14, "ls_in_dns_response") \
> +    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       15, "ls_in_l2_lkup")
>  \
>                                                                        \
>      /* Logical switch egress stages. */                               \
>      PIPELINE_STAGE(SWITCH, OUT, PRE_LB,       0, "ls_out_pre_lb")     \
> @@ -160,6 +162,7 @@ enum ovn_stage {
>  #define REGBIT_CONNTRACK_COMMIT "reg0[1]"
>  #define REGBIT_CONNTRACK_NAT    "reg0[2]"
>  #define REGBIT_DHCP_OPTS_RESULT "reg0[3]"
> +#define REGBIT_DNS_LOOKUP_RESULT "reg0[4]"
>
>  /* Register definitions for switches and routers. */
>  #define REGBIT_NAT_REDIRECT     "reg9[0]"
> @@ -2658,6 +2661,22 @@ ip_address_and_port_from_lb_key(const char *key,
> char **ip_address,
>      free(start);
>  }
>
> +/*
> + * Returns true if logical switch is configured with DNS records, false
> + * otherwise.
> + */
> +static bool
> +ls_has_dns_records(const struct nbrec_logical_switch *nbs)
> +{
> +    for (size_t i = 0; i < nbs->n_dns_records; i++) {
> +        if (!smap_is_empty(&nbs->dns_records[i]->records)) {
> +            return true;
> +        }
> +    }
> +
> +    return false;
> +}
> +
>  static void
>  build_pre_lb(struct ovn_datapath *od, struct hmap *lflows)
>  {
> @@ -2942,7 +2961,8 @@ build_acls(struct ovn_datapath *od, struct hmap
> *lflows)
>      }
>
>      /* Add 34000 priority flow to allow DHCP reply from ovn-controller to
> all
> -     * logical ports of the datapath if the CMS has configured DHCPv4
> options*/
> +     * logical ports of the datapath if the CMS has configured DHCPv4
> options.
> +     * */
>      for (size_t i = 0; i < od->nbs->n_ports; i++) {
>          if (od->nbs->ports[i]->dhcpv4_options) {
>              const char *server_id = smap_get(
> @@ -2993,6 +3013,16 @@ build_acls(struct ovn_datapath *od, struct hmap
> *lflows)
>              }
>          }
>      }
> +
> +    /* Add one 34000 priority flow to allow DNS reply from ovn-controller
> to
> +     * if the CMS has configured DNS records for the datapath.
>
The comment above looks to be incomplete.



> +     */
> +    if (ls_has_dns_records(od->nbs)) {
> +        const char *actions = has_stateful ? "ct_commit; next;" : "next;";
> +        ovn_lflow_add(
> +            lflows, od, S_SWITCH_OUT_ACL, 34000, "udp.src == 53",
> +            actions);
> +    }

  }


>  static void
> @@ -3430,8 +3460,43 @@ build_lswitch_flows(struct hmap *datapaths, struct
> hmap *ports,
>          }
>      }
>
> +    /* Logical switch ingress table 13 and 14: DNS lookup and response
> +     * priority 100 flows.*/
>
A space after the comma above.


> +    HMAP_FOR_EACH (od, key_node, datapaths) {
> +        if (!od->nbs || !ls_has_dns_records(od->nbs)) {
> +           continue;
> +        }
> +
> +        struct ds match;
> +        struct ds action;
> +        ds_init(&match);
> +        ds_init(&action);
> +        ds_put_cstr(&match, "udp.dst == 53");
> +        ds_put_format(&action,
> +                      REGBIT_DNS_LOOKUP_RESULT" = dns_lookup(); next;");
> +        ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 100,
> +                      ds_cstr(&match), ds_cstr(&action));
> +        ds_clear(&action);
> +        ds_put_cstr(&match, " && "REGBIT_DNS_LOOKUP_RESULT);
> +        ds_put_format(&action, "eth.dst <-> eth.src; ip4.src <-> ip4.dst;
> "
> +                      "udp.dst = udp.src; udp.src = 53; outport = inport;
> "
> +                      "flags.loopback = 1; output;");
> +        ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100,
> +                      ds_cstr(&match), ds_cstr(&action));
> +        ds_clear(&action);
> +        ds_put_format(&action, "eth.dst <-> eth.src; ip6.src <-> ip6.dst;
> "
> +                      "udp.dst = udp.src; udp.src = 53; outport = inport;
> "
> +                      "flags.loopback = 1; output;");
> +        ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100,
> +                      ds_cstr(&match), ds_cstr(&action));
> +        ds_destroy(&match);
> +        ds_destroy(&action);
> +    }
> +
>      /* Ingress table 11 and 12: DHCP options and response, by default
> goto next.
> -     * (priority 0). */
> +     * (priority 0).
> +     * Ingress table 13 and 14: DNS lookup and response, by default goto
> next.
> +     * (priority 0).*/
>
>      HMAP_FOR_EACH (od, key_node, datapaths) {
>          if (!od->nbs) {
> @@ -3440,9 +3505,11 @@ build_lswitch_flows(struct hmap *datapaths, struct
> hmap *ports,
>
>          ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_OPTIONS, 0, "1",
> "next;");
>          ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESPONSE, 0, "1",
> "next;");
> +        ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 0, "1",
> "next;");
> +        ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 0, "1",
> "next;");
>      }
>
> -    /* Ingress table 13: Destination lookup, broadcast and multicast
> handling
> +    /* Ingress table 15: Destination lookup, broadcast and multicast
> handling
>       * (priority 100). */
>      HMAP_FOR_EACH (op, key_node, ports) {
>          if (!op->nbsp) {
> @@ -5369,6 +5436,108 @@ sync_address_sets(struct northd_context *ctx)
>      }
>      shash_destroy(&sb_address_sets);
>  }
> +
> +/*
> + * struct 'dns_info' is used to sync the DNS records between OVN
> Northbound db
> + * and Southbound db.
> + */
> +struct dns_info {
> +    struct hmap_node hmap_node;
> +    const struct nbrec_dns *nb_dns; /* DNS record in the Northbound db. */
> +    const struct sbrec_dns *sb_dns; /* DNS record in the Soutbound db. */
> +
> +    /* Datapaths to which the DNS entry is associated with it. */
> +    const struct sbrec_datapath_binding **sbs;
> +    size_t n_sbs;
> +};
> +
> +static inline struct dns_info *
> +get_dns_info_from_hmap(struct hmap *dns_map, struct uuid *uuid)
> +{
> +    struct dns_info *dns_info;
> +    size_t hash = uuid_hash(uuid);
> +    HMAP_FOR_EACH_WITH_HASH (dns_info, hmap_node, hash, dns_map) {
> +        if (uuid_equals(&dns_info->nb_dns->header_.uuid, uuid)) {
> +            return dns_info;
> +        }
> +    }
> +
> +    return NULL;
> +}
> +
> +static void
> +sync_dns_entries(struct northd_context *ctx, struct hmap *datapaths)
> +{
> +    struct hmap dns_map = HMAP_INITIALIZER(&dns_map);
> +    struct ovn_datapath *od;
> +    HMAP_FOR_EACH (od, key_node, datapaths) {
> +        if (!od->nbs || !od->nbs->n_dns_records) {
> +            continue;
> +        }
> +
> +        for (size_t i = 0; i < od->nbs->n_dns_records; i++) {
> +            struct dns_info *dns_info = get_dns_info_from_hmap(
> +                &dns_map, &od->nbs->dns_records[i]->header_.uuid);
> +            if (!dns_info) {
> +                size_t hash = uuid_hash(
> +                    &od->nbs->dns_records[i]->header_.uuid);
> +                dns_info = xzalloc(sizeof *dns_info);;
> +                dns_info->nb_dns = od->nbs->dns_records[i];
> +                hmap_insert(&dns_map, &dns_info->hmap_node, hash);
> +            }
> +
> +            dns_info->n_sbs++;
> +            dns_info->sbs = xrealloc(dns_info->sbs,
> +                                     dns_info->n_sbs * sizeof
> *dns_info->sbs);
> +            dns_info->sbs[dns_info->n_sbs - 1] = od->sb;
> +        }
> +    }
> +
> +    const struct sbrec_dns *sbrec_dns, *next;
> +    SBREC_DNS_FOR_EACH_SAFE (sbrec_dns, next, ctx->ovnsb_idl) {
> +        const char *nb_dns_uuid = smap_get(&sbrec_dns->external_ids,
> "dns_id");
> +        struct uuid dns_uuid;
> +        if (!nb_dns_uuid || !uuid_from_string(&dns_uuid, nb_dns_uuid)) {
> +            sbrec_dns_delete(sbrec_dns);
> +            continue;
> +        }
> +
> +        struct dns_info *dns_info =
> +            get_dns_info_from_hmap(&dns_map, &dns_uuid);
> +        if (dns_info) {
> +            dns_info->sb_dns = sbrec_dns;
> +        } else {
> +            sbrec_dns_delete(sbrec_dns);
> +        }
> +    }
> +
> +    struct dns_info *dns_info;
> +    HMAP_FOR_EACH_POP (dns_info, hmap_node, &dns_map) {
> +        if (!dns_info->sb_dns) {
> +            struct sbrec_dns *sbrec_dns = sbrec_dns_insert(ctx->ovnsb_
> txn);
> +            dns_info->sb_dns = sbrec_dns;
> +            char *dns_id = xasprintf(
> +                UUID_FMT, UUID_ARGS(&dns_info->nb_dns->header_.uuid));
> +            const struct smap external_ids =
> +                SMAP_CONST1(&external_ids, "dns_id", dns_id);
> +            sbrec_dns_set_external_ids(sbrec_dns, &external_ids);
> +            free(dns_id);
> +        }
> +
> +        /* Set the datapaths and records. If nothing has changed, then
> +         * this will be a no-op.
> +         */
> +        sbrec_dns_set_datapaths(
> +            dns_info->sb_dns,
> +            (struct sbrec_datapath_binding **)dns_info->sbs,
> +            dns_info->n_sbs);
> +        sbrec_dns_set_records(dns_info->sb_dns,
> &dns_info->nb_dns->records);
> +        free(dns_info->sbs);
> +        free(dns_info);
> +    }
> +    hmap_destroy(&dns_map);
> +}
> +
>
>  static void
>  ovnnb_db_run(struct northd_context *ctx, struct ovsdb_idl_loop *sb_loop)
> @@ -5383,6 +5552,7 @@ ovnnb_db_run(struct northd_context *ctx, struct
> ovsdb_idl_loop *sb_loop)
>      build_lflows(ctx, &datapaths, &ports);
>
>      sync_address_sets(ctx);
> +    sync_dns_entries(ctx, &datapaths);
>
>      struct ovn_datapath *dp, *next_dp;
>      HMAP_FOR_EACH_SAFE (dp, next_dp, key_node, &datapaths) {
> @@ -5782,6 +5952,11 @@ main(int argc, char *argv[])
>      add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_name);
>      add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_
> addresses);
>
> +    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_dns);
> +    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dns_col_datapaths);
> +    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dns_col_records);
> +    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dns_col_external_ids);
> +
>      ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_chassis);
>      ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_chassis_col_nb_cfg);
>
> diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
> index dd0ac3d..4662653 100644
> --- a/ovn/ovn-nb.ovsschema
> +++ b/ovn/ovn-nb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Northbound",
> -    "version": "5.5.0",
> -    "cksum": "2099428463 14236",
> +    "version": "5.5.1",
> +    "cksum": "1743459612 15019",
>      "tables": {
>          "NB_Global": {
>              "columns": {
> @@ -45,6 +45,11 @@
>                                                    "refType": "strong"},
>                                             "min": 0,
>                                             "max": "unlimited"}},
> +                "dns_records": {"type": {"key": {"type": "uuid",
> +                                         "refTable": "DNS",
> +                                         "refType": "weak"},
> +                                  "min": 0,
> +                                  "max": "unlimited"}},
>                  "other_config": {
>                      "type": {"key": "string", "value": "string",
>                               "min": 0, "max": "unlimited"}},
> @@ -265,6 +270,17 @@
>                                      "max": "unlimited"},
>                                      "ephemeral": true}},
>              "indexes": [["target"]]},
> +        "DNS": {
> +            "columns": {
> +                "records": {"type": {"key": "string",
> +                                     "value": "string",
> +                                     "min": 0,
> +                                     "max": "unlimited"}},
> +                "external_ids": {"type": {"key": "string",
> +                                 "value": "string",
> +                                 "min": 0,
> +                                 "max": "unlimited"}}},
> +            "isRoot": true},
>          "SSL": {
>              "columns": {
>                  "private_key": {"type": "string"},
> diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
> index 2b416ce..11e32f0 100644
> --- a/ovn/ovn-nb.xml
> +++ b/ovn/ovn-nb.xml
> @@ -134,6 +134,12 @@
>        QOS marking rules that apply to packets within the logical switch.
>      </column>
>
> +    <column name="dns_records">
> +      This column defines the DNS records to be used for resolving
> internal
> +      DNS queries within the logical switch by the native DNS resolver.
> +      Please see the <ref table="DNS"/> table.
> +    </column>
> +
>      <group title="IP Address Assignment">
>        <p>
>          These options control automatic IP address management (IPAM) for
> ports
> @@ -1471,7 +1477,7 @@
>          <column name="options" key="lease_time"
>                  type='{"type": "integer", "minInteger": 0, "maxInteger":
> 4294967295}'>
>            <p>
> -            The offered lease time in seconds,
> +            The offered lease time in seconds,
>            </p>
>
>            <p>
> @@ -1963,6 +1969,24 @@
>        <column name="other_config"/>
>      </group>
>    </table>
> +  <table name="DNS" title="Native DNS resolution">
> +    <p>
> +      Each row in this table stores the DNS records. The
> +      <ref table="Logical_Switch"/> table's <ref table="Logical_Switch"
> +      column="dns_records"/> references these records.
> +    </p>
> +
> +    <column name="records">
> +      Key-value pair of DNS records with <code>hostname</code> as the key
> +      and value as a string of IP address(es) separated by comma or space.
> +
> +      <p><b>Example: </b> "vm1.ovn.org" = "10.0.0.4 aef0::4"</p>
> +    </column>
> +
> +    <column name="external_ids">
> +      See <em>External IDs</em> at the beginning of this document.
> +    </column>
> +  </table>
>    <table name="SSL">
>      SSL configuration for ovn-nb database access.
>
> @@ -2001,5 +2025,4 @@
>        <column name="external_ids"/>
>      </group>
>    </table>
> -
>  </database>
> diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
> index e9dcde7..3df8b81 100644
> --- a/ovn/utilities/ovn-nbctl.c
> +++ b/ovn/utilities/ovn-nbctl.c
> @@ -3062,6 +3062,9 @@ static const struct ctl_table_class
> tables[NBREC_N_TABLES] = {
>
>      [NBREC_TABLE_SSL].row_ids[0]
>      = {&nbrec_table_nb_global, NULL, &nbrec_nb_global_col_ssl},
> +
> +    [NBREC_TABLE_DNS].row_ids[0]
> +    = {&nbrec_table_dns, NULL, &nbrec_dns_col_records},
>  };
>
>  static void
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 53a2dd5..b9f8114 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -6435,6 +6435,383 @@ OVS_APP_EXIT_AND_WAIT([ovsdb-server])
>
>  AT_CLEANUP
>
> +AT_SETUP([ovn -- dns lookup : 1 HV, 2 LS, 2 LSPs/LS])
> +AT_SKIP_IF([test $HAVE_PYTHON = no])
> +ovn_start
> +
> +ovn-nbctl ls-add ls1
> +
> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4 aef0::4"
> +
> +ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4
> aef0::4"
> +
> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> +-- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02 10.0.0.6 20.0.0.4"
> +
> +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={}`
> +
> +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 Logical_switch ls1 dns_records="$DNS1"
> +
> +net_add n1
> +sim_add hv1
> +
> +as hv1
> +ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.1
> +ovs-vsctl -- add-port br-int hv1-vif1 -- \
> +    set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
> +    options:tx_pcap=hv1/vif1-tx.pcap \
> +    options:rxq_pcap=hv1/vif1-rx.pcap \
> +    ofport-request=1
> +
> +ovs-vsctl -- add-port br-int hv1-vif2 -- \
> +    set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \
> +    options:tx_pcap=hv1/vif2-tx.pcap \
> +    options:rxq_pcap=hv1/vif2-rx.pcap \
> +    ofport-request=2
> +
> +ovn_populate_arp
> +sleep 2
> +as hv1 ovs-vsctl show
> +
> +echo "*************************"
> +ovn-sbctl list DNS
> +echo "*************************"
> +
> +ip_to_hex() {
> +    printf "%02x%02x%02x%02x" "$@"
> +}
> +
> +reset_pcap_file() {
> +    local iface=$1
> +    local pcap_file=$2
> +    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
> +options:rxq_pcap=dummy-rx.pcap
> +    rm -f ${pcap_file}*.pcap
> +    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap
> \
> +options:rxq_pcap=${pcap_file}-rx.pcap
> +}
> +
> +# set_dns_params host_name
> +# Sets the dns_req_data and dns_resp_data
> +set_dns_params() {
> +    local hname=$1
> +    local ttl=00000e10
> +    an_count=0001
> +    type=0001
> +    case $hname in
> +    vm1)
> +        # vm1.ovn.org
> +        hostname=03766d31036f766e036f726700
> +        # IPv4 address - 10.0.0.4
> +        expected_dns_answer=${hostname}00010001${ttl}00040a000004
> +        ;;
> +    vm2)
> +        # vm2.ovn.org
> +        hostname=03766d32036f766e036f726700
> +        # IPv4 address - 10.0.0.6
> +        expected_dns_answer=${hostname}00010001${ttl}00040a000006
> +        # IPv4 address - 20.0.0.4
> +        expected_dns_answer=${expected_dns_answer}${
> hostname}00010001${ttl}000414000004
> +        an_count=0002
> +        ;;
> +    vm3)
> +        # vm3.ovn.org
> +        hostname=03766d33036f766e036f726700
> +        # IPv4 address - 40.0.0.4
> +        expected_dns_answer=${hostname}00010001${ttl}000428000004
> +        ;;
> +    vm1_ipv6_only)
> +        # vm1.ovn.org
> +        hostname=03766d31036f766e036f726700
> +        # IPv6 address - aef0::4
> +        type=001c
> +        expected_dns_answer=${hostname}${type}0001${ttl}
> 0010aef00000000000000000000000000004
> +        ;;
> +    vm1_ipv4_v6)
> +        # vm1.ovn.org
> +        hostname=03766d31036f766e036f726700
> +        type=00ff
> +        an_count=0002
> +        # IPv4 address - 10.0.0.4
> +        # IPv6 address - aef0::4
> +        expected_dns_answer=${hostname}00010001${ttl}00040a000004
> +        expected_dns_answer=${expected_dns_answer}${
> hostname}001c0001${ttl}0010
> +        expected_dns_answer=${expected_dns_answer}
> aef00000000000000000000000000004
> +        ;;
> +    vm1_invalid_type)
> +        # vm1.ovn.org
> +        hostname=03766d31036f766e036f726700
> +        # IPv6 address - aef0::4
> +        type=0002
> +        ;;
> +    vm1_incomplete)
> +        # set type to none
> +        type=''
> +    esac
> +    # TTL - 3600
> +    local dns_req_header=010201200001000000000000
> +    local dns_resp_header=010281200001${an_count}00000000
> +    dns_req_data=${dns_req_header}${hostname}${type}0001
> +    dns_resp_data=${dns_resp_header}${hostname}${type}0001$
> {expected_dns_answer}
> +}
> +
> +# This shell function sends a DNS request packet
> +# test_dns INPORT SRC_MAC DST_MAC SRC_IP DST_IP DNS_QUERY EXPEC
> +test_dns() {
> +    local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 dns_reply=$6
> +    local dns_query_data=$7
> +    shift; shift; shift; shift; shift; shift; shift;
> +    # Packet size => IPv4 header (20) + UDP header (8) +
> +    #                DNS data (header + query)
> +    ip_len=`expr 28 + ${#dns_query_data} / 2`
> +    udp_len=`expr $ip_len - 20`
> +    ip_len=$(printf "%x" $ip_len)
> +    udp_len=$(printf "%x" $udp_len)
> +    local request=${dst_mac}${src_mac}0800450000${ip_len}0000000080110000
> +    request=${request}${src_ip}${dst_ip}9234003500${udp_len}0000
> +    # dns data
> +    request=${request}${dns_query_data}
> +
> +    if test $dns_reply != 0; then
> +        local dns_reply=$1
> +        ip_len=`expr 28 + ${#dns_reply} / 2`
> +        udp_len=`expr $ip_len - 20`
> +        ip_len=$(printf "%x" $ip_len)
> +        udp_len=$(printf "%x" $udp_len)
> +        local reply=${src_mac}${dst_mac}0800450000${ip_len}
> 0000000080110000
> +        reply=${reply}${dst_ip}${src_ip}0035923400${udp_len}0000${
> dns_reply}
> +        echo $reply >> $inport.expected
> +    else
> +        for outport; do
> +            echo $request >> $outport.expected
> +        done
> +    fi
> +    as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $request
> +}
> +
> +AT_CAPTURE_FILE([ofctl_monitor0.log])
> +as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \
> +--pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log
> +
> +set_dns_params vm2
> +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 1.
> +OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> +
> +$PYTHON "$top_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
> +
> +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 2.
> +OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> +
> +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
> +cat 2.expected | cut -c -48 > expout
> +AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
> +# Skipping the IPv4 checksum.
> +cat 2.expected | cut -c 53- > expout
> +AT_CHECK([cat 2.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
> +
> +# Clear the hostname options for ls1-lp2
> +ovn-nbctl --wait=hv remove DNS $DNS1 records vm2.ovn.org
> +
> +set_dns_params vm2
> +src_ip=`ip_to_hex 10 0 0 4`
> +dst_ip=`ip_to_hex 10 0 0 1`
> +dns_reply=0
> +test_dns 1 f00000000001 f00000000002 $src_ip $dst_ip $dns_reply
> $dns_req_data
> +
> +# NXT_RESUMEs should be 3.
> +OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> +
> +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
> +AT_CHECK([cat 1.packets], [0], [])
> +
> +reset_pcap_file hv1-vif1 hv1/vif1
> +reset_pcap_file hv1-vif2 hv1/vif2
> +rm -f 1.expected
> +rm -f 2.expected
> +
> +# Clear the hostname for ls1-lp1
> +# Since ls1 has no hostnames configued,
> +# ovn-northd should not add the DNS flows.
> +ovn-nbctl --wait=hv remove DNS $DNS1 records vm1.ovn.org
> +
> +set_dns_params vm1
> +src_ip=`ip_to_hex 10 0 0 6`
> +dst_ip=`ip_to_hex 10 0 0 1`
> +dns_reply=0
> +test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply
> $dns_req_data
> +
> +# NXT_RESUMEs should be 3 only.
> +OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> +
> +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
> +AT_CHECK([cat 2.packets], [0], [])
> +
> +reset_pcap_file hv1-vif1 hv1/vif1
> +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 set DNS $DNS1 records:vm1.ovn.org="10.0.0.4 aef0::4"
> +
> +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 4.
> +OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> +
> +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
> +cat 2.expected | cut -c -48 > expout
> +AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
> +# Skipping the IPv4 checksum.
> +cat 2.expected | cut -c 53- > expout
> +AT_CHECK([cat 2.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 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`
> +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 5.
> +OVS_WAIT_UNTIL([test 5 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> +
> +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
> +cat 2.expected | cut -c -48 > expout
> +AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
> +# Skipping the IPv4 checksum.
> +cat 2.expected | cut -c 53- > expout
> +AT_CHECK([cat 2.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
> +
> +# Invalid type.
> +set_dns_params vm1_invalid_type
> +src_ip=`ip_to_hex 10 0 0 6`
> +dst_ip=`ip_to_hex 10 0 0 1`
> +dns_reply=0
> +test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply
> $dns_req_data
> +
> +# NXT_RESUMEs should be 6.
> +OVS_WAIT_UNTIL([test 6 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> +
> +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
> +AT_CHECK([cat 2.packets], [0], [])
> +
> +reset_pcap_file hv1-vif1 hv1/vif1
> +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`
> +dst_ip=`ip_to_hex 10 0 0 1`
> +dns_reply=0
> +test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply
> $dns_req_data
> +
> +# NXT_RESUMEs should be 7.
> +OVS_WAIT_UNTIL([test 7 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> +
> +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
> +AT_CHECK([cat 2.packets], [0], [])
> +
> +reset_pcap_file hv1-vif1 hv1/vif1
> +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"
> +
> +set_dns_params vm3
> +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 8.
> +OVS_WAIT_UNTIL([test 8 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> +
> +$PYTHON "$top_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
> +
> +as hv1
> + OVS_APP_EXIT_AND_WAIT([ovn-controller])
> +OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
> +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
> +
> +as ovn-sb
> +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
> +
> +as ovn-nb
> +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
> +
> +as northd
> +OVS_APP_EXIT_AND_WAIT([ovn-northd])
> +
> +as main
> +OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
> +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
> +AT_CLEANUP
> +
>  AT_SETUP([ovn -- 1 LR with distributed router gateway port])
>  AT_SKIP_IF([test $HAVE_PYTHON = no])
>  ovn_start
> --
> 2.9.3
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>

Patch hide | download patch | download mbox

diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
index ab8fd88..b7e2325 100644
--- a/ovn/northd/ovn-northd.8.xml
+++ b/ovn/northd/ovn-northd.8.xml
@@ -724,7 +724,71 @@  output;
       </li>
     </ul>
 
-    <h3>Ingress Table 13 Destination Lookup</h3>
+    <h3>Ingress Table 13 DNS Lookup</h3>
+
+    <p>
+      This table looks up and resolves the DNS names of the logical ports
+      if configured with the host names.
+    </p>
+
+    <ul>
+      <li>
+        <p>
+          A priority-100 logical flow for each logical switch datapath
+          if it is configured with DNS records, which matches the IPv4 and IPv6
+          packets with <code>udp.dst</code> = 53 and applies the action
+          <code>dns_lookup</code> and advances the packet to the next table.
+        </p>
+
+        <pre>
+reg0[4] = dns_lookup(); next;
+        </pre>
+
+        <p>
+          For valid DNS packets, this transforms the packet into a DNS
+          reply if the DNS name can be resolved, and stores 1 into reg0[4].
+          For failed DNS resolution or other kinds of packets, it just stores
+          0 into reg0[4]. Either way, it continues to the next table.
+        </p>
+      </li>
+    </ul>
+
+    <h3>Ingress Table 14 DNS Responses</h3>
+
+    <p>
+      This table implements DNS responder for the DNS replies generated by
+      the previous table.
+    </p>
+
+    <ul>
+      <li>
+        <p>
+          A priority-100 logical flow for each logical switch datapath
+          if it is configured with DNS records, which matches the IPv4 and IPv6
+          packets with <code>udp.dst = 53 &amp;&amp; reg0[4] == 1</code>
+          and responds back to the <code>inport</code> after applying these
+          actions.  If <code>reg0[4]</code> is set to 1, it means that the
+          action <code>dns_lookup</code> was successful.
+        </p>
+
+        <pre>
+eth.dst &lt;-&gt; eth.src;
+ip4.src &lt;-&gt; ip4.dst;
+udp.dst = udp.src;
+udp.src = 53;
+outport = <var>P</var>;
+flags.loopback = 1;
+output;
+        </pre>
+
+        <p>
+          (This terminates ingress packet processing; the packet does not go
+           to the next ingress table.)
+        </p>
+      </li>
+    </ul>
+
+    <h3>Ingress Table 15 Destination Lookup</h3>
 
     <p>
       This table implements switching behavior.  It contains these logical
@@ -834,11 +898,22 @@  output;
     </p>
 
     <p>
-      Also a priority 34000 logical flow is added for each logical port which
-      has DHCPv4 options defined to allow the DHCPv4 reply packet and which has
-      DHCPv6 options defined to allow the DHCPv6 reply packet from the
-      <code>Ingress Table 12: DHCP responses</code>.
+      Also the following flows are added.
     </p>
+    <ul>
+      <li>
+        A priority 34000 logical flow is added for each logical port which
+        has DHCPv4 options defined to allow the DHCPv4 reply packet and which has
+        DHCPv6 options defined to allow the DHCPv6 reply packet from the
+        <code>Ingress Table 12: DHCP responses</code>.
+      </li>
+
+      <li>
+        A priority 34000 logical flow is added for each logical switch datapath
+        if it is configured with DNS records, which allows the DNS reply packet
+        from the <code>Ingress Table 14:DNS responses</code>.
+      </li>
+    </ul>
 
     <h3>Egress Table 7: Egress Port Security - IP</h3>
 
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 027e5a1..226d05c 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -112,7 +112,9 @@  enum ovn_stage {
     PIPELINE_STAGE(SWITCH, IN,  ARP_ND_RSP,    10, "ls_in_arp_rsp")       \
     PIPELINE_STAGE(SWITCH, IN,  DHCP_OPTIONS,  11, "ls_in_dhcp_options")  \
     PIPELINE_STAGE(SWITCH, IN,  DHCP_RESPONSE, 12, "ls_in_dhcp_response") \
-    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       13, "ls_in_l2_lkup")       \
+    PIPELINE_STAGE(SWITCH, IN,  DNS_LOOKUP,      13, "ls_in_dns_lookup") \
+    PIPELINE_STAGE(SWITCH, IN,  DNS_RESPONSE,  14, "ls_in_dns_response") \
+    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       15, "ls_in_l2_lkup")       \
                                                                       \
     /* Logical switch egress stages. */                               \
     PIPELINE_STAGE(SWITCH, OUT, PRE_LB,       0, "ls_out_pre_lb")     \
@@ -160,6 +162,7 @@  enum ovn_stage {
 #define REGBIT_CONNTRACK_COMMIT "reg0[1]"
 #define REGBIT_CONNTRACK_NAT    "reg0[2]"
 #define REGBIT_DHCP_OPTS_RESULT "reg0[3]"
+#define REGBIT_DNS_LOOKUP_RESULT "reg0[4]"
 
 /* Register definitions for switches and routers. */
 #define REGBIT_NAT_REDIRECT     "reg9[0]"
@@ -2658,6 +2661,22 @@  ip_address_and_port_from_lb_key(const char *key, char **ip_address,
     free(start);
 }
 
+/*
+ * Returns true if logical switch is configured with DNS records, false
+ * otherwise.
+ */
+static bool
+ls_has_dns_records(const struct nbrec_logical_switch *nbs)
+{
+    for (size_t i = 0; i < nbs->n_dns_records; i++) {
+        if (!smap_is_empty(&nbs->dns_records[i]->records)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 static void
 build_pre_lb(struct ovn_datapath *od, struct hmap *lflows)
 {
@@ -2942,7 +2961,8 @@  build_acls(struct ovn_datapath *od, struct hmap *lflows)
     }
 
     /* Add 34000 priority flow to allow DHCP reply from ovn-controller to all
-     * logical ports of the datapath if the CMS has configured DHCPv4 options*/
+     * logical ports of the datapath if the CMS has configured DHCPv4 options.
+     * */
     for (size_t i = 0; i < od->nbs->n_ports; i++) {
         if (od->nbs->ports[i]->dhcpv4_options) {
             const char *server_id = smap_get(
@@ -2993,6 +3013,16 @@  build_acls(struct ovn_datapath *od, struct hmap *lflows)
             }
         }
     }
+
+    /* Add one 34000 priority flow to allow DNS reply from ovn-controller to
+     * if the CMS has configured DNS records for the datapath.
+     */
+    if (ls_has_dns_records(od->nbs)) {
+        const char *actions = has_stateful ? "ct_commit; next;" : "next;";
+        ovn_lflow_add(
+            lflows, od, S_SWITCH_OUT_ACL, 34000, "udp.src == 53",
+            actions);
+    }
 }
 
 static void
@@ -3430,8 +3460,43 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         }
     }
 
+    /* Logical switch ingress table 13 and 14: DNS lookup and response
+     * priority 100 flows.*/
+    HMAP_FOR_EACH (od, key_node, datapaths) {
+        if (!od->nbs || !ls_has_dns_records(od->nbs)) {
+           continue;
+        }
+
+        struct ds match;
+        struct ds action;
+        ds_init(&match);
+        ds_init(&action);
+        ds_put_cstr(&match, "udp.dst == 53");
+        ds_put_format(&action,
+                      REGBIT_DNS_LOOKUP_RESULT" = dns_lookup(); next;");
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 100,
+                      ds_cstr(&match), ds_cstr(&action));
+        ds_clear(&action);
+        ds_put_cstr(&match, " && "REGBIT_DNS_LOOKUP_RESULT);
+        ds_put_format(&action, "eth.dst <-> eth.src; ip4.src <-> ip4.dst; "
+                      "udp.dst = udp.src; udp.src = 53; outport = inport; "
+                      "flags.loopback = 1; output;");
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100,
+                      ds_cstr(&match), ds_cstr(&action));
+        ds_clear(&action);
+        ds_put_format(&action, "eth.dst <-> eth.src; ip6.src <-> ip6.dst; "
+                      "udp.dst = udp.src; udp.src = 53; outport = inport; "
+                      "flags.loopback = 1; output;");
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100,
+                      ds_cstr(&match), ds_cstr(&action));
+        ds_destroy(&match);
+        ds_destroy(&action);
+    }
+
     /* Ingress table 11 and 12: DHCP options and response, by default goto next.
-     * (priority 0). */
+     * (priority 0).
+     * Ingress table 13 and 14: DNS lookup and response, by default goto next.
+     * (priority 0).*/
 
     HMAP_FOR_EACH (od, key_node, datapaths) {
         if (!od->nbs) {
@@ -3440,9 +3505,11 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
 
         ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_OPTIONS, 0, "1", "next;");
         ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESPONSE, 0, "1", "next;");
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 0, "1", "next;");
+        ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 0, "1", "next;");
     }
 
-    /* Ingress table 13: Destination lookup, broadcast and multicast handling
+    /* Ingress table 15: Destination lookup, broadcast and multicast handling
      * (priority 100). */
     HMAP_FOR_EACH (op, key_node, ports) {
         if (!op->nbsp) {
@@ -5369,6 +5436,108 @@  sync_address_sets(struct northd_context *ctx)
     }
     shash_destroy(&sb_address_sets);
 }
+
+/*
+ * struct 'dns_info' is used to sync the DNS records between OVN Northbound db
+ * and Southbound db.
+ */
+struct dns_info {
+    struct hmap_node hmap_node;
+    const struct nbrec_dns *nb_dns; /* DNS record in the Northbound db. */
+    const struct sbrec_dns *sb_dns; /* DNS record in the Soutbound db. */
+
+    /* Datapaths to which the DNS entry is associated with it. */
+    const struct sbrec_datapath_binding **sbs;
+    size_t n_sbs;
+};
+
+static inline struct dns_info *
+get_dns_info_from_hmap(struct hmap *dns_map, struct uuid *uuid)
+{
+    struct dns_info *dns_info;
+    size_t hash = uuid_hash(uuid);
+    HMAP_FOR_EACH_WITH_HASH (dns_info, hmap_node, hash, dns_map) {
+        if (uuid_equals(&dns_info->nb_dns->header_.uuid, uuid)) {
+            return dns_info;
+        }
+    }
+
+    return NULL;
+}
+
+static void
+sync_dns_entries(struct northd_context *ctx, struct hmap *datapaths)
+{
+    struct hmap dns_map = HMAP_INITIALIZER(&dns_map);
+    struct ovn_datapath *od;
+    HMAP_FOR_EACH (od, key_node, datapaths) {
+        if (!od->nbs || !od->nbs->n_dns_records) {
+            continue;
+        }
+
+        for (size_t i = 0; i < od->nbs->n_dns_records; i++) {
+            struct dns_info *dns_info = get_dns_info_from_hmap(
+                &dns_map, &od->nbs->dns_records[i]->header_.uuid);
+            if (!dns_info) {
+                size_t hash = uuid_hash(
+                    &od->nbs->dns_records[i]->header_.uuid);
+                dns_info = xzalloc(sizeof *dns_info);;
+                dns_info->nb_dns = od->nbs->dns_records[i];
+                hmap_insert(&dns_map, &dns_info->hmap_node, hash);
+            }
+
+            dns_info->n_sbs++;
+            dns_info->sbs = xrealloc(dns_info->sbs,
+                                     dns_info->n_sbs * sizeof *dns_info->sbs);
+            dns_info->sbs[dns_info->n_sbs - 1] = od->sb;
+        }
+    }
+
+    const struct sbrec_dns *sbrec_dns, *next;
+    SBREC_DNS_FOR_EACH_SAFE (sbrec_dns, next, ctx->ovnsb_idl) {
+        const char *nb_dns_uuid = smap_get(&sbrec_dns->external_ids, "dns_id");
+        struct uuid dns_uuid;
+        if (!nb_dns_uuid || !uuid_from_string(&dns_uuid, nb_dns_uuid)) {
+            sbrec_dns_delete(sbrec_dns);
+            continue;
+        }
+
+        struct dns_info *dns_info =
+            get_dns_info_from_hmap(&dns_map, &dns_uuid);
+        if (dns_info) {
+            dns_info->sb_dns = sbrec_dns;
+        } else {
+            sbrec_dns_delete(sbrec_dns);
+        }
+    }
+
+    struct dns_info *dns_info;
+    HMAP_FOR_EACH_POP (dns_info, hmap_node, &dns_map) {
+        if (!dns_info->sb_dns) {
+            struct sbrec_dns *sbrec_dns = sbrec_dns_insert(ctx->ovnsb_txn);
+            dns_info->sb_dns = sbrec_dns;
+            char *dns_id = xasprintf(
+                UUID_FMT, UUID_ARGS(&dns_info->nb_dns->header_.uuid));
+            const struct smap external_ids =
+                SMAP_CONST1(&external_ids, "dns_id", dns_id);
+            sbrec_dns_set_external_ids(sbrec_dns, &external_ids);
+            free(dns_id);
+        }
+
+        /* Set the datapaths and records. If nothing has changed, then
+         * this will be a no-op.
+         */
+        sbrec_dns_set_datapaths(
+            dns_info->sb_dns,
+            (struct sbrec_datapath_binding **)dns_info->sbs,
+            dns_info->n_sbs);
+        sbrec_dns_set_records(dns_info->sb_dns, &dns_info->nb_dns->records);
+        free(dns_info->sbs);
+        free(dns_info);
+    }
+    hmap_destroy(&dns_map);
+}
+
 
 static void
 ovnnb_db_run(struct northd_context *ctx, struct ovsdb_idl_loop *sb_loop)
@@ -5383,6 +5552,7 @@  ovnnb_db_run(struct northd_context *ctx, struct ovsdb_idl_loop *sb_loop)
     build_lflows(ctx, &datapaths, &ports);
 
     sync_address_sets(ctx);
+    sync_dns_entries(ctx, &datapaths);
 
     struct ovn_datapath *dp, *next_dp;
     HMAP_FOR_EACH_SAFE (dp, next_dp, key_node, &datapaths) {
@@ -5782,6 +5952,11 @@  main(int argc, char *argv[])
     add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_name);
     add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_addresses);
 
+    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_dns);
+    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dns_col_datapaths);
+    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dns_col_records);
+    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dns_col_external_ids);
+
     ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_chassis);
     ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_chassis_col_nb_cfg);
 
diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
index dd0ac3d..4662653 100644
--- a/ovn/ovn-nb.ovsschema
+++ b/ovn/ovn-nb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Northbound",
-    "version": "5.5.0",
-    "cksum": "2099428463 14236",
+    "version": "5.5.1",
+    "cksum": "1743459612 15019",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -45,6 +45,11 @@ 
                                                   "refType": "strong"},
                                            "min": 0,
                                            "max": "unlimited"}},
+                "dns_records": {"type": {"key": {"type": "uuid",
+                                         "refTable": "DNS",
+                                         "refType": "weak"},
+                                  "min": 0,
+                                  "max": "unlimited"}},
                 "other_config": {
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}},
@@ -265,6 +270,17 @@ 
                                     "max": "unlimited"},
                                     "ephemeral": true}},
             "indexes": [["target"]]},
+        "DNS": {
+            "columns": {
+                "records": {"type": {"key": "string",
+                                     "value": "string",
+                                     "min": 0,
+                                     "max": "unlimited"}},
+                "external_ids": {"type": {"key": "string",
+                                 "value": "string",
+                                 "min": 0,
+                                 "max": "unlimited"}}},
+            "isRoot": true},
         "SSL": {
             "columns": {
                 "private_key": {"type": "string"},
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index 2b416ce..11e32f0 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -134,6 +134,12 @@ 
       QOS marking rules that apply to packets within the logical switch.
     </column>
 
+    <column name="dns_records">
+      This column defines the DNS records to be used for resolving internal
+      DNS queries within the logical switch by the native DNS resolver.
+      Please see the <ref table="DNS"/> table.
+    </column>
+
     <group title="IP Address Assignment">
       <p>
         These options control automatic IP address management (IPAM) for ports
@@ -1471,7 +1477,7 @@ 
         <column name="options" key="lease_time"
                 type='{"type": "integer", "minInteger": 0, "maxInteger": 4294967295}'>
           <p>
-            The offered lease time in seconds, 
+            The offered lease time in seconds,
           </p>
 
           <p>
@@ -1963,6 +1969,24 @@ 
       <column name="other_config"/>
     </group>
   </table>
+  <table name="DNS" title="Native DNS resolution">
+    <p>
+      Each row in this table stores the DNS records. The
+      <ref table="Logical_Switch"/> table's <ref table="Logical_Switch"
+      column="dns_records"/> references these records.
+    </p>
+
+    <column name="records">
+      Key-value pair of DNS records with <code>hostname</code> as the key
+      and value as a string of IP address(es) separated by comma or space.
+
+      <p><b>Example: </b> "vm1.ovn.org" = "10.0.0.4 aef0::4"</p>
+    </column>
+
+    <column name="external_ids">
+      See <em>External IDs</em> at the beginning of this document.
+    </column>
+  </table>
   <table name="SSL">
     SSL configuration for ovn-nb database access.
 
@@ -2001,5 +2025,4 @@ 
       <column name="external_ids"/>
     </group>
   </table>
-
 </database>
diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
index e9dcde7..3df8b81 100644
--- a/ovn/utilities/ovn-nbctl.c
+++ b/ovn/utilities/ovn-nbctl.c
@@ -3062,6 +3062,9 @@  static const struct ctl_table_class tables[NBREC_N_TABLES] = {
 
     [NBREC_TABLE_SSL].row_ids[0]
     = {&nbrec_table_nb_global, NULL, &nbrec_nb_global_col_ssl},
+
+    [NBREC_TABLE_DNS].row_ids[0]
+    = {&nbrec_table_dns, NULL, &nbrec_dns_col_records},
 };
 
 static void
diff --git a/tests/ovn.at b/tests/ovn.at
index 53a2dd5..b9f8114 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -6435,6 +6435,383 @@  OVS_APP_EXIT_AND_WAIT([ovsdb-server])
 
 AT_CLEANUP
 
+AT_SETUP([ovn -- dns lookup : 1 HV, 2 LS, 2 LSPs/LS])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+
+ovn-nbctl ls-add ls1
+
+ovn-nbctl lsp-add ls1 ls1-lp1 \
+-- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4 aef0::4"
+
+ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4 aef0::4"
+
+ovn-nbctl lsp-add ls1 ls1-lp2 \
+-- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02 10.0.0.6 20.0.0.4"
+
+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={}`
+
+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 Logical_switch ls1 dns_records="$DNS1"
+
+net_add n1
+sim_add hv1
+
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovs-vsctl -- add-port br-int hv1-vif1 -- \
+    set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+    options:tx_pcap=hv1/vif1-tx.pcap \
+    options:rxq_pcap=hv1/vif1-rx.pcap \
+    ofport-request=1
+
+ovs-vsctl -- add-port br-int hv1-vif2 -- \
+    set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \
+    options:tx_pcap=hv1/vif2-tx.pcap \
+    options:rxq_pcap=hv1/vif2-rx.pcap \
+    ofport-request=2
+
+ovn_populate_arp
+sleep 2
+as hv1 ovs-vsctl show
+
+echo "*************************"
+ovn-sbctl list DNS
+echo "*************************"
+
+ip_to_hex() {
+    printf "%02x%02x%02x%02x" "$@"
+}
+
+reset_pcap_file() {
+    local iface=$1
+    local pcap_file=$2
+    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
+options:rxq_pcap=dummy-rx.pcap
+    rm -f ${pcap_file}*.pcap
+    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
+options:rxq_pcap=${pcap_file}-rx.pcap
+}
+
+# set_dns_params host_name
+# Sets the dns_req_data and dns_resp_data
+set_dns_params() {
+    local hname=$1
+    local ttl=00000e10
+    an_count=0001
+    type=0001
+    case $hname in
+    vm1)
+        # vm1.ovn.org
+        hostname=03766d31036f766e036f726700
+        # IPv4 address - 10.0.0.4
+        expected_dns_answer=${hostname}00010001${ttl}00040a000004
+        ;;
+    vm2)
+        # vm2.ovn.org
+        hostname=03766d32036f766e036f726700
+        # IPv4 address - 10.0.0.6
+        expected_dns_answer=${hostname}00010001${ttl}00040a000006
+        # IPv4 address - 20.0.0.4
+        expected_dns_answer=${expected_dns_answer}${hostname}00010001${ttl}000414000004
+        an_count=0002
+        ;;
+    vm3)
+        # vm3.ovn.org
+        hostname=03766d33036f766e036f726700
+        # IPv4 address - 40.0.0.4
+        expected_dns_answer=${hostname}00010001${ttl}000428000004
+        ;;
+    vm1_ipv6_only)
+        # vm1.ovn.org
+        hostname=03766d31036f766e036f726700
+        # IPv6 address - aef0::4
+        type=001c
+        expected_dns_answer=${hostname}${type}0001${ttl}0010aef00000000000000000000000000004
+        ;;
+    vm1_ipv4_v6)
+        # vm1.ovn.org
+        hostname=03766d31036f766e036f726700
+        type=00ff
+        an_count=0002
+        # IPv4 address - 10.0.0.4
+        # IPv6 address - aef0::4
+        expected_dns_answer=${hostname}00010001${ttl}00040a000004
+        expected_dns_answer=${expected_dns_answer}${hostname}001c0001${ttl}0010
+        expected_dns_answer=${expected_dns_answer}aef00000000000000000000000000004
+        ;;
+    vm1_invalid_type)
+        # vm1.ovn.org
+        hostname=03766d31036f766e036f726700
+        # IPv6 address - aef0::4
+        type=0002
+        ;;
+    vm1_incomplete)
+        # set type to none
+        type=''
+    esac
+    # TTL - 3600
+    local dns_req_header=010201200001000000000000
+    local dns_resp_header=010281200001${an_count}00000000
+    dns_req_data=${dns_req_header}${hostname}${type}0001
+    dns_resp_data=${dns_resp_header}${hostname}${type}0001${expected_dns_answer}
+}
+
+# This shell function sends a DNS request packet
+# test_dns INPORT SRC_MAC DST_MAC SRC_IP DST_IP DNS_QUERY EXPEC
+test_dns() {
+    local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 dns_reply=$6
+    local dns_query_data=$7
+    shift; shift; shift; shift; shift; shift; shift;
+    # Packet size => IPv4 header (20) + UDP header (8) +
+    #                DNS data (header + query)
+    ip_len=`expr 28 + ${#dns_query_data} / 2`
+    udp_len=`expr $ip_len - 20`
+    ip_len=$(printf "%x" $ip_len)
+    udp_len=$(printf "%x" $udp_len)
+    local request=${dst_mac}${src_mac}0800450000${ip_len}0000000080110000
+    request=${request}${src_ip}${dst_ip}9234003500${udp_len}0000
+    # dns data
+    request=${request}${dns_query_data}
+
+    if test $dns_reply != 0; then
+        local dns_reply=$1
+        ip_len=`expr 28 + ${#dns_reply} / 2`
+        udp_len=`expr $ip_len - 20`
+        ip_len=$(printf "%x" $ip_len)
+        udp_len=$(printf "%x" $udp_len)
+        local reply=${src_mac}${dst_mac}0800450000${ip_len}0000000080110000
+        reply=${reply}${dst_ip}${src_ip}0035923400${udp_len}0000${dns_reply}
+        echo $reply >> $inport.expected
+    else
+        for outport; do
+            echo $request >> $outport.expected
+        done
+    fi
+    as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $request
+}
+
+AT_CAPTURE_FILE([ofctl_monitor0.log])
+as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \
+--pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log
+
+set_dns_params vm2
+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 1.
+OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_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
+
+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 2.
+OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
+cat 2.expected | cut -c -48 > expout
+AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
+# Skipping the IPv4 checksum.
+cat 2.expected | cut -c 53- > expout
+AT_CHECK([cat 2.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
+
+# Clear the hostname options for ls1-lp2
+ovn-nbctl --wait=hv remove DNS $DNS1 records vm2.ovn.org
+
+set_dns_params vm2
+src_ip=`ip_to_hex 10 0 0 4`
+dst_ip=`ip_to_hex 10 0 0 1`
+dns_reply=0
+test_dns 1 f00000000001 f00000000002 $src_ip $dst_ip $dns_reply $dns_req_data
+
+# NXT_RESUMEs should be 3.
+OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
+AT_CHECK([cat 1.packets], [0], [])
+
+reset_pcap_file hv1-vif1 hv1/vif1
+reset_pcap_file hv1-vif2 hv1/vif2
+rm -f 1.expected
+rm -f 2.expected
+
+# Clear the hostname for ls1-lp1
+# Since ls1 has no hostnames configued,
+# ovn-northd should not add the DNS flows.
+ovn-nbctl --wait=hv remove DNS $DNS1 records vm1.ovn.org
+
+set_dns_params vm1
+src_ip=`ip_to_hex 10 0 0 6`
+dst_ip=`ip_to_hex 10 0 0 1`
+dns_reply=0
+test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data
+
+# NXT_RESUMEs should be 3 only.
+OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
+AT_CHECK([cat 2.packets], [0], [])
+
+reset_pcap_file hv1-vif1 hv1/vif1
+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 set DNS $DNS1 records:vm1.ovn.org="10.0.0.4 aef0::4"
+
+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 4.
+OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
+cat 2.expected | cut -c -48 > expout
+AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
+# Skipping the IPv4 checksum.
+cat 2.expected | cut -c 53- > expout
+AT_CHECK([cat 2.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 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`
+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 5.
+OVS_WAIT_UNTIL([test 5 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
+cat 2.expected | cut -c -48 > expout
+AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
+# Skipping the IPv4 checksum.
+cat 2.expected | cut -c 53- > expout
+AT_CHECK([cat 2.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
+
+# Invalid type.
+set_dns_params vm1_invalid_type
+src_ip=`ip_to_hex 10 0 0 6`
+dst_ip=`ip_to_hex 10 0 0 1`
+dns_reply=0
+test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data
+
+# NXT_RESUMEs should be 6.
+OVS_WAIT_UNTIL([test 6 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
+AT_CHECK([cat 2.packets], [0], [])
+
+reset_pcap_file hv1-vif1 hv1/vif1
+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`
+dst_ip=`ip_to_hex 10 0 0 1`
+dns_reply=0
+test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data
+
+# NXT_RESUMEs should be 7.
+OVS_WAIT_UNTIL([test 7 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
+AT_CHECK([cat 2.packets], [0], [])
+
+reset_pcap_file hv1-vif1 hv1/vif1
+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"
+
+set_dns_params vm3
+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 8.
+OVS_WAIT_UNTIL([test 8 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$top_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
+
+as hv1
+ OVS_APP_EXIT_AND_WAIT([ovn-controller])
+OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([ovn-northd])
+
+as main
+OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+AT_CLEANUP
+
 AT_SETUP([ovn -- 1 LR with distributed router gateway port])
 AT_SKIP_IF([test $HAVE_PYTHON = no])
 ovn_start