On 13.08.2024 00:14, Dumitru Ceara wrote:
On 8/12/24 18:21, Martin Kalcok wrote:
This change adds two new LRP options:
  * routing-protocol-redirect
  * routing-protocols

These allow redirection of a routing protocol traffic to
an Logical Switch Port. This enables external routing daemons
to listen on an interface bound to an LSP and effectively act
as if they were listening on (and speaking from) LRP's IP address.

Option 'routing-protocols' takes a comma-separated list of routing
protocols whose traffic should be redirected. Currently supported
are BGP (tcp 179) and BFD (udp 3784).

Option 'routing-protocol-redirect' expects a string with an LSP
name.

When both of these options are set, any traffic entering LS
that's destined for LRP's IP addresses (including IPv6 LLA) and
routing protocol's port number, is redirected to the LSP specified
in the 'routing-protocol-redirect' value.

NOTE: this feature is experimental and may be subject to
removal/change in the future.

Signed-off-by: Martin Kalcok<[email protected]>
---

  v9 contains small changes based on the review of v8:
  * Simplified search for the port specified in 'routing-protocol-redirect',
    using 'ovn_port_find'
    * As a result this change a new possible warning was added when LRP
      is not connected to the same LS as LSP specified in
      'routing-protocol-redirect'.
  * Datapath test for this feature now includes verification of BFD's
    UDP traffic.
    * These tests required some more care as Ncat produced false positive
      results even when sending to a port where nothing was listening. My
      assumption is that Ncat tries to assert succes of a UDP connection
      based on lack of ICMP Port Unreachable message, and LR probably
      does not generate these?
  * nit: null pointer checks changed from 'if (p == NULL)' to 'if (!p)'
    for consistency.
This version looks good to me!

Acked-by: Dumitru Ceara<[email protected]>

Vladislav, do you happen to have some time to try this version out on
your end too?

Yes, just now finished testing.

With centralized routing the BGP and BFD works well (in my setup there are two VRFs with BGP and BFD peerings configured as a haipnit inside one node to each other):

# sh run
<...snip...>
router bgp 64512 vrf dxvif-62C25580
 bgp router-id 169.254.252.1
 no bgp ebgp-requires-policy
 no bgp network import-check
 neighbor 169.254.252.2 remote-as 64513
 neighbor 169.254.252.2 bfd
exit
!
router bgp 64513 vrf dxvif-9ED34880
 bgp router-id 169.254.252.2
 no bgp ebgp-requires-policy
 no bgp network import-check
 neighbor 169.254.252.1 remote-as 64512
 neighbor 169.254.252.1 bfd
 !
 address-family ipv4 unicast
  network 10.0.0.0/24
 exit-address-family
exit

# sh ip bgp vrf all

Instance dxvif-62C25580:
BGP table version is 1, local router ID is 169.254.252.1, vrf id 760
Default local pref 100, local AS 64512
Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found

   Network          Next Hop            Metric LocPrf Weight Path
*> 10.0.0.0/24      169.254.252.2            0             0 64513 i

Displayed  1 routes and 1 total paths

Instance dxvif-9ED34880:
BGP table version is 1, local router ID is 169.254.252.2, vrf id 763
Default local pref 100, local AS 64513
Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found

   Network          Next Hop            Metric LocPrf Weight Path
*> 10.0.0.0/24      0.0.0.0                  0         32768 i

Displayed  1 routes and 1 total paths

# sh bfd peers
BFD Peers:
    peer 169.254.252.1 local-address 169.254.252.2 vrf dxvif-9ED34880 interface dx-9ED34880-v
        ID: 3199966869
        Remote ID: 2401437121
        Active mode
        Status: up
        Uptime: 26 second(s)
        Diagnostics: ok
        Remote diagnostics: ok
        Peer Type: dynamic
        RTT min/avg/max: 0/0/0 usec
        Local timers:
            Detect-multiplier: 3
            Receive interval: 300ms
            Transmission interval: 300ms
            Echo receive interval: 50ms
            Echo transmission interval: disabled
        Remote timers:
            Detect-multiplier: 3
            Receive interval: 300ms
            Transmission interval: 300ms
            Echo receive interval: 50ms

    peer 169.254.252.2 local-address 169.254.252.1 vrf dxvif-62C25580 interface dx-62C25580-v
        ID: 2401437121
        Remote ID: 3199966869
        Active mode
        Status: up
        Uptime: 26 second(s)
        Diagnostics: ok
        Remote diagnostics: ok
        Peer Type: dynamic
        RTT min/avg/max: 0/0/0 usec
        Local timers:
            Detect-multiplier: 3
            Receive interval: 300ms
            Transmission interval: 300ms
            Echo receive interval: 50ms
            Echo transmission interval: disabled
        Remote timers:
            Detect-multiplier: 3
            Receive interval: 300ms
            Transmission interval: 300ms
            Echo receive interval: 50ms

