diff mbox series

[ovs-dev] northd, controller: Add support for DHCPv6 FQDN option

Message ID 20230602104341.245758-1-amusil@redhat.com
State Superseded
Headers show
Series [ovs-dev] northd, controller: Add support for DHCPv6 FQDN option | expand

Checks

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

Commit Message

Ales Musil June 2, 2023, 10:43 a.m. UTC
Add support for FQDN option (39), if specified the server
can overwrite FQDN for the client. It behaves similarly
to DHCP hostname option (12).

Signed-off-by: Ales Musil <amusil@redhat.com>
---
 NEWS                 |   2 +
 controller/pinctrl.c |  27 +++++++-
 lib/actions.c        |  30 ++++++++-
 lib/ovn-l7.h         |  10 +++
 northd/ovn-northd.c  |   3 +-
 ovn-nb.xml           |   7 ++
 ovn-sb.ovsschema     |   6 +-
 tests/ovn.at         | 156 ++++++++++++++++++++++++-------------------
 tests/test-ovn.c     |   1 +
 9 files changed, 166 insertions(+), 76 deletions(-)

Comments

Ales Musil June 2, 2023, 1:45 p.m. UTC | #1
On Fri, Jun 2, 2023 at 12:43 PM Ales Musil <amusil@redhat.com> wrote:

> Add support for FQDN option (39), if specified the server
> can overwrite FQDN for the client. It behaves similarly
> to DHCP hostname option (12).
>
>
Reported-at: https://bugzilla.redhat.com/2211890

I have created BZ for this it should be added on merge or I'll add it
if there is a need for v2.

