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
complaint add an option which when enabled will add extra flows
that match on the MAC address range specified by the RFC.

[0] https://datatracker.ietf.org/doc/html/rfc5798
Reported-at: https://issues.redhat.com/browse/FDP-2979
Signed-off-by: Ales Musil <[email protected]>
---
 NEWS               |   2 +
 controller/lflow.c |  67 ++++++++++++++++++++
 ovn-nb.xml         |   9 +++
 tests/ovn.at       | 149 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 227 insertions(+)

diff --git a/NEWS b/NEWS
index dc9d28f2c..e58f8ee1e 100644
--- a/NEWS
+++ b/NEWS
@@ -87,6 +87,8 @@ Post v25.09.0
      other_config column.
    - Introduce the capability to specify multiple ips for ovn-evpn-local-ip
      option.
+   - Add an LSP option called "port-security-rfc5798-compliant", this
+     allows VRRP v3 (RFC 5798) to work with port security enabled.
 
 OVN v25.09.0 - xxx xx xxxx
 --------------------------
diff --git a/controller/lflow.c b/controller/lflow.c
index b0998e605..93d9d090e 100644
--- a/controller/lflow.c
+++ b/controller/lflow.c
@@ -2551,6 +2551,11 @@ build_in_port_sec_arp_flows(const struct 
sbrec_port_binding *pb,
         return;
     }
 
