[ovs-dev,v6,ovn,1/2] controller: add ipv6 prefix delegation state machine
diff mbox series

Message ID 35a35326c24b3c4a68938c50998d9be29e427bea.1578999694.git.lorenzo.bianconi@redhat.com
State New
Headers show
Series
  • Add IPv6 Prefix delegation (RFC3633)
Related show

Commit Message

Lorenzo Bianconi Jan. 14, 2020, 11:07 a.m. UTC
Introduce IPv6 Prefix delegation state machine according to RFC 3633
https://tools.ietf.org/html/rfc3633.
Add handle_dhcpv6_reply controller action to parse advertise/reply from
IPv6 delegation server. Advertise/reply are parsed running respectively:
- pinctrl_parse_dhcv6_advt
- pinctrl_parse_dhcv6_reply
The IPv6 requesting router starts sending dhcpv6 solicit through the logical
router port marked with ipv6_prefix_delegation set to true.
An IPv6 prefix will be requested for each logical router port marked
with "prefix" set to true in option column of logical router port table.
Save IPv6 prefix received by IPv6 delegation router in the options columns of
SB port binding table in order to be reused by Router Advertisement framework
run by ovn logical router pipeline.
IPv6 Prefix delegation state machine is enabled on Gateway Router or on
a Gateway Router Port

Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
---
 controller/ovn-controller.c |   1 +
 controller/pinctrl.c        | 612 +++++++++++++++++++++++++++++++++++-
 controller/pinctrl.h        |   2 +
 include/ovn/actions.h       |   8 +-
 lib/actions.c               |  22 ++
 lib/ovn-l7.h                |  19 ++
 ovn-sb.xml                  |   8 +
 tests/ovn.at                |   6 +
 utilities/ovn-trace.c       |   3 +
 9 files changed, 673 insertions(+), 8 deletions(-)

Comments

Numan Siddique Jan. 16, 2020, 1:36 p.m. UTC | #1
"

On Tue, Jan 14, 2020 at 4:37 PM Lorenzo Bianconi
<lorenzo.bianconi@redhat.com> wrote:
>
> Introduce IPv6 Prefix delegation state machine according to RFC 3633
> https://tools.ietf.org/html/rfc3633.
> Add handle_dhcpv6_reply controller action to parse advertise/reply from
> IPv6 delegation server. Advertise/reply are parsed running respectively:
> - pinctrl_parse_dhcv6_advt
> - pinctrl_parse_dhcv6_reply
> The IPv6 requesting router starts sending dhcpv6 solicit through the logical
> router port marked with ipv6_prefix_delegation set to true.
> An IPv6 prefix will be requested for each logical router port marked
> with "prefix" set to true in option column of logical router port table.
> Save IPv6 prefix received by IPv6 delegation router in the options columns of
> SB port binding table in order to be reused by Router Advertisement framework
> run by ovn logical router pipeline.
> IPv6 Prefix delegation state machine is enabled on Gateway Router or on
> a Gateway Router Port
>
> Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>


Hi Lorenzo,
Thanks for the v6.

I tested this series.  Below are some comments

 - When I configured the prefix options on the router ports,
ovn-controller hosting the gateway
   router port sends the PD solicit message and gets the prefix, but
it never sends the renew/Solicit
   requests periodically. In the code I see that the next announce
time is chosen. But looks like that is not
  working. I think it's better to use the "prefix lifetime option"
value sent by the delegating server rather than
  current_time + random(3000) which this patch does.

- Suppose if a router port had received a prefix 'P1' and if I restart
ovn-controller, ovn-controller
  sends the PD messages and gets another prefix 'P2'. Ideally
ovn-controller should try to renew
  the existing prefix. Can you please check if it is possible to
include the prefix option in the Request message ?
  Probably ovn-controller can send a Renew request if a router port
has already a prefix ? If the delegating server,
  can't allocate the same prefix, ovn-controller can start the fresh
process of sending Solicit message.

- In the above case, I notice that when delegating server sends 'P2',
ovn-controller doesn't update this new prefix
  in the port_binding.options:ipv6_ra_pd_list. And the patch 2 of this
series, doesn't update the "ipv6_prefix column
  of logical router port.  I think it's better for ovn-northd to just
update/reset the "ipv6_prefix" column from the
  port_binding.options:ipv6_ra_pd_list, rather than appending the value.

 - The action handle_dhcp6_reply takes inner actions. But this is not
documented.
   I think it's better not to include the inner actions. When the
delegating server sends the "reply" message, ovn-controller
   doesn't need to send any reply. But since this action is used for
handling the "Advertise" and "Reply" messages from
   the delegating router, it doesn't seem appropriate to include the
inner actions.

Please see some more comments below


