On 2/9/26 7:45 PM, Ales Musil wrote:
>
>
> On Fri, Feb 6, 2026 at 11:21 PM Ilya Maximets <[email protected]
> <mailto:[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
> <https://datatracker.ietf.org/doc/html/rfc9568>
> > Reported-at: https://issues.redhat.com/browse/FDP-2979
> <https://issues.redhat.com/browse/FDP-2979>
> > Signed-off-by: Ales Musil <[email protected] <mailto:[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 <http://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.
Not really, proper initializers are much more common:
$ git grep 'struct [ a-z0-9_]* = {' | wc -l
161
$ git grep 'struct [ a-z0-9_]* = (struct [ a-z0-9_]*) {' | wc -l
23
But it's a minor thing, so I will not insist.
Best regards, Ilya Maximets.
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev