Numan, I tested this patchset along with the corresponding WIP ml2
native-dhcp openstack patch.
It looks good to me,

On Tue, May 24, 2016 at 11:04 AM, Numan Siddique <nusid...@redhat.com> wrote:
> OVN implements a native DHCP support which caters to the common
> use case of providing an IP address to a booting instance by
> providing stateless replies to DHCP requests based on statically
> configured address mappings. To do this it allows a short list of
> DHCP options to be configured and applied at each compute host
> running ovn-controller.
>
> A new table 'Subnet' is added in OVN NB DB to store the DHCP options.
>
> For each logical port following flows are added if the CMS has defined
> DHCP options in the 'Subnet' column
>
>  - A logical flow which copies the DHCP options to the DHCP
>    request packets using the 'put_dhcp_opts' action and advances the
>    packet to the next stage.
>
>  - A logical flow which implements the DHCP reponder by sending
>    the DHCP reply back to the inport once the 'put_dhcp_opts' action
>    is applied.
>
> Signed-Off-by: Numan Siddique <nusid...@redhat.com>
> ---
>  ovn/northd/ovn-northd.8.xml   |  89 +++++++++++-
>  ovn/northd/ovn-northd.c       | 265 ++++++++++++++++++++++++++++++++++-
>  ovn/ovn-nb.ovsschema          |  19 ++-
>  ovn/ovn-nb.xml                | 314 
> +++++++++++++++++++++++++++++++++++++++++-
>  ovn/utilities/ovn-nbctl.8.xml |  29 ++++
>  ovn/utilities/ovn-nbctl.c     | 196 ++++++++++++++++++++++++++
>  tests/ovn.at                  | 250 +++++++++++++++++++++++++++++++++
>  tests/test-ovn-dhcp.c         | 135 ++++++++++++++++++
>  8 files changed, 1287 insertions(+), 10 deletions(-)
>
> diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
> index 970c352..9a4a46a 100644
> --- a/ovn/northd/ovn-northd.8.xml
> +++ b/ovn/northd/ovn-northd.8.xml
> @@ -326,7 +326,88 @@ output;
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 6: Destination Lookup</h3>
> +    <h3>Ingress Table 6: DHCP pause</h3>
> +
> +    <p>
> +      This table adds the DHCP options to a DHCP packet from the
> +      logical ports configured with IPv4 address(es) and DHCP options.
> +    </p>
> +
> +    <ul>
> +      <li>
> +        <p>
> +          A priority-100 logical flow is added for these logical ports
> +          which matches the packet with <code>udp.src</code> = 68 and
> +          <code>udp.dst</code> = 67 and applies the action
> +          <code>put_dhcp_opts</code> and advances the packet to the table 7.
> +        </p>
> +
> +        <pre>
> +put_dhcp_opts(<var>R</var>, offer_ip = <var>O</var>, DHCP_OPTIONS);
> +next;
> +        </pre>
> +
> +        <p>
> +          The action <code>put_dhcp_opts</code> adds the DHCP options to
> +          the packet and sets the <code>yiaddr</code> to the offer ip
> +          defined in <var>O</var> only if the packet is a
> +          DHCPDISCOVER/DHCPREQUEST and resumes the pipeline by setting
> +          <code>0x1</code> to the OVS register <var>R</var>.
> +          If the packet is non-DHCP packet or invalid DHCP packet
> +          it resumes the pipeline without any modifications.
> +        </p>
> +
> +      </li>
> +
> +      <li>
> +        A priority-0 flow that matches all packets to advances to table 7.
> +      </li>
> +    </ul>
> +
> +    <h3>Ingress Table 7: DHCP resume</h3>
> +
> +    <p>
> +      This table implements DHCP responder for the resumed DHCP packets
> +      from the previous table.
> +    </p>
> +
> +    <ul>
> +      <li>
> +        <p>
> +          A priority 100 logical flow is added for the logical ports 
> configured
> +          with DHCP options which matches the packet with
> +          <code>udp.src</code> = 68, <code>udp.dst</code> = 67 and
> +          <code>reg0</code> = 0x1 and responds back to the
> +          <code>inport</code> after applying these actions.
> +          If <code>reg0</code> is set to 1, it means that the action
> +          <code>put_dhcp_opts</code> was successful.
> +        </p>
> +
> +        <pre>
> +eth.dst = eth.src;
> +eth.src = <var>E</var>;
> +ip4.dst = <var>O</var>;
> +ip4.src = <var>S</var>;
> +udp.src = 67;
> +udp.dst = 68;
> +outport = <var>P</var>;
> +inport = ""; /* Allow sending out inport. */
> +output;
> +        </pre>
> +
> +        <p>
> +          Where <var>E</var> is the server mac address and <var>S</var> is 
> the
> +          server IPv4 address defined in the DHCP options and <var>O</var> is
> +          the IPv4 address defined in the logical port's addresses column.
> +        </p>
> +      </li>
> +
> +      <li>
> +        A priority-0 flow that matches all packets to advances to table 8.
> +      </li>
> +    </ul>
> +
> +    <h3>Ingress Table 8: Destination Lookup</h3>
>
>      <p>
>        This table implements switching behavior.  It contains these logical
> @@ -370,6 +451,12 @@ output;
>        This is similar to ingress table 4 except for <code>to-lport</code> 
> ACLs.
>      </p>
>
> +    <p>
> +      Also a priority 34000 logical flow is added for each subnet of the 
> logical
> +      switch which has DHCP options defined to allow the DHCP reply packet
> +      from the <code>Ingress Table 7: DHCP resume</code>.
> +    </p>
> +
>      <h3>Egress Table 2: Egress Port Security - IP</h3>
>
>      <p>
> diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
> index 44e9430..ff16622 100644
> --- a/ovn/northd/ovn-northd.c
> +++ b/ovn/northd/ovn-northd.c
> @@ -26,6 +26,7 @@
>  #include "hash.h"
>  #include "hmap.h"
>  #include "json.h"
> +#include "ovn/lib/ovn-dhcp.h"
>  #include "ovn/lib/lex.h"
>  #include "ovn/lib/ovn-nb-idl.h"
>  #include "ovn/lib/ovn-sb-idl.h"
> @@ -33,6 +34,7 @@
>  #include "packets.h"
>  #include "poll-loop.h"
>  #include "smap.h"
> +#include "sset.h"
>  #include "stream.h"
>  #include "stream-ssl.h"
>  #include "unixctl.h"
> @@ -94,7 +96,9 @@ enum ovn_stage {
>      PIPELINE_STAGE(SWITCH, IN,  PRE_ACL,        3, "ls_in_pre_acl")      \
>      PIPELINE_STAGE(SWITCH, IN,  ACL,            4, "ls_in_acl")          \
>      PIPELINE_STAGE(SWITCH, IN,  ARP_RSP,        5, "ls_in_arp_rsp")      \
> -    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,        6, "ls_in_l2_lkup")      \
> +    PIPELINE_STAGE(SWITCH, IN,  DHCP_PAUSE,     6, "ls_in_dhcp_pause")      \
> +    PIPELINE_STAGE(SWITCH, IN,  DHCP_RESUME,    7, "ls_in_dhcp_resume")      
> \
> +    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,        8, "ls_in_l2_lkup")      \
>                                                                        \
>      /* Logical switch egress stages. */                               \
>      PIPELINE_STAGE(SWITCH, OUT, PRE_ACL,     0, "ls_out_pre_acl")     \
> @@ -1265,6 +1269,82 @@ has_stateful_acl(struct ovn_datapath *od)
>      return false;
>  }
>
> +static bool
> +build_dhcp_action(struct ovn_port *op, ovs_be32 offer_ip,
> +                  struct ds *pause_action, struct ds *resume_action)
> +{
> +    if(smap_get_bool(&op->nbs->options, "dhcp_disabled", false)) {
> +        /* CMS has disabled native dhcp for this lport */
> +        return false;
> +    }
> +
> +    struct nbrec_subnet *subnet = NULL;
> +    ovs_be32 host_ip, mask;
> +    for (size_t i = 0; i < op->od->nbs->n_subnets; i++) {
> +        char *error = ip_parse_masked(op->od->nbs->subnets[i]->cidr, 
> &host_ip,
> +                                      &mask);
> +        if (!error && !((offer_ip ^ host_ip) & mask)) {
> +           /* offerip belongs to this subnet */
> +            subnet = op->od->nbs->subnets[i];
> +            break;
> +        }
> +        free(error);
> +    }
> +
> +    if (!(subnet && subnet->gateway_ip && subnet->enable_dhcp
> +          && subnet->ip_version == 4)) {
> +        return false;
> +    }
> +
> +
> +    const char *server_ip = smap_get(&subnet->dhcp_options, "server_id");
> +    const char *server_mac = smap_get(&subnet->dhcp_options, "server_mac");
> +    const char *lease_time = smap_get(&subnet->dhcp_options, "lease_time");
> +
> +    if (!(server_ip && server_mac && lease_time)) {
> +        /* "server_id", "server_mac" and "lease_time" should be present
> +         * in the dhcp_options. */
> +        return false;
> +    }
> +
> +    struct smap dhcp_options = SMAP_INITIALIZER(&dhcp_options);
> +    smap_clone(&dhcp_options, &subnet->dhcp_options);
> +
> +    /* server_mac is not dhcp option, delete it from the smap */
> +    smap_remove(&dhcp_options, "server_mac");
> +    smap_add(&dhcp_options, "router", subnet->gateway_ip);
> +    char *netmask = xasprintf(IP_FMT, IP_ARGS(mask));
> +    smap_add(&dhcp_options, "netmask", netmask);
> +    free(netmask);
> +
> +    struct smap_node *node;
> +    /* override the dhcp options define in the lport options if any */
> +    SMAP_FOR_EACH(node, &op->nbs->options) {
> +        if(!strncmp(node->key, "dhcp_opt_", 9)) {
> +            smap_replace(&dhcp_options, &node->key[9], node->value);
> +        }
> +    }
> +
> +    ds_put_format(pause_action, "put_dhcp_opts(reg0, offerip = "IP_FMT", ",
> +                  IP_ARGS(offer_ip));
> +    SMAP_FOR_EACH(node, &dhcp_options) {
> +        ds_put_format(pause_action, "%s = %s, ", node->key, node->value);
> +    }
> +
> +    ds_chomp(pause_action, ' ');
> +    ds_chomp(pause_action, ',');
> +    ds_put_cstr(pause_action, "); next;");
> +
> +    ds_put_format(resume_action, "eth.dst = eth.src; eth.src = %s; "
> +                  "ip4.dst = "IP_FMT"; ip4.src = %s; udp.src = 67; "
> +                  "udp.dst = 68; outport = inport; inport = \"\";"
> +                  " /* Allow sending out inport. */ output;",
> +                  server_mac, IP_ARGS(offer_ip), server_ip);
> +
> +    smap_destroy(&dhcp_options);
> +    return true;
> +}
> +
>  static void
>  build_acls(struct ovn_datapath *od, struct hmap *lflows, struct hmap *ports)
>  {
> @@ -1415,6 +1495,36 @@ build_acls(struct ovn_datapath *od, struct hmap 
> *lflows, struct hmap *ports)
>                            acl->match, "drop;");
>          }
>      }
> +
> +    /* Add 34000 priority flow to allow DHCP reply from ovn-controller to all
> +     * logical ports of the datapath if the CMS has configured DHCP options*/
> +    if (od->nbs && od->nbs->n_ports && od->nbs->n_subnets) {
> +        for (size_t i = 0; i < od->nbs->n_subnets; i++) {
> +             if (!(od->nbs->subnets[i]->gateway_ip &&
> +                   od->nbs->subnets[i]->enable_dhcp &&
> +                   od->nbs->subnets[i]->ip_version == 4)) {
> +                 continue;
> +             }
> +
> +             const char *server_id = smap_get(
> +                 &od->nbs->subnets[i]->dhcp_options, "server_id");
> +             const char *server_mac = smap_get(
> +                 &od->nbs->subnets[i]->dhcp_options, "server_mac");
> +             const char *lease_time = smap_get(
> +                 &od->nbs->subnets[i]->dhcp_options, "lease_time");
> +             if (server_id && server_mac && lease_time) {
> +                 struct ds match = DS_EMPTY_INITIALIZER;
> +                 const char *actions = has_stateful ? "ct_commit; next;" :
> +                                       "next;";
> +                 ds_put_format(&match, "eth.src == %s && ip4.src == %s &&"
> +                               " udp && udp.src == 67 && udp.dst == 68",
> +                               server_mac, server_id);
> +                 ovn_lflow_add(
> +                     lflows, od, S_SWITCH_OUT_ACL, 34000, ds_cstr(&match),
> +                     actions);
> +             }
> +        }
> +    }
>  }
>
>  static void
> @@ -1497,7 +1607,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap 
> *ports,
>          ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_IP, 0, "1", "next;");
>      }
>
> -    /* Ingress table 3: ARP responder, skip requests coming from localnet 
> ports.
> +    /* Ingress table 5: ARP responder, skip requests coming from localnet 
> ports.
>       * (priority 100). */
>      HMAP_FOR_EACH (op, key_node, ports) {
>          if (!op->nbs) {
> @@ -1572,7 +1682,73 @@ build_lswitch_flows(struct hmap *datapaths, struct 
> hmap *ports,
>          ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_RSP, 0, "1", "next;");
>      }
>
> -    /* Ingress table 6: Destination lookup, broadcast and multicast handling
> +    /* Logical switch ingress table 6 and 7: DHCP pause and resume
> +     * priority 100 flows. */
> +    HMAP_FOR_EACH (op, key_node, ports) {
> +        if (!op->nbs) {
> +           continue;
> +        }
> +
> +        if (!lport_is_enabled(op->nbs) || !strcmp(op->nbs->type, "router")) {
> +            /* Don't add the DHCP flows if the port is not enabled or if the
> +             * port is a router port */
> +            continue;
> +        }
> +
> +        for (size_t i = 0; i < op->nbs->n_addresses; i++) {
> +            struct lport_addresses laddrs;
> +            if (!extract_lport_addresses(op->nbs->addresses[i], &laddrs,
> +                                        false)) {
> +                continue;
> +            }
> +
> +            if (!laddrs.n_ipv4_addrs) {
> +                continue;
> +            }
> +
> +            for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) {
> +                struct ds pause_action = DS_EMPTY_INITIALIZER;
> +                struct ds resume_action = DS_EMPTY_INITIALIZER;
> +                if (build_dhcp_action(op, laddrs.ipv4_addrs[j].addr,
> +                                      &pause_action, &resume_action)) {
> +                    struct ds match = DS_EMPTY_INITIALIZER;
> +                    ds_put_format(
> +                        &match, "inport == %s && eth.src == "ETH_ADDR_FMT
> +                        " && ip4.src == 0.0.0.0 && "
> +                        "ip4.dst == 255.255.255.255 && udp.src == 68 && "
> +                        "udp.dst == 67", op->json_key,
> +                        ETH_ADDR_ARGS(laddrs.ea));
> +
> +                    ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_PAUSE, 
> 100,
> +                                  ds_cstr(&match), ds_cstr(&pause_action));
> +                    /* If reg0 is set to 1, it means the put_dhcp_opts action
> +                     * is successful */
> +                    ds_put_cstr(&match, " && reg0 == 1");
> +                    ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_RESUME, 
> 100,
> +                                  ds_cstr(&match), ds_cstr(&resume_action));
> +                    ds_destroy(&match);
> +                    ds_destroy(&pause_action);
> +                    ds_destroy(&resume_action);
> +                    break;
> +                }
> +            }
> +            free(laddrs.ipv4_addrs);
> +        }
> +    }
> +
> +    /* Ingress table 6 and 7: DHCP pause and resume, by default goto next.
> +     * (priority 0)*/
> +
> +    HMAP_FOR_EACH (od, key_node, datapaths) {
> +        if (!od->nbs) {
> +            continue;
> +        }
> +
> +        ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_PAUSE, 0, "1", "next;");
> +        ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESUME, 0, "1", "next;");
> +    }
> +
> +    /* Ingress table 8: Destination lookup, broadcast and multicast handling
>       * (priority 100). */
>      HMAP_FOR_EACH (op, key_node, ports) {
>          if (!op->nbs) {
> @@ -1592,7 +1768,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap 
> *ports,
>                        "outport = \""MC_FLOOD"\"; output;");
>      }
>
> -    /* Ingress table 6: Destination lookup, unicast handling (priority 50), 
> */
> +    /* Ingress table 8: Destination lookup, unicast handling (priority 50), 
> */
>      HMAP_FOR_EACH (op, key_node, ports) {
>          if (!op->nbs) {
>              continue;
> @@ -1629,7 +1805,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap 
> *ports,
>          }
>      }
>
> -    /* Ingress table 6: Destination lookup for unknown MACs (priority 0). */
> +    /* Ingress table 8: Destination lookup for unknown MACs (priority 0). */
>      HMAP_FOR_EACH (od, key_node, datapaths) {
>          if (!od->nbs) {
>              continue;
> @@ -2283,6 +2459,77 @@ ovnsb_db_run(struct northd_context *ctx)
>  }
>
>
> +static struct dhcp_opts_map supported_dhcp_opts[] = {
> +    OFFERIP,
> +    DHCP_OPT_NETMASK,
> +    DHCP_OPT_ROUTER,
> +    DHCP_OPT_DNS_SERVER,
> +    DHCP_OPT_LOG_SERVER,
> +    DHCP_OPT_LPR_SERVER,
> +    DHCP_OPT_SWAP_SERVER,
> +    DHCP_OPT_POLICY_FILTER,
> +    DHCP_OPT_ROUTER_SOLICITATION,
> +    DHCP_OPT_NIS_SERVER,
> +    DHCP_OPT_NTP_SERVER,
> +    DHCP_OPT_SERVER_ID,
> +    DHCP_OPT_TFTP_SERVER,
> +    DHCP_OPT_CLASSLESS_STATIC_ROUTE,
> +    DHCP_OPT_MS_CLASSLESS_STATIC_ROUTE,
> +    DHCP_OPT_IP_FORWARD_ENABLE,
> +    DHCP_OPT_ROUTER_DISCOVERY,
> +    DHCP_OPT_ETHERNET_ENCAP,
> +    DHCP_OPT_DEFAULT_TTL,
> +    DHCP_OPT_TCP_TTL,
> +    DHCP_OPT_MTU,
> +    DHCP_OPT_LEASE_TIME,
> +    DHCP_OPT_T1,
> +    DHCP_OPT_T2
> +};
> +
> +static void
> +check_and_add_supported_dhcp_opts_to_sb_db(struct northd_context *ctx)
> +{
> +    static bool nothing_to_add = false;
> +
> +    if (nothing_to_add) {
> +        return;
> +    }
> +
> +    struct hmap dhcp_opts_to_add = HMAP_INITIALIZER(&dhcp_opts_to_add);
> +    for (size_t i = 0; (i < sizeof(supported_dhcp_opts) /
> +                            sizeof(supported_dhcp_opts[0])); i++) {
> +        hmap_insert(&dhcp_opts_to_add, &supported_dhcp_opts[i].hmap_node,
> +                    dhcp_opt_hash(supported_dhcp_opts[i].name));
> +    }
> +
> +    const struct sbrec_dhcp_options *opt_row, *opt_row_next;
> +    SBREC_DHCP_OPTIONS_FOR_EACH_SAFE(opt_row, opt_row_next, ctx->ovnsb_idl) {
> +        struct dhcp_opts_map *dhcp_opt =
> +            dhcp_opts_find(&dhcp_opts_to_add, opt_row->name);
> +        if (dhcp_opt) {
> +            hmap_remove(&dhcp_opts_to_add, &dhcp_opt->hmap_node);
> +        }
> +        else {
> +            sbrec_dhcp_options_delete(opt_row);
> +        }
> +    }
> +
> +    if (!dhcp_opts_to_add.n) {
> +        nothing_to_add = true;
> +    }
> +
> +    struct dhcp_opts_map *opt;
> +    HMAP_FOR_EACH_POP(opt, hmap_node, &dhcp_opts_to_add) {
> +        struct sbrec_dhcp_options *sbrec_dhcp_option =
> +            sbrec_dhcp_options_insert(ctx->ovnsb_txn);
> +        sbrec_dhcp_options_set_name(sbrec_dhcp_option, opt->name);
> +        sbrec_dhcp_options_set_code(sbrec_dhcp_option, opt->code);
> +        sbrec_dhcp_options_set_type(sbrec_dhcp_option, opt->type);
> +    }
> +
> +    hmap_destroy(&dhcp_opts_to_add);
> +}
> +
>  static char *default_nb_db_;
>
>  static const char *
> @@ -2451,6 +2698,10 @@ main(int argc, char *argv[])
>      add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_binding_col_options);
>      add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_binding_col_mac);
>      ovsdb_idl_add_column(ovnsb_idl_loop.idl, 
> &sbrec_port_binding_col_chassis);
> +    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_dhcp_options);
> +    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_code);
> +    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_type);
> +    add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_name);
>
>      /* Main loop. */
>      exiting = false;
> @@ -2464,7 +2715,9 @@ main(int argc, char *argv[])
>
>          ovnnb_db_run(&ctx);
>          ovnsb_db_run(&ctx);
> -
> +        if (ctx.ovnsb_txn) {
> +            check_and_add_supported_dhcp_opts_to_sb_db(&ctx);
> +        }
>          unixctl_server_run(unixctl);
>          unixctl_server_wait(unixctl);
>          if (exiting) {
> diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
> index 8163f6a..5edd032 100644
> --- a/ovn/ovn-nb.ovsschema
> +++ b/ovn/ovn-nb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Northbound",
> -    "version": "2.1.1",
> -    "cksum": "2615511875 5108",
> +    "version": "2.2.0",
> +    "cksum": "2898515457 5921",
>      "tables": {
>          "Logical_Switch": {
>              "columns": {
> @@ -16,6 +16,11 @@
>                                            "refType": "strong"},
>                                    "min": 0,
>                                    "max": "unlimited"}},
> +                "subnets": {"type": {"key": {"type": "uuid",
> +                                             "refTable": "Subnet",
> +                                             "refType": "strong"},
> +                                  "min": 0,
> +                                  "max": "unlimited"}},
>                  "external_ids": {
>                      "type": {"key": "string", "value": "string",
>                               "min": 0, "max": "unlimited"}}},
> @@ -63,6 +68,16 @@
>                      "type": {"key": "string", "value": "string",
>                               "min": 0, "max": "unlimited"}}},
>              "isRoot": false},
> +        "Subnet": {
> +            "columns": {
> +                "cidr": {"type": "string"},
> +                "ip_version": {"type": {"key": {"type": "integer",
> +                                                 "enum": ["set", [4, 6]]}}},
> +                "gateway_ip": {"type": "string"},
> +                "enable_dhcp": {"type": "boolean"},
> +                "dhcp_options": {"type": {"key": "string", "value": "string",
> +                                          "min": 0, "max": "unlimited"}}},
> +            "isRoot": false},
>          "Logical_Router": {
>              "columns": {
>                  "name": {"type": "string"},
> diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
> index c01455d..5bbe8c4 100644
> --- a/ovn/ovn-nb.xml
> +++ b/ovn/ovn-nb.xml
> @@ -73,11 +73,18 @@
>        Access control rules that apply to packets within the logical switch.
>      </column>
>
> +    <column name="subnets">
> +      <p>
> +        Subnets configured to the logical switch.
> +      </p>
> +    </column>
> +
>      <group title="Common Columns">
>        <column name="external_ids">
>          See <em>External IDs</em> at the beginning of this document.
>        </column>
>      </group>
> +
>    </table>
>
>    <table name="Logical_Port" title="L2 logical switch port">
> @@ -212,6 +219,28 @@
>            interface, in kb.
>          </column>
>        </group>
> +
> +      <group title="DHCP Options">
> +        <p>
> +          These options apply to logical ports with <ref column="type"/> 
> having
> +          (empty string)
> +        </p>
> +
> +        <column name="options" key="dhcp_opt_OPTION_NAME">
> +          Each logical port can override the DHCP options defined in the
> +          <ref column="dhcp_options"/> of <ref table="Subnet"/>
> +          by defining them in this column with the prefix "dhcp_opt_".
> +          Please see the <ref column="dhcp_options"/> of
> +          <ref table="Subnet"/> for supported DHCP options.
> +
> +          Example: key="dhcp_opt_mtu", value="1300"
> +        </column>
> +
> +        <column name="options" key="dhcp_disabled">
> +          If this is defined, <code>ovn-northd</code> will disable the native
> +          DHCP responder for the logical port.
> +        </column>
> +      </group>
>      </group>
>
>      <group title="Containers">
> @@ -605,6 +634,289 @@
>      </group>
>    </table>
>
> +  <table name="Subnet" title="L2 logical switch subnet">
> +    <p>
> +      A subnet within an L2 logical switch. This is an optional table. CMS
> +      can add the rows to this table to define the subnets belonging to 
> logical
> +      switch.
> +    </p>
> +
> +    <column name="cidr">
> +      <p>
> +        cidr of the subnet.
> +      </p>
> +    </column>
> +
> +    <column name="ip_version">
> +      <p>
> +        IP version of the subnet -4 or 6.
> +      </p>
> +    </column>
> +
> +    <column name="gateway_ip">
> +      <p>
> +        Gateway ip of the subnet.
> +      </p>
> +    </column>
> +
> +    <column name="enable_dhcp">
> +      <p>
> +        If set to true, native DHCP support will be enabled for all the
> +        logical ports having the IPv4 address from the subnet cidr.
> +      </p>
> +    </column>
> +
> +    <group title="DHCP options">
> +      <column name="dhcp_options">
> +        <p>
> +          OVN implements a native DHCP support which caters to the common
> +          use case of providing an IP address to a booting instance by
> +          providing stateless replies to DHCP requests based on statically
> +          configured address mappings. To do this it allows a short list of
> +          DHCP options to be configured and applied at each compute host
> +          running ovn-controller.
> +        </p>
> +
> +        <p>
> +          CMS should define the set of DHCP options as key/value pairs.
> +          The defined DHCP options will be include in the DHCP response to 
> the
> +          DHCP DISCOVER/REQUEST packet from the logical ports having the IPv4
> +          addresses in the <ref column="cidr"/>.
> +        </p>
> +      </column>
> +
> +      <group title="Supported DHCP options">
> +        <p>
> +          Below are the supported DHCP options whose values are IPv4 address
> +          or addresses. If the value has more than one IPv4 address, then it
> +          should be enclosed within '{}' braces. Please refer to the
> +          RFC 2132 <code>"https://tools.ietf.org/html/rfc2132";</code> for
> +          more details on the DHCP options and their codes.
> +        </p>
> +
> +        <column name="dhcp_options" key="netmask">
> +          <p>
> +            The DHCP option code for this option is 1.
> +          </p>
> +
> +          <p>
> +            Example. key="netmask", value="255.255.255.0"
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="router">
> +          <p>
> +            The DHCP option code for this option is 3.
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="dns_server">
> +          <p>
> +            The DHCP option code for this option is 6.
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="log_server">
> +          <p>
> +            The DHCP option code for this option is 7.
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="lpr_server">
> +          <p>
> +            The DHCP option code for this option is 9.
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="swap_server">
> +          <p>
> +            The DHCP option code for this option is 16.
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="policy_filter">
> +          <p>
> +            The DHCP option code for this option is 21.
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="router_solicitation">
> +          <p>
> +            The DHCP option code for this option is 32.
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="nis_server">
> +          <p>
> +            The DHCP option code for this option is 41.
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="ntp_server">
> +          <p>
> +            The DHCP option code for this option is 42.
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="server_id">
> +          <p>
> +            The DHCP option code for this option is 54.
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="tftp_server">
> +          <p>
> +            The DHCP option code for this option is 66.
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="classless_static_route">
> +          <p>
> +            The DHCP option code for this option is 121.
> +          </p>
> +
> +          <p>
> +             This option can contain one or more static routes, each of which
> +             consists of a destination descriptor and the IP address of the
> +             router that should be used to reach that destination. Please see
> +             RFC 3442 for more details.
> +          </p>
> +
> +          <p>
> +            Example.
> +            key="classless_static_route"
> +            value="{30.0.0.0/24,10.0.0.10, 0.0.0.0/0,10.0.0.1}"
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="ms_classless_static_route">
> +          <p>
> +            The DHCP option code for this option is 249. This option is
> +            similar to <code>classless_static_route</code> supported by
> +            Microsoft Windows DHCP clients.
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="server_mac">
> +          <p>
> +            <code>eth.src</code> will be set to this value in the DHCP
> +            response packet.
> +          </p>
> +        </column>
> +      </group>
> +
> +      <group title="Other supported DHCP options">
> +        <column name="dhcp_options" key="ip_forward_enable">
> +          <p>
> +            The DHCP option code for this option is 19.
> +          </p>
> +
> +          <p>
> +            The value of this DHCP option is of type <code>bool</code>.
> +
> +            Example. key="ip_forward_enable", value="1"
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="router_discovery">
> +          <p>
> +            The DHCP option code for this option is 31.
> +          </p>
> +
> +          <p>
> +            The value of this DHCP option is of type <code>bool</code>.
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="ethernet_encap">
> +          <p>
> +            The DHCP option code for this option is 36.
> +          </p>
> +
> +          <p>
> +            The value of this DHCP option is of type <code>bool</code>.
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="default_ttl">
> +          <p>
> +            The DHCP option code for this option is 23.
> +          </p>
> +
> +          <p>
> +            The value of this DHCP option is of type <code>uint8</code>.
> +
> +            Example. key="default_ttl", value="128".
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="tcp_ttl">
> +          <p>
> +            The DHCP option code for this option is 37.
> +          </p>
> +
> +          <p>
> +            The value of this DHCP option is of type <code>uint8</code>.
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="mtu">
> +          <p>
> +            The DHCP option code for this option is 26.
> +          </p>
> +
> +          <p>
> +            The value of this DHCP option is of type <code>uint16</code>.
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="lease_time">
> +          <p>
> +            The DHCP option code for this option is 51.
> +          </p>
> +
> +          <p>
> +            The value of this DHCP option is of type <code>uint32</code>.
> +
> +            Example. key="lease_time", value="42000"
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="T1">
> +          <p>
> +            The DHCP option code for this option is 58.
> +          </p>
> +
> +          <p>
> +            The value of this DHCP option is of type <code>uint32</code>.
> +
> +            Example. key="T1", value="30000"
> +          </p>
> +        </column>
> +
> +        <column name="dhcp_options" key="T2">
> +          <p>
> +            The DHCP option code for this option is 59.
> +          </p>
> +
> +          <p>
> +            The value of this DHCP option is of type <code>uint32</code>.
> +
> +            Example. key="T2", value="40000"
> +          </p>
> +        </column>
> +      </group>
> +
> +      <group title="Mandatory DHCP options">
> +        <p>
> +          DHCP options <code>"server_id"</code>, <code>"server_mac"</code>
> +          and <code>"lease_time"</code> are mandatory options which CMS 
> should
> +          define for <code>OVN</code> to support native DHCP.
> +        </p>
> +      </group>
> +    </group>
> +  </table>
> +
>    <table name="Logical_Router" title="L3 logical router">
>      <p>
>        Each row represents one L3 logical router.
> @@ -637,7 +949,7 @@
>        column is set to <code>false</code>, the router is disabled.  A 
> disabled
>        router has all ingress and egress traffic dropped.
>      </column>
> -
> +
>      <group title="Common Columns">
>        <column name="external_ids">
>          See <em>External IDs</em> at the beginning of this document.
> diff --git a/ovn/utilities/ovn-nbctl.8.xml b/ovn/utilities/ovn-nbctl.8.xml
> index 8375ab7..408badb 100644
> --- a/ovn/utilities/ovn-nbctl.8.xml
> +++ b/ovn/utilities/ovn-nbctl.8.xml
> @@ -64,6 +64,35 @@
>        </dd>
>      </dl>
>
> +    <h1>Subnet Commands</h1>
> +
> +    <dl>
> +      <dt><code>subnet-add</code> <var>lswitch</var> <var>cidr</var> 
> <var>gateway_ip</var> [<var>enable_dhcp</var>]</dt>
> +      <dd>
> +        Adds a Subnet to the <var>lswitch</var>
> +      </dd>
> +
> +      <dt><code>subnet-del</code> <var>subnet</var></dt>
> +      <dd>
> +        Deletes the subnet referred by <var>subnet</var> UUID.
> +      </dd>
> +
> +      <dt><code>subnet-list</code> <var>lswitch</var></dt>
> +      <dd>
> +        Lists all the subnets belonging to the <var>lswitch</var>
> +      </dd>
> +
> +      <dt><code>subnet-set-dhcp-options</code> <var>subnet</var> 
> [<var>key=value</var>]...</dt>
> +      <dd>
> +        Sets the DHCP options for the <var>subnet</var>
> +      </dd>
> +
> +      <dt><code>subnet-get-dhcp-options</code> <var>subnet</var></dt>
> +      <dd>
> +        Lists the DHCP options of the <var>subnet</var>
> +      </dd>
> +    </dl>
> +
>      <h1>ACL Commands</h1>
>      <dl>
>        <dt>[<code>--log</code>] <code>acl-add</code> <var>lswitch</var> 
> <var>direction</var> <var>priority</var> <var>match</var> 
> <var>action</var></dt>
> diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
> index d267829..0887961 100644
> --- a/ovn/utilities/ovn-nbctl.c
> +++ b/ovn/utilities/ovn-nbctl.c
> @@ -333,6 +333,18 @@ Logical port commands:\n\
>                              Set options related to the type of LPORT\n\
>    lport-get-options LPORT   Get the type specific options for LPORT\n\
>  \n\
> +Subnet commands:\n\
> +  subnet-add LSWITCH CIDR GATEWAYIP [ENABLE_DHCP]\n\
> +                            add a subnet to LSWITCH\n\
> +  subnet-del SUBNET_UUID\n\
> +                            remove subnet from its attached LSWITCH\n\
> +  subnet-list LSWITCH\n\
> +                            list subnets attached to LSWITCH\n\
> +  subnet-set-dhcp-options SUBNET_UUID KEY=VALUE [KEY=VALUE]...\n\
> +                            Set DHCP options for the SUBNET\n\
> +  subnet-get-dhcp-options SUBNET_UUID\n\
> +                            Get the DHCP options for the SUBNET\n\
> +\n\
>  %s\
>  \n\
>  Options:\n\
> @@ -502,6 +514,7 @@ nbctl_lswitch_list(struct ctl_context *ctx)
>      smap_destroy(&lswitches);
>      free(nodes);
>  }
> +
>
>  static const struct nbrec_logical_port *
>  lport_by_name_or_uuid(struct ctl_context *ctx, const char *id,
> @@ -922,6 +935,175 @@ nbctl_lport_get_options(struct ctl_context *ctx)
>      }
>  }
>
> +static void
> +nbctl_subnet_add(struct ctl_context *ctx)
> +{
> +    const struct nbrec_logical_switch *lswitch;
> +    lswitch = lswitch_by_name_or_uuid(ctx, ctx->argv[1], true);
> +
> +    /* Validate the cidr */
> +    int64_t ip_version = 4;
> +    ovs_be32 ip;
> +    unsigned int plen;
> +    char *error = ip_parse_cidr(ctx->argv[2], &ip, &plen);
> +    if (error){
> +        /* check if its IPv6 cidr */
> +        free(error);
> +        struct in6_addr ipv6;
> +        error = ipv6_parse_cidr(ctx->argv[2], &ipv6, &plen);
> +        if (error) {
> +            free(error);
> +            VLOG_WARN("Invalid cidr format '%s'", ctx->argv[2]);
> +            return;
> +        }
> +        ip_version = 6;
> +    }
> +
> +    /* Validate the gateway ip format */
> +    bool valid_gatewap_ip = false;
> +    if (ip_version == 4) {
> +        valid_gatewap_ip = ip_parse(ctx->argv[3], &ip);
> +    }
> +    else {
> +        struct in6_addr ipv6;
> +        valid_gatewap_ip = ipv6_parse(ctx->argv[3], &ipv6);
> +    }
> +
> +    if (!valid_gatewap_ip) {
> +        VLOG_WARN("Invalid Gateway ip format '%s'", ctx->argv[3]);
> +        return;
> +    }
> +
> +    bool enable_dhcp = true;
> +    if (ctx->argc == 5) {
> +         if (!strcmp(ctx->argv[4], "false")) {
> +             enable_dhcp = false;
> +         }
> +    }
> +
> +    struct nbrec_subnet *subnet = nbrec_subnet_insert(ctx->txn);
> +    nbrec_subnet_set_cidr(subnet, ctx->argv[2]);
> +    nbrec_subnet_set_ip_version(subnet, ip_version);
> +    nbrec_subnet_set_gateway_ip(subnet, ctx->argv[3]);
> +    nbrec_subnet_set_enable_dhcp(subnet, enable_dhcp);
> +
> +    /* Insert the subnet into the logical switch */
> +    nbrec_logical_switch_verify_subnets(lswitch);
> +    struct nbrec_subnet **new_subnets = xmalloc(sizeof *new_subnets *
> +                                                (lswitch->n_subnets + 1));
> +    memcpy(new_subnets, lswitch->subnets,
> +           sizeof *new_subnets * lswitch->n_subnets);
> +    new_subnets[lswitch->n_subnets] = subnet;
> +    nbrec_logical_switch_set_subnets(lswitch, new_subnets,
> +                                     lswitch->n_subnets + 1);
> +    free(new_subnets);
> +}
> +
> +static void
> +remove_subnet(const struct nbrec_logical_switch *lswitch, size_t idx)
> +{
> +    const struct nbrec_subnet *subnet = lswitch->subnets[idx];
> +
> +    struct nbrec_subnet **new_subnets
> +        = xmemdup(lswitch->subnets, sizeof *new_subnets * 
> lswitch->n_subnets);
> +    new_subnets[idx] = new_subnets[lswitch->n_subnets - 1];
> +    nbrec_logical_switch_verify_subnets(lswitch);
> +    nbrec_logical_switch_set_subnets(lswitch, new_subnets, 
> lswitch->n_subnets - 1);
> +    free(new_subnets);
> +
> +    nbrec_subnet_delete(subnet);
> +}
> +
> +static const struct nbrec_subnet *
> +subnet_get(struct ctl_context *ctx, char *id)
> +{
> +    struct uuid subnet_uuid;
> +    if (uuid_from_string(&subnet_uuid, id)) {
> +        return nbrec_subnet_get_for_uuid(ctx->idl, &subnet_uuid);
> +    }
> +
> +    return NULL;
> +}
> +
> +static void
> +nbctl_subnet_del(struct ctl_context *ctx)
> +{
> +    const struct nbrec_subnet *subnet = subnet_get(ctx, ctx->argv[1]);
> +    if (!subnet) {
> +        VLOG_WARN("subnet not found for '%s'", ctx->argv[1]);
> +        return;
> +    }
> +
> +    /* Find the switch that contains 'subnet', then delete it. */
> +    const struct nbrec_logical_switch *lswitch;
> +    NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, ctx->idl) {
> +        for (size_t i = 0; i < lswitch->n_subnets; i++) {
> +            if (lswitch->subnets[i] == subnet) {
> +                remove_subnet(lswitch, i);
> +                return;
> +            }
> +        }
> +    }
> +
> +    VLOG_WARN("subnet %s is not part of any logical switch",
> +              ctx->argv[1]);
> +}
> +
> +static void
> +nbctl_subnet_list(struct ctl_context *ctx)
> +{
> +    const struct nbrec_logical_switch *lswitch;
> +    lswitch = lswitch_by_name_or_uuid(ctx, ctx->argv[1], false);
> +    if (!lswitch) {
> +        return;
> +    }
> +
> +    for (size_t i = 0; i < lswitch->n_subnets; i++) {
> +        const struct nbrec_subnet *subnet = lswitch->subnets[i];
> +        ds_put_format(&ctx->output, UUID_FMT " (%s)\n",
> +                      UUID_ARGS(&subnet->header_.uuid), subnet->cidr);
> +    }
> +}
> +
> +static void
> +nbctl_subnet_set_dhcp_options(struct ctl_context *ctx)
> +{
> +    const struct nbrec_subnet *subnet = subnet_get(ctx, ctx->argv[1]);
> +    if (!subnet) {
> +        VLOG_WARN("subnet not found for '%s'", ctx->argv[1]);
> +        return;
> +    }
> +
> +    struct smap dhcp_options = SMAP_INITIALIZER(&dhcp_options);
> +    for (size_t i = 2; i < ctx->argc; i++) {
> +        char *key, *value;
> +        value = xstrdup(ctx->argv[i]);
> +        key = strsep(&value, "=");
> +        if (value) {
> +            smap_add(&dhcp_options, key, value);
> +        }
> +        free(key);
> +    }
> +
> +    nbrec_subnet_set_dhcp_options(subnet, &dhcp_options);
> +    smap_destroy(&dhcp_options);
> +}
> +
> +static void
> +nbctl_subnet_get_dhcp_options(struct ctl_context *ctx)
> +{
> +    const struct nbrec_subnet *subnet = subnet_get(ctx, ctx->argv[1]);
> +    if (!subnet) {
> +        VLOG_WARN("subnet not found for '%s'", ctx->argv[1]);
> +        return;
> +    }
> +
> +    struct smap_node *node;
> +    SMAP_FOR_EACH(node, &subnet->dhcp_options) {
> +        ds_put_format(&ctx->output, "%s=%s\n", node->key, node->value);
> +    }
> +}
> +
>  enum {
>      DIR_FROM_LPORT,
>      DIR_TO_LPORT
> @@ -1120,6 +1302,10 @@ static const struct ctl_table_class tables[] = {
>       {{NULL, NULL, NULL},
>        {NULL, NULL, NULL}}},
>
> +    {&nbrec_table_subnet,
> +     {{&nbrec_table_subnet, NULL, NULL},
> +     {NULL, NULL, NULL}}},
> +
>      {&nbrec_table_logical_router,
>       {{&nbrec_table_logical_router, &nbrec_logical_router_col_name, NULL},
>        {NULL, NULL, NULL}}},
> @@ -1386,6 +1572,16 @@ static const struct ctl_command_syntax 
> nbctl_commands[] = {
>      { "lport-get-options", 1, 1, "LPORT", NULL, nbctl_lport_get_options, 
> NULL,
>        "", RO },
>
> +    /* subnet commands */
> +    {"subnet-add", 3, 4, "LSWITCH CIDR GATEWAY_IP ENABLE_DHCP", NULL,
> +      nbctl_subnet_add, NULL, "", RW },
> +    {"subnet-del", 1, 1, "SUBNET", NULL, nbctl_subnet_del, NULL, "", RW},
> +    {"subnet-list", 1, 1, "LSWITCH", NULL, nbctl_subnet_list, NULL, "", RO},
> +    {"subnet-set-dhcp-options", 1, INT_MAX, "SUBNET KEY=VALUE 
> [KEY=VALUE]...",
> +      NULL, nbctl_subnet_set_dhcp_options, NULL, "", RW },
> +    {"subnet-get-dhcp-options", 1, 1, "SUBNET", NULL,
> +      nbctl_subnet_get_dhcp_options, NULL, "", RO },
> +
>      {NULL, 0, 0, NULL, NULL, NULL, NULL, "", RO},
>  };
>
> diff --git a/tests/ovn.at b/tests/ovn.at
> index b5f33ef..e9877e7 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -2663,3 +2663,253 @@ AT_CHECK([ovstest test-ovn-put-dhcp-opts-action reg0 
> $dhcp_options $expected_dhc
>           [1], [ignore])
>
>  AT_CLEANUP
> +
> +AT_SETUP([ovn -- dhcp : 1 HV, 2 LS, 3 lports])
> +AT_KEYWORDS([dhcp])
> +AT_SKIP_IF([test $HAVE_PYTHON = no])
> +ovn_start
> +
> +ovn-nbctl lswitch-add ls1
> +ovn-nbctl lswitch-add ls2
> +
> +ovn-nbctl -- --id=@s1 create Subnet cidr=10.0.0.0/24 gateway_ip=10.0.0.1 \
> +ip_version=4 enable_dhcp=true dhcp_options="\"server_id\"=\"10.0.0.1\" \
> +\"server_mac\"=\"00:00:00:10:00:01\" \"lease_time\"=\"36\"" \
> +-- add Logical_Switch ls1 subnets @s1
> +
> +ovn-nbctl lport-add ls1 ls1-lp1 \
> +-- lport-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4"
> +
> +ovn-nbctl lport-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4"
> +
> +ovn-nbctl lport-add ls2 ls2-lp1 \
> +-- lport-set-addresses ls2-lp1 "f0:00:00:00:00:02 20.0.0.3"
> +
> +ovn-nbctl lport-set-port-security ls2-lp1 "f0:00:00:00:00:02 20.0.0.3"
> +
> +ovn-nbctl lport-add ls1 ls1-lp2 \
> +-- lport-set-addresses ls1-lp2 "f0:00:00:00:00:03 10.0.0.5"
> +
> +ovn-nbctl lport-set-port-security ls1-lp2 "f0:00:00:00:00:03 10.0.0.5"
> +#disable dhcp on lport - ls1-lp2
> +ovn-nbctl lport-set-options ls1-lp2 "dhcp_disabled=true"
> +
> +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=ls2-lp1 \
> +    options:tx_pcap=hv1/vif2-tx.pcap \
> +    options:rxq_pcap=hv1/vif2-rx.pcap \
> +    ofport-request=2
> +
> +ovs-vsctl -- add-port br-int hv1-vif3 -- \
> +    set interface hv1-vif3 external-ids:iface-id=ls1-lp2 \
> +    options:tx_pcap=hv1/vif3-tx.pcap \
> +    options:rxq_pcap=hv1/vif3-rx.pcap \
> +    ofport-request=3
> +
> +ovn_populate_arp
> +
> +sleep 2
> +
> +# This shell function sends a DHCP request packet
> +# test_dhcp INPORT SRC_MAC DHCP_TYPE OUTPORT...
> +# The OUTPORTs (zero or more) list the VIFs on which the original DHCP
> +# packet should be received twice (one from ovn-controller and the other
> +# from the "ovs-ofctl monitor br-int resume"
> +test_dhcp() {
> +    local inport=$1 src_mac=$2 dhcp_type=$3
> +    local 
> request=ffffffffffff${src_mac}080045100110000000008011000000000000ffffffff
> +    # udp header and dhcp header
> +    request+=0044004300fc0000
> +    
> request+=010106006359aa760000000000000000000000000000000000000000${src_mac}
> +    # client hardware padding
> +    request+=00000000000000000000
> +    # server hostname
> +    request+=0000000000000000000000000000000000000000000000000000000000000000
> +    request+=0000000000000000000000000000000000000000000000000000000000000000
> +    # boot file name
> +    request+=0000000000000000000000000000000000000000000000000000000000000000
> +    request+=0000000000000000000000000000000000000000000000000000000000000000
> +    request+=0000000000000000000000000000000000000000000000000000000000000000
> +    request+=0000000000000000000000000000000000000000000000000000000000000000
> +    # dhcp magic cookie
> +    request+=63825363
> +    # dhcp message type
> +    request+=3501${dhcp_type}ff
> +    shift; shift; shift;
> +    for outport; do
> +        # the packet will be received twice, one from ovn-controller
> +        # and the other from ovs-ofctl monitor br-int resume
> +        echo $request | trim_zeros >> $outport.expected
> +        echo $request | trim_zeros >> $outport.expected
> +    done
> +    as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $request
> +}
> +
> +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
> +}
> +
> +trim_zeros() {
> +    sed 's/\(00\)\{1,\}$//'
> +}
> +
> +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
> +
> +echo "---------NB dump-----"
> +ovn-nbctl show
> +echo "---------------------"
> +echo "---------SB dump-----"
> +ovn-sbctl list datapath_binding
> +echo "---------------------"
> +ovn-sbctl list logical_flow
> +echo "---------------------"
> +
> +echo "---------------------"
> +ovn-sbctl dump-flows
> +echo "---------------------"
> +
> +echo "------ hv1 dump ----------"
> +as hv1 ovs-ofctl dump-flows br-int
> +
> +#send DHCP DISCOVER
> +test_dhcp 1 f00000000001 01
> +
> +# Wait for the expected number of NXT_RESUMEs to be logged.
> +echo "waiting for 1 NXT_RESUMEs..."
> +OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> +
> +# Extract the dhcp options in userdata. It will be in the format
> +# ' userdata=00.00.00.02.00.00.00.00.00.00.00.00.0a.00.00.04.01.04.ff......'
> +# Skip 59 columns - userdata=<DHCP_ACTION_CODE (8 B)><REG-IDX (4B)><OFFER-IP 
> (4 B)>
> +dhcp_opts_in_user_data=`cat ofctl_monitor0.log  | grep userdata | cut -c 59-`
> +
> +AT_CHECK([ovstest test-ovn-dhcp hv1/vif1-tx.pcap 10.0.0.4 10.0.0.1 \
> +          00:00:00:10:00:01 1 $dhcp_opts_in_user_data], [0], [ignore])
> +
> +# ovs-ofctl also resumes the packets and this causes other ports to receive
> +# the DHCP request packet. So reset the pcap files so that its easier
> +# for "test-ovn-dhcp" to validate the DHCP reply packet
> +reset_pcap_file hv1-vif1 hv1/vif1
> +reset_pcap_file hv1-vif2 hv1/vif2
> +reset_pcap_file hv1-vif3 hv1/vif3
> +
> +# run dhcp test on ls2-lp1. There are no dhcp options defined. So the dhcp
> +# packet should not be handled by ovn-controller
> +test_dhcp 2 f00000000002 01
> +
> +# NXT_RESUMEs should be 1.
> +echo "waiting for 1 NXT_RESUMEs..."
> +cat ofctl_monitor*.log
> +echo "###########################################"
> +
> +OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> +
> +ovn-nbctl subnet-add ls2 20.0.0.0/24 20.0.0.1 true
> +subnet_id=`ovn-nbctl subnet-list ls2 | cut -c -37`
> +
> +ovn-nbctl subnet-set-dhcp-options $subnet_id "server_id=20.0.0.1" \
> +"lease_time=4500"
> +
> +sleep 1
> +
> +# there is no "server_mac" field in the dhcp options. So there should be no
> +# dhcp flow for this port
> +OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | \
> +grep -c "actions=controller(userdata=00.00.00.02.00.00.00"`])
> +
> +ovn-nbctl subnet-set-dhcp-options $subnet_id "server_id=20.0.0.1" \
> +"server_mac=00:00:00:10:00:02" "lease_time=4500"
> +
> +sleep 2
> +OVS_WAIT_UNTIL([test 2 = `as hv1 ovs-ofctl dump-flows br-int | \
> +grep -c "actions=controller(userdata=00.00.00.02.00.00.00"`])
> +
> +test_dhcp 2 f00000000002 03
> +
> +# NXT_RESUMEs should be 2.
> +echo "waiting for 2 NXT_RESUMEs..."
> +OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> +
> +dhcp_opts_in_user_data=`cat ofctl_monitor0.log  | grep userdata | \
> +cut -c 59- | sed '1d'`
> +
> +AT_CHECK([ovstest test-ovn-dhcp hv1/vif2-tx.pcap 20.0.0.3 20.0.0.1 \
> +          00:00:00:10:00:02 3 $dhcp_opts_in_user_data], [0], [ignore])
> +
> +reset_pcap_file hv1-vif1 hv1/vif1
> +reset_pcap_file hv1-vif2 hv1/vif2
> +reset_pcap_file hv1-vif3 hv1/vif3
> +
> +#enable dhcp on lport - ls1-lp2
> +ovn-nbctl lport-set-options ls1-lp2 "dhcp_disabled=false"
> +
> +sleep 2
> +
> +OVS_WAIT_UNTIL([test 3 = `as hv1 ovs-ofctl dump-flows br-int | \
> +grep -c "actions=controller(userdata=00.00.00.02.00.00.00"`])
> +
> +#send invalid dhcp packet on hv1/vif3 (ie ls1-lp3).
> +# This packet should be received by hv1/vif1 (ls1-lp1)
> +test_dhcp 3 f00000000003 04 1
> +
> +# NXT_RESUMEs should be 3.
> +echo "waiting for 3 NXT_RESUMEs..."
> +OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> +
> +# vif3-tx.pcap should not have received the DHCP reply packet
> +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap | trim_zeros > 
> 3.packets
> +AT_CHECK([cat 3.packets], [0], [])
> +
> +# vif1-tx.pcap should have received the DHCP (invalid) request packet
> +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | trim_zeros > 
> 1.packets
> +cat 1.expected > expout
> +AT_CHECK([cat 1.packets], [0], [expout])
> +
> +
> +echo "---------NB dump-----"
> +ovn-nbctl show
> +echo "---------SB dump-----"
> +ovn-sbctl list datapath_binding
> +echo "---------------------"
> +ovn-sbctl list logical_flow
> +ovn-sbctl list port_binding
> +echo "---------------------"
> +ovn-sbctl dump-flows
> +echo "---------------------"
> +
> +echo "------ hv1 dump ----------"
> +as hv1 ovs-ofctl dump-flows br-int
> +
> +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
> diff --git a/tests/test-ovn-dhcp.c b/tests/test-ovn-dhcp.c
> index 79cee08..b879a03 100644
> --- a/tests/test-ovn-dhcp.c
> +++ b/tests/test-ovn-dhcp.c
> @@ -16,6 +16,10 @@
>  #include <assert.h>
>  #include <config.h>
>  #include "command-line.h"
> +#include "dp-packet.h"
> +#include <errno.h>
> +#include "flow.h"
> +#include "lib/dhcp.h"
>  #include "openvswitch/ofp-actions.h"
>  #include "ovstest.h"
>  #include "ovn/lib/actions.h"
> @@ -23,6 +27,7 @@
>  #include "ovn/lib/expr.h"
>  #include "ovn/lib/logical-fields.h"
>  #include "shash.h"
> +#include "pcap-file.h"
>
>  static void
>  add_logical_register(struct shash *symtab, enum mf_field_id id)
> @@ -146,4 +151,134 @@ test_put_dhcp_opts_action(int argc OVS_UNUSED, char 
> *argv[] OVS_UNUSED)
>      exit(0);
>  }
>
> +static void
> +test_ovn_dhcp_main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
> +{
> +    if (argc != 7) {
> +        printf("Usage: %s pcap-file offer-ip server-ip"
> +                " server-mac dhcp-type userdata\n", argv[0]);
> +        exit(1);
> +    }
> +
> +    int retval = 1;
> +    FILE *pcap;
> +
> +    set_program_name(argv[0]);
> +
> +    pcap = fopen(argv[1], "rb");
> +    if (!pcap) {
> +        ovs_fatal(errno, "failed to open %s", argv[1]);
> +    }
> +
> +    retval = ovs_pcap_read_header(pcap);
> +    if (retval) {
> +        ovs_fatal(retval > 0 ? retval : 0, "reading pcap header failed");
> +    }
> +
> +    /* verify if the offer-ip is in proper format */
> +    ovs_be32 expected_offer_ip;
> +    if (!ovs_scan(argv[2], IP_SCAN_FMT, IP_SCAN_ARGS(&expected_offer_ip))) {
> +        ovs_fatal(1, "invalid expected offer ip");
> +    }
> +
> +    /* verify if the server-ip is in proper format */
> +    ovs_be32 server_ip;
> +    if (!ovs_scan(argv[3], IP_SCAN_FMT, IP_SCAN_ARGS(&server_ip))) {
> +        ovs_fatal(1, "invalid expected server ip");
> +    }
> +
> +    struct eth_addr server_mac;
> +    if (!eth_addr_from_string(argv[4], &server_mac)) {
> +        ovs_fatal(1, "invalid expected server mac");
> +    }
> +
> +    struct dp_packet *packet = NULL;
> +    retval = ovs_pcap_read(pcap, &packet, NULL);
> +    if (retval == EOF) {
> +        ovs_fatal(0, "unexpected end of file reading pcap file : [%s]\n",
> +                  argv[1]);
> +    } else if (retval) {
> +        ovs_fatal(retval, "error reading pcap file");
> +    }
> +
> +    int exit_code = 1;
> +    struct flow flow;
> +    flow_extract(packet, &flow);
> +
> +    struct dhcp_header const *dhcp_data = dp_packet_get_udp_payload(packet);
> +    if (dhcp_data->op != (uint8_t)2) {
> +        printf("Invalid dhcp op reply code : %d\n", dhcp_data->op);
> +        goto exit;
> +    }
> +
> +    if (flow.tp_src != htons(DHCP_SERVER_PORT) &&
> +        flow.tp_dst != htons(DHCP_CLIENT_PORT)) {
> +        printf("Error. Not a dhcp response packet \n");
> +        goto exit;
> +    }
> +
> +    if (flow.nw_dst != expected_offer_ip) {
> +        printf("Error. Offered ip : "IP_FMT " : Expected ip : %s\n",
> +        IP_ARGS(flow.nw_dst), argv[2]);
> +        goto exit;
> +    }
> +
> +    if(dhcp_data->yiaddr != expected_offer_ip) {
> +        printf("Error. Offered yiaddr : "IP_FMT " : Expected ip : %s\n",
> +        IP_ARGS(dhcp_data->yiaddr), argv[2]);
> +        goto exit;
> +    }
> +
> +    /* Verify the dhcp option cookie */
> +    uint8_t const *footer = (uint8_t *)dhcp_data + sizeof(*dhcp_data);
> +    ovs_be32 dhcp_cookie = htonl(DHCP_MAGIC_COOKIE);
> +    if (memcmp(footer, &dhcp_cookie, sizeof(ovs_be32))) {
> +        printf("Error. Invalid dhcp magic cookie\n");
> +        goto exit;
> +    }
> +
> +    /* Validate userdata. It should be ASCII hex */
> +    uint64_t dhcp_opts_stub[1024 / 8];
> +    struct ofpbuf dhcp_opts = OFPBUF_STUB_INITIALIZER(dhcp_opts_stub);
> +    if (atoi(argv[5]) == 1) {
> +        /* DHCP reply type should be OFFER (02) */
> +        ofpbuf_put_hex(&dhcp_opts, "350102", NULL);
> +    } else {
> +        /* DHCP reply type should be ACK (05) */
> +        ofpbuf_put_hex(&dhcp_opts, "350105", NULL);
> +    }
> +
> +    if (ofpbuf_put_hex(&dhcp_opts, argv[6], NULL)[0] != '\0') {
> +        printf("Error. Invalid userdata\n");
> +        goto exit;
> +    }
> +
> +    /* 4 bytes padding, 1 byte FF and 4 bytes padding */
> +    ofpbuf_put_hex(&dhcp_opts, "00000000FF00000000", NULL);
> +
> +    footer += sizeof(uint32_t);
> +
> +    size_t dhcp_opts_size = (const char *)dp_packet_tail(packet) - (
> +        const char *)footer;
> +    if (dhcp_opts_size != dhcp_opts.size) {
> +        printf("Error. dhcp options size mismatch\n");
> +        goto exit;
> +    }
> +
> +    if (memcmp(footer, dhcp_opts.data, dhcp_opts.size)) {
> +        printf("Error. Invalid dhcp options present\n");
> +        goto exit;
> +    }
> +
> +    exit_code = 0;
> +
> +exit:
> +    fclose(pcap);
> +    if (packet) {
> +        dp_packet_delete(packet);
> +    }
> +    exit(exit_code);
> +}
> +
>  OVSTEST_REGISTER("test-ovn-put-dhcp-opts-action", test_put_dhcp_opts_action);
> +OVSTEST_REGISTER("test-ovn-dhcp", test_ovn_dhcp_main);
> --
> 2.5.5
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> http://openvswitch.org/mailman/listinfo/dev
_______________________________________________
dev mailing list
dev@openvswitch.org
http://openvswitch.org/mailman/listinfo/dev

Reply via email to