diff mbox series

[ovs-dev] introduce rdnss, dnssl and route_info opt in put_nd_ra_opts action

Message ID 29b75921d858bbd5372ccb33a80879f4ebfee6b8.1643738069.git.lorenzo.bianconi@redhat.com
State Superseded
Headers show
Series [ovs-dev] introduce rdnss, dnssl and route_info opt in put_nd_ra_opts action | 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

Lorenzo Bianconi Feb. 1, 2022, 5:59 p.m. UTC
Send non-periodic Router Advertisement with rdnss, dnssl and route_info
options if configured by the user.

Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=1851788
Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
---
 controller/pinctrl.c  |  37 ++---------
 include/ovn/actions.h |   1 +
 lib/actions.c         | 144 ++++++++++++++++++++++++++++++++++++++++++
 lib/ovn-l7.h          |   3 +
 northd/northd.c       |  16 +++++
 tests/ovn.at          |  30 +++++++--
 6 files changed, 197 insertions(+), 34 deletions(-)

Comments

Mark Michelson Feb. 4, 2022, 2:28 p.m. UTC | #1
Hi Lorenzo, the change is mostly good but I have a few findings down below:

On 2/1/22 12:59, Lorenzo Bianconi wrote:
> Send non-periodic Router Advertisement with rdnss, dnssl and route_info
> options if configured by the user.
> 
> Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=1851788
> Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
> ---
>   controller/pinctrl.c  |  37 ++---------
>   include/ovn/actions.h |   1 +
>   lib/actions.c         | 144 ++++++++++++++++++++++++++++++++++++++++++
>   lib/ovn-l7.h          |   3 +
>   northd/northd.c       |  16 +++++
>   tests/ovn.at          |  30 +++++++--
>   6 files changed, 197 insertions(+), 34 deletions(-)
> 
> diff --git a/controller/pinctrl.c b/controller/pinctrl.c
> index d2bb7f441..247d53d8c 100644
> --- a/controller/pinctrl.c
> +++ b/controller/pinctrl.c
> @@ -3705,38 +3705,17 @@ static void
>   packet_put_ra_dnssl_opt(struct dp_packet *b, ovs_be32 lifetime,
>                           char *dnssl_data)
>   {
> -    char *dnssl_list, *t0, *r0 = NULL, dnssl[255] = {};
>       size_t prev_l4_size = dp_packet_l4_size(b);
>       size_t size = sizeof(struct ovs_nd_dnssl);
> -    int i = 0;
> +    char dnssl[255] = {};
> +    int dnssl_size;
>   
> -    dnssl_list = xstrdup(dnssl_data);
> -
> -    /* Multiple DNS Search List must be 'comma' separated
> -     * (e.g. "a.b.c, d.e.f"). Domain names must be encoded
> -     * as described in Section 3.1 of RFC1035.
> -     * (e.g if dns list is a.b.c,www.ovn.org, it will be encoded as:
> -     * 01 61 01 62 01 63 00 03 77 77 77 03 6f 76 63 03 6f 72 67 00
> -     */
> -    for (t0 = strtok_r(dnssl_list, ",", &r0); t0;
> -         t0 = strtok_r(NULL, ",", &r0)) {
> -        char *t1, *r1 = NULL;
> -
> -        size += strlen(t0) + 2;
> -        if (size > sizeof(dnssl)) {
> -            goto out;
> -        }
> -
> -        for (t1 = strtok_r(t0, ".", &r1); t1;
> -             t1 = strtok_r(NULL, ".", &r1)) {
> -            dnssl[i++] = strlen(t1);
> -            memcpy(&dnssl[i], t1, strlen(t1));
> -            i += strlen(t1);
> -        }
> -        dnssl[i++] = 0;
> +    dnssl_size = encode_ra_dnssl_opt(dnssl_data, dnssl, sizeof(dnssl));

Should you pass a copy of dnssl_data instead of passing dnssl_data 
directly? encode_ra_dnssl_opt uses strtok_r, which will modify 
dnssl_data. Since dnssl_data is a pointer to ra->config->dnssl, wouldn't 
that mean that DNSSL packets will be encoded wrongly for every one sent 
after the first?

> +    if (dnssl_size < 0) {
> +        return;
>       }
> -    size = ROUND_UP(size, 8);
>   
> +    size += dnssl_size;

Note: The size calculation is a bit different now, but it actually works 
out OK since sizeof(ovs_nd_dnssl) == 8.

It used to be:
1. Add sizeof(ovs_nd_dnssl) to calculated dnssl packet size
2. Round result up to nearest 8 bytes.

Now it's:
1. Calculate dnssl packet size
2. Round that up to nearest 8 bytes
3. Add sizeof(ovs_nd_dnssl).

If sizeof(ovs_nd_dnssl) weren't a multiple of 8 this would result in a 
different calculation.

I think it may be worth adding a comment so that some eagle-eyed person 
in the future doesn't try to "correct" this or report it as a bug.

>       struct ip6_hdr *nh = dp_packet_l3(b);
>       nh->ip6_plen = htons(prev_l4_size + size);
>   
> @@ -3746,15 +3725,13 @@ packet_put_ra_dnssl_opt(struct dp_packet *b, ovs_be32 lifetime,
>       nd_dnssl->reserved = 0;
>       put_16aligned_be32(&nd_dnssl->lifetime, lifetime);
>   
> -    dp_packet_put(b, dnssl, size - sizeof *nd_dnssl);
> +    dp_packet_put(b, dnssl, dnssl_size);
>   
>       struct ovs_ra_msg *ra = dp_packet_l4(b);
>       ra->icmph.icmp6_cksum = 0;
>       uint32_t icmp_csum = packet_csum_pseudoheader6(dp_packet_l3(b));
>       ra->icmph.icmp6_cksum = csum_finish(csum_continue(icmp_csum, ra,
>                                                         prev_l4_size + size));
> -out:
> -    free(dnssl_list);
>   }
>   
>   static void
> diff --git a/include/ovn/actions.h b/include/ovn/actions.h
> index cdef5fb03..0641b927e 100644
> --- a/include/ovn/actions.h
> +++ b/include/ovn/actions.h
> @@ -807,5 +807,6 @@ void ovnacts_encode(const struct ovnact[], size_t ovnacts_len,
>   
>   void ovnacts_free(struct ovnact[], size_t ovnacts_len);
>   char *ovnact_op_to_string(uint32_t);
> +int encode_ra_dnssl_opt(char *data, char *buf, int buf_len);
>   
>   #endif /* ovn/actions.h */
> diff --git a/lib/actions.c b/lib/actions.c
> index d5d8391bb..0d3f78704 100644
> --- a/lib/actions.c
> +++ b/lib/actions.c
> @@ -40,6 +40,7 @@
>   #include "simap.h"
>   #include "uuid.h"
>   #include "socket-util.h"
> +#include "lib/ovn-util.h"
>   
>   VLOG_DEFINE_THIS_MODULE(actions);
>   
> @@ -2992,6 +2993,15 @@ parse_put_nd_ra_opts(struct action_context *ctx, const struct expr_field *dst,
>           case ND_OPT_MTU:
>               ok = c->format == LEX_F_DECIMAL;
>               break;
> +
> +        case ND_OPT_RDNSS:
> +            ok = c->format == LEX_F_IPV6;
> +            break;
> +
> +        case ND_OPT_ROUTE_INFO_TYPE:
> +        case ND_OPT_DNSSL:
> +            ok = !!c->string;
> +            break;
>           }
>   
>           if (!ok) {
> @@ -3022,6 +3032,98 @@ format_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po,
>       format_put_opts("put_nd_ra_opts", po, s);
>   }
>   
> +int encode_ra_dnssl_opt(char *data, char *buf, int buf_len)
> +{
> +    char *t0, *r0 = NULL;
> +    size_t size = 0;
> +    int i = 0;
> +
> +    /* Multiple DNS Search List must be 'comma' separated
> +     * (e.g. "a.b.c, d.e.f"). Domain names must be encoded
> +     * as described in Section 3.1 of RFC1035.
> +     * (e.g if dns list is a.b.c,www.ovn.org, it will be encoded as:
> +     * 01 61 01 62 01 63 00 03 77 77 77 03 6f 76 63 03 6f 72 67 00
> +     */
> +    for (t0 = strtok_r(data, ",", &r0); t0;
> +         t0 = strtok_r(NULL, ",", &r0)) {
> +        char *t1, *r1 = NULL;
> +
> +        size += strlen(t0) + 2;
> +        if (size > buf_len) {
> +            return -EINVAL;
> +        }
> +
> +        for (t1 = strtok_r(t0, ".", &r1); t1;
> +             t1 = strtok_r(NULL, ".", &r1)) {
> +            buf[i++] = strlen(t1);

I realize that this is not something new that you have introduced, but 
should there be a check to ensure that strlen(t1) is not greater than 
UINT8_MAX?

> +            memcpy(&buf[i], t1, strlen(t1));
> +            i += strlen(t1);
> +        }
> +        buf[i++] = 0;
> +    }
> +
> +    return ROUND_UP(size, 8);
> +}
> +
> +static void
> +encode_ra_route_info_opt(struct ofpbuf *ofpacts, char *route_data)
> +{
> +    char *t0, *r0 = NULL;
> +
> +    for (t0 = strtok_r(route_data, ",", &r0); t0;
> +         t0 = strtok_r(NULL, ",", &r0)) {
> +        struct ovs_nd_route_info nd_rinfo;
> +        char *t1, *r1 = NULL;
> +        int index;
> +
> +        for (t1 = strtok_r(t0, "-", &r1), index = 0; t1;
> +             t1 = strtok_r(NULL, "-", &r1), index++) {
> +
> +            nd_rinfo.type = ND_OPT_ROUTE_INFO_TYPE;
> +            nd_rinfo.route_lifetime = htonl(0xffffffff);
> +
> +            switch (index) {
> +            case 0:
> +                if (!strcmp(t1, "HIGH")) {
> +                    nd_rinfo.flags = IPV6_ND_RA_OPT_PRF_HIGH;
> +                } else if (!strcmp(t1, "LOW")) {
> +                    nd_rinfo.flags = IPV6_ND_RA_OPT_PRF_LOW;
> +                } else {
> +                    nd_rinfo.flags = IPV6_ND_RA_OPT_PRF_NORMAL;
> +                }
> +                break;
> +            case 1: {
> +                struct lport_addresses route;
> +                uint8_t plen;
> +
> +                if (!extract_ip_addresses(t1, &route)) {
> +                    break;
> +                }
> +                if (!route.n_ipv6_addrs) {
> +                    destroy_lport_addresses(&route);
> +                    break;
> +                }
> +
> +                nd_rinfo.prefix_len = route.ipv6_addrs->plen;
> +                plen = DIV_ROUND_UP(nd_rinfo.prefix_len, 64);
> +                nd_rinfo.len = 1 + plen;
> +                struct ovs_nd_route_info *rinfo_opt =
> +                    ofpbuf_put_uninit(ofpacts, sizeof *rinfo_opt);
> +                memcpy(rinfo_opt, &nd_rinfo, sizeof *rinfo_opt);
> +                void *data = ofpbuf_put_uninit(ofpacts, plen * 8);
> +                memcpy(data, &route.ipv6_addrs->network, plen * 8);
> +
> +                destroy_lport_addresses(&route);
> +                index = 0;
> +                break;
> +            }
> +            default:
> +                break;
> +            }
> +        }
> +    }
> +}
> +
>   static void
>   encode_put_nd_ra_option(const struct ovnact_gen_option *o,
>                           struct ofpbuf *ofpacts, ptrdiff_t ra_offset)
> @@ -3096,6 +3198,48 @@ encode_put_nd_ra_option(const struct ovnact_gen_option *o,
>                  sizeof(ovs_be32[4]));
>           break;
>       }
> +
> +    case ND_OPT_RDNSS:
> +    {
> +        struct nd_rdnss_opt *rdnss_opt =
> +            ofpbuf_put_uninit(ofpacts, sizeof *rdnss_opt);
> +        rdnss_opt->type = ND_OPT_RDNSS;
> +        rdnss_opt->len = 3;
> +        rdnss_opt->reserved = htons(0);
> +        put_16aligned_be32(&rdnss_opt->lifetime, htonl(0xffffffff));
> +        void *dns = ofpbuf_put_uninit(ofpacts, sizeof(ovs_be32[4]));
> +        memcpy(dns, &c->value.be128[7].be32, sizeof(ovs_be32[4]));
> +        break;
> +    }
> +
> +    case ND_OPT_DNSSL:
> +    {
> +        size_t size = sizeof(struct ovs_nd_dnssl);
> +        char dnssl[255] = {};
> +        int dnssl_size;
> +
> +        dnssl_size = encode_ra_dnssl_opt(c->string, dnssl, sizeof(dnssl));

Like in pinctrl, I think a copy of c->string should be passed to 
encode_ra_dnssl_opt(). Seeing that c is of const type, it seems like it 
is not intended for its contents to be modified.

> +        if (dnssl_size < 0) {
> +            break;
> +        }
> +
> +        size += dnssl_size;
> +        struct ovs_nd_dnssl *nd_dnssl =
> +            ofpbuf_put_uninit(ofpacts, sizeof *nd_dnssl);
> +        nd_dnssl->type = ND_OPT_DNSSL;
> +        nd_dnssl->len = size / 8;
> +        nd_dnssl->reserved = 0;
> +        put_16aligned_be32(&nd_dnssl->lifetime, htonl(0xffffffff));
> +        void *p = ofpbuf_put_uninit(ofpacts, dnssl_size);
> +        memcpy(p, dnssl, dnssl_size);
> +        break;
> +    }
> +
> +    case ND_OPT_ROUTE_INFO_TYPE:
> +    {
> +        encode_ra_route_info_opt(ofpacts, c->string);

Pass a copy of c->string to encode_ra_route_info_opt()

> +        break;
> +    }
>       }
>   }
>   
> diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
> index 256e963d9..49ecea81f 100644
> --- a/lib/ovn-l7.h
> +++ b/lib/ovn-l7.h
> @@ -409,6 +409,9 @@ nd_ra_opts_init(struct hmap *nd_ra_opts)
>       nd_ra_opt_add(nd_ra_opts, "slla", ND_OPT_SOURCE_LINKADDR, "mac");
>       nd_ra_opt_add(nd_ra_opts, "prefix", ND_OPT_PREFIX_INFORMATION, "ipv6");
>       nd_ra_opt_add(nd_ra_opts, "mtu", ND_OPT_MTU, "uint32");
> +    nd_ra_opt_add(nd_ra_opts, "rdnss", ND_OPT_RDNSS, "ipv6");
> +    nd_ra_opt_add(nd_ra_opts, "dnssl", ND_OPT_DNSSL, "str");
> +    nd_ra_opt_add(nd_ra_opts, "route_info", ND_OPT_ROUTE_INFO_TYPE, "str");
>   }
>   
>   #define EMPTY_LB_VIP           1
> diff --git a/northd/northd.c b/northd/northd.c
> index fc7a64f99..861812259 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -10855,6 +10855,22 @@ build_ND_RA_flows_for_lrouter_port(
>           ds_put_format(actions, ", router_preference = \"%s\"", prf);
>       }
>   
> +    const char *ra_rdnss = smap_get(&op->nbrp->ipv6_ra_configs, "rdnss");
> +    if (ra_rdnss) {
> +        ds_put_format(actions, ", rdnss = %s", ra_rdnss);
> +    }
> +
> +    const char *ra_dnssl = smap_get(&op->nbrp->ipv6_ra_configs, "dnssl");
> +    if (ra_dnssl) {
> +        ds_put_format(actions, ", dnssl = \"%s\"", ra_dnssl);
> +    }
> +
> +    const char *route_info = smap_get(&op->nbrp->ipv6_ra_configs,
> +                                      "route_info");
> +    if (route_info) {
> +        ds_put_format(actions, ", route_info = \"%s\"", route_info);
> +    }
> +
>       bool add_rs_response_flow = false;
>   
>       for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 957eb7850..33685109f 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -11902,9 +11902,10 @@ options:rxq_pcap=${pcap_file}-rx.pcap
>   OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
>   
>   # This shell function sends a Router Solicitation packet.
> -# test_ipv6_ra INPORT SRC_MAC SRC_LLA ADDR_MODE MTU RA_PREFIX_OPT
> +# test_ipv6_ra INPORT SRC_MAC SRC_LLA ADDR_MODE MTU RA_PREFIX_OPT RDNSS DNSSL ROUTE_INFO
>   test_ipv6_ra() {
>       local inport=$1 src_mac=$2 src_lla=$3 addr_mode=$4 mtu=$5 prefix_opt=$6
> +    local rdnss=$7 dnssl=$8 route_info=$9
>       local request=333300000002${src_mac}86dd6000000000103aff${src_lla}ff02000000000000000000000000000285000efc000000000101${src_mac}
>   
>       local len=24
> @@ -11914,6 +11915,18 @@ test_ipv6_ra() {
>           mtu_opt=05010000${mtu}
>       fi
>   
> +    if test ${#rdnss} != 0; then
> +        len=`expr $len + ${#rdnss} / 2`
> +    fi
> +
> +    if test ${#dnssl} != 0; then
> +        len=`expr $len + ${#dnssl} / 2`
> +    fi
> +
> +    if test ${#route_info} != 0; then
> +        len=`expr $len + ${#route_info} / 2`
> +    fi
> +
>       if test ${#prefix_opt} != 0; then
>           prefix_opt=${prefix_opt}fdad1234567800000000000000000000
>           len=`expr $len + ${#prefix_opt} / 2`
> @@ -11922,7 +11935,7 @@ test_ipv6_ra() {
>       len=$(printf "%x" $len)
>       local lrp_mac=fa163e000001
>       local lrp_lla=fe80000000000000f8163efffe000001
> -    local reply=${src_mac}${lrp_mac}86dd6000000000${len}3aff${lrp_lla}${src_lla}8600XXXXff${addr_mode}ffff00000000000000000101${lrp_mac}${mtu_opt}${prefix_opt}
> +    local reply=${src_mac}${lrp_mac}86dd6000000000${len}3aff${lrp_lla}${src_lla}8600XXXXff${addr_mode}ffff00000000000000000101${lrp_mac}${mtu_opt}${rdnss}${dnssl}${route_info}${prefix_opt}
>       echo $reply >> $inport.expected
>   
>       as hv1 ovs-appctl netdev-dummy/receive hv1-vif${inport} $request
> @@ -11960,6 +11973,9 @@ reset_pcap_file hv1-vif3 hv1/vif3
>   ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:mtu=1500
>   ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:send_periodic="false"
>   ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:router_preference="LOW"
> +ovn-nbctl --wait=hv set Logical_Router_port lrp0 ipv6_ra_configs:rdnss=1000::11
> +ovn-nbctl --wait=hv set Logical_Router_port lrp0 ipv6_ra_configs:dnssl=aa.bb.cc
> +ovn-nbctl --wait=hv set Logical_Router_port lrp0 ipv6_ra_configs:route_info=HIGH-1001::11/48,LOW-1002::11/96
>   
>   # Make sure that ovn-controller has installed the corresponding OF Flow.
>   OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
> @@ -11970,8 +11986,11 @@ default_prefix_option_config=030440c0ffffffffffffffff00000000
>   src_mac=fa163e000003
>   src_lla=fe80000000000000f8163efffe000003
>   mtu=000005dc
> +rdnss=19030000ffffffff10000000000000000000000000000011
> +dnssl=1f030000ffffffff02616102626202636300000000000000
> +route_info=18023008ffffffff100100000000000018036018ffffffff10020000000000000000000000000000
>   
> -test_ipv6_ra 2 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
> +test_ipv6_ra 2 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config $rdnss $dnssl $route_info
>   
>   # NXT_RESUME should be 2.
>   OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> @@ -11993,6 +12012,8 @@ reset_pcap_file hv1-vif3 hv1/vif3
>   # Set the address mode to dhcpv6_stateful, router_preference to HIGH
>   ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateful
>   ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:router_preference="HIGH"
> +ovn-nbctl --wait=hv remove Logical_Router_Port lrp0 ipv6_ra_configs rdnss
> +ovn-nbctl --wait=hv remove Logical_Router_Port lrp0 ipv6_ra_configs route_info
>   # Make sure that ovn-controller has installed the corresponding OF Flow.
>   OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
>   
> @@ -12003,7 +12024,7 @@ src_mac=fa163e000004
>   src_lla=fe80000000000000f8163efffe000004
>   mtu=000005dc
>   
> -test_ipv6_ra 3 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
> +test_ipv6_ra 3 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config "" $dnssl
>   
>   # NXT_RESUME should be 3.
>   OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> @@ -12025,6 +12046,7 @@ reset_pcap_file hv1-vif3 hv1/vif3
>   # Set the address mode to dhcpv6_stateless, reset router preference to default
>   ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateless
>   ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:router_preference="MEDIUM"
> +ovn-nbctl --wait=hv remove Logical_Router_Port lrp0 ipv6_ra_configs dnssl
>   # Make sure that ovn-controller has installed the corresponding OF Flow.
>   OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
>   
>
Lorenzo Bianconi Feb. 4, 2022, 10:44 p.m. UTC | #2
> Hi Lorenzo, the change is mostly good but I have a few findings down below:

Hi Mark,

thx for the review.

> 
> On 2/1/22 12:59, Lorenzo Bianconi wrote:
> > Send non-periodic Router Advertisement with rdnss, dnssl and route_info
> > options if configured by the user.
> > 
> > Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=1851788
> > Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
> > ---
> >   controller/pinctrl.c  |  37 ++---------
> >   include/ovn/actions.h |   1 +
> >   lib/actions.c         | 144 ++++++++++++++++++++++++++++++++++++++++++
> >   lib/ovn-l7.h          |   3 +
> >   northd/northd.c       |  16 +++++
> >   tests/ovn.at          |  30 +++++++--
> >   6 files changed, 197 insertions(+), 34 deletions(-)
> > 
> > diff --git a/controller/pinctrl.c b/controller/pinctrl.c
> > index d2bb7f441..247d53d8c 100644
> > --- a/controller/pinctrl.c
> > +++ b/controller/pinctrl.c
> > @@ -3705,38 +3705,17 @@ static void
> >   packet_put_ra_dnssl_opt(struct dp_packet *b, ovs_be32 lifetime,
> >                           char *dnssl_data)
> >   {
> > -    char *dnssl_list, *t0, *r0 = NULL, dnssl[255] = {};
> >       size_t prev_l4_size = dp_packet_l4_size(b);
> >       size_t size = sizeof(struct ovs_nd_dnssl);
> > -    int i = 0;
> > +    char dnssl[255] = {};
> > +    int dnssl_size;
> > -    dnssl_list = xstrdup(dnssl_data);
> > -
> > -    /* Multiple DNS Search List must be 'comma' separated
> > -     * (e.g. "a.b.c, d.e.f"). Domain names must be encoded
> > -     * as described in Section 3.1 of RFC1035.
> > -     * (e.g if dns list is a.b.c,www.ovn.org, it will be encoded as:
> > -     * 01 61 01 62 01 63 00 03 77 77 77 03 6f 76 63 03 6f 72 67 00
> > -     */
> > -    for (t0 = strtok_r(dnssl_list, ",", &r0); t0;
> > -         t0 = strtok_r(NULL, ",", &r0)) {
> > -        char *t1, *r1 = NULL;
> > -
> > -        size += strlen(t0) + 2;
> > -        if (size > sizeof(dnssl)) {
> > -            goto out;
> > -        }
> > -
> > -        for (t1 = strtok_r(t0, ".", &r1); t1;
> > -             t1 = strtok_r(NULL, ".", &r1)) {
> > -            dnssl[i++] = strlen(t1);
> > -            memcpy(&dnssl[i], t1, strlen(t1));
> > -            i += strlen(t1);
> > -        }
> > -        dnssl[i++] = 0;
> > +    dnssl_size = encode_ra_dnssl_opt(dnssl_data, dnssl, sizeof(dnssl));
> 
> Should you pass a copy of dnssl_data instead of passing dnssl_data directly?
> encode_ra_dnssl_opt uses strtok_r, which will modify dnssl_data. Since
> dnssl_data is a pointer to ra->config->dnssl, wouldn't that mean that DNSSL
> packets will be encoded wrongly for every one sent after the first?

ack, right. I will fix it in v2.

> 
> > +    if (dnssl_size < 0) {
> > +        return;
> >       }
> > -    size = ROUND_UP(size, 8);
> > +    size += dnssl_size;
> 
> Note: The size calculation is a bit different now, but it actually works out
> OK since sizeof(ovs_nd_dnssl) == 8.
> 
> It used to be:
> 1. Add sizeof(ovs_nd_dnssl) to calculated dnssl packet size
> 2. Round result up to nearest 8 bytes.
> 
> Now it's:
> 1. Calculate dnssl packet size
> 2. Round that up to nearest 8 bytes
> 3. Add sizeof(ovs_nd_dnssl).
> 
> If sizeof(ovs_nd_dnssl) weren't a multiple of 8 this would result in a
> different calculation.
> 
> I think it may be worth adding a comment so that some eagle-eyed person in
> the future doesn't try to "correct" this or report it as a bug.

I guess we can sum sizeof(ovs_nd_dnssl) in encode_ra_dnssl_opt.

> 
> >       struct ip6_hdr *nh = dp_packet_l3(b);
> >       nh->ip6_plen = htons(prev_l4_size + size);
> > @@ -3746,15 +3725,13 @@ packet_put_ra_dnssl_opt(struct dp_packet *b, ovs_be32 lifetime,
> >       nd_dnssl->reserved = 0;
> >       put_16aligned_be32(&nd_dnssl->lifetime, lifetime);
> > -    dp_packet_put(b, dnssl, size - sizeof *nd_dnssl);
> > +    dp_packet_put(b, dnssl, dnssl_size);
> >       struct ovs_ra_msg *ra = dp_packet_l4(b);
> >       ra->icmph.icmp6_cksum = 0;
> >       uint32_t icmp_csum = packet_csum_pseudoheader6(dp_packet_l3(b));
> >       ra->icmph.icmp6_cksum = csum_finish(csum_continue(icmp_csum, ra,
> >                                                         prev_l4_size + size));
> > -out:
> > -    free(dnssl_list);
> >   }
> >   static void
> > diff --git a/include/ovn/actions.h b/include/ovn/actions.h
> > index cdef5fb03..0641b927e 100644
> > --- a/include/ovn/actions.h
> > +++ b/include/ovn/actions.h
> > @@ -807,5 +807,6 @@ void ovnacts_encode(const struct ovnact[], size_t ovnacts_len,
> >   void ovnacts_free(struct ovnact[], size_t ovnacts_len);
> >   char *ovnact_op_to_string(uint32_t);
> > +int encode_ra_dnssl_opt(char *data, char *buf, int buf_len);
> >   #endif /* ovn/actions.h */
> > diff --git a/lib/actions.c b/lib/actions.c
> > index d5d8391bb..0d3f78704 100644
> > --- a/lib/actions.c
> > +++ b/lib/actions.c
> > @@ -40,6 +40,7 @@
> >   #include "simap.h"
> >   #include "uuid.h"
> >   #include "socket-util.h"
> > +#include "lib/ovn-util.h"
> >   VLOG_DEFINE_THIS_MODULE(actions);
> >   
> > @@ -2992,6 +2993,15 @@ parse_put_nd_ra_opts(struct action_context *ctx, const struct expr_field *dst,
> >           case ND_OPT_MTU:
> >               ok = c->format == LEX_F_DECIMAL;
> >               break;
> > +
> > +        case ND_OPT_RDNSS:
> > +            ok = c->format == LEX_F_IPV6;
> > +            break;
> > +
> > +        case ND_OPT_ROUTE_INFO_TYPE:
> > +        case ND_OPT_DNSSL:
> > +            ok = !!c->string;
> > +            break;
> >           }
> >           if (!ok) {
> > @@ -3022,6 +3032,98 @@ format_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po,
> >       format_put_opts("put_nd_ra_opts", po, s);
> >   }
> > +int encode_ra_dnssl_opt(char *data, char *buf, int buf_len)
> > +{
> > +    char *t0, *r0 = NULL;
> > +    size_t size = 0;
> > +    int i = 0;
> > +
> > +    /* Multiple DNS Search List must be 'comma' separated
> > +     * (e.g. "a.b.c, d.e.f"). Domain names must be encoded
> > +     * as described in Section 3.1 of RFC1035.
> > +     * (e.g if dns list is a.b.c,www.ovn.org, it will be encoded as:
> > +     * 01 61 01 62 01 63 00 03 77 77 77 03 6f 76 63 03 6f 72 67 00
> > +     */
> > +    for (t0 = strtok_r(data, ",", &r0); t0;
> > +         t0 = strtok_r(NULL, ",", &r0)) {
> > +        char *t1, *r1 = NULL;
> > +
> > +        size += strlen(t0) + 2;
> > +        if (size > buf_len) {
> > +            return -EINVAL;
> > +        }
> > +
> > +        for (t1 = strtok_r(t0, ".", &r1); t1;
> > +             t1 = strtok_r(NULL, ".", &r1)) {
> > +            buf[i++] = strlen(t1);
> 
> I realize that this is not something new that you have introduced, but
> should there be a check to ensure that strlen(t1) is not greater than
> UINT8_MAX?

ack, fine. I will add it in v2.

> 
> > +            memcpy(&buf[i], t1, strlen(t1));
> > +            i += strlen(t1);
> > +        }
> > +        buf[i++] = 0;
> > +    }
> > +
> > +    return ROUND_UP(size, 8);
> > +}
> > +
> > +static void
> > +encode_ra_route_info_opt(struct ofpbuf *ofpacts, char *route_data)
> > +{
> > +    char *t0, *r0 = NULL;
> > +
> > +    for (t0 = strtok_r(route_data, ",", &r0); t0;
> > +         t0 = strtok_r(NULL, ",", &r0)) {
> > +        struct ovs_nd_route_info nd_rinfo;
> > +        char *t1, *r1 = NULL;
> > +        int index;
> > +
> > +        for (t1 = strtok_r(t0, "-", &r1), index = 0; t1;
> > +             t1 = strtok_r(NULL, "-", &r1), index++) {
> > +
> > +            nd_rinfo.type = ND_OPT_ROUTE_INFO_TYPE;
> > +            nd_rinfo.route_lifetime = htonl(0xffffffff);
> > +
> > +            switch (index) {
> > +            case 0:
> > +                if (!strcmp(t1, "HIGH")) {
> > +                    nd_rinfo.flags = IPV6_ND_RA_OPT_PRF_HIGH;
> > +                } else if (!strcmp(t1, "LOW")) {
> > +                    nd_rinfo.flags = IPV6_ND_RA_OPT_PRF_LOW;
> > +                } else {
> > +                    nd_rinfo.flags = IPV6_ND_RA_OPT_PRF_NORMAL;
> > +                }
> > +                break;
> > +            case 1: {
> > +                struct lport_addresses route;
> > +                uint8_t plen;
> > +
> > +                if (!extract_ip_addresses(t1, &route)) {
> > +                    break;
> > +                }
> > +                if (!route.n_ipv6_addrs) {
> > +                    destroy_lport_addresses(&route);
> > +                    break;
> > +                }
> > +
> > +                nd_rinfo.prefix_len = route.ipv6_addrs->plen;
> > +                plen = DIV_ROUND_UP(nd_rinfo.prefix_len, 64);
> > +                nd_rinfo.len = 1 + plen;
> > +                struct ovs_nd_route_info *rinfo_opt =
> > +                    ofpbuf_put_uninit(ofpacts, sizeof *rinfo_opt);
> > +                memcpy(rinfo_opt, &nd_rinfo, sizeof *rinfo_opt);
> > +                void *data = ofpbuf_put_uninit(ofpacts, plen * 8);
> > +                memcpy(data, &route.ipv6_addrs->network, plen * 8);
> > +
> > +                destroy_lport_addresses(&route);
> > +                index = 0;
> > +                break;
> > +            }
> > +            default:
> > +                break;
> > +            }
> > +        }
> > +    }
> > +}
> > +
> >   static void
> >   encode_put_nd_ra_option(const struct ovnact_gen_option *o,
> >                           struct ofpbuf *ofpacts, ptrdiff_t ra_offset)
> > @@ -3096,6 +3198,48 @@ encode_put_nd_ra_option(const struct ovnact_gen_option *o,
> >                  sizeof(ovs_be32[4]));
> >           break;
> >       }
> > +
> > +    case ND_OPT_RDNSS:
> > +    {
> > +        struct nd_rdnss_opt *rdnss_opt =
> > +            ofpbuf_put_uninit(ofpacts, sizeof *rdnss_opt);
> > +        rdnss_opt->type = ND_OPT_RDNSS;
> > +        rdnss_opt->len = 3;
> > +        rdnss_opt->reserved = htons(0);
> > +        put_16aligned_be32(&rdnss_opt->lifetime, htonl(0xffffffff));
> > +        void *dns = ofpbuf_put_uninit(ofpacts, sizeof(ovs_be32[4]));
> > +        memcpy(dns, &c->value.be128[7].be32, sizeof(ovs_be32[4]));
> > +        break;
> > +    }
> > +
> > +    case ND_OPT_DNSSL:
> > +    {
> > +        size_t size = sizeof(struct ovs_nd_dnssl);
> > +        char dnssl[255] = {};
> > +        int dnssl_size;
> > +
> > +        dnssl_size = encode_ra_dnssl_opt(c->string, dnssl, sizeof(dnssl));
> 
> Like in pinctrl, I think a copy of c->string should be passed to
> encode_ra_dnssl_opt(). Seeing that c is of const type, it seems like it is
> not intended for its contents to be modified.

ack, I will fix it in v2.
> 
> > +        if (dnssl_size < 0) {
> > +            break;
> > +        }
> > +
> > +        size += dnssl_size;
> > +        struct ovs_nd_dnssl *nd_dnssl =
> > +            ofpbuf_put_uninit(ofpacts, sizeof *nd_dnssl);
> > +        nd_dnssl->type = ND_OPT_DNSSL;
> > +        nd_dnssl->len = size / 8;
> > +        nd_dnssl->reserved = 0;
> > +        put_16aligned_be32(&nd_dnssl->lifetime, htonl(0xffffffff));
> > +        void *p = ofpbuf_put_uninit(ofpacts, dnssl_size);
> > +        memcpy(p, dnssl, dnssl_size);
> > +        break;
> > +    }
> > +
> > +    case ND_OPT_ROUTE_INFO_TYPE:
> > +    {
> > +        encode_ra_route_info_opt(ofpacts, c->string);
> 
> Pass a copy of c->string to encode_ra_route_info_opt()
ack, I will fix it in v2.

Regards,
Lorenzo

> 
> > +        break;
> > +    }
> >       }
> >   }
> > diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
> > index 256e963d9..49ecea81f 100644
> > --- a/lib/ovn-l7.h
> > +++ b/lib/ovn-l7.h
> > @@ -409,6 +409,9 @@ nd_ra_opts_init(struct hmap *nd_ra_opts)
> >       nd_ra_opt_add(nd_ra_opts, "slla", ND_OPT_SOURCE_LINKADDR, "mac");
> >       nd_ra_opt_add(nd_ra_opts, "prefix", ND_OPT_PREFIX_INFORMATION, "ipv6");
> >       nd_ra_opt_add(nd_ra_opts, "mtu", ND_OPT_MTU, "uint32");
> > +    nd_ra_opt_add(nd_ra_opts, "rdnss", ND_OPT_RDNSS, "ipv6");
> > +    nd_ra_opt_add(nd_ra_opts, "dnssl", ND_OPT_DNSSL, "str");
> > +    nd_ra_opt_add(nd_ra_opts, "route_info", ND_OPT_ROUTE_INFO_TYPE, "str");
> >   }
> >   #define EMPTY_LB_VIP           1
> > diff --git a/northd/northd.c b/northd/northd.c
> > index fc7a64f99..861812259 100644
> > --- a/northd/northd.c
> > +++ b/northd/northd.c
> > @@ -10855,6 +10855,22 @@ build_ND_RA_flows_for_lrouter_port(
> >           ds_put_format(actions, ", router_preference = \"%s\"", prf);
> >       }
> > +    const char *ra_rdnss = smap_get(&op->nbrp->ipv6_ra_configs, "rdnss");
> > +    if (ra_rdnss) {
> > +        ds_put_format(actions, ", rdnss = %s", ra_rdnss);
> > +    }
> > +
> > +    const char *ra_dnssl = smap_get(&op->nbrp->ipv6_ra_configs, "dnssl");
> > +    if (ra_dnssl) {
> > +        ds_put_format(actions, ", dnssl = \"%s\"", ra_dnssl);
> > +    }
> > +
> > +    const char *route_info = smap_get(&op->nbrp->ipv6_ra_configs,
> > +                                      "route_info");
> > +    if (route_info) {
> > +        ds_put_format(actions, ", route_info = \"%s\"", route_info);
> > +    }
> > +
> >       bool add_rs_response_flow = false;
> >       for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
> > diff --git a/tests/ovn.at b/tests/ovn.at
> > index 957eb7850..33685109f 100644
> > --- a/tests/ovn.at
> > +++ b/tests/ovn.at
> > @@ -11902,9 +11902,10 @@ options:rxq_pcap=${pcap_file}-rx.pcap
> >   OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
> >   # This shell function sends a Router Solicitation packet.
> > -# test_ipv6_ra INPORT SRC_MAC SRC_LLA ADDR_MODE MTU RA_PREFIX_OPT
> > +# test_ipv6_ra INPORT SRC_MAC SRC_LLA ADDR_MODE MTU RA_PREFIX_OPT RDNSS DNSSL ROUTE_INFO
> >   test_ipv6_ra() {
> >       local inport=$1 src_mac=$2 src_lla=$3 addr_mode=$4 mtu=$5 prefix_opt=$6
> > +    local rdnss=$7 dnssl=$8 route_info=$9
> >       local request=333300000002${src_mac}86dd6000000000103aff${src_lla}ff02000000000000000000000000000285000efc000000000101${src_mac}
> >       local len=24
> > @@ -11914,6 +11915,18 @@ test_ipv6_ra() {
> >           mtu_opt=05010000${mtu}
> >       fi
> > +    if test ${#rdnss} != 0; then
> > +        len=`expr $len + ${#rdnss} / 2`
> > +    fi
> > +
> > +    if test ${#dnssl} != 0; then
> > +        len=`expr $len + ${#dnssl} / 2`
> > +    fi
> > +
> > +    if test ${#route_info} != 0; then
> > +        len=`expr $len + ${#route_info} / 2`
> > +    fi
> > +
> >       if test ${#prefix_opt} != 0; then
> >           prefix_opt=${prefix_opt}fdad1234567800000000000000000000
> >           len=`expr $len + ${#prefix_opt} / 2`
> > @@ -11922,7 +11935,7 @@ test_ipv6_ra() {
> >       len=$(printf "%x" $len)
> >       local lrp_mac=fa163e000001
> >       local lrp_lla=fe80000000000000f8163efffe000001
> > -    local reply=${src_mac}${lrp_mac}86dd6000000000${len}3aff${lrp_lla}${src_lla}8600XXXXff${addr_mode}ffff00000000000000000101${lrp_mac}${mtu_opt}${prefix_opt}
> > +    local reply=${src_mac}${lrp_mac}86dd6000000000${len}3aff${lrp_lla}${src_lla}8600XXXXff${addr_mode}ffff00000000000000000101${lrp_mac}${mtu_opt}${rdnss}${dnssl}${route_info}${prefix_opt}
> >       echo $reply >> $inport.expected
> >       as hv1 ovs-appctl netdev-dummy/receive hv1-vif${inport} $request
> > @@ -11960,6 +11973,9 @@ reset_pcap_file hv1-vif3 hv1/vif3
> >   ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:mtu=1500
> >   ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:send_periodic="false"
> >   ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:router_preference="LOW"
> > +ovn-nbctl --wait=hv set Logical_Router_port lrp0 ipv6_ra_configs:rdnss=1000::11
> > +ovn-nbctl --wait=hv set Logical_Router_port lrp0 ipv6_ra_configs:dnssl=aa.bb.cc
> > +ovn-nbctl --wait=hv set Logical_Router_port lrp0 ipv6_ra_configs:route_info=HIGH-1001::11/48,LOW-1002::11/96
> >   # Make sure that ovn-controller has installed the corresponding OF Flow.
> >   OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
> > @@ -11970,8 +11986,11 @@ default_prefix_option_config=030440c0ffffffffffffffff00000000
> >   src_mac=fa163e000003
> >   src_lla=fe80000000000000f8163efffe000003
> >   mtu=000005dc
> > +rdnss=19030000ffffffff10000000000000000000000000000011
> > +dnssl=1f030000ffffffff02616102626202636300000000000000
> > +route_info=18023008ffffffff100100000000000018036018ffffffff10020000000000000000000000000000
> > -test_ipv6_ra 2 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
> > +test_ipv6_ra 2 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config $rdnss $dnssl $route_info
> >   # NXT_RESUME should be 2.
> >   OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > @@ -11993,6 +12012,8 @@ reset_pcap_file hv1-vif3 hv1/vif3
> >   # Set the address mode to dhcpv6_stateful, router_preference to HIGH
> >   ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateful
> >   ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:router_preference="HIGH"
> > +ovn-nbctl --wait=hv remove Logical_Router_Port lrp0 ipv6_ra_configs rdnss
> > +ovn-nbctl --wait=hv remove Logical_Router_Port lrp0 ipv6_ra_configs route_info
> >   # Make sure that ovn-controller has installed the corresponding OF Flow.
> >   OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
> > @@ -12003,7 +12024,7 @@ src_mac=fa163e000004
> >   src_lla=fe80000000000000f8163efffe000004
> >   mtu=000005dc
> > -test_ipv6_ra 3 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
> > +test_ipv6_ra 3 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config "" $dnssl
> >   # NXT_RESUME should be 3.
> >   OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> > @@ -12025,6 +12046,7 @@ reset_pcap_file hv1-vif3 hv1/vif3
> >   # Set the address mode to dhcpv6_stateless, reset router preference to default
> >   ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateless
> >   ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:router_preference="MEDIUM"
> > +ovn-nbctl --wait=hv remove Logical_Router_Port lrp0 ipv6_ra_configs dnssl
> >   # Make sure that ovn-controller has installed the corresponding OF Flow.
> >   OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
> > 
>
diff mbox series

Patch

diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index d2bb7f441..247d53d8c 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -3705,38 +3705,17 @@  static void
 packet_put_ra_dnssl_opt(struct dp_packet *b, ovs_be32 lifetime,
                         char *dnssl_data)
 {
-    char *dnssl_list, *t0, *r0 = NULL, dnssl[255] = {};
     size_t prev_l4_size = dp_packet_l4_size(b);
     size_t size = sizeof(struct ovs_nd_dnssl);
-    int i = 0;
+    char dnssl[255] = {};
+    int dnssl_size;
 
-    dnssl_list = xstrdup(dnssl_data);
-
-    /* Multiple DNS Search List must be 'comma' separated
-     * (e.g. "a.b.c, d.e.f"). Domain names must be encoded
-     * as described in Section 3.1 of RFC1035.
-     * (e.g if dns list is a.b.c,www.ovn.org, it will be encoded as:
-     * 01 61 01 62 01 63 00 03 77 77 77 03 6f 76 63 03 6f 72 67 00
-     */
-    for (t0 = strtok_r(dnssl_list, ",", &r0); t0;
-         t0 = strtok_r(NULL, ",", &r0)) {
-        char *t1, *r1 = NULL;
-
-        size += strlen(t0) + 2;
-        if (size > sizeof(dnssl)) {
-            goto out;
-        }
-
-        for (t1 = strtok_r(t0, ".", &r1); t1;
-             t1 = strtok_r(NULL, ".", &r1)) {
-            dnssl[i++] = strlen(t1);
-            memcpy(&dnssl[i], t1, strlen(t1));
-            i += strlen(t1);
-        }
-        dnssl[i++] = 0;
+    dnssl_size = encode_ra_dnssl_opt(dnssl_data, dnssl, sizeof(dnssl));
+    if (dnssl_size < 0) {
+        return;
     }
-    size = ROUND_UP(size, 8);
 
+    size += dnssl_size;
     struct ip6_hdr *nh = dp_packet_l3(b);
     nh->ip6_plen = htons(prev_l4_size + size);
 
@@ -3746,15 +3725,13 @@  packet_put_ra_dnssl_opt(struct dp_packet *b, ovs_be32 lifetime,
     nd_dnssl->reserved = 0;
     put_16aligned_be32(&nd_dnssl->lifetime, lifetime);
 
-    dp_packet_put(b, dnssl, size - sizeof *nd_dnssl);
+    dp_packet_put(b, dnssl, dnssl_size);
 
     struct ovs_ra_msg *ra = dp_packet_l4(b);
     ra->icmph.icmp6_cksum = 0;
     uint32_t icmp_csum = packet_csum_pseudoheader6(dp_packet_l3(b));
     ra->icmph.icmp6_cksum = csum_finish(csum_continue(icmp_csum, ra,
                                                       prev_l4_size + size));
-out:
-    free(dnssl_list);
 }
 
 static void
diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index cdef5fb03..0641b927e 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -807,5 +807,6 @@  void ovnacts_encode(const struct ovnact[], size_t ovnacts_len,
 
 void ovnacts_free(struct ovnact[], size_t ovnacts_len);
 char *ovnact_op_to_string(uint32_t);
+int encode_ra_dnssl_opt(char *data, char *buf, int buf_len);
 
 #endif /* ovn/actions.h */
diff --git a/lib/actions.c b/lib/actions.c
index d5d8391bb..0d3f78704 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -40,6 +40,7 @@ 
 #include "simap.h"
 #include "uuid.h"
 #include "socket-util.h"
+#include "lib/ovn-util.h"
 
 VLOG_DEFINE_THIS_MODULE(actions);
 
@@ -2992,6 +2993,15 @@  parse_put_nd_ra_opts(struct action_context *ctx, const struct expr_field *dst,
         case ND_OPT_MTU:
             ok = c->format == LEX_F_DECIMAL;
             break;
+
+        case ND_OPT_RDNSS:
+            ok = c->format == LEX_F_IPV6;
+            break;
+
+        case ND_OPT_ROUTE_INFO_TYPE:
+        case ND_OPT_DNSSL:
+            ok = !!c->string;
+            break;
         }
 
         if (!ok) {
@@ -3022,6 +3032,98 @@  format_PUT_ND_RA_OPTS(const struct ovnact_put_opts *po,
     format_put_opts("put_nd_ra_opts", po, s);
 }
 
+int encode_ra_dnssl_opt(char *data, char *buf, int buf_len)
+{
+    char *t0, *r0 = NULL;
+    size_t size = 0;
+    int i = 0;
+
+    /* Multiple DNS Search List must be 'comma' separated
+     * (e.g. "a.b.c, d.e.f"). Domain names must be encoded
+     * as described in Section 3.1 of RFC1035.
+     * (e.g if dns list is a.b.c,www.ovn.org, it will be encoded as:
+     * 01 61 01 62 01 63 00 03 77 77 77 03 6f 76 63 03 6f 72 67 00
+     */
+    for (t0 = strtok_r(data, ",", &r0); t0;
+         t0 = strtok_r(NULL, ",", &r0)) {
+        char *t1, *r1 = NULL;
+
+        size += strlen(t0) + 2;
+        if (size > buf_len) {
+            return -EINVAL;
+        }
+
+        for (t1 = strtok_r(t0, ".", &r1); t1;
+             t1 = strtok_r(NULL, ".", &r1)) {
+            buf[i++] = strlen(t1);
+            memcpy(&buf[i], t1, strlen(t1));
+            i += strlen(t1);
+        }
+        buf[i++] = 0;
+    }
+
+    return ROUND_UP(size, 8);
+}
+
+static void
+encode_ra_route_info_opt(struct ofpbuf *ofpacts, char *route_data)
+{
+    char *t0, *r0 = NULL;
+
+    for (t0 = strtok_r(route_data, ",", &r0); t0;
+         t0 = strtok_r(NULL, ",", &r0)) {
+        struct ovs_nd_route_info nd_rinfo;
+        char *t1, *r1 = NULL;
+        int index;
+
+        for (t1 = strtok_r(t0, "-", &r1), index = 0; t1;
+             t1 = strtok_r(NULL, "-", &r1), index++) {
+
+            nd_rinfo.type = ND_OPT_ROUTE_INFO_TYPE;
+            nd_rinfo.route_lifetime = htonl(0xffffffff);
+
+            switch (index) {
+            case 0:
+                if (!strcmp(t1, "HIGH")) {
+                    nd_rinfo.flags = IPV6_ND_RA_OPT_PRF_HIGH;
+                } else if (!strcmp(t1, "LOW")) {
+                    nd_rinfo.flags = IPV6_ND_RA_OPT_PRF_LOW;
+                } else {
+                    nd_rinfo.flags = IPV6_ND_RA_OPT_PRF_NORMAL;
+                }
+                break;
+            case 1: {
+                struct lport_addresses route;
+                uint8_t plen;
+
+                if (!extract_ip_addresses(t1, &route)) {
+                    break;
+                }
+                if (!route.n_ipv6_addrs) {
+                    destroy_lport_addresses(&route);
+                    break;
+                }
+
+                nd_rinfo.prefix_len = route.ipv6_addrs->plen;
+                plen = DIV_ROUND_UP(nd_rinfo.prefix_len, 64);
+                nd_rinfo.len = 1 + plen;
+                struct ovs_nd_route_info *rinfo_opt =
+                    ofpbuf_put_uninit(ofpacts, sizeof *rinfo_opt);
+                memcpy(rinfo_opt, &nd_rinfo, sizeof *rinfo_opt);
+                void *data = ofpbuf_put_uninit(ofpacts, plen * 8);
+                memcpy(data, &route.ipv6_addrs->network, plen * 8);
+
+                destroy_lport_addresses(&route);
+                index = 0;
+                break;
+            }
+            default:
+                break;
+            }
+        }
+    }
+}
+
 static void
 encode_put_nd_ra_option(const struct ovnact_gen_option *o,
                         struct ofpbuf *ofpacts, ptrdiff_t ra_offset)
@@ -3096,6 +3198,48 @@  encode_put_nd_ra_option(const struct ovnact_gen_option *o,
                sizeof(ovs_be32[4]));
         break;
     }
+
+    case ND_OPT_RDNSS:
+    {
+        struct nd_rdnss_opt *rdnss_opt =
+            ofpbuf_put_uninit(ofpacts, sizeof *rdnss_opt);
+        rdnss_opt->type = ND_OPT_RDNSS;
+        rdnss_opt->len = 3;
+        rdnss_opt->reserved = htons(0);
+        put_16aligned_be32(&rdnss_opt->lifetime, htonl(0xffffffff));
+        void *dns = ofpbuf_put_uninit(ofpacts, sizeof(ovs_be32[4]));
+        memcpy(dns, &c->value.be128[7].be32, sizeof(ovs_be32[4]));
+        break;
+    }
+
+    case ND_OPT_DNSSL:
+    {
+        size_t size = sizeof(struct ovs_nd_dnssl);
+        char dnssl[255] = {};
+        int dnssl_size;
+
+        dnssl_size = encode_ra_dnssl_opt(c->string, dnssl, sizeof(dnssl));
+        if (dnssl_size < 0) {
+            break;
+        }
+
+        size += dnssl_size;
+        struct ovs_nd_dnssl *nd_dnssl =
+            ofpbuf_put_uninit(ofpacts, sizeof *nd_dnssl);
+        nd_dnssl->type = ND_OPT_DNSSL;
+        nd_dnssl->len = size / 8;
+        nd_dnssl->reserved = 0;
+        put_16aligned_be32(&nd_dnssl->lifetime, htonl(0xffffffff));
+        void *p = ofpbuf_put_uninit(ofpacts, dnssl_size);
+        memcpy(p, dnssl, dnssl_size);
+        break;
+    }
+
+    case ND_OPT_ROUTE_INFO_TYPE:
+    {
+        encode_ra_route_info_opt(ofpacts, c->string);
+        break;
+    }
     }
 }
 
diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
index 256e963d9..49ecea81f 100644
--- a/lib/ovn-l7.h
+++ b/lib/ovn-l7.h
@@ -409,6 +409,9 @@  nd_ra_opts_init(struct hmap *nd_ra_opts)
     nd_ra_opt_add(nd_ra_opts, "slla", ND_OPT_SOURCE_LINKADDR, "mac");
     nd_ra_opt_add(nd_ra_opts, "prefix", ND_OPT_PREFIX_INFORMATION, "ipv6");
     nd_ra_opt_add(nd_ra_opts, "mtu", ND_OPT_MTU, "uint32");
+    nd_ra_opt_add(nd_ra_opts, "rdnss", ND_OPT_RDNSS, "ipv6");
+    nd_ra_opt_add(nd_ra_opts, "dnssl", ND_OPT_DNSSL, "str");
+    nd_ra_opt_add(nd_ra_opts, "route_info", ND_OPT_ROUTE_INFO_TYPE, "str");
 }
 
 #define EMPTY_LB_VIP           1
diff --git a/northd/northd.c b/northd/northd.c
index fc7a64f99..861812259 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -10855,6 +10855,22 @@  build_ND_RA_flows_for_lrouter_port(
         ds_put_format(actions, ", router_preference = \"%s\"", prf);
     }
 
+    const char *ra_rdnss = smap_get(&op->nbrp->ipv6_ra_configs, "rdnss");
+    if (ra_rdnss) {
+        ds_put_format(actions, ", rdnss = %s", ra_rdnss);
+    }
+
+    const char *ra_dnssl = smap_get(&op->nbrp->ipv6_ra_configs, "dnssl");
+    if (ra_dnssl) {
+        ds_put_format(actions, ", dnssl = \"%s\"", ra_dnssl);
+    }
+
+    const char *route_info = smap_get(&op->nbrp->ipv6_ra_configs,
+                                      "route_info");
+    if (route_info) {
+        ds_put_format(actions, ", route_info = \"%s\"", route_info);
+    }
+
     bool add_rs_response_flow = false;
 
     for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
diff --git a/tests/ovn.at b/tests/ovn.at
index 957eb7850..33685109f 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -11902,9 +11902,10 @@  options:rxq_pcap=${pcap_file}-rx.pcap
 OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
 
 # This shell function sends a Router Solicitation packet.
-# test_ipv6_ra INPORT SRC_MAC SRC_LLA ADDR_MODE MTU RA_PREFIX_OPT
+# test_ipv6_ra INPORT SRC_MAC SRC_LLA ADDR_MODE MTU RA_PREFIX_OPT RDNSS DNSSL ROUTE_INFO
 test_ipv6_ra() {
     local inport=$1 src_mac=$2 src_lla=$3 addr_mode=$4 mtu=$5 prefix_opt=$6
+    local rdnss=$7 dnssl=$8 route_info=$9
     local request=333300000002${src_mac}86dd6000000000103aff${src_lla}ff02000000000000000000000000000285000efc000000000101${src_mac}
 
     local len=24
@@ -11914,6 +11915,18 @@  test_ipv6_ra() {
         mtu_opt=05010000${mtu}
     fi
 
+    if test ${#rdnss} != 0; then
+        len=`expr $len + ${#rdnss} / 2`
+    fi
+
+    if test ${#dnssl} != 0; then
+        len=`expr $len + ${#dnssl} / 2`
+    fi
+
+    if test ${#route_info} != 0; then
+        len=`expr $len + ${#route_info} / 2`
+    fi
+
     if test ${#prefix_opt} != 0; then
         prefix_opt=${prefix_opt}fdad1234567800000000000000000000
         len=`expr $len + ${#prefix_opt} / 2`
@@ -11922,7 +11935,7 @@  test_ipv6_ra() {
     len=$(printf "%x" $len)
     local lrp_mac=fa163e000001
     local lrp_lla=fe80000000000000f8163efffe000001
-    local reply=${src_mac}${lrp_mac}86dd6000000000${len}3aff${lrp_lla}${src_lla}8600XXXXff${addr_mode}ffff00000000000000000101${lrp_mac}${mtu_opt}${prefix_opt}
+    local reply=${src_mac}${lrp_mac}86dd6000000000${len}3aff${lrp_lla}${src_lla}8600XXXXff${addr_mode}ffff00000000000000000101${lrp_mac}${mtu_opt}${rdnss}${dnssl}${route_info}${prefix_opt}
     echo $reply >> $inport.expected
 
     as hv1 ovs-appctl netdev-dummy/receive hv1-vif${inport} $request
@@ -11960,6 +11973,9 @@  reset_pcap_file hv1-vif3 hv1/vif3
 ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:mtu=1500
 ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:send_periodic="false"
 ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:router_preference="LOW"
+ovn-nbctl --wait=hv set Logical_Router_port lrp0 ipv6_ra_configs:rdnss=1000::11
+ovn-nbctl --wait=hv set Logical_Router_port lrp0 ipv6_ra_configs:dnssl=aa.bb.cc
+ovn-nbctl --wait=hv set Logical_Router_port lrp0 ipv6_ra_configs:route_info=HIGH-1001::11/48,LOW-1002::11/96
 
 # Make sure that ovn-controller has installed the corresponding OF Flow.
 OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
@@ -11970,8 +11986,11 @@  default_prefix_option_config=030440c0ffffffffffffffff00000000
 src_mac=fa163e000003
 src_lla=fe80000000000000f8163efffe000003
 mtu=000005dc
+rdnss=19030000ffffffff10000000000000000000000000000011
+dnssl=1f030000ffffffff02616102626202636300000000000000
+route_info=18023008ffffffff100100000000000018036018ffffffff10020000000000000000000000000000
 
-test_ipv6_ra 2 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
+test_ipv6_ra 2 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config $rdnss $dnssl $route_info
 
 # NXT_RESUME should be 2.
 OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
@@ -11993,6 +12012,8 @@  reset_pcap_file hv1-vif3 hv1/vif3
 # Set the address mode to dhcpv6_stateful, router_preference to HIGH
 ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateful
 ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:router_preference="HIGH"
+ovn-nbctl --wait=hv remove Logical_Router_Port lrp0 ipv6_ra_configs rdnss
+ovn-nbctl --wait=hv remove Logical_Router_Port lrp0 ipv6_ra_configs route_info
 # Make sure that ovn-controller has installed the corresponding OF Flow.
 OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
 
@@ -12003,7 +12024,7 @@  src_mac=fa163e000004
 src_lla=fe80000000000000f8163efffe000004
 mtu=000005dc
 
-test_ipv6_ra 3 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
+test_ipv6_ra 3 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config "" $dnssl
 
 # NXT_RESUME should be 3.
 OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
@@ -12025,6 +12046,7 @@  reset_pcap_file hv1-vif3 hv1/vif3
 # Set the address mode to dhcpv6_stateless, reset router preference to default
 ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateless
 ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:router_preference="MEDIUM"
+ovn-nbctl --wait=hv remove Logical_Router_Port lrp0 ipv6_ra_configs dnssl
 # Make sure that ovn-controller has installed the corresponding OF Flow.
 OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])