At this point this looks good with a note, that for LB VIPs and NAT addresses we do use ha-chassis-group with primary/secondary chassis, which is currently not supported by the redirect feature.

This probably should be somehow addressed in a future development.

Tested-by: Vladislav Odintsov <[email protected]>


Mark, Numan, Han, as discussed before branching, I think it's fine to
include this experimental feature on branch 24.09 (and in v24.09.0) too.

Do you guys agree?

Thanks,
Dumitru

  northd/northd.c         | 231 ++++++++++++++++++++++++++++++++++++++++
  northd/northd.h         |   7 ++
  northd/ovn-northd.8.xml |  54 ++++++++++
  ovn-nb.xml              |  42 ++++++++
  tests/ovn-northd.at     |  93 ++++++++++++++++
  tests/system-ovn.at     | 149 ++++++++++++++++++++++++++
  6 files changed, 576 insertions(+)

diff --git a/northd/northd.c b/northd/northd.c
index 5ad30d854..8a240d93d 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -14002,6 +14002,234 @@ build_arp_resolve_flows_for_lrp(struct ovn_port *op,
      }
  }
+static void
+build_routing_protocols_redirect_rule__(
+        const char *s_addr, const char *redirect_port_name, int protocol_port,
+        const char *proto, bool is_ipv6, struct ovn_port *ls_peer,
+        struct lflow_table *lflows, struct ds *match, struct ds *actions,
+        struct lflow_ref *lflow_ref)
+{
+    int ip_ver = is_ipv6 ? 6 : 4;
+    ds_clear(actions);
+    ds_put_format(actions, "outport = \"%s\"; output;", redirect_port_name);
+
+    /* Redirect packets in the input pipeline destined for LR's IP
+     * and the routing protocol's port to the LSP specified in
+     * 'routing-protocol-redirect' option.*/
+    ds_clear(match);
+    ds_put_format(match, "ip%d.dst == %s && %s.dst == %d", ip_ver, s_addr,
+                  proto, protocol_port);
+    ovn_lflow_add(lflows, ls_peer->od, S_SWITCH_IN_L2_LKUP, 100,
+                  ds_cstr(match),
+                  ds_cstr(actions),
+                  lflow_ref);
+
+    /* To accomodate "peer" nature of the routing daemons, redirect also
+     * replies to the daemons' client requests. */
+    ds_clear(match);
+    ds_put_format(match, "ip%d.dst == %s && %s.src == %d", ip_ver, s_addr,
+                  proto, protocol_port);
+    ovn_lflow_add(lflows, ls_peer->od, S_SWITCH_IN_L2_LKUP, 100,
+                  ds_cstr(match),
+                  ds_cstr(actions),
+                  lflow_ref);
+}
+
+static void
+apply_routing_protocols_redirect__(
+        const char *s_addr, const char *redirect_port_name, int protocol_flags,
+        bool is_ipv6, struct ovn_port *ls_peer, struct lflow_table *lflows,
+        struct ds *match, struct ds *actions, struct lflow_ref *lflow_ref)
+{
+    if (protocol_flags & REDIRECT_BGP) {
+        build_routing_protocols_redirect_rule__(s_addr, redirect_port_name,
+                                                179, "tcp", is_ipv6, ls_peer,
+                                                lflows, match, actions,
+                                                lflow_ref);
+    }
+
+    if (protocol_flags & REDIRECT_BFD) {
+        build_routing_protocols_redirect_rule__(s_addr, redirect_port_name,
+                                                3784, "udp", is_ipv6, ls_peer,
+                                                lflows, match, actions,
+                                                lflow_ref);
+    }
+
+    /* Because the redirected port shares IP and MAC addresses with the LRP,
+     * special consideration needs to be given to the signaling protocols. */
+    ds_clear(actions);
+    ds_put_format(actions,
+                 "clone { outport = \"%s\"; output; }; "
+                 "outport = %s; output;",
+                  redirect_port_name, ls_peer->json_key);
+    if (is_ipv6) {
+        /* Ensure that redirect port receives copy of NA messages destined to
+         * its IP.*/
+        ds_clear(match);
+        ds_put_format(match, "ip6.dst == %s && nd_na", s_addr);
+        ovn_lflow_add(lflows, ls_peer->od, S_SWITCH_IN_L2_LKUP, 100,
+                      ds_cstr(match),
+                      ds_cstr(actions),
+                      lflow_ref);
+    } else {
+        /* Ensure that redirect port receives copy of ARP replies destined to
+         * its IP */
+        ds_clear(match);
+        ds_put_format(match, "arp.op == 2 && arp.tpa == %s", s_addr);
+        ovn_lflow_add(lflows, ls_peer->od, S_SWITCH_IN_L2_LKUP, 100,
+                      ds_cstr(match),
+                      ds_cstr(actions),
+                      lflow_ref);
+    }
+}
+
+static int
+parse_redirected_routing_protocols(struct ovn_port *lrp) {
+    int redirected_protocol_flags = 0;
+    const char *redirect_protocols = smap_get(&lrp->nbrp->options,
+                                              "routing-protocols");
+    if (!redirect_protocols) {
+        return redirected_protocol_flags;
+    }
+
+    char *proto;
+    char *save_ptr = NULL;
+    char *tokstr = xstrdup(redirect_protocols);
+    for (proto = strtok_r(tokstr, ",", &save_ptr); proto != NULL;
+         proto = strtok_r(NULL, ",", &save_ptr)) {
+        if (!strcmp(proto, "BGP")) {
+            redirected_protocol_flags |= REDIRECT_BGP;
+            continue;
+        }
+
+        if (!strcmp(proto, "BFD")) {
+            redirected_protocol_flags |= REDIRECT_BFD;
+            continue;
+        }
+
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "Option 'routing-protocols' encountered unknown "
+                          "value %s",
+                          proto);
+    }
+    free(tokstr);
+    return redirected_protocol_flags;
+}
+
+static void
+build_lrouter_routing_protocol_redirect(
+        struct ovn_port *op, struct lflow_table *lflows, struct ds *match,
+        struct ds *actions, struct lflow_ref *lflow_ref,
+        const struct hmap *ls_ports)
+{
+    /* LRP has to have a peer.*/
+    if (!op->peer) {
+        return;
+    }
+
+    /* LRP has to have NB record.*/
+    if (!op->nbrp) {
+        return;
+    }
+
+    /* Proceed only for LRPs that have 'routing-protocol-redirect' option set.
+     * Value of this option is the name of LSP to which the routing protocol
+     * traffic will be redirected. */
+    const char *redirect_port_name = smap_get(&op->nbrp->options,
+                                              "routing-protocol-redirect");
+    if (!redirect_port_name) {
+        return;
+    }
+
+    if (op->cr_port) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "Option 'routing-protocol-redirect' is not "
+                          "supported on Distributed Gateway Port '%s'",
+                          op->key);
+        return;
+    }
+
+    /* Ensure that LSP, to which the routing protocol traffic is redirected,
+     * exists. */
+    struct ovn_port *lsp_in_peer = ovn_port_find(ls_ports,
+                                                 redirect_port_name);
+    if (!lsp_in_peer) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "Option 'routing-protocol-redirect' set on Logical "
+                          "Router Port '%s' refers to non-existent Logical "
+                          "Switch Port. Routing protocol redirecting won't be "
+                          "configured.",
+                          op->key);
+        return;
+    }
+    if (lsp_in_peer->od != op->peer->od) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "Logical Router Port '%s' is connected to a "
+                          "different switch than the Logical Switch Port "
+                          "'%s' defined in its 'routing-protocol-redirect' "
+                          "option. Routing protocol redirecting won't be "
+                          "configured.",
+                          op->key, redirect_port_name);
+        return;
+    }
+
+    int redirected_protocols = parse_redirected_routing_protocols(op);
+    if (!redirected_protocols) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "Option 'routing-protocol-redirect' is set on "
+                          "Logical Router Port '%s' but no known protocols "
+                          "were set via 'routing-protocols' options. This "
+                          "configuration has no effect.",
+                          op->key);
+        return;
+    }
+
+    /* Redirect traffic destined for LRP's IPs and the specified routing
+     * protocol ports to the port defined in 'routing-protocol-redirect'
+     * option.*/
+    for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+        const char *ip_s = op->lrp_networks.ipv4_addrs[i].addr_s;
+        apply_routing_protocols_redirect__(ip_s, redirect_port_name,
+                                           redirected_protocols, false,
+                                           op->peer, lflows, match, actions,
+                                           lflow_ref);
+    }
+    for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
+        const char *ip_s = op->lrp_networks.ipv6_addrs[i].addr_s;
+        apply_routing_protocols_redirect__(ip_s, redirect_port_name,
+                                           redirected_protocols, true,
+                                           op->peer, lflows, match, actions,
+                                           lflow_ref);
+    }
+
+    /* Drop ARP replies and IPv6 RA/NA packets originating from
+     * 'routing-protocol-redirect' LSP. As this port shares IP and MAC
+     * addresses with LRP, we don't want to create duplicates.*/
+    ds_clear(match);
+    ds_put_format(match, "inport == \"%s\" && arp.op == 2",
+                  redirect_port_name);
+    ovn_lflow_add(lflows, op->peer->od, S_SWITCH_IN_CHECK_PORT_SEC, 80,
+                  ds_cstr(match),
+                  REGBIT_PORT_SEC_DROP " = 1; next;",
+                  lflow_ref);
+
+    ds_clear(match);
+    ds_put_format(match, "inport == \"%s\" && nd_na",
+                  redirect_port_name);
+    ovn_lflow_add(lflows, op->peer->od, S_SWITCH_IN_CHECK_PORT_SEC, 80,
+                  ds_cstr(match),
+                  REGBIT_PORT_SEC_DROP " = 1; next;",
+                  lflow_ref);
+
+    ds_clear(match);
+    ds_put_format(match, "inport == \"%s\" && nd_ra",
+                  redirect_port_name);
+    ovn_lflow_add(lflows, op->peer->od, S_SWITCH_IN_CHECK_PORT_SEC, 80,
+                  ds_cstr(match),
+                  REGBIT_PORT_SEC_DROP " = 1; next;",
+                  lflow_ref);
+}
+
  /* This function adds ARP resolve flows related to a LSP. */
  static void
  build_arp_resolve_flows_for_lsp(
@@ -16969,6 +17197,9 @@ build_lswitch_and_lrouter_iterate_by_lrp(struct 
ovn_port *op,
                                  op->lflow_ref);
      build_lrouter_icmp_packet_toobig_admin_flows(op, lsi->lflows, &lsi->match,
                                                   &lsi->actions, 
op->lflow_ref);
+    build_lrouter_routing_protocol_redirect(op, lsi->lflows, &lsi->match,
+                                            &lsi->actions, op->lflow_ref,
+                                            lsi->ls_ports);
  }