Signed-off-by: Ales Musil <amusil@redhat.com>
> ---
>  NEWS                 |   2 +
>  controller/pinctrl.c |  27 +++++++-
>  lib/actions.c        |  30 ++++++++-
>  lib/ovn-l7.h         |  10 +++
>  northd/ovn-northd.c  |   3 +-
>  ovn-nb.xml           |   7 ++
>  ovn-sb.ovsschema     |   6 +-
>  tests/ovn.at         | 156 ++++++++++++++++++++++++-------------------
>  tests/test-ovn.c     |   1 +
>  9 files changed, 166 insertions(+), 76 deletions(-)
>
> diff --git a/NEWS b/NEWS
> index 1532f635a..b4dd0da05 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -1,5 +1,7 @@
>  Post v23.06.0
>  -------------
> +  - Add DHCPv6 "fqdn" (39) option, that works similarly to
> +    DHCPv4 "hostname" (12) option.
>
>  OVN v23.06.0 - xx xxx xxxx
>  --------------------------
> diff --git a/controller/pinctrl.c b/controller/pinctrl.c
> index c396ad4c2..51dbadc99 100644
> --- a/controller/pinctrl.c
> +++ b/controller/pinctrl.c
> @@ -2288,7 +2288,7 @@ exit:
>  static bool
>  compose_out_dhcpv6_opts(struct ofpbuf *userdata,
>                          struct ofpbuf *out_dhcpv6_opts,
> -                        ovs_be32 iaid, bool ipxe_req)
> +                        ovs_be32 iaid, bool ipxe_req, uint8_t fqdn_flags)
>  {
>      while (userdata->size) {
>          struct dhcpv6_opt_header *userdata_opt = ofpbuf_try_pull(
> @@ -2412,6 +2412,24 @@ compose_out_dhcpv6_opts(struct ofpbuf *userdata,
>              break;
>          }
>
> +        case DHCPV6_OPT_FQDN_CODE: {
> +            if (fqdn_flags != DHCPV6_FQDN_FLAGS_UNDEFINED) {
> +                struct dhcpv6_opt_header *header =
> +                        ofpbuf_put_zeros(out_dhcpv6_opts, sizeof *header);
> +                header->code = htons(DHCPV6_OPT_FQDN_CODE);
> +                header->len = htons(size + 1);
> +                uint8_t *flags = ofpbuf_put_zeros(out_dhcpv6_opts, 1);
> +                /* If client requested N or S inform him that it
> +                 * was overwritten by the server. */
> +                if (fqdn_flags & DHCPV6_FQDN_FLAGS_N ||
> +                    fqdn_flags & DHCPV6_FQDN_FLAGS_S) {
> +                    *flags |= DHCPV6_FQDN_FLAGS_O;
> +                }
> +                ofpbuf_put(out_dhcpv6_opts, userdata_opt_data, size);
> +            }
> +            break;
> +        }
> +
>          default:
>              return false;
>          }
> @@ -2500,6 +2518,7 @@ pinctrl_handle_put_dhcpv6_opts(
>      size_t l4_len = dp_packet_l4_size(pkt_in);
>      uint8_t *end = (uint8_t *)in_udp + MIN(udp_len, l4_len);
>      bool ipxe_req = false;
> +    uint8_t fqdn_flags = DHCPV6_FQDN_FLAGS_UNDEFINED;
>      while (in_dhcpv6_data < end) {
>          struct dhcpv6_opt_header const *in_opt =
>               (struct dhcpv6_opt_header *)in_dhcpv6_data;
> @@ -2524,6 +2543,10 @@ pinctrl_handle_put_dhcpv6_opts(
>              break;
>          }
>
> +        case DHCPV6_OPT_FQDN_CODE:
> +            fqdn_flags = *(in_dhcpv6_data + sizeof *in_opt);
> +            break;
> +
>          default:
>              break;
>          }
> @@ -2547,7 +2570,7 @@ pinctrl_handle_put_dhcpv6_opts(
>          OFPBUF_STUB_INITIALIZER(out_ofpacts_dhcpv6_opts_stub);
>
>      if (!compose_out_dhcpv6_opts(userdata, &out_dhcpv6_opts,
> -                                 iaid, ipxe_req)) {
> +                                 iaid, ipxe_req, fqdn_flags)) {
>          VLOG_WARN_RL(&rl, "Invalid userdata");
>          goto exit;
>      }
> diff --git a/lib/actions.c b/lib/actions.c
> index ec27223f9..0ec69bec9 100644
> --- a/lib/actions.c
> +++ b/lib/actions.c
> @@ -2470,7 +2470,8 @@ parse_gen_opt(struct action_context *ctx, struct
> ovnact_gen_option *o,
>      }
>
>      if (!strcmp(o->option->type, "str") ||
> -        !strcmp(o->option->type, "domains")) {
> +        !strcmp(o->option->type, "domains") ||
> +        !strcmp(o->option->type, "domain")) {
>          if (o->value.type != EXPR_C_STRING) {
>              lexer_error(ctx->lexer, "%s option %s requires string value.",
>                          opts_type, o->option->name);
> @@ -2903,6 +2904,33 @@ encode_put_dhcpv6_option(const struct
> ovnact_gen_option *o,
>          size = strlen(c->string);
>          opt->len = htons(size);
>          ofpbuf_put(ofpacts, c->string, size);
> +    } else if (!strcmp(o->option->type, "domain")) {
> +        /* The DNS format is 2 bytes longer than the "domain".
> +         * It replaces every '.' with len of the next name. */
> +        size_t domain_len = strlen(c->string);
> +        size = domain_len + 2;
> +        char *encoded = xzalloc(size);
> +
> +        int8_t label_len = 0;
> +        for (size_t i = 0; i < domain_len; i++) {
> +            if (c->string[i] == '.') {
> +                encoded[i - label_len] = label_len;
> +                label_len = 0;
> +            } else {
> +                encoded[i + 1] = c->string[i];
> +                label_len++;
> +            }
> +        }
> +
> +        /* This is required for the last label if it doesn't end with '.'
> */
> +        if (label_len) {
> +            encoded[domain_len - label_len] = label_len;
> +        }
> +
> +        opt->len = htons(size);
> +        ofpbuf_put(ofpacts, encoded, size);
> +
> +        free(encoded);
>      }
>  }
>
> diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
> index d9b103119..9dc331421 100644
> --- a/lib/ovn-l7.h
> +++ b/lib/ovn-l7.h
> @@ -270,6 +270,7 @@ BUILD_ASSERT_DECL(DHCP_OPT_HEADER_LEN == sizeof(struct
> dhcp_opt_header));
>  #define DHCPV6_OPT_DOMAIN_SEARCH_CODE    24
>  #define DHCPV6_OPT_IA_PD                 25
>  #define DHCPV6_OPT_IA_PREFIX             26
> +#define DHCPV6_OPT_FQDN_CODE             39
>  #define DHCPV6_OPT_BOOT_FILE_URL         59
>  #define DHCPV6_OPT_BOOT_FILE_URL_ALT    254
>
> @@ -291,6 +292,15 @@ BUILD_ASSERT_DECL(DHCP_OPT_HEADER_LEN ==
> sizeof(struct dhcp_opt_header));
>  #define DHCPV6_OPT_BOOTFILE_NAME_ALT \
>      DHCP_OPTION("bootfile_name_alt", DHCPV6_OPT_BOOT_FILE_URL_ALT, "str")
>
> +#define DHCPV6_OPT_FQDN \
> +    DHCP_OPTION("fqdn", DHCPV6_OPT_FQDN_CODE, "domain")
> +
> +/* DHCPv6 FQDN flags. RFC 4704 */
> +#define DHCPV6_FQDN_FLAGS_UNDEFINED 0xff
> +#define DHCPV6_FQDN_FLAGS_S 1 << 0
> +#define DHCPV6_FQDN_FLAGS_O 1 << 1
> +#define DHCPV6_FQDN_FLAGS_N 1 << 2
> +
>  OVS_PACKED(
>  struct dhcpv6_opt_header {
>      ovs_be16 code;
> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> index 3515b68a2..2bafbd4af 100644
> --- a/northd/ovn-northd.c
> +++ b/northd/ovn-northd.c
> @@ -273,7 +273,8 @@ static struct gen_opts_map supported_dhcpv6_opts[] = {
>      DHCPV6_OPT_DOMAIN_SEARCH,
>      DHCPV6_OPT_DNS_SERVER,
>      DHCPV6_OPT_BOOTFILE_NAME,
> -    DHCPV6_OPT_BOOTFILE_NAME_ALT
> +    DHCPV6_OPT_BOOTFILE_NAME_ALT,
> +    DHCPV6_OPT_FQDN,
>  };
>
>  static bool
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index 9afe3b584..05e155b89 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -4191,6 +4191,13 @@ or
>              way. Default value for this option is false.
>            </p>
>          </column>
> +
> +        <column name="options" key="fqdn">
> +          <p>
> +            The DHCPv6 option code for this option is 39.
> +            If set, indicates the DHCPv6 option "FQDN".
> +          </p>
> +        </column>
>        </group>
>      </group>
>
> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
> index f59af8cc5..06e16b403 100644
> --- a/ovn-sb.ovsschema
> +++ b/ovn-sb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Southbound",
> -    "version": "20.27.2",
> -    "cksum": "1291808617 30462",
> +    "version": "20.27.3",
> +    "cksum": "3876528905 30472",
>      "tables": {
>          "SB_Global": {
>              "columns": {
> @@ -311,7 +311,7 @@
>                  "type": {
>                      "type": {"key": {
>                          "type": "string",
> -                        "enum": ["set", ["ipv6", "str", "mac"]]}}}},
> +                        "enum": ["set", ["ipv6", "str", "mac",
> "domain"]]}}}},
>              "isRoot": true},
>          "Connection": {
>              "columns": {
> diff --git a/tests/ovn.at b/tests/ovn.at
> index d8c694002..ab6052299 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -1697,6 +1697,9 @@ reg1[0] = put_dhcpv6_opts(bootfile_name="
> https://127.0.0.1/boot.ipxe");
>  reg1[0] = put_dhcpv6_opts(bootfile_name_alt="https://127.0.0.1/boot.ipxe
> ");
>      formats as reg1[0] = put_dhcpv6_opts(bootfile_name_alt = "
> https://127.0.0.1/boot.ipxe");
>      encodes as
> controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40.00.fe.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,pause)
> +reg1[0] = put_dhcpv6_opts(fqdn="ovn.org");
> +    formats as reg1[0] = put_dhcpv6_opts(fqdn = "ovn.org");
> +    encodes as
> controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40.00.27.00.09.03.6f.76.6e.03.6f.72.67.00,pause)
>
>  # lookup_nd
>  reg2[0] = lookup_nd(inport, ip6.dst, eth.src);
> @@ -7083,6 +7086,7 @@ AT_CLEANUP
>
>  OVN_FOR_EACH_NORTHD([
>  AT_SETUP([dhcpv6 : 1 HV, 2 LS, 5 LSPs])
> +AT_SKIP_IF([test $HAVE_SCAPY = no])
>  ovn_start
>
>  ovn-nbctl ls-add ls1
> @@ -7171,61 +7175,56 @@ trim_zeros() {
>  # packet should be received twice (one from ovn-controller and the other
>  # from the "ovs-ofctl monitor br-int resume"
>  test_dhcpv6() {
> -    local inport=$1 src_mac=$2 src_lla=$3 msg_code=$4 offer_ip=$5 ipxe=$6
> -    if test $ipxe -eq 2; then
> -        req_len=34
> -    elif test $msg_code != 0b; then
> -        req_len=2a
> -    else
> -        req_len=1a
> -    fi
> -    local
> request=ffffffffffff${src_mac}86dd6000000000${req_len}1101${src_lla}
> -    # dst ip ff02::1:2
> -    request=${request}ff020000000000000000000000010002
> -    # udp header and dhcpv6 header
> -    request=${request}0222022300${req_len}ffff${msg_code}010203
> -    # Client identifier
> -    request=${request}0001000a00030001${src_mac}
> +    local inport=$1 src_mac=$2 src_lla=$3 msg_code=$4 offer_ip=$5
> boot_file=$6
> +    local fqdn=$7
> +
> +    local req_scapy="Ether(dst='ff:ff:ff:ff:ff:ff', src='${src_mac}')/ \
> +                     IPv6(dst='ff02::1:2', src='${src_lla}')/ \
> +                     UDP(sport=546, dport=547)/ \
> +                     DHCP6(msgtype=${msg_code}, trid=0x010203)/ \
> +                     DHCP6OptClientId(duid=DUID_LL(lladdr='${src_mac}'))"
> +
>      # Add IA-NA (Identity Association for Non Temporary Address) if
> msg_code
>      # is not 11 (information request packet)
> -    if test $msg_code != 0b; then
> -        request=${request}0003000c0102030400000e1000001518
> +    if test $msg_code != 11; then
> +        req_scapy="${req_scapy}/DHCP6OptIA_NA(iaid=0x01020304, T1=3600,
> T2=5400)"
>      fi
> -    if test $ipxe -eq 2; then
> -        request=${request}000f0006000669505845
> +    if test "$boot_file" = "bootfile_name"; then
> +        local uc_data="USER_CLASS_DATA(data=b'iPXE')"
> +
> req_scapy="${req_scapy}/DHCP6OptUserClass(userclassdata=[[${uc_data}]])"
>      fi
> -    shift; shift; shift; shift; shift; shift;
> +    if test -n "$fqdn"; then
> +        req_scapy="${req_scapy}/DHCP6OptClientFQDN(flags=0x01,
> fqdn=b'test')"
> +    fi
> +    request=$(fmt_pkt "${req_scapy}")
> +    shift; shift; shift; shift; shift; shift; shift;
>      if test $offer_ip != 0; then
> -        local server_mac=000000100001
> -        local server_lla=fe80000000000000020000fffe100001
> -        local reply_code=07
> -        if test $msg_code = 01; then
> -            reply_code=02
> -        fi
> -        local msg_len=54
> -        if test $ipxe -eq 1; then
> -            msg_len=69
> -        elif test $ipxe -eq 2; then
> -            msg_len=65
> -        elif test $offer_ip = 1; then
> -            msg_len=28
> +        local reply_code=7
> +        if test $msg_code = 1; then
> +            reply_code=2
>          fi
> -        local
> reply=${src_mac}${server_mac}86dd6000000000${msg_len}1101${server_lla}${src_lla}
> -        # udp header and dhcpv6 header
> -        reply=${reply}0223022200${msg_len}ffff${reply_code}010203
> -        # Client identifier
> -        reply=${reply}0001000a00030001${src_mac}
> -        # IA-NA
> +
> +        local rep_scapy="Ether(dst='${src_mac}',
> src='00:00:00:10:00:01')/ \
> +                         IPv6(dst='${src_lla}',
> src='fe80::0200:00ff:fe10:0001')/ \
> +                         UDP(sport=547, dport=546)/ \
> +                         DHCP6(msgtype=${reply_code}, trid=0x010203)/ \
> +
>  DHCP6OptClientId(duid=DUID_LL(lladdr='${src_mac}'))"
> +
>          if test $offer_ip != 1; then
> -
> reply=${reply}0003002801020304ffffffffffffffff00050018${offer_ip}ffffffffffffffff
> +            local ip_scapy="DHCP6OptIAAddress(addr='${offer_ip}', \
> +                            preflft=0xffffffff, validlft=0xffffffff)"
> +            rep_scapy="${rep_scapy}/ \
> +                       DHCP6OptIA_NA(iaid=0x01020304, T1=0xffffffff, \
> +                       T2=0xffffffff, ianaopts=[[${ip_scapy}]])"
>          fi
> -        if test $ipxe -eq 1; then
> -            reply=${reply}003b0011626f6f7466696c655f6e616d655f616c74
> -        elif test $ipxe -eq 2; then
> -            reply=${reply}003b000d626f6f7466696c655f6e616d65
> +        if test -n "$boot_file"; then
> +
> rep_scapy="${rep_scapy}/DHCP6OptBootFileUrl(optdata=b'${boot_file}')"
>          fi
> -        # Server identifier
> -        reply=${reply}0002000a00030001${server_mac}
> +        if test -n "$fqdn"; then
> +            rep_scapy="${rep_scapy}/DHCP6OptClientFQDN(flags=0x02,
> fqdn=b'${fqdn}')"
> +        fi
> +
> rep_scapy="${rep_scapy}/DHCP6OptServerId(duid=DUID_LL(lladdr='00:00:00:10:00:01'))"
> +        reply=$(fmt_pkt "${rep_scapy}")
>          echo $reply | trim_zeros >> $inport.expected
>      else
>          for outport; do
> @@ -7249,16 +7248,16 @@ echo "---------------------"
>  ovn-sbctl list logical_flow
>  echo "---------------------"
>
> -ovn-sbctl dump-flows > sbflows
> +#ovn-sbctl dump-flows > sbflows
>  AT_CAPTURE_FILE([sbflows])
>
>  echo "------ hv1 dump ----------"
>  as hv1 ovs-ofctl dump-flows br-int
>
> -src_mac=f00000000001
> -src_lla=fe80000000000000f20000fffe000001
> -offer_ip=ae700000000000000000000000000004
> -test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip 0
> +src_mac="f0:00:00:00:00:01"
> +src_lla="fe80::f200:00ff:fe00:0001"
> +offer_ip="ae70::4"
> +test_dhcpv6 1 $src_mac $src_lla 1 $offer_ip "" ""
>
>  # NXT_RESUMEs should be 1.
>  OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> @@ -7281,12 +7280,12 @@ rm  1.expected
>  reset_pcap_file hv1-vif1 hv1/vif1
>  reset_pcap_file hv1-vif2 hv1/vif2
>
> -src_mac=f00000000002
> -src_lla=fe80000000000000f20000fffe000002
> -offer_ip=ae700000000000000000000000000005
> +src_mac="f0:00:00:00:00:02"
> +src_lla="fe80::f200:00ff:fe00:0002"
> +offer_ip="ae70::5"
>  # Set invalid msg_type
>
> -test_dhcpv6 2 $src_mac $src_lla 10 0 0 1 1
> +test_dhcpv6 2 $src_mac $src_lla 16 0 "" "" 1 1
>
>  # NXT_RESUMEs should be 2.
>  OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> @@ -7305,9 +7304,9 @@ AT_CHECK([cat 1.packets], [0], [expout])
>  # There should be no DHCPv6 reply from ovn-controller and the request
> packet
>  # should be received by ls2-lp2.
>
> -src_mac=f00000000003
> -src_lla=fe80000000000000f20000fffe000003
> -test_dhcpv6 3 $src_mac $src_lla 01 0 0 4
> +src_mac="f0:00:00:00:00:03"
> +src_lla="fe80::f200:00ff:fe00:0003"
> +test_dhcpv6 3 $src_mac $src_lla 1 0 "" "" 4
>
>  # NXT_RESUMEs should be 2 only.
>  OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> @@ -7323,10 +7322,10 @@ AT_CHECK([cat 4.packets], [0], [expout])
>
>  # Send DHCPv6 packet on ls1-lp3. native DHCPv6 works as stateless mode
> for this port.
>  # The DHCPv6 reply shouldn't contain offer_ip.
> -src_mac=f00000000022
> -src_lla=fe80000000000000f20000fffe000022
> +src_mac="f0:00:00:00:00:22"
> +src_lla="fe80::f200:00ff:fe00:0022"
>  reset_pcap_file hv1-vif5 hv1/vif5
> -test_dhcpv6 5 $src_mac $src_lla 01 1 0 5
> +test_dhcpv6 5 $src_mac $src_lla 1 1 "" "" 5
>
>  # NXT_RESUMEs should be 3.
>  OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> @@ -7338,11 +7337,11 @@ AT_CHECK([cat 5.packets | cut -c 1-120,125- ],
> [0], [expout])
>
>  # Send DHCPv6 information request (code 11) on ls1-lp3. The DHCPv6 reply
>  # shouldn't contain offer_ip
> -src_mac=f00000000022
> -src_lla=fe80000000000000f20000fffe000022
> +src_mac="f0:00:00:00:00:22"
> +src_lla="fe80::f200:00ff:fe00:0022"
>  reset_pcap_file hv1-vif5 hv1/vif5
>  rm -f 5.expected
> -test_dhcpv6 5 $src_mac $src_lla 0b 1 0 5
> +test_dhcpv6 5 $src_mac $src_lla 11 1 "" "" 5
>
>  # NXT_RESUMEs should be 4.
>  OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> @@ -7363,11 +7362,11 @@ ovn-nbctl lsp-set-dhcpv6-options ls1-lp2 ${d1}
>
>  reset_pcap_file hv1-vif2 hv1/vif2
>
> -src_mac=f00000000002
> -src_lla=fe80000000000000f20000fffe000002
> -offer_ip=ae700000000000000000000000000005
> +src_mac="f0:00:00:00:00:02"
> +src_lla="fe80::f200:00ff:fe00:0002"
> +offer_ip="ae70::5"
>
> -test_dhcpv6 2 $src_mac $src_lla 01 $offer_ip 1
> +test_dhcpv6 2 $src_mac $src_lla 1 $offer_ip "bootfile_name_alt" ""
>  # NXT_RESUMEs should be 5.
>  OVS_WAIT_UNTIL([test 5 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
>
> @@ -7381,7 +7380,7 @@ AT_CHECK([cat 2.packets | cut -c 1-120,125- ], [0],
> [expout])
>  reset_pcap_file hv1-vif2 hv1/vif2
>  rm 2.packets 2.expected
>
> -test_dhcpv6 2 $src_mac $src_lla 01 $offer_ip 2
> +test_dhcpv6 2 $src_mac $src_lla 1 $offer_ip "bootfile_name" ""
>  # NXT_RESUMEs should be 6.
>  OVS_WAIT_UNTIL([test 6 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
>
> @@ -7389,6 +7388,25 @@ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in"
> hv1/vif2-tx.pcap | trim_zeros > 2.pa
>  cat 2.expected | cut -c 1-120,125- > expout
>  AT_CHECK([cat 2.packets | cut -c 1-120,125- ], [0], [expout])
>
> +ovn-nbctl --all destroy dhcp-option
> +d1="$(ovn-nbctl create DHCP_Options cidr="ae70\:\:/64")"
> +ovn-nbctl dhcp-options-set-options $d1 \
> +    server_id=00:00:00:10:00:01 \
> +    fqdn=\"ovn.org\"
> +ovn-nbctl --wait=hv lsp-set-dhcpv6-options ls1-lp2 ${d1}
> +
> +reset_pcap_file hv1-vif2 hv1/vif2
> +rm 2.packets 2.expected
> +
> +test_dhcpv6 2 $src_mac $src_lla 1 $offer_ip "" "ovn.org"
> +# NXT_RESUMEs should be 7.
> +OVS_WAIT_UNTIL([test 7 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> +
> +$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap |
> trim_zeros > 2.packets
> +cat 2.expected | cut -c 1-120,125- > expout
> +cat 2.packets
> +AT_CHECK([cat 2.packets | cut -c 1-120,125- ], [0], [expout])
> +
>  OVN_CLEANUP([hv1])
>
>  AT_CLEANUP
> diff --git a/tests/test-ovn.c b/tests/test-ovn.c
> index ce9213c1d..6c7754eac 100644
> --- a/tests/test-ovn.c
> +++ b/tests/test-ovn.c
> @@ -207,6 +207,7 @@ create_gen_opts(struct hmap *dhcp_opts, struct hmap
> *dhcpv6_opts,
>      dhcp_opt_add(dhcpv6_opts, "domain_search",  24, "str");
>      dhcp_opt_add(dhcpv6_opts, "bootfile_name", 59, "str");
>      dhcp_opt_add(dhcpv6_opts, "bootfile_name_alt", 254, "str");
> +    dhcp_opt_add(dhcpv6_opts, "fqdn", 39, "domain");
>
>      /* IPv6 ND RA options. */
>      hmap_init(nd_ra_opts);
> --
> 2.40.1
>
>
Mark Michelson June 5, 2023, 7:31 p.m. UTC | #2
On 6/2/23 09:45, Ales Musil wrote:
> On Fri, Jun 2, 2023 at 12:43 PM Ales Musil <amusil@redhat.com> wrote:
> 
>> Add support for FQDN option (39), if specified the server
>> can overwrite FQDN for the client. It behaves similarly
>> to DHCP hostname option (12).
>>
>>
> Reported-at: https://bugzilla.redhat.com/2211890
> 
> I have created BZ for this it should be added on merge or I'll add it
> if there is a need for v2.
> 
> Signed-off-by: Ales Musil <amusil@redhat.com>
>> ---
>>   NEWS                 |   2 +
>>   controller/pinctrl.c |  27 +++++++-
>>   lib/actions.c        |  30 ++++++++-
>>   lib/ovn-l7.h         |  10 +++
>>   northd/ovn-northd.c  |   3 +-
>>   ovn-nb.xml           |   7 ++
>>   ovn-sb.ovsschema     |   6 +-
>>   tests/ovn.at         | 156 ++++++++++++++++++++++++-------------------
>>   tests/test-ovn.c     |   1 +
>>   9 files changed, 166 insertions(+), 76 deletions(-)
>>
>> diff --git a/NEWS b/NEWS
>> index 1532f635a..b4dd0da05 100644
>> --- a/NEWS
>> +++ b/NEWS
>> @@ -1,5 +1,7 @@
>>   Post v23.06.0
>>   -------------
>> +  - Add DHCPv6 "fqdn" (39) option, that works similarly to
>> +    DHCPv4 "hostname" (12) option.
>>
>>   OVN v23.06.0 - xx xxx xxxx
>>   --------------------------
>> diff --git a/controller/pinctrl.c b/controller/pinctrl.c
>> index c396ad4c2..51dbadc99 100644
>> --- a/controller/pinctrl.c
>> +++ b/controller/pinctrl.c
>> @@ -2288,7 +2288,7 @@ exit:
>>   static bool
>>   compose_out_dhcpv6_opts(struct ofpbuf *userdata,
>>                           struct ofpbuf *out_dhcpv6_opts,
>> -                        ovs_be32 iaid, bool ipxe_req)
>> +                        ovs_be32 iaid, bool ipxe_req, uint8_t fqdn_flags)
>>   {
>>       while (userdata->size) {
>>           struct dhcpv6_opt_header *userdata_opt = ofpbuf_try_pull(
>> @@ -2412,6 +2412,24 @@ compose_out_dhcpv6_opts(struct ofpbuf *userdata,
>>               break;
>>           }
>>
>> +        case DHCPV6_OPT_FQDN_CODE: {
>> +            if (fqdn_flags != DHCPV6_FQDN_FLAGS_UNDEFINED) {
>> +                struct dhcpv6_opt_header *header =
>> +                        ofpbuf_put_zeros(out_dhcpv6_opts, sizeof *header);
>> +                header->code = htons(DHCPV6_OPT_FQDN_CODE);
>> +                header->len = htons(size + 1);
>> +                uint8_t *flags = ofpbuf_put_zeros(out_dhcpv6_opts, 1);
>> +                /* If client requested N or S inform him that it
>> +                 * was overwritten by the server. */
>> +                if (fqdn_flags & DHCPV6_FQDN_FLAGS_N ||
>> +                    fqdn_flags & DHCPV6_FQDN_FLAGS_S) {
>> +                    *flags |= DHCPV6_FQDN_FLAGS_O;
>> +                }

I read through RFC 4704 section 4.1, and I think the flag-handling is 
slightly off here.

First, here's what is said about the "O" bit:

    The "O" bit indicates whether the server has overridden the client's
    preference for the "S" bit.  A client MUST set this bit to 0.  A
    server MUST set this bit to 1 if the "S" bit in its reply to the
    client does not match the "S" bit received from the client.

Based on my reading, we only need to set the O bit if the client set the 
S bit to 1. The client N bit should not factor into our setting of the O 
bit.

Then later, when discussing the "N" bit:

    ... A server sets the "N" bit to indicate whether the
    server SHALL (0) or SHALL NOT (1) perform DNS updates.  If the "N"
    bit is 1, the "S" bit MUST be 0.

I think our stance is that we are not performing DNS updates as the 
server, so we probably should be setting the N bit to 1 in all our replies.


>> +                ofpbuf_put(out_dhcpv6_opts, userdata_opt_data, size);
>> +            }
>> +            break;
>> +        }
>> +
>>           default:
>>               return false;
>>           }
>> @@ -2500,6 +2518,7 @@ pinctrl_handle_put_dhcpv6_opts(
>>       size_t l4_len = dp_packet_l4_size(pkt_in);
>>       uint8_t *end = (uint8_t *)in_udp + MIN(udp_len, l4_len);
>>       bool ipxe_req = false;
>> +    uint8_t fqdn_flags = DHCPV6_FQDN_FLAGS_UNDEFINED;
>>       while (in_dhcpv6_data < end) {
>>           struct dhcpv6_opt_header const *in_opt =
>>                (struct dhcpv6_opt_header *)in_dhcpv6_data;
>> @@ -2524,6 +2543,10 @@ pinctrl_handle_put_dhcpv6_opts(
>>               break;
>>           }
>>
>> +        case DHCPV6_OPT_FQDN_CODE:
>> +            fqdn_flags = *(in_dhcpv6_data + sizeof *in_opt);
>> +            break;
>> +
>>           default:
>>               break;
>>           }
>> @@ -2547,7 +2570,7 @@ pinctrl_handle_put_dhcpv6_opts(
>>           OFPBUF_STUB_INITIALIZER(out_ofpacts_dhcpv6_opts_stub);
>>
>>       if (!compose_out_dhcpv6_opts(userdata, &out_dhcpv6_opts,
>> -                                 iaid, ipxe_req)) {
>> +                                 iaid, ipxe_req, fqdn_flags)) {
>>           VLOG_WARN_RL(&rl, "Invalid userdata");
>>           goto exit;
>>       }
>> diff --git a/lib/actions.c b/lib/actions.c
>> index ec27223f9..0ec69bec9 100644
>> --- a/lib/actions.c
>> +++ b/lib/actions.c
>> @@ -2470,7 +2470,8 @@ parse_gen_opt(struct action_context *ctx, struct
>> ovnact_gen_option *o,
>>       }
>>
>>       if (!strcmp(o->option->type, "str") ||
>> -        !strcmp(o->option->type, "domains")) {
>> +        !strcmp(o->option->type, "domains") ||
>> +        !strcmp(o->option->type, "domain")) {
>>           if (o->value.type != EXPR_C_STRING) {
>>               lexer_error(ctx->lexer, "%s option %s requires string value.",
>>                           opts_type, o->option->name);
>> @@ -2903,6 +2904,33 @@ encode_put_dhcpv6_option(const struct
>> ovnact_gen_option *o,
>>           size = strlen(c->string);
>>           opt->len = htons(size);
>>           ofpbuf_put(ofpacts, c->string, size);
>> +    } else if (!strcmp(o->option->type, "domain")) {
>> +        /* The DNS format is 2 bytes longer than the "domain".
>> +         * It replaces every '.' with len of the next name. */
>> +        size_t domain_len = strlen(c->string);
>> +        size = domain_len + 2;
>> +        char *encoded = xzalloc(size);
>> +
>> +        int8_t label_len = 0;
>> +        for (size_t i = 0; i < domain_len; i++) {
>> +            if (c->string[i] == '.') {
>> +                encoded[i - label_len] = label_len;
>> +                label_len = 0;
>> +            } else {
>> +                encoded[i + 1] = c->string[i];
>> +                label_len++;
>> +            }
>> +        }
>> +
>> +        /* This is required for the last label if it doesn't end with '.'
>> */
>> +        if (label_len) {
>> +            encoded[domain_len - label_len] = label_len;
>> +        }

This section where you encode the domain name is nearly identical to 
existing code in dns_build_ptr_answer() in controller/pinctrl.c

I suggest extracting the common code into a function and calling that 
function here and in dns_build_ptr_answer().

>> +
>> +        opt->len = htons(size);
>> +        ofpbuf_put(ofpacts, encoded, size);
>> +
>> +        free(encoded);
>>       }
>>   }
>>
>> diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
>> index d9b103119..9dc331421 100644
>> --- a/lib/ovn-l7.h
>> +++ b/lib/ovn-l7.h
>> @@ -270,6 +270,7 @@ BUILD_ASSERT_DECL(DHCP_OPT_HEADER_LEN == sizeof(struct
>> dhcp_opt_header));
>>   #define DHCPV6_OPT_DOMAIN_SEARCH_CODE    24
>>   #define DHCPV6_OPT_IA_PD                 25
>>   #define DHCPV6_OPT_IA_PREFIX             26
>> +#define DHCPV6_OPT_FQDN_CODE             39
>>   #define DHCPV6_OPT_BOOT_FILE_URL         59
>>   #define DHCPV6_OPT_BOOT_FILE_URL_ALT    254
>>
>> @@ -291,6 +292,15 @@ BUILD_ASSERT_DECL(DHCP_OPT_HEADER_LEN ==
>> sizeof(struct dhcp_opt_header));
>>   #define DHCPV6_OPT_BOOTFILE_NAME_ALT \
>>       DHCP_OPTION("bootfile_name_alt", DHCPV6_OPT_BOOT_FILE_URL_ALT, "str")
>>
>> +#define DHCPV6_OPT_FQDN \
>> +    DHCP_OPTION("fqdn", DHCPV6_OPT_FQDN_CODE, "domain")
>> +
>> +/* DHCPv6 FQDN flags. RFC 4704 */
>> +#define DHCPV6_FQDN_FLAGS_UNDEFINED 0xff
>> +#define DHCPV6_FQDN_FLAGS_S 1 << 0
>> +#define DHCPV6_FQDN_FLAGS_O 1 << 1
>> +#define DHCPV6_FQDN_FLAGS_N 1 << 2
>> +
>>   OVS_PACKED(
>>   struct dhcpv6_opt_header {
>>       ovs_be16 code;
>> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
>> index 3515b68a2..2bafbd4af 100644
>> --- a/northd/ovn-northd.c
>> +++ b/northd/ovn-northd.c
>> @@ -273,7 +273,8 @@ static struct gen_opts_map supported_dhcpv6_opts[] = {
>>       DHCPV6_OPT_DOMAIN_SEARCH,
>>       DHCPV6_OPT_DNS_SERVER,
>>       DHCPV6_OPT_BOOTFILE_NAME,
>> -    DHCPV6_OPT_BOOTFILE_NAME_ALT
>> +    DHCPV6_OPT_BOOTFILE_NAME_ALT,
>> +    DHCPV6_OPT_FQDN,
>>   };
>>
>>   static bool
>> diff --git a/ovn-nb.xml b/ovn-nb.xml
>> index 9afe3b584..05e155b89 100644
>> --- a/ovn-nb.xml
>> +++ b/ovn-nb.xml
>> @@ -4191,6 +4191,13 @@ or
>>               way. Default value for this option is false.
>>             </p>
>>           </column>
>> +
>> +        <column name="options" key="fqdn">
>> +          <p>
>> +            The DHCPv6 option code for this option is 39.
>> +            If set, indicates the DHCPv6 option "FQDN".
>> +          </p>
>> +        </column>
>>         </group>
>>       </group>
>>
>> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
>> index f59af8cc5..06e16b403 100644
>> --- a/ovn-sb.ovsschema
>> +++ b/ovn-sb.ovsschema
>> @@ -1,7 +1,7 @@
>>   {
>>       "name": "OVN_Southbound",
>> -    "version": "20.27.2",
>> -    "cksum": "1291808617 30462",
>> +    "version": "20.27.3",
>> +    "cksum": "3876528905 30472",
>>       "tables": {
>>           "SB_Global": {
>>               "columns": {
>> @@ -311,7 +311,7 @@
>>                   "type": {
>>                       "type": {"key": {
>>                           "type": "string",
>> -                        "enum": ["set", ["ipv6", "str", "mac"]]}}}},
>> +                        "enum": ["set", ["ipv6", "str", "mac",
>> "domain"]]}}}},
>>               "isRoot": true},
>>           "Connection": {
>>               "columns": {
>> diff --git a/tests/ovn.at b/tests/ovn.at
>> index d8c694002..ab6052299 100644
>> --- a/tests/ovn.at
>> +++ b/tests/ovn.at
>> @@ -1697,6 +1697,9 @@ reg1[0] = put_dhcpv6_opts(bootfile_name="
>> https://127.0.0.1/boot.ipxe");
>>   reg1[0] = put_dhcpv6_opts(bootfile_name_alt="https://127.0.0.1/boot.ipxe
>> ");
>>       formats as reg1[0] = put_dhcpv6_opts(bootfile_name_alt = "
>> https://127.0.0.1/boot.ipxe");
>>       encodes as
>> controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40.00.fe.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,pause)
>> +reg1[0] = put_dhcpv6_opts(fqdn="ovn.org");
>> +    formats as reg1[0] = put_dhcpv6_opts(fqdn = "ovn.org");
>> +    encodes as
>> controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40.00.27.00.09.03.6f.76.6e.03.6f.72.67.00,pause)
>>
>>   # lookup_nd
>>   reg2[0] = lookup_nd(inport, ip6.dst, eth.src);
>> @@ -7083,6 +7086,7 @@ AT_CLEANUP
>>
>>   OVN_FOR_EACH_NORTHD([
>>   AT_SETUP([dhcpv6 : 1 HV, 2 LS, 5 LSPs])
>> +AT_SKIP_IF([test $HAVE_SCAPY = no])
>>   ovn_start
>>
>>   ovn-nbctl ls-add ls1
>> @@ -7171,61 +7175,56 @@ trim_zeros() {
>>   # packet should be received twice (one from ovn-controller and the other
>>   # from the "ovs-ofctl monitor br-int resume"
>>   test_dhcpv6() {
>> -    local inport=$1 src_mac=$2 src_lla=$3 msg_code=$4 offer_ip=$5 ipxe=$6
>> -    if test $ipxe -eq 2; then
>> -        req_len=34
>> -    elif test $msg_code != 0b; then
>> -        req_len=2a
>> -    else
>> -        req_len=1a
>> -    fi
>> -    local
>> request=ffffffffffff${src_mac}86dd6000000000${req_len}1101${src_lla}
>> -    # dst ip ff02::1:2
>> -    request=${request}ff020000000000000000000000010002
>> -    # udp header and dhcpv6 header
>> -    request=${request}0222022300${req_len}ffff${msg_code}010203
>> -    # Client identifier
>> -    request=${request}0001000a00030001${src_mac}
>> +    local inport=$1 src_mac=$2 src_lla=$3 msg_code=$4 offer_ip=$5
>> boot_file=$6
>> +    local fqdn=$7
>> +
>> +    local req_scapy="Ether(dst='ff:ff:ff:ff:ff:ff', src='${src_mac}')/ \
>> +                     IPv6(dst='ff02::1:2', src='${src_lla}')/ \
>> +                     UDP(sport=546, dport=547)/ \
>> +                     DHCP6(msgtype=${msg_code}, trid=0x010203)/ \
>> +                     DHCP6OptClientId(duid=DUID_LL(lladdr='${src_mac}'))"
>> +

Yay for switching to a more readable method of generating packets!

>>       # Add IA-NA (Identity Association for Non Temporary Address) if
>> msg_code
>>       # is not 11 (information request packet)
>> -    if test $msg_code != 0b; then
>> -        request=${request}0003000c0102030400000e1000001518
>> +    if test $msg_code != 11; then
>> +        req_scapy="${req_scapy}/DHCP6OptIA_NA(iaid=0x01020304, T1=3600,
>> T2=5400)"
>>       fi
>> -    if test $ipxe -eq 2; then
>> -        request=${request}000f0006000669505845
>> +    if test "$boot_file" = "bootfile_name"; then
>> +        local uc_data="USER_CLASS_DATA(data=b'iPXE')"
>> +
>> req_scapy="${req_scapy}/DHCP6OptUserClass(userclassdata=[[${uc_data}]])"
>>       fi
>> -    shift; shift; shift; shift; shift; shift;
>> +    if test -n "$fqdn"; then
>> +        req_scapy="${req_scapy}/DHCP6OptClientFQDN(flags=0x01,
>> fqdn=b'test')"
>> +    fi
>> +    request=$(fmt_pkt "${req_scapy}")
>> +    shift; shift; shift; shift; shift; shift; shift;
>>       if test $offer_ip != 0; then
>> -        local server_mac=000000100001
>> -        local server_lla=fe80000000000000020000fffe100001
>> -        local reply_code=07
>> -        if test $msg_code = 01; then
>> -            reply_code=02
>> -        fi
>> -        local msg_len=54
>> -        if test $ipxe -eq 1; then
>> -            msg_len=69
>> -        elif test $ipxe -eq 2; then
>> -            msg_len=65
>> -        elif test $offer_ip = 1; then
>> -            msg_len=28
>> +        local reply_code=7
>> +        if test $msg_code = 1; then
>> +            reply_code=2
>>           fi
>> -        local
>> reply=${src_mac}${server_mac}86dd6000000000${msg_len}1101${server_lla}${src_lla}
>> -        # udp header and dhcpv6 header
>> -        reply=${reply}0223022200${msg_len}ffff${reply_code}010203
>> -        # Client identifier
>> -        reply=${reply}0001000a00030001${src_mac}
>> -        # IA-NA
>> +
>> +        local rep_scapy="Ether(dst='${src_mac}',
>> src='00:00:00:10:00:01')/ \
>> +                         IPv6(dst='${src_lla}',
>> src='fe80::0200:00ff:fe10:0001')/ \
>> +                         UDP(sport=547, dport=546)/ \
>> +                         DHCP6(msgtype=${reply_code}, trid=0x010203)/ \
>> +
>>   DHCP6OptClientId(duid=DUID_LL(lladdr='${src_mac}'))"
>> +
>>           if test $offer_ip != 1; then
>> -
>> reply=${reply}0003002801020304ffffffffffffffff00050018${offer_ip}ffffffffffffffff
>> +            local ip_scapy="DHCP6OptIAAddress(addr='${offer_ip}', \
>> +                            preflft=0xffffffff, validlft=0xffffffff)"
>> +            rep_scapy="${rep_scapy}/ \
>> +                       DHCP6OptIA_NA(iaid=0x01020304, T1=0xffffffff, \
>> +                       T2=0xffffffff, ianaopts=[[${ip_scapy}]])"
>>           fi
>> -        if test $ipxe -eq 1; then
>> -            reply=${reply}003b0011626f6f7466696c655f6e616d655f616c74
>> -        elif test $ipxe -eq 2; then
>> -            reply=${reply}003b000d626f6f7466696c655f6e616d65
>> +        if test -n "$boot_file"; then
>> +
>> rep_scapy="${rep_scapy}/DHCP6OptBootFileUrl(optdata=b'${boot_file}')"
>>           fi
>> -        # Server identifier
>> -        reply=${reply}0002000a00030001${server_mac}
>> +        if test -n "$fqdn"; then
>> +            rep_scapy="${rep_scapy}/DHCP6OptClientFQDN(flags=0x02,
>> fqdn=b'${fqdn}')"
>> +        fi
>> +
>> rep_scapy="${rep_scapy}/DHCP6OptServerId(duid=DUID_LL(lladdr='00:00:00:10:00:01'))"
>> +        reply=$(fmt_pkt "${rep_scapy}")
>>           echo $reply | trim_zeros >> $inport.expected
>>       else
>>           for outport; do
>> @@ -7249,16 +7248,16 @@ echo "---------------------"
>>   ovn-sbctl list logical_flow
>>   echo "---------------------"
>>
>> -ovn-sbctl dump-flows > sbflows
>> +#ovn-sbctl dump-flows > sbflows
>>   AT_CAPTURE_FILE([sbflows])
>>
>>   echo "------ hv1 dump ----------"
>>   as hv1 ovs-ofctl dump-flows br-int
>>
>> -src_mac=f00000000001
>> -src_lla=fe80000000000000f20000fffe000001
>> -offer_ip=ae700000000000000000000000000004
>> -test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip 0
>> +src_mac="f0:00:00:00:00:01"
>> +src_lla="fe80::f200:00ff:fe00:0001"
>> +offer_ip="ae70::4"
>> +test_dhcpv6 1 $src_mac $src_lla 1 $offer_ip "" ""
>>
>>   # NXT_RESUMEs should be 1.
>>   OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
>> @@ -7281,12 +7280,12 @@ rm  1.expected
>>   reset_pcap_file hv1-vif1 hv1/vif1
>>   reset_pcap_file hv1-vif2 hv1/vif2
>>
>> -src_mac=f00000000002
>> -src_lla=fe80000000000000f20000fffe000002
>> -offer_ip=ae700000000000000000000000000005
>> +src_mac="f0:00:00:00:00:02"
>> +src_lla="fe80::f200:00ff:fe00:0002"
>> +offer_ip="ae70::5"
>>   # Set invalid msg_type
>>
>> -test_dhcpv6 2 $src_mac $src_lla 10 0 0 1 1
>> +test_dhcpv6 2 $src_mac $src_lla 16 0 "" "" 1 1
>>
>>   # NXT_RESUMEs should be 2.
>>   OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
>> @@ -7305,9 +7304,9 @@ AT_CHECK([cat 1.packets], [0], [expout])
>>   # There should be no DHCPv6 reply from ovn-controller and the request
>> packet
>>   # should be received by ls2-lp2.
>>
>> -src_mac=f00000000003
>> -src_lla=fe80000000000000f20000fffe000003
>> -test_dhcpv6 3 $src_mac $src_lla 01 0 0 4
>> +src_mac="f0:00:00:00:00:03"
>> +src_lla="fe80::f200:00ff:fe00:0003"
>> +test_dhcpv6 3 $src_mac $src_lla 1 0 "" "" 4
>>
>>   # NXT_RESUMEs should be 2 only.
>>   OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
>> @@ -7323,10 +7322,10 @@ AT_CHECK([cat 4.packets], [0], [expout])
>>
>>   # Send DHCPv6 packet on ls1-lp3. native DHCPv6 works as stateless mode
>> for this port.
>>   # The DHCPv6 reply shouldn't contain offer_ip.
>> -src_mac=f00000000022
>> -src_lla=fe80000000000000f20000fffe000022
>> +src_mac="f0:00:00:00:00:22"
>> +src_lla="fe80::f200:00ff:fe00:0022"
>>   reset_pcap_file hv1-vif5 hv1/vif5
>> -test_dhcpv6 5 $src_mac $src_lla 01 1 0 5
>> +test_dhcpv6 5 $src_mac $src_lla 1 1 "" "" 5
>>
>>   # NXT_RESUMEs should be 3.
>>   OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
>> @@ -7338,11 +7337,11 @@ AT_CHECK([cat 5.packets | cut -c 1-120,125- ],
>> [0], [expout])
>>
>>   # Send DHCPv6 information request (code 11) on ls1-lp3. The DHCPv6 reply
>>   # shouldn't contain offer_ip
>> -src_mac=f00000000022
>> -src_lla=fe80000000000000f20000fffe000022
>> +src_mac="f0:00:00:00:00:22"
>> +src_lla="fe80::f200:00ff:fe00:0022"
>>   reset_pcap_file hv1-vif5 hv1/vif5
>>   rm -f 5.expected
>> -test_dhcpv6 5 $src_mac $src_lla 0b 1 0 5
>> +test_dhcpv6 5 $src_mac $src_lla 11 1 "" "" 5
>>
>>   # NXT_RESUMEs should be 4.
>>   OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
>> @@ -7363,11 +7362,11 @@ ovn-nbctl lsp-set-dhcpv6-options ls1-lp2 ${d1}
>>
>>   reset_pcap_file hv1-vif2 hv1/vif2
>>
>> -src_mac=f00000000002
>> -src_lla=fe80000000000000f20000fffe000002
>> -offer_ip=ae700000000000000000000000000005
>> +src_mac="f0:00:00:00:00:02"
>> +src_lla="fe80::f200:00ff:fe00:0002"
>> +offer_ip="ae70::5"
>>
>> -test_dhcpv6 2 $src_mac $src_lla 01 $offer_ip 1
>> +test_dhcpv6 2 $src_mac $src_lla 1 $offer_ip "bootfile_name_alt" ""
>>   # NXT_RESUMEs should be 5.
>>   OVS_WAIT_UNTIL([test 5 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
>>
>> @@ -7381,7 +7380,7 @@ AT_CHECK([cat 2.packets | cut -c 1-120,125- ], [0],
>> [expout])
>>   reset_pcap_file hv1-vif2 hv1/vif2
>>   rm 2.packets 2.expected
>>
>> -test_dhcpv6 2 $src_mac $src_lla 01 $offer_ip 2
>> +test_dhcpv6 2 $src_mac $src_lla 1 $offer_ip "bootfile_name" ""
>>   # NXT_RESUMEs should be 6.
>>   OVS_WAIT_UNTIL([test 6 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
>>
>> @@ -7389,6 +7388,25 @@ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in"
>> hv1/vif2-tx.pcap | trim_zeros > 2.pa
>>   cat 2.expected | cut -c 1-120,125- > expout
>>   AT_CHECK([cat 2.packets | cut -c 1-120,125- ], [0], [expout])
>>
>> +ovn-nbctl --all destroy dhcp-option
>> +d1="$(ovn-nbctl create DHCP_Options cidr="ae70\:\:/64")"
>> +ovn-nbctl dhcp-options-set-options $d1 \
>> +    server_id=00:00:00:10:00:01 \
>> +    fqdn=\"ovn.org\"
>> +ovn-nbctl --wait=hv lsp-set-dhcpv6-options ls1-lp2 ${d1}
>> +
>> +reset_pcap_file hv1-vif2 hv1/vif2
>> +rm 2.packets 2.expected
>> +
>> +test_dhcpv6 2 $src_mac $src_lla 1 $offer_ip "" "ovn.org"
>> +# NXT_RESUMEs should be 7.
>> +OVS_WAIT_UNTIL([test 7 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
>> +
>> +$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap |
>> trim_zeros > 2.packets
>> +cat 2.expected | cut -c 1-120,125- > expout
>> +cat 2.packets
>> +AT_CHECK([cat 2.packets | cut -c 1-120,125- ], [0], [expout])
>> +
>>   OVN_CLEANUP([hv1])
>>
>>   AT_CLEANUP
>> diff --git a/tests/test-ovn.c b/tests/test-ovn.c
>> index ce9213c1d..6c7754eac 100644
>> --- a/tests/test-ovn.c
>> +++ b/tests/test-ovn.c
>> @@ -207,6 +207,7 @@ create_gen_opts(struct hmap *dhcp_opts, struct hmap
>> *dhcpv6_opts,
>>       dhcp_opt_add(dhcpv6_opts, "domain_search",  24, "str");
>>       dhcp_opt_add(dhcpv6_opts, "bootfile_name", 59, "str");
>>       dhcp_opt_add(dhcpv6_opts, "bootfile_name_alt", 254, "str");
>> +    dhcp_opt_add(dhcpv6_opts, "fqdn", 39, "domain");
>>
>>       /* IPv6 ND RA options. */
>>       hmap_init(nd_ra_opts);
>> --
>> 2.40.1
>>
>>
>
Ales Musil June 6, 2023, 6:50 a.m. UTC | #3
On Mon, Jun 5, 2023 at 9:31 PM Mark Michelson <mmichels@redhat.com> wrote:

> On 6/2/23 09:45, Ales Musil wrote:
> > On Fri, Jun 2, 2023 at 12:43 PM Ales Musil <amusil@redhat.com> wrote:
> >
> >> Add support for FQDN option (39), if specified the server
> >> can overwrite FQDN for the client. It behaves similarly
> >> to DHCP hostname option (12).
> >>
> >>
> > Reported-at: https://bugzilla.redhat.com/2211890
> >
> > I have created BZ for this it should be added on merge or I'll add it
> > if there is a need for v2.
> >
> > Signed-off-by: Ales Musil <amusil@redhat.com>
> >> ---
> >>   NEWS                 |   2 +
> >>   controller/pinctrl.c |  27 +++++++-
> >>   lib/actions.c        |  30 ++++++++-
> >>   lib/ovn-l7.h         |  10 +++
> >>   northd/ovn-northd.c  |   3 +-
> >>   ovn-nb.xml           |   7 ++
> >>   ovn-sb.ovsschema     |   6 +-
> >>   tests/ovn.at         | 156
> ++++++++++++++++++++++++-------------------
> >>   tests/test-ovn.c     |   1 +
> >>   9 files changed, 166 insertions(+), 76 deletions(-)
> >>
> >> diff --git a/NEWS b/NEWS
> >> index 1532f635a..b4dd0da05 100644
> >> --- a/NEWS
> >> +++ b/NEWS
> >> @@ -1,5 +1,7 @@
> >>   Post v23.06.0
> >>   -------------
> >> +  - Add DHCPv6 "fqdn" (39) option, that works similarly to
> >> +    DHCPv4 "hostname" (12) option.
> >>
> >>   OVN v23.06.0 - xx xxx xxxx
> >>   --------------------------
> >> diff --git a/controller/pinctrl.c b/controller/pinctrl.c
> >> index c396ad4c2..51dbadc99 100644
> >> --- a/controller/pinctrl.c
> >> +++ b/controller/pinctrl.c
> >> @@ -2288,7 +2288,7 @@ exit:
> >>   static bool
> >>   compose_out_dhcpv6_opts(struct ofpbuf *userdata,
> >>                           struct ofpbuf *out_dhcpv6_opts,
> >> -                        ovs_be32 iaid, bool ipxe_req)
> >> +                        ovs_be32 iaid, bool ipxe_req, uint8_t
> fqdn_flags)
> >>   {
> >>       while (userdata->size) {
> >>           struct dhcpv6_opt_header *userdata_opt = ofpbuf_try_pull(
> >> @@ -2412,6 +2412,24 @@ compose_out_dhcpv6_opts(struct ofpbuf *userdata,
> >>               break;
> >>           }
> >>
> >> +        case DHCPV6_OPT_FQDN_CODE: {
> >> +            if (fqdn_flags != DHCPV6_FQDN_FLAGS_UNDEFINED) {
> >> +                struct dhcpv6_opt_header *header =
> >> +                        ofpbuf_put_zeros(out_dhcpv6_opts, sizeof
> *header);
> >> +                header->code = htons(DHCPV6_OPT_FQDN_CODE);
> >> +                header->len = htons(size + 1);
> >> +                uint8_t *flags = ofpbuf_put_zeros(out_dhcpv6_opts, 1);
> >> +                /* If client requested N or S inform him that it
> >> +                 * was overwritten by the server. */
> >> +                if (fqdn_flags & DHCPV6_FQDN_FLAGS_N ||
> >> +                    fqdn_flags & DHCPV6_FQDN_FLAGS_S) {
> >> +                    *flags |= DHCPV6_FQDN_FLAGS_O;
> >> +                }
>
> I read through RFC 4704 section 4.1, and I think the flag-handling is
> slightly off here.
>
> First, here's what is said about the "O" bit:
>
>     The "O" bit indicates whether the server has overridden the client's
>     preference for the "S" bit.  A client MUST set this bit to 0.  A
>     server MUST set this bit to 1 if the "S" bit in its reply to the
>     client does not match the "S" bit received from the client.
>
> Based on my reading, we only need to set the O bit if the client set the
> S bit to 1. The client N bit should not factor into our setting of the O
> bit.
>
> Then later, when discussing the "N" bit:
>
>     ... A server sets the "N" bit to indicate whether the
>     server SHALL (0) or SHALL NOT (1) perform DNS updates.  If the "N"
>     bit is 1, the "S" bit MUST be 0.
>
> I think our stance is that we are not performing DNS updates as the
> server, so we probably should be setting the N bit to 1 in all our replies.
>


NGL the flags are a bit confusing. But reading through it again it seems
that we need to
always set "S" = 0, "N" = 1 and "O" = 1 if "S" from client == 1.

>
>
> >> +                ofpbuf_put(out_dhcpv6_opts, userdata_opt_data, size);
> >> +            }
> >> +            break;
> >> +        }
> >> +
> >>           default:
> >>               return false;
> >>           }
> >> @@ -2500,6 +2518,7 @@ pinctrl_handle_put_dhcpv6_opts(
> >>       size_t l4_len = dp_packet_l4_size(pkt_in);
> >>       uint8_t *end = (uint8_t *)in_udp + MIN(udp_len, l4_len);
> >>       bool ipxe_req = false;
> >> +    uint8_t fqdn_flags = DHCPV6_FQDN_FLAGS_UNDEFINED;
> >>       while (in_dhcpv6_data < end) {
> >>           struct dhcpv6_opt_header const *in_opt =
> >>                (struct dhcpv6_opt_header *)in_dhcpv6_data;
> >> @@ -2524,6 +2543,10 @@ pinctrl_handle_put_dhcpv6_opts(
> >>               break;
> >>           }
> >>
> >> +        case DHCPV6_OPT_FQDN_CODE:
> >> +            fqdn_flags = *(in_dhcpv6_data + sizeof *in_opt);
> >> +            break;
> >> +
> >>           default:
> >>               break;
> >>           }
> >> @@ -2547,7 +2570,7 @@ pinctrl_handle_put_dhcpv6_opts(
> >>           OFPBUF_STUB_INITIALIZER(out_ofpacts_dhcpv6_opts_stub);
> >>
> >>       if (!compose_out_dhcpv6_opts(userdata, &out_dhcpv6_opts,
> >> -                                 iaid, ipxe_req)) {
> >> +                                 iaid, ipxe_req, fqdn_flags)) {
> >>           VLOG_WARN_RL(&rl, "Invalid userdata");
> >>           goto exit;
> >>       }
> >> diff --git a/lib/actions.c b/lib/actions.c
> >> index ec27223f9..0ec69bec9 100644
> >> --- a/lib/actions.c
> >> +++ b/lib/actions.c
> >> @@ -2470,7 +2470,8 @@ parse_gen_opt(struct action_context *ctx, struct
> >> ovnact_gen_option *o,
> >>       }
> >>
> >>       if (!strcmp(o->option->type, "str") ||
> >> -        !strcmp(o->option->type, "domains")) {
> >> +        !strcmp(o->option->type, "domains") ||
> >> +        !strcmp(o->option->type, "domain")) {
> >>           if (o->value.type != EXPR_C_STRING) {
> >>               lexer_error(ctx->lexer, "%s option %s requires string
> value.",
> >>                           opts_type, o->option->name);
> >> @@ -2903,6 +2904,33 @@ encode_put_dhcpv6_option(const struct
> >> ovnact_gen_option *o,
> >>           size = strlen(c->string);
> >>           opt->len = htons(size);
> >>           ofpbuf_put(ofpacts, c->string, size);
> >> +    } else if (!strcmp(o->option->type, "domain")) {
> >> +        /* The DNS format is 2 bytes longer than the "domain".
> >> +         * It replaces every '.' with len of the next name. */
> >> +        size_t domain_len = strlen(c->string);
> >> +        size = domain_len + 2;
> >> +        char *encoded = xzalloc(size);
> >> +
> >> +        int8_t label_len = 0;
> >> +        for (size_t i = 0; i < domain_len; i++) {
> >> +            if (c->string[i] == '.') {
> >> +                encoded[i - label_len] = label_len;
> >> +                label_len = 0;
> >> +            } else {
> >> +                encoded[i + 1] = c->string[i];
> >> +                label_len++;
> >> +            }
> >> +        }
> >> +
> >> +        /* This is required for the last label if it doesn't end with
> '.'
> >> */
> >> +        if (label_len) {
> >> +            encoded[domain_len - label_len] = label_len;
> >> +        }
>
> This section where you encode the domain name is nearly identical to
> existing code in dns_build_ptr_answer() in controller/pinctrl.c
>
> I suggest extracting the common code into a function and calling that
> function here and in dns_build_ptr_answer().
>

Yeah you are right, I'll extract it to some common place.


> >> +
> >> +        opt->len = htons(size);
> >> +        ofpbuf_put(ofpacts, encoded, size);
> >> +
> >> +        free(encoded);
> >>       }
> >>   }
> >>
> >> diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
> >> index d9b103119..9dc331421 100644
> >> --- a/lib/ovn-l7.h
> >> +++ b/lib/ovn-l7.h
> >> @@ -270,6 +270,7 @@ BUILD_ASSERT_DECL(DHCP_OPT_HEADER_LEN ==
> sizeof(struct
> >> dhcp_opt_header));
> >>   #define DHCPV6_OPT_DOMAIN_SEARCH_CODE    24
> >>   #define DHCPV6_OPT_IA_PD                 25
> >>   #define DHCPV6_OPT_IA_PREFIX             26
> >> +#define DHCPV6_OPT_FQDN_CODE             39
> >>   #define DHCPV6_OPT_BOOT_FILE_URL         59
> >>   #define DHCPV6_OPT_BOOT_FILE_URL_ALT    254
> >>
> >> @@ -291,6 +292,15 @@ BUILD_ASSERT_DECL(DHCP_OPT_HEADER_LEN ==
> >> sizeof(struct dhcp_opt_header));
> >>   #define DHCPV6_OPT_BOOTFILE_NAME_ALT \
> >>       DHCP_OPTION("bootfile_name_alt", DHCPV6_OPT_BOOT_FILE_URL_ALT,
> "str")
> >>
> >> +#define DHCPV6_OPT_FQDN \
> >> +    DHCP_OPTION("fqdn", DHCPV6_OPT_FQDN_CODE, "domain")
> >> +
> >> +/* DHCPv6 FQDN flags. RFC 4704 */
> >> +#define DHCPV6_FQDN_FLAGS_UNDEFINED 0xff
> >> +#define DHCPV6_FQDN_FLAGS_S 1 << 0
> >> +#define DHCPV6_FQDN_FLAGS_O 1 << 1
> >> +#define DHCPV6_FQDN_FLAGS_N 1 << 2
> >> +
> >>   OVS_PACKED(
> >>   struct dhcpv6_opt_header {
> >>       ovs_be16 code;
> >> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> >> index 3515b68a2..2bafbd4af 100644
> >> --- a/northd/ovn-northd.c
> >> +++ b/northd/ovn-northd.c
> >> @@ -273,7 +273,8 @@ static struct gen_opts_map supported_dhcpv6_opts[]
> = {
> >>       DHCPV6_OPT_DOMAIN_SEARCH,
> >>       DHCPV6_OPT_DNS_SERVER,
> >>       DHCPV6_OPT_BOOTFILE_NAME,
> >> -    DHCPV6_OPT_BOOTFILE_NAME_ALT
> >> +    DHCPV6_OPT_BOOTFILE_NAME_ALT,
> >> +    DHCPV6_OPT_FQDN,
> >>   };
> >>
> >>   static bool
> >> diff --git a/ovn-nb.xml b/ovn-nb.xml
> >> index 9afe3b584..05e155b89 100644
> >> --- a/ovn-nb.xml
> >> +++ b/ovn-nb.xml
> >> @@ -4191,6 +4191,13 @@ or
> >>               way. Default value for this option is false.
> >>             </p>
> >>           </column>
> >> +
> >> +        <column name="options" key="fqdn">
> >> +          <p>
> >> +            The DHCPv6 option code for this option is 39.
> >> +            If set, indicates the DHCPv6 option "FQDN".
> >> +          </p>
> >> +        </column>
> >>         </group>
> >>       </group>
> >>
> >> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
> >> index f59af8cc5..06e16b403 100644
> >> --- a/ovn-sb.ovsschema
> >> +++ b/ovn-sb.ovsschema
> >> @@ -1,7 +1,7 @@
> >>   {
> >>       "name": "OVN_Southbound",
> >> -    "version": "20.27.2",
> >> -    "cksum": "1291808617 30462",
> >> +    "version": "20.27.3",
> >> +    "cksum": "3876528905 30472",
> >>       "tables": {
> >>           "SB_Global": {
> >>               "columns": {
> >> @@ -311,7 +311,7 @@
> >>                   "type": {
> >>                       "type": {"key": {
> >>                           "type": "string",
> >> -                        "enum": ["set", ["ipv6", "str", "mac"]]}}}},
> >> +                        "enum": ["set", ["ipv6", "str", "mac",
> >> "domain"]]}}}},
> >>               "isRoot": true},
> >>           "Connection": {
> >>               "columns": {
> >> diff --git a/tests/ovn.at b/tests/ovn.at
> >> index d8c694002..ab6052299 100644
> >> --- a/tests/ovn.at
> >> +++ b/tests/ovn.at
> >> @@ -1697,6 +1697,9 @@ reg1[0] = put_dhcpv6_opts(bootfile_name="
> >> https://127.0.0.1/boot.ipxe");
> >>   reg1[0] = put_dhcpv6_opts(bootfile_name_alt="
> https://127.0.0.1/boot.ipxe
> >> ");
> >>       formats as reg1[0] = put_dhcpv6_opts(bootfile_name_alt = "
> >> https://127.0.0.1/boot.ipxe");
> >>       encodes as
> >>
> controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40.00.fe.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,pause)
> >> +reg1[0] = put_dhcpv6_opts(fqdn="ovn.org");
> >> +    formats as reg1[0] = put_dhcpv6_opts(fqdn = "ovn.org");
> >> +    encodes as
> >>
> controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40.00.27.00.09.03.6f.76.6e.03.6f.72.67.00,pause)
> >>
> >>   # lookup_nd
> >>   reg2[0] = lookup_nd(inport, ip6.dst, eth.src);
> >> @@ -7083,6 +7086,7 @@ AT_CLEANUP
> >>
> >>   OVN_FOR_EACH_NORTHD([
> >>   AT_SETUP([dhcpv6 : 1 HV, 2 LS, 5 LSPs])
> >> +AT_SKIP_IF([test $HAVE_SCAPY = no])
> >>   ovn_start
> >>
> >>   ovn-nbctl ls-add ls1
> >> @@ -7171,61 +7175,56 @@ trim_zeros() {
> >>   # packet should be received twice (one from ovn-controller and the
> other
> >>   # from the "ovs-ofctl monitor br-int resume"
> >>   test_dhcpv6() {
> >> -    local inport=$1 src_mac=$2 src_lla=$3 msg_code=$4 offer_ip=$5
> ipxe=$6
> >> -    if test $ipxe -eq 2; then
> >> -        req_len=34
> >> -    elif test $msg_code != 0b; then
> >> -        req_len=2a
> >> -    else
> >> -        req_len=1a
> >> -    fi
> >> -    local
> >> request=ffffffffffff${src_mac}86dd6000000000${req_len}1101${src_lla}
> >> -    # dst ip ff02::1:2
> >> -    request=${request}ff020000000000000000000000010002
> >> -    # udp header and dhcpv6 header
> >> -    request=${request}0222022300${req_len}ffff${msg_code}010203
> >> -    # Client identifier
> >> -    request=${request}0001000a00030001${src_mac}
> >> +    local inport=$1 src_mac=$2 src_lla=$3 msg_code=$4 offer_ip=$5
> >> boot_file=$6
> >> +    local fqdn=$7
> >> +
> >> +    local req_scapy="Ether(dst='ff:ff:ff:ff:ff:ff', src='${src_mac}')/
> \
> >> +                     IPv6(dst='ff02::1:2', src='${src_lla}')/ \
> >> +                     UDP(sport=546, dport=547)/ \
> >> +                     DHCP6(msgtype=${msg_code}, trid=0x010203)/ \
> >> +
>  DHCP6OptClientId(duid=DUID_LL(lladdr='${src_mac}'))"
> >> +
>
> Yay for switching to a more readable method of generating packets!
>

It's a pretty time consuming process for packets with so many nested
options, but worth it.


>
> >>       # Add IA-NA (Identity Association for Non Temporary Address) if
> >> msg_code
> >>       # is not 11 (information request packet)
> >> -    if test $msg_code != 0b; then
> >> -        request=${request}0003000c0102030400000e1000001518
> >> +    if test $msg_code != 11; then
> >> +        req_scapy="${req_scapy}/DHCP6OptIA_NA(iaid=0x01020304, T1=3600,
> >> T2=5400)"
> >>       fi
> >> -    if test $ipxe -eq 2; then
> >> -        request=${request}000f0006000669505845
> >> +    if test "$boot_file" = "bootfile_name"; then
> >> +        local uc_data="USER_CLASS_DATA(data=b'iPXE')"
> >> +
> >> req_scapy="${req_scapy}/DHCP6OptUserClass(userclassdata=[[${uc_data}]])"
> >>       fi
> >> -    shift; shift; shift; shift; shift; shift;
> >> +    if test -n "$fqdn"; then
> >> +        req_scapy="${req_scapy}/DHCP6OptClientFQDN(flags=0x01,
> >> fqdn=b'test')"
> >> +    fi
> >> +    request=$(fmt_pkt "${req_scapy}")
> >> +    shift; shift; shift; shift; shift; shift; shift;
> >>       if test $offer_ip != 0; then
> >> -        local server_mac=000000100001
> >> -        local server_lla=fe80000000000000020000fffe100001
> >> -        local reply_code=07
> >> -        if test $msg_code = 01; then
> >> -            reply_code=02
> >> -        fi
> >> -        local msg_len=54
> >> -        if test $ipxe -eq 1; then
> >> -            msg_len=69
> >> -        elif test $ipxe -eq 2; then
> >> -            msg_len=65
> >> -        elif test $offer_ip = 1; then
> >> -            msg_len=28
> >> +        local reply_code=7
> >> +        if test $msg_code = 1; then
> >> +            reply_code=2
> >>           fi
> >> -        local
> >>
> reply=${src_mac}${server_mac}86dd6000000000${msg_len}1101${server_lla}${src_lla}
> >> -        # udp header and dhcpv6 header
> >> -        reply=${reply}0223022200${msg_len}ffff${reply_code}010203
> >> -        # Client identifier
> >> -        reply=${reply}0001000a00030001${src_mac}
> >> -        # IA-NA
> >> +
> >> +        local rep_scapy="Ether(dst='${src_mac}',
> >> src='00:00:00:10:00:01')/ \
> >> +                         IPv6(dst='${src_lla}',
> >> src='fe80::0200:00ff:fe10:0001')/ \
> >> +                         UDP(sport=547, dport=546)/ \
> >> +                         DHCP6(msgtype=${reply_code}, trid=0x010203)/ \
> >> +
> >>   DHCP6OptClientId(duid=DUID_LL(lladdr='${src_mac}'))"
> >> +
> >>           if test $offer_ip != 1; then
> >> -
> >>
> reply=${reply}0003002801020304ffffffffffffffff00050018${offer_ip}ffffffffffffffff
> >> +            local ip_scapy="DHCP6OptIAAddress(addr='${offer_ip}', \
> >> +                            preflft=0xffffffff, validlft=0xffffffff)"
> >> +            rep_scapy="${rep_scapy}/ \
> >> +                       DHCP6OptIA_NA(iaid=0x01020304, T1=0xffffffff, \
> >> +                       T2=0xffffffff, ianaopts=[[${ip_scapy}]])"
> >>           fi
> >> -        if test $ipxe -eq 1; then
> >> -            reply=${reply}003b0011626f6f7466696c655f6e616d655f616c74
> >> -        elif test $ipxe -eq 2; then
> >> -            reply=${reply}003b000d626f6f7466696c655f6e616d65
> >> +        if test -n "$boot_file"; then
> >> +
> >> rep_scapy="${rep_scapy}/DHCP6OptBootFileUrl(optdata=b'${boot_file}')"
> >>           fi
> >> -        # Server identifier
> >> -        reply=${reply}0002000a00030001${server_mac}
> >> +        if test -n "$fqdn"; then
> >> +            rep_scapy="${rep_scapy}/DHCP6OptClientFQDN(flags=0x02,
> >> fqdn=b'${fqdn}')"
> >> +        fi
> >> +
> >>
> rep_scapy="${rep_scapy}/DHCP6OptServerId(duid=DUID_LL(lladdr='00:00:00:10:00:01'))"
> >> +        reply=$(fmt_pkt "${rep_scapy}")
> >>           echo $reply | trim_zeros >> $inport.expected
> >>       else
> >>           for outport; do
> >> @@ -7249,16 +7248,16 @@ echo "---------------------"
> >>   ovn-sbctl list logical_flow
> >>   echo "---------------------"
> >>
> >> -ovn-sbctl dump-flows > sbflows
> >> +#ovn-sbctl dump-flows > sbflows
> >>   AT_CAPTURE_FILE([sbflows])
> >>
> >>   echo "------ hv1 dump ----------"
> >>   as hv1 ovs-ofctl dump-flows br-int
> >>
> >> -src_mac=f00000000001
> >> -src_lla=fe80000000000000f20000fffe000001
> >> -offer_ip=ae700000000000000000000000000004
> >> -test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip 0
> >> +src_mac="f0:00:00:00:00:01"
> >> +src_lla="fe80::f200:00ff:fe00:0001"
> >> +offer_ip="ae70::4"
> >> +test_dhcpv6 1 $src_mac $src_lla 1 $offer_ip "" ""
> >>
> >>   # NXT_RESUMEs should be 1.
> >>   OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c
> NXT_RESUME`])
> >> @@ -7281,12 +7280,12 @@ rm  1.expected
> >>   reset_pcap_file hv1-vif1 hv1/vif1
> >>   reset_pcap_file hv1-vif2 hv1/vif2
> >>
> >> -src_mac=f00000000002
> >> -src_lla=fe80000000000000f20000fffe000002
> >> -offer_ip=ae700000000000000000000000000005
> >> +src_mac="f0:00:00:00:00:02"
> >> +src_lla="fe80::f200:00ff:fe00:0002"
> >> +offer_ip="ae70::5"
> >>   # Set invalid msg_type
> >>
> >> -test_dhcpv6 2 $src_mac $src_lla 10 0 0 1 1
> >> +test_dhcpv6 2 $src_mac $src_lla 16 0 "" "" 1 1
> >>
> >>   # NXT_RESUMEs should be 2.
> >>   OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c
> NXT_RESUME`])
> >> @@ -7305,9 +7304,9 @@ AT_CHECK([cat 1.packets], [0], [expout])
> >>   # There should be no DHCPv6 reply from ovn-controller and the request
> >> packet
> >>   # should be received by ls2-lp2.
> >>
> >> -src_mac=f00000000003
> >> -src_lla=fe80000000000000f20000fffe000003
> >> -test_dhcpv6 3 $src_mac $src_lla 01 0 0 4
> >> +src_mac="f0:00:00:00:00:03"
> >> +src_lla="fe80::f200:00ff:fe00:0003"
> >> +test_dhcpv6 3 $src_mac $src_lla 1 0 "" "" 4
> >>
> >>   # NXT_RESUMEs should be 2 only.
> >>   OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c
> NXT_RESUME`])
> >> @@ -7323,10 +7322,10 @@ AT_CHECK([cat 4.packets], [0], [expout])
> >>
> >>   # Send DHCPv6 packet on ls1-lp3. native DHCPv6 works as stateless mode
> >> for this port.
> >>   # The DHCPv6 reply shouldn't contain offer_ip.
> >> -src_mac=f00000000022
> >> -src_lla=fe80000000000000f20000fffe000022
> >> +src_mac="f0:00:00:00:00:22"
> >> +src_lla="fe80::f200:00ff:fe00:0022"
> >>   reset_pcap_file hv1-vif5 hv1/vif5
> >> -test_dhcpv6 5 $src_mac $src_lla 01 1 0 5
> >> +test_dhcpv6 5 $src_mac $src_lla 1 1 "" "" 5
> >>
> >>   # NXT_RESUMEs should be 3.
> >>   OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c
> NXT_RESUME`])
> >> @@ -7338,11 +7337,11 @@ AT_CHECK([cat 5.packets | cut -c 1-120,125- ],
> >> [0], [expout])
> >>
> >>   # Send DHCPv6 information request (code 11) on ls1-lp3. The DHCPv6
> reply
> >>   # shouldn't contain offer_ip
> >> -src_mac=f00000000022
> >> -src_lla=fe80000000000000f20000fffe000022
> >> +src_mac="f0:00:00:00:00:22"
> >> +src_lla="fe80::f200:00ff:fe00:0022"
> >>   reset_pcap_file hv1-vif5 hv1/vif5
> >>   rm -f 5.expected
> >> -test_dhcpv6 5 $src_mac $src_lla 0b 1 0 5
> >> +test_dhcpv6 5 $src_mac $src_lla 11 1 "" "" 5
> >>
> >>   # NXT_RESUMEs should be 4.
> >>   OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c
> NXT_RESUME`])
> >> @@ -7363,11 +7362,11 @@ ovn-nbctl lsp-set-dhcpv6-options ls1-lp2 ${d1}
> >>
> >>   reset_pcap_file hv1-vif2 hv1/vif2
> >>
> >> -src_mac=f00000000002
> >> -src_lla=fe80000000000000f20000fffe000002
> >> -offer_ip=ae700000000000000000000000000005
> >> +src_mac="f0:00:00:00:00:02"
> >> +src_lla="fe80::f200:00ff:fe00:0002"
> >> +offer_ip="ae70::5"
> >>
> >> -test_dhcpv6 2 $src_mac $src_lla 01 $offer_ip 1
> >> +test_dhcpv6 2 $src_mac $src_lla 1 $offer_ip "bootfile_name_alt" ""
> >>   # NXT_RESUMEs should be 5.
> >>   OVS_WAIT_UNTIL([test 5 = `cat ofctl_monitor*.log | grep -c
> NXT_RESUME`])
> >>
> >> @@ -7381,7 +7380,7 @@ AT_CHECK([cat 2.packets | cut -c 1-120,125- ],
> [0],
> >> [expout])
> >>   reset_pcap_file hv1-vif2 hv1/vif2
> >>   rm 2.packets 2.expected
> >>
> >> -test_dhcpv6 2 $src_mac $src_lla 01 $offer_ip 2
> >> +test_dhcpv6 2 $src_mac $src_lla 1 $offer_ip "bootfile_name" ""
> >>   # NXT_RESUMEs should be 6.
> >>   OVS_WAIT_UNTIL([test 6 = `cat ofctl_monitor*.log | grep -c
> NXT_RESUME`])
> >>
> >> @@ -7389,6 +7388,25 @@ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in"
> >> hv1/vif2-tx.pcap | trim_zeros > 2.pa
> >>   cat 2.expected | cut -c 1-120,125- > expout
> >>   AT_CHECK([cat 2.packets | cut -c 1-120,125- ], [0], [expout])
> >>
> >> +ovn-nbctl --all destroy dhcp-option
> >> +d1="$(ovn-nbctl create DHCP_Options cidr="ae70\:\:/64")"
> >> +ovn-nbctl dhcp-options-set-options $d1 \
> >> +    server_id=00:00:00:10:00:01 \
> >> +    fqdn=\"ovn.org\"
> >> +ovn-nbctl --wait=hv lsp-set-dhcpv6-options ls1-lp2 ${d1}
> >> +
> >> +reset_pcap_file hv1-vif2 hv1/vif2
> >> +rm 2.packets 2.expected
> >> +
> >> +test_dhcpv6 2 $src_mac $src_lla 1 $offer_ip "" "ovn.org"
> >> +# NXT_RESUMEs should be 7.
> >> +OVS_WAIT_UNTIL([test 7 = `cat ofctl_monitor*.log | grep -c
> NXT_RESUME`])
> >> +
> >> +$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap |
> >> trim_zeros > 2.packets
> >> +cat 2.expected | cut -c 1-120,125- > expout
> >> +cat 2.packets
> >> +AT_CHECK([cat 2.packets | cut -c 1-120,125- ], [0], [expout])
> >> +
> >>   OVN_CLEANUP([hv1])
> >>
> >>   AT_CLEANUP
> >> diff --git a/tests/test-ovn.c b/tests/test-ovn.c
> >> index ce9213c1d..6c7754eac 100644
> >> --- a/tests/test-ovn.c
> >> +++ b/tests/test-ovn.c
> >> @@ -207,6 +207,7 @@ create_gen_opts(struct hmap *dhcp_opts, struct hmap
> >> *dhcpv6_opts,
> >>       dhcp_opt_add(dhcpv6_opts, "domain_search",  24, "str");
> >>       dhcp_opt_add(dhcpv6_opts, "bootfile_name", 59, "str");
> >>       dhcp_opt_add(dhcpv6_opts, "bootfile_name_alt", 254, "str");
> >> +    dhcp_opt_add(dhcpv6_opts, "fqdn", 39, "domain");
> >>
> >>       /* IPv6 ND RA options. */
> >>       hmap_init(nd_ra_opts);
> >> --
> >> 2.40.1
> >>
> >>
> >
>
>
Thanks,
Ales
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index 1532f635a..b4dd0da05 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,7 @@ 
 Post v23.06.0
 -------------
+  - Add DHCPv6 "fqdn" (39) option, that works similarly to
+    DHCPv4 "hostname" (12) option.
 
 OVN v23.06.0 - xx xxx xxxx
 --------------------------
diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index c396ad4c2..51dbadc99 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -2288,7 +2288,7 @@  exit:
 static bool
 compose_out_dhcpv6_opts(struct ofpbuf *userdata,
                         struct ofpbuf *out_dhcpv6_opts,
-                        ovs_be32 iaid, bool ipxe_req)
+                        ovs_be32 iaid, bool ipxe_req, uint8_t fqdn_flags)
 {
     while (userdata->size) {
         struct dhcpv6_opt_header *userdata_opt = ofpbuf_try_pull(
@@ -2412,6 +2412,24 @@  compose_out_dhcpv6_opts(struct ofpbuf *userdata,
             break;
         }
 
+        case DHCPV6_OPT_FQDN_CODE: {
+            if (fqdn_flags != DHCPV6_FQDN_FLAGS_UNDEFINED) {
+                struct dhcpv6_opt_header *header =
+                        ofpbuf_put_zeros(out_dhcpv6_opts, sizeof *header);
+                header->code = htons(DHCPV6_OPT_FQDN_CODE);
+                header->len = htons(size + 1);
+                uint8_t *flags = ofpbuf_put_zeros(out_dhcpv6_opts, 1);
+                /* If client requested N or S inform him that it
+                 * was overwritten by the server. */
+                if (fqdn_flags & DHCPV6_FQDN_FLAGS_N ||
+                    fqdn_flags & DHCPV6_FQDN_FLAGS_S) {
+                    *flags |= DHCPV6_FQDN_FLAGS_O;
+                }
+                ofpbuf_put(out_dhcpv6_opts, userdata_opt_data, size);
+            }
+            break;
+        }
+
         default:
             return false;
         }
@@ -2500,6 +2518,7 @@  pinctrl_handle_put_dhcpv6_opts(
     size_t l4_len = dp_packet_l4_size(pkt_in);
     uint8_t *end = (uint8_t *)in_udp + MIN(udp_len, l4_len);
     bool ipxe_req = false;
+    uint8_t fqdn_flags = DHCPV6_FQDN_FLAGS_UNDEFINED;
     while (in_dhcpv6_data < end) {
         struct dhcpv6_opt_header const *in_opt =
              (struct dhcpv6_opt_header *)in_dhcpv6_data;
@@ -2524,6 +2543,10 @@  pinctrl_handle_put_dhcpv6_opts(
             break;
         }
 
+        case DHCPV6_OPT_FQDN_CODE:
+            fqdn_flags = *(in_dhcpv6_data + sizeof *in_opt);
+            break;
+
         default:
             break;
         }
@@ -2547,7 +2570,7 @@  pinctrl_handle_put_dhcpv6_opts(
         OFPBUF_STUB_INITIALIZER(out_ofpacts_dhcpv6_opts_stub);
 
     if (!compose_out_dhcpv6_opts(userdata, &out_dhcpv6_opts,
-                                 iaid, ipxe_req)) {
+                                 iaid, ipxe_req, fqdn_flags)) {
         VLOG_WARN_RL(&rl, "Invalid userdata");
         goto exit;
     }
diff --git a/lib/actions.c b/lib/actions.c
index ec27223f9..0ec69bec9 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -2470,7 +2470,8 @@  parse_gen_opt(struct action_context *ctx, struct ovnact_gen_option *o,
     }
 
     if (!strcmp(o->option->type, "str") ||
-        !strcmp(o->option->type, "domains")) {
+        !strcmp(o->option->type, "domains") ||
+        !strcmp(o->option->type, "domain")) {
         if (o->value.type != EXPR_C_STRING) {
             lexer_error(ctx->lexer, "%s option %s requires string value.",
                         opts_type, o->option->name);
@@ -2903,6 +2904,33 @@  encode_put_dhcpv6_option(const struct ovnact_gen_option *o,
         size = strlen(c->string);
         opt->len = htons(size);
         ofpbuf_put(ofpacts, c->string, size);
+    } else if (!strcmp(o->option->type, "domain")) {
+        /* The DNS format is 2 bytes longer than the "domain".
+         * It replaces every '.' with len of the next name. */
+        size_t domain_len = strlen(c->string);
+        size = domain_len + 2;
+        char *encoded = xzalloc(size);
+
+        int8_t label_len = 0;
+        for (size_t i = 0; i < domain_len; i++) {
+            if (c->string[i] == '.') {
+                encoded[i - label_len] = label_len;
+                label_len = 0;
+            } else {
+                encoded[i + 1] = c->string[i];
+                label_len++;
+            }
+        }
+
+        /* This is required for the last label if it doesn't end with '.' */
+        if (label_len) {
+            encoded[domain_len - label_len] = label_len;
+        }
+
+        opt->len = htons(size);
+        ofpbuf_put(ofpacts, encoded, size);
+
+        free(encoded);
     }
 }
 
diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
index d9b103119..9dc331421 100644
--- a/lib/ovn-l7.h
+++ b/lib/ovn-l7.h
@@ -270,6 +270,7 @@  BUILD_ASSERT_DECL(DHCP_OPT_HEADER_LEN == sizeof(struct dhcp_opt_header));
 #define DHCPV6_OPT_DOMAIN_SEARCH_CODE    24
 #define DHCPV6_OPT_IA_PD                 25
 #define DHCPV6_OPT_IA_PREFIX             26
+#define DHCPV6_OPT_FQDN_CODE             39
 #define DHCPV6_OPT_BOOT_FILE_URL         59
 #define DHCPV6_OPT_BOOT_FILE_URL_ALT    254
 
@@ -291,6 +292,15 @@  BUILD_ASSERT_DECL(DHCP_OPT_HEADER_LEN == sizeof(struct dhcp_opt_header));
 #define DHCPV6_OPT_BOOTFILE_NAME_ALT \
     DHCP_OPTION("bootfile_name_alt", DHCPV6_OPT_BOOT_FILE_URL_ALT, "str")
 
+#define DHCPV6_OPT_FQDN \
+    DHCP_OPTION("fqdn", DHCPV6_OPT_FQDN_CODE, "domain")
+
+/* DHCPv6 FQDN flags. RFC 4704 */
+#define DHCPV6_FQDN_FLAGS_UNDEFINED 0xff
+#define DHCPV6_FQDN_FLAGS_S 1 << 0
+#define DHCPV6_FQDN_FLAGS_O 1 << 1
+#define DHCPV6_FQDN_FLAGS_N 1 << 2
+
 OVS_PACKED(
 struct dhcpv6_opt_header {
     ovs_be16 code;
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index 3515b68a2..2bafbd4af 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -273,7 +273,8 @@  static struct gen_opts_map supported_dhcpv6_opts[] = {
     DHCPV6_OPT_DOMAIN_SEARCH,
     DHCPV6_OPT_DNS_SERVER,
     DHCPV6_OPT_BOOTFILE_NAME,
-    DHCPV6_OPT_BOOTFILE_NAME_ALT
+    DHCPV6_OPT_BOOTFILE_NAME_ALT,
+    DHCPV6_OPT_FQDN,
 };
 
 static bool
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 9afe3b584..05e155b89 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -4191,6 +4191,13 @@  or
             way. Default value for this option is false.
           </p>
         </column>
+
+        <column name="options" key="fqdn">
+          <p>
+            The DHCPv6 option code for this option is 39.
+            If set, indicates the DHCPv6 option "FQDN".
+          </p>
+        </column>
       </group>
     </group>
 
diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
index f59af8cc5..06e16b403 100644
--- a/ovn-sb.ovsschema
+++ b/ovn-sb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Southbound",
-    "version": "20.27.2",
-    "cksum": "1291808617 30462",
+    "version": "20.27.3",
+    "cksum": "3876528905 30472",
     "tables": {
         "SB_Global": {
             "columns": {
@@ -311,7 +311,7 @@ 
                 "type": {
                     "type": {"key": {
                         "type": "string",
-                        "enum": ["set", ["ipv6", "str", "mac"]]}}}},
+                        "enum": ["set", ["ipv6", "str", "mac", "domain"]]}}}},
             "isRoot": true},
         "Connection": {
             "columns": {
diff --git a/tests/ovn.at b/tests/ovn.at
index d8c694002..ab6052299 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -1697,6 +1697,9 @@  reg1[0] = put_dhcpv6_opts(bootfile_name="https://127.0.0.1/boot.ipxe");
 reg1[0] = put_dhcpv6_opts(bootfile_name_alt="https://127.0.0.1/boot.ipxe");
     formats as reg1[0] = put_dhcpv6_opts(bootfile_name_alt = "https://127.0.0.1/boot.ipxe");
     encodes as controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40.00.fe.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,pause)
+reg1[0] = put_dhcpv6_opts(fqdn="ovn.org");
+    formats as reg1[0] = put_dhcpv6_opts(fqdn = "ovn.org");
+    encodes as controller(userdata=00.00.00.05.00.00.00.00.00.01.de.10.00.00.00.40.00.27.00.09.03.6f.76.6e.03.6f.72.67.00,pause)
 
 # lookup_nd
 reg2[0] = lookup_nd(inport, ip6.dst, eth.src);
@@ -7083,6 +7086,7 @@  AT_CLEANUP
 
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([dhcpv6 : 1 HV, 2 LS, 5 LSPs])
+AT_SKIP_IF([test $HAVE_SCAPY = no])
 ovn_start
 
 ovn-nbctl ls-add ls1
@@ -7171,61 +7175,56 @@  trim_zeros() {
 # packet should be received twice (one from ovn-controller and the other
 # from the "ovs-ofctl monitor br-int resume"
 test_dhcpv6() {
-    local inport=$1 src_mac=$2 src_lla=$3 msg_code=$4 offer_ip=$5 ipxe=$6
-    if test $ipxe -eq 2; then
-        req_len=34
-    elif test $msg_code != 0b; then
-        req_len=2a
-    else
-        req_len=1a
-    fi
-    local request=ffffffffffff${src_mac}86dd6000000000${req_len}1101${src_lla}
-    # dst ip ff02::1:2
-    request=${request}ff020000000000000000000000010002
-    # udp header and dhcpv6 header
-    request=${request}0222022300${req_len}ffff${msg_code}010203
-    # Client identifier
-    request=${request}0001000a00030001${src_mac}
+    local inport=$1 src_mac=$2 src_lla=$3 msg_code=$4 offer_ip=$5 boot_file=$6
+    local fqdn=$7
+
+    local req_scapy="Ether(dst='ff:ff:ff:ff:ff:ff', src='${src_mac}')/ \
+                     IPv6(dst='ff02::1:2', src='${src_lla}')/ \
+                     UDP(sport=546, dport=547)/ \
+                     DHCP6(msgtype=${msg_code}, trid=0x010203)/ \
+                     DHCP6OptClientId(duid=DUID_LL(lladdr='${src_mac}'))"
+
     # Add IA-NA (Identity Association for Non Temporary Address) if msg_code
     # is not 11 (information request packet)
-    if test $msg_code != 0b; then
-        request=${request}0003000c0102030400000e1000001518
+    if test $msg_code != 11; then
+        req_scapy="${req_scapy}/DHCP6OptIA_NA(iaid=0x01020304, T1=3600, T2=5400)"
     fi
-    if test $ipxe -eq 2; then
-        request=${request}000f0006000669505845
+    if test "$boot_file" = "bootfile_name"; then
+        local uc_data="USER_CLASS_DATA(data=b'iPXE')"
+        req_scapy="${req_scapy}/DHCP6OptUserClass(userclassdata=[[${uc_data}]])"
     fi
-    shift; shift; shift; shift; shift; shift;
+    if test -n "$fqdn"; then
+        req_scapy="${req_scapy}/DHCP6OptClientFQDN(flags=0x01, fqdn=b'test')"
+    fi
+    request=$(fmt_pkt "${req_scapy}")
+    shift; shift; shift; shift; shift; shift; shift;
     if test $offer_ip != 0; then
-        local server_mac=000000100001
-        local server_lla=fe80000000000000020000fffe100001
-        local reply_code=07
-        if test $msg_code = 01; then
-            reply_code=02
-        fi
-        local msg_len=54
-        if test $ipxe -eq 1; then
-            msg_len=69
-        elif test $ipxe -eq 2; then
-            msg_len=65
-        elif test $offer_ip = 1; then
-            msg_len=28
+        local reply_code=7
+        if test $msg_code = 1; then
+            reply_code=2
         fi
-        local reply=${src_mac}${server_mac}86dd6000000000${msg_len}1101${server_lla}${src_lla}
-        # udp header and dhcpv6 header
-        reply=${reply}0223022200${msg_len}ffff${reply_code}010203
-        # Client identifier
-        reply=${reply}0001000a00030001${src_mac}
-        # IA-NA
+
+        local rep_scapy="Ether(dst='${src_mac}', src='00:00:00:10:00:01')/ \
+                         IPv6(dst='${src_lla}', src='fe80::0200:00ff:fe10:0001')/ \
+                         UDP(sport=547, dport=546)/ \
+                         DHCP6(msgtype=${reply_code}, trid=0x010203)/ \
+                         DHCP6OptClientId(duid=DUID_LL(lladdr='${src_mac}'))"
+
         if test $offer_ip != 1; then
-            reply=${reply}0003002801020304ffffffffffffffff00050018${offer_ip}ffffffffffffffff
+            local ip_scapy="DHCP6OptIAAddress(addr='${offer_ip}', \
+                            preflft=0xffffffff, validlft=0xffffffff)"
+            rep_scapy="${rep_scapy}/ \
+                       DHCP6OptIA_NA(iaid=0x01020304, T1=0xffffffff, \
+                       T2=0xffffffff, ianaopts=[[${ip_scapy}]])"
         fi
-        if test $ipxe -eq 1; then
-            reply=${reply}003b0011626f6f7466696c655f6e616d655f616c74
-        elif test $ipxe -eq 2; then
-            reply=${reply}003b000d626f6f7466696c655f6e616d65
+        if test -n "$boot_file"; then
+            rep_scapy="${rep_scapy}/DHCP6OptBootFileUrl(optdata=b'${boot_file}')"
         fi
-        # Server identifier
-        reply=${reply}0002000a00030001${server_mac}
+        if test -n "$fqdn"; then
+            rep_scapy="${rep_scapy}/DHCP6OptClientFQDN(flags=0x02, fqdn=b'${fqdn}')"
+        fi
+        rep_scapy="${rep_scapy}/DHCP6OptServerId(duid=DUID_LL(lladdr='00:00:00:10:00:01'))"
+        reply=$(fmt_pkt "${rep_scapy}")
         echo $reply | trim_zeros >> $inport.expected
     else
         for outport; do
@@ -7249,16 +7248,16 @@  echo "---------------------"
 ovn-sbctl list logical_flow
 echo "---------------------"
 
-ovn-sbctl dump-flows > sbflows
+#ovn-sbctl dump-flows > sbflows
 AT_CAPTURE_FILE([sbflows])
 
 echo "------ hv1 dump ----------"
 as hv1 ovs-ofctl dump-flows br-int
 
-src_mac=f00000000001
-src_lla=fe80000000000000f20000fffe000001
-offer_ip=ae700000000000000000000000000004
-test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip 0
+src_mac="f0:00:00:00:00:01"
+src_lla="fe80::f200:00ff:fe00:0001"
+offer_ip="ae70::4"
+test_dhcpv6 1 $src_mac $src_lla 1 $offer_ip "" ""
 
 # NXT_RESUMEs should be 1.
 OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
@@ -7281,12 +7280,12 @@  rm  1.expected
 reset_pcap_file hv1-vif1 hv1/vif1
 reset_pcap_file hv1-vif2 hv1/vif2
 
-src_mac=f00000000002
-src_lla=fe80000000000000f20000fffe000002
-offer_ip=ae700000000000000000000000000005
+src_mac="f0:00:00:00:00:02"
+src_lla="fe80::f200:00ff:fe00:0002"
+offer_ip="ae70::5"
 # Set invalid msg_type
 
-test_dhcpv6 2 $src_mac $src_lla 10 0 0 1 1
+test_dhcpv6 2 $src_mac $src_lla 16 0 "" "" 1 1
 
 # NXT_RESUMEs should be 2.
 OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
@@ -7305,9 +7304,9 @@  AT_CHECK([cat 1.packets], [0], [expout])
 # There should be no DHCPv6 reply from ovn-controller and the request packet
 # should be received by ls2-lp2.
 
-src_mac=f00000000003
-src_lla=fe80000000000000f20000fffe000003
-test_dhcpv6 3 $src_mac $src_lla 01 0 0 4
+src_mac="f0:00:00:00:00:03"
+src_lla="fe80::f200:00ff:fe00:0003"
+test_dhcpv6 3 $src_mac $src_lla 1 0 "" "" 4
 
 # NXT_RESUMEs should be 2 only.
 OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
@@ -7323,10 +7322,10 @@  AT_CHECK([cat 4.packets], [0], [expout])
 
 # Send DHCPv6 packet on ls1-lp3. native DHCPv6 works as stateless mode for this port.
 # The DHCPv6 reply shouldn't contain offer_ip.
-src_mac=f00000000022
-src_lla=fe80000000000000f20000fffe000022
+src_mac="f0:00:00:00:00:22"
+src_lla="fe80::f200:00ff:fe00:0022"
 reset_pcap_file hv1-vif5 hv1/vif5
-test_dhcpv6 5 $src_mac $src_lla 01 1 0 5
+test_dhcpv6 5 $src_mac $src_lla 1 1 "" "" 5
 
 # NXT_RESUMEs should be 3.
 OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
@@ -7338,11 +7337,11 @@  AT_CHECK([cat 5.packets | cut -c 1-120,125- ], [0], [expout])
 
 # Send DHCPv6 information request (code 11) on ls1-lp3. The DHCPv6 reply
 # shouldn't contain offer_ip
-src_mac=f00000000022
-src_lla=fe80000000000000f20000fffe000022
+src_mac="f0:00:00:00:00:22"
+src_lla="fe80::f200:00ff:fe00:0022"
 reset_pcap_file hv1-vif5 hv1/vif5
 rm -f 5.expected
-test_dhcpv6 5 $src_mac $src_lla 0b 1 0 5
+test_dhcpv6 5 $src_mac $src_lla 11 1 "" "" 5
 
 # NXT_RESUMEs should be 4.
 OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
@@ -7363,11 +7362,11 @@  ovn-nbctl lsp-set-dhcpv6-options ls1-lp2 ${d1}
 
 reset_pcap_file hv1-vif2 hv1/vif2
 
-src_mac=f00000000002
-src_lla=fe80000000000000f20000fffe000002
-offer_ip=ae700000000000000000000000000005
+src_mac="f0:00:00:00:00:02"
+src_lla="fe80::f200:00ff:fe00:0002"
+offer_ip="ae70::5"
 
-test_dhcpv6 2 $src_mac $src_lla 01 $offer_ip 1
+test_dhcpv6 2 $src_mac $src_lla 1 $offer_ip "bootfile_name_alt" ""
 # NXT_RESUMEs should be 5.
 OVS_WAIT_UNTIL([test 5 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
 
@@ -7381,7 +7380,7 @@  AT_CHECK([cat 2.packets | cut -c 1-120,125- ], [0], [expout])
 reset_pcap_file hv1-vif2 hv1/vif2
 rm 2.packets 2.expected
 
-test_dhcpv6 2 $src_mac $src_lla 01 $offer_ip 2
+test_dhcpv6 2 $src_mac $src_lla 1 $offer_ip "bootfile_name" ""
 # NXT_RESUMEs should be 6.
 OVS_WAIT_UNTIL([test 6 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
 
@@ -7389,6 +7388,25 @@  $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap | trim_zeros > 2.pa
 cat 2.expected | cut -c 1-120,125- > expout
 AT_CHECK([cat 2.packets | cut -c 1-120,125- ], [0], [expout])
 
+ovn-nbctl --all destroy dhcp-option
+d1="$(ovn-nbctl create DHCP_Options cidr="ae70\:\:/64")"
+ovn-nbctl dhcp-options-set-options $d1 \
+    server_id=00:00:00:10:00:01 \
+    fqdn=\"ovn.org\"
+ovn-nbctl --wait=hv lsp-set-dhcpv6-options ls1-lp2 ${d1}
+
+reset_pcap_file hv1-vif2 hv1/vif2
+rm 2.packets 2.expected
+
+test_dhcpv6 2 $src_mac $src_lla 1 $offer_ip "" "ovn.org"
+# NXT_RESUMEs should be 7.
+OVS_WAIT_UNTIL([test 7 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap | trim_zeros > 2.packets
+cat 2.expected | cut -c 1-120,125- > expout
+cat 2.packets
+AT_CHECK([cat 2.packets | cut -c 1-120,125- ], [0], [expout])
+
 OVN_CLEANUP([hv1])
 
 AT_CLEANUP
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
index ce9213c1d..6c7754eac 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -207,6 +207,7 @@  create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts,
     dhcp_opt_add(dhcpv6_opts, "domain_search",  24, "str");
     dhcp_opt_add(dhcpv6_opts, "bootfile_name", 59, "str");
     dhcp_opt_add(dhcpv6_opts, "bootfile_name_alt", 254, "str");
+    dhcp_opt_add(dhcpv6_opts, "fqdn", 39, "domain");
 
     /* IPv6 ND RA options. */
     hmap_init(nd_ra_opts);