On Fri, Feb 6, 2026 at 11:21 PM Ilya Maximets <[email protected]> wrote:

> On 2/6/26 10:54 AM, Ales Musil via dev wrote:
> > The RFC defines a Virtual Router Redundancy Protocol [0], in order
> > for that protocol to work the workload might "spoof" MAC address
> > within ARP or ND request/response. This wasn't allowed as the port
> > security is specifically designed against spoofing and checks if
> > the port security MAC address is the same for source of ARP/ND
> > and the inner source/target address. To make the port security
> > compliant add a special literal which when specified will allow
> > user to add any/all MAC addresses defined by VRRPv3. The traffic
> > from and to those additional MAC addresses will be allowed as
> > well as permutations of ARP/ND inner MACs combined with the
> > physical MAC as a source.
> >
> > [0] https://datatracker.ietf.org/doc/html/rfc9568
> > Reported-at: https://issues.redhat.com/browse/FDP-2979
> > Signed-off-by: Ales Musil <[email protected]>
> > ---
> > v4: Rebase on top of latest main.
> >     Update the RFC url.
> >     Add Jacob's ack.
> > v5: Rebase on top of latest main.
> >     Address nits pointed out by Dumitru.
> >     Add extra test for invalid VRRPv3 MAC.
> >     Update the wording in documentation.
> >     Remove acks as the code changed.
> >     Do not populate flows for physical MAC when VRRP is specified.
>
> Thanks, Ales!  Functionally this version looks good to me for the
> most part, see some small comments below.
>

Thank you for the review Ilya,


>
> > ---
> >  NEWS               |   3 +
> >  controller/lflow.c | 932 +++++++++++++++++++++++++++++----------------
> >  ovn-nb.xml         |  55 +++
> >  tests/ovn.at       | 779 +++++++++++++++++++++++++++++++++++++
> >  4 files changed, 1446 insertions(+), 323 deletions(-)
> >
> > diff --git a/NEWS b/NEWS
> > index 2a2b5e12d..7c7458224 100644
> > --- a/NEWS
> > +++ b/NEWS
> > @@ -98,6 +98,9 @@ Post v25.09.0
> >       reserving an unused IP from the backend's subnet. This change
> allows
> >       using LRP IPs directly, eliminating the need to reserve additional
> IPs
> >       per backend port.
> > +   - Add support for special port_security prefix "VRRPv3". This prefix
> allows
> > +     CMS to specify one physical MAC and multiple VRRPv3 MAC addresses.
> > +     The VRRPv3 MAC also accepts masked format.
>
> nit: It feels like this record doesn't contain enough info to start using
> the feature, which is fine, but it also doesn't contain enough info to
> explain what it is for.
>
> Maybe replace the last two sentences with something like "This prefix
> allows CMS to allow all required traffic for a VRRPv3 virtual router
> behind LSP.  See <man page reference> for more details." ?
>

Ack.


>
> >
> >  OVN v25.09.0 - xxx xx xxxx
> >  --------------------------
> > diff --git a/controller/lflow.c b/controller/lflow.c
> > index 94fd8807c..904de8ff0 100644
> > --- a/controller/lflow.c
> > +++ b/controller/lflow.c
> > @@ -2340,6 +2340,157 @@ add_port_sec_flows(const struct shash
> *binding_lports,
> >      }
> >  }
> >
> > +struct masked_ip4_addr {
> > +    ovs_be32 addr;
> > +    ovs_be32 mask;
> > +    ovs_be32 bcast;
> > +};
> > +
> > +struct masked_ip6_addr {
> > +    struct in6_addr addr;
> > +    struct in6_addr mask;
> > +};
> > +
> > +struct masked_eth_addr {
> > +    struct eth_addr addr;
> > +    struct eth_addr mask;
> > +};
> > +
> > +struct port_security_addresses {
> > +    struct eth_addr phys_addr;
> > +    /* Vector of 'struct masked_eth_addr'. */
> > +    struct vector vrrp4;
> > +    /* Vector of 'struct masked_eth_addr'. */
> > +    struct vector vrrp6;
> > +    /* Vector of 'struct masked_ip4_addr' .*/
> > +    struct vector ip4;
> > +    /* Vector of 'struct masked_ip6_addr' .*/
> > +    struct vector ip6;
> > +};
> > +
> > +static void
> > +port_security_addresses_init(struct port_security_addresses *ps_addr)
> > +{
> > +    *ps_addr = (struct port_security_addresses) {
> > +        .phys_addr = eth_addr_zero,
> > +        .vrrp4 = VECTOR_EMPTY_INITIALIZER(struct masked_eth_addr),
> > +        .vrrp6 = VECTOR_EMPTY_INITIALIZER(struct masked_eth_addr),
> > +        .ip4 = VECTOR_EMPTY_INITIALIZER(struct masked_ip4_addr),
> > +        .ip6 = VECTOR_EMPTY_INITIALIZER(struct masked_ip6_addr),
> > +    };
> > +}
> > +
> > +static void
> > +port_security_addresses_clear(struct port_security_addresses *ps_addr)
> > +{
> > +    vector_clear(&ps_addr->vrrp4);
> > +    vector_clear(&ps_addr->vrrp6);
> > +    vector_clear(&ps_addr->ip4);
> > +    vector_clear(&ps_addr->ip6);
> > +}
> > +
> > +static void
> > +port_security_addresses_destroy(struct port_security_addresses *ps_addr)
> > +{
> > +    vector_destroy(&ps_addr->vrrp4);
> > +    vector_destroy(&ps_addr->vrrp6);
> > +    vector_destroy(&ps_addr->ip4);
> > +    vector_destroy(&ps_addr->ip6);
> > +}
> > +
> > +static const struct masked_eth_addr maddr_any_vrrp4 = {
> > +    .addr = ETH_ADDR_C(00,00,5e,00,01,00),
> > +    .mask = ETH_ADDR_C(ff,ff,ff,ff,ff,00)
> > +};
> > +static const struct masked_eth_addr maddr_any_vrrp6 = {
> > +    .addr = ETH_ADDR_C(00,00,5e,00,02,00),
> > +    .mask = ETH_ADDR_C(ff,ff,ff,ff,ff,00)
>
> nit: trailing comma?  Other structure initializers have it and ones for
> this
> structure inside the functions as well.
>
> > +};
> > +
> > +static bool
> > +port_security_addresses_add_vrrp_mac(struct port_security_addresses
> *ps_addr,
> > +                                     struct eth_addr mac, unsigned int
> plen)
> > +{
> > +    /* Only the last byte contains ID for VRRPv3. */
> > +    if (plen < 40) {
> > +        return false;
> > +    }
> > +
> > +    /* If the masked portion is non-zero, the host can only use
> > +     * the specified MAC address.  If zero, the host is allowed
> > +     * to use any MAC address within the mask.
>
> This looks a little strange to me.  I'd not expect 00:00:5e:00:01:42/40 to
> only allow 00:00:5e:00:01:42 address.  We should probbaly just fail here
> if the masked part is not zero.
>
> The format where the masked part is not zero makes sense for the IPs in the
> regular port security record, because it means IP within the subnet +
> subnet
> prefix length, and not the whole subnet.  This kind of configuration is
> needed, because we need to create a broadcast flow, and so we have to know
> the prefix, even if we want to allow a single IP.
>
> For the VRRP MAC addresses though this doesn't make a lot of sense, we
> don't
> need to create any broadcast flows, we just need to know the subnet.  So,
> we either clear the masked bits or fail if they are present.  Failing seems
> less confusing from the user's perspective.
>
> WDYT?
>

Makes sense, let's reject masked MACs that have non-zero masked portion.


>
> Either way this behavior is documented for IPs, but not for MACs.  But it
> will be hard to explain in the docs why only the specified IP is used, when
> there is no use for the prefix.
>
> > +     */
> > +    struct eth_addr mask = eth_addr_create_mask(plen);
> > +    struct masked_eth_addr maddr = (struct masked_eth_addr) {
>
> nit: shouldn't need to cast, it can be an initializer.  Compliler probbaly
> doesn't care, but semantially it's a copy of an anonymous structure vs
> designated initializer.
>

We are using this style all over OVN, so I would rather remain consistent.


> > +        .addr = mac,
> > +        .mask = (eth_addr_to_uint64(mac) & ~eth_addr_to_uint64(mask))
> > +              ? eth_addr_exact : mask,
>
> nit: should probably be 2 spaces to the right.
>
> > +    };
> > +
> > +    /* The exact match on VRRPv3 MAC ending with zero is not allowed,
> the
> > +     * id is starting from 1. */
> > +    if (plen == 48) {
> > +        if (eth_addr_equals(mac, maddr_any_vrrp4.addr) ||
> > +            eth_addr_equals(mac, maddr_any_vrrp6.addr)) {
> > +            return false;
> > +        }
> > +    }
> > +
> > +    if (eth_addr_equal_except(maddr_any_vrrp4.addr, mac,
> > +                              maddr_any_vrrp4.mask)) {
> > +        vector_push(&ps_addr->vrrp4, &maddr);
> > +        return true;
> > +    }
> > +
> > +    if (eth_addr_equal_except(maddr_any_vrrp6.addr, mac,
> > +                              maddr_any_vrrp6.mask)) {
> > +        vector_push(&ps_addr->vrrp6, &maddr);
> > +        return true;
> > +    }
> > +
> > +    return false;
> > +}
> > +
> > +static bool
> > +port_security_addresses_add_ip(struct port_security_addresses *ps_addr,
> > +                               struct in6_addr ip, unsigned int plen)
> > +{
> > +    /* When the netmask is applied, if the host portion is
> > +     * non-zero, the host can only use the specified
> > +     * address.  If zero, the host is allowed to use any
> > +     * address in the subnet. Also add broadcast in the special case
> > +     * of matching only the specified address.
>
> nit: Line lengths seem inconsistent.  Also single vs double spaces.
>

Ack.


>
> > +     */
> > +    if (IN6_IS_ADDR_V4MAPPED(&ip)) {
> > +        if (plen > 32) {
> > +            return false;
> > +        }
> > +
> > +        ovs_be32 addr = in6_addr_get_mapped_ipv4(&ip);
> > +        ovs_be32 mask = be32_prefix_mask(plen);
> > +
> > +        struct masked_ip4_addr maddr = (struct masked_ip4_addr) {
>
> nit: cast.
>
> > +            .addr = addr,
> > +            .mask = (addr & ~mask) ? OVS_BE32_MAX : mask,
> > +            .bcast = (addr & ~mask) ? (addr | ~mask) : htonl(0),
> > +        };
> > +        vector_push(&ps_addr->ip4, &maddr);
> > +    } else {
> > +        if (plen > 128) {
> > +            return false;
> > +        }
> > +
> > +        struct in6_addr mask = ipv6_create_mask(plen);
> > +        struct masked_ip6_addr maddr = (struct masked_ip6_addr) {
>
> nit: cast.
>
> > +            .addr = ip,
> > +            .mask = !ipv6_addr_is_host_zero(&ip, &mask) ? in6addr_exact
> : mask,
> > +        };
> > +        vector_push(&ps_addr->ip6, &maddr);
> > +    }
> > +
> > +    return true;
> > +}
> > +
> >  static void
> >  reset_match_for_port_sec_flows(const struct sbrec_port_binding *pb,
> >                                 enum mf_field_id reg_id, struct match
> *match)
> > @@ -2446,24 +2597,39 @@ build_in_port_sec_default_flows(const struct
> sbrec_port_binding *pb,
> >                      &pb->header_.uuid);
> >  }
> >
> > +static void
> > +build_out_port_sec_default_flows(const struct sbrec_port_binding *pb,
> > +                                struct match *m, struct ofpbuf *ofpacts,
> > +                                struct ovn_desired_flow_table
> *flow_table)
> > +{
> > +    /* Add the below logical flow equivalent OF rules in
> 'out_port_sec_nd'
> > +     * table.
> > +     * priority: 80
> > +     * match - "outport == pb->logical_port"
> > +     * action - "port_sec_failed = 1;"
> > +     * descrption: "Drop all traffic"
> > +     */
> > +    reset_match_for_port_sec_flows(pb, MFF_LOG_OUTPORT, m);
> > +    build_port_sec_deny_action(ofpacts);
> > +    ofctrl_add_flow(flow_table, OFTABLE_CHK_OUT_PORT_SEC, 80,
> > +                    pb->header_.uuid.parts[0], m, ofpacts,
> > +                    &pb->header_.uuid);
> > +}
> > +
> >  static void
> >  build_in_port_sec_no_ip_flows(const struct sbrec_port_binding *pb,
> > -                              struct lport_addresses *ps_addr,
> > +                              struct eth_addr mac, struct eth_addr mask,
> >                                struct match *m, struct ofpbuf *ofpacts,
> >                                struct ovn_desired_flow_table *flow_table)
> >  {
> > -    if (ps_addr->n_ipv4_addrs || ps_addr->n_ipv6_addrs) {
> > -        return;
> > -    }
> > -
> >      /* Add the below logical flow equivalent OF rules in 'in_port_sec'
> table.
> >       * priority: 90
> > -     * match - "inport == pb->logical_port && eth.src == ps_addr.ea"
> > +     * match - "inport == pb->logical_port && eth.src == mac/mask"
> >       * action - "next;"
> >       * description: "Advance the packet for ARP/ND check"
> >       */
> >      reset_match_for_port_sec_flows(pb, MFF_LOG_INPORT, m);
> > -    match_set_dl_src(m, ps_addr->ea);
> > +    match_set_dl_src_masked(m, mac, mask);
> >      build_port_sec_adv_nd_check(ofpacts);
> >      ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC, 90,
> >                      pb->header_.uuid.parts[0], m, ofpacts,
> > @@ -2472,43 +2638,26 @@ build_in_port_sec_no_ip_flows(const struct
> sbrec_port_binding *pb,
> >
> >  static void
> >  build_in_port_sec_ip4_flows(const struct sbrec_port_binding *pb,
> > -                           struct lport_addresses *ps_addr,
> > -                           struct match *m, struct ofpbuf *ofpacts,
> > -                           struct ovn_desired_flow_table *flow_table)
> > +                            struct eth_addr mac, struct eth_addr mask,
> > +                            const struct vector *ip4_addrs,
> > +                            struct match *m, struct ofpbuf *ofpacts,
> > +                            struct ovn_desired_flow_table *flow_table)
> >  {
> > -    if (!ps_addr->n_ipv4_addrs) {
> > -        /* If no IPv4 addresses, then 'pb' is not allowed to send IPv4
> traffic.
> > -         * build_in_port_sec_default_flows() takes care of this
> scenario. */
> > -        return;
> > -    }
> > -
> >      /* Advance all traffic from the port security eth address for ND
> check. */
> >      build_port_sec_allow_action(ofpacts);
> > +    reset_match_for_port_sec_flows(pb, MFF_LOG_INPORT, m);
> > +    match_set_dl_src_masked(m, mac, mask);
> > +    match_set_dl_type(m, htons(ETH_TYPE_IP));
> >
> >      /* Add the below logical flow equivalent OF rules in in_port_sec.
> >       * priority: 90
> > -     * match - "inport == pb->port && eth.src == ps_addr.ea &&
> > -     *         ip4.src == {ps_addr.ipv4_addrs}"
> > +     * match - "inport == pb->port && eth.src == mac/mask &&
> > +     *         ip4.src == {ip4}"
> >       * action - "port_sec_failed = 0;"
> >       */
> > -    for (size_t j = 0; j < ps_addr->n_ipv4_addrs; j++) {
> > -        reset_match_for_port_sec_flows(pb, MFF_LOG_INPORT, m);
> > -        match_set_dl_src(m, ps_addr->ea);
> > -        match_set_dl_type(m, htons(ETH_TYPE_IP));
> > -
> > -        ovs_be32 mask = ps_addr->ipv4_addrs[j].mask;
> > -        /* When the netmask is applied, if the host portion is
> > -         * non-zero, the host can only use the specified
> > -         * address.  If zero, the host is allowed to use any
> > -         * address in the subnet.
> > -         */
> > -        if (ps_addr->ipv4_addrs[j].plen == 32 ||
> > -                ps_addr->ipv4_addrs[j].addr & ~mask) {
> > -            match_set_nw_src(m, ps_addr->ipv4_addrs[j].addr);
> > -        } else {
> > -            match_set_nw_src_masked(m, ps_addr->ipv4_addrs[j].addr,
> mask);
> > -        }
> > -
> > +    const struct masked_ip4_addr *ip;
> > +    VECTOR_FOR_EACH_PTR (ip4_addrs, ip) {
> > +        match_set_nw_src_masked(m, ip->addr, ip->mask);
> >          ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC, 90,
> >                          pb->header_.uuid.parts[0], m, ofpacts,
> >                          &pb->header_.uuid);
> > @@ -2516,20 +2665,14 @@ build_in_port_sec_ip4_flows(const struct
> sbrec_port_binding *pb,
> >
> >      /* Add the below logical flow equivalent OF rules in in_port_sec.
> >       * priority: 90
> > -     * match - "inport == pb->port && eth.src == ps_addr.ea &&
> > +     * match - "inport == pb->port && eth.src == mac/mask &&
> >       *          ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 &&
> >       *          udp.src == 67 && udp.dst == 68"
> >       * action - "port_sec_failed = 0;"
> >       * description: "Allow the DHCP requests."
> >       */
> > -    reset_match_for_port_sec_flows(pb, MFF_LOG_INPORT, m);
> > -    match_set_dl_src(m, ps_addr->ea);
> > -    match_set_dl_type(m, htons(ETH_TYPE_IP));
> > -
> > -    ovs_be32 ip4 = htonl(0);
> > -    match_set_nw_src(m, ip4);
> > -    ip4 = htonl(0xffffffff);
> > -    match_set_nw_dst(m, ip4);
> > +    match_set_nw_src(m, htonl(0));
> > +    match_set_nw_dst(m, htonl(0xffffffff));
> >      match_set_nw_proto(m, IPPROTO_UDP);
> >      match_set_tp_src(m, htons(68));
> >      match_set_tp_dst(m, htons(67));
> > @@ -2542,33 +2685,42 @@ build_in_port_sec_ip4_flows(const struct
> sbrec_port_binding *pb,
> >  /* Adds the OF rules to allow ARP packets in 'in_port_sec_nd' table. */
> >  static void
> >  build_in_port_sec_arp_flows(const struct sbrec_port_binding *pb,
> > -                           struct lport_addresses *ps_addr,
> > -                           struct match *m, struct ofpbuf *ofpacts,
> > -                           struct ovn_desired_flow_table *flow_table)
> > +                            struct eth_addr phys_mac,
> > +                            const struct vector *ip4_addrs,
> > +                            const struct vector *vrrp4_addrs,
> > +                            bool is_vrrp, struct match *m,
> > +                            struct ofpbuf *ofpacts,
> > +                            struct ovn_desired_flow_table *flow_table)
> >  {
> > -    if (!ps_addr->n_ipv4_addrs && ps_addr->n_ipv6_addrs) {
> > -        /* No ARP is allowed as only IPv6 addresses are configured. */
> > -        return;
> > -    }
> > -
> >      build_port_sec_allow_action(ofpacts);
> > +    reset_match_for_port_sec_flows(pb, MFF_LOG_INPORT, m);
> > +    match_set_dl_src(m, phys_mac);
> > +    match_set_dl_type(m, htons(ETH_TYPE_ARP));
> >
> > -    if (!ps_addr->n_ipv4_addrs) {
> > +    if (vector_is_empty(ip4_addrs)) {
> >          /* No IPv4 addresses.
> >           * Add the below logical flow equivalent OF rules in
> 'in_port_sec_nd'
> >           * table.
> >           * priority: 90
> > -         * match - "inport == pb->port && eth.src == ps_addr.ea &&
> > -         *          arp && arp.sha == ps_addr.ea"
> > +         * match - "inport == pb->port && eth.src == phys_mac &&
> > +         *          arp && arp.sha == {phys_mac, vrrp4_addrs}"
> >           * action - "port_sec_failed = 0;"
> >           */
> > -        reset_match_for_port_sec_flows(pb, MFF_LOG_INPORT, m);
> > -        match_set_dl_src(m, ps_addr->ea);
> > -        match_set_dl_type(m, htons(ETH_TYPE_ARP));
> > -        match_set_arp_sha(m, ps_addr->ea);
> > -        ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
> > -                        pb->header_.uuid.parts[0], m, ofpacts,
> > -                        &pb->header_.uuid);
> > +
> > +        if (!is_vrrp) {
> > +            match_set_arp_sha(m, phys_mac);
> > +            ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
> > +                            pb->header_.uuid.parts[0], m, ofpacts,
> > +                            &pb->header_.uuid);
> > +        }
> > +
> > +        struct masked_eth_addr *mmac;
> > +        VECTOR_FOR_EACH_PTR (vrrp4_addrs, mmac) {
> > +            match_set_arp_sha_masked(m, mmac->addr, mmac->mask);
> > +            ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
> > +                            pb->header_.uuid.parts[0], m, ofpacts,
> > +                            &pb->header_.uuid);
> > +        }
> >      }
> >
> >      /* Add the below logical flow equivalent OF rules in
> 'in_port_sec_nd'
> > @@ -2578,74 +2730,62 @@ build_in_port_sec_arp_flows(const struct
> sbrec_port_binding *pb,
> >       *         arp && arp.sha == ps_addr.ea && arp.spa ==
> {ps_addr.ipv4_addrs}"
> >       * action - "port_sec_failed = 0;"
> >       */
> > -    for (size_t j = 0; j < ps_addr->n_ipv4_addrs; j++) {
> > -        reset_match_for_port_sec_flows(pb, MFF_LOG_INPORT, m);
> > -        match_set_dl_src(m, ps_addr->ea);
> > -        match_set_dl_type(m, htons(ETH_TYPE_ARP));
> > -        match_set_arp_sha(m, ps_addr->ea);
> > -
> > -        ovs_be32 mask = ps_addr->ipv4_addrs[j].mask;
> > -        if (ps_addr->ipv4_addrs[j].plen == 32 ||
> > -                ps_addr->ipv4_addrs[j].addr & ~mask) {
> > -            match_set_nw_src(m, ps_addr->ipv4_addrs[j].addr);
> > -        } else {
> > -            match_set_nw_src_masked(m, ps_addr->ipv4_addrs[j].addr,
> mask);
> > +    const struct masked_ip4_addr *ip;
> > +    VECTOR_FOR_EACH_PTR (ip4_addrs, ip) {
> > +        match_set_nw_src_masked(m, ip->addr, ip->mask);
> > +
> > +        if (!is_vrrp) {
> > +            match_set_arp_sha(m, phys_mac);
> > +            ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
> > +                            pb->header_.uuid.parts[0], m, ofpacts,
> > +                            &pb->header_.uuid);
> > +        }
> > +
> > +        struct masked_eth_addr *mmac;
> > +        VECTOR_FOR_EACH_PTR (vrrp4_addrs, mmac) {
> > +            match_set_arp_sha_masked(m, mmac->addr, mmac->mask);
> > +            ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
> > +                            pb->header_.uuid.parts[0], m, ofpacts,
> > +                            &pb->header_.uuid);
> >          }
> > -        ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
> > -                        pb->header_.uuid.parts[0], m, ofpacts,
> > -                        &pb->header_.uuid);
> >      }
> >  }
> >
> >  static void
> >  build_in_port_sec_ip6_flows(const struct sbrec_port_binding *pb,
> > -                           struct lport_addresses *ps_addr,
> > -                           struct match *m, struct ofpbuf *ofpacts,
> > -                           struct ovn_desired_flow_table *flow_table)
> > +                            struct eth_addr mac, struct eth_addr mask,
> > +                            const struct vector *ip6_addrs,
> > +                            struct match *m, struct ofpbuf *ofpacts,
> > +                            struct ovn_desired_flow_table *flow_table)
> >  {
> > -    if (!ps_addr->n_ipv6_addrs) {
> > -        /* If no IPv6 addresses, then 'pb' is not allowed to send IPv6
> traffic.
> > -         * build_in_port_sec_default_flows() takes care of this
> scenario. */
> > -        return;
> > -    }
> > -
> >      /* Add the below logical flow equivalent OF rules in
> 'in_port_sec_nd'
> >       * table.
> >       * priority: 90
> > -     * match - "inport == pb->port && eth.src == ps_addr.ea &&
> > +     * match - "inport == pb->port && eth.src == mac/mask &&
> >       *         ip6.src == {ps_addr.ipv6_addrs, lla}"
> >       * action - "next;"
> >       * description - Advance the packet for Neighbor Solicit/Adv check.
> >       */
> >      build_port_sec_adv_nd_check(ofpacts);
> > +    reset_match_for_port_sec_flows(pb, MFF_LOG_INPORT, m);
> > +    match_set_dl_src_masked(m, mac, mask);
> > +    match_set_dl_type(m, htons(ETH_TYPE_IPV6));
> >
> > -    for (size_t j = 0; j < ps_addr->n_ipv6_addrs; j++) {
> > -        reset_match_for_port_sec_flows(pb, MFF_LOG_INPORT, m);
> > -        match_set_dl_src(m, ps_addr->ea);
> > -        match_set_dl_type(m, htons(ETH_TYPE_IPV6));
> > -
> > -        if (ps_addr->ipv6_addrs[j].plen == 128
> > -            || !ipv6_addr_is_host_zero(&ps_addr->ipv6_addrs[j].addr,
> > -                                        &ps_addr->ipv6_addrs[j].mask)) {
> > -            match_set_ipv6_src(m, &ps_addr->ipv6_addrs[j].addr);
> > -        } else {
> > -            match_set_ipv6_src_masked(m,
> &ps_addr->ipv6_addrs[j].network,
> > -                                        &ps_addr->ipv6_addrs[j].mask);
> > -        }
> > -
> > +    const struct masked_ip6_addr *ip;
> > +    VECTOR_FOR_EACH_PTR (ip6_addrs, ip) {
> > +        match_set_ipv6_src_masked(m, &ip->addr, &ip->mask);
> >          ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC, 90,
> >                          pb->header_.uuid.parts[0], m, ofpacts,
> >                          &pb->header_.uuid);
> >      }
> >
> > -    reset_match_for_port_sec_flows(pb, MFF_LOG_INPORT, m);
> > -    match_set_dl_src(m, ps_addr->ea);
> > -    match_set_dl_type(m, htons(ETH_TYPE_IPV6));
> >
> >      struct in6_addr lla;
> > -    in6_generate_lla(ps_addr->ea, &lla);
> > -    match_set_ipv6_src(m, &lla);
> > +    in6_generate_lla(mac, &lla);
> > +    unsigned int plen = 128 - 48 + eth_addr_get_prefix_len(mask);
> > +    struct in6_addr lla_mask = ipv6_create_mask(plen);
> >
> > +    match_set_ipv6_src_masked(m, &lla, &lla_mask);
> >      ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC, 90,
> >                      pb->header_.uuid.parts[0], m, ofpacts,
> >                      &pb->header_.uuid);
> > @@ -2660,11 +2800,12 @@ build_in_port_sec_ip6_flows(const struct
> sbrec_port_binding *pb,
> >       */
> >      build_port_sec_allow_action(ofpacts);
> >      match_set_ipv6_src(m, &in6addr_any);
> > -    struct in6_addr ip6, mask;
> > -    char *err = ipv6_parse_masked("ff02::/16", &ip6, &mask);
> > +
> > +    struct in6_addr ip6, ip_mask;
> > +    char *err = ipv6_parse_masked("ff02::/16", &ip6, &ip_mask);
> >      ovs_assert(!err);
> >
> > -    match_set_ipv6_dst_masked(m, &ip6, &mask);
> > +    match_set_ipv6_dst_masked(m, &ip6, &ip_mask);
> >      match_set_nw_proto(m, IPPROTO_ICMPV6);
> >      match_set_icmp_type(m, 131);
> >      match_set_icmp_code(m, 0);
> > @@ -2697,99 +2838,118 @@ build_in_port_sec_ip6_flows(const struct
> sbrec_port_binding *pb,
> >   * 'in_port_sec_nd' table. */
> >  static void
> >  build_in_port_sec_nd_flows(const struct sbrec_port_binding *pb,
> > -                           struct lport_addresses *ps_addr,
> > -                           struct match *m, struct ofpbuf *ofpacts,
> > +                           struct eth_addr phys_mac,
> > +                           const struct vector *ip6_addrs,
> > +                           const struct vector *vrrp6_addrs,
> > +                           bool is_vrrp, struct match *m,
> > +                           struct ofpbuf *ofpacts,
> >                             struct ovn_desired_flow_table *flow_table)
> >  {
> >      build_port_sec_allow_action(ofpacts);
> > +    reset_match_for_port_sec_flows(pb, MFF_LOG_INPORT, m);
> > +    match_set_dl_src(m, phys_mac);
> > +    match_set_dl_type(m, htons(ETH_TYPE_IPV6));
> > +    match_set_nw_proto(m, IPPROTO_ICMPV6);
> > +    match_set_icmp_code(m, 0);
> > +    match_set_nw_ttl(m, 255);
> >
> >      /* Add the below logical flow equivalent OF rules in
> 'in_port_sec_nd'
> >       * table.
> >       * priority: 90
> > -     * match - "inport == pb->port && eth.src == ps_addr.ea &&
> > -     *          icmp6 && icmp6.code == 135 && icmp6.type == 0 &&
> > -     *          ip6.tll == 255 && nd.sll == {00:00:00:00:00:00,
> ps_addr.ea}"
> > +     * match - "inport == pb->port && eth.src == phys_mac &&
> > +     *          icmp6 && icmp6.type == 135 && icmp6.code == 0 &&
> > +     *          ip6.tll == 255 &&
> > +     *          nd.sll == {00:00:00:00:00:00, phys_mac, vrrp6_addrs}"
> >       * action - "port_sec_failed = 0;"
> >       */
> > -    reset_match_for_port_sec_flows(pb, MFF_LOG_INPORT, m);
> > -    match_set_dl_type(m, htons(ETH_TYPE_IPV6));
> > -    match_set_nw_proto(m, IPPROTO_ICMPV6);
> > -    match_set_dl_src(m, ps_addr->ea);
> > -    match_set_nw_ttl(m, 255);
> > +
> >      match_set_icmp_type(m, 135);
> > -    match_set_icmp_code(m, 0);
> >
> >      match_set_arp_sha(m, eth_addr_zero);
> >      ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
> >                      pb->header_.uuid.parts[0], m, ofpacts,
> >                      &pb->header_.uuid);
> >
> > -    match_set_arp_sha(m, ps_addr->ea);
> > -    ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
> > -                    pb->header_.uuid.parts[0], m, ofpacts,
> > -                    &pb->header_.uuid);
> > +    if (!is_vrrp) {
> > +        match_set_arp_sha(m, phys_mac);
> > +        ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
> > +                        pb->header_.uuid.parts[0], m, ofpacts,
> > +                        &pb->header_.uuid);
> > +    }
> >
> > +    struct masked_eth_addr *mmac;
> > +    VECTOR_FOR_EACH_PTR (vrrp6_addrs, mmac) {
> > +        match_set_arp_sha_masked(m, mmac->addr, mmac->mask);
> > +        ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
> > +                        pb->header_.uuid.parts[0], m, ofpacts,
> > +                        &pb->header_.uuid);
> > +    }
> > +
> > +    match_set_arp_sha_masked(m, eth_addr_zero, eth_addr_zero);
> >      match_set_icmp_type(m, 136);
> > -    match_set_icmp_code(m, 0);
> > -    if (ps_addr->n_ipv6_addrs) {
> > +    if (!vector_is_empty(ip6_addrs)) {
> >          /* Add the below logical flow equivalent OF rules in
> 'in_port_sec_nd'
> >           * table if IPv6 addresses are configured.
> >           * priority: 90
> > -         * match - "inport == pb->port && eth.src == ps_addr.ea &&
> icmp6 &&
> > -         *          icmp6.code == 136 && icmp6.type == 0 && ip6.tll ==
> 255 &&
> > -         *          nd.tll == {00:00:00:00:00:00, ps_addr.ea} &&
> > -         *          nd.target == {ps_addr.ipv6_addrs, lla}"
> > +         * match - "inport == pb->port && eth.src == phys_mac && icmp6
> &&
> > +         *          icmp6.type == 136 && icmp6.code == 0 && ip6.tll ==
> 255 &&
> > +         *          nd.tll == {00:00:00:00:00:00, phys_mac,
> vrrp6_addrs} &&
> > +         *          nd.target == {lla, ip6_addrs}"
> >           * action - "port_sec_failed = 0;"
> >           */
> >          struct in6_addr lla;
> > -        in6_generate_lla(ps_addr->ea, &lla);
> > -        match_set_arp_tha(m, eth_addr_zero);
> > -
> > -        match_set_nd_target(m, &lla);
> > -        ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
> > -                        pb->header_.uuid.parts[0], m, ofpacts,
> > -                        &pb->header_.uuid);
> > -        match_set_arp_tha(m, ps_addr->ea);
> > +        in6_generate_lla(phys_mac, &lla);
> >          match_set_nd_target(m, &lla);
> > +
> > +        match_set_arp_tha(m, eth_addr_zero);
> >          ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
> >                          pb->header_.uuid.parts[0], m, ofpacts,
> >                          &pb->header_.uuid);
> >
> > -        for (size_t j = 0; j < ps_addr->n_ipv6_addrs; j++) {
> > -            reset_match_for_port_sec_flows(pb, MFF_LOG_INPORT, m);
> > -            match_set_dl_src(m, ps_addr->ea);
> > -            match_set_dl_type(m, htons(ETH_TYPE_IPV6));
> > -            match_set_nw_proto(m, IPPROTO_ICMPV6);
> > -            match_set_nw_ttl(m, 255);
> > -            match_set_icmp_type(m, 136);
> > -            match_set_icmp_code(m, 0);
> > -            match_set_arp_tha(m, eth_addr_zero);
> > -
> > -            if (ps_addr->ipv6_addrs[j].plen == 128
> > -                || !ipv6_addr_is_host_zero(&ps_addr->ipv6_addrs[j].addr,
> > -
> &ps_addr->ipv6_addrs[j].mask)) {
> > -                match_set_nd_target(m, &ps_addr->ipv6_addrs[j].addr);
> > -            } else {
> > -                match_set_nd_target_masked(m,
> &ps_addr->ipv6_addrs[j].network,
> > -
>  &ps_addr->ipv6_addrs[j].mask);
> > -            }
> > +        if (!is_vrrp) {
> > +            match_set_arp_tha(m, phys_mac);
> > +            ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
> > +                            pb->header_.uuid.parts[0], m, ofpacts,
> > +                            &pb->header_.uuid);
> > +        }
> >
> > +        VECTOR_FOR_EACH_PTR (vrrp6_addrs, mmac) {
> > +            match_set_arp_tha_masked(m, mmac->addr, mmac->mask);
> >              ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
> >                              pb->header_.uuid.parts[0], m, ofpacts,
> >                              &pb->header_.uuid);
> > +        }
> >
> > -            match_set_arp_tha(m, ps_addr->ea);
> > +        const struct masked_ip6_addr *ip;
> > +        VECTOR_FOR_EACH_PTR (ip6_addrs, ip) {
> > +            match_set_nd_target_masked(m, &ip->addr, &ip->mask);
> > +
> > +            match_set_arp_tha(m, eth_addr_zero);
> >              ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
> >                              pb->header_.uuid.parts[0], m, ofpacts,
> >                              &pb->header_.uuid);
> > +
> > +            if (!is_vrrp) {
> > +                match_set_arp_tha(m, phys_mac);
> > +                ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND,
> 90,
> > +                                pb->header_.uuid.parts[0], m, ofpacts,
> > +                                &pb->header_.uuid);
> > +            }
> > +
> > +            VECTOR_FOR_EACH_PTR (vrrp6_addrs, mmac) {
> > +                match_set_arp_tha_masked(m, mmac->addr, mmac->mask);
> > +                ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND,
> 90,
> > +                                pb->header_.uuid.parts[0], m, ofpacts,
> > +                                &pb->header_.uuid);
> > +            }
> >          }
> >      } else {
> >          /* Add the below logical flow equivalent OF rules in
> 'in_port_sec_nd'
> >           * table if no IPv6 addresses are configured.
> >           * priority: 90
> > -         * match - "inport == pb->port && eth.src == ps_addr.ea &&
> icmp6 &&
> > +         * match - "inport == pb->port && eth.src == phys_mac && icmp6
> &&
> >           *          icmp6.code == 136 && icmp6.type == 0 && ip6.tll ==
> 255 &&
> > -         *          nd.tll == {00:00:00:00:00:00, ps_addr.ea}"
> > +         *          nd.tll == {00:00:00:00:00:00, phys_mac,
> vrrp6_addrs}"
> >           * action - "port_sec_failed = 0;"
> >           */
> >          match_set_arp_tha(m, eth_addr_zero);
> > @@ -2797,27 +2957,36 @@ build_in_port_sec_nd_flows(const struct
> sbrec_port_binding *pb,
> >                          pb->header_.uuid.parts[0], m, ofpacts,
> >                          &pb->header_.uuid);
> >
> > -        match_set_arp_tha(m, ps_addr->ea);
> > -        ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
> > -                        pb->header_.uuid.parts[0], m, ofpacts,
> > -                        &pb->header_.uuid);
> > +        if (!is_vrrp) {
> > +            match_set_arp_tha(m, phys_mac);
> > +            ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
> > +                            pb->header_.uuid.parts[0], m, ofpacts,
> > +                            &pb->header_.uuid);
> > +        }
> > +
> > +        VECTOR_FOR_EACH_PTR (vrrp6_addrs, mmac) {
> > +            match_set_arp_tha_masked(m, mmac->addr, mmac->mask);
> > +            ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
> > +                            pb->header_.uuid.parts[0], m, ofpacts,
> > +                            &pb->header_.uuid);
> > +        }
> >      }
> >  }
> >
> >  static void
> >  build_out_port_sec_no_ip_flows(const struct sbrec_port_binding *pb,
> > -                               struct lport_addresses *ps_addr,
> > +                               struct eth_addr mac, struct eth_addr
> mask,
> >                                 struct match *m, struct ofpbuf *ofpacts,
> >                                 struct ovn_desired_flow_table
> *flow_table)
> >  {
> >      /* Add the below logical flow equivalent OF rules in 'out_port_sec'
> table.
> >       * priority: 85
> > -     * match - "outport == pb->logical_port && eth.dst == ps_addr.ea"
> > +     * match - "outport == pb->logical_port && eth.dst == mac/mask"
> >       * action - "port_sec_failed = 0;"
> >       * description: "Allow the packet if eth.dst matches."
> >       */
> >      reset_match_for_port_sec_flows(pb, MFF_LOG_OUTPORT, m);
> > -    match_set_dl_dst(m, ps_addr->ea);
> > +    match_set_dl_dst_masked(m, mac, mask);
> >      build_port_sec_allow_action(ofpacts);
> >      ofctrl_add_flow(flow_table, OFTABLE_CHK_OUT_PORT_SEC, 85,
> >                      pb->header_.uuid.parts[0], m, ofpacts,
> > @@ -2826,97 +2995,70 @@ build_out_port_sec_no_ip_flows(const struct
> sbrec_port_binding *pb,
> >
> >  static void
> >  build_out_port_sec_ip4_flows(const struct sbrec_port_binding *pb,
> > -                            struct lport_addresses *ps_addr,
> > -                            struct match *m, struct ofpbuf *ofpacts,
> > -                            struct ovn_desired_flow_table *flow_table)
> > +                             struct eth_addr mac, struct eth_addr mask,
> > +                             const struct vector *ip4_addrs,
> > +                             struct match *m, struct ofpbuf *ofpacts,
> > +                             struct ovn_desired_flow_table *flow_table)
> >  {
> > -    if (!ps_addr->n_ipv4_addrs && !ps_addr->n_ipv6_addrs) {
> > -         /* No IPv4 and no IPv6 addresses in the port security.
> > -          * Both IPv4 and IPv6 traffic should be delivered to the
> > -          * lport. build_out_port_sec_no_ip_flows() takes care of
> > -          * adding the required flow(s) to allow. */
> > -        return;
> > -    }
> > +    reset_match_for_port_sec_flows(pb, MFF_LOG_OUTPORT, m);
> > +    match_set_dl_dst_masked(m, mac, mask);
> > +    match_set_dl_type(m, htons(ETH_TYPE_IP));
> >
> >      /* Add the below logical flow equivalent OF rules in 'out_port_sec'
> table.
> >       * priority: 90
> > -     * match - "outport == pb->logical_port && eth.dst == ps_addr.ea &&
> ip4"
> > +     * match - "outport == pb->logical_port && eth.dst == mac/mask &&
> ip4"
> >       * action - "port_sec_failed = 1;"
> >       * description: Default drop IPv4 packets.  If IPv4 addresses are
> >       *              configured, then higher priority flows are added
> >       *              to allow specific IPv4 packets.
> >       */
> > -    reset_match_for_port_sec_flows(pb, MFF_LOG_OUTPORT, m);
> > -    match_set_dl_dst(m, ps_addr->ea);
> > -    match_set_dl_type(m, htons(ETH_TYPE_IP));
> > +
> >      build_port_sec_deny_action(ofpacts);
> >      ofctrl_add_flow(flow_table, OFTABLE_CHK_OUT_PORT_SEC, 90,
> >                      pb->header_.uuid.parts[0], m, ofpacts,
> >                      &pb->header_.uuid);
> >
> > -    if (!ps_addr->n_ipv4_addrs) {
> > +    if (vector_is_empty(ip4_addrs)) {
> >          return;
> >      }
> >
> > +    build_port_sec_allow_action(ofpacts);
> >      /* Add the below logical flow equivalent OF rules in 'out_port_sec'
> table.
> >       * priority: 95
> > -     * match - "outport == pb->logical_port && eth.dst == ps_addr.ea &&
> > -     *          ip4.dst == {ps_addr.ipv4_addrs, 255.255.255.255,
> 224.0.0.0/4},"
> > +     * match - "outport == pb->logical_port && eth.dst == mac/mask &&
> > +     *          ip4.dst == {ip4_addrs, 255.255.255.255, 224.0.0.0/4},"
> >       * action - "port_sec_failed = 0;"
> >       */
> > -    build_port_sec_allow_action(ofpacts);
> > -    for (size_t j = 0; j < ps_addr->n_ipv4_addrs; j++) {
> > -        reset_match_for_port_sec_flows(pb, MFF_LOG_OUTPORT, m);
> > -        match_set_dl_dst(m, ps_addr->ea);
> > -        match_set_dl_type(m, htons(ETH_TYPE_IP));
> > -        ovs_be32 mask = ps_addr->ipv4_addrs[j].mask;
> > -        if (ps_addr->ipv4_addrs[j].plen == 32
> > -                || ps_addr->ipv4_addrs[j].addr & ~mask) {
> > -
> > -            if (ps_addr->ipv4_addrs[j].plen != 32) {
> > -                /* Special case to allow bcast traffic.
> > -                 * Eg. If ps_addr is 10.0.0.4/24, then add the below
> flow
> > -                 * priority: 95
> > -                 * match - "outport == pb->logical_port &&
> > -                 *          eth.dst == ps_addr.ea &&
> > -                 *          ip4.dst == 10.0.0.255"
> > -                 * action - "port_sec_failed = 0;"
> > -                 */
> > -                ovs_be32 bcast_addr;
> > -                ovs_assert(ip_parse(ps_addr->ipv4_addrs[j].bcast_s,
> > -                                    &bcast_addr));
> > -                match_set_nw_dst(m, bcast_addr);
> > -                ofctrl_add_flow(flow_table, OFTABLE_CHK_OUT_PORT_SEC,
> 95,
> > -                                pb->header_.uuid.parts[0], m, ofpacts,
> > -                                &pb->header_.uuid);
> > -            }
> > -
> > -            match_set_nw_dst(m, ps_addr->ipv4_addrs[j].addr);
> > -        } else {
> > -            /* host portion is zero */
> > -            match_set_nw_dst_masked(m, ps_addr->ipv4_addrs[j].addr,
> > -                                    mask);
> > -        }
> > -
> > +    const struct masked_ip4_addr *ip;
> > +    VECTOR_FOR_EACH_PTR (ip4_addrs, ip) {
> > +        match_set_nw_dst_masked(m, ip->addr, ip->mask);
> >          ofctrl_add_flow(flow_table, OFTABLE_CHK_OUT_PORT_SEC, 95,
> >                          pb->header_.uuid.parts[0], m, ofpacts,
> >                          &pb->header_.uuid);
> > -    }
> >
> > -    reset_match_for_port_sec_flows(pb, MFF_LOG_OUTPORT, m);
> > -    match_set_dl_dst(m, ps_addr->ea);
> > -    match_set_dl_type(m, htons(ETH_TYPE_IP));
> > +        if (ip->bcast) {
> > +            /* Special case to allow bcast traffic.
> > +             * Eg. If address is 10.0.0.4/24, then add the below flow
> > +             * priority: 95
> > +             * match - "outport == pb->logical_port &&
> > +             *          eth.dst == ps_addr.ea &&
> > +             *          ip4.dst == 10.0.0.255"
> > +             * action - "port_sec_failed = 0;"
> > +             */
> > +            match_set_nw_dst(m, ip->bcast);
> > +            ofctrl_add_flow(flow_table, OFTABLE_CHK_OUT_PORT_SEC, 95,
> > +                            pb->header_.uuid.parts[0], m, ofpacts,
> > +                            &pb->header_.uuid);
> > +        }
> > +    }
> >
> > -    ovs_be32 ip4 = htonl(0xffffffff);
> > -    match_set_nw_dst(m, ip4);
> > +    match_set_nw_dst(m, htonl(0xffffffff));
> >      ofctrl_add_flow(flow_table, OFTABLE_CHK_OUT_PORT_SEC, 95,
> >                      pb->header_.uuid.parts[0], m, ofpacts,
> >                      &pb->header_.uuid);
> >
> >      /* Allow 224.0.0.0/4 traffic. */
> > -    ip4 = htonl(0xe0000000);
> > -    ovs_be32 mask = htonl(0xf0000000);
> > -    match_set_nw_dst_masked(m, ip4, mask);
> > +    match_set_nw_dst_masked(m, htonl(0xe0000000), htonl(0xf0000000));
> >      ofctrl_add_flow(flow_table, OFTABLE_CHK_OUT_PORT_SEC, 95,
> >                      pb->header_.uuid.parts[0], m, ofpacts,
> >                      &pb->header_.uuid);
> > @@ -2924,112 +3066,270 @@ build_out_port_sec_ip4_flows(const struct
> sbrec_port_binding *pb,
> >
> >  static void
> >  build_out_port_sec_ip6_flows(const struct sbrec_port_binding *pb,
> > -                            struct lport_addresses *ps_addr,
> > -                            struct match *m, struct ofpbuf *ofpacts,
> > -                            struct ovn_desired_flow_table *flow_table)
> > +                             struct eth_addr mac, struct eth_addr mask,
> > +                             const struct vector *ip6_addrs,
> > +                             struct match *m, struct ofpbuf *ofpacts,
> > +                             struct ovn_desired_flow_table *flow_table)
> >  {
> > -    if (!ps_addr->n_ipv4_addrs && !ps_addr->n_ipv6_addrs) {
> > -        /* No IPv4 and no IPv6 addresses in the port security.
> > -         * Both IPv4 and IPv6 traffic should be delivered to the
> > -         * lport. build_out_port_sec_no_ip_flows() takes care of
> > -         * adding the required flow(s) to allow. */
> > -        return;
> > -    }
> > +    reset_match_for_port_sec_flows(pb, MFF_LOG_OUTPORT, m);
> > +    match_set_dl_dst_masked(m, mac, mask);
> > +    match_set_dl_type(m, htons(ETH_TYPE_IPV6));
> >
> >      /* Add the below logical flow equivalent OF rules in 'out_port_sec'
> table.
> >       * priority: 90
> > -     * match - "outport == pb->logical_port && eth.dst == ps_addr.ea &&
> ip6"
> > +     * match - "outport == pb->logical_port && eth.dst == mac/mask &&
> ip6"
> >       * action - "port_sec_failed = 1;"
> >       * description: Default drop IPv6 packets.  If IPv6 addresses are
> >       *              configured, then higher priority flows are added
> >       *              to allow specific IPv6 packets.
> >       */
> > -    reset_match_for_port_sec_flows(pb, MFF_LOG_OUTPORT, m);
> > -    match_set_dl_dst(m, ps_addr->ea);
> > -    match_set_dl_type(m, htons(ETH_TYPE_IPV6));
> >      build_port_sec_deny_action(ofpacts);
> >      ofctrl_add_flow(flow_table, OFTABLE_CHK_OUT_PORT_SEC, 90,
> >                      pb->header_.uuid.parts[0], m, ofpacts,
> >                      &pb->header_.uuid);
> >
> > -    if (!ps_addr->n_ipv6_addrs) {
> > +    if (vector_is_empty(ip6_addrs)) {
> >          return;
> >      }
> >
> > +    build_port_sec_allow_action(ofpacts);
> >      /* Add the below logical flow equivalent OF rules in 'out_port_sec'
> table.
> >       * priority: 95
> > -     * match - "outport == pb->logical_port && eth.dst == ps_addr.ea &&
> > -     *          ip6.dst == {ps_addr.ipv6_addrs, lla, ff00::/8},"
> > +     * match - "outport == pb->logical_port && eth.dst == mac/mask &&
> > +     *          ip6.dst == {mac/mask, ip6_addrs, lla, ff00::/8},"
> >       * action - "port_sec_failed = 0;"
> >       */
> > -    build_port_sec_allow_action(ofpacts);
> > -    for (size_t j = 0; j < ps_addr->n_ipv6_addrs; j++) {
> > -        reset_match_for_port_sec_flows(pb, MFF_LOG_OUTPORT, m);
> > -        match_set_dl_dst(m, ps_addr->ea);
> > -        match_set_dl_type(m, htons(ETH_TYPE_IPV6));
> > -
> > -        if (ps_addr->ipv6_addrs[j].plen == 128
> > -            || !ipv6_addr_is_host_zero(&ps_addr->ipv6_addrs[j].addr,
> > -                                        &ps_addr->ipv6_addrs[j].mask)) {
> > -            match_set_ipv6_dst(m, &ps_addr->ipv6_addrs[j].addr);
> > -        } else {
> > -            match_set_ipv6_dst_masked(m,
> &ps_addr->ipv6_addrs[j].network,
> > -                                      &ps_addr->ipv6_addrs[j].mask);
> > -        }
> > -
> > +    const struct masked_ip6_addr *ip;
> > +    VECTOR_FOR_EACH_PTR (ip6_addrs, ip) {
> > +        match_set_ipv6_dst_masked(m, &ip->addr, &ip->mask);
> >          ofctrl_add_flow(flow_table, OFTABLE_CHK_OUT_PORT_SEC, 95,
> >                          pb->header_.uuid.parts[0], m, ofpacts,
> >                          &pb->header_.uuid);
> >      }
> >
> >      struct in6_addr lla;
> > -    in6_generate_lla(ps_addr->ea, &lla);
> > +    in6_generate_lla(mac, &lla);
> > +    unsigned int plen = 128 - 48 + eth_addr_get_prefix_len(mask);
> > +    struct in6_addr lla_mask = ipv6_create_mask(plen);
> >
> > -    reset_match_for_port_sec_flows(pb, MFF_LOG_OUTPORT, m);
> > -    match_set_dl_dst(m, ps_addr->ea);
> > -    match_set_dl_type(m, htons(ETH_TYPE_IPV6));
> > -    match_set_ipv6_dst(m, &lla);
> > +    match_set_ipv6_dst_masked(m, &lla, &lla_mask);
> >      ofctrl_add_flow(flow_table, OFTABLE_CHK_OUT_PORT_SEC, 95,
> >                      pb->header_.uuid.parts[0], m, ofpacts,
> >                      &pb->header_.uuid);
> >
> > -    struct in6_addr ip6, mask;
> > -    char *err = ipv6_parse_masked("ff00::/8", &ip6, &mask);
> > +    struct in6_addr ip6, ip_mask;
> > +    char *err = ipv6_parse_masked("ff00::/8", &ip6, &ip_mask);
> >      ovs_assert(!err);
> >
> > -    match_set_ipv6_dst_masked(m, &ip6, &mask);
> > +    match_set_ipv6_dst_masked(m, &ip6, &ip_mask);
> >      ofctrl_add_flow(flow_table, OFTABLE_CHK_OUT_PORT_SEC, 95,
> >                      pb->header_.uuid.parts[0], m, ofpacts,
> >                      &pb->header_.uuid);
> >  }
> >
> >  static void
> > -consider_port_sec_flows(const struct sbrec_port_binding *pb,
> > -                        struct ovn_desired_flow_table *flow_table)
> > +build_port_sec_entry_flows(const struct sbrec_port_binding *pb,
> > +                           const struct port_security_addresses
> *ps_addr,
> > +                           struct match *m, struct ofpbuf *ofpacts,
> > +                           struct ovn_desired_flow_table *flow_table)
> >  {
> > -    if (!pb->n_port_security) {
> > -        return;
> > +    /* Input no-ip flows. */
> > +    if (vector_is_empty(&ps_addr->ip4) &&
> vector_is_empty(&ps_addr->ip6)) {
> > +        build_in_port_sec_no_ip_flows(pb, ps_addr->phys_addr,
> eth_addr_exact,
> > +                                      m, ofpacts, flow_table);
> >      }
> >
> > -    struct lport_addresses *ps_addrs;   /* Port security addresses. */
> > -    size_t n_ps_addrs = 0;
> > +    /* Input IPv4 flows. */
> > +    if (!vector_is_empty(&ps_addr->ip4)) {
> > +        build_in_port_sec_ip4_flows(pb, ps_addr->phys_addr,
> eth_addr_exact,
> > +                                    &ps_addr->ip4, m, ofpacts,
> flow_table);
> > +    }
> >
> > -    ps_addrs = xmalloc(sizeof *ps_addrs * pb->n_port_security);
> > -    for (size_t i = 0; i < pb->n_port_security; i++) {
> > -        if (!extract_lsp_addresses(pb->port_security[i],
> > -                                    &ps_addrs[n_ps_addrs])) {
> > -            static struct vlog_rate_limit rl
> > -                = VLOG_RATE_LIMIT_INIT(1, 1);
> > -            VLOG_WARN_RL(&rl, "invalid syntax '%s' in port "
> > -                         "security. No MAC address found",
> > -                         pb->port_security[i]);
> > -            continue;
> > +    /* Input ARP flows. */
> > +    if (!vector_is_empty(&ps_addr->ip4) ||
> vector_is_empty(&ps_addr->ip6)) {
> > +        build_in_port_sec_arp_flows(pb, ps_addr->phys_addr,
> &ps_addr->ip4,
> > +                                    &ps_addr->vrrp4, false, m, ofpacts,
> > +                                    flow_table);
> > +    }
> > +
> > +    /* Input Ipv6 flows. */
> > +    if (!vector_is_empty(&ps_addr->ip6)) {
> > +        build_in_port_sec_ip6_flows(pb, ps_addr->phys_addr,
> eth_addr_exact,
> > +                                    &ps_addr->ip6, m, ofpacts,
> flow_table);
> > +    }
> > +
> > +    /* Input ND flows. */
> > +    build_in_port_sec_nd_flows(pb, ps_addr->phys_addr, &ps_addr->ip6,
> > +                               &ps_addr->vrrp6, false, m, ofpacts,
> > +                               flow_table);
> > +
> > +    /* Output no-ip flows. */
> > +    build_out_port_sec_no_ip_flows(pb, ps_addr->phys_addr,
> eth_addr_exact,
> > +                                    m, ofpacts, flow_table);
> > +
> > +    /* Output IPv4 flows. */
> > +    if (!vector_is_empty(&ps_addr->ip4) ||
> !vector_is_empty(&ps_addr->ip6)) {
> > +        build_out_port_sec_ip4_flows(pb, ps_addr->phys_addr,
> eth_addr_exact,
> > +                                     &ps_addr->ip4, m, ofpacts,
> flow_table);
> > +    }
> > +
> > +    /* Output Ipv6 flows. */
> > +    if (!vector_is_empty(&ps_addr->ip4) ||
> !vector_is_empty(&ps_addr->ip6)) {
> > +        build_out_port_sec_ip6_flows(pb, ps_addr->phys_addr,
> eth_addr_exact,
> > +                                     &ps_addr->ip6, m, ofpacts,
> flow_table);
> > +    }
> > +}
> > +
> > +static void
> > +build_port_sec_entry_vrrp_flows(const struct sbrec_port_binding *pb,
> > +                                const struct port_security_addresses
> *ps_addr,
> > +                                struct match *m, struct ofpbuf *ofpacts,
> > +                                struct ovn_desired_flow_table
> *flow_table)
> > +{
> > +    const struct masked_eth_addr *maddr;
> > +
> > +    /* Input no-ip flows. */
> > +    if (vector_is_empty(&ps_addr->ip4) &&
> vector_is_empty(&ps_addr->ip6)) {
> > +        VECTOR_FOR_EACH_PTR (&ps_addr->vrrp4, maddr) {
> > +            build_in_port_sec_no_ip_flows(pb, maddr->addr, maddr->mask,
> > +                                          m, ofpacts, flow_table);
> > +        }
> > +
> > +        VECTOR_FOR_EACH_PTR (&ps_addr->vrrp6, maddr) {
> > +            build_in_port_sec_no_ip_flows(pb, maddr->addr, maddr->mask,
> > +                                          m, ofpacts, flow_table);
> >          }
> > -        n_ps_addrs++;
> >      }
> >
> > -    if (!n_ps_addrs) {
> > -        free(ps_addrs);
> > +    /* Input IPv4 flows. */
> > +    if (!vector_is_empty(&ps_addr->ip4)) {
> > +        VECTOR_FOR_EACH_PTR (&ps_addr->vrrp4, maddr) {
> > +            build_in_port_sec_ip4_flows(pb, maddr->addr, maddr->mask,
> > +                                        &ps_addr->ip4, m, ofpacts,
> flow_table);
> > +        }
> > +    }
> > +
> > +    /* Input ARP flows. */
> > +    if (!vector_is_empty(&ps_addr->ip4) ||
> vector_is_empty(&ps_addr->ip6)) {
> > +        build_in_port_sec_arp_flows(pb, ps_addr->phys_addr,
> &ps_addr->ip4,
> > +                                    &ps_addr->vrrp4, true, m, ofpacts,
> > +                                    flow_table);
> > +    }
> > +
> > +    /* Input Ipv6 flows. */
> > +    if (!vector_is_empty(&ps_addr->ip6)) {
> > +        VECTOR_FOR_EACH_PTR (&ps_addr->vrrp6, maddr) {
> > +            build_in_port_sec_ip6_flows(pb, maddr->addr, maddr->mask,
> > +                                        &ps_addr->ip6, m, ofpacts,
> flow_table);
> > +        }
> > +    }
> > +
> > +    /* Input ND flows. */
> > +    build_in_port_sec_nd_flows(pb, ps_addr->phys_addr, &ps_addr->ip6,
> > +                               &ps_addr->vrrp6, true, m, ofpacts,
> > +                               flow_table);
> > +
> > +    /* Output no-ip flows. */
> > +    VECTOR_FOR_EACH_PTR (&ps_addr->vrrp4, maddr) {
> > +        build_out_port_sec_no_ip_flows(pb, maddr->addr, maddr->mask,
> > +                                       m, ofpacts, flow_table);
> > +    }
> > +
> > +    VECTOR_FOR_EACH_PTR (&ps_addr->vrrp6, maddr) {
> > +        build_out_port_sec_no_ip_flows(pb, maddr->addr, maddr->mask,
> > +                                       m, ofpacts, flow_table);
> > +    }
> > +
> > +    /* Output IPv4 flows. */
> > +    if (!vector_is_empty(&ps_addr->ip4) ||
> !vector_is_empty(&ps_addr->ip6)) {
> > +        VECTOR_FOR_EACH_PTR (&ps_addr->vrrp4, maddr) {
> > +            build_out_port_sec_ip4_flows(pb, maddr->addr, maddr->mask,
> > +                                         &ps_addr->ip4, m, ofpacts,
> > +                                         flow_table);
> > +        }
> > +    }
> > +
> > +    /* Output Ipv6 flows. */
> > +    if (!vector_is_empty(&ps_addr->ip4) ||
> !vector_is_empty(&ps_addr->ip6)) {
> > +        VECTOR_FOR_EACH_PTR (&ps_addr->vrrp6, maddr) {
> > +            build_out_port_sec_ip6_flows(pb, maddr->addr, maddr->mask,
> > +                                         &ps_addr->ip6, m, ofpacts,
> > +                                         flow_table);
> > +        }
> > +    }
> > +}
> > +
> > +static bool
> > +port_security_addresses_parse_entry(const char *entry, const char *lsp,
> > +                                    struct port_security_addresses
> *ps_addr)
> > +{
> > +    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> > +
> > +    bool vrrpv3 = !strncmp(entry, "VRRPv3", 6);
> > +    int n = vrrpv3 ? 7 : 0;
> > +
> > +    if (!ovs_scan_len(entry, &n, ETH_ADDR_SCAN_FMT,
> > +                      ETH_ADDR_SCAN_ARGS(ps_addr->phys_addr))) {
> > +        VLOG_WARN_RL(&rl, "invalid syntax '%s' in port security for LSP
> %s: "
> > +                     "No MAC address found", entry, lsp);
> > +        return false;
> > +    }
> > +
> > +    bool ok = true;
> > +
> > +    /* Only MAC address is provided. */
> > +    if (!entry[n]) {
> > +        goto vrrp_check;
> > +    }
> > +
> > +    char *save_ptr = NULL;
> > +    char *tokstr = xstrdup(entry + n);
> > +    for (char *token = strtok_r(tokstr, " ", &save_ptr);
> > +         token != NULL;
> > +         token = strtok_r(NULL, " ", &save_ptr)) {
> > +        struct eth_addr mac;
> > +        struct in6_addr ip;
> > +        unsigned int plen;
> > +
> > +        if (vrrpv3 && eth_addr_parse_masked(token, &mac, &plen)) {
> > +            if (!port_security_addresses_add_vrrp_mac(ps_addr, mac,
> plen)) {
> > +                VLOG_WARN_RL(&rl, "invalid syntax '%s' in port security
> for"
> > +                             " LSP %s: Invalid VRRPv3 MAC", token, lsp);
> > +                ok = false;
> > +                break;
> > +            }
> > +        } else if (ip46_parse_cidr(token, &ip, &plen)) {
> > +            if (!port_security_addresses_add_ip(ps_addr, ip, plen)) {
> > +                VLOG_WARN_RL(&rl, "invalid syntax '%s' in port security
> for"
> > +                             " LSP %s: Invalid IP", token, lsp);
> > +                ok = false;
> > +                break;
> > +            }
> > +        } else {
> > +            VLOG_WARN_RL(&rl, "invalid syntax '%s' in port security for"
> > +                         " LSP %s: Invalid IP or MAC", token, lsp);
> > +            ok = false;
> > +            break;
> > +        }
> > +    }
> > +
> > +    free(tokstr);
> > +
> > +vrrp_check:
> > +    if (vrrpv3 && vector_is_empty(&ps_addr->vrrp4) &&
> > +        vector_is_empty(&ps_addr->vrrp6)) {
> > +        vector_push(&ps_addr->vrrp4, &maddr_any_vrrp4);
> > +        vector_push(&ps_addr->vrrp6, &maddr_any_vrrp6);
> > +    }
> > +
> > +    return ok;
> > +}
> > +
> > +static void
> > +consider_port_sec_flows(const struct sbrec_port_binding *pb,
> > +                        struct ovn_desired_flow_table *flow_table)
> > +{
> > +    if (!pb->n_port_security) {
> >          return;
> >      }
> >
> > @@ -3037,48 +3337,34 @@ consider_port_sec_flows(const struct
> sbrec_port_binding *pb,
> >      uint64_t stub[1024 / 8];
> >      struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub);
> >
> > -    build_in_port_sec_default_flows(pb, &match, &ofpacts, flow_table);
> > +    bool flows_installed = false;
> > +    struct port_security_addresses ps_addr;
> > +    port_security_addresses_init(&ps_addr);
> >
> > -    for (size_t i = 0; i < n_ps_addrs; i++) {
> > -        build_in_port_sec_no_ip_flows(pb, &ps_addrs[i], &match,
> &ofpacts,
> > -                                      flow_table);
> > -        build_in_port_sec_ip4_flows(pb, &ps_addrs[i], &match, &ofpacts,
> > -                                    flow_table);
> > -        build_in_port_sec_arp_flows(pb, &ps_addrs[i], &match, &ofpacts,
> > -                                    flow_table);
> > -        build_in_port_sec_ip6_flows(pb, &ps_addrs[i], &match, &ofpacts,
> > -                                    flow_table);
> > -        build_in_port_sec_nd_flows(pb, &ps_addrs[i], &match, &ofpacts,
> > -                                   flow_table);
> > -    }
> > +    for (size_t i = 0; i < pb->n_port_security; i++) {
> > +        if (port_security_addresses_parse_entry(pb->port_security[i],
> > +                                                pb->logical_port,
> &ps_addr)) {
> > +            if (vector_is_empty(&ps_addr.vrrp4) &&
> > +                vector_is_empty(&ps_addr.vrrp6)) {
> > +                build_port_sec_entry_flows(pb, &ps_addr, &match,
> > +                                           &ofpacts, flow_table);
> >
> > -    /* Out port security. */
> > +            } else {
> > +                build_port_sec_entry_vrrp_flows(pb, &ps_addr, &match,
> > +                                                &ofpacts, flow_table);
> > +            }
> >
> > -    /* Add the below logical flow equivalent OF rules in
> 'out_port_sec_nd'
> > -     * table.
> > -     * priority: 80
> > -     * match - "outport == pb->logical_port"
> > -     * action - "port_sec_failed = 1;"
> > -     * descrption: "Drop all traffic"
> > -     */
> > -    reset_match_for_port_sec_flows(pb, MFF_LOG_OUTPORT, &match);
> > -    build_port_sec_deny_action(&ofpacts);
> > -    ofctrl_add_flow(flow_table, OFTABLE_CHK_OUT_PORT_SEC, 80,
> > -                    pb->header_.uuid.parts[0], &match, &ofpacts,
> > -                    &pb->header_.uuid);
> > +            flows_installed = true;
> > +        }
> >
> > -    for (size_t i = 0; i < n_ps_addrs; i++) {
> > -        build_out_port_sec_no_ip_flows(pb, &ps_addrs[i], &match,
> &ofpacts,
> > -                                       flow_table);
> > -        build_out_port_sec_ip4_flows(pb, &ps_addrs[i], &match, &ofpacts,
> > -                                       flow_table);
> > -        build_out_port_sec_ip6_flows(pb, &ps_addrs[i], &match, &ofpacts,
> > -                                       flow_table);
> > +        port_security_addresses_clear(&ps_addr);
> >      }
> >
> > -    ofpbuf_uninit(&ofpacts);
> > -    for (size_t i = 0; i < n_ps_addrs; i++) {
> > -        destroy_lport_addresses(&ps_addrs[i]);
> > +    if (flows_installed) {
> > +        build_in_port_sec_default_flows(pb, &match, &ofpacts,
> flow_table);
> > +        build_out_port_sec_default_flows(pb, &match, &ofpacts,
> flow_table);
> >      }
> > -    free(ps_addrs);
> > +
> > +    ofpbuf_uninit(&ofpacts);
> > +    port_security_addresses_destroy(&ps_addr);
> >  }
> > diff --git a/ovn-nb.xml b/ovn-nb.xml
> > index 1acbf202b..a9c68e1a4 100644
> > --- a/ovn-nb.xml
> > +++ b/ovn-nb.xml
> > @@ -2057,6 +2057,21 @@
> >            addresses within an element may be space or comma separated.
> >          </p>
> >
> > +        <p>
> > +          This also supports the special prefix "VRRPv3" that allows
> > +          specification of single physical MAC and multiple VRRPv3 MAC
> > +          addresses. As for non VRRPv3 entries, multiple IP addresses
> can be
> > +          associated with the specified MACs. "VRRPv3" with a single
> > +          physical MAC translates to allowing traffic for the whole
> "VRRPv3"
> > +          range of MACs. See more in the examples.
> > +
> > +          When the port security configuration entry contains the
> VRRPv3 label,
> > +          the behavior for physical MAC (the first specified) is
> different.
> > +          The installed flows will allow traffic from to VRRP MACs +
> IPs.
>
> from/to ?
>
> > +          The physical MAC is there to properly allow ARP/ND with given
> > +          VRRP MACs.
>
> Maybe mention here that to allow traffic that is not related to a virtual
> router, e.g. IP traffic with a physical MAC as a source, a regular port
> security entry should be added separately.
>
> > +        </p>
> > +
> >          <p>
> >            This column is provided as a convenience to cloud management
> systems,
> >            but all of the features that it implements can be implemented
> as ACLs
> > @@ -2102,6 +2117,46 @@
> >              255.255.255.255, and any address in 224.0.0.0/4.  The host
> may not
> >              send or receive any IPv6 (including IPv6 Neighbor
> Discovery) traffic.
> >            </dd>
> > +
> > +          <dt><code>"VRRPv3 &lt;PHYSICAL_MAC&gt;"</code></dt>
> > +          <dd>
> > +            The host may also send ARP/ND packets with physical
> > +            MAC as a source and the inner SHA/TLL/SLL all the VRRPv3
> > +            MACs, 00:00:5e:00:01:{VRID}/40 for IPv4 and
> > +            00:00:5E:00:02:{VRID}/40 for IPv6.
> > +          </dd>
> > +
> > +          <dt><code>"VRRPv3 &lt;PHYSICAL_MAC&gt;
> > +                    &lt;VRRPV3_MACv4_1&gt;/[&lt;MASK1&gt;]
> > +                    &lt;VRRPV3_MACv4_N&gt;
> > +                    &lt;VRRPV3_MACv6_1&gt;/[&lt;MASK2&gt;]
> > +                    &lt;VRRPV3_MACv6_N&gt;"</code></dt>
> > +          <dd>
> > +            The host may also send ARP/ND packets with physical
> > +            MAC as a source and the inner SHA/TLL/SLL all the VRRPv3
> > +            MACs, 00:00:5e:00:01:{VRID} for IPv4 and
> > +            00:00:5E:00:02:{VRID} for IPv6. The VRRPv3 MACs can be
> provided
> > +            with a mask with prefix between /40 and /48.
> > +          </dd>
> > +
> > +          <dt><code>"VRRPv3 &lt;PHYSICAL_MAC&gt;
> > +            &lt;VRRPV3_MACv4_1&gt;/[&lt;MASK1&gt;]
> > +            &lt;VRRPV3_MACv4_N&gt;
> > +            &lt;VRRPV3_MACv6_1&gt;/[&lt;MASK2&gt;]
> > +            &lt;VRRPV3_MACv6_N&gt;
> > +            &lt;VRRPV3_IPv4_1&gt;/[&lt;MASK1&gt;]
> > +            &lt;VRRPV3_IPv4_N&gt;
> > +            &lt;VRRPV3_IPv6_1&gt;/[&lt;MASK2&gt;]
> > +            &lt;VRRPV3_IPv6_N&gt;"</code></dt>
> > +          <dd>
> > +            The host may also send ARP/ND packets with physical
> > +            MAC as a source and the inner SHA/TLL/SLL all the VRRPv3
> > +            MACs, 00:00:5e:00:01:{VRID} for IPv4 and
> > +            00:00:5E:00:02:{VRID} for IPv6. The VRRPv3 MACs can be
> provided
> > +            with a mask with prefix between /40 and /48. The provided IP
> > +            addresses further restrict the inner IP for ARP/ND. As well
> > +            as traffic to/from given VRRPv3 MACs and the provided IPs.
>
> 'and the provided IPs' seems redundant here.
>
> > +          </dd>
> >          </dl>
> >        </column>
> >      </group>
> > diff --git a/tests/ovn.at b/tests/ovn.at
> > index 961b6ecf7..28f53a491 100644
> > --- a/tests/ovn.at
> > +++ b/tests/ovn.at
> > @@ -44611,3 +44611,782 @@ check ovn-nbctl --wait=hv lsp-set-type
> down_ext localnet
> >  OVN_CLEANUP([hv1],[hv2])
> >  AT_CLEANUP
> >  ])
> > +
> > +OVN_FOR_EACH_NORTHD([
> > +AT_SETUP([Port security - VRRPv3 ARP/ND])
> > +AT_SKIP_IF([test $HAVE_SCAPY = no])
> > +ovn_start
> > +net_add n1
> > +sim_add hv1
> > +as hv1
> > +check ovs-vsctl add-br br-phys
> > +ovn_attach n1 br-phys 192.168.0.1
> > +
> > +check ovn-nbctl ls-add ls \
> > +    -- set logical_switch ls other-config:requested-tnl-key=1
> > +
> > +check ovn-nbctl lsp-add ls lsp1
> > +check ovn-nbctl lsp-set-addresses lsp1 "00:00:00:00:10:01 192.168.10.1
> fd10::1" \
> > +    -- set logical_switch_port lsp1 options:requested-tnl-key=1
> > +
> > +check ovn-nbctl lsp-add ls lsp2
> > +check ovn-nbctl lsp-set-addresses lsp2 "00:00:00:00:10:02 192.168.10.2
> fd10::2" \
> > +    -- set logical_switch_port lsp2 options:requested-tnl-key=2
> > +
> > +check ovs-vsctl -- add-port br-int vif1 -- \
> > +      set interface vif1 external-ids:iface-id=lsp1 \
> > +      options:tx_pcap=hv1/vif1-tx.pcap \
> > +      options:rxq_pcap=hv1/vif1-rx.pcap
> > +
> > +check ovs-vsctl -- add-port br-int vif2 -- \
> > +      set interface vif2 external-ids:iface-id=lsp2 \
> > +      options:tx_pcap=hv1/vif2-tx.pcap \
> > +      options:rxq_pcap=hv1/vif2-rx.pcap
> > +
> > +wait_for_ports_up
> > +
> > +test_arp() {
> > +    local dropped=$1
> > +
> > +    packet=$(fmt_pkt "
> > +        Ether(dst='ff:ff:ff:ff:ff:ff', src='00:00:00:00:10:01') /
> > +        ARP(op=1, hwsrc='00:00:5e:00:01:05', hwdst='ff:ff:ff:ff:ff:ff',
> psrc='192.168.10.1', pdst='192.168.10.2')
> > +    ")
> > +    check as hv1 ovs-appctl netdev-dummy/receive vif1 $packet
> > +
> > +    packet=$(fmt_pkt "
> > +            Ether(dst='ff:ff:ff:ff:ff:ff', src='00:00:00:00:10:01') /
> > +            ARP(op=2, hwsrc='00:00:5e:00:01:05',
> hwdst='ff:ff:ff:ff:ff:ff', psrc='192.168.10.1', pdst='192.168.10.1')
>
> nit: Indentation still inconsistent here.  Should be 4 spaces to the left.
>
> > +    ")
> > +    check as hv1 ovs-appctl netdev-dummy/receive vif1 $packet
> > +
> > +    if [[ "$dropped" != "yes" ]]; then
> > +        echo $packet >> vif2.expected
> > +        packet=$(fmt_pkt "
> > +            Ether(dst='00:00:00:00:10:01', src='00:00:00:00:10:02') /
> > +            ARP(op=2, hwsrc='00:00:00:00:10:02',
> hwdst='00:00:5e:00:01:05', psrc='192.168.10.2', pdst='192.168.10.1')
> > +        ")
> > +        echo $packet >> vif1.expected
> > +    fi
> > +}
> > +test_nd() {
> > +    local dropped=$1
> > +
> > +    packet=$(fmt_pkt "
> > +        Ether(dst='33:33:ff:00:00:01', src='00:00:00:00:10:01') /
> > +        IPv6(src='fd10::1', dst='ff02::1:ff00:2') /
> > +        ICMPv6ND_NS(tgt='fd10::2') /
> > +        ICMPv6NDOptSrcLLAddr(lladdr='00:00:5e:00:02:05')
> > +    ")
> > +    check as hv1 ovs-appctl netdev-dummy/receive vif1 $packet
> > +
> > +    packet=$(fmt_pkt "
> > +        Ether(dst='33:33:ff:00:00:01', src='00:00:00:00:10:01') /
> > +        IPv6(src='fd10::1', dst='ff02::1:ff00:1') /
> > +        ICMPv6ND_NA(tgt='fd10::1') /
> > +        ICMPv6NDOptDstLLAddr(lladdr='00:00:5e:00:02:05')
> > +    ")
> > +    check as hv1 ovs-appctl netdev-dummy/receive vif1 $packet
> > +
> > +    if [[ "$dropped" != "yes" ]]; then
> > +        echo $packet >> vif2.expected
> > +
> > +        packet=$(fmt_pkt "
> > +                Ether(dst='00:00:00:00:10:01', src='00:00:00:00:10:02')
> /
> > +                IPv6(src='fd10::2', dst='fd10::1') /
> > +                ICMPv6ND_NA(tgt='fd10::2', R=0, S=1, O=1) /
> > +                ICMPv6NDOptDstLLAddr(lladdr='00:00:00:00:10:02')
> > +        ")
> > +        echo $packet >> vif1.expected
> > +    fi
> > +}
> > +
> > +reset_pcap_and_expected() {
> > +    reset_pcap_file vif1 hv1/vif1
> > +    reset_pcap_file vif2 hv1/vif2
> > +
> > +    : > vif1.expected
> > +    : > vif2.expected
> > +}
> > +
> > +AS_BOX([Without port security])
> > +reset_pcap_and_expected
> > +
> > +test_arp no
> > +test_nd no
> > +
> > +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> > +OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [vif2.expected])
> > +
> > +AT_CHECK([ovs-ofctl dump-flows br-int table=OFTABLE_CHK_IN_PORT_SEC |
> ofctl_strip_all | sort | grep -v NXST_FLOW], [1])
> > +AT_CHECK([ovs-ofctl dump-flows br-int table=OFTABLE_CHK_IN_PORT_SEC_ND
> | ofctl_strip_all | sort | grep -v NXST_FLOW], [1])
> > +AT_CHECK([ovs-ofctl dump-flows br-int table=OFTABLE_CHK_OUT_PORT_SEC |
> ofctl_strip_all | sort | grep -v NXST_FLOW], [1])
> > +
> > +AS_BOX([With MAC only port security])
> > +reset_pcap_and_expected
> > +check ovn-nbctl --wait=hv lsp-set-port-security lsp1 "00:00:00:00:10:01"
> > +
> > +test_arp yes
> > +test_nd yes
> > +
> > +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> > +OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [vif2.expected])
> > +
> > +AT_CHECK([ovs-ofctl dump-flows br-int table=OFTABLE_CHK_IN_PORT_SEC |
> ofctl_strip_all | sort | grep -v NXST_FLOW], [0], [dnl
> > + table=OFTABLE_CHK_IN_PORT_SEC, priority=80,reg14=0x1,metadata=0x1
> actions=load:0x1->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC,
> priority=90,reg14=0x1,metadata=0x1,dl_src=00:00:00:00:10:01
> actions=resubmit(,OFTABLE_CHK_IN_PORT_SEC_ND)
> > + table=OFTABLE_CHK_IN_PORT_SEC, priority=95,arp,reg14=0x1,metadata=0x1
> actions=resubmit(,OFTABLE_CHK_IN_PORT_SEC_ND)
> > +])
> > +AT_CHECK([ovs-ofctl dump-flows br-int table=OFTABLE_CHK_IN_PORT_SEC_ND
> | ofctl_strip_all | sort | grep -v NXST_FLOW], [0], [dnl
> > + table=OFTABLE_CHK_IN_PORT_SEC_ND,
> priority=80,arp,reg14=0x1,metadata=0x1 actions=load:0x1->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC_ND,
> priority=80,icmp6,reg14=0x1,metadata=0x1,nw_ttl=255,icmp_type=135
> actions=load:0x1->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC_ND,
> priority=80,icmp6,reg14=0x1,metadata=0x1,nw_ttl=255,icmp_type=136
> actions=load:0x1->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC_ND,
> priority=90,arp,reg14=0x1,metadata=0x1,dl_src=00:00:00:00:10:01,arp_sha=00:00:00:00:10:01
> actions=load:0->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC_ND,
> priority=90,icmp6,reg14=0x1,metadata=0x1,dl_src=00:00:00:00:10:01,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:00
> actions=load:0->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC_ND,
> priority=90,icmp6,reg14=0x1,metadata=0x1,dl_src=00:00:00:00:10:01,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:10:01
> actions=load:0->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC_ND,
> priority=90,icmp6,reg14=0x1,metadata=0x1,dl_src=00:00:00:00:10:01,nw_ttl=255,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:00
> actions=load:0->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC_ND,
> priority=90,icmp6,reg14=0x1,metadata=0x1,dl_src=00:00:00:00:10:01,nw_ttl=255,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:10:01
> actions=load:0->NXM_NX_REG10[[12]]
> > +])
> > +AT_CHECK([ovs-ofctl dump-flows br-int table=OFTABLE_CHK_OUT_PORT_SEC |
> ofctl_strip_all | sort | grep -v NXST_FLOW], [0], [dnl
> > + table=OFTABLE_CHK_OUT_PORT_SEC, priority=80,reg15=0x1,metadata=0x1
> actions=load:0x1->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_OUT_PORT_SEC,
> priority=85,reg15=0x1,metadata=0x1,dl_dst=00:00:00:00:10:01
> actions=load:0->NXM_NX_REG10[[12]]
> > +])
> > +
> > +AS_BOX([With MAC + IP port security])
> > +reset_pcap_and_expected
> > +check ovn-nbctl --wait=hv lsp-set-port-security lsp1 "00:00:00:00:10:01
> 192.168.10.1 fd10::1"
> > +
> > +test_arp yes
> > +test_nd yes
> > +
> > +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> > +OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [vif2.expected])
> > +
> > +AT_CHECK([ovs-ofctl dump-flows br-int table=OFTABLE_CHK_IN_PORT_SEC |
> ofctl_strip_all | sort | grep -v NXST_FLOW], [0], [dnl
> > + table=OFTABLE_CHK_IN_PORT_SEC, priority=80,reg14=0x1,metadata=0x1
> actions=load:0x1->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC,
> priority=90,icmp6,reg14=0x1,metadata=0x1,dl_src=00:00:00:00:10:01,ipv6_src=::,ipv6_dst=ff02::/16,icmp_type=131,icmp_code=0
> actions=load:0->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC,
> priority=90,icmp6,reg14=0x1,metadata=0x1,dl_src=00:00:00:00:10:01,ipv6_src=::,ipv6_dst=ff02::/16,icmp_type=135,icmp_code=0
> actions=resubmit(,OFTABLE_CHK_IN_PORT_SEC_ND)
> > + table=OFTABLE_CHK_IN_PORT_SEC,
> priority=90,icmp6,reg14=0x1,metadata=0x1,dl_src=00:00:00:00:10:01,ipv6_src=::,ipv6_dst=ff02::/16,icmp_type=143,icmp_code=0
> actions=load:0->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC,
> priority=90,ip,reg14=0x1,metadata=0x1,dl_src=00:00:00:00:10:01,nw_src=192.168.10.1
> actions=load:0->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC,
> priority=90,ipv6,reg14=0x1,metadata=0x1,dl_src=00:00:00:00:10:01,ipv6_src=fd10::1
> actions=resubmit(,OFTABLE_CHK_IN_PORT_SEC_ND)
> > + table=OFTABLE_CHK_IN_PORT_SEC,
> priority=90,ipv6,reg14=0x1,metadata=0x1,dl_src=00:00:00:00:10:01,ipv6_src=fe80::200:ff:fe00:1001
> actions=resubmit(,OFTABLE_CHK_IN_PORT_SEC_ND)
> > + table=OFTABLE_CHK_IN_PORT_SEC,
> priority=90,udp,reg14=0x1,metadata=0x1,dl_src=00:00:00:00:10:01,nw_src=0.0.0.0,nw_dst=255.255.255.255,tp_src=68,tp_dst=67
> actions=load:0->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC, priority=95,arp,reg14=0x1,metadata=0x1
> actions=resubmit(,OFTABLE_CHK_IN_PORT_SEC_ND)
> > +])
> > +AT_CHECK([ovs-ofctl dump-flows br-int table=OFTABLE_CHK_IN_PORT_SEC_ND
> | ofctl_strip_all | sort | grep -v NXST_FLOW], [0], [dnl
> > + table=OFTABLE_CHK_IN_PORT_SEC_ND,
> priority=80,arp,reg14=0x1,metadata=0x1 actions=load:0x1->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC_ND,
> priority=80,icmp6,reg14=0x1,metadata=0x1,nw_ttl=255,icmp_type=135
> actions=load:0x1->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC_ND,
> priority=80,icmp6,reg14=0x1,metadata=0x1,nw_ttl=255,icmp_type=136
> actions=load:0x1->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC_ND,
> priority=90,arp,reg14=0x1,metadata=0x1,dl_src=00:00:00:00:10:01,arp_spa=192.168.10.1,arp_sha=00:00:00:00:10:01
> actions=load:0->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC_ND,
> priority=90,icmp6,reg14=0x1,metadata=0x1,dl_src=00:00:00:00:10:01,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:00
> actions=load:0->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC_ND,
> priority=90,icmp6,reg14=0x1,metadata=0x1,dl_src=00:00:00:00:10:01,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:10:01
> actions=load:0->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC_ND,
> priority=90,icmp6,reg14=0x1,metadata=0x1,dl_src=00:00:00:00:10:01,nw_ttl=255,icmp_type=136,icmp_code=0,nd_target=fd10::1,nd_tll=00:00:00:00:00:00
> actions=load:0->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC_ND,
> priority=90,icmp6,reg14=0x1,metadata=0x1,dl_src=00:00:00:00:10:01,nw_ttl=255,icmp_type=136,icmp_code=0,nd_target=fd10::1,nd_tll=00:00:00:00:10:01
> actions=load:0->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC_ND,
> priority=90,icmp6,reg14=0x1,metadata=0x1,dl_src=00:00:00:00:10:01,nw_ttl=255,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:1001,nd_tll=00:00:00:00:00:00
> actions=load:0->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_IN_PORT_SEC_ND,
> priority=90,icmp6,reg14=0x1,metadata=0x1,dl_src=00:00:00:00:10:01,nw_ttl=255,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:1001,nd_tll=00:00:00:00:10:01
> actions=load:0->NXM_NX_REG10[[12]]
> > +])
> > +AT_CHECK([ovs-ofctl dump-flows br-int table=OFTABLE_CHK_OUT_PORT_SEC |
> ofctl_strip_all | sort | grep -v NXST_FLOW], [0], [dnl
> > + table=OFTABLE_CHK_OUT_PORT_SEC, priority=80,reg15=0x1,metadata=0x1
> actions=load:0x1->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_OUT_PORT_SEC,
> priority=85,reg15=0x1,metadata=0x1,dl_dst=00:00:00:00:10:01
> actions=load:0->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_OUT_PORT_SEC,
> priority=90,ip,reg15=0x1,metadata=0x1,dl_dst=00:00:00:00:10:01
> actions=load:0x1->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_OUT_PORT_SEC,
> priority=90,ipv6,reg15=0x1,metadata=0x1,dl_dst=00:00:00:00:10:01
> actions=load:0x1->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_OUT_PORT_SEC,
> priority=95,ip,reg15=0x1,metadata=0x1,dl_dst=00:00:00:00:10:01,nw_dst=192.168.10.1
> actions=load:0->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_OUT_PORT_SEC,
> priority=95,ip,reg15=0x1,metadata=0x1,dl_dst=00:00:00:00:10:01,nw_dst=
> 224.0.0.0/4 actions=load:0->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_OUT_PORT_SEC,
> priority=95,ip,reg15=0x1,metadata=0x1,dl_dst=00:00:00:00:10:01,nw_dst=255.255.255.255
> actions=load:0->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_OUT_PORT_SEC,
> priority=95,ipv6,reg15=0x1,metadata=0x1,dl_dst=00:00:00:00:10:01,ipv6_dst=fd10::1
> actions=load:0->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_OUT_PORT_SEC,
> priority=95,ipv6,reg15=0x1,metadata=0x1,dl_dst=00:00:00:00:10:01,ipv6_dst=fe80::200:ff:fe00:1001
> actions=load:0->NXM_NX_REG10[[12]]
> > + table=OFTABLE_CHK_OUT_PORT_SEC,
> priority=95,ipv6,reg15=0x1,metadata=0x1,dl_dst=00:00:00:00:10:01,ipv6_dst=ff00::/8
> actions=load:0->NXM_NX_REG10[[12]]
> > +])
> > +
> > +
> > +AS_BOX([With MAC only port security, VRRPv3=any])
> > +reset_pcap_and_expected
> > +check ovn-nbctl --wait=hv lsp-set-port-security lsp1
> "00:00:00:00:10:01" "VRRPv3 00:00:00:00:10:01"
>
> It's good that we have all these combined tests, as that's how the feature
> will most likely be used, but we now lost all the coverage for VRRP-only
> cases.  We need at least a few, otherwise we're not checking from which
> of these entries the flows are coming from.
>

Ack, I have added a couple of checks for flows without the physical port
security in v6.


>
> Best regards, Ilya Maximets.
>
>
Thanks,
Ales
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to