diff mbox series

[ovs-dev,v6] northd: Routing protocol port redirection.

Message ID 20240809100852.2440605-1-martin.kalcok@canonical.com
State Superseded
Headers show
Series [ovs-dev,v6] northd: Routing protocol port redirection. | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_ovn-kubernetes fail github build: failed

Commit Message

Martin Kalcok Aug. 9, 2024, 10:07 a.m. UTC
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 <martin.kalcok@canonical.com>
---

 v6 addresses broken documentation build and couple of code nits.
 
 northd/northd.c         | 221 ++++++++++++++++++++++++++++++++++++++++
 northd/northd.h         |   7 ++
 northd/ovn-northd.8.xml |  54 ++++++++++
 ovn-nb.xml              |  42 ++++++++
 tests/ovn-northd.at     |  93 +++++++++++++++++
 tests/system-ovn.at     | 100 ++++++++++++++++++
 6 files changed, 517 insertions(+)

Comments

Dumitru Ceara Aug. 9, 2024, 11:17 a.m. UTC | #1
On Friday, August 9, 2024, Martin Kalcok <martin.kalcok@canonical.com>
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 <martin.kalcok@canonical.com>
> ---
>
>  v6 addresses broken documentation build and couple of code nits.
>
>  northd/northd.c         | 221 ++++++++++++++++++++++++++++++++++++++++
>  northd/northd.h         |   7 ++
>  northd/ovn-northd.8.xml |  54 ++++++++++
>  ovn-nb.xml              |  42 ++++++++
>  tests/ovn-northd.at     |  93 +++++++++++++++++
>  tests/system-ovn.at     | 100 ++++++++++++++++++
>  6 files changed, 517 insertions(+)
>
> diff --git a/northd/northd.c b/northd/northd.c
> index 0c73e70df..420563389 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -13935,6 +13935,225 @@ 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)
> +{
> +    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),
> +                  ls_peer->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),
> +                  ls_peer->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)
> +{
> +    if (protocol_flags & REDIRECT_BGP) {
> +        build_routing_protocols_redirect_rule__(s_addr,
> redirect_port_name,
> +                                                179, "tcp", is_ipv6,
> ls_peer,
> +                                                lflows, match, actions);
> +    }
> +
> +    if (protocol_flags & REDIRECT_BFD) {
> +        build_routing_protocols_redirect_rule__(s_addr,
> redirect_port_name,
> +                                                3784, "udp", is_ipv6,
> ls_peer,
> +                                                lflows, match, actions);
> +    }
> +
> +    /* Because the redirected port shares IP and MAC addresses with the
> LRP,
> +     * special consideration needs to be given to the signaling
> protocols. */
> +    if (is_ipv6) {
> +        /* Ensure that redirect port receives copy of NA messages
> destined to
> +         * its IP.*/
> +        ds_clear(match);
> +        ds_clear(actions);
> +        ds_put_format(actions, "clone { outport = \"%s\"; output; };",
> +                      redirect_port_name);
> +        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),
> +                      ls_peer->lflow_ref);
> +    } else {
> +        /* Ensure that redirect port receives copy of ARP replies
> destined to
> +         * its IP */
> +        ds_clear(match);
> +        ds_clear(actions);
> +        ds_put_format(actions, "clone { outport = \"%s\"; output; };",
> +                      redirect_port_name);


Re-reading this part I’m afraid the action doesn’t do what the comment
says. Maybe I’m wrong but It will clone the packet, set outport for the
clone and move the cloned packet to the egress pipeline. But the original
packet is dropped.

I guess this means that we disrupt all transit traffic that was being
forwarded by the router via the target of the ARP request when the redirect
is configured.  The system test doesn’t cover this scenario either.

I can’t really test at the moment but maybe we should just prepend this
“clone to redirect port” to the actions of the L2_lookup logical flows that
normally forward arp/nd replies to the router port.

We should also add a regular transit traffic check to the system test I
guess.

Sorry for missing this earlier.

Regards,
Dumitru


> +        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),
> +                      ls_peer->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 == NULL) {
> +        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)
> +{
> +    /* LRP has to have a peer.*/
> +    if (op->peer == NULL) {
> +        return;
> +    }
> +
> +    /* LRP has to have NB record.*/
> +    if (op->nbrp == NULL) {
> +        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 = smap_get(&op->nbrp->options,
> +                                         "routing-protocol-redirect");
> +    if (redirect_port == NULL) {
> +        return;
> +    }
> +
> +    if (op->cr_port != NULL) {
> +        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 *peer_lsp;
> +    bool redirect_port_exists = false;
> +    HMAP_FOR_EACH (peer_lsp, dp_node, &op->peer->od->ports) {
> +        size_t peer_lsp_s = strlen(peer_lsp->key);
> +        if (peer_lsp_s == strlen(redirect_port)
> +            && !strncmp(peer_lsp->key, redirect_port, peer_lsp_s)){
> +            redirect_port_exists = true;
> +            break;
> +        }
> +    }
> +
> +    if (!redirect_port_exists) {
> +        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;
> +    }
> +
> +    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;
> +    }
> +
> +    /* Redirected 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,
> +                                           redirected_protocols, false,
> +                                           op->peer, lflows,match,
> actions);
> +    }
> +    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,
> +                                           redirected_protocols, true,
> +                                           op->peer, lflows,match,
> actions);
> +    }
> +
> +    /* 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);
> +    ovn_lflow_add(lflows, op->peer->od, S_SWITCH_IN_CHECK_PORT_SEC, 80,
> +                  ds_cstr(match),
> +                  REGBIT_PORT_SEC_DROP " = 1; next;",
> +                  op->peer->lflow_ref);
> +
> +    ds_clear(match);
> +    ds_put_format(match, "inport == \"%s\" && nd_na", redirect_port);
> +    ovn_lflow_add(lflows, op->peer->od, S_SWITCH_IN_CHECK_PORT_SEC, 80,
> +                  ds_cstr(match),
> +                  REGBIT_PORT_SEC_DROP " = 1; next;",
> +                  op->peer->lflow_ref);
> +
> +    ds_clear(match);
> +    ds_put_format(match, "inport == \"%s\" && nd_ra", redirect_port);
> +    ovn_lflow_add(lflows, op->peer->od, S_SWITCH_IN_CHECK_PORT_SEC, 80,
> +                  ds_cstr(match),
> +                  REGBIT_PORT_SEC_DROP " = 1; next;",
> +                  op->peer->lflow_ref);
> +}
> +
>  /* This function adds ARP resolve flows related to a LSP. */
>  static void
>  build_arp_resolve_flows_for_lsp(
> @@ -16900,6 +17119,8 @@ 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);
>  }
>
>  static void *
> diff --git a/northd/northd.h b/northd/northd.h
> index e04ec5856..9e326b746 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 f2f42275a..f1e206cfc 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -13757,3 +13757,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; };)
> +])
> +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; };)
> +])
> +
> +# 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 7ba2e150b..93ed7d17b 100644
> --- a/tests/system-ovn.at
> +++ b/tests/system-ovn.at
> @@ -13504,3 +13504,103 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query
> port patch-.*/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 lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.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 public bgp-daemon \
> +    -- lsp-set-addresses bgp-daemon unknown
> +
> +AT_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 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
> +
> +# 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 "TCP test" | 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 "TCP test" | nc --send-only -6
> fe80::200:2ff:fe01:203%ext-foo 179])
> +
> +# Verify connection in other direction. i.e when 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 "TCP test" | 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 "TCP test" | nc --send-only -6
> fe80::210:10ff:fe01:213%bgp-daemon 179])
> +
> +
> +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
> +])
> --
> 2.43.0
>
>
Martin Kalcok Aug. 9, 2024, 11:32 a.m. UTC | #2
On Fri, 2024-08-09 at 13:17 +0200, Dumitru Ceara wrote:
> 
> 
> On Friday, August 9, 2024, Martin Kalcok
> <martin.kalcok@canonical.com> 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 <martin.kalcok@canonical.com>
> > ---
> > 
> >  v6 addresses broken documentation build and couple of code nits.
> > 
> >  northd/northd.c         | 221
> > ++++++++++++++++++++++++++++++++++++++++
> >  northd/northd.h         |   7 ++
> >  northd/ovn-northd.8.xml |  54 ++++++++++
> >  ovn-nb.xml              |  42 ++++++++
> >  tests/ovn-northd.at     |  93 +++++++++++++++++
> >  tests/system-ovn.at     | 100 ++++++++++++++++++
> >  6 files changed, 517 insertions(+)
> > 
> > diff --git a/northd/northd.c b/northd/northd.c
> > index 0c73e70df..420563389 100644
> > --- a/northd/northd.c
> > +++ b/northd/northd.c
> > @@ -13935,6 +13935,225 @@ 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)
> > +{
> > +    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),
> > +                  ls_peer->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),
> > +                  ls_peer->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)
> > +{
> > +    if (protocol_flags & REDIRECT_BGP) {
> > +        build_routing_protocols_redirect_rule__(s_addr,
> > redirect_port_name,
> > +                                                179, "tcp",
> > is_ipv6, ls_peer,
> > +                                                lflows, match,
> > actions);
> > +    }
> > +
> > +    if (protocol_flags & REDIRECT_BFD) {
> > +        build_routing_protocols_redirect_rule__(s_addr,
> > redirect_port_name,
> > +                                                3784, "udp",
> > is_ipv6, ls_peer,
> > +                                                lflows, match,
> > actions);
> > +    }
> > +
> > +    /* Because the redirected port shares IP and MAC addresses
> > with the LRP,
> > +     * special consideration needs to be given to the signaling
> > protocols. */
> > +    if (is_ipv6) {
> > +        /* Ensure that redirect port receives copy of NA messages
> > destined to
> > +         * its IP.*/
> > +        ds_clear(match);
> > +        ds_clear(actions);
> > +        ds_put_format(actions, "clone { outport = \"%s\"; output;
> > };",
> > +                      redirect_port_name);
> > +        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),
> > +                      ls_peer->lflow_ref);
> > +    } else {
> > +        /* Ensure that redirect port receives copy of ARP replies
> > destined to
> > +         * its IP */
> > +        ds_clear(match);
> > +        ds_clear(actions);
> > +        ds_put_format(actions, "clone { outport = \"%s\"; output;
> > };",
> > +                      redirect_port_name);
> > 
> 
> Re-reading this part I’m afraid the action doesn’t do what the
> comment says. Maybe I’m wrong but It will clone the packet, set
> outport for the clone and move the cloned packet to the egress
> pipeline. But the original packet is dropped.
I see, thanks for pointing that out. From reading docs and looking at
similar "clone" usage in northd.c I assumed that the original packet
would continue traversing the current table.
> 
> I guess this means that we disrupt all transit traffic that was being
> forwarded by the router via the target of the ARP request when the
> redirect is configured.  The system test doesn’t cover this scenario
> either.
> 
> I can’t really test at the moment but maybe we should just prepend
> this “clone to redirect port” to the actions of the L2_lookup logical
> flows that normally forward arp/nd replies to the router port.
> 
> We should also add a regular transit traffic check to the system test
> I guess.
I agree that this should be added. Would you say that adding new LS,
configuring SNAT, and testing that container connected to the LS can
establish connection with external node would be sufficient? 
> 
> Sorry for missing this earlier.
> 
> Regards,
> Dumitru
>  
> > +        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),
> > +                      ls_peer->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 == NULL) {
> > +        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)
> > +{
> > +    /* LRP has to have a peer.*/
> > +    if (op->peer == NULL) {
> > +        return;
> > +    }
> > +
> > +    /* LRP has to have NB record.*/
> > +    if (op->nbrp == NULL) {
> > +        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 = smap_get(&op->nbrp->options,
> > +                                         "routing-protocol-
> > redirect");
> > +    if (redirect_port == NULL) {
> > +        return;
> > +    }
> > +
> > +    if (op->cr_port != NULL) {
> > +        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 *peer_lsp;
> > +    bool redirect_port_exists = false;
> > +    HMAP_FOR_EACH (peer_lsp, dp_node, &op->peer->od->ports) {
> > +        size_t peer_lsp_s = strlen(peer_lsp->key);
> > +        if (peer_lsp_s == strlen(redirect_port)
> > +            && !strncmp(peer_lsp->key, redirect_port,
> > peer_lsp_s)){
> > +            redirect_port_exists = true;
> > +            break;
> > +        }
> > +    }
> > +
> > +    if (!redirect_port_exists) {
> > +        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;
> > +    }
> > +
> > +    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;
> > +    }
> > +
> > +    /* Redirected 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,
> > +                                           redirected_protocols,
> > false,
> > +                                           op->peer, lflows,match,
> > actions);
> > +    }
> > +    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,
> > +                                           redirected_protocols,
> > true,
> > +                                           op->peer, lflows,match,
> > actions);
> > +    }
> > +
> > +    /* 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);
> > +    ovn_lflow_add(lflows, op->peer->od,
> > S_SWITCH_IN_CHECK_PORT_SEC, 80,
> > +                  ds_cstr(match),
> > +                  REGBIT_PORT_SEC_DROP " = 1; next;",
> > +                  op->peer->lflow_ref);
> > +
> > +    ds_clear(match);
> > +    ds_put_format(match, "inport == \"%s\" && nd_na",
> > redirect_port);
> > +    ovn_lflow_add(lflows, op->peer->od,
> > S_SWITCH_IN_CHECK_PORT_SEC, 80,
> > +                  ds_cstr(match),
> > +                  REGBIT_PORT_SEC_DROP " = 1; next;",
> > +                  op->peer->lflow_ref);
> > +
> > +    ds_clear(match);
> > +    ds_put_format(match, "inport == \"%s\" && nd_ra",
> > redirect_port);
> > +    ovn_lflow_add(lflows, op->peer->od,
> > S_SWITCH_IN_CHECK_PORT_SEC, 80,
> > +                  ds_cstr(match),
> > +                  REGBIT_PORT_SEC_DROP " = 1; next;",
> > +                  op->peer->lflow_ref);
> > +}
> > +
> >  /* This function adds ARP resolve flows related to a LSP. */
> >  static void
> >  build_arp_resolve_flows_for_lsp(
> > @@ -16900,6 +17119,8 @@
> > 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);
> >  }
> > 
> >  static void *
> > diff --git a/northd/northd.h b/northd/northd.h
> > index e04ec5856..9e326b746 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 f2f42275a..f1e206cfc 100644
> > --- a/tests/ovn-northd.at
> > +++ b/tests/ovn-northd.at
> > @@ -13757,3 +13757,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; };)
> > +])
> > +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; };)
> > +])
> > +
> > +# 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 7ba2e150b..93ed7d17b 100644
> > --- a/tests/system-ovn.at
> > +++ b/tests/system-ovn.at
> > @@ -13504,3 +13504,103 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to
> > query port patch-.*/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 lrp-add R1 rp-public 00:00:02:01:02:03
> > 172.16.1.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 public bgp-daemon \
> > +    -- lsp-set-addresses bgp-daemon unknown
> > +
> > +AT_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 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
> > +
> > +# 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 "TCP test" | 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 "TCP test" | nc --send-only -6
> > fe80::200:2ff:fe01:203%ext-foo 179])
> > +
> > +# Verify connection in other direction. i.e when 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 "TCP test" | 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 "TCP test" | nc --send-only -6
> > fe80::210:10ff:fe01:213%bgp-daemon 179])
> > +
> > +
> > +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
> > +])
Dumitru Ceara Aug. 9, 2024, 11:47 a.m. UTC | #3
On Friday, August 9, 2024, <martin.kalcok@canonical.com> wrote:

> On Fri, 2024-08-09 at 13:17 +0200, Dumitru Ceara wrote:
>
>
>
> On Friday, August 9, 2024, Martin Kalcok <martin.kalcok@canonical.com>
> 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 <martin.kalcok@canonical.com>
> ---
>
>  v6 addresses broken documentation build and couple of code nits.
>
>  northd/northd.c         | 221 ++++++++++++++++++++++++++++++++++++++++
>  northd/northd.h         |   7 ++
>  northd/ovn-northd.8.xml |  54 ++++++++++
>  ovn-nb.xml              |  42 ++++++++
>  tests/ovn-northd.at     |  93 +++++++++++++++++
>  tests/system-ovn.at     | 100 ++++++++++++++++++
>  6 files changed, 517 insertions(+)
>
> diff --git a/northd/northd.c b/northd/northd.c
> index 0c73e70df..420563389 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -13935,6 +13935,225 @@ 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)
> +{
> +    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),
> +                  ls_peer->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),
> +                  ls_peer->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)
> +{
> +    if (protocol_flags & REDIRECT_BGP) {
> +        build_routing_protocols_redirect_rule__(s_addr,
> redirect_port_name,
> +                                                179, "tcp", is_ipv6,
> ls_peer,
> +                                                lflows, match, actions);
> +    }
> +
> +    if (protocol_flags & REDIRECT_BFD) {
> +        build_routing_protocols_redirect_rule__(s_addr,
> redirect_port_name,
> +                                                3784, "udp", is_ipv6,
> ls_peer,
> +                                                lflows, match, actions);
> +    }
> +
> +    /* Because the redirected port shares IP and MAC addresses with the
> LRP,
> +     * special consideration needs to be given to the signaling
> protocols. */
> +    if (is_ipv6) {
> +        /* Ensure that redirect port receives copy of NA messages
> destined to
> +         * its IP.*/
> +        ds_clear(match);
> +        ds_clear(actions);
> +        ds_put_format(actions, "clone { outport = \"%s\"; output; };",
> +                      redirect_port_name);
> +        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),
> +                      ls_peer->lflow_ref);
> +    } else {
> +        /* Ensure that redirect port receives copy of ARP replies
> destined to
> +         * its IP */
> +        ds_clear(match);
> +        ds_clear(actions);
> +        ds_put_format(actions, "clone { outport = \"%s\"; output; };",
> +                      redirect_port_name);
>
>
> Re-reading this part I’m afraid the action doesn’t do what the comment
> says. Maybe I’m wrong but It will clone the packet, set outport for the
> clone and move the cloned packet to the egress pipeline. But the original
> packet is dropped.
>
> I see, thanks for pointing that out. From reading docs and looking at
> similar "clone" usage in northd.c I assumed that the original packet would
> continue traversing the current table.
>
>
> I guess this means that we disrupt all transit traffic that was being
> forwarded by the router via the target of the ARP request when the redirect
> is configured.  The system test doesn’t cover this scenario either.
>
> I can’t really test at the moment but maybe we should just prepend this
> “clone to redirect port” to the actions of the L2_lookup logical flows that
> normally forward arp/nd replies to the router port.
>
> We should also add a regular transit traffic check to the system test I
> guess.
>
> I agree that this should be added. Would you say that adding new LS,
> configuring SNAT, and testing that container connected to the LS can
> establish connection with external node would be sufficient?
>

Sounds good to me, thanks!



> Sorry for missing this earlier.
>
> Regards,
> Dumitru
>
>
> +        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),
> +                      ls_peer->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 == NULL) {
> +        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)
> +{
> +    /* LRP has to have a peer.*/
> +    if (op->peer == NULL) {
> +        return;
> +    }
> +
> +    /* LRP has to have NB record.*/
> +    if (op->nbrp == NULL) {
> +        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 = smap_get(&op->nbrp->options,
> +                                         "routing-protocol-redirect");
> +    if (redirect_port == NULL) {
> +        return;
> +    }
> +
> +    if (op->cr_port != NULL) {
> +        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 *peer_lsp;
> +    bool redirect_port_exists = false;
> +    HMAP_FOR_EACH (peer_lsp, dp_node, &op->peer->od->ports) {
> +        size_t peer_lsp_s = strlen(peer_lsp->key);
> +        if (peer_lsp_s == strlen(redirect_port)
> +            && !strncmp(peer_lsp->key, redirect_port, peer_lsp_s)){
> +            redirect_port_exists = true;
> +            break;
> +        }
> +    }
> +
> +    if (!redirect_port_exists) {
> +        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;
> +    }
> +
> +    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;
> +    }
> +
> +    /* Redirected 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,
> +                                           redirected_protocols, false,
> +                                           op->peer, lflows,match,
> actions);
> +    }
> +    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,
> +                                           redirected_protocols, true,
> +                                           op->peer, lflows,match,
> actions);
> +    }
> +
> +    /* 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);
> +    ovn_lflow_add(lflows, op->peer->od, S_SWITCH_IN_CHECK_PORT_SEC, 80,
> +                  ds_cstr(match),
> +                  REGBIT_PORT_SEC_DROP " = 1; next;",
> +                  op->peer->lflow_ref);
> +
> +    ds_clear(match);
> +    ds_put_format(match, "inport == \"%s\" && nd_na", redirect_port);
> +    ovn_lflow_add(lflows, op->peer->od, S_SWITCH_IN_CHECK_PORT_SEC, 80,
> +                  ds_cstr(match),
> +                  REGBIT_PORT_SEC_DROP " = 1; next;",
> +                  op->peer->lflow_ref);
> +
> +    ds_clear(match);
> +    ds_put_format(match, "inport == \"%s\" && nd_ra", redirect_port);
> +    ovn_lflow_add(lflows, op->peer->od, S_SWITCH_IN_CHECK_PORT_SEC, 80,
> +                  ds_cstr(match),
> +                  REGBIT_PORT_SEC_DROP " = 1; next;",
> +                  op->peer->lflow_ref);
> +}
> +
>  /* This function adds ARP resolve flows related to a LSP. */
>  static void
>  build_arp_resolve_flows_for_lsp(
> @@ -16900,6 +17119,8 @@ 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);
>  }
>
>  static void *
> diff --git a/northd/northd.h b/northd/northd.h
> index e04ec5856..9e326b746 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 f2f42275a..f1e206cfc 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -13757,3 +13757,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; };)
> +])
> +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; };)
> +])
> +
> +# 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 7ba2e150b..93ed7d17b 100644
> --- a/tests/system-ovn.at
> +++ b/tests/system-ovn.at
> @@ -13504,3 +13504,103 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query
> port patch-.*/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 lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.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 public bgp-daemon \
> +    -- lsp-set-addresses bgp-daemon unknown
> +
> +AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappin
> gs=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 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
> +
> +# 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 "TCP test" | 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 "TCP test" | nc --send-only -6
> fe80::200:2ff:fe01:203%ext-foo 179])
> +
> +# Verify connection in other direction. i.e when 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 "TCP test" | 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 "TCP test" | nc --send-only -6
> fe80::210:10ff:fe01:213%bgp-daemon 179])
> +
> +
> +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
> +])
>
>
>
Martin Kalcok Aug. 9, 2024, 3:29 p.m. UTC | #4
On Fri, 2024-08-09 at 13:47 +0200, Dumitru Ceara wrote:
> On Friday, August 9, 2024, <martin.kalcok@canonical.com> wrote:
> > On Fri, 2024-08-09 at 13:17 +0200, Dumitru Ceara wrote:
> > > 
> > > 
> > > On Friday, August 9, 2024, Martin Kalcok
> > > <martin.kalcok@canonical.com> 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 <martin.kalcok@canonical.com>
> > > > ---
> > > > 
> > > >  v6 addresses broken documentation build and couple of code
> > > > nits.
> > > > 
> > > >  northd/northd.c         | 221
> > > > ++++++++++++++++++++++++++++++++++++++++
> > > >  northd/northd.h         |   7 ++
> > > >  northd/ovn-northd.8.xml |  54 ++++++++++
> > > >  ovn-nb.xml              |  42 ++++++++
> > > >  tests/ovn-northd.at     |  93 +++++++++++++++++
> > > >  tests/system-ovn.at     | 100 ++++++++++++++++++
> > > >  6 files changed, 517 insertions(+)
> > > > 
> > > > diff --git a/northd/northd.c b/northd/northd.c
> > > > index 0c73e70df..420563389 100644
> > > > --- a/northd/northd.c
> > > > +++ b/northd/northd.c
> > > > @@ -13935,6 +13935,225 @@
> > > > 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)
> > > > +{
> > > > +    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),
> > > > +                  ls_peer->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),
> > > > +                  ls_peer->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)
> > > > +{
> > > > +    if (protocol_flags & REDIRECT_BGP) {
> > > > +        build_routing_protocols_redirect_rule__(s_addr,
> > > > redirect_port_name,
> > > > +                                                179, "tcp",
> > > > is_ipv6, ls_peer,
> > > > +                                                lflows, match,
> > > > actions);
> > > > +    }
> > > > +
> > > > +    if (protocol_flags & REDIRECT_BFD) {
> > > > +        build_routing_protocols_redirect_rule__(s_addr,
> > > > redirect_port_name,
> > > > +                                                3784, "udp",
> > > > is_ipv6, ls_peer,
> > > > +                                                lflows, match,
> > > > actions);
> > > > +    }
> > > > +
> > > > +    /* Because the redirected port shares IP and MAC addresses
> > > > with the LRP,
> > > > +     * special consideration needs to be given to the
> > > > signaling protocols. */
> > > > +    if (is_ipv6) {
> > > > +        /* Ensure that redirect port receives copy of NA
> > > > messages destined to
> > > > +         * its IP.*/
> > > > +        ds_clear(match);
> > > > +        ds_clear(actions);
> > > > +        ds_put_format(actions, "clone { outport = \"%s\";
> > > > output; };",
> > > > +                      redirect_port_name);
> > > > +        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),
> > > > +                      ls_peer->lflow_ref);
> > > > +    } else {
> > > > +        /* Ensure that redirect port receives copy of ARP
> > > > replies destined to
> > > > +         * its IP */
> > > > +        ds_clear(match);
> > > > +        ds_clear(actions);
> > > > +        ds_put_format(actions, "clone { outport = \"%s\";
> > > > output; };",
> > > > +                      redirect_port_name);
> > > > 
> > > 
> > > Re-reading this part I’m afraid the action doesn’t do what the
> > > comment says. Maybe I’m wrong but It will clone the packet, set
> > > outport for the clone and move the cloned packet to the egress
> > > pipeline. But the original packet is dropped.
> > 
> > I see, thanks for pointing that out. From reading docs and looking
> > at similar "clone" usage in northd.c I assumed that the original
> > packet would continue traversing the current table.
> > > 
> > > I guess this means that we disrupt all transit traffic that was
> > > being forwarded by the router via the target of the ARP request
> > > when the redirect is configured.  The system test doesn’t cover
> > > this scenario either.
> > > 
> > > I can’t really test at the moment but maybe we should just
> > > prepend this “clone to redirect port” to the actions of the
> > > L2_lookup logical flows that normally forward arp/nd replies to
> > > the router port.

I didn't find a rule that handles specifically arp replies (arp.op ==
2) in the L2_lookup stage. However, we do have enough information in
the place where we did the cloning originally. I expanded those rules
to clone arp/nd to the redirected port and forward it to the LRP's peer
port as well. I believe that this should be sufficient and looks good
in my testing. I also expanded functional tests to make sure that
transit traffic (from internal host via SNAT on router) is functional
as well.

I'm awaiting CI results in my branch and I'll be posting v7 shortly.

Martin.

> > > 
> > > We should also add a regular transit traffic check to the system
> > > test I guess.
> > 
> > I agree that this should be added. Would you say that adding new
> > LS, configuring SNAT, and testing that container connected to the
> > LS can establish connection with external node would be sufficient?
> > 
> 
> Sounds good to me, thanks! 
> 
>  
> > > Sorry for missing this earlier.
> > > 
> > > Regards,
> > > Dumitru
> > >  
> > > > +        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),
> > > > +                      ls_peer->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 == NULL) {
> > > > +        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)
> > > > +{
> > > > +    /* LRP has to have a peer.*/
> > > > +    if (op->peer == NULL) {
> > > > +        return;
> > > > +    }
> > > > +
> > > > +    /* LRP has to have NB record.*/
> > > > +    if (op->nbrp == NULL) {
> > > > +        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 = smap_get(&op->nbrp->options,
> > > > +                                         "routing-protocol-
> > > > redirect");
> > > > +    if (redirect_port == NULL) {
> > > > +        return;
> > > > +    }
> > > > +
> > > > +    if (op->cr_port != NULL) {
> > > > +        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 *peer_lsp;
> > > > +    bool redirect_port_exists = false;
> > > > +    HMAP_FOR_EACH (peer_lsp, dp_node, &op->peer->od->ports) {
> > > > +        size_t peer_lsp_s = strlen(peer_lsp->key);
> > > > +        if (peer_lsp_s == strlen(redirect_port)
> > > > +            && !strncmp(peer_lsp->key, redirect_port,
> > > > peer_lsp_s)){
> > > > +            redirect_port_exists = true;
> > > > +            break;
> > > > +        }
> > > > +    }
> > > > +
> > > > +    if (!redirect_port_exists) {
> > > > +        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;
> > > > +    }
> > > > +
> > > > +    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;
> > > > +    }
> > > > +
> > > > +    /* Redirected 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,
> > > > +                                         
> > > >  redirected_protocols, false,
> > > > +                                           op->peer,
> > > > lflows,match, actions);
> > > > +    }
> > > > +    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,
> > > > +                                         
> > > >  redirected_protocols, true,
> > > > +                                           op->peer,
> > > > lflows,match, actions);
> > > > +    }
> > > > +
> > > > +    /* 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);
> > > > +    ovn_lflow_add(lflows, op->peer->od,
> > > > S_SWITCH_IN_CHECK_PORT_SEC, 80,
> > > > +                  ds_cstr(match),
> > > > +                  REGBIT_PORT_SEC_DROP " = 1; next;",
> > > > +                  op->peer->lflow_ref);
> > > > +
> > > > +    ds_clear(match);
> > > > +    ds_put_format(match, "inport == \"%s\" && nd_na",
> > > > redirect_port);
> > > > +    ovn_lflow_add(lflows, op->peer->od,
> > > > S_SWITCH_IN_CHECK_PORT_SEC, 80,
> > > > +                  ds_cstr(match),
> > > > +                  REGBIT_PORT_SEC_DROP " = 1; next;",
> > > > +                  op->peer->lflow_ref);
> > > > +
> > > > +    ds_clear(match);
> > > > +    ds_put_format(match, "inport == \"%s\" && nd_ra",
> > > > redirect_port);
> > > > +    ovn_lflow_add(lflows, op->peer->od,
> > > > S_SWITCH_IN_CHECK_PORT_SEC, 80,
> > > > +                  ds_cstr(match),
> > > > +                  REGBIT_PORT_SEC_DROP " = 1; next;",
> > > > +                  op->peer->lflow_ref);
> > > > +}
> > > > +
> > > >  /* This function adds ARP resolve flows related to a LSP. */
> > > >  static void
> > > >  build_arp_resolve_flows_for_lsp(
> > > > @@ -16900,6 +17119,8 @@
> > > > 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);
> > > >  }
> > > > 
> > > >  static void *
> > > > diff --git a/northd/northd.h b/northd/northd.h
> > > > index e04ec5856..9e326b746 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 f2f42275a..f1e206cfc 100644
> > > > --- a/tests/ovn-northd.at
> > > > +++ b/tests/ovn-northd.at
> > > > @@ -13757,3 +13757,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; };)
> > > > +])
> > > > +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; };)
> > > > +])
> > > > +
> > > > +# 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 7ba2e150b..93ed7d17b 100644
> > > > --- a/tests/system-ovn.at
> > > > +++ b/tests/system-ovn.at
> > > > @@ -13504,3 +13504,103 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed
> > > > to query port patch-.*/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 lrp-add R1 rp-public 00:00:02:01:02:03
> > > > 172.16.1.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 public bgp-daemon \
> > > > +    -- lsp-set-addresses bgp-daemon unknown
> > > > +
> > > > +AT_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 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
> > > > +
> > > > +# 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 "TCP test" | 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 "TCP test" | nc --send-only -6
> > > > fe80::200:2ff:fe01:203%ext-foo 179])
> > > > +
> > > > +# Verify connection in other direction. i.e when 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 "TCP test" | 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 "TCP test" | nc --send-only
> > > > -6 fe80::210:10ff:fe01:213%bgp-daemon 179])
> > > > +
> > > > +
> > > > +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
> > > > +])
> > 
> > 
> >
Martin Kalcok Aug. 9, 2024, 4:36 p.m. UTC | #5
On Fri, 2024-08-09 at 17:29 +0200, martin.kalcok@canonical.com wrote:
> On Fri, 2024-08-09 at 13:47 +0200, Dumitru Ceara wrote:
> > On Friday, August 9, 2024, <martin.kalcok@canonical.com> wrote:
> > > On Fri, 2024-08-09 at 13:17 +0200, Dumitru Ceara wrote:
> > > > 
> > > > 
> > > > On Friday, August 9, 2024, Martin Kalcok
> > > > <martin.kalcok@canonical.com> 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 <martin.kalcok@canonical.com>
> > > > > ---
> > > > > 
> > > > >  v6 addresses broken documentation build and couple of code
> > > > > nits.
> > > > > 
> > > > >  northd/northd.c         | 221
> > > > > ++++++++++++++++++++++++++++++++++++++++
> > > > >  northd/northd.h         |   7 ++
> > > > >  northd/ovn-northd.8.xml |  54 ++++++++++
> > > > >  ovn-nb.xml              |  42 ++++++++
> > > > >  tests/ovn-northd.at     |  93 +++++++++++++++++
> > > > >  tests/system-ovn.at     | 100 ++++++++++++++++++
> > > > >  6 files changed, 517 insertions(+)
> > > > > 
> > > > > diff --git a/northd/northd.c b/northd/northd.c
> > > > > index 0c73e70df..420563389 100644
> > > > > --- a/northd/northd.c
> > > > > +++ b/northd/northd.c
> > > > > @@ -13935,6 +13935,225 @@
> > > > > 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)
> > > > > +{
> > > > > +    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),
> > > > > +                  ls_peer->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),
> > > > > +                  ls_peer->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)
> > > > > +{
> > > > > +    if (protocol_flags & REDIRECT_BGP) {
> > > > > +        build_routing_protocols_redirect_rule__(s_addr,
> > > > > redirect_port_name,
> > > > > +                                                179, "tcp",
> > > > > is_ipv6, ls_peer,
> > > > > +                                                lflows,
> > > > > match, actions);
> > > > > +    }
> > > > > +
> > > > > +    if (protocol_flags & REDIRECT_BFD) {
> > > > > +        build_routing_protocols_redirect_rule__(s_addr,
> > > > > redirect_port_name,
> > > > > +                                                3784, "udp",
> > > > > is_ipv6, ls_peer,
> > > > > +                                                lflows,
> > > > > match, actions);
> > > > > +    }
> > > > > +
> > > > > +    /* Because the redirected port shares IP and MAC
> > > > > addresses with the LRP,
> > > > > +     * special consideration needs to be given to the
> > > > > signaling protocols. */
> > > > > +    if (is_ipv6) {
> > > > > +        /* Ensure that redirect port receives copy of NA
> > > > > messages destined to
> > > > > +         * its IP.*/
> > > > > +        ds_clear(match);
> > > > > +        ds_clear(actions);
> > > > > +        ds_put_format(actions, "clone { outport = \"%s\";
> > > > > output; };",
> > > > > +                      redirect_port_name);
> > > > > +        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),
> > > > > +                      ls_peer->lflow_ref);
> > > > > +    } else {
> > > > > +        /* Ensure that redirect port receives copy of ARP
> > > > > replies destined to
> > > > > +         * its IP */
> > > > > +        ds_clear(match);
> > > > > +        ds_clear(actions);
> > > > > +        ds_put_format(actions, "clone { outport = \"%s\";
> > > > > output; };",
> > > > > +                      redirect_port_name);
> > > > > 
> > > > 
> > > > Re-reading this part I’m afraid the action doesn’t do what the
> > > > comment says. Maybe I’m wrong but It will clone the packet, set
> > > > outport for the clone and move the cloned packet to the egress
> > > > pipeline. But the original packet is dropped.
> > > 
> > > I see, thanks for pointing that out. From reading docs and
> > > looking at similar "clone" usage in northd.c I assumed that the
> > > original packet would continue traversing the current table.
> > > > 
> > > > I guess this means that we disrupt all transit traffic that was
> > > > being forwarded by the router via the target of the ARP request
> > > > when the redirect is configured.  The system test doesn’t cover
> > > > this scenario either.
> > > > 
> > > > I can’t really test at the moment but maybe we should just
> > > > prepend this “clone to redirect port” to the actions of the
> > > > L2_lookup logical flows that normally forward arp/nd replies to
> > > > the router port.
> 
> I didn't find a rule that handles specifically arp replies (arp.op ==
> 2) in the L2_lookup stage. However, we do have enough information in
> the place where we did the cloning originally. I expanded those rules
> to clone arp/nd to the redirected port and forward it to the LRP's
> peer port as well. I believe that this should be sufficient and looks
> good in my testing. I also expanded functional tests to make sure
> that transit traffic (from internal host via SNAT on router) is
> functional as well.
> 
> I'm awaiting CI results in my branch and I'll be posting v7 shortly.
And by shortly I meant that there are of course CI failures that did
not occur in my local testing :(
> 
> Martin.
> 
> > > > 
> > > > We should also add a regular transit traffic check to the
> > > > system test I guess.
> > > 
> > > I agree that this should be added. Would you say that adding new
> > > LS, configuring SNAT, and testing that container connected to the
> > > LS can establish connection with external node would be
> > > sufficient?
> > > 
> > 
> > Sounds good to me, thanks! 
> > 
> >  
> > > > Sorry for missing this earlier.
> > > > 
> > > > Regards,
> > > > Dumitru
> > > >  
> > > > > +        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),
> > > > > +                      ls_peer->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 == NULL) {
> > > > > +        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)
> > > > > +{
> > > > > +    /* LRP has to have a peer.*/
> > > > > +    if (op->peer == NULL) {
> > > > > +        return;
> > > > > +    }
> > > > > +
> > > > > +    /* LRP has to have NB record.*/
> > > > > +    if (op->nbrp == NULL) {
> > > > > +        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 = smap_get(&op->nbrp->options,
> > > > > +                                         "routing-protocol-
> > > > > redirect");
> > > > > +    if (redirect_port == NULL) {
> > > > > +        return;
> > > > > +    }
> > > > > +
> > > > > +    if (op->cr_port != NULL) {
> > > > > +        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 *peer_lsp;
> > > > > +    bool redirect_port_exists = false;
> > > > > +    HMAP_FOR_EACH (peer_lsp, dp_node, &op->peer->od->ports)
> > > > > {
> > > > > +        size_t peer_lsp_s = strlen(peer_lsp->key);
> > > > > +        if (peer_lsp_s == strlen(redirect_port)
> > > > > +            && !strncmp(peer_lsp->key, redirect_port,
> > > > > peer_lsp_s)){
> > > > > +            redirect_port_exists = true;
> > > > > +            break;
> > > > > +        }
> > > > > +    }
> > > > > +
> > > > > +    if (!redirect_port_exists) {
> > > > > +        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;
> > > > > +    }
> > > > > +
> > > > > +    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;
> > > > > +    }
> > > > > +
> > > > > +    /* Redirected 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,
> > > > > +                                         
> > > > >  redirected_protocols, false,
> > > > > +                                           op->peer,
> > > > > lflows,match, actions);
> > > > > +    }
> > > > > +    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,
> > > > > +                                         
> > > > >  redirected_protocols, true,
> > > > > +                                           op->peer,
> > > > > lflows,match, actions);
> > > > > +    }
> > > > > +
> > > > > +    /* 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);
> > > > > +    ovn_lflow_add(lflows, op->peer->od,
> > > > > S_SWITCH_IN_CHECK_PORT_SEC, 80,
> > > > > +                  ds_cstr(match),
> > > > > +                  REGBIT_PORT_SEC_DROP " = 1; next;",
> > > > > +                  op->peer->lflow_ref);
> > > > > +
> > > > > +    ds_clear(match);
> > > > > +    ds_put_format(match, "inport == \"%s\" && nd_na",
> > > > > redirect_port);
> > > > > +    ovn_lflow_add(lflows, op->peer->od,
> > > > > S_SWITCH_IN_CHECK_PORT_SEC, 80,
> > > > > +                  ds_cstr(match),
> > > > > +                  REGBIT_PORT_SEC_DROP " = 1; next;",
> > > > > +                  op->peer->lflow_ref);
> > > > > +
> > > > > +    ds_clear(match);
> > > > > +    ds_put_format(match, "inport == \"%s\" && nd_ra",
> > > > > redirect_port);
> > > > > +    ovn_lflow_add(lflows, op->peer->od,
> > > > > S_SWITCH_IN_CHECK_PORT_SEC, 80,
> > > > > +                  ds_cstr(match),
> > > > > +                  REGBIT_PORT_SEC_DROP " = 1; next;",
> > > > > +                  op->peer->lflow_ref);
> > > > > +}
> > > > > +
> > > > >  /* This function adds ARP resolve flows related to a LSP. */
> > > > >  static void
> > > > >  build_arp_resolve_flows_for_lsp(
> > > > > @@ -16900,6 +17119,8 @@
> > > > > 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);
> > > > >  }
> > > > > 
> > > > >  static void *
> > > > > diff --git a/northd/northd.h b/northd/northd.h
> > > > > index e04ec5856..9e326b746 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 f2f42275a..f1e206cfc 100644
> > > > > --- a/tests/ovn-northd.at
> > > > > +++ b/tests/ovn-northd.at
> > > > > @@ -13757,3 +13757,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; };)
> > > > > +])
> > > > > +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; };)
> > > > > +])
> > > > > +
> > > > > +# 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 7ba2e150b..93ed7d17b 100644
> > > > > --- a/tests/system-ovn.at
> > > > > +++ b/tests/system-ovn.at
> > > > > @@ -13504,3 +13504,103 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed
> > > > > to query port patch-.*/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 lrp-add R1 rp-public 00:00:02:01:02:03
> > > > > 172.16.1.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 public bgp-daemon \
> > > > > +    -- lsp-set-addresses bgp-daemon unknown
> > > > > +
> > > > > +AT_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 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
> > > > > +
> > > > > +# 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 "TCP test" | 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 "TCP test" | nc --send-only -
> > > > > 6 fe80::200:2ff:fe01:203%ext-foo 179])
> > > > > +
> > > > > +# Verify connection in other direction. i.e when 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 "TCP test" | 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 "TCP test" | nc --send-
> > > > > only -6 fe80::210:10ff:fe01:213%bgp-daemon 179])
> > > > > +
> > > > > +
> > > > > +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
> > > > > +])
> > > 
> > > 
> > > 
>
diff mbox series

