diff mbox series

[ovs-dev,v2] dhcp: add iPXE support to OVN

Message ID e9895072116c63204650ae44a35018b9352f4a3b.1602586513.git.lorenzo.bianconi@redhat.com
State Changes Requested
Headers show
Series [ovs-dev,v2] dhcp: add iPXE support to OVN | expand

Commit Message

Lorenzo Bianconi Oct. 13, 2020, 11:14 a.m. UTC
Introduce the possibility to specify more than 255 options in
put_dhcp_opts userdata buffer. This is useful to support conditional
options for the DHCP reply. (e.g option 67 used in iPXE protocol).
Express the option code using 2 bytes instead of just one in userdata
buffer. Moreover, parse userdata buffer in pinctrl_handle_put_dhcp_opts
in order to distinguish between 'optional' and 'fixed' options.

Add iPXE support to OVN introducing "bootfile_name_alt" dhcp option.
When both "bootfile_name" and "bootfile_name_alt" are provided
by the CMS, "bootfile_name" will be used for option 67 if the
dhcp request contains etherboot option (175), otherwise
"bootfile_name_alt" will be used.

Tested-by: Lucas Alvares Gomes Martins <lmartins@redhat.com>
Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
---
 controller/pinctrl.c |  56 +++++++++++++++++---
 lib/actions.c        |  28 +++++-----
 lib/ovn-l7.h         |  11 ++++
 northd/ovn-northd.c  |   1 +
 ovn-nb.xml           |  10 ++++
 ovn-sb.ovsschema     |   6 +--
 tests/ovn.at         | 118 ++++++++++++++++++++++++++++++-------------
 tests/test-ovn.c     |   1 +
 8 files changed, 171 insertions(+), 60 deletions(-)

Comments

Numan Siddique Oct. 22, 2020, 12:11 p.m. UTC | #1
On Tue, Oct 13, 2020 at 4:45 PM Lorenzo Bianconi
<lorenzo.bianconi@redhat.com> wrote:
>
> Introduce the possibility to specify more than 255 options in
> put_dhcp_opts userdata buffer. This is useful to support conditional
> options for the DHCP reply. (e.g option 67 used in iPXE protocol).
> Express the option code using 2 bytes instead of just one in userdata
> buffer. Moreover, parse userdata buffer in pinctrl_handle_put_dhcp_opts
> in order to distinguish between 'optional' and 'fixed' options.
>
> Add iPXE support to OVN introducing "bootfile_name_alt" dhcp option.
> When both "bootfile_name" and "bootfile_name_alt" are provided
> by the CMS, "bootfile_name" will be used for option 67 if the
> dhcp request contains etherboot option (175), otherwise
> "bootfile_name_alt" will be used.
>
> Tested-by: Lucas Alvares Gomes Martins <lmartins@redhat.com>
> Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>

Hi Lorenzo,

Thanks for the patch. If I understand correctly, you are encoding all
the dhcp codes as 2 bytes in the controller userdata
buffer right ? So that you can encode the  bootfile_alt option as 300 ?

The drawback of this approach is that it increases the userdata buffer
a bit and pinctrl thread needs to reencode the dhcp options
to the standard format - i.e 1 byte code, 1 byte length.

I have another approach.
* We can assign a private unused dhcp option code for bootfile_alt -
may be a code between 224 and 254 -
https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml
   Since the chosen option code will not be part of the actual dhcp
reply, I think it should be fine.

* We can encode the bootfile_name (67) and bootfile_name_alt (let's
say we chose 224) soon after the offer_ip in the userdata buffer (in
actions.c) and then encode the rest of the options.

* When pinctrl thread has to send the dhcp reply, it will check if 67
and 224 are present in the userdata buffer. It can easily check since
those will be encoded after offer_ip.

* If the dhcp request has etherboot option, it can put the
bootfile_name in option 67, else it can put bootfile_name_alt in
option 67.

* It can then just blindly copy the rest of the userdata buffer in the
reply like we do now.

What do you think ?

Thanks
Numan