> ---
>  controller/ovn-controller.c |   1 +
>  controller/pinctrl.c        | 612 +++++++++++++++++++++++++++++++++++-
>  controller/pinctrl.h        |   2 +
>  include/ovn/actions.h       |   8 +-
>  lib/actions.c               |  22 ++
>  lib/ovn-l7.h                |  19 ++
>  ovn-sb.xml                  |   8 +
>  tests/ovn.at                |   6 +
>  utilities/ovn-trace.c       |   3 +
>  9 files changed, 673 insertions(+), 8 deletions(-)
>
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index 17744d416..d559e845e 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -2145,6 +2145,7 @@ main(int argc, char *argv[])
>                      runtime_data = engine_get_data(&en_runtime_data);
>                      if (runtime_data) {
>                          pinctrl_run(ovnsb_idl_txn,
> +                                    ovnsb_idl_loop.idl,
>                                      sbrec_datapath_binding_by_key,
>                                      sbrec_port_binding_by_datapath,
>                                      sbrec_port_binding_by_key,
> diff --git a/controller/pinctrl.c b/controller/pinctrl.c
> index 452ca8a1c..2a37fc525 100644
> --- a/controller/pinctrl.c
> +++ b/controller/pinctrl.c
> @@ -270,6 +270,20 @@ static void pinctrl_ip_mcast_handle_igmp(
>      const struct match *md,
>      struct ofpbuf *userdata);
>
> +static void init_ipv6_prefixd(void);
> +static void destroy_ipv6_prefixd(void);
> +static void ipv6_prefixd_wait(long long int timeout);
> +static void
> +prepare_ipv6_prefixd(struct ovsdb_idl_txn *ovnsb_idl_txn,
> +                     struct ovsdb_idl *ovnsb_idl,
> +                     struct ovsdb_idl_index *sbrec_port_binding_by_name,
> +                     const struct hmap *local_datapaths,
> +                     const struct sbrec_chassis *chassis,
> +                     const struct sset *active_tunnels)
> +    OVS_REQUIRES(pinctrl_mutex);
> +static void
> +send_ipv6_prefixd(struct rconn *swconn, long long int *send_prefixd_time)
> +    OVS_REQUIRES(pinctrl_mutex);
>  static bool may_inject_pkts(void);
>
>  static void init_put_vport_bindings(void);
> @@ -457,6 +471,7 @@ pinctrl_init(void)
>      init_put_mac_bindings();
>      init_send_garps_rarps();
>      init_ipv6_ras();
> +    init_ipv6_prefixd();
>      init_buffered_packets_map();
>      init_event_table();
>      ip_mcast_snoop_init();
> @@ -544,6 +559,49 @@ set_actions_and_enqueue_msg(struct rconn *swconn,
>      ofpbuf_uninit(&ofpacts);
>  }
>
> +static struct shash ipv6_prefixd;
> +
> +enum {
> +    PREFIX_SOLICIT,
> +    PREFIX_PENDING,
> +    PREFIX_DONE,
> +};
> +
> +struct ipv6_prefixd_state {
> +    long long int next_announce;
> +    struct in6_addr ipv6_addr;
> +    struct eth_addr ea;
> +    struct eth_addr cmac;
> +    int64_t port_key;
> +    int64_t metadata;
> +    struct in6_addr prefix;
> +    unsigned plife_time;
> +    unsigned vlife_time;
> +    unsigned aid;
> +    unsigned t1;
> +    unsigned t2;
> +    int8_t plen;
> +    int state;
> +};
> +
> +static void
> +init_ipv6_prefixd(void)
> +{
> +    shash_init(&ipv6_prefixd);
> +}
> +
> +static void
> +destroy_ipv6_prefixd(void)
> +{
> +    struct shash_node *iter, *next;
> +    SHASH_FOR_EACH_SAFE (iter, next, &ipv6_prefixd) {
> +        struct ipv6_prefixd_state *pfd = iter->data;
> +        free(pfd);
> +        shash_delete(&ipv6_prefixd, iter);
> +    }
> +    shash_destroy(&ipv6_prefixd);
> +}
> +
>  struct buffer_info {
>      struct ofpbuf ofpacts;
>      struct dp_packet *p;
> @@ -967,6 +1025,259 @@ pinctrl_handle_tcp_reset(struct rconn *swconn, const struct flow *ip_flow,
>      dp_packet_uninit(&packet);
>  }
>
> +static void
> +pinctrl_parse_dhcpv6_advt(struct rconn *swconn, const struct flow *ip_flow,
> +                          struct dp_packet *pkt_in, const struct match *md,
> +                          struct ofpbuf *userdata)
> +{
> +    struct udp_header *udp_in = dp_packet_l4(pkt_in);
> +    size_t dlen = MIN(ntohs(udp_in->udp_len), dp_packet_l4_size(pkt_in));
> +    unsigned char *in_dhcpv6_data = (unsigned char *)(udp_in + 1);
> +    uint8_t *data, *end = (uint8_t *)udp_in + dlen;
> +    int len = 0;
> +
> +    data = xmalloc(dlen);
> +    /* skip DHCPv6 common header */
> +    in_dhcpv6_data += 4;
> +    while (in_dhcpv6_data < end) {
> +        struct dhcpv6_opt_header *in_opt =
> +             (struct dhcpv6_opt_header *)in_dhcpv6_data;
> +        int opt_len = sizeof *in_opt + ntohs(in_opt->len);
> +
> +        if (dlen < opt_len + len) {
> +            goto out;
> +        }
> +
> +        switch (ntohs(in_opt->code)) {
> +        case DHCPV6_OPT_IA_PD: {
> +            int orig_len = len, hdr_len = 0, size = sizeof *in_opt + 12;
> +
> +            memcpy(&data[len], in_opt, size);
> +            in_opt = (struct dhcpv6_opt_header *)(in_dhcpv6_data + size);
> +            len += size;
> +
> +            while (size < opt_len) {
> +                int flen = sizeof *in_opt + ntohs(in_opt->len);
> +
> +                if (dlen < flen + len) {
> +                    goto out;
> +                }
> +
> +                if (ntohs(in_opt->code) == DHCPV6_OPT_IA_PREFIX) {
> +                    memcpy(&data[len], in_opt, flen);
> +                    hdr_len += flen;
> +                    len += flen;
> +                }
> +                if (ntohs(in_opt->code) == DHCPV6_OPT_STATUS_CODE) {
> +                   struct dhcpv6_opt_status *status;
> +
> +                   status = (struct dhcpv6_opt_status *)in_opt;
> +                   if (ntohs(status->status_code)) {
> +                       goto out;
> +                   }
> +                }
> +                size += flen;
> +                in_opt = (struct dhcpv6_opt_header *)(in_dhcpv6_data + size);
> +            }
> +            in_opt = (struct dhcpv6_opt_header *)&data[orig_len];
> +            in_opt->len = htons(hdr_len + 12);
> +            break;
> +        }
> +        case DHCPV6_OPT_SERVER_ID_CODE:
> +        case DHCPV6_OPT_CLIENT_ID_CODE:
> +            memcpy(&data[len], in_opt, opt_len);
> +            len += opt_len;
> +            break;
> +        default:
> +            break;
> +        }
> +        in_dhcpv6_data += opt_len;
> +    }
> +
> +    uint64_t packet_stub[256 / 8];
> +    struct dp_packet packet;
> +
> +    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
> +    eth_compose(&packet, ip_flow->dl_dst, ip_flow->dl_src, ETH_TYPE_IPV6,
> +                IPV6_HEADER_LEN);
> +
> +    struct udp_header *udp_h = compose_ipv6(&packet, IPPROTO_UDP,
> +                                            &ip_flow->ipv6_src,
> +                                            &ip_flow->ipv6_dst, 0, 0, 255,
> +                                            len + UDP_HEADER_LEN + 4);
> +    udp_h->udp_len = htons(len + UDP_HEADER_LEN + 4);
> +    udp_h->udp_csum = 0;
> +    packet_set_udp_port(&packet, htons(546), htons(547));
> +
> +    unsigned char *dhcp_hdr = (unsigned char *)(udp_h + 1);
> +    *dhcp_hdr = DHCPV6_MSG_TYPE_REQUEST;
> +    memcpy(dhcp_hdr + 4, data, len);
> +
> +    uint32_t csum = packet_csum_pseudoheader6(dp_packet_l3(&packet));
> +    csum = csum_continue(csum, udp_h, dp_packet_size(&packet) -
> +                         ((const unsigned char *)udp_h -
> +                          (const unsigned char *)dp_packet_eth(&packet)));
> +    udp_h->udp_csum = csum_finish(csum);
> +    if (!udp_h->udp_csum) {
> +        udp_h->udp_csum = htons(0xffff);
> +    }
> +
> +    if (ip_flow->vlans[0].tci & htons(VLAN_CFI)) {
> +        eth_push_vlan(&packet, htons(ETH_TYPE_VLAN_8021Q),
> +                      ip_flow->vlans[0].tci);
> +    }
> +
> +    set_actions_and_enqueue_msg(swconn, &packet, md, userdata);
> +    dp_packet_uninit(&packet);
> +
> +out:
> +    free(data);
> +}
> +
> +static struct ipv6_prefixd_state *
> +pinctrl_find_prefixd_state(const struct flow *ip_flow, unsigned aid)
> +{
> +    struct shash_node *iter;
> +
> +    SHASH_FOR_EACH (iter, &ipv6_prefixd) {
> +        struct ipv6_prefixd_state *pfd = iter->data;
> +        if (IN6_ARE_ADDR_EQUAL(&pfd->ipv6_addr, &ip_flow->ipv6_dst) &&
> +            eth_addr_equals(pfd->ea, ip_flow->dl_dst) &&
> +            pfd->aid == aid) {
> +            return pfd;
> +        }
> +    }
> +    return NULL;
> +}
> +
> +/* Called with in the pinctrl_handler thread context. */
> +static void
> +notify_pinctrl_main(void)
> +{
> +    seq_change(pinctrl_main_seq);
> +}
> +
> +static void
> +pinctrl_prefixd_state_handler(const struct flow *ip_flow,
> +                              struct in6_addr addr, unsigned aid,
> +                              char prefix_len, unsigned t1, unsigned t2,
> +                              unsigned plife_time, unsigned vlife_time)
> +{
> +    struct ipv6_prefixd_state *pfd;
> +
> +    pfd = pinctrl_find_prefixd_state(ip_flow, aid);
> +    if (pfd) {
> +        pfd->state = PREFIX_PENDING;
> +        pfd->plife_time = plife_time;
> +        pfd->vlife_time = vlife_time;

We don't make use of these values at all ? We probably need to make
use of preferred life time atleast ? and
send renewal just before this value expires ?


> +        pfd->plen = prefix_len;
> +        pfd->prefix = addr;
> +        pfd->t1 = t1;
> +        pfd->t2 = t2;
> +        notify_pinctrl_main();
> +    }
> +}
> +
> +static void
> +pinctrl_parse_dhcpv6_reply(struct dp_packet *pkt_in,
> +                           const struct flow *ip_flow)
> +    OVS_REQUIRES(pinctrl_mutex)
> +{
> +    struct udp_header *udp_in = dp_packet_l4(pkt_in);
> +    unsigned char *in_dhcpv6_data = (unsigned char *)(udp_in + 1);
> +    size_t dlen = MIN(ntohs(udp_in->udp_len), dp_packet_l4_size(pkt_in));
> +    unsigned t1 = 0, t2 = 0, vlife_time = 0, plife_time = 0;
> +    uint8_t *end = (uint8_t *)udp_in + dlen;
> +    uint8_t prefix_len = 0;
> +    struct in6_addr ipv6;
> +    bool status = false;
> +    unsigned aid = 0;
> +
> +    memset(&ipv6, 0, sizeof (struct in6_addr));
> +    /* skip DHCPv6 common header */
> +    in_dhcpv6_data += 4;
> +    while (in_dhcpv6_data < end) {
> +        struct dhcpv6_opt_header *in_opt =
> +             (struct dhcpv6_opt_header *)in_dhcpv6_data;
> +        int opt_len = sizeof *in_opt + ntohs(in_opt->len);
> +
> +        if (in_dhcpv6_data + opt_len > end) {
> +            break;
> +        }
> +
> +        switch (ntohs(in_opt->code)) {
> +        case DHCPV6_OPT_IA_PD: {
> +            int size = sizeof *in_opt + 12;
> +            in_opt = (struct dhcpv6_opt_header *)(in_dhcpv6_data + size);
> +            struct dhcpv6_opt_ia_na *ia_na =
> +                (struct dhcpv6_opt_ia_na *)in_dhcpv6_data;
> +
> +            aid = ntohl(ia_na->iaid);
> +            t1 = ntohl(ia_na->t1);
> +            t2 = ntohl(ia_na->t2);
> +            if (t1 > t2 && t2 > 0) {
> +                break;
> +            }
> +
> +            while (size < opt_len) {
> +                if (ntohs(in_opt->code) == DHCPV6_OPT_IA_PREFIX) {
> +                    struct dhcpv6_opt_ia_prefix *ia_hdr =
> +                        (struct dhcpv6_opt_ia_prefix *)(in_dhcpv6_data + size);
> +
> +                    prefix_len = ia_hdr->plen;
> +                    plife_time = ntohl(ia_hdr->plife_time);
> +                    vlife_time = ntohl(ia_hdr->vlife_time);
> +                    memcpy(&ipv6, &ia_hdr->ipv6, sizeof (struct in6_addr));
> +                }
> +                if (ntohs(in_opt->code) == DHCPV6_OPT_STATUS_CODE) {
> +                   struct dhcpv6_opt_status *status_hdr;
> +
> +                   status_hdr = (struct dhcpv6_opt_status *)in_opt;
> +                   status = ntohs(status_hdr->status_code) == 0;
> +                }
> +                size += sizeof *in_opt + ntohs(in_opt->len);
> +                in_opt = (struct dhcpv6_opt_header *)(in_dhcpv6_data + size);
> +            }
> +            break;
> +        }
> +        default:
> +            break;
> +        }
> +        in_dhcpv6_data += opt_len;
> +    }
> +    if (status) {
> +        pinctrl_prefixd_state_handler(ip_flow, ipv6, aid, prefix_len,
> +                                      t1, t2, plife_time, vlife_time);
> +    }
> +}
> +
> +static void
> +pinctrl_handle_dhcp6_server(struct rconn *swconn, const struct flow *ip_flow,
> +                            struct dp_packet *pkt_in, const struct match *md,
> +                            struct ofpbuf *userdata)
> +{
> +    if (ip_flow->dl_type != htons(ETH_TYPE_IPV6) ||
> +        ip_flow->nw_proto != IPPROTO_UDP) {
> +        return;
> +    }
> +
> +    struct udp_header *udp_in = dp_packet_l4(pkt_in);
> +    unsigned char *dhcp_hdr = (unsigned char *)(udp_in + 1);
> +
> +    switch (*dhcp_hdr) {
> +    case DHCPV6_MSG_TYPE_ADVT:
> +        pinctrl_parse_dhcpv6_advt(swconn, ip_flow, pkt_in, md, userdata);
> +        break;
> +    case DHCPV6_MSG_TYPE_REPLY:
> +        ovs_mutex_lock(&pinctrl_mutex);
> +        pinctrl_parse_dhcpv6_reply(pkt_in, ip_flow);
> +        ovs_mutex_unlock(&pinctrl_mutex);
> +        break;
> +    default:
> +        break;
> +    }
> +}
> +
>  /* Called with in the pinctrl_handler thread context. */
>  static void
>  pinctrl_handle_put_dhcp_opts(
> @@ -1998,6 +2309,10 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg)
>          pinctrl_handle_bind_vport(&pin.flow_metadata.flow, &userdata);
>          ovs_mutex_unlock(&pinctrl_mutex);
>          break;
> +    case ACTION_OPCODE_DHCP6_SERVER:
> +        pinctrl_handle_dhcp6_server(swconn, &headers, &packet,
> +                                    &pin.flow_metadata, &userdata);
> +        break;
>
>      case ACTION_OPCODE_HANDLE_SVC_CHECK:
>          ovs_mutex_lock(&pinctrl_mutex);
> @@ -2048,13 +2363,6 @@ notify_pinctrl_handler(void)
>      seq_change(pinctrl_handler_seq);
>  }
>
> -/* Called with in the pinctrl_handler thread context. */
> -static void
> -notify_pinctrl_main(void)
> -{
> -    seq_change(pinctrl_main_seq);
> -}
> -
>  /* pinctrl_handler pthread function. */
>  static void *
>  pinctrl_handler(void *arg_)
> @@ -2076,6 +2384,7 @@ pinctrl_handler(void *arg_)
>      /* Next multicast query (IGMP) in ms. */
>      static long long int send_mcast_query_time = LLONG_MAX;
>      static long long int svc_monitors_next_run_time = LLONG_MAX;
> +    static long long int send_prefixd_time = LLONG_MAX;
>
>      swconn = rconn_create(5, 0, DSCP_DEFAULT, 1 << OFP13_VERSION);
>
> @@ -2129,6 +2438,7 @@ pinctrl_handler(void *arg_)
>                  ovs_mutex_lock(&pinctrl_mutex);
>                  send_garp_rarp_run(swconn, &send_garp_rarp_time);
>                  send_ipv6_ras(swconn, &send_ipv6_ra_time);
> +                send_ipv6_prefixd(swconn, &send_prefixd_time);
>                  send_mac_binding_buffered_pkts(swconn);
>                  ovs_mutex_unlock(&pinctrl_mutex);
>
> @@ -2146,6 +2456,7 @@ pinctrl_handler(void *arg_)
>          ipv6_ra_wait(send_ipv6_ra_time);
>          ip_mcast_querier_wait(send_mcast_query_time);
>          svc_monitors_wait(svc_monitors_next_run_time);
> +        ipv6_prefixd_wait(send_prefixd_time);
>
>          new_seq = seq_read(pinctrl_handler_seq);
>          seq_wait(pinctrl_handler_seq, new_seq);
> @@ -2162,6 +2473,7 @@ pinctrl_handler(void *arg_)
>  /* Called by ovn-controller. */
>  void
>  pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
> +            struct ovsdb_idl *ovnsb_idl,
>              struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
>              struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
>              struct ovsdb_idl_index *sbrec_port_binding_by_key,
> @@ -2197,6 +2509,9 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
>                             sbrec_port_binding_by_name, br_int, chassis,
>                             local_datapaths, active_tunnels);
>      prepare_ipv6_ras(local_datapaths);
> +    prepare_ipv6_prefixd(ovnsb_idl_txn, ovnsb_idl,
> +                         sbrec_port_binding_by_name, local_datapaths,
> +                         chassis, active_tunnels);
>      sync_dns_cache(dns_table);
>      controller_event_run(ovnsb_idl_txn, ce_table, chassis);
>      ip_mcast_sync(ovnsb_idl_txn, chassis, local_datapaths,
> @@ -2644,6 +2959,151 @@ send_ipv6_ras(struct rconn *swconn, long long int *send_ipv6_ra_time)
>      }
>  }
>
> +static void
> +compose_prefixd_solicit(struct dp_packet *b,
> +                        const struct eth_addr eth_src,
> +                        const struct eth_addr eth_dst,
> +                        const struct in6_addr *ipv6_src,
> +                        const struct in6_addr *ipv6_dst,
> +                        const struct eth_addr cmac,
> +                        unsigned aid)
> +{
> +    eth_compose(b, eth_dst, eth_src, ETH_TYPE_IPV6, IPV6_HEADER_LEN);
> +
> +    int len = UDP_HEADER_LEN + 4 + sizeof(struct dhcpv6_opt_server_id) +
> +              sizeof(struct dhcpv6_opt_ia_na);
> +    struct udp_header *udp_h = compose_ipv6(b, IPPROTO_UDP, ipv6_src,
> +                                            ipv6_dst, 0, 0, 255, len);
> +    udp_h->udp_len = htons(len);
> +    udp_h->udp_csum = 0;
> +    packet_set_udp_port(b, htons(546), htons(547));
> +
> +    unsigned char *dhcp_hdr = (unsigned char *)(udp_h + 1);
> +    *dhcp_hdr = DHCPV6_MSG_TYPE_SOLICIT;
> +
> +    struct dhcpv6_opt_server_id *opt_client_id =
> +        (struct dhcpv6_opt_server_id *)(dhcp_hdr + 4);
> +    opt_client_id->opt.code = htons(DHCPV6_OPT_CLIENT_ID_CODE);
> +    opt_client_id->opt.len = htons(sizeof(struct dhcpv6_opt_server_id) -
> +                                   sizeof(struct dhcpv6_opt_header));
> +    opt_client_id->duid_type = htons(DHCPV6_DUID_LL);
> +    opt_client_id->hw_type = htons(DHCPV6_HW_TYPE_ETH);
> +    opt_client_id->mac = cmac;
> +
> +    struct dhcpv6_opt_ia_na *ia_pd =
> +            (struct dhcpv6_opt_ia_na *)(opt_client_id + 1);
> +    ia_pd->opt.code = htons(DHCPV6_OPT_IA_PD);
> +    ia_pd->opt.len = htons(sizeof(struct dhcpv6_opt_ia_na) -
> +                           sizeof(struct dhcpv6_opt_header));
> +    ia_pd->iaid = htonl(aid);
> +    ia_pd->t1 = htonl(0xffffffff);
> +    ia_pd->t2 = htonl(0xffffffff);
> +
> +    uint32_t csum = packet_csum_pseudoheader6(dp_packet_l3(b));
> +    csum = csum_continue(csum, udp_h, dp_packet_size(b) -
> +                         ((const unsigned char *)udp_h -
> +                          (const unsigned char *)dp_packet_eth(b)));
> +    udp_h->udp_csum = csum_finish(csum);
> +    if (!udp_h->udp_csum) {
> +        udp_h->udp_csum = htons(0xffff);
> +    }
> +}
> +
> +#define IPV6_PREFIXD_TIMEOUT    3000LL
> +static long long int
> +ipv6_prefixd_send(struct rconn *swconn, struct ipv6_prefixd_state *pfd)
> +{
> +    long long int cur_time = time_msec();
> +    if (cur_time < pfd->next_announce) {
> +        return pfd->next_announce;
> +    }
> +
> +    uint64_t packet_stub[256 / 8];
> +    struct dp_packet packet;
> +
> +    struct eth_addr eth_dst;
> +    eth_dst = (struct eth_addr) ETH_ADDR_C(33,33,00,01,00,02);
> +    struct in6_addr ipv6_dst;
> +    ipv6_parse("ff02::1:2", &ipv6_dst);
> +
> +    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
> +    compose_prefixd_solicit(&packet, pfd->ea, eth_dst, &pfd->ipv6_addr,
> +                            &ipv6_dst, pfd->cmac, pfd->aid);
> +
> +    uint64_t ofpacts_stub[4096 / 8];
> +    struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
> +
> +    /* Set MFF_LOG_DATAPATH and MFF_LOG_INPORT. */
> +    uint32_t dp_key = pfd->metadata;
> +    uint32_t port_key = pfd->port_key;
> +    put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts);
> +    put_load(port_key, MFF_LOG_INPORT, 0, 32, &ofpacts);
> +    put_load(1, MFF_LOG_FLAGS, MLF_LOCAL_ONLY_BIT, 1, &ofpacts);
> +    struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
> +    resubmit->in_port = OFPP_CONTROLLER;
> +    resubmit->table_id = OFTABLE_LOG_INGRESS_PIPELINE;
> +
> +    struct ofputil_packet_out po = {
> +        .packet = dp_packet_data(&packet),
> +        .packet_len = dp_packet_size(&packet),
> +        .buffer_id = UINT32_MAX,
> +        .ofpacts = ofpacts.data,
> +        .ofpacts_len = ofpacts.size,
> +    };
> +
> +    match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
> +    enum ofp_version version = rconn_get_version(swconn);
> +    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
> +    queue_msg(swconn, ofputil_encode_packet_out(&po, proto));
> +    dp_packet_uninit(&packet);
> +    ofpbuf_uninit(&ofpacts);
> +    pfd->next_announce = cur_time + random_range(IPV6_PREFIXD_TIMEOUT);
> +
> +    return pfd->next_announce;
> +}
> +
> +static bool ipv6_prefixd_should_inject(void)
> +{
> +    struct shash_node *iter;
> +
> +    SHASH_FOR_EACH (iter, &ipv6_prefixd) {
> +        struct ipv6_prefixd_state *pfd = iter->data;
> +        if (pfd->state == PREFIX_SOLICIT) {
> +            return true;
> +        }
> +        if (pfd->state == PREFIX_DONE &&
> +            pfd->next_announce < time_msec()) {
> +            pfd->state = PREFIX_SOLICIT;
> +            return true;
> +        }
> +    }
> +    return false;
> +}
> +
> +static void
> +ipv6_prefixd_wait(long long int timeout)
> +{
> +    if (ipv6_prefixd_should_inject()) {
> +        poll_timer_wait_until(timeout);
> +    }
> +}
> +
> +static void
> +send_ipv6_prefixd(struct rconn *swconn, long long int *send_prefixd_time)
> +    OVS_REQUIRES(pinctrl_mutex)
> +{
> +    struct shash_node *iter;
> +
> +    *send_prefixd_time = LLONG_MAX;
> +    SHASH_FOR_EACH (iter, &ipv6_prefixd) {
> +        struct ipv6_prefixd_state *pfd = iter->data;
> +        long long int next_msg = ipv6_prefixd_send(swconn, pfd);
> +        if (*send_prefixd_time > next_msg) {
> +            *send_prefixd_time = next_msg;
> +        }
> +    }
> +}
> +
>  /* Called by pinctrl_run(). Runs with in the main ovn-controller
>   * thread context. */
>  static void
> @@ -2721,6 +3181,142 @@ prepare_ipv6_ras(const struct hmap *local_datapaths)
>
>  }
>
> +static bool
> +fill_ipv6_prefix_state(struct ovsdb_idl_txn *ovnsb_idl_txn,
> +                       struct ovsdb_idl *ovnsb_idl,
> +                       const struct hmap *local_datapaths,
> +                       struct eth_addr ea, struct in6_addr ipv6_addr,
> +                       int64_t tunnel_key, int64_t dp_tunnel_key)
> +    OVS_REQUIRES(pinctrl_mutex)
> +{
> +    const struct local_datapath *ld;
> +    bool changed = false;
> +
> +    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
> +        const struct sbrec_port_binding *pb, *pb_next;
> +        SBREC_PORT_BINDING_FOR_EACH_SAFE (pb, pb_next, ovnsb_idl) {
> +            if (!smap_get_bool(&pb->options, "ipv6_prefix", false)) {
> +                continue;
> +            }
> +
> +            struct lport_addresses c_addrs;
> +            int i;
> +            for (i = 0; i < pb->n_mac; i++) {
> +                if (extract_lsp_addresses(pb->mac[i], &c_addrs)) {
> +                        break;
> +                }
> +            }
> +
> +            struct ipv6_prefixd_state *pfd = shash_find_data(
> +                    &ipv6_prefixd, pb->logical_port);
> +            if (!pfd) {
> +                pfd = xzalloc(sizeof *pfd);
> +                pfd->ipv6_addr = ipv6_addr;
> +                pfd->ea = ea;
> +                pfd->cmac = c_addrs.ea;
> +                pfd->aid = random_range(0xfffffff);
> +                pfd->metadata = dp_tunnel_key;
> +                pfd->port_key = tunnel_key;
> +                shash_add(&ipv6_prefixd, pb->logical_port, pfd);
> +                pfd->next_announce = time_msec() +
> +                                     random_range(IPV6_PREFIXD_TIMEOUT);
> +                changed = true;
> +            } else if (pfd->state == PREFIX_PENDING && ovnsb_idl_txn) {
> +                char prefix_str[INET6_ADDRSTRLEN + 1] = {};
> +                struct smap options;
> +
> +                pfd->state = PREFIX_DONE;
> +                pfd->next_announce = time_msec() + pfd->t1 * 1000;
> +                ipv6_string_mapped(prefix_str, &pfd->prefix);
> +                smap_clone(&options, &pb->options);
> +                smap_add_format(&options, "ipv6_ra_pd_list", "%s/%d",
> +                                prefix_str, pfd->plen);
> +                sbrec_port_binding_set_options(pb, &options);
> +                smap_destroy(&options);
> +            }
> +        }
> +    }
> +
> +    return changed;
> +}
> +
> +static void
> +prepare_ipv6_prefixd(struct ovsdb_idl_txn *ovnsb_idl_txn,
> +                     struct ovsdb_idl *ovnsb_idl,
> +                     struct ovsdb_idl_index *sbrec_port_binding_by_name,
> +                     const struct hmap *local_datapaths,
> +                     const struct sbrec_chassis *chassis,
> +                     const struct sset *active_tunnels)
> +    OVS_REQUIRES(pinctrl_mutex)
> +{
> +    const struct local_datapath *ld;
> +    bool changed = false;
> +    int i;
> +
> +    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
> +        const struct sbrec_port_binding *pb, *pb_next;
> +        SBREC_PORT_BINDING_FOR_EACH_SAFE (pb, pb_next, ovnsb_idl) {

We are having totally 4 for loops. 2 here and 2 in the fill_ipv6_prefix_state().
In a highly loaded system, this can be a bottleneck. I think we can
optimize here.

something like


HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
    if (!smap_get(&ld->datapath->external_ids, "logical-router") {
        /* This is a logical switch and we can skip it. */
        continue;
    }

   for (size_t i = 0; i < ld->n_peer_ports; ld++) {
          if (!smap_get_bool(&ld->peer[i]->options,
"ipv6_prefix_delegation", false)) {
               continue;
          }

          ....
           ....
   }
}


> +            if (!smap_get_bool(&pb->options, "ipv6_prefix_delegation",
> +                               false)) {
> +                continue;
> +            }
> +
> +            const char *peer_s = smap_get(&pb->options, "peer");
> +            if (!peer_s) {
> +                continue;
> +            }
> +
> +            const struct sbrec_port_binding *peer
> +                = lport_lookup_by_name(sbrec_port_binding_by_name, peer_s);
> +            if (!peer) {
> +                continue;
> +            }
> +
> +            char *redirect_name = xasprintf("cr-%s", pb->logical_port);
> +            bool resident = lport_is_chassis_resident(
> +                    sbrec_port_binding_by_name, chassis, active_tunnels,
> +                    redirect_name);
> +            free(redirect_name);
> +            if (!resident && strcmp(pb->type, "l3gateway")) {
> +                continue;
> +            }
> +
> +            struct in6_addr ip6_addr;
> +            struct eth_addr ea;
> +            for (i = 0; i < pb->n_mac; i++) {
> +                struct lport_addresses laddrs;
> +
> +                if (!extract_lsp_addresses(pb->mac[i], &laddrs)) {
> +                    continue;
> +                }
> +
> +                ea = laddrs.ea;
> +                if (laddrs.n_ipv6_addrs > 0) {
> +                    ip6_addr = laddrs.ipv6_addrs[0].addr;
> +                    break;
> +                }
> +            }
> +
> +            if (eth_addr_is_zero(ea)) {
> +                continue;
> +            }
> +
> +            if (i == pb->n_mac) {
> +                in6_generate_lla(ea, &ip6_addr);
> +            }
> +
> +            changed |= fill_ipv6_prefix_state(ovnsb_idl_txn, ovnsb_idl,
> +                                              local_datapaths, ea, ip6_addr,
> +                                              peer->tunnel_key,
> +                                              peer->datapath->tunnel_key);
> +        }
> +    }
> +
> +    if (changed) {
> +        notify_pinctrl_handler();
> +    }
> +}
> +
>  /* Called by pinctrl_run(). Runs with in the main ovn-controller
>   * thread context. */
>  void
> @@ -2743,6 +3339,7 @@ pinctrl_destroy(void)
>      free(pinctrl.br_int_name);
>      destroy_send_garps_rarps();
>      destroy_ipv6_ras();
> +    destroy_ipv6_prefixd();
>      destroy_buffered_packets_map();
>      event_table_destroy();
>      destroy_put_mac_bindings();
> @@ -4240,6 +4837,7 @@ may_inject_pkts(void)
>  {
>      return (!shash_is_empty(&ipv6_ras) ||
>              !shash_is_empty(&send_garp_rarp_data) ||
> +            ipv6_prefixd_should_inject() ||
>              !ovs_list_is_empty(&mcast_query_list) ||
>              !ovs_list_is_empty(&buffered_mac_bindings));
>  }
> diff --git a/controller/pinctrl.h b/controller/pinctrl.h
> index 8fa4baae9..a34749f02 100644
> --- a/controller/pinctrl.h
> +++ b/controller/pinctrl.h
> @@ -26,6 +26,7 @@ struct hmap;
>  struct lport_index;
>  struct ovsdb_idl_index;
>  struct ovsdb_idl_txn;
> +struct ovsdb_idl;
>  struct ovsrec_bridge;
>  struct sbrec_chassis;
>  struct sbrec_dns_table;
> @@ -34,6 +35,7 @@ struct sbrec_service_monitor_table;
>
>  void pinctrl_init(void);
>  void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
> +                 struct ovsdb_idl *ovnsb_idl,
>                   struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
>                   struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
>                   struct ovsdb_idl_index *sbrec_port_binding_by_key,
> diff --git a/include/ovn/actions.h b/include/ovn/actions.h
> index 047a8d737..c3d719985 100644
> --- a/include/ovn/actions.h
> +++ b/include/ovn/actions.h
> @@ -89,7 +89,8 @@ struct ovn_extend_table;
>      OVNACT(CHECK_PKT_LARGER,  ovnact_check_pkt_larger) \
>      OVNACT(TRIGGER_EVENT,     ovnact_controller_event) \
>      OVNACT(BIND_VPORT,        ovnact_bind_vport)       \
> -    OVNACT(HANDLE_SVC_CHECK,  ovnact_handle_svc_check)
> +    OVNACT(HANDLE_SVC_CHECK,  ovnact_handle_svc_check) \
> +    OVNACT(DHCP6_REPLY,       ovnact_nest)
>
>  /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
>  enum OVS_PACKED_ENUM ovnact_type {
> @@ -552,6 +553,11 @@ enum action_opcode {
>       *     MFF_LOG_INPORT = port
>       */
>      ACTION_OPCODE_HANDLE_SVC_CHECK,
> +    /* handle_dhcpv6_reply { ...actions ...}."
> +     *
> +     *  The actions, in OpenFlow 1.3 format, follow the action_header.
> +     */
> +    ACTION_OPCODE_DHCP6_SERVER,
>  };
>
>  /* Header. */
> diff --git a/lib/actions.c b/lib/actions.c
> index 051e6c875..b7bd219e4 100644
> --- a/lib/actions.c
> +++ b/lib/actions.c
> @@ -2172,6 +2172,26 @@ ovnact_put_opts_free(struct ovnact_put_opts *pdo)
>      free_gen_options(pdo->options, pdo->n_options);
>  }
>
> +static void
> +parse_DHCP6_REPLY(struct action_context *ctx)
> +{
> +    parse_nested_action(ctx, OVNACT_DHCP6_REPLY, "ip6");
> +}
> +
> +static void
> +format_DHCP6_REPLY(const struct ovnact_nest *nest, struct ds *s)
> +{
> +    format_nested_action(nest, "handle_dhcpv6_reply", s);
> +}
> +
> +static void
> +encode_DHCP6_REPLY(const struct ovnact_nest *on,
> +                   const struct ovnact_encode_params *ep,
> +                   struct ofpbuf *ofpacts)
> +{
> +    encode_nested_actions(on, ep, ACTION_OPCODE_DHCP6_SERVER, ofpacts);
> +}
> +
>  static void
>  parse_SET_QUEUE(struct action_context *ctx)
>  {
> @@ -2973,6 +2993,8 @@ parse_action(struct action_context *ctx)
>          parse_bind_vport(ctx);
>      } else if (lexer_match_id(ctx->lexer, "handle_svc_check")) {
>          parse_handle_svc_check(ctx);
> +    } else if (lexer_match_id(ctx->lexer, "handle_dhcpv6_reply")) {
> +        parse_DHCP6_REPLY(ctx);
>      } else {
>          lexer_syntax_error(ctx->lexer, "expecting action");
>      }
> diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
> index 375b77014..2987277a9 100644
> --- a/lib/ovn-l7.h
> +++ b/lib/ovn-l7.h
> @@ -174,8 +174,11 @@ struct dhcp_opt6_header {
>  #define DHCPV6_OPT_SERVER_ID_CODE        2
>  #define DHCPV6_OPT_IA_NA_CODE            3
>  #define DHCPV6_OPT_IA_ADDR_CODE          5
> +#define DHCPV6_OPT_STATUS_CODE           13
>  #define DHCPV6_OPT_DNS_SERVER_CODE       23
>  #define DHCPV6_OPT_DOMAIN_SEARCH_CODE    24
> +#define DHCPV6_OPT_IA_PD                 25
> +#define DHCPV6_OPT_IA_PREFIX             26
>
>  #define DHCPV6_OPT_SERVER_ID \
>      DHCP_OPTION("server_id", DHCPV6_OPT_SERVER_ID_CODE, "mac")
> @@ -254,6 +257,22 @@ struct ovs_nd_route_info {
>  };
>  BUILD_ASSERT_DECL(ND_ROUTE_INFO_OPT_LEN == sizeof(struct ovs_nd_route_info));
>
> +OVS_PACKED(
> +struct dhcpv6_opt_ia_prefix {
> +    struct dhcpv6_opt_header opt;
> +    ovs_be32 plife_time;
> +    ovs_be32 vlife_time;
> +    uint8_t plen;
> +    struct in6_addr ipv6;
> +});
> +
> +OVS_PACKED(
> +struct dhcpv6_opt_status {
> +    struct dhcpv6_opt_header opt;
> +    ovs_be16 status_code;
> +    uint8_t msg[];
> +});
> +
>  #define DHCPV6_DUID_LL      3
>  #define DHCPV6_HW_TYPE_ETH  1
>
> diff --git a/ovn-sb.xml b/ovn-sb.xml
> index 82167c488..14fe249ec 100644
> --- a/ovn-sb.xml
> +++ b/ovn-sb.xml
> @@ -2114,6 +2114,14 @@ tcp.flags = RST;
>
>            <p><b>Example:</b> <code>handle_svc_check(inport);</code></p>
>          </dd>
> +
> +        <dt><code>handle_dhcpv6_reply;</code></dt>
> +        <dd>
> +          <p>
> +            This action is used to parse DHCPv6 replies from IPv6
> +            Delegation Router and managed IPv6 Prefix delegation state machine
> +          </p>
> +        </dd>
>        </dl>
>      </column>
>
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 411b76804..9aa30d435 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -1481,6 +1481,12 @@ handle_svc_check();
>  handle_svc_check(reg0);
>      Cannot use numeric field reg0 where string field is required.
>
> +# prefix delegation
> +handle_dhcpv6_reply{};
> +    formats as handle_dhcpv6_reply { drop; };
> +    encodes as controller(userdata=00.00.00.13.00.00.00.00)
> +    has prereqs ip6
> +
>  # Miscellaneous negative tests.
>  ;
>      Syntax error at `;'.
> diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
> index 264543876..3b132a2eb 100644
> --- a/utilities/ovn-trace.c
> +++ b/utilities/ovn-trace.c
> @@ -2224,6 +2224,9 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
>
>          case OVNACT_HANDLE_SVC_CHECK:
>              break;
> +
> +        case OVNACT_DHCP6_REPLY:
> +            break;
>          }
>      }
>      ds_destroy(&s);
> --
> 2.21.1
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
Lorenzo Bianconi March 16, 2020, 6:44 p.m. UTC | #2
> "
> 
> On Tue, Jan 14, 2020 at 4:37 PM Lorenzo Bianconi
> <lorenzo.bianconi@redhat.com> wrote:
> >
> > Introduce IPv6 Prefix delegation state machine according to RFC 3633
> > https://tools.ietf.org/html/rfc3633.
> > Add handle_dhcpv6_reply controller action to parse advertise/reply from
> > IPv6 delegation server. Advertise/reply are parsed running respectively:
> > - pinctrl_parse_dhcv6_advt
> > - pinctrl_parse_dhcv6_reply
> > The IPv6 requesting router starts sending dhcpv6 solicit through the logical
> > router port marked with ipv6_prefix_delegation set to true.
> > An IPv6 prefix will be requested for each logical router port marked
> > with "prefix" set to true in option column of logical router port table.
> > Save IPv6 prefix received by IPv6 delegation router in the options columns of
> > SB port binding table in order to be reused by Router Advertisement framework
> > run by ovn logical router pipeline.
> > IPv6 Prefix delegation state machine is enabled on Gateway Router or on
> > a Gateway Router Port
> >
> > Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
> 
> 
> Hi Lorenzo,
> Thanks for the v6.

Hi Numan,

thx for the reivew and sorry for the delay

> 
> I tested this series.  Below are some comments
> 
>  - When I configured the prefix options on the router ports,
> ovn-controller hosting the gateway
>    router port sends the PD solicit message and gets the prefix, but
> it never sends the renew/Solicit
>    requests periodically. In the code I see that the next announce
> time is chosen. But looks like that is not
>   working. I think it's better to use the "prefix lifetime option"
> value sent by the delegating server rather than
>   current_time + random(3000) which this patch does.

renew message timeout is set based on t1 field of IA_PD option received
from the delation router. According to the RFC3633 (pag 8):

"The time at which the requesting router should contact the delegating router
 from which the prefixes in the IA_PD were obtained to extend the lifetimes
 of the prefixes delegated to the IA_PD"

Do you think we should use preferred-lifetime/valid-lifetime fields?
> 
> - Suppose if a router port had received a prefix 'P1' and if I restart
> ovn-controller, ovn-controller
>   sends the PD messages and gets another prefix 'P2'. Ideally
> ovn-controller should try to renew
>   the existing prefix. Can you please check if it is possible to
> include the prefix option in the Request message ?
>   Probably ovn-controller can send a Renew request if a router port
> has already a prefix ? If the delegating server,
>   can't allocate the same prefix, ovn-controller can start the fresh
> process of sending Solicit message.

ack, I will fix it in v7

> 
> - In the above case, I notice that when delegating server sends 'P2',
> ovn-controller doesn't update this new prefix
>   in the port_binding.options:ipv6_ra_pd_list. And the patch 2 of this
> series, doesn't update the "ipv6_prefix column
>   of logical router port.  I think it's better for ovn-northd to just
> update/reset the "ipv6_prefix" column from the
>   port_binding.options:ipv6_ra_pd_list, rather than appending the value.

ack, I will fix it in v7

> 
>  - The action handle_dhcp6_reply takes inner actions. But this is not
> documented.
>    I think it's better not to include the inner actions. When the
> delegating server sends the "reply" message, ovn-controller
>    doesn't need to send any reply. But since this action is used for
> handling the "Advertise" and "Reply" messages from
>    the delegating router, it doesn't seem appropriate to include the
> inner actions.
> 
> Please see some more comments below
> 
> 
> > ---
> >  controller/ovn-controller.c |   1 +
> >  controller/pinctrl.c        | 612 +++++++++++++++++++++++++++++++++++-
> >  controller/pinctrl.h        |   2 +
> >  include/ovn/actions.h       |   8 +-
> >  lib/actions.c               |  22 ++
> >  lib/ovn-l7.h                |  19 ++
> >  ovn-sb.xml                  |   8 +
> >  tests/ovn.at                |   6 +
> >  utilities/ovn-trace.c       |   3 +
> >  9 files changed, 673 insertions(+), 8 deletions(-)
> >
> > diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> > index 17744d416..d559e845e 100644
> > --- a/controller/ovn-controller.c
> > +++ b/controller/ovn-controller.c
> > @@ -2145,6 +2145,7 @@ main(int argc, char *argv[])
> >                      runtime_data = engine_get_data(&en_runtime_data);
> >                      if (runtime_data) {
> >                          pinctrl_run(ovnsb_idl_txn,
> > +                                    ovnsb_idl_loop.idl,
> >                                      sbrec_datapath_binding_by_key,
> >                                      sbrec_port_binding_by_datapath,
> >                                      sbrec_port_binding_by_key,

[...]

> > +pinctrl_prefixd_state_handler(const struct flow *ip_flow,
> > +                              struct in6_addr addr, unsigned aid,
> > +                              char prefix_len, unsigned t1, unsigned t2,
> > +                              unsigned plife_time, unsigned vlife_time)
> > +{
> > +    struct ipv6_prefixd_state *pfd;
> > +
> > +    pfd = pinctrl_find_prefixd_state(ip_flow, aid);
> > +    if (pfd) {
> > +        pfd->state = PREFIX_PENDING;
> > +        pfd->plife_time = plife_time;
> > +        pfd->vlife_time = vlife_time;
> 
> We don't make use of these values at all ? We probably need to make
> use of preferred life time atleast ? and
> send renewal just before this value expires ?

we are not using them for the moment, do you prefer to remove them?

> 
> 
> > +        pfd->plen = prefix_len;
> > +        pfd->prefix = addr;
> > +        pfd->t1 = t1;
> > +        pfd->t2 = t2;
> > +        notify_pinctrl_main();
> > +    }
> > +}
> > +
> > +static void
> > +pinctrl_parse_dhcpv6_reply(struct dp_packet *pkt_in,

[...]

> > +    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
> > +        const struct sbrec_port_binding *pb, *pb_next;
> > +        SBREC_PORT_BINDING_FOR_EACH_SAFE (pb, pb_next, ovnsb_idl) {
> 
> We are having totally 4 for loops. 2 here and 2 in the fill_ipv6_prefix_state().
> In a highly loaded system, this can be a bottleneck. I think we can
> optimize here.
> 
> something like
> 
> 
> HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
>     if (!smap_get(&ld->datapath->external_ids, "logical-router") {
>         /* This is a logical switch and we can skip it. */
>         continue;
>     }
> 
>    for (size_t i = 0; i < ld->n_peer_ports; ld++) {
>           if (!smap_get_bool(&ld->peer[i]->options,
> "ipv6_prefix_delegation", false)) {
>                continue;
>           }
> 
>           ....
>            ....
>    }
> }

ack, I will fix it in v7

Regards,
Lorenzo

> 
> 
> > +            if (!smap_get_bool(&pb->options, "ipv6_prefix_delegation",
> > +                               false)) {
> > +                continue;
> > +            }
> > +
> > +            const char *peer_s = smap_get(&pb->options, "peer");
> > +            if (!peer_s) {
> > +                continue;
> > +            }
> > +
> > +            const struct sbrec_port_binding *peer
> > +                = lport_lookup_by_name(sbrec_port_binding_by_name, peer_s);
> > +            if (!peer) {
> > +                continue;
> > +            }
> > +
> > +            char *redirect_name = xasprintf("cr-%s", pb->logical_port);
> > +            bool resident = lport_is_chassis_resident(
> > +                    sbrec_port_binding_by_name, chassis, active_tunnels,
> > +                    redirect_name);
> > +            free(redirect_name);
> > +            if (!resident && strcmp(pb->type, "l3gateway")) {
> > +                continue;
> > +            }
> > +
> > +            struct in6_addr ip6_addr;
> > +            struct eth_addr ea;
> > +            for (i = 0; i < pb->n_mac; i++) {
> > +                struct lport_addresses laddrs;
> > +
> > +                if (!extract_lsp_addresses(pb->mac[i], &laddrs)) {
> > +                    continue;
> > +                }
> > +
> > +                ea = laddrs.ea;
> > +                if (laddrs.n_ipv6_addrs > 0) {
> > +                    ip6_addr = laddrs.ipv6_addrs[0].addr;
> > +                    break;
> > +                }
> > +            }
> > +
> > +            if (eth_addr_is_zero(ea)) {
> > +                continue;
> > +            }
> > +
> > +            if (i == pb->n_mac) {
> > +                in6_generate_lla(ea, &ip6_addr);
> > +            }
> > +
> > +            changed |= fill_ipv6_prefix_state(ovnsb_idl_txn, ovnsb_idl,
> > +                                              local_datapaths, ea, ip6_addr,
> > +                                              peer->tunnel_key,
> > +                                              peer->datapath->tunnel_key);
> > +        }
> > +    }
> > +
> > +    if (changed) {
> > +        notify_pinctrl_handler();
> > +    }
> > +}
> > +
> >  /* Called by pinctrl_run(). Runs with in the main ovn-controller
> >   * thread context. */
> >  void
> > @@ -2743,6 +3339,7 @@ pinctrl_destroy(void)
> >      free(pinctrl.br_int_name);
> >      destroy_send_garps_rarps();
> >      destroy_ipv6_ras();
> > +    destroy_ipv6_prefixd();
> >      destroy_buffered_packets_map();
> >      event_table_destroy();
> >      destroy_put_mac_bindings();
> > @@ -4240,6 +4837,7 @@ may_inject_pkts(void)
> >  {
> >      return (!shash_is_empty(&ipv6_ras) ||
> >              !shash_is_empty(&send_garp_rarp_data) ||
> > +            ipv6_prefixd_should_inject() ||
> >              !ovs_list_is_empty(&mcast_query_list) ||
> >              !ovs_list_is_empty(&buffered_mac_bindings));
> >  }
> > diff --git a/controller/pinctrl.h b/controller/pinctrl.h
> > index 8fa4baae9..a34749f02 100644
> > --- a/controller/pinctrl.h
> > +++ b/controller/pinctrl.h
> > @@ -26,6 +26,7 @@ struct hmap;
> >  struct lport_index;
> >  struct ovsdb_idl_index;
> >  struct ovsdb_idl_txn;
> > +struct ovsdb_idl;
> >  struct ovsrec_bridge;
> >  struct sbrec_chassis;
> >  struct sbrec_dns_table;
> > @@ -34,6 +35,7 @@ struct sbrec_service_monitor_table;
> >
> >  void pinctrl_init(void);
> >  void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > +                 struct ovsdb_idl *ovnsb_idl,
> >                   struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
> >                   struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
> >                   struct ovsdb_idl_index *sbrec_port_binding_by_key,
> > diff --git a/include/ovn/actions.h b/include/ovn/actions.h
> > index 047a8d737..c3d719985 100644
> > --- a/include/ovn/actions.h
> > +++ b/include/ovn/actions.h
> > @@ -89,7 +89,8 @@ struct ovn_extend_table;
> >      OVNACT(CHECK_PKT_LARGER,  ovnact_check_pkt_larger) \
> >      OVNACT(TRIGGER_EVENT,     ovnact_controller_event) \
> >      OVNACT(BIND_VPORT,        ovnact_bind_vport)       \
> > -    OVNACT(HANDLE_SVC_CHECK,  ovnact_handle_svc_check)
> > +    OVNACT(HANDLE_SVC_CHECK,  ovnact_handle_svc_check) \
> > +    OVNACT(DHCP6_REPLY,       ovnact_nest)
> >
> >  /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
> >  enum OVS_PACKED_ENUM ovnact_type {
> > @@ -552,6 +553,11 @@ enum action_opcode {
> >       *     MFF_LOG_INPORT = port
> >       */
> >      ACTION_OPCODE_HANDLE_SVC_CHECK,
> > +    /* handle_dhcpv6_reply { ...actions ...}."
> > +     *
> > +     *  The actions, in OpenFlow 1.3 format, follow the action_header.
> > +     */
> > +    ACTION_OPCODE_DHCP6_SERVER,
> >  };
> >
> >  /* Header. */
> > diff --git a/lib/actions.c b/lib/actions.c
> > index 051e6c875..b7bd219e4 100644
> > --- a/lib/actions.c
> > +++ b/lib/actions.c
> > @@ -2172,6 +2172,26 @@ ovnact_put_opts_free(struct ovnact_put_opts *pdo)
> >      free_gen_options(pdo->options, pdo->n_options);
> >  }
> >
> > +static void
> > +parse_DHCP6_REPLY(struct action_context *ctx)
> > +{
> > +    parse_nested_action(ctx, OVNACT_DHCP6_REPLY, "ip6");
> > +}
> > +
> > +static void
> > +format_DHCP6_REPLY(const struct ovnact_nest *nest, struct ds *s)
> > +{
> > +    format_nested_action(nest, "handle_dhcpv6_reply", s);
> > +}
> > +
> > +static void
> > +encode_DHCP6_REPLY(const struct ovnact_nest *on,
> > +                   const struct ovnact_encode_params *ep,
> > +                   struct ofpbuf *ofpacts)
> > +{
> > +    encode_nested_actions(on, ep, ACTION_OPCODE_DHCP6_SERVER, ofpacts);
> > +}
> > +
> >  static void
> >  parse_SET_QUEUE(struct action_context *ctx)
> >  {
> > @@ -2973,6 +2993,8 @@ parse_action(struct action_context *ctx)
> >          parse_bind_vport(ctx);
> >      } else if (lexer_match_id(ctx->lexer, "handle_svc_check")) {
> >          parse_handle_svc_check(ctx);
> > +    } else if (lexer_match_id(ctx->lexer, "handle_dhcpv6_reply")) {
> > +        parse_DHCP6_REPLY(ctx);
> >      } else {
> >          lexer_syntax_error(ctx->lexer, "expecting action");
> >      }
> > diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
> > index 375b77014..2987277a9 100644
> > --- a/lib/ovn-l7.h
> > +++ b/lib/ovn-l7.h
> > @@ -174,8 +174,11 @@ struct dhcp_opt6_header {
> >  #define DHCPV6_OPT_SERVER_ID_CODE        2
> >  #define DHCPV6_OPT_IA_NA_CODE            3
> >  #define DHCPV6_OPT_IA_ADDR_CODE          5
> > +#define DHCPV6_OPT_STATUS_CODE           13
> >  #define DHCPV6_OPT_DNS_SERVER_CODE       23
> >  #define DHCPV6_OPT_DOMAIN_SEARCH_CODE    24
> > +#define DHCPV6_OPT_IA_PD                 25
> > +#define DHCPV6_OPT_IA_PREFIX             26
> >
> >  #define DHCPV6_OPT_SERVER_ID \
> >      DHCP_OPTION("server_id", DHCPV6_OPT_SERVER_ID_CODE, "mac")
> > @@ -254,6 +257,22 @@ struct ovs_nd_route_info {
> >  };
> >  BUILD_ASSERT_DECL(ND_ROUTE_INFO_OPT_LEN == sizeof(struct ovs_nd_route_info));
> >
> > +OVS_PACKED(
> > +struct dhcpv6_opt_ia_prefix {
> > +    struct dhcpv6_opt_header opt;
> > +    ovs_be32 plife_time;
> > +    ovs_be32 vlife_time;
> > +    uint8_t plen;
> > +    struct in6_addr ipv6;
> > +});
> > +
> > +OVS_PACKED(
> > +struct dhcpv6_opt_status {
> > +    struct dhcpv6_opt_header opt;
> > +    ovs_be16 status_code;
> > +    uint8_t msg[];
> > +});
> > +
> >  #define DHCPV6_DUID_LL      3
> >  #define DHCPV6_HW_TYPE_ETH  1
> >
> > diff --git a/ovn-sb.xml b/ovn-sb.xml
> > index 82167c488..14fe249ec 100644
> > --- a/ovn-sb.xml
> > +++ b/ovn-sb.xml
> > @@ -2114,6 +2114,14 @@ tcp.flags = RST;
> >
> >            <p><b>Example:</b> <code>handle_svc_check(inport);</code></p>
> >          </dd>
> > +
> > +        <dt><code>handle_dhcpv6_reply;</code></dt>
> > +        <dd>
> > +          <p>
> > +            This action is used to parse DHCPv6 replies from IPv6
> > +            Delegation Router and managed IPv6 Prefix delegation state machine
> > +          </p>
> > +        </dd>
> >        </dl>
> >      </column>
> >
> > diff --git a/tests/ovn.at b/tests/ovn.at
> > index 411b76804..9aa30d435 100644
> > --- a/tests/ovn.at
> > +++ b/tests/ovn.at
> > @@ -1481,6 +1481,12 @@ handle_svc_check();
> >  handle_svc_check(reg0);
> >      Cannot use numeric field reg0 where string field is required.
> >
> > +# prefix delegation
> > +handle_dhcpv6_reply{};
> > +    formats as handle_dhcpv6_reply { drop; };
> > +    encodes as controller(userdata=00.00.00.13.00.00.00.00)
> > +    has prereqs ip6
> > +
> >  # Miscellaneous negative tests.
> >  ;
> >      Syntax error at `;'.
> > diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
> > index 264543876..3b132a2eb 100644
> > --- a/utilities/ovn-trace.c
> > +++ b/utilities/ovn-trace.c
> > @@ -2224,6 +2224,9 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
> >
> >          case OVNACT_HANDLE_SVC_CHECK:
> >              break;
> > +
> > +        case OVNACT_DHCP6_REPLY:
> > +            break;
> >          }
> >      }
> >      ds_destroy(&s);
> > --
> > 2.21.1
> >
> > _______________________________________________
> > dev mailing list
> > dev@openvswitch.org
> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> >
>