Patch

diff --git a/northd/northd.c b/northd/northd.c
index 0c73e70df..420563389 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -13935,6 +13935,225 @@  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)
+{
+    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),
+                  ls_peer->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),
+                  ls_peer->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)
+{
+    if (protocol_flags & REDIRECT_BGP) {
+        build_routing_protocols_redirect_rule__(s_addr, redirect_port_name,
+                                                179, "tcp", is_ipv6, ls_peer,
+                                                lflows, match, actions);
+    }
+
+    if (protocol_flags & REDIRECT_BFD) {
+        build_routing_protocols_redirect_rule__(s_addr, redirect_port_name,
+                                                3784, "udp", is_ipv6, ls_peer,
+                                                lflows, match, actions);
+    }
+
+    /* Because the redirected port shares IP and MAC addresses with the LRP,
+     * special consideration needs to be given to the signaling protocols. */
+    if (is_ipv6) {
+        /* Ensure that redirect port receives copy of NA messages destined to
+         * its IP.*/
+        ds_clear(match);
+        ds_clear(actions);
+        ds_put_format(actions, "clone { outport = \"%s\"; output; };",
+                      redirect_port_name);
+        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),
+                      ls_peer->lflow_ref);
+    } else {
+        /* Ensure that redirect port receives copy of ARP replies destined to
+         * its IP */
+        ds_clear(match);
+        ds_clear(actions);
+        ds_put_format(actions, "clone { outport = \"%s\"; output; };",
+                      redirect_port_name);
+        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),
+                      ls_peer->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 == NULL) {
+        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)
+{
+    /* LRP has to have a peer.*/
+    if (op->peer == NULL) {
+        return;
+    }
+
+    /* LRP has to have NB record.*/
+    if (op->nbrp == NULL) {
+        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 = smap_get(&op->nbrp->options,
+                                         "routing-protocol-redirect");
+    if (redirect_port == NULL) {
+        return;
+    }
+
+    if (op->cr_port != NULL) {
+        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 *peer_lsp;
+    bool redirect_port_exists = false;
+    HMAP_FOR_EACH (peer_lsp, dp_node, &op->peer->od->ports) {
+        size_t peer_lsp_s = strlen(peer_lsp->key);
+        if (peer_lsp_s == strlen(redirect_port)
+            && !strncmp(peer_lsp->key, redirect_port, peer_lsp_s)){
+            redirect_port_exists = true;
+            break;
+        }
+    }
+
+    if (!redirect_port_exists) {
+        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;
+    }
+
+    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;
+    }
+
+    /* Redirected 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,
+                                           redirected_protocols, false,
+                                           op->peer, lflows,match, actions);
+    }
+    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,
+                                           redirected_protocols, true,
+                                           op->peer, lflows,match, actions);
+    }
+
+    /* 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);
+    ovn_lflow_add(lflows, op->peer->od, S_SWITCH_IN_CHECK_PORT_SEC, 80,
+                  ds_cstr(match),
+                  REGBIT_PORT_SEC_DROP " = 1; next;",
+                  op->peer->lflow_ref);
+
+    ds_clear(match);
+    ds_put_format(match, "inport == \"%s\" && nd_na", redirect_port);
+    ovn_lflow_add(lflows, op->peer->od, S_SWITCH_IN_CHECK_PORT_SEC, 80,
+                  ds_cstr(match),
+                  REGBIT_PORT_SEC_DROP " = 1; next;",
+                  op->peer->lflow_ref);
+
+    ds_clear(match);
+    ds_put_format(match, "inport == \"%s\" && nd_ra", redirect_port);
+    ovn_lflow_add(lflows, op->peer->od, S_SWITCH_IN_CHECK_PORT_SEC, 80,
+                  ds_cstr(match),
+                  REGBIT_PORT_SEC_DROP " = 1; next;",
+                  op->peer->lflow_ref);
+}
+
 /* This function adds ARP resolve flows related to a LSP. */
 static void
 build_arp_resolve_flows_for_lsp(
@@ -16900,6 +17119,8 @@  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);
 }
 
 static void *
diff --git a/northd/northd.h b/northd/northd.h
index e04ec5856..9e326b746 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 f2f42275a..f1e206cfc 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -13757,3 +13757,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; };)
+])
+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; };)
+])
+
+# 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 7ba2e150b..93ed7d17b 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -13504,3 +13504,103 @@  OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/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 lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.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 public bgp-daemon \
+    -- lsp-set-addresses bgp-daemon unknown
+
+AT_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 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
+
+# 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 "TCP test" | 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 "TCP test" | nc --send-only -6 fe80::200:2ff:fe01:203%ext-foo 179])
+
+# Verify connection in other direction. i.e when 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 "TCP test" | 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 "TCP test" | nc --send-only -6 fe80::210:10ff:fe01:213%bgp-daemon 179])
+
+
+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
+])