+    bool vrrp_compliant =
+        smap_get_bool(&pb->options, "port-security-rfc5798-compliant", false);
+    const struct eth_addr vrrp_mac = ETH_ADDR_C(00,00,5e,00,01,00);
+    const struct eth_addr vrrp_mask = ETH_ADDR_C(ff,ff,ff,ff,ff,00);
+
     build_port_sec_allow_action(ofpacts);
 
     if (!ps_addr->n_ipv4_addrs) {
@@ -2561,6 +2566,9 @@ build_in_port_sec_arp_flows(const struct 
sbrec_port_binding *pb,
          * match - "inport == pb->port && eth.src == ps_addr.ea &&
          *          arp && arp.sha == ps_addr.ea"
          * action - "port_sec_failed = 0;"
+         * If port security is configured to be VRRP (RFC5798) compliant add
+         * an extra flow to match on
+         * arp.sha == 00:00:5e:00:01:00/ff:ff:ff:ff:ff:00
          */
         reset_match_for_port_sec_flows(pb, MFF_LOG_INPORT, m);
         match_set_dl_src(m, ps_addr->ea);
@@ -2569,6 +2577,13 @@ build_in_port_sec_arp_flows(const struct 
sbrec_port_binding *pb,
         ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
                         pb->header_.uuid.parts[0], m, ofpacts,
                         &pb->header_.uuid);
+
+        if (vrrp_compliant) {
+            match_set_arp_sha_masked(m, vrrp_mac, vrrp_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'
@@ -2577,6 +2592,9 @@ build_in_port_sec_arp_flows(const struct 
sbrec_port_binding *pb,
      * match - "inport == pb->port && eth.src == ps_addr.ea &&
      *         arp && arp.sha == ps_addr.ea && arp.spa == {ps_addr.ipv4_addrs}"
      * action - "port_sec_failed = 0;"
+     * If port security is configured to be VRRP (RFC5798) compliant add
+     * an extra flow to match on
+     * arp.sha == 00:00:5e:00:01:00/ff:ff:ff:ff:ff:00
      */
     for (size_t j = 0; j < ps_addr->n_ipv4_addrs; j++) {
         reset_match_for_port_sec_flows(pb, MFF_LOG_INPORT, m);
@@ -2594,6 +2612,13 @@ build_in_port_sec_arp_flows(const struct 
sbrec_port_binding *pb,
         ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
                         pb->header_.uuid.parts[0], m, ofpacts,
                         &pb->header_.uuid);
+
+        if (vrrp_compliant) {
+            match_set_arp_sha_masked(m, vrrp_mac, vrrp_mask);
+            ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
+                            pb->header_.uuid.parts[0], m, ofpacts,
+                            &pb->header_.uuid);
+        }
     }
 }
 
@@ -2701,6 +2726,11 @@ build_in_port_sec_nd_flows(const struct 
sbrec_port_binding *pb,
                            struct match *m, struct ofpbuf *ofpacts,
                            struct ovn_desired_flow_table *flow_table)
 {
+    bool vrrp_compliant =
+        smap_get_bool(&pb->options, "port-security-rfc5798-compliant", false);
+    const struct eth_addr vrrp_mac = ETH_ADDR_C(00,00,5e,00,02,00);
+    const struct eth_addr vrrp_mask = ETH_ADDR_C(ff,ff,ff,ff,ff,00);
+
     build_port_sec_allow_action(ofpacts);
 
     /* Add the below logical flow equivalent OF rules in 'in_port_sec_nd'
@@ -2710,6 +2740,9 @@ build_in_port_sec_nd_flows(const struct 
sbrec_port_binding *pb,
      *          icmp6 && icmp6.code == 135 && icmp6.type == 0 &&
      *          ip6.tll == 255 && nd.sll == {00:00:00:00:00:00, ps_addr.ea}"
      * action - "port_sec_failed = 0;"
+     * If port security is configured to be VRRP (RFC5798) compliant add
+     * an extra flow to match on
+     * nd.sll == 00:00:5e:00:02:00/ff:ff:ff:ff:ff:00
      */
     reset_match_for_port_sec_flows(pb, MFF_LOG_INPORT, m);
     match_set_dl_type(m, htons(ETH_TYPE_IPV6));
@@ -2728,6 +2761,13 @@ build_in_port_sec_nd_flows(const struct 
sbrec_port_binding *pb,
                     pb->header_.uuid.parts[0], m, ofpacts,
                     &pb->header_.uuid);
 
+    if (vrrp_compliant) {
+        match_set_arp_sha_masked(m, vrrp_mac, vrrp_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_icmp_type(m, 136);
     match_set_icmp_code(m, 0);
     if (ps_addr->n_ipv6_addrs) {
@@ -2739,6 +2779,9 @@ build_in_port_sec_nd_flows(const struct 
sbrec_port_binding *pb,
          *          nd.tll == {00:00:00:00:00:00, ps_addr.ea} &&
          *          nd.target == {ps_addr.ipv6_addrs, lla}"
          * action - "port_sec_failed = 0;"
+         * If port security is configured to be VRRP (RFC5798) compliant add
+         * an extra flow to match on
+         * nd.tll == 00:00:5e:00:02:00/ff:ff:ff:ff:ff:00
          */
         struct in6_addr lla;
         in6_generate_lla(ps_addr->ea, &lla);
@@ -2754,6 +2797,13 @@ build_in_port_sec_nd_flows(const struct 
sbrec_port_binding *pb,
                         pb->header_.uuid.parts[0], m, ofpacts,
                         &pb->header_.uuid);
 
+        if (vrrp_compliant) {
+            match_set_arp_tha_masked(m, vrrp_mac, vrrp_mask);
+            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);
@@ -2781,6 +2831,13 @@ build_in_port_sec_nd_flows(const struct 
sbrec_port_binding *pb,
             ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
                             pb->header_.uuid.parts[0], m, ofpacts,
                             &pb->header_.uuid);
+
+            if (vrrp_compliant) {
+                match_set_arp_tha_masked(m, vrrp_mac, vrrp_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'
@@ -2790,6 +2847,9 @@ build_in_port_sec_nd_flows(const struct 
sbrec_port_binding *pb,
          *          icmp6.code == 136 && icmp6.type == 0 && ip6.tll == 255 &&
          *          nd.tll == {00:00:00:00:00:00, ps_addr.ea}"
          * action - "port_sec_failed = 0;"
+         * If port security is configured to be VRRP (RFC5798) compliant add
+         * extra an flow to match on
+         * nd.tll == 00:00:5e:00:02:00/ff:ff:ff:ff:ff:00
          */
         match_set_arp_tha(m, eth_addr_zero);
         ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
@@ -2800,6 +2860,13 @@ build_in_port_sec_nd_flows(const struct 
sbrec_port_binding *pb,
         ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
                         pb->header_.uuid.parts[0], m, ofpacts,
                         &pb->header_.uuid);
+
+        if (vrrp_compliant) {
+            match_set_arp_tha_masked(m, vrrp_mac, vrrp_mask);
+            ofctrl_add_flow(flow_table, OFTABLE_CHK_IN_PORT_SEC_ND, 90,
+                            pb->header_.uuid.parts[0], m, ofpacts,
+                            &pb->header_.uuid);
+        }
     }
 }
 
diff --git a/ovn-nb.xml b/ovn-nb.xml
index e74c0d010..4e95a13b6 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -1674,6 +1674,15 @@
             <ref table="Interface" db="vswitch"/> table.  This in turn will
             make OVS vswitchd update the MTU of the linked interface.
           </column>
+
+          <column name="options" key="port-security-rfc5798-compliant"
+                  type='{"type": "boolean"}'>
+            Allows the inner ARP SHA or ND TLL/SLL to be within the range of
+            MAC addresses specified by RFC 5798. The result is a masked match
+            that allows the whole range of MAC addresses to pass through.
+            This option is only applicable if <ref column="port_security"/>
+            is populated.
+          </column>
         </group>
       </group>
 
diff --git a/tests/ovn.at b/tests/ovn.at
index d5ee90e17..4455b7580 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -44181,3 +44181,152 @@ 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 - RFC 5798])
+AT_SKIP_IF([test $HAVE_SCAPY = no])
+ovn_start
+net_add n1
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+
+check ovn-nbctl ls-add ls
+
+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"
+
+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"
+
+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')
+    ")
+    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')
+        ")
+    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:ff:ff:ff', 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')
+    ")
+    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') /
+        IPv6(src='fd10::1', dst='ff01::1') /
+        ICMPv6ND_NA(tgt='fd10::1') /
+        ICMPv6NDOptDstLLAddr(lladdr='00:00:5e:00:02:05')
+    ")
+    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])
+
+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])
+
+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])
+
+AS_BOX([With MAC only port security, compatibility enabled])
+reset_pcap_and_expected
+check ovn-nbctl lsp-set-options lsp1 port-security-rfc5798-compliant=true
+check ovn-nbctl --wait=hv lsp-set-port-security lsp1 "00:00:00:00:10:01"
+
+test_arp no
+test_nd no
+
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
+OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [vif2.expected])
+
+AS_BOX([With MAC + IP port security, compatibility enabled])
+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 no
+test_nd no
+
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
+OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [vif2.expected])
+
+OVN_CLEANUP([hv1])
+
+AT_CLEANUP
+])
-- 
2.52.0

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to