> ---
>  controller/pinctrl.c |  56 +++++++++++++++++---
>  lib/actions.c        |  28 +++++-----
>  lib/ovn-l7.h         |  11 ++++
>  northd/ovn-northd.c  |   1 +
>  ovn-nb.xml           |  10 ++++
>  ovn-sb.ovsschema     |   6 +--
>  tests/ovn.at         | 118 ++++++++++++++++++++++++++++++-------------
>  tests/test-ovn.c     |   1 +
>  8 files changed, 171 insertions(+), 60 deletions(-)
>
> diff --git a/controller/pinctrl.c b/controller/pinctrl.c
> index 0a7020533..260184566 100644
> --- a/controller/pinctrl.c
> +++ b/controller/pinctrl.c
> @@ -1766,6 +1766,7 @@ pinctrl_handle_put_dhcp_opts(
>      }
>      in_dhcp_ptr += sizeof magic_cookie;
>
> +    bool ipxe_req = false;
>      const uint8_t *in_dhcp_msg_type = NULL;
>      ovs_be32 request_ip = in_dhcp_data->ciaddr;
>      while (in_dhcp_ptr < end) {
> @@ -1798,6 +1799,9 @@ pinctrl_handle_put_dhcp_opts(
>                  request_ip = get_unaligned_be32(DHCP_OPT_PAYLOAD(in_dhcp_opt));
>              }
>              break;
> +        case DHCP_OPT_ETHERBOOT:
> +            ipxe_req = true;
> +            break;
>          default:
>              break;
>          }
> @@ -1855,8 +1859,8 @@ pinctrl_handle_put_dhcp_opts(
>
>          reply_dhcp_opts_ptr = dhcp_inform_reply_buf;
>          while (in_dhcp_ptr < end) {
> -            const struct dhcp_opt_header *in_dhcp_opt =
> -                (const struct dhcp_opt_header *)in_dhcp_ptr;
> +            const struct dhcp_opt_userdata_hdr *in_dhcp_opt =
> +                (const struct dhcp_opt_userdata_hdr *)in_dhcp_ptr;
>
>              switch (in_dhcp_opt->code) {
>              case OVN_DHCP_OPT_CODE_NETMASK:
> @@ -1916,9 +1920,47 @@ pinctrl_handle_put_dhcp_opts(
>       *| 4 Bytes padding | 1 Byte (option end 0xFF ) | 4 Bytes padding|
>       * --------------------------------------------------------------
>       */
> +    /* compute opt len */
> +    unsigned char *boot_filename_alt = NULL, *boot_filname_opt_ptr = NULL;
> +    unsigned char *ptr = (unsigned char *)reply_dhcp_opts_ptr->data;
> +    uint16_t boot_filename_len = 0, boot_filename_alt_len = 0;
> +    unsigned char *ptr_end = ptr + reply_dhcp_opts_ptr->size;
> +    unsigned char *buf = xmalloc(reply_dhcp_opts_ptr->size);
> +    uint16_t opt_len = 0;
> +
> +    while (ptr < ptr_end) {
> +        const struct dhcp_opt_userdata_hdr *in_dhcp_opt =
> +            (const struct dhcp_opt_userdata_hdr *)ptr;
> +
> +        switch (in_dhcp_opt->code) {
> +        case 300: /* DHCP_OPT_BOOTFILE_ALT */
> +          boot_filename_alt = ptr + sizeof *in_dhcp_opt;
> +          boot_filename_alt_len = in_dhcp_opt->len;
> +          break;
> +        case 67: /* DHCP_OPT_BOOTFILENAME */
> +          boot_filname_opt_ptr = &buf[opt_len];
> +          boot_filename_len = in_dhcp_opt->len;
> +          /* fall through */
> +        default:
> +          /* code < 255 */
> +          buf[opt_len] = in_dhcp_opt->code;
> +          memcpy(buf + 1 + opt_len, ptr + 2, in_dhcp_opt->len + 1);
> +          opt_len += 2 + in_dhcp_opt->len;
> +          break;
> +        }
> +        ptr += sizeof *in_dhcp_opt + in_dhcp_opt->len;
> +    }
> +
> +    if (!ipxe_req && boot_filename_alt_len > 0 && boot_filename_len > 0) {
> +        memcpy(&boot_filname_opt_ptr[2], boot_filename_alt,
> +               boot_filename_alt_len);
> +        boot_filname_opt_ptr[1] = boot_filename_alt_len;
> +        opt_len += boot_filename_alt_len - boot_filename_len;
> +    }
> +
>      uint16_t new_l4_size = UDP_HEADER_LEN + DHCP_HEADER_LEN + 16;
>      if (msg_type != DHCP_MSG_NAK) {
> -        new_l4_size += reply_dhcp_opts_ptr->size;
> +        new_l4_size += opt_len;
>      }
>      size_t new_packet_size = pkt_in->l4_ofs + new_l4_size;
>
> @@ -1954,7 +1996,7 @@ pinctrl_handle_put_dhcp_opts(
>
>      uint16_t out_dhcp_opts_size = 12;
>      if (msg_type != DHCP_MSG_NAK) {
> -      out_dhcp_opts_size += reply_dhcp_opts_ptr->size;
> +      out_dhcp_opts_size += opt_len;
>      }
>      uint8_t *out_dhcp_opts = dp_packet_put_zeros(&pkt_out,
>                                                   out_dhcp_opts_size);
> @@ -1965,9 +2007,8 @@ pinctrl_handle_put_dhcp_opts(
>      out_dhcp_opts += 3;
>
>      if (msg_type != DHCP_MSG_NAK) {
> -        memcpy(out_dhcp_opts, reply_dhcp_opts_ptr->data,
> -               reply_dhcp_opts_ptr->size);
> -        out_dhcp_opts += reply_dhcp_opts_ptr->size;
> +      memcpy(out_dhcp_opts, buf, opt_len);
> +      out_dhcp_opts += opt_len;
>      }
>
>      /* Padding */
> @@ -2005,6 +2046,7 @@ pinctrl_handle_put_dhcp_opts(
>                   ETH_ADDR_ARGS(l2->eth_src), IP_ARGS(*offer_ip));
>
>      success = 1;
> +    free(buf);
>  exit:
>      if (!ofperr) {
>          union mf_subvalue sv;
> diff --git a/lib/actions.c b/lib/actions.c
> index 5fe0a3897..48726b7f5 100644
> --- a/lib/actions.c
> +++ b/lib/actions.c
> @@ -2308,23 +2308,23 @@ static void
>  encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
>                           struct ofpbuf *ofpacts)
>  {
> -    uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2);
> -    opt_header[0] = o->option->code;
> +    uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 3);
>
> +    memcpy(opt_header, &o->option->code, 2);
>      const union expr_constant *c = o->value.values;
>      size_t n_values = o->value.n_values;
>      if (!strcmp(o->option->type, "bool") ||
>          !strcmp(o->option->type, "uint8")) {
> -        opt_header[1] = 1;
> +        opt_header[2] = 1;
>          ofpbuf_put(ofpacts, &c->value.u8_val, 1);
>      } else if (!strcmp(o->option->type, "uint16")) {
> -        opt_header[1] = 2;
> +        opt_header[2] = 2;
>          ofpbuf_put(ofpacts, &c->value.be16_int, 2);
>      } else if (!strcmp(o->option->type, "uint32")) {
> -        opt_header[1] = 4;
> +        opt_header[2] = 4;
>          ofpbuf_put(ofpacts, &c->value.be32_int, 4);
>      } else if (!strcmp(o->option->type, "ipv4")) {
> -        opt_header[1] = n_values * sizeof(ovs_be32);
> +        opt_header[2] = n_values * sizeof(ovs_be32);
>          for (size_t i = 0; i < n_values; i++) {
>              ofpbuf_put(ofpacts, &c[i].value.ipv4, sizeof(ovs_be32));
>          }
> @@ -2333,7 +2333,7 @@ encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
>          if (no_of_routes % 2) {
>              no_of_routes -= 1;
>          }
> -        opt_header[1] = 0;
> +        opt_header[2] = 0;
>
>          /* Calculating the length of this option first because when
>           * we call ofpbuf_put, it might reallocate the buffer if the
> @@ -2345,7 +2345,7 @@ encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
>              if (c[i].masked) {
>                  plen = (uint8_t) ip_count_cidr_bits(c[i].mask.ipv4);
>              }
> -            opt_header[1] += (1 + DIV_ROUND_UP(plen, 8) + sizeof(ovs_be32));
> +            opt_header[2] += (1 + DIV_ROUND_UP(plen, 8) + sizeof(ovs_be32));
>          }
>
>          /* Copied from RFC 3442. Please refer to this RFC for the format of
> @@ -2375,14 +2375,14 @@ encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
>                         sizeof(ovs_be32));
>          }
>      } else if (!strcmp(o->option->type, "str")) {
> -        opt_header[1] = strlen(c->string);
> -        ofpbuf_put(ofpacts, c->string, opt_header[1]);
> +        opt_header[2] = strlen(c->string);
> +        ofpbuf_put(ofpacts, c->string, opt_header[2]);
>      } else if (!strcmp(o->option->type, "host_id")) {
>          if (o->value.type == EXPR_C_STRING) {
> -            opt_header[1] = strlen(c->string);
> -            ofpbuf_put(ofpacts, c->string, opt_header[1]);
> +            opt_header[2] = strlen(c->string);
> +            ofpbuf_put(ofpacts, c->string, opt_header[2]);
>          } else {
> -           opt_header[1] = sizeof(ovs_be32);
> +           opt_header[2] = sizeof(ovs_be32);
>             ofpbuf_put(ofpacts, &c->value.ipv4, sizeof(ovs_be32));
>          }
>      } else if (!strcmp(o->option->type, "domains")) {
> @@ -2461,7 +2461,7 @@ encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
>                  encode_offset += sizeof(uint8_t);
>              }
>          }
> -        opt_header[1] = encode_offset;
> +        opt_header[2] = encode_offset;
>          ofpbuf_put(ofpacts, dns_encoded, encode_offset);
>
>          out:
> diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
> index 18c59896d..79c037ba3 100644
> --- a/lib/ovn-l7.h
> +++ b/lib/ovn-l7.h
> @@ -93,6 +93,11 @@ struct gen_opts_map {
>  #define DHCP_OPT_DOMAIN_SEARCH_LIST \
>      DHCP_OPTION("domain_search_list", 119, "domains")
>
> +/* fixed DHCP header */
> +#define DHCP_OPT_BOOTFILE_ALT DHCP_OPTION("bootfile_name_alt", 300, "str")
> +
> +#define DHCP_OPT_ETHERBOOT     175
> +
>  #define DHCP_OPT_ARP_CACHE_TIMEOUT \
>      DHCP_OPTION("arp_cache_timeout", 35, "uint32")
>  #define DHCP_OPT_TCP_KEEPALIVE_INTERVAL \
> @@ -179,6 +184,12 @@ struct dhcp_opt6_header {
>      ovs_be16 size;
>  };
>
> +OVS_PACKED(
> +struct dhcp_opt_userdata_hdr {
> +    uint16_t code;
> +    uint8_t len;
> +});
> +
>  /* These are not defined in ovs/lib/dhcp.h, hence defining here. */
>  #define OVN_DHCP_MSG_DECLINE        4
>  #define OVN_DHCP_MSG_RELEASE        7
> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> index 366fdd95f..a9941bb9a 100644
> --- a/northd/ovn-northd.c
> +++ b/northd/ovn-northd.c
> @@ -12514,6 +12514,7 @@ static struct gen_opts_map supported_dhcp_opts[] = {
>      DHCP_OPT_ARP_CACHE_TIMEOUT,
>      DHCP_OPT_TCP_KEEPALIVE_INTERVAL,
>      DHCP_OPT_DOMAIN_SEARCH_LIST,
> +    DHCP_OPT_BOOTFILE_ALT,
>  };
>
>  static struct gen_opts_map supported_dhcpv6_opts[] = {
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index 86195af34..43694535e 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -3096,6 +3096,16 @@
>              resolving hostnames via the Domain Name System.
>            </p>
>          </column>
> +
> +        <column name="options" key="bootfile_name_alt">
> +          <p>
> +          </p>
> +            "bootfile_name_alt" option is used to support iPXE.
> +            When both "bootfile_name" and "bootfile_name_alt" are provided
> +            by the CMS, "bootfile_name" will be used for option 67 if the
> +            dhcp request contains etherboot option (175), otherwise
> +            "bootfile_name_alt" will be used.
> +        </column>
>        </group>
>
>        <group title="DHCP Options of type host_id">
> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
> index d1c506a22..9a684d8c0 100644
> --- a/ovn-sb.ovsschema
> +++ b/ovn-sb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Southbound",
> -    "version": "2.10.0",
> -    "cksum": "2548342632 22615",
> +    "version": "2.11.0",
> +    "cksum": "1750191326 22616",
>      "tables": {
>          "SB_Global": {
>              "columns": {
> @@ -226,7 +226,7 @@
>                  "name": {"type": "string"},
>                  "code": {
>                      "type": {"key": {"type": "integer",
> -                                     "minInteger": 0, "maxInteger": 254}}},
> +                                     "minInteger": 0, "maxInteger": 1000}}},
>                  "type": {
>                      "type": {"key": {
>                          "type": "string",
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 488fd119b..9206336ac 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -1277,25 +1277,25 @@ reg0[0] = lookup_arp_ip(inport, eth.dst);
>
>  # put_dhcp_opts
>  reg1[0] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1);
> -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.40.01.02.03.04.03.04.0a.00.00.01,pause)
> +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.40.01.02.03.04.03.00.04.0a.00.00.01,pause)
>  reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain_name="ovn.org",wpad="https://example.org",bootfile_name="https://127.0.0.1/boot.ipxe",path_prefix="/tftpboot");
>      formats as reg2[5] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.254.0, mtu = 1400, domain_name = "ovn.org", wpad = "https://example.org", bootfile_name = "https://127.0.0.1/boot.ipxe", path_prefix = "/tftpboot");
> -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.09.2f.74.66.74.70.62.6f.6f.74,pause)
> +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.fe.00.1a.00.02.05.78.0f.00.07.6f.76.6e.2e.6f.72.67.fc.00.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.00.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.00.09.2f.74.66.74.70.62.6f.6f.74,pause)
>  reg0[15] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,0.0.0.0/0,10.0.0.1},ethernet_encap=1,router_discovery=0,tftp_server_address={10.0.0.4,10.0.0.5},arp_cache_timeout=10,tcp_keepalive_interval=10);
>      formats as reg0[15] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.255.0, mtu = 1400, ip_forward_enable = 1, default_ttl = 121, dns_server = {8.8.8.8, 7.7.7.7}, classless_static_route = {30.0.0.0/24, 10.0.0.4, 40.0.0.0/16, 10.0.0.6, 0.0.0.0/0, 10.0.0.1}, ethernet_encap = 1, router_discovery = 0, tftp_server_address = {10.0.0.4, 10.0.0.5}, arp_cache_timeout = 10, tcp_keepalive_interval = 10);
> -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.96.08.0a.00.00.04.0a.00.00.05.23.04.00.00.00.0a.26.04.00.00.00.0a,pause)
> +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.ff.00.1a.00.02.05.78.13.00.01.01.17.00.01.79.06.00.08.08.08.08.08.07.07.07.07.79.00.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.00.01.01.1f.00.01.00.96.00.08.0a.00.00.04.0a.00.00.05.23.00.04.00.00.00.0a.26.00.04.00.00.00.0a,pause)
>  reg0[15] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,0.0.0.0/0,10.0.0.1},ethernet_encap=1,router_discovery=0,tftp_server=10.0.0.10);
>      formats as reg0[15] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.255.0, mtu = 1400, ip_forward_enable = 1, default_ttl = 121, dns_server = {8.8.8.8, 7.7.7.7}, classless_static_route = {30.0.0.0/24, 10.0.0.4, 40.0.0.0/16, 10.0.0.6, 0.0.0.0/0, 10.0.0.1}, ethernet_encap = 1, router_discovery = 0, tftp_server = 10.0.0.10);
> -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.42.04.0a.00.00.0a,pause)
> +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.ff.00.1a.00.02.05.78.13.00.01.01.17.00.01.79.06.00.08.08.08.08.08.07.07.07.07.79.00.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.00.01.01.1f.00.01.00.42.00.04.0a.00.00.0a,pause)
>  reg0[15] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,0.0.0.0/0,10.0.0.1},ethernet_encap=1,router_discovery=0,tftp_server="tftp_server_test");
>      formats as reg0[15] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.255.0, mtu = 1400, ip_forward_enable = 1, default_ttl = 121, dns_server = {8.8.8.8, 7.7.7.7}, classless_static_route = {30.0.0.0/24, 10.0.0.4, 40.0.0.0/16, 10.0.0.6, 0.0.0.0/0, 10.0.0.1}, ethernet_encap = 1, router_discovery = 0, tftp_server = "tftp_server_test");
> -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.42.10.74.66.74.70.5f.73.65.72.76.65.72.5f.74.65.73.74,pause)
> +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.ff.00.1a.00.02.05.78.13.00.01.01.17.00.01.79.06.00.08.08.08.08.08.07.07.07.07.79.00.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.00.01.01.1f.00.01.00.42.00.10.74.66.74.70.5f.73.65.72.76.65.72.5f.74.65.73.74,pause)
>  reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain_name="ovn.org",wpad="https://example.org",bootfile_name="https://127.0.0.1/boot.ipxe",path_prefix="/tftpboot",domain_search_list="ovn.org,abc.ovn.org");
>      formats as reg2[5] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.254.0, mtu = 1400, domain_name = "ovn.org", wpad = "https://example.org", bootfile_name = "https://127.0.0.1/boot.ipxe", path_prefix = "/tftpboot", domain_search_list = "ovn.org,abc.ovn.org");
> -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.09.2f.74.66.74.70.62.6f.6f.74.77.0f.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00,pause)
> +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.fe.00.1a.00.02.05.78.0f.00.07.6f.76.6e.2e.6f.72.67.fc.00.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.00.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.00.09.2f.74.66.74.70.62.6f.6f.74.77.00.0f.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00,pause)
>  reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain_name="ovn.org",wpad="https://example.org",bootfile_name="https://127.0.0.1/boot.ipxe",path_prefix="/tftpboot",domain_search_list="ovn.org,abc.ovn.org,def.ovn.org,ovn.test,def.ovn.test,test.org,abc.com");
>      formats as reg2[5] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.254.0, mtu = 1400, domain_name = "ovn.org", wpad = "https://example.org", bootfile_name = "https://127.0.0.1/boot.ipxe", path_prefix = "/tftpboot", domain_search_list = "ovn.org,abc.ovn.org,def.ovn.org,ovn.test,def.ovn.test,test.org,abc.com");
> -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.09.2f.74.66.74.70.62.6f.6f.74.77.35.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00.03.64.65.66.c0.00.03.6f.76.6e.04.74.65.73.74.00.03.64.65.66.c0.15.04.74.65.73.74.c0.04.03.61.62.63.03.63.6f.6d.00,pause)
> +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.fe.00.1a.00.02.05.78.0f.00.07.6f.76.6e.2e.6f.72.67.fc.00.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.00.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.00.09.2f.74.66.74.70.62.6f.6f.74.77.00.35.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00.03.64.65.66.c0.00.03.6f.76.6e.04.74.65.73.74.00.03.64.65.66.c0.15.04.74.65.73.74.c0.04.03.61.62.63.03.63.6f.6d.00,pause)
>
>  reg1[0..1] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1);
>      Cannot use 2-bit field reg1[0..1] where 1-bit field is required.
> @@ -5302,10 +5302,10 @@ sleep 2
>  as hv1 ovs-vsctl show
>
>  # This shell function sends a DHCP request packet
> -# test_dhcp INPORT SRC_MAC DHCP_TYPE BROADCAST CIADDR OFFER_IP REQUEST_IP USE_IP ...
> +# test_dhcp INPORT SRC_MAC DHCP_TYPE BROADCAST CIADDR OFFER_IP REQUEST_IP ETH_BOOT USE_IP ...
>  test_dhcp() {
> -    local inport=$1 src_mac=$2 dhcp_type=$3 broadcast=$4 ciaddr=$5 offer_ip=$6 request_ip=$7 use_ip=$8
> -    shift; shift; shift; shift; shift; shift; shift; shift;
> +    local inport=$1 src_mac=$2 dhcp_type=$3 broadcast=$4 ciaddr=$5 offer_ip=$6 request_ip=$7 eth_boot=$8 use_ip=$9
> +    shift; shift; shift; shift; shift; shift; shift; shift; shift;
>
>      if test $use_ip != 0; then
>          src_ip=$1
> @@ -5317,11 +5317,21 @@ test_dhcp() {
>      fi
>
>      if test $request_ip != 0; then
> -        ip_len=0120
> -        udp_len=010b
> +        if test $eth_boot != 0; then
> +            ip_len=0124
> +            udp_len=010f
> +        else
> +            ip_len=0120
> +            udp_len=010b
> +        fi
>      else
> -        ip_len=011a
> -        udp_len=0106
> +        if test $eth_boot != 0; then
> +            ip_len=011e
> +            udp_len=010a
> +        else
> +            ip_len=011a
> +            udp_len=0106
> +        fi
>      fi
>
>      if test $broadcast != 0; then
> @@ -5362,6 +5372,9 @@ test_dhcp() {
>          # dhcp requested ip
>          request=${request}3204${request_ip}
>      fi
> +    if test $eth_boot != 0; then
> +        request=${request}af020000
> +    fi
>      # dhcp end option
>      request=${request}ff
>
> @@ -5457,7 +5470,7 @@ server_ip=`ip_to_hex 10 0 0 1`
>  ciaddr=`ip_to_hex 0 0 0 0`
>  request_ip=0
>  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
> -test_dhcp 1 f00000000001 01 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
> +test_dhcp 1 f00000000001 01 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
>
>  # NXT_RESUMEs should be 1.
>  OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> @@ -5483,7 +5496,7 @@ server_ip=`ip_to_hex 10 0 0 1`
>  ciaddr=`ip_to_hex 0 0 0 0`
>  request_ip=$offer_ip
>  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
> -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
> +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
>
>  # NXT_RESUMEs should be 2.
>  OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> @@ -5507,7 +5520,7 @@ server_ip=`ip_to_hex 10 0 0 1`
>  ciaddr=`ip_to_hex 0 0 0 0`
>  request_ip=`ip_to_hex 10 0 0 7`
>  expected_dhcp_opts=""
> -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 06 $expected_dhcp_opts
> +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 06 $expected_dhcp_opts
>
>  # NXT_RESUMEs should be 3.
>  OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> @@ -5531,7 +5544,7 @@ rm -f 2.expected
>  ciaddr=`ip_to_hex 0 0 0 0`
>  offer_ip=0
>  request_ip=0
> -test_dhcp 2 f00000000002 09 0 $ciaddr $offer_ip $request_ip 0 1 1
> +test_dhcp 2 f00000000002 09 0 $ciaddr $offer_ip $request_ip 0 0 1 1
>
>  # NXT_RESUMEs should be 4.
>  OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> @@ -5548,18 +5561,18 @@ rm -f 2.expected
>  # ls2-lp2 (vif4-tx.pcap) should receive the DHCPv4 request packet once.
>
>  ciaddr=`ip_to_hex 0 0 0 0`
> -test_dhcp 3 f00000000003 01 0 $ciaddr 0 0 4 0
> +test_dhcp 3 f00000000003 01 0 $ciaddr 0 0 0 4 0
>
>  # Send DHCPv4 packet on ls2-lp2. "router" DHCPv4 option is not defined for
>  # this lport.
>  ciaddr=`ip_to_hex 0 0 0 0`
> -test_dhcp 4 f00000000004 01 0 $ciaddr 0 0 3 0
> +test_dhcp 4 f00000000004 01 0 $ciaddr 0 0 0 3 0
>
>  # NXT_RESUMEs should be 4.
>  OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
>
> -#OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [3.expected])
> -#OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [4.expected])
> +OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [3.expected])
> +OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [4.expected])
>
>  # Send DHCPREQUEST in the RENEWING/REBINDING state with ip4.src set to 10.0.0.6
>  # and ip4.dst set to 10.0.0.1.
> @@ -5570,7 +5583,7 @@ request_ip=0
>  src_ip=$offer_ip
>  dst_ip=$server_ip
>  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
> -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
>
>  # NXT_RESUMEs should be 5.
>  OVS_WAIT_UNTIL([test 5 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> @@ -5596,7 +5609,7 @@ request_ip=0
>  src_ip=$offer_ip
>  dst_ip=`ip_to_hex 255 255 255 255`
>  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
> -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
>
>  # NXT_RESUMEs should be 6.
>  OVS_WAIT_UNTIL([test 6 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> @@ -5622,7 +5635,7 @@ request_ip=0
>  src_ip=$offer_ip
>  dst_ip=`ip_to_hex 255 255 255 255`
>  expected_dhcp_opts=""
> -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts
> +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts
>
>  # NXT_RESUMEs should be 7.
>  OVS_WAIT_UNTIL([test 7 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> @@ -5648,7 +5661,7 @@ request_ip=0
>  src_ip=$offer_ip
>  dst_ip=`ip_to_hex 255 255 255 255`
>  expected_dhcp_opts=""
> -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts
> +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts
>
>  # NXT_RESUMEs should be 8.
>  OVS_WAIT_UNTIL([test 8 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> @@ -5670,7 +5683,7 @@ rm -f 2.expected
>  ciaddr=`ip_to_hex 0 0 0 0`
>  src_ip=`ip_to_hex 10 0 0 6`
>  dst_ip=`ip_to_hex 10 0 0 4`
> -test_dhcp 2 f00000000002 03 0 $ciaddr 0 0 1 $src_ip $dst_ip 1
> +test_dhcp 2 f00000000002 03 0 $ciaddr 0 0 0 1 $src_ip $dst_ip 1
>
>  # NXT_RESUMEs should be 8.
>  OVS_WAIT_UNTIL([test 8 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> @@ -5689,7 +5702,7 @@ server_ip=`ip_to_hex 10 0 0 1`
>  ciaddr=`ip_to_hex 0 0 0 0`
>  request_ip=0
>  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
> -test_dhcp 1 f00000000001 01 1 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
> +test_dhcp 1 f00000000001 01 1 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
>
>  # NXT_RESUMEs should be 9.
>  OVS_WAIT_UNTIL([test 9 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> @@ -5712,7 +5725,7 @@ server_ip=`ip_to_hex 10 0 0 1`
>  ciaddr=`ip_to_hex 10 0 0 6`
>  request_ip=0
>  expected_dhcp_opts=0
> -test_dhcp 2 f00000000002 07 0 $ciaddr $offer_ip $request_ip 0 ff1000000001
> +test_dhcp 2 f00000000002 07 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001
>
>  # NXT_RESUMEs should be 10.
>  OVS_WAIT_UNTIL([test 10 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)])
> @@ -5739,7 +5752,7 @@ dst_ip=$server_ip
>  # In the expected_dhcp_opts we should not see 330400000e10 which is
>  # dhcp lease time option and 0104ffffff00 which is subnet mask option.
>  expected_dhcp_opts=03040a00000136040a000001
> -test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> +test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
>
>  # NXT_RESUMEs should be 11.
>  OVS_WAIT_UNTIL([test 11 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)])
> @@ -5769,7 +5782,7 @@ dst_ip=$server_ip
>  # In the expected_dhcp_opts we should not see 330400000e10 which is
>  # dhcp lease time option.
>  expected_dhcp_opts=3a0400000fa0330400000e100104ffffff0003040a00000136040a000001
> -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
>
>  # NXT_RESUMEs should be 12.
>  OVS_WAIT_UNTIL([test 12 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)])
> @@ -5796,7 +5809,7 @@ dst_ip=$server_ip
>  # In the expected_dhcp_opts we should not see 330400000e10 which is
>  # dhcp lease time option and 0104ffffff00 which is subnet mask option.
>  expected_dhcp_opts=03040a00000136040a000001
> -test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> +test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
>
>  # NXT_RESUMEs should be 13.
>  OVS_WAIT_UNTIL([test 13 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)])
> @@ -5827,7 +5840,7 @@ server_ip=`ip_to_hex 10 0 0 1`
>  ciaddr=`ip_to_hex 0 0 0 0`
>  request_ip=$offer_ip
>  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a00000142040a0a0a0a
> -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
> +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
>
>  # NXT_RESUMEs should be 14.
>  OVS_WAIT_UNTIL([test 14 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> @@ -5858,7 +5871,7 @@ server_ip=`ip_to_hex 10 0 0 1`
>  ciaddr=`ip_to_hex 0 0 0 0`
>  request_ip=$offer_ip
>  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a0000014210746573745f746674705f736572766572
> -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
> +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
>
>  # NXT_RESUMEs should be 15.
>  OVS_WAIT_UNTIL([test 15 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> @@ -5889,7 +5902,7 @@ server_ip=`ip_to_hex 10 0 0 1`
>  ciaddr=`ip_to_hex 0 0 0 0`
>  request_ip=$offer_ip
>  expected_dhcp_opts=771305746573743103636f6d00057465737432c006330400000e100104ffffff0003040a00000136040a000001
> -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
> +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
>
>  # NXT_RESUMEs should be 16.
>  OVS_WAIT_UNTIL([test 16 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> @@ -5907,9 +5920,42 @@ server_ip=`ip_to_hex 10 0 0 1`
>  ciaddr=`ip_to_hex 0 0 0 0`
>  request_ip=0
>  expected_dhcp_opts=""
> -test_dhcp 1 f00000000001 04 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
> +test_dhcp 1 f00000000001 04 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
>  AT_CHECK([fgrep -iq 'DHCPDECLINE from f0:00:00:00:00:01, 10.0.0.4 duplicated' hv1/ovn-controller.log], [0], [])
>
> +# Send Etherboot.
> +
> +reset_pcap_file hv1-vif1 hv1/vif1
> +reset_pcap_file hv1-vif2 hv1/vif2
> +rm -f 1.expected
> +rm -f 2.expected
> +
> +ovn-nbctl --all destroy dhcp-option
> +
> +ovn-nbctl dhcp-options-create 10.0.0.0/24
> +d3=$(ovn-nbctl --bare --columns=_uuid find dhcp_options cidr="10.0.0.0/24")
> +ovn-nbctl dhcp-options-set-options $d3 \
> +   server_id=10.0.0.1 server_mac=ff:10:00:00:00:01 \
> +   lease_time=3600 router=10.0.0.1 bootfile_name_alt=\"filename\" \
> +   bootfile_name=\"bootfile\"
> +
> +ovn-nbctl lsp-set-dhcpv4-options ls1-lp1 $d3
> +
> +offer_ip=`ip_to_hex 10 0 0 4`
> +server_ip=`ip_to_hex 10 0 0 1`
> +ciaddr=`ip_to_hex 0 0 0 0`
> +request_ip=0
> +boofile=4308626f6f7466696c65
> +expected_dhcp_opts=${boofile}330400000e100104ffffff0003040a00000136040a000001
> +test_dhcp 1 f00000000001 01 0 $ciaddr $offer_ip $request_ip 1 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
> +
> +$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
> +cat 1.expected | cut -c -48 > expout
> +AT_CHECK([cat 1.packets | cut -c -48], [0], [expout])
> +# Skipping the IPv4 checksum.
> +cat 1.expected | cut -c 53- > expout
> +AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout])
> +
>  OVN_CLEANUP([hv1])
>
>  AT_CLEANUP
> diff --git a/tests/test-ovn.c b/tests/test-ovn.c
> index d94ab025d..96763be60 100644
> --- a/tests/test-ovn.c
> +++ b/tests/test-ovn.c
> @@ -191,6 +191,7 @@ create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts,
>      dhcp_opt_add(dhcp_opts, "arp_cache_timeout", 35, "uint32");
>      dhcp_opt_add(dhcp_opts, "tcp_keepalive_interval", 38, "uint32");
>      dhcp_opt_add(dhcp_opts, "domain_search_list", 119, "domains");
> +    dhcp_opt_add(dhcp_opts, "bootfile_name_alt", 300, "str");
>
>      /* DHCPv6 options. */
>      hmap_init(dhcpv6_opts);
> --
> 2.26.2
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
Lorenzo Bianconi Oct. 22, 2020, 12:34 p.m. UTC | #2
> On Tue, Oct 13, 2020 at 4:45 PM Lorenzo Bianconi
> <lorenzo.bianconi@redhat.com> wrote:
> >
> > Introduce the possibility to specify more than 255 options in
> > put_dhcp_opts userdata buffer. This is useful to support conditional
> > options for the DHCP reply. (e.g option 67 used in iPXE protocol).
> > Express the option code using 2 bytes instead of just one in userdata
> > buffer. Moreover, parse userdata buffer in pinctrl_handle_put_dhcp_opts
> > in order to distinguish between 'optional' and 'fixed' options.
> >
> > Add iPXE support to OVN introducing "bootfile_name_alt" dhcp option.
> > When both "bootfile_name" and "bootfile_name_alt" are provided
> > by the CMS, "bootfile_name" will be used for option 67 if the
> > dhcp request contains etherboot option (175), otherwise
> > "bootfile_name_alt" will be used.
> >
> > Tested-by: Lucas Alvares Gomes Martins <lmartins@redhat.com>
> > Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
> 
> Hi Lorenzo,

Hi Numan,

thx for the review.

> 
> Thanks for the patch. If I understand correctly, you are encoding all
> the dhcp codes as 2 bytes in the controller userdata
> buffer right ? So that you can encode the  bootfile_alt option as 300 ?

correct

> 
> The drawback of this approach is that it increases the userdata buffer
> a bit and pinctrl thread needs to reencode the dhcp options
> to the standard format - i.e 1 byte code, 1 byte length.
> 
> I have another approach.
> * We can assign a private unused dhcp option code for bootfile_alt -
> may be a code between 224 and 254 -
> https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml
>    Since the chosen option code will not be part of the actual dhcp
> reply, I think it should be fine.
> 
> * We can encode the bootfile_name (67) and bootfile_name_alt (let's
> say we chose 224) soon after the offer_ip in the userdata buffer (in
> actions.c) and then encode the rest of the options.
> 
> * When pinctrl thread has to send the dhcp reply, it will check if 67
> and 224 are present in the userdata buffer. It can easily check since
> those will be encoded after offer_ip.
> 
> * If the dhcp request has etherboot option, it can put the
> bootfile_name in option 67, else it can put bootfile_name_alt in
> option 67.
> 
> * It can then just blindly copy the rest of the userdata buffer in the
> reply like we do now.
> 
> What do you think ?

I think it will work, we will just encode the bootfile_name_alt as option 224
in the userdata buffer. The only problem I can see is if option 224 will be
used in the future, but I am fine with your approach. What do you think?

Regards,
Lorenzo

> 
> Thanks
> Numan
> 
> 
> > ---
> >  controller/pinctrl.c |  56 +++++++++++++++++---
> >  lib/actions.c        |  28 +++++-----
> >  lib/ovn-l7.h         |  11 ++++
> >  northd/ovn-northd.c  |   1 +
> >  ovn-nb.xml           |  10 ++++
> >  ovn-sb.ovsschema     |   6 +--
> >  tests/ovn.at         | 118 ++++++++++++++++++++++++++++++-------------
> >  tests/test-ovn.c     |   1 +
> >  8 files changed, 171 insertions(+), 60 deletions(-)
> >
> > diff --git a/controller/pinctrl.c b/controller/pinctrl.c
> > index 0a7020533..260184566 100644
> > --- a/controller/pinctrl.c
> > +++ b/controller/pinctrl.c
> > @@ -1766,6 +1766,7 @@ pinctrl_handle_put_dhcp_opts(
> >      }
> >      in_dhcp_ptr += sizeof magic_cookie;
> >
> > +    bool ipxe_req = false;
> >      const uint8_t *in_dhcp_msg_type = NULL;
> >      ovs_be32 request_ip = in_dhcp_data->ciaddr;
> >      while (in_dhcp_ptr < end) {
> > @@ -1798,6 +1799,9 @@ pinctrl_handle_put_dhcp_opts(
> >                  request_ip = get_unaligned_be32(DHCP_OPT_PAYLOAD(in_dhcp_opt));
> >              }
> >              break;
> > +        case DHCP_OPT_ETHERBOOT:
> > +            ipxe_req = true;
> > +            break;
> >          default:
> >              break;
> >          }
> > @@ -1855,8 +1859,8 @@ pinctrl_handle_put_dhcp_opts(
> >
> >          reply_dhcp_opts_ptr = dhcp_inform_reply_buf;
> >          while (in_dhcp_ptr < end) {
> > -            const struct dhcp_opt_header *in_dhcp_opt =
> > -                (const struct dhcp_opt_header *)in_dhcp_ptr;
> > +            const struct dhcp_opt_userdata_hdr *in_dhcp_opt =
> > +                (const struct dhcp_opt_userdata_hdr *)in_dhcp_ptr;
> >
> >              switch (in_dhcp_opt->code) {
> >              case OVN_DHCP_OPT_CODE_NETMASK:
> > @@ -1916,9 +1920,47 @@ pinctrl_handle_put_dhcp_opts(
> >       *| 4 Bytes padding | 1 Byte (option end 0xFF ) | 4 Bytes padding|
> >       * --------------------------------------------------------------
> >       */
> > +    /* compute opt len */
> > +    unsigned char *boot_filename_alt = NULL, *boot_filname_opt_ptr = NULL;
> > +    unsigned char *ptr = (unsigned char *)reply_dhcp_opts_ptr->data;
> > +    uint16_t boot_filename_len = 0, boot_filename_alt_len = 0;
> > +    unsigned char *ptr_end = ptr + reply_dhcp_opts_ptr->size;
> > +    unsigned char *buf = xmalloc(reply_dhcp_opts_ptr->size);
> > +    uint16_t opt_len = 0;
> > +
> > +    while (ptr < ptr_end) {
> > +        const struct dhcp_opt_userdata_hdr *in_dhcp_opt =
> > +            (const struct dhcp_opt_userdata_hdr *)ptr;
> > +
> > +        switch (in_dhcp_opt->code) {
> > +        case 300: /* DHCP_OPT_BOOTFILE_ALT */
> > +          boot_filename_alt = ptr + sizeof *in_dhcp_opt;
> > +          boot_filename_alt_len = in_dhcp_opt->len;
> > +          break;
> > +        case 67: /* DHCP_OPT_BOOTFILENAME */
> > +          boot_filname_opt_ptr = &buf[opt_len];
> > +          boot_filename_len = in_dhcp_opt->len;
> > +          /* fall through */
> > +        default:
> > +          /* code < 255 */
> > +          buf[opt_len] = in_dhcp_opt->code;
> > +          memcpy(buf + 1 + opt_len, ptr + 2, in_dhcp_opt->len + 1);
> > +          opt_len += 2 + in_dhcp_opt->len;
> > +          break;
> > +        }
> > +        ptr += sizeof *in_dhcp_opt + in_dhcp_opt->len;
> > +    }
> > +
> > +    if (!ipxe_req && boot_filename_alt_len > 0 && boot_filename_len > 0) {
> > +        memcpy(&boot_filname_opt_ptr[2], boot_filename_alt,
> > +               boot_filename_alt_len);
> > +        boot_filname_opt_ptr[1] = boot_filename_alt_len;
> > +        opt_len += boot_filename_alt_len - boot_filename_len;
> > +    }
> > +
> >      uint16_t new_l4_size = UDP_HEADER_LEN + DHCP_HEADER_LEN + 16;
> >      if (msg_type != DHCP_MSG_NAK) {
> > -        new_l4_size += reply_dhcp_opts_ptr->size;
> > +        new_l4_size += opt_len;
> >      }
> >      size_t new_packet_size = pkt_in->l4_ofs + new_l4_size;
> >
> > @@ -1954,7 +1996,7 @@ pinctrl_handle_put_dhcp_opts(
> >
> >      uint16_t out_dhcp_opts_size = 12;
> >      if (msg_type != DHCP_MSG_NAK) {
> > -      out_dhcp_opts_size += reply_dhcp_opts_ptr->size;
> > +      out_dhcp_opts_size += opt_len;
> >      }
> >      uint8_t *out_dhcp_opts = dp_packet_put_zeros(&pkt_out,
> >                                                   out_dhcp_opts_size);
> > @@ -1965,9 +2007,8 @@ pinctrl_handle_put_dhcp_opts(
> >      out_dhcp_opts += 3;
> >
> >      if (msg_type != DHCP_MSG_NAK) {
> > -        memcpy(out_dhcp_opts, reply_dhcp_opts_ptr->data,
> > -               reply_dhcp_opts_ptr->size);
> > -        out_dhcp_opts += reply_dhcp_opts_ptr->size;
> > +      memcpy(out_dhcp_opts, buf, opt_len);
> > +      out_dhcp_opts += opt_len;
> >      }
> >
> >      /* Padding */
> > @@ -2005,6 +2046,7 @@ pinctrl_handle_put_dhcp_opts(
> >                   ETH_ADDR_ARGS(l2->eth_src), IP_ARGS(*offer_ip));
> >
> >      success = 1;
> > +    free(buf);
> >  exit:
> >      if (!ofperr) {
> >          union mf_subvalue sv;
> > diff --git a/lib/actions.c b/lib/actions.c
> > index 5fe0a3897..48726b7f5 100644
> > --- a/lib/actions.c
> > +++ b/lib/actions.c
> > @@ -2308,23 +2308,23 @@ static void
> >  encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
> >                           struct ofpbuf *ofpacts)
> >  {
> > -    uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2);
> > -    opt_header[0] = o->option->code;
> > +    uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 3);
> >
> > +    memcpy(opt_header, &o->option->code, 2);
> >      const union expr_constant *c = o->value.values;
> >      size_t n_values = o->value.n_values;
> >      if (!strcmp(o->option->type, "bool") ||
> >          !strcmp(o->option->type, "uint8")) {
> > -        opt_header[1] = 1;
> > +        opt_header[2] = 1;
> >          ofpbuf_put(ofpacts, &c->value.u8_val, 1);
> >      } else if (!strcmp(o->option->type, "uint16")) {
> > -        opt_header[1] = 2;
> > +        opt_header[2] = 2;
> >          ofpbuf_put(ofpacts, &c->value.be16_int, 2);
> >      } else if (!strcmp(o->option->type, "uint32")) {
> > -        opt_header[1] = 4;
> > +        opt_header[2] = 4;
> >          ofpbuf_put(ofpacts, &c->value.be32_int, 4);
> >      } else if (!strcmp(o->option->type, "ipv4")) {
> > -        opt_header[1] = n_values * sizeof(ovs_be32);
> > +        opt_header[2] = n_values * sizeof(ovs_be32);
> >          for (size_t i = 0; i < n_values; i++) {
> >              ofpbuf_put(ofpacts, &c[i].value.ipv4, sizeof(ovs_be32));
> >          }
> > @@ -2333,7 +2333,7 @@ encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
> >          if (no_of_routes % 2) {
> >              no_of_routes -= 1;
> >          }
> > -        opt_header[1] = 0;
> > +        opt_header[2] = 0;
> >
> >          /* Calculating the length of this option first because when
> >           * we call ofpbuf_put, it might reallocate the buffer if the
> > @@ -2345,7 +2345,7 @@ encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
> >              if (c[i].masked) {
> >                  plen = (uint8_t) ip_count_cidr_bits(c[i].mask.ipv4);
> >              }
> > -            opt_header[1] += (1 + DIV_ROUND_UP(plen, 8) + sizeof(ovs_be32));
> > +            opt_header[2] += (1 + DIV_ROUND_UP(plen, 8) + sizeof(ovs_be32));
> >          }
> >
> >          /* Copied from RFC 3442. Please refer to this RFC for the format of
> > @@ -2375,14 +2375,14 @@ encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
> >                         sizeof(ovs_be32));
> >          }
> >      } else if (!strcmp(o->option->type, "str")) {
> > -        opt_header[1] = strlen(c->string);
> > -        ofpbuf_put(ofpacts, c->string, opt_header[1]);
> > +        opt_header[2] = strlen(c->string);
> > +        ofpbuf_put(ofpacts, c->string, opt_header[2]);
> >      } else if (!strcmp(o->option->type, "host_id")) {
> >          if (o->value.type == EXPR_C_STRING) {
> > -            opt_header[1] = strlen(c->string);
> > -            ofpbuf_put(ofpacts, c->string, opt_header[1]);
> > +            opt_header[2] = strlen(c->string);
> > +            ofpbuf_put(ofpacts, c->string, opt_header[2]);
> >          } else {
> > -           opt_header[1] = sizeof(ovs_be32);
> > +           opt_header[2] = sizeof(ovs_be32);
> >             ofpbuf_put(ofpacts, &c->value.ipv4, sizeof(ovs_be32));
> >          }
> >      } else if (!strcmp(o->option->type, "domains")) {
> > @@ -2461,7 +2461,7 @@ encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
> >                  encode_offset += sizeof(uint8_t);
> >              }
> >          }
> > -        opt_header[1] = encode_offset;
> > +        opt_header[2] = encode_offset;
> >          ofpbuf_put(ofpacts, dns_encoded, encode_offset);
> >
> >          out:
> > diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
> > index 18c59896d..79c037ba3 100644
> > --- a/lib/ovn-l7.h
> > +++ b/lib/ovn-l7.h
> > @@ -93,6 +93,11 @@ struct gen_opts_map {
> >  #define DHCP_OPT_DOMAIN_SEARCH_LIST \
> >      DHCP_OPTION("domain_search_list", 119, "domains")
> >
> > +/* fixed DHCP header */
> > +#define DHCP_OPT_BOOTFILE_ALT DHCP_OPTION("bootfile_name_alt", 300, "str")
> > +
> > +#define DHCP_OPT_ETHERBOOT     175
> > +
> >  #define DHCP_OPT_ARP_CACHE_TIMEOUT \
> >      DHCP_OPTION("arp_cache_timeout", 35, "uint32")
> >  #define DHCP_OPT_TCP_KEEPALIVE_INTERVAL \
> > @@ -179,6 +184,12 @@ struct dhcp_opt6_header {
> >      ovs_be16 size;
> >  };
> >
> > +OVS_PACKED(
> > +struct dhcp_opt_userdata_hdr {
> > +    uint16_t code;
> > +    uint8_t len;
> > +});
> > +
> >  /* These are not defined in ovs/lib/dhcp.h, hence defining here. */
> >  #define OVN_DHCP_MSG_DECLINE        4
> >  #define OVN_DHCP_MSG_RELEASE        7
> > diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> > index 366fdd95f..a9941bb9a 100644
> > --- a/northd/ovn-northd.c
> > +++ b/northd/ovn-northd.c
> > @@ -12514,6 +12514,7 @@ static struct gen_opts_map supported_dhcp_opts[] = {
> >      DHCP_OPT_ARP_CACHE_TIMEOUT,
> >      DHCP_OPT_TCP_KEEPALIVE_INTERVAL,
> >      DHCP_OPT_DOMAIN_SEARCH_LIST,
> > +    DHCP_OPT_BOOTFILE_ALT,
> >  };
> >
> >  static struct gen_opts_map supported_dhcpv6_opts[] = {
> > diff --git a/ovn-nb.xml b/ovn-nb.xml
> > index 86195af34..43694535e 100644
> > --- a/ovn-nb.xml
> > +++ b/ovn-nb.xml
> > @@ -3096,6 +3096,16 @@
> >              resolving hostnames via the Domain Name System.
> >            </p>
> >          </column>
> > +
> > +        <column name="options" key="bootfile_name_alt">
> > +          <p>
> > +          </p>
> > +            "bootfile_name_alt" option is used to support iPXE.
> > +            When both "bootfile_name" and "bootfile_name_alt" are provided
> > +            by the CMS, "bootfile_name" will be used for option 67 if the
> > +            dhcp request contains etherboot option (175), otherwise
> > +            "bootfile_name_alt" will be used.
> > +        </column>
> >        </group>
> >
> >        <group title="DHCP Options of type host_id">
> > diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
> > index d1c506a22..9a684d8c0 100644
> > --- a/ovn-sb.ovsschema
> > +++ b/ovn-sb.ovsschema
> > @@ -1,7 +1,7 @@
> >  {
> >      "name": "OVN_Southbound",
> > -    "version": "2.10.0",
> > -    "cksum": "2548342632 22615",
> > +    "version": "2.11.0",
> > +    "cksum": "1750191326 22616",
> >      "tables": {
> >          "SB_Global": {
> >              "columns": {
> > @@ -226,7 +226,7 @@
> >                  "name": {"type": "string"},
> >                  "code": {
> >                      "type": {"key": {"type": "integer",
> > -                                     "minInteger": 0, "maxInteger": 254}}},
> > +                                     "minInteger": 0, "maxInteger": 1000}}},
> >                  "type": {
> >                      "type": {"key": {
> >                          "type": "string",
> > diff --git a/tests/ovn.at b/tests/ovn.at
> > index 488fd119b..9206336ac 100644
> > --- a/tests/ovn.at
> > +++ b/tests/ovn.at
> > @@ -1277,25 +1277,25 @@ reg0[0] = lookup_arp_ip(inport, eth.dst);
> >
> >  # put_dhcp_opts
> >  reg1[0] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1);
> > -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.40.01.02.03.04.03.04.0a.00.00.01,pause)
> > +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.40.01.02.03.04.03.00.04.0a.00.00.01,pause)
> >  reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain_name="ovn.org",wpad="https://example.org",bootfile_name="https://127.0.0.1/boot.ipxe",path_prefix="/tftpboot");
> >      formats as reg2[5] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.254.0, mtu = 1400, domain_name = "ovn.org", wpad = "https://example.org", bootfile_name = "https://127.0.0.1/boot.ipxe", path_prefix = "/tftpboot");
> > -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.09.2f.74.66.74.70.62.6f.6f.74,pause)
> > +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.fe.00.1a.00.02.05.78.0f.00.07.6f.76.6e.2e.6f.72.67.fc.00.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.00.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.00.09.2f.74.66.74.70.62.6f.6f.74,pause)
> >  reg0[15] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,0.0.0.0/0,10.0.0.1},ethernet_encap=1,router_discovery=0,tftp_server_address={10.0.0.4,10.0.0.5},arp_cache_timeout=10,tcp_keepalive_interval=10);
> >      formats as reg0[15] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.255.0, mtu = 1400, ip_forward_enable = 1, default_ttl = 121, dns_server = {8.8.8.8, 7.7.7.7}, classless_static_route = {30.0.0.0/24, 10.0.0.4, 40.0.0.0/16, 10.0.0.6, 0.0.0.0/0, 10.0.0.1}, ethernet_encap = 1, router_discovery = 0, tftp_server_address = {10.0.0.4, 10.0.0.5}, arp_cache_timeout = 10, tcp_keepalive_interval = 10);
> > -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.96.08.0a.00.00.04.0a.00.00.05.23.04.00.00.00.0a.26.04.00.00.00.0a,pause)
> > +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.ff.00.1a.00.02.05.78.13.00.01.01.17.00.01.79.06.00.08.08.08.08.08.07.07.07.07.79.00.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.00.01.01.1f.00.01.00.96.00.08.0a.00.00.04.0a.00.00.05.23.00.04.00.00.00.0a.26.00.04.00.00.00.0a,pause)
> >  reg0[15] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,0.0.0.0/0,10.0.0.1},ethernet_encap=1,router_discovery=0,tftp_server=10.0.0.10);
> >      formats as reg0[15] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.255.0, mtu = 1400, ip_forward_enable = 1, default_ttl = 121, dns_server = {8.8.8.8, 7.7.7.7}, classless_static_route = {30.0.0.0/24, 10.0.0.4, 40.0.0.0/16, 10.0.0.6, 0.0.0.0/0, 10.0.0.1}, ethernet_encap = 1, router_discovery = 0, tftp_server = 10.0.0.10);
> > -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.42.04.0a.00.00.0a,pause)
> > +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.ff.00.1a.00.02.05.78.13.00.01.01.17.00.01.79.06.00.08.08.08.08.08.07.07.07.07.79.00.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.00.01.01.1f.00.01.00.42.00.04.0a.00.00.0a,pause)
> >  reg0[15] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,0.0.0.0/0,10.0.0.1},ethernet_encap=1,router_discovery=0,tftp_server="tftp_server_test");
> >      formats as reg0[15] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.255.0, mtu = 1400, ip_forward_enable = 1, default_ttl = 121, dns_server = {8.8.8.8, 7.7.7.7}, classless_static_route = {30.0.0.0/24, 10.0.0.4, 40.0.0.0/16, 10.0.0.6, 0.0.0.0/0, 10.0.0.1}, ethernet_encap = 1, router_discovery = 0, tftp_server = "tftp_server_test");
> > -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.42.10.74.66.74.70.5f.73.65.72.76.65.72.5f.74.65.73.74,pause)
> > +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.ff.00.1a.00.02.05.78.13.00.01.01.17.00.01.79.06.00.08.08.08.08.08.07.07.07.07.79.00.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.00.01.01.1f.00.01.00.42.00.10.74.66.74.70.5f.73.65.72.76.65.72.5f.74.65.73.74,pause)
> >  reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain_name="ovn.org",wpad="https://example.org",bootfile_name="https://127.0.0.1/boot.ipxe",path_prefix="/tftpboot",domain_search_list="ovn.org,abc.ovn.org");
> >      formats as reg2[5] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.254.0, mtu = 1400, domain_name = "ovn.org", wpad = "https://example.org", bootfile_name = "https://127.0.0.1/boot.ipxe", path_prefix = "/tftpboot", domain_search_list = "ovn.org,abc.ovn.org");
> > -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.09.2f.74.66.74.70.62.6f.6f.74.77.0f.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00,pause)
> > +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.fe.00.1a.00.02.05.78.0f.00.07.6f.76.6e.2e.6f.72.67.fc.00.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.00.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.00.09.2f.74.66.74.70.62.6f.6f.74.77.00.0f.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00,pause)
> >  reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain_name="ovn.org",wpad="https://example.org",bootfile_name="https://127.0.0.1/boot.ipxe",path_prefix="/tftpboot",domain_search_list="ovn.org,abc.ovn.org,def.ovn.org,ovn.test,def.ovn.test,test.org,abc.com");
> >      formats as reg2[5] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.254.0, mtu = 1400, domain_name = "ovn.org", wpad = "https://example.org", bootfile_name = "https://127.0.0.1/boot.ipxe", path_prefix = "/tftpboot", domain_search_list = "ovn.org,abc.ovn.org,def.ovn.org,ovn.test,def.ovn.test,test.org,abc.com");
> > -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.09.2f.74.66.74.70.62.6f.6f.74.77.35.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00.03.64.65.66.c0.00.03.6f.76.6e.04.74.65.73.74.00.03.64.65.66.c0.15.04.74.65.73.74.c0.04.03.61.62.63.03.63.6f.6d.00,pause)
> > +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.fe.00.1a.00.02.05.78.0f.00.07.6f.76.6e.2e.6f.72.67.fc.00.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.00.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.00.09.2f.74.66.74.70.62.6f.6f.74.77.00.35.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00.03.64.65.66.c0.00.03.6f.76.6e.04.74.65.73.74.00.03.64.65.66.c0.15.04.74.65.73.74.c0.04.03.61.62.63.03.63.6f.6d.00,pause)
> >
> >  reg1[0..1] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1);
> >      Cannot use 2-bit field reg1[0..1] where 1-bit field is required.
> > @@ -5302,10 +5302,10 @@ sleep 2
> >  as hv1 ovs-vsctl show
> >
> >  # This shell function sends a DHCP request packet
> > -# test_dhcp INPORT SRC_MAC DHCP_TYPE BROADCAST CIADDR OFFER_IP REQUEST_IP USE_IP ...
> > +# test_dhcp INPORT SRC_MAC DHCP_TYPE BROADCAST CIADDR OFFER_IP REQUEST_IP ETH_BOOT USE_IP ...
> >  test_dhcp() {
> > -    local inport=$1 src_mac=$2 dhcp_type=$3 broadcast=$4 ciaddr=$5 offer_ip=$6 request_ip=$7 use_ip=$8
> > -    shift; shift; shift; shift; shift; shift; shift; shift;
> > +    local inport=$1 src_mac=$2 dhcp_type=$3 broadcast=$4 ciaddr=$5 offer_ip=$6 request_ip=$7 eth_boot=$8 use_ip=$9
> > +    shift; shift; shift; shift; shift; shift; shift; shift; shift;
> >
> >      if test $use_ip != 0; then
> >          src_ip=$1
> > @@ -5317,11 +5317,21 @@ test_dhcp() {
> >      fi
> >
> >      if test $request_ip != 0; then
> > -        ip_len=0120
> > -        udp_len=010b
> > +        if test $eth_boot != 0; then
> > +            ip_len=0124
> > +            udp_len=010f
> > +        else
> > +            ip_len=0120
> > +            udp_len=010b
> > +        fi
> >      else
> > -        ip_len=011a
> > -        udp_len=0106
> > +        if test $eth_boot != 0; then
> > +            ip_len=011e
> > +            udp_len=010a
> > +        else
> > +            ip_len=011a
> > +            udp_len=0106
> > +        fi
> >      fi
> >
> >      if test $broadcast != 0; then
> > @@ -5362,6 +5372,9 @@ test_dhcp() {
> >          # dhcp requested ip
> >          request=${request}3204${request_ip}
> >      fi
> > +    if test $eth_boot != 0; then
> > +        request=${request}af020000
> > +    fi
> >      # dhcp end option
> >      request=${request}ff
> >
> > @@ -5457,7 +5470,7 @@ server_ip=`ip_to_hex 10 0 0 1`
> >  ciaddr=`ip_to_hex 0 0 0 0`
> >  request_ip=0
> >  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
> > -test_dhcp 1 f00000000001 01 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
> > +test_dhcp 1 f00000000001 01 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
> >
> >  # NXT_RESUMEs should be 1.
> >  OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > @@ -5483,7 +5496,7 @@ server_ip=`ip_to_hex 10 0 0 1`
> >  ciaddr=`ip_to_hex 0 0 0 0`
> >  request_ip=$offer_ip
> >  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
> > -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
> > +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
> >
> >  # NXT_RESUMEs should be 2.
> >  OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > @@ -5507,7 +5520,7 @@ server_ip=`ip_to_hex 10 0 0 1`
> >  ciaddr=`ip_to_hex 0 0 0 0`
> >  request_ip=`ip_to_hex 10 0 0 7`
> >  expected_dhcp_opts=""
> > -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 06 $expected_dhcp_opts
> > +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 06 $expected_dhcp_opts
> >
> >  # NXT_RESUMEs should be 3.
> >  OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > @@ -5531,7 +5544,7 @@ rm -f 2.expected
> >  ciaddr=`ip_to_hex 0 0 0 0`
> >  offer_ip=0
> >  request_ip=0
> > -test_dhcp 2 f00000000002 09 0 $ciaddr $offer_ip $request_ip 0 1 1
> > +test_dhcp 2 f00000000002 09 0 $ciaddr $offer_ip $request_ip 0 0 1 1
> >
> >  # NXT_RESUMEs should be 4.
> >  OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > @@ -5548,18 +5561,18 @@ rm -f 2.expected
> >  # ls2-lp2 (vif4-tx.pcap) should receive the DHCPv4 request packet once.
> >
> >  ciaddr=`ip_to_hex 0 0 0 0`
> > -test_dhcp 3 f00000000003 01 0 $ciaddr 0 0 4 0
> > +test_dhcp 3 f00000000003 01 0 $ciaddr 0 0 0 4 0
> >
> >  # Send DHCPv4 packet on ls2-lp2. "router" DHCPv4 option is not defined for
> >  # this lport.
> >  ciaddr=`ip_to_hex 0 0 0 0`
> > -test_dhcp 4 f00000000004 01 0 $ciaddr 0 0 3 0
> > +test_dhcp 4 f00000000004 01 0 $ciaddr 0 0 0 3 0
> >
> >  # NXT_RESUMEs should be 4.
> >  OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> >
> > -#OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [3.expected])
> > -#OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [4.expected])
> > +OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [3.expected])
> > +OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [4.expected])
> >
> >  # Send DHCPREQUEST in the RENEWING/REBINDING state with ip4.src set to 10.0.0.6
> >  # and ip4.dst set to 10.0.0.1.
> > @@ -5570,7 +5583,7 @@ request_ip=0
> >  src_ip=$offer_ip
> >  dst_ip=$server_ip
> >  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
> > -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> > +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> >
> >  # NXT_RESUMEs should be 5.
> >  OVS_WAIT_UNTIL([test 5 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > @@ -5596,7 +5609,7 @@ request_ip=0
> >  src_ip=$offer_ip
> >  dst_ip=`ip_to_hex 255 255 255 255`
> >  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
> > -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> > +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> >
> >  # NXT_RESUMEs should be 6.
> >  OVS_WAIT_UNTIL([test 6 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > @@ -5622,7 +5635,7 @@ request_ip=0
> >  src_ip=$offer_ip
> >  dst_ip=`ip_to_hex 255 255 255 255`
> >  expected_dhcp_opts=""
> > -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts
> > +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts
> >
> >  # NXT_RESUMEs should be 7.
> >  OVS_WAIT_UNTIL([test 7 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > @@ -5648,7 +5661,7 @@ request_ip=0
> >  src_ip=$offer_ip
> >  dst_ip=`ip_to_hex 255 255 255 255`
> >  expected_dhcp_opts=""
> > -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts
> > +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts
> >
> >  # NXT_RESUMEs should be 8.
> >  OVS_WAIT_UNTIL([test 8 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > @@ -5670,7 +5683,7 @@ rm -f 2.expected
> >  ciaddr=`ip_to_hex 0 0 0 0`
> >  src_ip=`ip_to_hex 10 0 0 6`
> >  dst_ip=`ip_to_hex 10 0 0 4`
> > -test_dhcp 2 f00000000002 03 0 $ciaddr 0 0 1 $src_ip $dst_ip 1
> > +test_dhcp 2 f00000000002 03 0 $ciaddr 0 0 0 1 $src_ip $dst_ip 1
> >
> >  # NXT_RESUMEs should be 8.
> >  OVS_WAIT_UNTIL([test 8 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > @@ -5689,7 +5702,7 @@ server_ip=`ip_to_hex 10 0 0 1`
> >  ciaddr=`ip_to_hex 0 0 0 0`
> >  request_ip=0
> >  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
> > -test_dhcp 1 f00000000001 01 1 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
> > +test_dhcp 1 f00000000001 01 1 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
> >
> >  # NXT_RESUMEs should be 9.
> >  OVS_WAIT_UNTIL([test 9 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > @@ -5712,7 +5725,7 @@ server_ip=`ip_to_hex 10 0 0 1`
> >  ciaddr=`ip_to_hex 10 0 0 6`
> >  request_ip=0
> >  expected_dhcp_opts=0
> > -test_dhcp 2 f00000000002 07 0 $ciaddr $offer_ip $request_ip 0 ff1000000001
> > +test_dhcp 2 f00000000002 07 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001
> >
> >  # NXT_RESUMEs should be 10.
> >  OVS_WAIT_UNTIL([test 10 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)])
> > @@ -5739,7 +5752,7 @@ dst_ip=$server_ip
> >  # In the expected_dhcp_opts we should not see 330400000e10 which is
> >  # dhcp lease time option and 0104ffffff00 which is subnet mask option.
> >  expected_dhcp_opts=03040a00000136040a000001
> > -test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> > +test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> >
> >  # NXT_RESUMEs should be 11.
> >  OVS_WAIT_UNTIL([test 11 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)])
> > @@ -5769,7 +5782,7 @@ dst_ip=$server_ip
> >  # In the expected_dhcp_opts we should not see 330400000e10 which is
> >  # dhcp lease time option.
> >  expected_dhcp_opts=3a0400000fa0330400000e100104ffffff0003040a00000136040a000001
> > -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> > +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> >
> >  # NXT_RESUMEs should be 12.
> >  OVS_WAIT_UNTIL([test 12 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)])
> > @@ -5796,7 +5809,7 @@ dst_ip=$server_ip
> >  # In the expected_dhcp_opts we should not see 330400000e10 which is
> >  # dhcp lease time option and 0104ffffff00 which is subnet mask option.
> >  expected_dhcp_opts=03040a00000136040a000001
> > -test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> > +test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> >
> >  # NXT_RESUMEs should be 13.
> >  OVS_WAIT_UNTIL([test 13 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)])
> > @@ -5827,7 +5840,7 @@ server_ip=`ip_to_hex 10 0 0 1`
> >  ciaddr=`ip_to_hex 0 0 0 0`
> >  request_ip=$offer_ip
> >  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a00000142040a0a0a0a
> > -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
> > +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
> >
> >  # NXT_RESUMEs should be 14.
> >  OVS_WAIT_UNTIL([test 14 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > @@ -5858,7 +5871,7 @@ server_ip=`ip_to_hex 10 0 0 1`
> >  ciaddr=`ip_to_hex 0 0 0 0`
> >  request_ip=$offer_ip
> >  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a0000014210746573745f746674705f736572766572
> > -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
> > +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
> >
> >  # NXT_RESUMEs should be 15.
> >  OVS_WAIT_UNTIL([test 15 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > @@ -5889,7 +5902,7 @@ server_ip=`ip_to_hex 10 0 0 1`
> >  ciaddr=`ip_to_hex 0 0 0 0`
> >  request_ip=$offer_ip
> >  expected_dhcp_opts=771305746573743103636f6d00057465737432c006330400000e100104ffffff0003040a00000136040a000001
> > -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
> > +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
> >
> >  # NXT_RESUMEs should be 16.
> >  OVS_WAIT_UNTIL([test 16 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > @@ -5907,9 +5920,42 @@ server_ip=`ip_to_hex 10 0 0 1`
> >  ciaddr=`ip_to_hex 0 0 0 0`
> >  request_ip=0
> >  expected_dhcp_opts=""
> > -test_dhcp 1 f00000000001 04 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
> > +test_dhcp 1 f00000000001 04 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
> >  AT_CHECK([fgrep -iq 'DHCPDECLINE from f0:00:00:00:00:01, 10.0.0.4 duplicated' hv1/ovn-controller.log], [0], [])
> >
> > +# Send Etherboot.
> > +
> > +reset_pcap_file hv1-vif1 hv1/vif1
> > +reset_pcap_file hv1-vif2 hv1/vif2
> > +rm -f 1.expected
> > +rm -f 2.expected
> > +
> > +ovn-nbctl --all destroy dhcp-option
> > +
> > +ovn-nbctl dhcp-options-create 10.0.0.0/24
> > +d3=$(ovn-nbctl --bare --columns=_uuid find dhcp_options cidr="10.0.0.0/24")
> > +ovn-nbctl dhcp-options-set-options $d3 \
> > +   server_id=10.0.0.1 server_mac=ff:10:00:00:00:01 \
> > +   lease_time=3600 router=10.0.0.1 bootfile_name_alt=\"filename\" \
> > +   bootfile_name=\"bootfile\"
> > +
> > +ovn-nbctl lsp-set-dhcpv4-options ls1-lp1 $d3
> > +
> > +offer_ip=`ip_to_hex 10 0 0 4`
> > +server_ip=`ip_to_hex 10 0 0 1`
> > +ciaddr=`ip_to_hex 0 0 0 0`
> > +request_ip=0
> > +boofile=4308626f6f7466696c65
> > +expected_dhcp_opts=${boofile}330400000e100104ffffff0003040a00000136040a000001
> > +test_dhcp 1 f00000000001 01 0 $ciaddr $offer_ip $request_ip 1 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
> > +
> > +$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
> > +cat 1.expected | cut -c -48 > expout
> > +AT_CHECK([cat 1.packets | cut -c -48], [0], [expout])
> > +# Skipping the IPv4 checksum.
> > +cat 1.expected | cut -c 53- > expout
> > +AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout])
> > +
> >  OVN_CLEANUP([hv1])
> >
> >  AT_CLEANUP
> > diff --git a/tests/test-ovn.c b/tests/test-ovn.c
> > index d94ab025d..96763be60 100644
> > --- a/tests/test-ovn.c
> > +++ b/tests/test-ovn.c
> > @@ -191,6 +191,7 @@ create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts,
> >      dhcp_opt_add(dhcp_opts, "arp_cache_timeout", 35, "uint32");
> >      dhcp_opt_add(dhcp_opts, "tcp_keepalive_interval", 38, "uint32");
> >      dhcp_opt_add(dhcp_opts, "domain_search_list", 119, "domains");
> > +    dhcp_opt_add(dhcp_opts, "bootfile_name_alt", 300, "str");
> >
> >      /* DHCPv6 options. */
> >      hmap_init(dhcpv6_opts);
> > --
> > 2.26.2
> >
> > _______________________________________________
> > dev mailing list
> > dev@openvswitch.org
> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> >
>
Numan Siddique Oct. 22, 2020, 1:05 p.m. UTC | #3
On Thu, Oct 22, 2020 at 6:04 PM Lorenzo Bianconi
<lorenzo.bianconi@redhat.com> wrote:
>
> > On Tue, Oct 13, 2020 at 4:45 PM Lorenzo Bianconi
> > <lorenzo.bianconi@redhat.com> wrote:
> > >
> > > Introduce the possibility to specify more than 255 options in
> > > put_dhcp_opts userdata buffer. This is useful to support conditional
> > > options for the DHCP reply. (e.g option 67 used in iPXE protocol).
> > > Express the option code using 2 bytes instead of just one in userdata
> > > buffer. Moreover, parse userdata buffer in pinctrl_handle_put_dhcp_opts
> > > in order to distinguish between 'optional' and 'fixed' options.
> > >
> > > Add iPXE support to OVN introducing "bootfile_name_alt" dhcp option.
> > > When both "bootfile_name" and "bootfile_name_alt" are provided
> > > by the CMS, "bootfile_name" will be used for option 67 if the
> > > dhcp request contains etherboot option (175), otherwise
> > > "bootfile_name_alt" will be used.
> > >
> > > Tested-by: Lucas Alvares Gomes Martins <lmartins@redhat.com>
> > > Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
> >
> > Hi Lorenzo,
>
> Hi Numan,
>
> thx for the review.
>
> >
> > Thanks for the patch. If I understand correctly, you are encoding all
> > the dhcp codes as 2 bytes in the controller userdata
> > buffer right ? So that you can encode the  bootfile_alt option as 300 ?
>
> correct
>
> >
> > The drawback of this approach is that it increases the userdata buffer
> > a bit and pinctrl thread needs to reencode the dhcp options
> > to the standard format - i.e 1 byte code, 1 byte length.
> >
> > I have another approach.
> > * We can assign a private unused dhcp option code for bootfile_alt -
> > may be a code between 224 and 254 -
> > https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml
> >    Since the chosen option code will not be part of the actual dhcp
> > reply, I think it should be fine.
> >
> > * We can encode the bootfile_name (67) and bootfile_name_alt (let's
> > say we chose 224) soon after the offer_ip in the userdata buffer (in
> > actions.c) and then encode the rest of the options.
> >
> > * When pinctrl thread has to send the dhcp reply, it will check if 67
> > and 224 are present in the userdata buffer. It can easily check since
> > those will be encoded after offer_ip.
> >
> > * If the dhcp request has etherboot option, it can put the
> > bootfile_name in option 67, else it can put bootfile_name_alt in
> > option 67.
> >
> > * It can then just blindly copy the rest of the userdata buffer in the
> > reply like we do now.
> >
> > What do you think ?
>
> I think it will work, we will just encode the bootfile_name_alt as option 224
> in the userdata buffer. The only problem I can see is if option 224 will be
> used in the future, but I am fine with your approach. What do you think?

I think we can handle that in the future. We can probably choose 254 too.
I'd suggest adding appropriate comments in the code about the
assumptions we are making.

Thanks
Numan

>
> Regards,
> Lorenzo
>
> >
> > Thanks
> > Numan
> >
> >
> > > ---
> > >  controller/pinctrl.c |  56 +++++++++++++++++---
> > >  lib/actions.c        |  28 +++++-----
> > >  lib/ovn-l7.h         |  11 ++++
> > >  northd/ovn-northd.c  |   1 +
> > >  ovn-nb.xml           |  10 ++++
> > >  ovn-sb.ovsschema     |   6 +--
> > >  tests/ovn.at         | 118 ++++++++++++++++++++++++++++++-------------
> > >  tests/test-ovn.c     |   1 +
> > >  8 files changed, 171 insertions(+), 60 deletions(-)
> > >
> > > diff --git a/controller/pinctrl.c b/controller/pinctrl.c
> > > index 0a7020533..260184566 100644
> > > --- a/controller/pinctrl.c
> > > +++ b/controller/pinctrl.c
> > > @@ -1766,6 +1766,7 @@ pinctrl_handle_put_dhcp_opts(
> > >      }
> > >      in_dhcp_ptr += sizeof magic_cookie;
> > >
> > > +    bool ipxe_req = false;
> > >      const uint8_t *in_dhcp_msg_type = NULL;
> > >      ovs_be32 request_ip = in_dhcp_data->ciaddr;
> > >      while (in_dhcp_ptr < end) {
> > > @@ -1798,6 +1799,9 @@ pinctrl_handle_put_dhcp_opts(
> > >                  request_ip = get_unaligned_be32(DHCP_OPT_PAYLOAD(in_dhcp_opt));
> > >              }
> > >              break;
> > > +        case DHCP_OPT_ETHERBOOT:
> > > +            ipxe_req = true;
> > > +            break;
> > >          default:
> > >              break;
> > >          }
> > > @@ -1855,8 +1859,8 @@ pinctrl_handle_put_dhcp_opts(
> > >
> > >          reply_dhcp_opts_ptr = dhcp_inform_reply_buf;
> > >          while (in_dhcp_ptr < end) {
> > > -            const struct dhcp_opt_header *in_dhcp_opt =
> > > -                (const struct dhcp_opt_header *)in_dhcp_ptr;
> > > +            const struct dhcp_opt_userdata_hdr *in_dhcp_opt =
> > > +                (const struct dhcp_opt_userdata_hdr *)in_dhcp_ptr;
> > >
> > >              switch (in_dhcp_opt->code) {
> > >              case OVN_DHCP_OPT_CODE_NETMASK:
> > > @@ -1916,9 +1920,47 @@ pinctrl_handle_put_dhcp_opts(
> > >       *| 4 Bytes padding | 1 Byte (option end 0xFF ) | 4 Bytes padding|
> > >       * --------------------------------------------------------------
> > >       */
> > > +    /* compute opt len */
> > > +    unsigned char *boot_filename_alt = NULL, *boot_filname_opt_ptr = NULL;
> > > +    unsigned char *ptr = (unsigned char *)reply_dhcp_opts_ptr->data;
> > > +    uint16_t boot_filename_len = 0, boot_filename_alt_len = 0;
> > > +    unsigned char *ptr_end = ptr + reply_dhcp_opts_ptr->size;
> > > +    unsigned char *buf = xmalloc(reply_dhcp_opts_ptr->size);
> > > +    uint16_t opt_len = 0;
> > > +
> > > +    while (ptr < ptr_end) {
> > > +        const struct dhcp_opt_userdata_hdr *in_dhcp_opt =
> > > +            (const struct dhcp_opt_userdata_hdr *)ptr;
> > > +
> > > +        switch (in_dhcp_opt->code) {
> > > +        case 300: /* DHCP_OPT_BOOTFILE_ALT */
> > > +          boot_filename_alt = ptr + sizeof *in_dhcp_opt;
> > > +          boot_filename_alt_len = in_dhcp_opt->len;
> > > +          break;
> > > +        case 67: /* DHCP_OPT_BOOTFILENAME */
> > > +          boot_filname_opt_ptr = &buf[opt_len];
> > > +          boot_filename_len = in_dhcp_opt->len;
> > > +          /* fall through */
> > > +        default:
> > > +          /* code < 255 */
> > > +          buf[opt_len] = in_dhcp_opt->code;
> > > +          memcpy(buf + 1 + opt_len, ptr + 2, in_dhcp_opt->len + 1);
> > > +          opt_len += 2 + in_dhcp_opt->len;
> > > +          break;
> > > +        }
> > > +        ptr += sizeof *in_dhcp_opt + in_dhcp_opt->len;
> > > +    }
> > > +
> > > +    if (!ipxe_req && boot_filename_alt_len > 0 && boot_filename_len > 0) {
> > > +        memcpy(&boot_filname_opt_ptr[2], boot_filename_alt,
> > > +               boot_filename_alt_len);
> > > +        boot_filname_opt_ptr[1] = boot_filename_alt_len;
> > > +        opt_len += boot_filename_alt_len - boot_filename_len;
> > > +    }
> > > +
> > >      uint16_t new_l4_size = UDP_HEADER_LEN + DHCP_HEADER_LEN + 16;
> > >      if (msg_type != DHCP_MSG_NAK) {
> > > -        new_l4_size += reply_dhcp_opts_ptr->size;
> > > +        new_l4_size += opt_len;
> > >      }
> > >      size_t new_packet_size = pkt_in->l4_ofs + new_l4_size;
> > >
> > > @@ -1954,7 +1996,7 @@ pinctrl_handle_put_dhcp_opts(
> > >
> > >      uint16_t out_dhcp_opts_size = 12;
> > >      if (msg_type != DHCP_MSG_NAK) {
> > > -      out_dhcp_opts_size += reply_dhcp_opts_ptr->size;
> > > +      out_dhcp_opts_size += opt_len;
> > >      }
> > >      uint8_t *out_dhcp_opts = dp_packet_put_zeros(&pkt_out,
> > >                                                   out_dhcp_opts_size);
> > > @@ -1965,9 +2007,8 @@ pinctrl_handle_put_dhcp_opts(
> > >      out_dhcp_opts += 3;
> > >
> > >      if (msg_type != DHCP_MSG_NAK) {
> > > -        memcpy(out_dhcp_opts, reply_dhcp_opts_ptr->data,
> > > -               reply_dhcp_opts_ptr->size);
> > > -        out_dhcp_opts += reply_dhcp_opts_ptr->size;
> > > +      memcpy(out_dhcp_opts, buf, opt_len);
> > > +      out_dhcp_opts += opt_len;
> > >      }
> > >
> > >      /* Padding */
> > > @@ -2005,6 +2046,7 @@ pinctrl_handle_put_dhcp_opts(
> > >                   ETH_ADDR_ARGS(l2->eth_src), IP_ARGS(*offer_ip));
> > >
> > >      success = 1;
> > > +    free(buf);
> > >  exit:
> > >      if (!ofperr) {
> > >          union mf_subvalue sv;
> > > diff --git a/lib/actions.c b/lib/actions.c
> > > index 5fe0a3897..48726b7f5 100644
> > > --- a/lib/actions.c
> > > +++ b/lib/actions.c
> > > @@ -2308,23 +2308,23 @@ static void
> > >  encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
> > >                           struct ofpbuf *ofpacts)
> > >  {
> > > -    uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2);
> > > -    opt_header[0] = o->option->code;
> > > +    uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 3);
> > >
> > > +    memcpy(opt_header, &o->option->code, 2);
> > >      const union expr_constant *c = o->value.values;
> > >      size_t n_values = o->value.n_values;
> > >      if (!strcmp(o->option->type, "bool") ||
> > >          !strcmp(o->option->type, "uint8")) {
> > > -        opt_header[1] = 1;
> > > +        opt_header[2] = 1;
> > >          ofpbuf_put(ofpacts, &c->value.u8_val, 1);
> > >      } else if (!strcmp(o->option->type, "uint16")) {
> > > -        opt_header[1] = 2;
> > > +        opt_header[2] = 2;
> > >          ofpbuf_put(ofpacts, &c->value.be16_int, 2);
> > >      } else if (!strcmp(o->option->type, "uint32")) {
> > > -        opt_header[1] = 4;
> > > +        opt_header[2] = 4;
> > >          ofpbuf_put(ofpacts, &c->value.be32_int, 4);
> > >      } else if (!strcmp(o->option->type, "ipv4")) {
> > > -        opt_header[1] = n_values * sizeof(ovs_be32);
> > > +        opt_header[2] = n_values * sizeof(ovs_be32);
> > >          for (size_t i = 0; i < n_values; i++) {
> > >              ofpbuf_put(ofpacts, &c[i].value.ipv4, sizeof(ovs_be32));
> > >          }
> > > @@ -2333,7 +2333,7 @@ encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
> > >          if (no_of_routes % 2) {
> > >              no_of_routes -= 1;
> > >          }
> > > -        opt_header[1] = 0;
> > > +        opt_header[2] = 0;
> > >
> > >          /* Calculating the length of this option first because when
> > >           * we call ofpbuf_put, it might reallocate the buffer if the
> > > @@ -2345,7 +2345,7 @@ encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
> > >              if (c[i].masked) {
> > >                  plen = (uint8_t) ip_count_cidr_bits(c[i].mask.ipv4);
> > >              }
> > > -            opt_header[1] += (1 + DIV_ROUND_UP(plen, 8) + sizeof(ovs_be32));
> > > +            opt_header[2] += (1 + DIV_ROUND_UP(plen, 8) + sizeof(ovs_be32));
> > >          }
> > >
> > >          /* Copied from RFC 3442. Please refer to this RFC for the format of
> > > @@ -2375,14 +2375,14 @@ encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
> > >                         sizeof(ovs_be32));
> > >          }
> > >      } else if (!strcmp(o->option->type, "str")) {
> > > -        opt_header[1] = strlen(c->string);
> > > -        ofpbuf_put(ofpacts, c->string, opt_header[1]);
> > > +        opt_header[2] = strlen(c->string);
> > > +        ofpbuf_put(ofpacts, c->string, opt_header[2]);
> > >      } else if (!strcmp(o->option->type, "host_id")) {
> > >          if (o->value.type == EXPR_C_STRING) {
> > > -            opt_header[1] = strlen(c->string);
> > > -            ofpbuf_put(ofpacts, c->string, opt_header[1]);
> > > +            opt_header[2] = strlen(c->string);
> > > +            ofpbuf_put(ofpacts, c->string, opt_header[2]);
> > >          } else {
> > > -           opt_header[1] = sizeof(ovs_be32);
> > > +           opt_header[2] = sizeof(ovs_be32);
> > >             ofpbuf_put(ofpacts, &c->value.ipv4, sizeof(ovs_be32));
> > >          }
> > >      } else if (!strcmp(o->option->type, "domains")) {
> > > @@ -2461,7 +2461,7 @@ encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
> > >                  encode_offset += sizeof(uint8_t);
> > >              }
> > >          }
> > > -        opt_header[1] = encode_offset;
> > > +        opt_header[2] = encode_offset;
> > >          ofpbuf_put(ofpacts, dns_encoded, encode_offset);
> > >
> > >          out:
> > > diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
> > > index 18c59896d..79c037ba3 100644
> > > --- a/lib/ovn-l7.h
> > > +++ b/lib/ovn-l7.h
> > > @@ -93,6 +93,11 @@ struct gen_opts_map {
> > >  #define DHCP_OPT_DOMAIN_SEARCH_LIST \
> > >      DHCP_OPTION("domain_search_list", 119, "domains")
> > >
> > > +/* fixed DHCP header */
> > > +#define DHCP_OPT_BOOTFILE_ALT DHCP_OPTION("bootfile_name_alt", 300, "str")
> > > +
> > > +#define DHCP_OPT_ETHERBOOT     175
> > > +
> > >  #define DHCP_OPT_ARP_CACHE_TIMEOUT \
> > >      DHCP_OPTION("arp_cache_timeout", 35, "uint32")
> > >  #define DHCP_OPT_TCP_KEEPALIVE_INTERVAL \
> > > @@ -179,6 +184,12 @@ struct dhcp_opt6_header {
> > >      ovs_be16 size;
> > >  };
> > >
> > > +OVS_PACKED(
> > > +struct dhcp_opt_userdata_hdr {
> > > +    uint16_t code;
> > > +    uint8_t len;
> > > +});
> > > +
> > >  /* These are not defined in ovs/lib/dhcp.h, hence defining here. */
> > >  #define OVN_DHCP_MSG_DECLINE        4
> > >  #define OVN_DHCP_MSG_RELEASE        7
> > > diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> > > index 366fdd95f..a9941bb9a 100644
> > > --- a/northd/ovn-northd.c
> > > +++ b/northd/ovn-northd.c
> > > @@ -12514,6 +12514,7 @@ static struct gen_opts_map supported_dhcp_opts[] = {
> > >      DHCP_OPT_ARP_CACHE_TIMEOUT,
> > >      DHCP_OPT_TCP_KEEPALIVE_INTERVAL,
> > >      DHCP_OPT_DOMAIN_SEARCH_LIST,
> > > +    DHCP_OPT_BOOTFILE_ALT,
> > >  };
> > >
> > >  static struct gen_opts_map supported_dhcpv6_opts[] = {
> > > diff --git a/ovn-nb.xml b/ovn-nb.xml
> > > index 86195af34..43694535e 100644
> > > --- a/ovn-nb.xml
> > > +++ b/ovn-nb.xml
> > > @@ -3096,6 +3096,16 @@
> > >              resolving hostnames via the Domain Name System.
> > >            </p>
> > >          </column>
> > > +
> > > +        <column name="options" key="bootfile_name_alt">
> > > +          <p>
> > > +          </p>
> > > +            "bootfile_name_alt" option is used to support iPXE.
> > > +            When both "bootfile_name" and "bootfile_name_alt" are provided
> > > +            by the CMS, "bootfile_name" will be used for option 67 if the
> > > +            dhcp request contains etherboot option (175), otherwise
> > > +            "bootfile_name_alt" will be used.
> > > +        </column>
> > >        </group>
> > >
> > >        <group title="DHCP Options of type host_id">
> > > diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
> > > index d1c506a22..9a684d8c0 100644
> > > --- a/ovn-sb.ovsschema
> > > +++ b/ovn-sb.ovsschema
> > > @@ -1,7 +1,7 @@
> > >  {
> > >      "name": "OVN_Southbound",
> > > -    "version": "2.10.0",
> > > -    "cksum": "2548342632 22615",
> > > +    "version": "2.11.0",
> > > +    "cksum": "1750191326 22616",
> > >      "tables": {
> > >          "SB_Global": {
> > >              "columns": {
> > > @@ -226,7 +226,7 @@
> > >                  "name": {"type": "string"},
> > >                  "code": {
> > >                      "type": {"key": {"type": "integer",
> > > -                                     "minInteger": 0, "maxInteger": 254}}},
> > > +                                     "minInteger": 0, "maxInteger": 1000}}},
> > >                  "type": {
> > >                      "type": {"key": {
> > >                          "type": "string",
> > > diff --git a/tests/ovn.at b/tests/ovn.at
> > > index 488fd119b..9206336ac 100644
> > > --- a/tests/ovn.at
> > > +++ b/tests/ovn.at
> > > @@ -1277,25 +1277,25 @@ reg0[0] = lookup_arp_ip(inport, eth.dst);
> > >
> > >  # put_dhcp_opts
> > >  reg1[0] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1);
> > > -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.40.01.02.03.04.03.04.0a.00.00.01,pause)
> > > +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.40.01.02.03.04.03.00.04.0a.00.00.01,pause)
> > >  reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain_name="ovn.org",wpad="https://example.org",bootfile_name="https://127.0.0.1/boot.ipxe",path_prefix="/tftpboot");
> > >      formats as reg2[5] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.254.0, mtu = 1400, domain_name = "ovn.org", wpad = "https://example.org", bootfile_name = "https://127.0.0.1/boot.ipxe", path_prefix = "/tftpboot");
> > > -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.09.2f.74.66.74.70.62.6f.6f.74,pause)
> > > +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.fe.00.1a.00.02.05.78.0f.00.07.6f.76.6e.2e.6f.72.67.fc.00.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.00.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.00.09.2f.74.66.74.70.62.6f.6f.74,pause)
> > >  reg0[15] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,0.0.0.0/0,10.0.0.1},ethernet_encap=1,router_discovery=0,tftp_server_address={10.0.0.4,10.0.0.5},arp_cache_timeout=10,tcp_keepalive_interval=10);
> > >      formats as reg0[15] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.255.0, mtu = 1400, ip_forward_enable = 1, default_ttl = 121, dns_server = {8.8.8.8, 7.7.7.7}, classless_static_route = {30.0.0.0/24, 10.0.0.4, 40.0.0.0/16, 10.0.0.6, 0.0.0.0/0, 10.0.0.1}, ethernet_encap = 1, router_discovery = 0, tftp_server_address = {10.0.0.4, 10.0.0.5}, arp_cache_timeout = 10, tcp_keepalive_interval = 10);
> > > -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.96.08.0a.00.00.04.0a.00.00.05.23.04.00.00.00.0a.26.04.00.00.00.0a,pause)
> > > +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.ff.00.1a.00.02.05.78.13.00.01.01.17.00.01.79.06.00.08.08.08.08.08.07.07.07.07.79.00.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.00.01.01.1f.00.01.00.96.00.08.0a.00.00.04.0a.00.00.05.23.00.04.00.00.00.0a.26.00.04.00.00.00.0a,pause)
> > >  reg0[15] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,0.0.0.0/0,10.0.0.1},ethernet_encap=1,router_discovery=0,tftp_server=10.0.0.10);
> > >      formats as reg0[15] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.255.0, mtu = 1400, ip_forward_enable = 1, default_ttl = 121, dns_server = {8.8.8.8, 7.7.7.7}, classless_static_route = {30.0.0.0/24, 10.0.0.4, 40.0.0.0/16, 10.0.0.6, 0.0.0.0/0, 10.0.0.1}, ethernet_encap = 1, router_discovery = 0, tftp_server = 10.0.0.10);
> > > -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.42.04.0a.00.00.0a,pause)
> > > +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.ff.00.1a.00.02.05.78.13.00.01.01.17.00.01.79.06.00.08.08.08.08.08.07.07.07.07.79.00.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.00.01.01.1f.00.01.00.42.00.04.0a.00.00.0a,pause)
> > >  reg0[15] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,0.0.0.0/0,10.0.0.1},ethernet_encap=1,router_discovery=0,tftp_server="tftp_server_test");
> > >      formats as reg0[15] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.255.0, mtu = 1400, ip_forward_enable = 1, default_ttl = 121, dns_server = {8.8.8.8, 7.7.7.7}, classless_static_route = {30.0.0.0/24, 10.0.0.4, 40.0.0.0/16, 10.0.0.6, 0.0.0.0/0, 10.0.0.1}, ethernet_encap = 1, router_discovery = 0, tftp_server = "tftp_server_test");
> > > -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.42.10.74.66.74.70.5f.73.65.72.76.65.72.5f.74.65.73.74,pause)
> > > +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.ff.00.1a.00.02.05.78.13.00.01.01.17.00.01.79.06.00.08.08.08.08.08.07.07.07.07.79.00.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.00.01.01.1f.00.01.00.42.00.10.74.66.74.70.5f.73.65.72.76.65.72.5f.74.65.73.74,pause)
> > >  reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain_name="ovn.org",wpad="https://example.org",bootfile_name="https://127.0.0.1/boot.ipxe",path_prefix="/tftpboot",domain_search_list="ovn.org,abc.ovn.org");
> > >      formats as reg2[5] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.254.0, mtu = 1400, domain_name = "ovn.org", wpad = "https://example.org", bootfile_name = "https://127.0.0.1/boot.ipxe", path_prefix = "/tftpboot", domain_search_list = "ovn.org,abc.ovn.org");
> > > -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.09.2f.74.66.74.70.62.6f.6f.74.77.0f.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00,pause)
> > > +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.fe.00.1a.00.02.05.78.0f.00.07.6f.76.6e.2e.6f.72.67.fc.00.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.00.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.00.09.2f.74.66.74.70.62.6f.6f.74.77.00.0f.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00,pause)
> > >  reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain_name="ovn.org",wpad="https://example.org",bootfile_name="https://127.0.0.1/boot.ipxe",path_prefix="/tftpboot",domain_search_list="ovn.org,abc.ovn.org,def.ovn.org,ovn.test,def.ovn.test,test.org,abc.com");
> > >      formats as reg2[5] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.254.0, mtu = 1400, domain_name = "ovn.org", wpad = "https://example.org", bootfile_name = "https://127.0.0.1/boot.ipxe", path_prefix = "/tftpboot", domain_search_list = "ovn.org,abc.ovn.org,def.ovn.org,ovn.test,def.ovn.test,test.org,abc.com");
> > > -    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.09.2f.74.66.74.70.62.6f.6f.74.77.35.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00.03.64.65.66.c0.00.03.6f.76.6e.04.74.65.73.74.00.03.64.65.66.c0.15.04.74.65.73.74.c0.04.03.61.62.63.03.63.6f.6d.00,pause)
> > > +    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.fe.00.1a.00.02.05.78.0f.00.07.6f.76.6e.2e.6f.72.67.fc.00.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.00.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.00.09.2f.74.66.74.70.62.6f.6f.74.77.00.35.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00.03.64.65.66.c0.00.03.6f.76.6e.04.74.65.73.74.00.03.64.65.66.c0.15.04.74.65.73.74.c0.04.03.61.62.63.03.63.6f.6d.00,pause)
> > >
> > >  reg1[0..1] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1);
> > >      Cannot use 2-bit field reg1[0..1] where 1-bit field is required.
> > > @@ -5302,10 +5302,10 @@ sleep 2
> > >  as hv1 ovs-vsctl show
> > >
> > >  # This shell function sends a DHCP request packet
> > > -# test_dhcp INPORT SRC_MAC DHCP_TYPE BROADCAST CIADDR OFFER_IP REQUEST_IP USE_IP ...
> > > +# test_dhcp INPORT SRC_MAC DHCP_TYPE BROADCAST CIADDR OFFER_IP REQUEST_IP ETH_BOOT USE_IP ...
> > >  test_dhcp() {
> > > -    local inport=$1 src_mac=$2 dhcp_type=$3 broadcast=$4 ciaddr=$5 offer_ip=$6 request_ip=$7 use_ip=$8
> > > -    shift; shift; shift; shift; shift; shift; shift; shift;
> > > +    local inport=$1 src_mac=$2 dhcp_type=$3 broadcast=$4 ciaddr=$5 offer_ip=$6 request_ip=$7 eth_boot=$8 use_ip=$9
> > > +    shift; shift; shift; shift; shift; shift; shift; shift; shift;
> > >
> > >      if test $use_ip != 0; then
> > >          src_ip=$1
> > > @@ -5317,11 +5317,21 @@ test_dhcp() {
> > >      fi
> > >
> > >      if test $request_ip != 0; then
> > > -        ip_len=0120
> > > -        udp_len=010b
> > > +        if test $eth_boot != 0; then
> > > +            ip_len=0124
> > > +            udp_len=010f
> > > +        else
> > > +            ip_len=0120
> > > +            udp_len=010b
> > > +        fi
> > >      else
> > > -        ip_len=011a
> > > -        udp_len=0106
> > > +        if test $eth_boot != 0; then
> > > +            ip_len=011e
> > > +            udp_len=010a
> > > +        else
> > > +            ip_len=011a
> > > +            udp_len=0106
> > > +        fi
> > >      fi
> > >
> > >      if test $broadcast != 0; then
> > > @@ -5362,6 +5372,9 @@ test_dhcp() {
> > >          # dhcp requested ip
> > >          request=${request}3204${request_ip}
> > >      fi
> > > +    if test $eth_boot != 0; then
> > > +        request=${request}af020000
> > > +    fi
> > >      # dhcp end option
> > >      request=${request}ff
> > >
> > > @@ -5457,7 +5470,7 @@ server_ip=`ip_to_hex 10 0 0 1`
> > >  ciaddr=`ip_to_hex 0 0 0 0`
> > >  request_ip=0
> > >  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
> > > -test_dhcp 1 f00000000001 01 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
> > > +test_dhcp 1 f00000000001 01 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
> > >
> > >  # NXT_RESUMEs should be 1.
> > >  OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > > @@ -5483,7 +5496,7 @@ server_ip=`ip_to_hex 10 0 0 1`
> > >  ciaddr=`ip_to_hex 0 0 0 0`
> > >  request_ip=$offer_ip
> > >  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
> > > -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
> > > +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
> > >
> > >  # NXT_RESUMEs should be 2.
> > >  OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > > @@ -5507,7 +5520,7 @@ server_ip=`ip_to_hex 10 0 0 1`
> > >  ciaddr=`ip_to_hex 0 0 0 0`
> > >  request_ip=`ip_to_hex 10 0 0 7`
> > >  expected_dhcp_opts=""
> > > -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 06 $expected_dhcp_opts
> > > +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 06 $expected_dhcp_opts
> > >
> > >  # NXT_RESUMEs should be 3.
> > >  OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > > @@ -5531,7 +5544,7 @@ rm -f 2.expected
> > >  ciaddr=`ip_to_hex 0 0 0 0`
> > >  offer_ip=0
> > >  request_ip=0
> > > -test_dhcp 2 f00000000002 09 0 $ciaddr $offer_ip $request_ip 0 1 1
> > > +test_dhcp 2 f00000000002 09 0 $ciaddr $offer_ip $request_ip 0 0 1 1
> > >
> > >  # NXT_RESUMEs should be 4.
> > >  OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > > @@ -5548,18 +5561,18 @@ rm -f 2.expected
> > >  # ls2-lp2 (vif4-tx.pcap) should receive the DHCPv4 request packet once.
> > >
> > >  ciaddr=`ip_to_hex 0 0 0 0`
> > > -test_dhcp 3 f00000000003 01 0 $ciaddr 0 0 4 0
> > > +test_dhcp 3 f00000000003 01 0 $ciaddr 0 0 0 4 0
> > >
> > >  # Send DHCPv4 packet on ls2-lp2. "router" DHCPv4 option is not defined for
> > >  # this lport.
> > >  ciaddr=`ip_to_hex 0 0 0 0`
> > > -test_dhcp 4 f00000000004 01 0 $ciaddr 0 0 3 0
> > > +test_dhcp 4 f00000000004 01 0 $ciaddr 0 0 0 3 0
> > >
> > >  # NXT_RESUMEs should be 4.
> > >  OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > >
> > > -#OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [3.expected])
> > > -#OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [4.expected])
> > > +OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [3.expected])
> > > +OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [4.expected])
> > >
> > >  # Send DHCPREQUEST in the RENEWING/REBINDING state with ip4.src set to 10.0.0.6
> > >  # and ip4.dst set to 10.0.0.1.
> > > @@ -5570,7 +5583,7 @@ request_ip=0
> > >  src_ip=$offer_ip
> > >  dst_ip=$server_ip
> > >  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
> > > -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> > > +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> > >
> > >  # NXT_RESUMEs should be 5.
> > >  OVS_WAIT_UNTIL([test 5 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > > @@ -5596,7 +5609,7 @@ request_ip=0
> > >  src_ip=$offer_ip
> > >  dst_ip=`ip_to_hex 255 255 255 255`
> > >  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
> > > -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> > > +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> > >
> > >  # NXT_RESUMEs should be 6.
> > >  OVS_WAIT_UNTIL([test 6 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > > @@ -5622,7 +5635,7 @@ request_ip=0
> > >  src_ip=$offer_ip
> > >  dst_ip=`ip_to_hex 255 255 255 255`
> > >  expected_dhcp_opts=""
> > > -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts
> > > +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts
> > >
> > >  # NXT_RESUMEs should be 7.
> > >  OVS_WAIT_UNTIL([test 7 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > > @@ -5648,7 +5661,7 @@ request_ip=0
> > >  src_ip=$offer_ip
> > >  dst_ip=`ip_to_hex 255 255 255 255`
> > >  expected_dhcp_opts=""
> > > -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts
> > > +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts
> > >
> > >  # NXT_RESUMEs should be 8.
> > >  OVS_WAIT_UNTIL([test 8 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > > @@ -5670,7 +5683,7 @@ rm -f 2.expected
> > >  ciaddr=`ip_to_hex 0 0 0 0`
> > >  src_ip=`ip_to_hex 10 0 0 6`
> > >  dst_ip=`ip_to_hex 10 0 0 4`
> > > -test_dhcp 2 f00000000002 03 0 $ciaddr 0 0 1 $src_ip $dst_ip 1
> > > +test_dhcp 2 f00000000002 03 0 $ciaddr 0 0 0 1 $src_ip $dst_ip 1
> > >
> > >  # NXT_RESUMEs should be 8.
> > >  OVS_WAIT_UNTIL([test 8 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > > @@ -5689,7 +5702,7 @@ server_ip=`ip_to_hex 10 0 0 1`
> > >  ciaddr=`ip_to_hex 0 0 0 0`
> > >  request_ip=0
> > >  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
> > > -test_dhcp 1 f00000000001 01 1 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
> > > +test_dhcp 1 f00000000001 01 1 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
> > >
> > >  # NXT_RESUMEs should be 9.
> > >  OVS_WAIT_UNTIL([test 9 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > > @@ -5712,7 +5725,7 @@ server_ip=`ip_to_hex 10 0 0 1`
> > >  ciaddr=`ip_to_hex 10 0 0 6`
> > >  request_ip=0
> > >  expected_dhcp_opts=0
> > > -test_dhcp 2 f00000000002 07 0 $ciaddr $offer_ip $request_ip 0 ff1000000001
> > > +test_dhcp 2 f00000000002 07 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001
> > >
> > >  # NXT_RESUMEs should be 10.
> > >  OVS_WAIT_UNTIL([test 10 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)])
> > > @@ -5739,7 +5752,7 @@ dst_ip=$server_ip
> > >  # In the expected_dhcp_opts we should not see 330400000e10 which is
> > >  # dhcp lease time option and 0104ffffff00 which is subnet mask option.
> > >  expected_dhcp_opts=03040a00000136040a000001
> > > -test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> > > +test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> > >
> > >  # NXT_RESUMEs should be 11.
> > >  OVS_WAIT_UNTIL([test 11 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)])
> > > @@ -5769,7 +5782,7 @@ dst_ip=$server_ip
> > >  # In the expected_dhcp_opts we should not see 330400000e10 which is
> > >  # dhcp lease time option.
> > >  expected_dhcp_opts=3a0400000fa0330400000e100104ffffff0003040a00000136040a000001
> > > -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> > > +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> > >
> > >  # NXT_RESUMEs should be 12.
> > >  OVS_WAIT_UNTIL([test 12 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)])
> > > @@ -5796,7 +5809,7 @@ dst_ip=$server_ip
> > >  # In the expected_dhcp_opts we should not see 330400000e10 which is
> > >  # dhcp lease time option and 0104ffffff00 which is subnet mask option.
> > >  expected_dhcp_opts=03040a00000136040a000001
> > > -test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> > > +test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
> > >
> > >  # NXT_RESUMEs should be 13.
> > >  OVS_WAIT_UNTIL([test 13 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)])
> > > @@ -5827,7 +5840,7 @@ server_ip=`ip_to_hex 10 0 0 1`
> > >  ciaddr=`ip_to_hex 0 0 0 0`
> > >  request_ip=$offer_ip
> > >  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a00000142040a0a0a0a
> > > -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
> > > +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
> > >
> > >  # NXT_RESUMEs should be 14.
> > >  OVS_WAIT_UNTIL([test 14 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > > @@ -5858,7 +5871,7 @@ server_ip=`ip_to_hex 10 0 0 1`
> > >  ciaddr=`ip_to_hex 0 0 0 0`
> > >  request_ip=$offer_ip
> > >  expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a0000014210746573745f746674705f736572766572
> > > -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
> > > +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
> > >
> > >  # NXT_RESUMEs should be 15.
> > >  OVS_WAIT_UNTIL([test 15 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > > @@ -5889,7 +5902,7 @@ server_ip=`ip_to_hex 10 0 0 1`
> > >  ciaddr=`ip_to_hex 0 0 0 0`
> > >  request_ip=$offer_ip
> > >  expected_dhcp_opts=771305746573743103636f6d00057465737432c006330400000e100104ffffff0003040a00000136040a000001
> > > -test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
> > > +test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
> > >
> > >  # NXT_RESUMEs should be 16.
> > >  OVS_WAIT_UNTIL([test 16 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > > @@ -5907,9 +5920,42 @@ server_ip=`ip_to_hex 10 0 0 1`
> > >  ciaddr=`ip_to_hex 0 0 0 0`
> > >  request_ip=0
> > >  expected_dhcp_opts=""
> > > -test_dhcp 1 f00000000001 04 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
> > > +test_dhcp 1 f00000000001 04 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
> > >  AT_CHECK([fgrep -iq 'DHCPDECLINE from f0:00:00:00:00:01, 10.0.0.4 duplicated' hv1/ovn-controller.log], [0], [])
> > >
> > > +# Send Etherboot.
> > > +
> > > +reset_pcap_file hv1-vif1 hv1/vif1
> > > +reset_pcap_file hv1-vif2 hv1/vif2
> > > +rm -f 1.expected
> > > +rm -f 2.expected
> > > +
> > > +ovn-nbctl --all destroy dhcp-option
> > > +
> > > +ovn-nbctl dhcp-options-create 10.0.0.0/24
> > > +d3=$(ovn-nbctl --bare --columns=_uuid find dhcp_options cidr="10.0.0.0/24")
> > > +ovn-nbctl dhcp-options-set-options $d3 \
> > > +   server_id=10.0.0.1 server_mac=ff:10:00:00:00:01 \
> > > +   lease_time=3600 router=10.0.0.1 bootfile_name_alt=\"filename\" \
> > > +   bootfile_name=\"bootfile\"
> > > +
> > > +ovn-nbctl lsp-set-dhcpv4-options ls1-lp1 $d3
> > > +
> > > +offer_ip=`ip_to_hex 10 0 0 4`
> > > +server_ip=`ip_to_hex 10 0 0 1`
> > > +ciaddr=`ip_to_hex 0 0 0 0`
> > > +request_ip=0
> > > +boofile=4308626f6f7466696c65
> > > +expected_dhcp_opts=${boofile}330400000e100104ffffff0003040a00000136040a000001
> > > +test_dhcp 1 f00000000001 01 0 $ciaddr $offer_ip $request_ip 1 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
> > > +
> > > +$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
> > > +cat 1.expected | cut -c -48 > expout
> > > +AT_CHECK([cat 1.packets | cut -c -48], [0], [expout])
> > > +# Skipping the IPv4 checksum.
> > > +cat 1.expected | cut -c 53- > expout
> > > +AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout])
> > > +
> > >  OVN_CLEANUP([hv1])
> > >
> > >  AT_CLEANUP
> > > diff --git a/tests/test-ovn.c b/tests/test-ovn.c
> > > index d94ab025d..96763be60 100644
> > > --- a/tests/test-ovn.c
> > > +++ b/tests/test-ovn.c
> > > @@ -191,6 +191,7 @@ create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts,
> > >      dhcp_opt_add(dhcp_opts, "arp_cache_timeout", 35, "uint32");
> > >      dhcp_opt_add(dhcp_opts, "tcp_keepalive_interval", 38, "uint32");
> > >      dhcp_opt_add(dhcp_opts, "domain_search_list", 119, "domains");
> > > +    dhcp_opt_add(dhcp_opts, "bootfile_name_alt", 300, "str");
> > >
> > >      /* DHCPv6 options. */
> > >      hmap_init(dhcpv6_opts);
> > > --
> > > 2.26.2
> > >
> > > _______________________________________________
> > > dev mailing list
> > > dev@openvswitch.org
> > > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> > >
> >
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
diff mbox series

Patch

diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index 0a7020533..260184566 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -1766,6 +1766,7 @@  pinctrl_handle_put_dhcp_opts(
     }
     in_dhcp_ptr += sizeof magic_cookie;
 
+    bool ipxe_req = false;
     const uint8_t *in_dhcp_msg_type = NULL;
     ovs_be32 request_ip = in_dhcp_data->ciaddr;
     while (in_dhcp_ptr < end) {
@@ -1798,6 +1799,9 @@  pinctrl_handle_put_dhcp_opts(
                 request_ip = get_unaligned_be32(DHCP_OPT_PAYLOAD(in_dhcp_opt));
             }
             break;
+        case DHCP_OPT_ETHERBOOT:
+            ipxe_req = true;
+            break;
         default:
             break;
         }
@@ -1855,8 +1859,8 @@  pinctrl_handle_put_dhcp_opts(
 
         reply_dhcp_opts_ptr = dhcp_inform_reply_buf;
         while (in_dhcp_ptr < end) {
-            const struct dhcp_opt_header *in_dhcp_opt =
-                (const struct dhcp_opt_header *)in_dhcp_ptr;
+            const struct dhcp_opt_userdata_hdr *in_dhcp_opt =
+                (const struct dhcp_opt_userdata_hdr *)in_dhcp_ptr;
 
             switch (in_dhcp_opt->code) {
             case OVN_DHCP_OPT_CODE_NETMASK:
@@ -1916,9 +1920,47 @@  pinctrl_handle_put_dhcp_opts(
      *| 4 Bytes padding | 1 Byte (option end 0xFF ) | 4 Bytes padding|
      * --------------------------------------------------------------
      */
+    /* compute opt len */
+    unsigned char *boot_filename_alt = NULL, *boot_filname_opt_ptr = NULL;
+    unsigned char *ptr = (unsigned char *)reply_dhcp_opts_ptr->data;
+    uint16_t boot_filename_len = 0, boot_filename_alt_len = 0;
+    unsigned char *ptr_end = ptr + reply_dhcp_opts_ptr->size;
+    unsigned char *buf = xmalloc(reply_dhcp_opts_ptr->size);
+    uint16_t opt_len = 0;
+
+    while (ptr < ptr_end) {
+        const struct dhcp_opt_userdata_hdr *in_dhcp_opt =
+            (const struct dhcp_opt_userdata_hdr *)ptr;
+
+        switch (in_dhcp_opt->code) {
+        case 300: /* DHCP_OPT_BOOTFILE_ALT */
+          boot_filename_alt = ptr + sizeof *in_dhcp_opt;
+          boot_filename_alt_len = in_dhcp_opt->len;
+          break;
+        case 67: /* DHCP_OPT_BOOTFILENAME */
+          boot_filname_opt_ptr = &buf[opt_len];
+          boot_filename_len = in_dhcp_opt->len;
+          /* fall through */
+        default:
+          /* code < 255 */
+          buf[opt_len] = in_dhcp_opt->code;
+          memcpy(buf + 1 + opt_len, ptr + 2, in_dhcp_opt->len + 1);
+          opt_len += 2 + in_dhcp_opt->len;
+          break;
+        }
+        ptr += sizeof *in_dhcp_opt + in_dhcp_opt->len;
+    }
+
+    if (!ipxe_req && boot_filename_alt_len > 0 && boot_filename_len > 0) {
+        memcpy(&boot_filname_opt_ptr[2], boot_filename_alt,
+               boot_filename_alt_len);
+        boot_filname_opt_ptr[1] = boot_filename_alt_len;
+        opt_len += boot_filename_alt_len - boot_filename_len;
+    }
+
     uint16_t new_l4_size = UDP_HEADER_LEN + DHCP_HEADER_LEN + 16;
     if (msg_type != DHCP_MSG_NAK) {
-        new_l4_size += reply_dhcp_opts_ptr->size;
+        new_l4_size += opt_len;
     }
     size_t new_packet_size = pkt_in->l4_ofs + new_l4_size;
 
@@ -1954,7 +1996,7 @@  pinctrl_handle_put_dhcp_opts(
 
     uint16_t out_dhcp_opts_size = 12;
     if (msg_type != DHCP_MSG_NAK) {
-      out_dhcp_opts_size += reply_dhcp_opts_ptr->size;
+      out_dhcp_opts_size += opt_len;
     }
     uint8_t *out_dhcp_opts = dp_packet_put_zeros(&pkt_out,
                                                  out_dhcp_opts_size);
@@ -1965,9 +2007,8 @@  pinctrl_handle_put_dhcp_opts(
     out_dhcp_opts += 3;
 
     if (msg_type != DHCP_MSG_NAK) {
-        memcpy(out_dhcp_opts, reply_dhcp_opts_ptr->data,
-               reply_dhcp_opts_ptr->size);
-        out_dhcp_opts += reply_dhcp_opts_ptr->size;
+      memcpy(out_dhcp_opts, buf, opt_len);
+      out_dhcp_opts += opt_len;
     }
 
     /* Padding */
@@ -2005,6 +2046,7 @@  pinctrl_handle_put_dhcp_opts(
                  ETH_ADDR_ARGS(l2->eth_src), IP_ARGS(*offer_ip));
 
     success = 1;
+    free(buf);
 exit:
     if (!ofperr) {
         union mf_subvalue sv;
diff --git a/lib/actions.c b/lib/actions.c
index 5fe0a3897..48726b7f5 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -2308,23 +2308,23 @@  static void
 encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
                          struct ofpbuf *ofpacts)
 {
-    uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 2);
-    opt_header[0] = o->option->code;
+    uint8_t *opt_header = ofpbuf_put_zeros(ofpacts, 3);
 
+    memcpy(opt_header, &o->option->code, 2);
     const union expr_constant *c = o->value.values;
     size_t n_values = o->value.n_values;
     if (!strcmp(o->option->type, "bool") ||
         !strcmp(o->option->type, "uint8")) {
-        opt_header[1] = 1;
+        opt_header[2] = 1;
         ofpbuf_put(ofpacts, &c->value.u8_val, 1);
     } else if (!strcmp(o->option->type, "uint16")) {
-        opt_header[1] = 2;
+        opt_header[2] = 2;
         ofpbuf_put(ofpacts, &c->value.be16_int, 2);
     } else if (!strcmp(o->option->type, "uint32")) {
-        opt_header[1] = 4;
+        opt_header[2] = 4;
         ofpbuf_put(ofpacts, &c->value.be32_int, 4);
     } else if (!strcmp(o->option->type, "ipv4")) {
-        opt_header[1] = n_values * sizeof(ovs_be32);
+        opt_header[2] = n_values * sizeof(ovs_be32);
         for (size_t i = 0; i < n_values; i++) {
             ofpbuf_put(ofpacts, &c[i].value.ipv4, sizeof(ovs_be32));
         }
@@ -2333,7 +2333,7 @@  encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
         if (no_of_routes % 2) {
             no_of_routes -= 1;
         }
-        opt_header[1] = 0;
+        opt_header[2] = 0;
 
         /* Calculating the length of this option first because when
          * we call ofpbuf_put, it might reallocate the buffer if the
@@ -2345,7 +2345,7 @@  encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
             if (c[i].masked) {
                 plen = (uint8_t) ip_count_cidr_bits(c[i].mask.ipv4);
             }
-            opt_header[1] += (1 + DIV_ROUND_UP(plen, 8) + sizeof(ovs_be32));
+            opt_header[2] += (1 + DIV_ROUND_UP(plen, 8) + sizeof(ovs_be32));
         }
 
         /* Copied from RFC 3442. Please refer to this RFC for the format of
@@ -2375,14 +2375,14 @@  encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
                        sizeof(ovs_be32));
         }
     } else if (!strcmp(o->option->type, "str")) {
-        opt_header[1] = strlen(c->string);
-        ofpbuf_put(ofpacts, c->string, opt_header[1]);
+        opt_header[2] = strlen(c->string);
+        ofpbuf_put(ofpacts, c->string, opt_header[2]);
     } else if (!strcmp(o->option->type, "host_id")) {
         if (o->value.type == EXPR_C_STRING) {
-            opt_header[1] = strlen(c->string);
-            ofpbuf_put(ofpacts, c->string, opt_header[1]);
+            opt_header[2] = strlen(c->string);
+            ofpbuf_put(ofpacts, c->string, opt_header[2]);
         } else {
-           opt_header[1] = sizeof(ovs_be32);
+           opt_header[2] = sizeof(ovs_be32);
            ofpbuf_put(ofpacts, &c->value.ipv4, sizeof(ovs_be32));
         }
     } else if (!strcmp(o->option->type, "domains")) {
@@ -2461,7 +2461,7 @@  encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
                 encode_offset += sizeof(uint8_t);
             }
         }
-        opt_header[1] = encode_offset;
+        opt_header[2] = encode_offset;
         ofpbuf_put(ofpacts, dns_encoded, encode_offset);
 
         out:
diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
index 18c59896d..79c037ba3 100644
--- a/lib/ovn-l7.h
+++ b/lib/ovn-l7.h
@@ -93,6 +93,11 @@  struct gen_opts_map {
 #define DHCP_OPT_DOMAIN_SEARCH_LIST \
     DHCP_OPTION("domain_search_list", 119, "domains")
 
+/* fixed DHCP header */
+#define DHCP_OPT_BOOTFILE_ALT DHCP_OPTION("bootfile_name_alt", 300, "str")
+
+#define DHCP_OPT_ETHERBOOT	175
+
 #define DHCP_OPT_ARP_CACHE_TIMEOUT \
     DHCP_OPTION("arp_cache_timeout", 35, "uint32")
 #define DHCP_OPT_TCP_KEEPALIVE_INTERVAL \
@@ -179,6 +184,12 @@  struct dhcp_opt6_header {
     ovs_be16 size;
 };
 
+OVS_PACKED(
+struct dhcp_opt_userdata_hdr {
+    uint16_t code;
+    uint8_t len;
+});
+
 /* These are not defined in ovs/lib/dhcp.h, hence defining here. */
 #define OVN_DHCP_MSG_DECLINE        4
 #define OVN_DHCP_MSG_RELEASE        7
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index 366fdd95f..a9941bb9a 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -12514,6 +12514,7 @@  static struct gen_opts_map supported_dhcp_opts[] = {
     DHCP_OPT_ARP_CACHE_TIMEOUT,
     DHCP_OPT_TCP_KEEPALIVE_INTERVAL,
     DHCP_OPT_DOMAIN_SEARCH_LIST,
+    DHCP_OPT_BOOTFILE_ALT,
 };
 
 static struct gen_opts_map supported_dhcpv6_opts[] = {
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 86195af34..43694535e 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -3096,6 +3096,16 @@ 
             resolving hostnames via the Domain Name System.
           </p>
         </column>
+
+        <column name="options" key="bootfile_name_alt">
+          <p>
+          </p>
+            "bootfile_name_alt" option is used to support iPXE.
+            When both "bootfile_name" and "bootfile_name_alt" are provided
+            by the CMS, "bootfile_name" will be used for option 67 if the
+            dhcp request contains etherboot option (175), otherwise
+            "bootfile_name_alt" will be used.
+        </column>
       </group>
 
       <group title="DHCP Options of type host_id">
diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
index d1c506a22..9a684d8c0 100644
--- a/ovn-sb.ovsschema
+++ b/ovn-sb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Southbound",
-    "version": "2.10.0",
-    "cksum": "2548342632 22615",
+    "version": "2.11.0",
+    "cksum": "1750191326 22616",
     "tables": {
         "SB_Global": {
             "columns": {
@@ -226,7 +226,7 @@ 
                 "name": {"type": "string"},
                 "code": {
                     "type": {"key": {"type": "integer",
-                                     "minInteger": 0, "maxInteger": 254}}},
+                                     "minInteger": 0, "maxInteger": 1000}}},
                 "type": {
                     "type": {"key": {
                         "type": "string",
diff --git a/tests/ovn.at b/tests/ovn.at
index 488fd119b..9206336ac 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -1277,25 +1277,25 @@  reg0[0] = lookup_arp_ip(inport, eth.dst);
 
 # put_dhcp_opts
 reg1[0] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1);
-    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.40.01.02.03.04.03.04.0a.00.00.01,pause)
+    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.40.01.02.03.04.03.00.04.0a.00.00.01,pause)
 reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain_name="ovn.org",wpad="https://example.org",bootfile_name="https://127.0.0.1/boot.ipxe",path_prefix="/tftpboot");
     formats as reg2[5] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.254.0, mtu = 1400, domain_name = "ovn.org", wpad = "https://example.org", bootfile_name = "https://127.0.0.1/boot.ipxe", path_prefix = "/tftpboot");
-    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.09.2f.74.66.74.70.62.6f.6f.74,pause)
+    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.fe.00.1a.00.02.05.78.0f.00.07.6f.76.6e.2e.6f.72.67.fc.00.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.00.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.00.09.2f.74.66.74.70.62.6f.6f.74,pause)
 reg0[15] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,0.0.0.0/0,10.0.0.1},ethernet_encap=1,router_discovery=0,tftp_server_address={10.0.0.4,10.0.0.5},arp_cache_timeout=10,tcp_keepalive_interval=10);
     formats as reg0[15] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.255.0, mtu = 1400, ip_forward_enable = 1, default_ttl = 121, dns_server = {8.8.8.8, 7.7.7.7}, classless_static_route = {30.0.0.0/24, 10.0.0.4, 40.0.0.0/16, 10.0.0.6, 0.0.0.0/0, 10.0.0.1}, ethernet_encap = 1, router_discovery = 0, tftp_server_address = {10.0.0.4, 10.0.0.5}, arp_cache_timeout = 10, tcp_keepalive_interval = 10);
-    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.96.08.0a.00.00.04.0a.00.00.05.23.04.00.00.00.0a.26.04.00.00.00.0a,pause)
+    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.ff.00.1a.00.02.05.78.13.00.01.01.17.00.01.79.06.00.08.08.08.08.08.07.07.07.07.79.00.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.00.01.01.1f.00.01.00.96.00.08.0a.00.00.04.0a.00.00.05.23.00.04.00.00.00.0a.26.00.04.00.00.00.0a,pause)
 reg0[15] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,0.0.0.0/0,10.0.0.1},ethernet_encap=1,router_discovery=0,tftp_server=10.0.0.10);
     formats as reg0[15] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.255.0, mtu = 1400, ip_forward_enable = 1, default_ttl = 121, dns_server = {8.8.8.8, 7.7.7.7}, classless_static_route = {30.0.0.0/24, 10.0.0.4, 40.0.0.0/16, 10.0.0.6, 0.0.0.0/0, 10.0.0.1}, ethernet_encap = 1, router_discovery = 0, tftp_server = 10.0.0.10);
-    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.42.04.0a.00.00.0a,pause)
+    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.ff.00.1a.00.02.05.78.13.00.01.01.17.00.01.79.06.00.08.08.08.08.08.07.07.07.07.79.00.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.00.01.01.1f.00.01.00.42.00.04.0a.00.00.0a,pause)
 reg0[15] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,0.0.0.0/0,10.0.0.1},ethernet_encap=1,router_discovery=0,tftp_server="tftp_server_test");
     formats as reg0[15] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.255.0, mtu = 1400, ip_forward_enable = 1, default_ttl = 121, dns_server = {8.8.8.8, 7.7.7.7}, classless_static_route = {30.0.0.0/24, 10.0.0.4, 40.0.0.0/16, 10.0.0.6, 0.0.0.0/0, 10.0.0.1}, ethernet_encap = 1, router_discovery = 0, tftp_server = "tftp_server_test");
-    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.42.10.74.66.74.70.5f.73.65.72.76.65.72.5f.74.65.73.74,pause)
+    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.ff.00.1a.00.02.05.78.13.00.01.01.17.00.01.79.06.00.08.08.08.08.08.07.07.07.07.79.00.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.00.01.01.1f.00.01.00.42.00.10.74.66.74.70.5f.73.65.72.76.65.72.5f.74.65.73.74,pause)
 reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain_name="ovn.org",wpad="https://example.org",bootfile_name="https://127.0.0.1/boot.ipxe",path_prefix="/tftpboot",domain_search_list="ovn.org,abc.ovn.org");
     formats as reg2[5] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.254.0, mtu = 1400, domain_name = "ovn.org", wpad = "https://example.org", bootfile_name = "https://127.0.0.1/boot.ipxe", path_prefix = "/tftpboot", domain_search_list = "ovn.org,abc.ovn.org");