Patch
diff mbox series

diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index 17744d416..d559e845e 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -2145,6 +2145,7 @@  main(int argc, char *argv[])
                     runtime_data = engine_get_data(&en_runtime_data);
                     if (runtime_data) {
                         pinctrl_run(ovnsb_idl_txn,
+                                    ovnsb_idl_loop.idl,
                                     sbrec_datapath_binding_by_key,
                                     sbrec_port_binding_by_datapath,
                                     sbrec_port_binding_by_key,
diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index 452ca8a1c..2a37fc525 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -270,6 +270,20 @@  static void pinctrl_ip_mcast_handle_igmp(
     const struct match *md,
     struct ofpbuf *userdata);
 
+static void init_ipv6_prefixd(void);
+static void destroy_ipv6_prefixd(void);
+static void ipv6_prefixd_wait(long long int timeout);
+static void
+prepare_ipv6_prefixd(struct ovsdb_idl_txn *ovnsb_idl_txn,
+                     struct ovsdb_idl *ovnsb_idl,
+                     struct ovsdb_idl_index *sbrec_port_binding_by_name,
+                     const struct hmap *local_datapaths,
+                     const struct sbrec_chassis *chassis,
+                     const struct sset *active_tunnels)
+    OVS_REQUIRES(pinctrl_mutex);
+static void
+send_ipv6_prefixd(struct rconn *swconn, long long int *send_prefixd_time)
+    OVS_REQUIRES(pinctrl_mutex);
 static bool may_inject_pkts(void);
 
 static void init_put_vport_bindings(void);
@@ -457,6 +471,7 @@  pinctrl_init(void)
     init_put_mac_bindings();
     init_send_garps_rarps();
     init_ipv6_ras();
+    init_ipv6_prefixd();
     init_buffered_packets_map();
     init_event_table();
     ip_mcast_snoop_init();
@@ -544,6 +559,49 @@  set_actions_and_enqueue_msg(struct rconn *swconn,
     ofpbuf_uninit(&ofpacts);
 }
 
+static struct shash ipv6_prefixd;
+
+enum {
+    PREFIX_SOLICIT,
+    PREFIX_PENDING,
+    PREFIX_DONE,
+};
+
+struct ipv6_prefixd_state {
+    long long int next_announce;
+    struct in6_addr ipv6_addr;
+    struct eth_addr ea;
+    struct eth_addr cmac;
+    int64_t port_key;
+    int64_t metadata;
+    struct in6_addr prefix;
+    unsigned plife_time;
+    unsigned vlife_time;
+    unsigned aid;
+    unsigned t1;
+    unsigned t2;
+    int8_t plen;
+    int state;
+};
+
+static void
+init_ipv6_prefixd(void)
+{
+    shash_init(&ipv6_prefixd);
+}
+
+static void
+destroy_ipv6_prefixd(void)
+{
+    struct shash_node *iter, *next;
+    SHASH_FOR_EACH_SAFE (iter, next, &ipv6_prefixd) {
+        struct ipv6_prefixd_state *pfd = iter->data;
+        free(pfd);
+        shash_delete(&ipv6_prefixd, iter);
+    }
+    shash_destroy(&ipv6_prefixd);
+}
+
 struct buffer_info {
     struct ofpbuf ofpacts;
     struct dp_packet *p;
@@ -967,6 +1025,259 @@  pinctrl_handle_tcp_reset(struct rconn *swconn, const struct flow *ip_flow,
     dp_packet_uninit(&packet);
 }
 
+static void
+pinctrl_parse_dhcpv6_advt(struct rconn *swconn, const struct flow *ip_flow,
+                          struct dp_packet *pkt_in, const struct match *md,
+                          struct ofpbuf *userdata)
+{
+    struct udp_header *udp_in = dp_packet_l4(pkt_in);
+    size_t dlen = MIN(ntohs(udp_in->udp_len), dp_packet_l4_size(pkt_in));
+    unsigned char *in_dhcpv6_data = (unsigned char *)(udp_in + 1);
+    uint8_t *data, *end = (uint8_t *)udp_in + dlen;
+    int len = 0;
+
+    data = xmalloc(dlen);
+    /* skip DHCPv6 common header */
+    in_dhcpv6_data += 4;
+    while (in_dhcpv6_data < end) {
+        struct dhcpv6_opt_header *in_opt =
+             (struct dhcpv6_opt_header *)in_dhcpv6_data;
+        int opt_len = sizeof *in_opt + ntohs(in_opt->len);
+
+        if (dlen < opt_len + len) {
+            goto out;
+        }
+
+        switch (ntohs(in_opt->code)) {
+        case DHCPV6_OPT_IA_PD: {
+            int orig_len = len, hdr_len = 0, size = sizeof *in_opt + 12;
+
+            memcpy(&data[len], in_opt, size);
+            in_opt = (struct dhcpv6_opt_header *)(in_dhcpv6_data + size);
+            len += size;
+
+            while (size < opt_len) {
+                int flen = sizeof *in_opt + ntohs(in_opt->len);
+
+                if (dlen < flen + len) {
+                    goto out;
+                }
+
+                if (ntohs(in_opt->code) == DHCPV6_OPT_IA_PREFIX) {
+                    memcpy(&data[len], in_opt, flen);
+                    hdr_len += flen;
+                    len += flen;
+                }
+                if (ntohs(in_opt->code) == DHCPV6_OPT_STATUS_CODE) {
+                   struct dhcpv6_opt_status *status;
+
+                   status = (struct dhcpv6_opt_status *)in_opt;
+                   if (ntohs(status->status_code)) {
+                       goto out;
+                   }
+                }
+                size += flen;
+                in_opt = (struct dhcpv6_opt_header *)(in_dhcpv6_data + size);
+            }
+            in_opt = (struct dhcpv6_opt_header *)&data[orig_len];
+            in_opt->len = htons(hdr_len + 12);
+            break;
+        }
+        case DHCPV6_OPT_SERVER_ID_CODE:
+        case DHCPV6_OPT_CLIENT_ID_CODE:
+            memcpy(&data[len], in_opt, opt_len);
+            len += opt_len;
+            break;
+        default:
+            break;
+        }
+        in_dhcpv6_data += opt_len;
+    }
+
+    uint64_t packet_stub[256 / 8];
+    struct dp_packet packet;
+
+    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
+    eth_compose(&packet, ip_flow->dl_dst, ip_flow->dl_src, ETH_TYPE_IPV6,
+                IPV6_HEADER_LEN);
+
+    struct udp_header *udp_h = compose_ipv6(&packet, IPPROTO_UDP,
+                                            &ip_flow->ipv6_src,
+                                            &ip_flow->ipv6_dst, 0, 0, 255,
+                                            len + UDP_HEADER_LEN + 4);
+    udp_h->udp_len = htons(len + UDP_HEADER_LEN + 4);
+    udp_h->udp_csum = 0;
+    packet_set_udp_port(&packet, htons(546), htons(547));
+
+    unsigned char *dhcp_hdr = (unsigned char *)(udp_h + 1);
+    *dhcp_hdr = DHCPV6_MSG_TYPE_REQUEST;
+    memcpy(dhcp_hdr + 4, data, len);
+
+    uint32_t csum = packet_csum_pseudoheader6(dp_packet_l3(&packet));
+    csum = csum_continue(csum, udp_h, dp_packet_size(&packet) -
+                         ((const unsigned char *)udp_h -
+                          (const unsigned char *)dp_packet_eth(&packet)));
+    udp_h->udp_csum = csum_finish(csum);
+    if (!udp_h->udp_csum) {
+        udp_h->udp_csum = htons(0xffff);
+    }
+
+    if (ip_flow->vlans[0].tci & htons(VLAN_CFI)) {
+        eth_push_vlan(&packet, htons(ETH_TYPE_VLAN_8021Q),
+                      ip_flow->vlans[0].tci);
+    }
+
+    set_actions_and_enqueue_msg(swconn, &packet, md, userdata);
+    dp_packet_uninit(&packet);
+
+out:
+    free(data);
+}
+
+static struct ipv6_prefixd_state *
+pinctrl_find_prefixd_state(const struct flow *ip_flow, unsigned aid)
+{
+    struct shash_node *iter;
+
+    SHASH_FOR_EACH (iter, &ipv6_prefixd) {
+        struct ipv6_prefixd_state *pfd = iter->data;
+        if (IN6_ARE_ADDR_EQUAL(&pfd->ipv6_addr, &ip_flow->ipv6_dst) &&
+            eth_addr_equals(pfd->ea, ip_flow->dl_dst) &&
+            pfd->aid == aid) {
+            return pfd;
+        }
+    }
+    return NULL;
+}
+
+/* Called with in the pinctrl_handler thread context. */
+static void
+notify_pinctrl_main(void)
+{
+    seq_change(pinctrl_main_seq);
+}
+
+static void
+pinctrl_prefixd_state_handler(const struct flow *ip_flow,
+                              struct in6_addr addr, unsigned aid,
+                              char prefix_len, unsigned t1, unsigned t2,
+                              unsigned plife_time, unsigned vlife_time)
+{
+    struct ipv6_prefixd_state *pfd;
+
+    pfd = pinctrl_find_prefixd_state(ip_flow, aid);
+    if (pfd) {
+        pfd->state = PREFIX_PENDING;
+        pfd->plife_time = plife_time;
+        pfd->vlife_time = vlife_time;
+        pfd->plen = prefix_len;
+        pfd->prefix = addr;
+        pfd->t1 = t1;
+        pfd->t2 = t2;
+        notify_pinctrl_main();
+    }
+}
+
+static void
+pinctrl_parse_dhcpv6_reply(struct dp_packet *pkt_in,
+                           const struct flow *ip_flow)
+    OVS_REQUIRES(pinctrl_mutex)
+{
+    struct udp_header *udp_in = dp_packet_l4(pkt_in);
+    unsigned char *in_dhcpv6_data = (unsigned char *)(udp_in + 1);
+    size_t dlen = MIN(ntohs(udp_in->udp_len), dp_packet_l4_size(pkt_in));
+    unsigned t1 = 0, t2 = 0, vlife_time = 0, plife_time = 0;
+    uint8_t *end = (uint8_t *)udp_in + dlen;
+    uint8_t prefix_len = 0;
+    struct in6_addr ipv6;
+    bool status = false;
+    unsigned aid = 0;
+
+    memset(&ipv6, 0, sizeof (struct in6_addr));
+    /* skip DHCPv6 common header */
+    in_dhcpv6_data += 4;
+    while (in_dhcpv6_data < end) {
+        struct dhcpv6_opt_header *in_opt =
+             (struct dhcpv6_opt_header *)in_dhcpv6_data;
+        int opt_len = sizeof *in_opt + ntohs(in_opt->len);
+
+        if (in_dhcpv6_data + opt_len > end) {
+            break;
+        }
+
+        switch (ntohs(in_opt->code)) {
+        case DHCPV6_OPT_IA_PD: {
+            int size = sizeof *in_opt + 12;
+            in_opt = (struct dhcpv6_opt_header *)(in_dhcpv6_data + size);
+            struct dhcpv6_opt_ia_na *ia_na =
+                (struct dhcpv6_opt_ia_na *)in_dhcpv6_data;
+
+            aid = ntohl(ia_na->iaid);
+            t1 = ntohl(ia_na->t1);
+            t2 = ntohl(ia_na->t2);
+            if (t1 > t2 && t2 > 0) {
+                break;
+            }
+
+            while (size < opt_len) {
+                if (ntohs(in_opt->code) == DHCPV6_OPT_IA_PREFIX) {
+                    struct dhcpv6_opt_ia_prefix *ia_hdr =
+                        (struct dhcpv6_opt_ia_prefix *)(in_dhcpv6_data + size);
+
+                    prefix_len = ia_hdr->plen;
+                    plife_time = ntohl(ia_hdr->plife_time);
+                    vlife_time = ntohl(ia_hdr->vlife_time);
+                    memcpy(&ipv6, &ia_hdr->ipv6, sizeof (struct in6_addr));
+                }
+                if (ntohs(in_opt->code) == DHCPV6_OPT_STATUS_CODE) {
+                   struct dhcpv6_opt_status *status_hdr;
+
+                   status_hdr = (struct dhcpv6_opt_status *)in_opt;
+                   status = ntohs(status_hdr->status_code) == 0;
+                }
+                size += sizeof *in_opt + ntohs(in_opt->len);
+                in_opt = (struct dhcpv6_opt_header *)(in_dhcpv6_data + size);
+            }
+            break;
+        }
+        default:
+            break;
+        }
+        in_dhcpv6_data += opt_len;
+    }
+    if (status) {
+        pinctrl_prefixd_state_handler(ip_flow, ipv6, aid, prefix_len,
+                                      t1, t2, plife_time, vlife_time);
+    }
+}
+
+static void
+pinctrl_handle_dhcp6_server(struct rconn *swconn, const struct flow *ip_flow,
+                            struct dp_packet *pkt_in, const struct match *md,
+                            struct ofpbuf *userdata)
+{
+    if (ip_flow->dl_type != htons(ETH_TYPE_IPV6) ||
+        ip_flow->nw_proto != IPPROTO_UDP) {
+        return;
+    }
+
+    struct udp_header *udp_in = dp_packet_l4(pkt_in);
+    unsigned char *dhcp_hdr = (unsigned char *)(udp_in + 1);
+
+    switch (*dhcp_hdr) {
+    case DHCPV6_MSG_TYPE_ADVT:
+        pinctrl_parse_dhcpv6_advt(swconn, ip_flow, pkt_in, md, userdata);
+        break;
+    case DHCPV6_MSG_TYPE_REPLY:
+        ovs_mutex_lock(&pinctrl_mutex);
+        pinctrl_parse_dhcpv6_reply(pkt_in, ip_flow);
+        ovs_mutex_unlock(&pinctrl_mutex);
+        break;
+    default:
+        break;
+    }
+}
+
 /* Called with in the pinctrl_handler thread context. */
 static void
 pinctrl_handle_put_dhcp_opts(
@@ -1998,6 +2309,10 @@  process_packet_in(struct rconn *swconn, const struct ofp_header *msg)
         pinctrl_handle_bind_vport(&pin.flow_metadata.flow, &userdata);
         ovs_mutex_unlock(&pinctrl_mutex);
         break;
+    case ACTION_OPCODE_DHCP6_SERVER:
+        pinctrl_handle_dhcp6_server(swconn, &headers, &packet,
+                                    &pin.flow_metadata, &userdata);
+        break;
 
     case ACTION_OPCODE_HANDLE_SVC_CHECK:
         ovs_mutex_lock(&pinctrl_mutex);
@@ -2048,13 +2363,6 @@  notify_pinctrl_handler(void)
     seq_change(pinctrl_handler_seq);
 }
 
-/* Called with in the pinctrl_handler thread context. */
-static void
-notify_pinctrl_main(void)
-{
-    seq_change(pinctrl_main_seq);
-}
-
 /* pinctrl_handler pthread function. */
 static void *
 pinctrl_handler(void *arg_)
@@ -2076,6 +2384,7 @@  pinctrl_handler(void *arg_)
     /* Next multicast query (IGMP) in ms. */
     static long long int send_mcast_query_time = LLONG_MAX;
     static long long int svc_monitors_next_run_time = LLONG_MAX;
+    static long long int send_prefixd_time = LLONG_MAX;
 
     swconn = rconn_create(5, 0, DSCP_DEFAULT, 1 << OFP13_VERSION);
 
@@ -2129,6 +2438,7 @@  pinctrl_handler(void *arg_)
                 ovs_mutex_lock(&pinctrl_mutex);
                 send_garp_rarp_run(swconn, &send_garp_rarp_time);
                 send_ipv6_ras(swconn, &send_ipv6_ra_time);
+                send_ipv6_prefixd(swconn, &send_prefixd_time);
                 send_mac_binding_buffered_pkts(swconn);
                 ovs_mutex_unlock(&pinctrl_mutex);
 
@@ -2146,6 +2456,7 @@  pinctrl_handler(void *arg_)
         ipv6_ra_wait(send_ipv6_ra_time);
         ip_mcast_querier_wait(send_mcast_query_time);
         svc_monitors_wait(svc_monitors_next_run_time);
+        ipv6_prefixd_wait(send_prefixd_time);
 
         new_seq = seq_read(pinctrl_handler_seq);
         seq_wait(pinctrl_handler_seq, new_seq);
@@ -2162,6 +2473,7 @@  pinctrl_handler(void *arg_)
 /* Called by ovn-controller. */
 void
 pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
+            struct ovsdb_idl *ovnsb_idl,
             struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
             struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
             struct ovsdb_idl_index *sbrec_port_binding_by_key,
@@ -2197,6 +2509,9 @@  pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
                            sbrec_port_binding_by_name, br_int, chassis,
                            local_datapaths, active_tunnels);
     prepare_ipv6_ras(local_datapaths);
+    prepare_ipv6_prefixd(ovnsb_idl_txn, ovnsb_idl,
+                         sbrec_port_binding_by_name, local_datapaths,
+                         chassis, active_tunnels);
     sync_dns_cache(dns_table);
     controller_event_run(ovnsb_idl_txn, ce_table, chassis);
     ip_mcast_sync(ovnsb_idl_txn, chassis, local_datapaths,
@@ -2644,6 +2959,151 @@  send_ipv6_ras(struct rconn *swconn, long long int *send_ipv6_ra_time)
     }
 }
 
+static void
+compose_prefixd_solicit(struct dp_packet *b,
+                        const struct eth_addr eth_src,
+                        const struct eth_addr eth_dst,
+                        const struct in6_addr *ipv6_src,
+                        const struct in6_addr *ipv6_dst,
+                        const struct eth_addr cmac,
+                        unsigned aid)
+{
+    eth_compose(b, eth_dst, eth_src, ETH_TYPE_IPV6, IPV6_HEADER_LEN);
+
+    int len = UDP_HEADER_LEN + 4 + sizeof(struct dhcpv6_opt_server_id) +
+              sizeof(struct dhcpv6_opt_ia_na);
+    struct udp_header *udp_h = compose_ipv6(b, IPPROTO_UDP, ipv6_src,
+                                            ipv6_dst, 0, 0, 255, len);
+    udp_h->udp_len = htons(len);
+    udp_h->udp_csum = 0;
+    packet_set_udp_port(b, htons(546), htons(547));
+
+    unsigned char *dhcp_hdr = (unsigned char *)(udp_h + 1);
+    *dhcp_hdr = DHCPV6_MSG_TYPE_SOLICIT;
+
+    struct dhcpv6_opt_server_id *opt_client_id =
+        (struct dhcpv6_opt_server_id *)(dhcp_hdr + 4);
+    opt_client_id->opt.code = htons(DHCPV6_OPT_CLIENT_ID_CODE);
+    opt_client_id->opt.len = htons(sizeof(struct dhcpv6_opt_server_id) -
+                                   sizeof(struct dhcpv6_opt_header));
+    opt_client_id->duid_type = htons(DHCPV6_DUID_LL);
+    opt_client_id->hw_type = htons(DHCPV6_HW_TYPE_ETH);
+    opt_client_id->mac = cmac;
+
+    struct dhcpv6_opt_ia_na *ia_pd =
+            (struct dhcpv6_opt_ia_na *)(opt_client_id + 1);
+    ia_pd->opt.code = htons(DHCPV6_OPT_IA_PD);
+    ia_pd->opt.len = htons(sizeof(struct dhcpv6_opt_ia_na) -
+                           sizeof(struct dhcpv6_opt_header));
+    ia_pd->iaid = htonl(aid);
+    ia_pd->t1 = htonl(0xffffffff);
+    ia_pd->t2 = htonl(0xffffffff);
+
+    uint32_t csum = packet_csum_pseudoheader6(dp_packet_l3(b));
+    csum = csum_continue(csum, udp_h, dp_packet_size(b) -
+                         ((const unsigned char *)udp_h -
+                          (const unsigned char *)dp_packet_eth(b)));
+    udp_h->udp_csum = csum_finish(csum);
+    if (!udp_h->udp_csum) {
+        udp_h->udp_csum = htons(0xffff);
+    }
+}
+
+#define IPV6_PREFIXD_TIMEOUT    3000LL
+static long long int
+ipv6_prefixd_send(struct rconn *swconn, struct ipv6_prefixd_state *pfd)
+{
+    long long int cur_time = time_msec();
+    if (cur_time < pfd->next_announce) {
+        return pfd->next_announce;
+    }
+
+    uint64_t packet_stub[256 / 8];
+    struct dp_packet packet;
+
+    struct eth_addr eth_dst;
+    eth_dst = (struct eth_addr) ETH_ADDR_C(33,33,00,01,00,02);
+    struct in6_addr ipv6_dst;
+    ipv6_parse("ff02::1:2", &ipv6_dst);
+
+    dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
+    compose_prefixd_solicit(&packet, pfd->ea, eth_dst, &pfd->ipv6_addr,
+                            &ipv6_dst, pfd->cmac, pfd->aid);
+
+    uint64_t ofpacts_stub[4096 / 8];
+    struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
+
+    /* Set MFF_LOG_DATAPATH and MFF_LOG_INPORT. */
+    uint32_t dp_key = pfd->metadata;
+    uint32_t port_key = pfd->port_key;
+    put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts);
+    put_load(port_key, MFF_LOG_INPORT, 0, 32, &ofpacts);
+    put_load(1, MFF_LOG_FLAGS, MLF_LOCAL_ONLY_BIT, 1, &ofpacts);
+    struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
+    resubmit->in_port = OFPP_CONTROLLER;
+    resubmit->table_id = OFTABLE_LOG_INGRESS_PIPELINE;
+
+    struct ofputil_packet_out po = {
+        .packet = dp_packet_data(&packet),
+        .packet_len = dp_packet_size(&packet),
+        .buffer_id = UINT32_MAX,
+        .ofpacts = ofpacts.data,
+        .ofpacts_len = ofpacts.size,
+    };
+
+    match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
+    enum ofp_version version = rconn_get_version(swconn);
+    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
+    queue_msg(swconn, ofputil_encode_packet_out(&po, proto));
+    dp_packet_uninit(&packet);
+    ofpbuf_uninit(&ofpacts);
+    pfd->next_announce = cur_time + random_range(IPV6_PREFIXD_TIMEOUT);
+
+    return pfd->next_announce;
+}
+
+static bool ipv6_prefixd_should_inject(void)
+{
+    struct shash_node *iter;
+
+    SHASH_FOR_EACH (iter, &ipv6_prefixd) {
+        struct ipv6_prefixd_state *pfd = iter->data;
+        if (pfd->state == PREFIX_SOLICIT) {
+            return true;
+        }
+        if (pfd->state == PREFIX_DONE &&
+            pfd->next_announce < time_msec()) {
+            pfd->state = PREFIX_SOLICIT;
+            return true;
+        }
+    }
+    return false;
+}
+
+static void
+ipv6_prefixd_wait(long long int timeout)
+{
+    if (ipv6_prefixd_should_inject()) {
+        poll_timer_wait_until(timeout);
+    }
+}
+
+static void
+send_ipv6_prefixd(struct rconn *swconn, long long int *send_prefixd_time)
+    OVS_REQUIRES(pinctrl_mutex)
+{
+    struct shash_node *iter;
+
+    *send_prefixd_time = LLONG_MAX;
+    SHASH_FOR_EACH (iter, &ipv6_prefixd) {
+        struct ipv6_prefixd_state *pfd = iter->data;
+        long long int next_msg = ipv6_prefixd_send(swconn, pfd);
+        if (*send_prefixd_time > next_msg) {
+            *send_prefixd_time = next_msg;
+        }
+    }
+}
+
 /* Called by pinctrl_run(). Runs with in the main ovn-controller
  * thread context. */
 static void