static void *
diff --git a/northd/northd.h b/northd/northd.h
index 6e0258ff4..c7f0b829b 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -93,6 +93,13 @@ ovn_datapath_find_by_key(struct hmap *datapaths, uint32_t 
dp_key);
bool od_has_lb_vip(const struct ovn_datapath *od); +/* List of routing and routing-related protocols which
+ * OVN is capable of redirecting from LRP to specific LSP. */
+enum redirected_routing_protcol_flag_type {
+    REDIRECT_BGP = (1 << 0),
+    REDIRECT_BFD = (1 << 1),
+};
+
  struct tracked_ovn_ports {
      /* tracked created ports.
       * hmapx node data is 'struct ovn_port *' */
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 3abd5f75b..ede38882a 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -284,6 +284,32 @@
          dropped in the next stage.
        </li>
+ <li>
+        <p>
+          For each logical port that's defined as a target of routing protocol
+          redirecting (via <code>routing-protocol-redirect</code> option set on
+          Logical Router Port), a filter is set in place that disallows
+          following traffic exiting this port:
+        </p>
+        <ul>
+          <li>
+            ARP replies
+          </li>
+          <li>
+            IPv6 Neighbor Discovery - Router Advertisements
+          </li>
+          <li>
+            IPv6 Neighbor Discovery - Neighbor Advertisements
+          </li>
+        </ul>
+        <p>
+          Since this port shares IP and MAC addresses with the Logical Router
+          Port, we wan't to prevent duplicate replies and advertisements. This
+          is achieved by a rule with priority 80 that sets
+          <code>REGBIT_PORT_SEC_DROP" = 1; next;"</code>.
+        </p>
+      </li>
+
        <li>
          For each (enabled) vtep logical port, a priority 70 flow is added 
which
          matches on all packets and applies the action
@@ -2002,6 +2028,34 @@ output;
          on the logical switch.
        </li>
+ <li>
+        <p>
+          For any logical port that's defined as a target of routing protocol
+          redirecting (via <code>routing-protocol-redirect</code> option set on
+          Logical Router Port), we redirect the traffic related to protocols
+          specified in <code>routing-protocols</code> option. It's acoomplished
+          with following priority-100 flows:
+        </p>
+        <ul>
+          <li>
+            Flows that match Logical Router Port's IPs and destination port of
+            the routing daemon are redirected to this port to allow external
+            peers' connection to the daemon listening on this port.
+          </li>
+          <li>
+            Flows that match Logical Router Port's IPs and source port of
+            the routing daemon are redirected to this port to allow replies
+            from the peers.
+          </li>
+        </ul>
+        <p>
+          In addition to this, we add priority-100 rules that
+          <code>clone</code> ARP replies and IPv6 Neighbor Advertisements to
+          this port as well. These allow to build proper ARP/IPv6 neighbor
+          list on this port.
+        </p>
+      </li>
+
        <li>
          Priority-90 flows for transit switches that forward registered
          IP multicast traffic to their corresponding multicast group , which
diff --git a/ovn-nb.xml b/ovn-nb.xml
index bbda423a5..2836f58f5 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -3575,6 +3575,48 @@ or
          </p>
        </column>
+ <column name="options" key="routing-protocol-redirect"
+              type='{"type": "string"}'>
+        <p>
+          NOTE: this feature is experimental and may be subject to
+          removal/change in the future.
+        </p>
+        <p>
+          This option expects a name of a Logical Switch Port that's present
+          in the peer's Logical Switch. If set, it causes any traffic
+          that's destined for Logical Router Port's IP addresses (including
+          its IPv6 LLA) and the ports associated with routing protocols defined
+          ip <code>routing-protocols</code> option, to be redirected
+          to the specified Logical Switch Port.
+
+          This allows external routing daemons to be bound to a port in OVN's
+          Logical Switch and act as if they were listening on Logical Router
+          Port's IP addresses.
+        </p>
+      </column>
+
+      <column name="options" key="routing-protocols" type='{"type": "string"}'>
+        <p>
+          NOTE: this feature is experimental and may be subject to
+          removal/change in the future.
+        </p>
+        <p>
+          This option expects a comma-separated list of routing, and
+          routing-related protocols, whose control plane traffic will be
+          redirected to a port specified in
+          <code>routing-protocol-redirect</code> option. Currently supported
+          options are:
+        </p>
+        <ul>
+          <li>
+            <code>BGP</code> (forwards TCP port 179)
+          </li>
+          <li>
+            <code>BFD</code> (forwards UDP port 3784)
+          </li>
+        </ul>
+      </column>
+
        <column name="options" key="gateway_mtu_bypass">
          <p>
            When configured, represents a match expression, in the same
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index e4c882265..b32639a81 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -13761,3 +13761,96 @@ AT_CHECK([grep -e "172.168.0.110" -e "172.168.0.120" -e 
"10.0.0.3" -e "20.0.0.3"
AT_CLEANUP
  ])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([Routing protocol control plane redirect])
+ovn_start
+
+check ovn-sbctl chassis-add hv1 geneve 127.0.0.1
+
+check ovn-nbctl lr-add lr -- \
+    lrp-add lr lr-ls 02:ac:10:01:00:01 172.16.1.1/24
+check ovn-nbctl --wait=sb set logical_router lr options:chassis=hv1
+
+check ovn-nbctl ls-add ls -- \
+    lsp-add ls ls-lr -- \
+    lsp-set-type ls-lr router -- \
+    lsp-set-addresses ls-lr router -- \
+    lsp-set-options ls-lr router-port=lr-ls
+
+check ovn-nbctl lsp-add ls lsp-bgp -- \
+    lsp-set-addresses lsp-bgp unknown
+
+# Function that ensures that no redirect rules are installed.
+check_no_redirect() {
+    AT_CHECK([ovn-sbctl dump-flows ls | grep ls_in_l2_lkup  | grep -E "tcp.dst == 
179|tcp.src == 179" | wc -l], [0], [0
+])
+
+    AT_CHECK([ovn-sbctl dump-flows ls | grep ls_in_check_port_sec | grep -E 
"priority=80" | wc -l], [0], [0
+])
+    check_no_bfd_redirect
+}
+
+# Function that ensures that no BFD redirect rules are installed.
+check_no_bfd_redirect() {
+    AT_CHECK([ovn-sbctl dump-flows ls | grep ls_in_l2_lkup  | grep -E "udp.dst == 
3784|udp.src == 3784" | wc -l], [0], [0
+])
+}
+
+# By default, no rules related to routing protocol redirect are present
+check_no_redirect
+
+# Set "lsp-bgp" port as target of BGP control plane redirected traffic
+check ovn-nbctl --wait=sb set logical_router_port lr-ls 
options:routing-protocol-redirect=lsp-bgp
+check ovn-nbctl --wait=sb set logical_router_port lr-ls 
options:routing-protocols=BGP
+
+# Check that BGP control plane traffic is redirected "lsp-bgp"
+AT_CHECK([ovn-sbctl dump-flows ls | grep ls_in_l2_lkup | grep -E "tcp.dst == 
179|tcp.src == 179" | ovn_strip_lflows], [0], [dnl
+  table=??(ls_in_l2_lkup      ), priority=100  , match=(ip4.dst == 172.16.1.1 && tcp.dst 
== 179), action=(outport = "lsp-bgp"; output;)
+  table=??(ls_in_l2_lkup      ), priority=100  , match=(ip4.dst == 172.16.1.1 && tcp.src 
== 179), action=(outport = "lsp-bgp"; output;)
+  table=??(ls_in_l2_lkup      ), priority=100  , match=(ip6.dst == fe80::ac:10ff:fe01:1 
&& tcp.dst == 179), action=(outport = "lsp-bgp"; output;)
+  table=??(ls_in_l2_lkup      ), priority=100  , match=(ip6.dst == fe80::ac:10ff:fe01:1 
&& tcp.src == 179), action=(outport = "lsp-bgp"; output;)
+])
+
+# Check that ARP/ND traffic is cloned to the "lsp-bgp"
+AT_CHECK([ovn-sbctl dump-flows ls | grep ls_in_l2_lkup | grep "arp.op == 2 && 
arp.tpa == 172.16.1.1" | ovn_strip_lflows], [0], [dnl
+  table=??(ls_in_l2_lkup      ), priority=100  , match=(arp.op == 2 && arp.tpa == 172.16.1.1), 
action=(clone { outport = "lsp-bgp"; output; }; outport = "ls-lr"; output;)
+])
+AT_CHECK([ovn-sbctl dump-flows ls | grep ls_in_l2_lkup | grep "&& nd_na" | 
ovn_strip_lflows], [0], [dnl
+  table=??(ls_in_l2_lkup      ), priority=100  , match=(ip6.dst == fe80::ac:10ff:fe01:1 && nd_na), 
action=(clone { outport = "lsp-bgp"; output; }; outport = "ls-lr"; output;)
+])
+
+# Check that at this point no BFD redirecting is present
+check_no_bfd_redirect
+
+# Add BFD traffic redirect
+check ovn-nbctl --wait=sb set logical_router_port lr-ls 
options:routing-protocols=BGP,BFD
+
+# Check that BFD traffic is redirected to "lsp-bgp"
+AT_CHECK([ovn-sbctl dump-flows ls | grep ls_in_l2_lkup | grep -E "udp.dst == 
3784|udp.src == 3784" | ovn_strip_lflows], [0], [dnl
+  table=??(ls_in_l2_lkup      ), priority=100  , match=(ip4.dst == 172.16.1.1 && udp.dst 
== 3784), action=(outport = "lsp-bgp"; output;)
+  table=??(ls_in_l2_lkup      ), priority=100  , match=(ip4.dst == 172.16.1.1 && udp.src 
== 3784), action=(outport = "lsp-bgp"; output;)
+  table=??(ls_in_l2_lkup      ), priority=100  , match=(ip6.dst == fe80::ac:10ff:fe01:1 
&& udp.dst == 3784), action=(outport = "lsp-bgp"; output;)
+  table=??(ls_in_l2_lkup      ), priority=100  , match=(ip6.dst == fe80::ac:10ff:fe01:1 
&& udp.src == 3784), action=(outport = "lsp-bgp"; output;)
+])
+
+
+# Check that ARP replies and ND advertisements are blocked from exiting 
"lsp-bgp"
+AT_CHECK([ovn-sbctl dump-flows ls | grep ls_in_check_port_sec | grep 
"priority=80" | ovn_strip_lflows], [0], [dnl
+  table=??(ls_in_check_port_sec), priority=80   , match=(inport == "lsp-bgp" 
&& arp.op == 2), action=(reg0[[15]] = 1; next;)
+  table=??(ls_in_check_port_sec), priority=80   , match=(inport == "lsp-bgp" 
&& nd_na), action=(reg0[[15]] = 1; next;)
+  table=??(ls_in_check_port_sec), priority=80   , match=(inport == "lsp-bgp" 
&& nd_ra), action=(reg0[[15]] = 1; next;)
+])
+
+# Remove 'bgp-redirect' option from LRP and check that rules are removed
+check ovn-nbctl --wait=sb remove logical_router_port lr-ls options 
routing-protocol-redirect
+check ovn-nbctl --wait=sb remove logical_router_port lr-ls options 
routing-protocols
+check_no_redirect
+
+# Set non-existent LSP as target of 'bgp-redirect' and check that no rules are 
added
+check ovn-nbctl --wait=sb set logical_router_port lr-ls 
options:routing-protocol-redirect=lsp-foo
+check ovn-nbctl --wait=sb set logical_router_port lr-ls 
options:routing-protocols=BGP,BFD
+check_no_redirect
+
+AT_CLEANUP
+])
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index c54b0f3a5..6e4ec4247 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -13750,3 +13750,152 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
  /.*terminating with signal 15.*/d"])
  AT_CLEANUP
  ])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Routing protocol redirect])