-    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.09.2f.74.66.74.70.62.6f.6f.74.77.0f.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00,pause)
+    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.fe.00.1a.00.02.05.78.0f.00.07.6f.76.6e.2e.6f.72.67.fc.00.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.00.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.00.09.2f.74.66.74.70.62.6f.6f.74.77.00.0f.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00,pause)
 reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain_name="ovn.org",wpad="https://example.org",bootfile_name="https://127.0.0.1/boot.ipxe",path_prefix="/tftpboot",domain_search_list="ovn.org,abc.ovn.org,def.ovn.org,ovn.test,def.ovn.test,test.org,abc.com");
     formats as reg2[5] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.254.0, mtu = 1400, domain_name = "ovn.org", wpad = "https://example.org", bootfile_name = "https://127.0.0.1/boot.ipxe", path_prefix = "/tftpboot", domain_search_list = "ovn.org,abc.ovn.org,def.ovn.org,ovn.test,def.ovn.test,test.org,abc.com");
-    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.09.2f.74.66.74.70.62.6f.6f.74.77.35.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00.03.64.65.66.c0.00.03.6f.76.6e.04.74.65.73.74.00.03.64.65.66.c0.15.04.74.65.73.74.c0.04.03.61.62.63.03.63.6f.6d.00,pause)
+    encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.00.04.0a.00.00.01.01.00.04.ff.ff.fe.00.1a.00.02.05.78.0f.00.07.6f.76.6e.2e.6f.72.67.fc.00.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.00.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.00.09.2f.74.66.74.70.62.6f.6f.74.77.00.35.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00.03.64.65.66.c0.00.03.6f.76.6e.04.74.65.73.74.00.03.64.65.66.c0.15.04.74.65.73.74.c0.04.03.61.62.63.03.63.6f.6d.00,pause)
 
 reg1[0..1] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1);
     Cannot use 2-bit field reg1[0..1] where 1-bit field is required.