@@ -2721,6 +3181,142 @@  prepare_ipv6_ras(const struct hmap *local_datapaths)
 
 }
 
+static bool
+fill_ipv6_prefix_state(struct ovsdb_idl_txn *ovnsb_idl_txn,
+                       struct ovsdb_idl *ovnsb_idl,
+                       const struct hmap *local_datapaths,
+                       struct eth_addr ea, struct in6_addr ipv6_addr,
+                       int64_t tunnel_key, int64_t dp_tunnel_key)
+    OVS_REQUIRES(pinctrl_mutex)
+{
+    const struct local_datapath *ld;
+    bool changed = false;
+
+    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
+        const struct sbrec_port_binding *pb, *pb_next;
+        SBREC_PORT_BINDING_FOR_EACH_SAFE (pb, pb_next, ovnsb_idl) {
+            if (!smap_get_bool(&pb->options, "ipv6_prefix", false)) {
+                continue;
+            }
+
+            struct lport_addresses c_addrs;
+            int i;
+            for (i = 0; i < pb->n_mac; i++) {
+                if (extract_lsp_addresses(pb->mac[i], &c_addrs)) {
+                        break;
+                }
+            }
+
+            struct ipv6_prefixd_state *pfd = shash_find_data(
+                    &ipv6_prefixd, pb->logical_port);
+            if (!pfd) {
+                pfd = xzalloc(sizeof *pfd);
+                pfd->ipv6_addr = ipv6_addr;
+                pfd->ea = ea;
+                pfd->cmac = c_addrs.ea;
+                pfd->aid = random_range(0xfffffff);
+                pfd->metadata = dp_tunnel_key;
+                pfd->port_key = tunnel_key;
+                shash_add(&ipv6_prefixd, pb->logical_port, pfd);
+                pfd->next_announce = time_msec() +
+                                     random_range(IPV6_PREFIXD_TIMEOUT);
+                changed = true;
+            } else if (pfd->state == PREFIX_PENDING && ovnsb_idl_txn) {
+                char prefix_str[INET6_ADDRSTRLEN + 1] = {};
+                struct smap options;
+
+                pfd->state = PREFIX_DONE;
+                pfd->next_announce = time_msec() + pfd->t1 * 1000;
+                ipv6_string_mapped(prefix_str, &pfd->prefix);
+                smap_clone(&options, &pb->options);
+                smap_add_format(&options, "ipv6_ra_pd_list", "%s/%d",
+                                prefix_str, pfd->plen);
+                sbrec_port_binding_set_options(pb, &options);
+                smap_destroy(&options);
+            }
+        }
+    }
+
+    return changed;
+}
+
+static void
+prepare_ipv6_prefixd(struct ovsdb_idl_txn *ovnsb_idl_txn,
+                     struct ovsdb_idl *ovnsb_idl,
+                     struct ovsdb_idl_index *sbrec_port_binding_by_name,
+                     const struct hmap *local_datapaths,
+                     const struct sbrec_chassis *chassis,
+                     const struct sset *active_tunnels)
+    OVS_REQUIRES(pinctrl_mutex)
+{
+    const struct local_datapath *ld;
+    bool changed = false;
+    int i;
+
+    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
+        const struct sbrec_port_binding *pb, *pb_next;
+        SBREC_PORT_BINDING_FOR_EACH_SAFE (pb, pb_next, ovnsb_idl) {
+            if (!smap_get_bool(&pb->options, "ipv6_prefix_delegation",
+                               false)) {
+                continue;
+            }
+
+            const char *peer_s = smap_get(&pb->options, "peer");
+            if (!peer_s) {
+                continue;
+            }
+
+            const struct sbrec_port_binding *peer
+                = lport_lookup_by_name(sbrec_port_binding_by_name, peer_s);
+            if (!peer) {
+                continue;
+            }
+
+            char *redirect_name = xasprintf("cr-%s", pb->logical_port);
+            bool resident = lport_is_chassis_resident(
+                    sbrec_port_binding_by_name, chassis, active_tunnels,
+                    redirect_name);
+            free(redirect_name);
+            if (!resident && strcmp(pb->type, "l3gateway")) {
+                continue;
+            }
+
+            struct in6_addr ip6_addr;
+            struct eth_addr ea;
+            for (i = 0; i < pb->n_mac; i++) {
+                struct lport_addresses laddrs;
+
+                if (!extract_lsp_addresses(pb->mac[i], &laddrs)) {
+                    continue;
+                }
+
+                ea = laddrs.ea;
+                if (laddrs.n_ipv6_addrs > 0) {
+                    ip6_addr = laddrs.ipv6_addrs[0].addr;
+                    break;
+                }
+            }
+
+            if (eth_addr_is_zero(ea)) {
+                continue;
+            }
+
+            if (i == pb->n_mac) {
+                in6_generate_lla(ea, &ip6_addr);
+            }
+
+            changed |= fill_ipv6_prefix_state(ovnsb_idl_txn, ovnsb_idl,
+                                              local_datapaths, ea, ip6_addr,
+                                              peer->tunnel_key,
+                                              peer->datapath->tunnel_key);
+        }
+    }
+
+    if (changed) {
+        notify_pinctrl_handler();
+    }
+}
+
 /* Called by pinctrl_run(). Runs with in the main ovn-controller
  * thread context. */
 void