+AT_SKIP_IF([test $HAVE_NC = no])
+
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+
+ADD_BR([br-int])
+ADD_BR([br-ext])
+
+check ovs-ofctl add-flow br-ext action=normal
+# Set external-ids in br-int needed for ovn-controller
+check ovs-vsctl \
+        -- set Open_vSwitch . external-ids:system-id=hv1 \
+        -- set Open_vSwitch . 
external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+# Start ovn-controller
+start_daemon ovn-controller
+
+check ovn-nbctl lr-add R1 \
+    -- set Logical_Router R1 options:chassis=hv1
+
+check ovn-nbctl ls-add public
+check ovn-nbctl ls-add bar
+
+check ovn-nbctl lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24
+check ovn-nbctl lrp-add R1 rp-bar 00:00:ff:00:00:01 192.168.10.1/24
+
+check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port public-rp \
+    type=router options:router-port=rp-public \
+    -- lsp-set-addresses public-rp router
+
+check ovn-nbctl lsp-add bar bar-rp -- set Logical_Switch_Port bar-rp \
+    type=router options:router-port=rp-bar \
+    -- lsp-set-addresses bar-rp router
+
+check ovn-nbctl lsp-add public bgp-daemon \
+    -- lsp-set-addresses bgp-daemon unknown
+
+# Setup container "bar1" representing host on an internal network
+ADD_NAMESPACES(bar1)
+ADD_VETH(bar1, bar1, br-int, "192.168.10.2/24", "00:00:ff:ff:ff:01", \
+         "192.168.10.1")
+check ovn-nbctl lsp-add bar bar1 \
+    -- lsp-set-addresses bar1 "00:00:ff:ff:ff:01 192.168.10.2"
+
+# Setup SNAT for the internal host
+check ovn-nbctl lr-nat-add R1 snat 172.16.1.1 192.168.10.2
+
+# Configure external connectivity
+check ovs-vsctl set Open_vSwitch . 
external-ids:ovn-bridge-mappings=phynet:br-ext
+check ovn-nbctl lsp-add public public1 \
+        -- lsp-set-addresses public1 unknown \
+        -- lsp-set-type public1 localnet \
+        -- lsp-set-options public1 network_name=phynet
+
+check ovn-nbctl --wait=hv sync
+
+# Set option that redirects BGP and BFD traffic to a LSP "bgp-daemon"
+check ovn-nbctl --wait=sb set logical_router_port rp-public 
options:routing-protocol-redirect=bgp-daemon
+check ovn-nbctl --wait=sb set logical_router_port rp-public 
options:routing-protocols=BGP,BFD
+
+# Create "bgp-daemon" interface in a namespace with IP and MAC matching LRP 
"rp-public"
+ADD_NAMESPACES(bgp-daemon)
+ADD_VETH(bgp-daemon, bgp-daemon, br-int, "172.16.1.1/24", "00:00:02:01:02:03")
+
+ADD_NAMESPACES(ext-foo)
+ADD_VETH(ext-foo, ext-foo, br-ext, "172.16.1.100/24", "00:10:10:01:02:13", \
+         "172.16.1.1")
+
+# Flip the interface down/up to get proper IPv6 LLA
+NS_EXEC([bgp-daemon], [ip link set down bgp-daemon])
+NS_EXEC([bgp-daemon], [ip link set up bgp-daemon])
+NS_EXEC([ext-foo], [ip link set down ext-foo])
+NS_EXEC([ext-foo], [ip link set up ext-foo])
+
+# Wait until IPv6 LLA loses the "tentative" flag otherwise it can't be bound 
to.
+OVS_WAIT_UNTIL([NS_EXEC([bgp-daemon], [ip a show dev bgp-daemon | grep 
"fe80::" | grep -v tentative])])
+OVS_WAIT_UNTIL([NS_EXEC([ext-foo], [ip a show dev ext-foo | grep "fe80::" | 
grep -v tentative])])
+
+# Verify that BGP control plane traffic is delivered to the "bgp-daemon"
+# interface on both IPv4 and IPv6 LLA addresses
+NETNS_DAEMONIZE([bgp-daemon], [nc -l -k 172.16.1.1 179], [bgp_v4.pid])
+NS_CHECK_EXEC([ext-foo], [echo "BGP IPv4 server traffic" | nc --send-only 
172.16.1.1 179])
+
+NETNS_DAEMONIZE([bgp-daemon], [nc -l -6 -k fe80::200:2ff:fe01:203%bgp-daemon 
179], [bgp_v6.pid])
+NS_CHECK_EXEC([ext-foo], [echo "BGP IPv6 server traffic" | nc --send-only -6 
fe80::200:2ff:fe01:203%ext-foo 179])
+
+# Perform same set of checks as above for BFD daemon.
+# We need to manually check that the message arrived on the receiving end as 
Ncat will
+# produce false positive results over UDP due to lack of ICMP port unreachable 
messages
+# from LRP's IP.
+NETNS_DAEMONIZE([bgp-daemon], [nc -l -u 172.16.1.1 3784 > 
bgp-daemon_bfd_v4.out], [bfd_v4.pid])
+NS_CHECK_EXEC([ext-foo], [echo "from ext-foo: BFD IPv4 server traffic" | nc -u 
172.16.1.1 3784])
+AT_CHECK([cat bgp-daemon_bfd_v4.out], [0], [dnl
+from ext-foo: BFD IPv4 server traffic
+])
+
+NETNS_DAEMONIZE([bgp-daemon], [nc -l -6 -u fe80::200:2ff:fe01:203%bgp-daemon 3784 
> bgp-daemon_bfd_v6.out], [bfd_v6.pid])
+NS_CHECK_EXEC([ext-foo], [echo "from ext-foo: BFD IPv6 server traffic" | nc -u 
-6 fe80::200:2ff:fe01:203%ext-foo 3784])
+AT_CHECK([cat bgp-daemon_bfd_v6.out], [0], [dnl
+from ext-foo: BFD IPv6 server traffic
+])
+
+# Verify connection in other direction. i.e when BGP daemon running on 
"bgp-daemon" port
+# makes a client connection to its peer
+NETNS_DAEMONIZE([ext-foo], [nc -l -k 172.16.1.100 179], [reply_bgp_v4.pid])
+NS_CHECK_EXEC([bgp-daemon], [echo "BGP IPv4 client traffic" | nc --send-only 
172.16.1.100 179])
+
+NETNS_DAEMONIZE([ext-foo], [nc -l -6 -k fe80::210:10ff:fe01:213%ext-foo 179], 
[reply_bgp_v6.pid])
+NS_CHECK_EXEC([bgp-daemon], [echo "BGP IPv6 client traffic" | nc --send-only 
-6 fe80::210:10ff:fe01:213%bgp-daemon 179])
+
+# Perform same checks in other direction for BFD daemon
+NETNS_DAEMONIZE([ext-foo], [nc -l -u 172.16.1.100 3784 > ext-foo_bfd_v4.out], 
[reply_bfd_v4.pid])
+NS_CHECK_EXEC([bgp-daemon], [echo "from bgp-daemon: BFD IPv4 client traffic" | 
nc -u 172.16.1.100 3784])
+AT_CHECK([cat ext-foo_bfd_v4.out], [0], [dnl
+from bgp-daemon: BFD IPv4 client traffic
+])
+
+NETNS_DAEMONIZE([ext-foo], [nc -l -6 -u fe80::210:10ff:fe01:213%ext-foo 3784 > 
ext-foo_bfd_v6.out], [reply_bfd_v6.pid])
+NS_CHECK_EXEC([bgp-daemon], [echo "from bgp-daemon: BFD IPv6 client traffic" | 
nc -u -6 fe80::210:10ff:fe01:213%bgp-daemon 3784])
+AT_CHECK([cat ext-foo_bfd_v6.out], [0], [dnl
+from bgp-daemon: BFD IPv6 client traffic
+])
+
+# Verify that hosts on the internal network can reach external networks
+NETNS_DAEMONIZE([ext-foo], [nc -l -k 172.16.1.100 2222], [nc_external.pid])
+NS_CHECK_EXEC([bar1], [echo "TCP test" | nc -w 1 --send-only 172.16.1.100 
2222])
+
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([ovn-northd])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
+/.*terminating with signal 15.*/d"])
+AT_CLEANUP
+])
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to