@@ -5302,10 +5302,10 @@  sleep 2
 as hv1 ovs-vsctl show
 
 # This shell function sends a DHCP request packet
-# test_dhcp INPORT SRC_MAC DHCP_TYPE BROADCAST CIADDR OFFER_IP REQUEST_IP USE_IP ...
+# test_dhcp INPORT SRC_MAC DHCP_TYPE BROADCAST CIADDR OFFER_IP REQUEST_IP ETH_BOOT USE_IP ...
 test_dhcp() {
-    local inport=$1 src_mac=$2 dhcp_type=$3 broadcast=$4 ciaddr=$5 offer_ip=$6 request_ip=$7 use_ip=$8
-    shift; shift; shift; shift; shift; shift; shift; shift;
+    local inport=$1 src_mac=$2 dhcp_type=$3 broadcast=$4 ciaddr=$5 offer_ip=$6 request_ip=$7 eth_boot=$8 use_ip=$9
+    shift; shift; shift; shift; shift; shift; shift; shift; shift;
 
     if test $use_ip != 0; then
         src_ip=$1
@@ -5317,11 +5317,21 @@  test_dhcp() {
     fi
 
     if test $request_ip != 0; then
-        ip_len=0120
-        udp_len=010b
+        if test $eth_boot != 0; then
+            ip_len=0124
+            udp_len=010f
+        else
+            ip_len=0120
+            udp_len=010b
+        fi
     else
-        ip_len=011a
-        udp_len=0106
+        if test $eth_boot != 0; then
+            ip_len=011e
+            udp_len=010a
+        else
+            ip_len=011a
+            udp_len=0106
+        fi
     fi
 
     if test $broadcast != 0; then
@@ -5362,6 +5372,9 @@  test_dhcp() {
         # dhcp requested ip
         request=${request}3204${request_ip}
     fi
+    if test $eth_boot != 0; then
+        request=${request}af020000
+    fi
     # dhcp end option
     request=${request}ff
 
@@ -5457,7 +5470,7 @@  server_ip=`ip_to_hex 10 0 0 1`
 ciaddr=`ip_to_hex 0 0 0 0`
 request_ip=0
 expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
-test_dhcp 1 f00000000001 01 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
+test_dhcp 1 f00000000001 01 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
 
 # NXT_RESUMEs should be 1.
 OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
@@ -5483,7 +5496,7 @@  server_ip=`ip_to_hex 10 0 0 1`
 ciaddr=`ip_to_hex 0 0 0 0`
 request_ip=$offer_ip
 expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
-test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
+test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
 
 # NXT_RESUMEs should be 2.
 OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
@@ -5507,7 +5520,7 @@  server_ip=`ip_to_hex 10 0 0 1`
 ciaddr=`ip_to_hex 0 0 0 0`
 request_ip=`ip_to_hex 10 0 0 7`
 expected_dhcp_opts=""
-test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 06 $expected_dhcp_opts
+test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 06 $expected_dhcp_opts
 
 # NXT_RESUMEs should be 3.
 OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
@@ -5531,7 +5544,7 @@  rm -f 2.expected
 ciaddr=`ip_to_hex 0 0 0 0`
 offer_ip=0
 request_ip=0
-test_dhcp 2 f00000000002 09 0 $ciaddr $offer_ip $request_ip 0 1 1
+test_dhcp 2 f00000000002 09 0 $ciaddr $offer_ip $request_ip 0 0 1 1
 
 # NXT_RESUMEs should be 4.
 OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
@@ -5548,18 +5561,18 @@  rm -f 2.expected
 # ls2-lp2 (vif4-tx.pcap) should receive the DHCPv4 request packet once.
 
 ciaddr=`ip_to_hex 0 0 0 0`
-test_dhcp 3 f00000000003 01 0 $ciaddr 0 0 4 0
+test_dhcp 3 f00000000003 01 0 $ciaddr 0 0 0 4 0
 
 # Send DHCPv4 packet on ls2-lp2. "router" DHCPv4 option is not defined for
 # this lport.
 ciaddr=`ip_to_hex 0 0 0 0`
-test_dhcp 4 f00000000004 01 0 $ciaddr 0 0 3 0
+test_dhcp 4 f00000000004 01 0 $ciaddr 0 0 0 3 0
 
 # NXT_RESUMEs should be 4.
 OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
 
-#OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [3.expected])
-#OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [4.expected])
+OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [3.expected])
+OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [4.expected])
 
 # Send DHCPREQUEST in the RENEWING/REBINDING state with ip4.src set to 10.0.0.6
 # and ip4.dst set to 10.0.0.1.
@@ -5570,7 +5583,7 @@  request_ip=0
 src_ip=$offer_ip
 dst_ip=$server_ip
 expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
-test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
+test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
 
 # NXT_RESUMEs should be 5.
 OVS_WAIT_UNTIL([test 5 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
@@ -5596,7 +5609,7 @@  request_ip=0
 src_ip=$offer_ip
 dst_ip=`ip_to_hex 255 255 255 255`
 expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
-test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
+test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
 
 # NXT_RESUMEs should be 6.
 OVS_WAIT_UNTIL([test 6 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
@@ -5622,7 +5635,7 @@  request_ip=0
 src_ip=$offer_ip
 dst_ip=`ip_to_hex 255 255 255 255`
 expected_dhcp_opts=""
-test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts
+test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts
 
 # NXT_RESUMEs should be 7.
 OVS_WAIT_UNTIL([test 7 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
@@ -5648,7 +5661,7 @@  request_ip=0
 src_ip=$offer_ip
 dst_ip=`ip_to_hex 255 255 255 255`
 expected_dhcp_opts=""
-test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts
+test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 06 $expected_dhcp_opts
 
 # NXT_RESUMEs should be 8.
 OVS_WAIT_UNTIL([test 8 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
@@ -5670,7 +5683,7 @@  rm -f 2.expected
 ciaddr=`ip_to_hex 0 0 0 0`
 src_ip=`ip_to_hex 10 0 0 6`
 dst_ip=`ip_to_hex 10 0 0 4`
-test_dhcp 2 f00000000002 03 0 $ciaddr 0 0 1 $src_ip $dst_ip 1
+test_dhcp 2 f00000000002 03 0 $ciaddr 0 0 0 1 $src_ip $dst_ip 1
 
 # NXT_RESUMEs should be 8.
 OVS_WAIT_UNTIL([test 8 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
@@ -5689,7 +5702,7 @@  server_ip=`ip_to_hex 10 0 0 1`
 ciaddr=`ip_to_hex 0 0 0 0`
 request_ip=0
 expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
-test_dhcp 1 f00000000001 01 1 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
+test_dhcp 1 f00000000001 01 1 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
 
 # NXT_RESUMEs should be 9.
 OVS_WAIT_UNTIL([test 9 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
@@ -5712,7 +5725,7 @@  server_ip=`ip_to_hex 10 0 0 1`
 ciaddr=`ip_to_hex 10 0 0 6`
 request_ip=0
 expected_dhcp_opts=0
-test_dhcp 2 f00000000002 07 0 $ciaddr $offer_ip $request_ip 0 ff1000000001
+test_dhcp 2 f00000000002 07 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001
 
 # NXT_RESUMEs should be 10.
 OVS_WAIT_UNTIL([test 10 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)])
@@ -5739,7 +5752,7 @@  dst_ip=$server_ip
 # In the expected_dhcp_opts we should not see 330400000e10 which is
 # dhcp lease time option and 0104ffffff00 which is subnet mask option.
 expected_dhcp_opts=03040a00000136040a000001
-test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
+test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
 
 # NXT_RESUMEs should be 11.
 OVS_WAIT_UNTIL([test 11 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)])
@@ -5769,7 +5782,7 @@  dst_ip=$server_ip
 # In the expected_dhcp_opts we should not see 330400000e10 which is
 # dhcp lease time option.
 expected_dhcp_opts=3a0400000fa0330400000e100104ffffff0003040a00000136040a000001
-test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
+test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
 
 # NXT_RESUMEs should be 12.
 OVS_WAIT_UNTIL([test 12 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)])
@@ -5796,7 +5809,7 @@  dst_ip=$server_ip
 # In the expected_dhcp_opts we should not see 330400000e10 which is
 # dhcp lease time option and 0104ffffff00 which is subnet mask option.
 expected_dhcp_opts=03040a00000136040a000001
-test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
+test_dhcp 2 f00000000002 08 0 $ciaddr $offer_ip $request_ip 0 1 $src_ip $dst_ip ff1000000001 $server_ip 05 $expected_dhcp_opts
 
 # NXT_RESUMEs should be 13.
 OVS_WAIT_UNTIL([test 13 = $(cat ofctl_monitor*.log | grep -c NXT_RESUME)])
@@ -5827,7 +5840,7 @@  server_ip=`ip_to_hex 10 0 0 1`
 ciaddr=`ip_to_hex 0 0 0 0`
 request_ip=$offer_ip
 expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a00000142040a0a0a0a
-test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
+test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
 
 # NXT_RESUMEs should be 14.
 OVS_WAIT_UNTIL([test 14 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
@@ -5858,7 +5871,7 @@  server_ip=`ip_to_hex 10 0 0 1`
 ciaddr=`ip_to_hex 0 0 0 0`
 request_ip=$offer_ip
 expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a0000014210746573745f746674705f736572766572
-test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
+test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
 
 # NXT_RESUMEs should be 15.
 OVS_WAIT_UNTIL([test 15 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
@@ -5889,7 +5902,7 @@  server_ip=`ip_to_hex 10 0 0 1`
 ciaddr=`ip_to_hex 0 0 0 0`
 request_ip=$offer_ip
 expected_dhcp_opts=771305746573743103636f6d00057465737432c006330400000e100104ffffff0003040a00000136040a000001
-test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
+test_dhcp 2 f00000000002 03 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 05 $expected_dhcp_opts
 
 # NXT_RESUMEs should be 16.
 OVS_WAIT_UNTIL([test 16 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
@@ -5907,9 +5920,42 @@  server_ip=`ip_to_hex 10 0 0 1`
 ciaddr=`ip_to_hex 0 0 0 0`
 request_ip=0
 expected_dhcp_opts=""
-test_dhcp 1 f00000000001 04 0 $ciaddr $offer_ip $request_ip 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
+test_dhcp 1 f00000000001 04 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
 AT_CHECK([fgrep -iq 'DHCPDECLINE from f0:00:00:00:00:01, 10.0.0.4 duplicated' hv1/ovn-controller.log], [0], [])
 
+# Send Etherboot.
+
+reset_pcap_file hv1-vif1 hv1/vif1
+reset_pcap_file hv1-vif2 hv1/vif2
+rm -f 1.expected
+rm -f 2.expected
+
+ovn-nbctl --all destroy dhcp-option
+
+ovn-nbctl dhcp-options-create 10.0.0.0/24
+d3=$(ovn-nbctl --bare --columns=_uuid find dhcp_options cidr="10.0.0.0/24")
+ovn-nbctl dhcp-options-set-options $d3 \
+   server_id=10.0.0.1 server_mac=ff:10:00:00:00:01 \
+   lease_time=3600 router=10.0.0.1 bootfile_name_alt=\"filename\" \
+   bootfile_name=\"bootfile\"
+
+ovn-nbctl lsp-set-dhcpv4-options ls1-lp1 $d3
+
+offer_ip=`ip_to_hex 10 0 0 4`
+server_ip=`ip_to_hex 10 0 0 1`
+ciaddr=`ip_to_hex 0 0 0 0`
+request_ip=0
+boofile=4308626f6f7466696c65
+expected_dhcp_opts=${boofile}330400000e100104ffffff0003040a00000136040a000001
+test_dhcp 1 f00000000001 01 0 $ciaddr $offer_ip $request_ip 1 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
+
+$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
+cat 1.expected | cut -c -48 > expout
+AT_CHECK([cat 1.packets | cut -c -48], [0], [expout])
+# Skipping the IPv4 checksum.
+cat 1.expected | cut -c 53- > expout
+AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout])
+
 OVN_CLEANUP([hv1])
 
 AT_CLEANUP
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
index d94ab025d..96763be60 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -191,6 +191,7 @@  create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts,
     dhcp_opt_add(dhcp_opts, "arp_cache_timeout", 35, "uint32");
     dhcp_opt_add(dhcp_opts, "tcp_keepalive_interval", 38, "uint32");
     dhcp_opt_add(dhcp_opts, "domain_search_list", 119, "domains");
+    dhcp_opt_add(dhcp_opts, "bootfile_name_alt", 300, "str");
 
     /* DHCPv6 options. */
     hmap_init(dhcpv6_opts);