@@ -2743,6 +3339,7 @@  pinctrl_destroy(void)
     free(pinctrl.br_int_name);
     destroy_send_garps_rarps();
     destroy_ipv6_ras();
+    destroy_ipv6_prefixd();
     destroy_buffered_packets_map();
     event_table_destroy();
     destroy_put_mac_bindings();
@@ -4240,6 +4837,7 @@  may_inject_pkts(void)
 {
     return (!shash_is_empty(&ipv6_ras) ||
             !shash_is_empty(&send_garp_rarp_data) ||
+            ipv6_prefixd_should_inject() ||
             !ovs_list_is_empty(&mcast_query_list) ||
             !ovs_list_is_empty(&buffered_mac_bindings));
 }
diff --git a/controller/pinctrl.h b/controller/pinctrl.h
index 8fa4baae9..a34749f02 100644
--- a/controller/pinctrl.h
+++ b/controller/pinctrl.h
@@ -26,6 +26,7 @@  struct hmap;
 struct lport_index;
 struct ovsdb_idl_index;
 struct ovsdb_idl_txn;
+struct ovsdb_idl;
 struct ovsrec_bridge;
 struct sbrec_chassis;
 struct sbrec_dns_table;
@@ -34,6 +35,7 @@  struct sbrec_service_monitor_table;
 
 void pinctrl_init(void);
 void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
+                 struct ovsdb_idl *ovnsb_idl,
                  struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
                  struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
                  struct ovsdb_idl_index *sbrec_port_binding_by_key,
diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index 047a8d737..c3d719985 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -89,7 +89,8 @@  struct ovn_extend_table;
     OVNACT(CHECK_PKT_LARGER,  ovnact_check_pkt_larger) \
     OVNACT(TRIGGER_EVENT,     ovnact_controller_event) \
     OVNACT(BIND_VPORT,        ovnact_bind_vport)       \
-    OVNACT(HANDLE_SVC_CHECK,  ovnact_handle_svc_check)
+    OVNACT(HANDLE_SVC_CHECK,  ovnact_handle_svc_check) \
+    OVNACT(DHCP6_REPLY,       ovnact_nest)
 
 /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
 enum OVS_PACKED_ENUM ovnact_type {
@@ -552,6 +553,11 @@  enum action_opcode {
      *     MFF_LOG_INPORT = port
      */
     ACTION_OPCODE_HANDLE_SVC_CHECK,
+    /* handle_dhcpv6_reply { ...actions ...}."
+     *
+     *  The actions, in OpenFlow 1.3 format, follow the action_header.
+     */
+    ACTION_OPCODE_DHCP6_SERVER,
 };
 
 /* Header. */
diff --git a/lib/actions.c b/lib/actions.c
index 051e6c875..b7bd219e4 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -2172,6 +2172,26 @@  ovnact_put_opts_free(struct ovnact_put_opts *pdo)
     free_gen_options(pdo->options, pdo->n_options);
 }
 
+static void
+parse_DHCP6_REPLY(struct action_context *ctx)
+{
+    parse_nested_action(ctx, OVNACT_DHCP6_REPLY, "ip6");
+}
+
+static void
+format_DHCP6_REPLY(const struct ovnact_nest *nest, struct ds *s)
+{
+    format_nested_action(nest, "handle_dhcpv6_reply", s);
+}
+
+static void
+encode_DHCP6_REPLY(const struct ovnact_nest *on,
+                   const struct ovnact_encode_params *ep,
+                   struct ofpbuf *ofpacts)
+{
+    encode_nested_actions(on, ep, ACTION_OPCODE_DHCP6_SERVER, ofpacts);
+}
+
 static void
 parse_SET_QUEUE(struct action_context *ctx)
 {
@@ -2973,6 +2993,8 @@  parse_action(struct action_context *ctx)
         parse_bind_vport(ctx);
     } else if (lexer_match_id(ctx->lexer, "handle_svc_check")) {
         parse_handle_svc_check(ctx);
+    } else if (lexer_match_id(ctx->lexer, "handle_dhcpv6_reply")) {
+        parse_DHCP6_REPLY(ctx);
     } else {
         lexer_syntax_error(ctx->lexer, "expecting action");
     }
diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
index 375b77014..2987277a9 100644
--- a/lib/ovn-l7.h
+++ b/lib/ovn-l7.h
@@ -174,8 +174,11 @@  struct dhcp_opt6_header {
 #define DHCPV6_OPT_SERVER_ID_CODE        2
 #define DHCPV6_OPT_IA_NA_CODE            3
 #define DHCPV6_OPT_IA_ADDR_CODE          5
+#define DHCPV6_OPT_STATUS_CODE           13
 #define DHCPV6_OPT_DNS_SERVER_CODE       23
 #define DHCPV6_OPT_DOMAIN_SEARCH_CODE    24
+#define DHCPV6_OPT_IA_PD                 25
+#define DHCPV6_OPT_IA_PREFIX             26
 
 #define DHCPV6_OPT_SERVER_ID \
     DHCP_OPTION("server_id", DHCPV6_OPT_SERVER_ID_CODE, "mac")
@@ -254,6 +257,22 @@  struct ovs_nd_route_info {
 };
 BUILD_ASSERT_DECL(ND_ROUTE_INFO_OPT_LEN == sizeof(struct ovs_nd_route_info));
 
+OVS_PACKED(
+struct dhcpv6_opt_ia_prefix {
+    struct dhcpv6_opt_header opt;
+    ovs_be32 plife_time;
+    ovs_be32 vlife_time;
+    uint8_t plen;
+    struct in6_addr ipv6;
+});
+
+OVS_PACKED(
+struct dhcpv6_opt_status {
+    struct dhcpv6_opt_header opt;
+    ovs_be16 status_code;
+    uint8_t msg[];
+});
+
 #define DHCPV6_DUID_LL      3
 #define DHCPV6_HW_TYPE_ETH  1
 
diff --git a/ovn-sb.xml b/ovn-sb.xml
index 82167c488..14fe249ec 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -2114,6 +2114,14 @@  tcp.flags = RST;
 
           <p><b>Example:</b> <code>handle_svc_check(inport);</code></p>
         </dd>
+
+        <dt><code>handle_dhcpv6_reply;</code></dt>
+        <dd>
+          <p>
+            This action is used to parse DHCPv6 replies from IPv6
+            Delegation Router and managed IPv6 Prefix delegation state machine
+          </p>
+        </dd>
       </dl>
     </column>
 
diff --git a/tests/ovn.at b/tests/ovn.at
index 411b76804..9aa30d435 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -1481,6 +1481,12 @@  handle_svc_check();
 handle_svc_check(reg0);
     Cannot use numeric field reg0 where string field is required.
 
+# prefix delegation
+handle_dhcpv6_reply{};
+    formats as handle_dhcpv6_reply { drop; };
+    encodes as controller(userdata=00.00.00.13.00.00.00.00)
+    has prereqs ip6
+
 # Miscellaneous negative tests.
 ;
     Syntax error at `;'.
diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
index 264543876..3b132a2eb 100644
--- a/utilities/ovn-trace.c
+++ b/utilities/ovn-trace.c
@@ -2224,6 +2224,9 @@  trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
 
         case OVNACT_HANDLE_SVC_CHECK:
             break;
+
+        case OVNACT_DHCP6_REPLY:
+            break;
         }
     }
     ds_destroy(&s);