diff mbox series

[ovs-dev,v3,26/33] controller: Allow network namespaces for routes.

Message ID 9b502ff3e16c25d341a0bc308dee61e350af869e.1732630355.git.felix.huettner@stackit.cloud
State Changes Requested
Headers show
Series OVN Fabric integration | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success

Commit Message

Felix Huettner Nov. 26, 2024, 2:38 p.m. UTC
instead of using VRFs to announce routes we can now also use network
namespaces. This can be usefull if the network namespaces is used to
also run the routing protocol agent (e.g. frr).
The network namespace could then also have a vif port based on the
routing-protocol-redirect feature.

Signed-off-by: Felix Huettner <felix.huettner@stackit.cloud>
---
 NEWS                                |   2 +
 controller/route-exchange-netlink.c |  43 +++--
 controller/route-exchange-netlink.h |  14 +-
 controller/route-exchange.c         |   4 +-
 northd/northd.c                     |   3 +
 ovn-nb.xml                          |  18 ++
 tests/system-ovn.at                 | 247 +++++++++++++++++++++++++++-
 7 files changed, 308 insertions(+), 23 deletions(-)
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index a780940fc..da1e6a8d5 100644
--- a/NEWS
+++ b/NEWS
@@ -16,6 +16,8 @@  Post v24.09.0
     a lower priority than static routes.
   - Add the option "dynamic-routing-connected-as-host-routes" to LRPs. If set
     to true then connected routes are announced as individual host routes.
+  - Now also support announcing/learning routes in network namespaces. To use
+    this set "use-netns" on the LRP.
 
 OVN v24.09.0 - 13 Sep 2024
 --------------------------
diff --git a/controller/route-exchange-netlink.c b/controller/route-exchange-netlink.c
index 229fed595..4a3fb096c 100644
--- a/controller/route-exchange-netlink.c
+++ b/controller/route-exchange-netlink.c
@@ -38,7 +38,6 @@  static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
 #define TABLE_ID_VALID(table_id) (table_id != RT_TABLE_UNSPEC &&              \
                                   table_id != RT_TABLE_COMPAT &&              \
                                   table_id != RT_TABLE_DEFAULT &&             \
-                                  table_id != RT_TABLE_MAIN &&                \
                                   table_id != RT_TABLE_LOCAL &&               \
                                   table_id != RT_TABLE_MAX)
 
@@ -101,8 +100,8 @@  re_nl_delete_vrf(const char *ifname)
 }
 
 static int
-modify_route(uint32_t type, uint32_t flags_arg, uint32_t table_id,
-             const struct in6_addr *dst, unsigned int plen,
+modify_route(const char *netns, uint32_t type, uint32_t flags_arg,
+             uint32_t table_id, const struct in6_addr *dst, unsigned int plen,
              unsigned int priority)
 {
     uint32_t flags = NLM_F_REQUEST | NLM_F_ACK;
@@ -137,15 +136,16 @@  modify_route(uint32_t type, uint32_t flags_arg, uint32_t table_id,
         nl_msg_put_in6_addr(&request, RTA_DST, dst);
     }
 
-    err = nl_ns_transact(NULL, NETLINK_ROUTE, &request, NULL);
+    err = nl_ns_transact(netns, NETLINK_ROUTE, &request, NULL);
     ofpbuf_uninit(&request);
 
     return err;
 }
 
 int
-re_nl_add_route(uint32_t table_id, const struct in6_addr *dst,
-                unsigned int plen, unsigned int priority)
+re_nl_add_route(const char *netns, uint32_t table_id,
+                const struct in6_addr *dst, unsigned int plen,
+                unsigned int priority)
 {
     uint32_t flags = NLM_F_CREATE | NLM_F_EXCL;
     uint32_t type = RTM_NEWROUTE;
@@ -157,12 +157,13 @@  re_nl_add_route(uint32_t table_id, const struct in6_addr *dst,
         return EINVAL;
     }
 
-    return modify_route(type, flags, table_id, dst, plen, priority);
+    return modify_route(netns, type, flags, table_id, dst, plen, priority);
 }
 
 int
-re_nl_delete_route(uint32_t table_id, const struct in6_addr *dst,
-                   unsigned int plen, unsigned int priority)
+re_nl_delete_route(const char * netns, uint32_t table_id,
+                   const struct in6_addr *dst, unsigned int plen,
+                   unsigned int priority)
 {
     if (!TABLE_ID_VALID(table_id)) {
         VLOG_WARN_RL(&rl,
@@ -171,7 +172,7 @@  re_nl_delete_route(uint32_t table_id, const struct in6_addr *dst,
         return EINVAL;
     }
 
-    return modify_route(RTM_DELROUTE, 0, table_id, dst, plen, priority);
+    return modify_route(netns, RTM_DELROUTE, 0, table_id, dst, plen, priority);
 }
 
 static uint32_t
@@ -195,6 +196,7 @@  re_nl_received_routes_destroy(struct hmap *host_routes)
 struct route_msg_handle_data {
     const struct hmap *routes;
     struct hmap *learned_routes;
+    const char *netns;
 };
 
 static void
@@ -237,7 +239,8 @@  handle_route_msg_delete_routes(const struct route_table_msg *msg, void *data)
         }
     }
 
-    err = re_nl_delete_route(rd->rta_table_id, &rd->rta_dst,
+    err = re_nl_delete_route(handle_data->netns,
+                             rd->rta_table_id, &rd->rta_dst,
                              rd->plen, rd->rta_priority);
     if (err) {
         char addr_s[INET6_ADDRSTRLEN + 1];
@@ -252,8 +255,16 @@  handle_route_msg_delete_routes(const struct route_table_msg *msg, void *data)
 
 void
 re_nl_sync_routes(uint32_t table_id,
-                  const struct hmap *routes, struct hmap *learned_routes)
+                  const struct hmap *routes, struct hmap *learned_routes,
+                  bool use_netns)
 {
+
+    char * netns = NULL;
+    if (use_netns) {
+        netns = xasprintf("ovnns%d", table_id);
+        table_id = RT_TABLE_MAIN;
+    }
+
     struct advertise_route_entry *ar;
     HMAP_FOR_EACH (ar, node, routes) {
         ar->installed = false;
@@ -265,8 +276,9 @@  re_nl_sync_routes(uint32_t table_id,
     struct route_msg_handle_data data = {
         .routes = routes,
         .learned_routes = learned_routes,
+        .netns = netns,
     };
-    route_table_dump_one_table(NULL, table_id, handle_route_msg_delete_routes,
+    route_table_dump_one_table(netns, table_id, handle_route_msg_delete_routes,
                                &data);
 
     /* Add any remaining routes in the host_routes hmap to the system routing
@@ -275,8 +287,8 @@  re_nl_sync_routes(uint32_t table_id,
         if (ar->installed) {
             continue;
         }
-        int err = re_nl_add_route(table_id, &ar->addr, ar->plen,
-                                  ar->priority);
+        int err = re_nl_add_route(netns, table_id, &ar->addr,
+                                  ar->plen, ar->priority);
         if (err) {
             char addr_s[INET6_ADDRSTRLEN + 1];
             VLOG_WARN_RL(&rl, "Add route table_id=%"PRIu32" dst=%s "
@@ -288,4 +300,5 @@  re_nl_sync_routes(uint32_t table_id,
                          ovs_strerror(err));
         }
     }
+    free(netns);
 }
diff --git a/controller/route-exchange-netlink.h b/controller/route-exchange-netlink.h
index 13346e944..d93835178 100644
--- a/controller/route-exchange-netlink.h
+++ b/controller/route-exchange-netlink.h
@@ -15,6 +15,7 @@ 
 #ifndef ROUTE_EXCHANGE_NETLINK_H
 #define ROUTE_EXCHANGE_NETLINK_H 1
 
+#include <stdbool.h>
 #include <stdint.h>
 #include "openvswitch/hmap.h"
 #include <netinet/in.h>
@@ -38,16 +39,19 @@  struct re_nl_received_route_node {
 int re_nl_create_vrf(const char *ifname, uint32_t table_id);
 int re_nl_delete_vrf(const char *ifname);
 
-int re_nl_add_route(uint32_t table_id, const struct in6_addr *dst,
-                    unsigned int plen, unsigned int priority);
-int re_nl_delete_route(uint32_t table_id, const struct in6_addr *dst,
-                       unsigned int plen, unsigned int priority);
+int re_nl_add_route(const char *netns, uint32_t table_id,
+                    const struct in6_addr *dst, unsigned int plen,
+                    unsigned int priority);
+int re_nl_delete_route(const char *netns, uint32_t table_id,
+                       const struct in6_addr *dst, unsigned int plen,
+                       unsigned int priority);
 
 void re_nl_dump(uint32_t table_id);
 
 void re_nl_received_routes_destroy(struct hmap *);
 void re_nl_sync_routes(uint32_t table_id,
                        const struct hmap *host_routes,
-                       struct hmap *learned_routes);
+                       struct hmap *learned_routes,
+                       bool use_netns);
 
 #endif /* route-exchange-netlink.h */
diff --git a/controller/route-exchange.c b/controller/route-exchange.c
index cc4a1af03..f6cd97bb9 100644
--- a/controller/route-exchange.c
+++ b/controller/route-exchange.c
@@ -209,8 +209,8 @@  route_exchange_run(struct route_exchange_ctx_in *r_ctx_in,
             sset_find_and_delete(&old_maintained_vrfs, vrf_name);
         }
 
-        re_nl_sync_routes(ad->key, &ad->routes,
-                          &received_routes);
+        re_nl_sync_routes(ad->key,
+                          &ad->routes, &received_routes, ad->use_netns);
 
         sb_sync_learned_routes(ad->db, &received_routes,
                                &ad->bound_ports,
diff --git a/northd/northd.c b/northd/northd.c
index b637a7183..d6b14b615 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -4062,6 +4062,9 @@  sync_pb_for_lrp(struct ovn_port *op,
         if (smap_get_bool(&op->nbrp->options, "maintain-vrf", false)) {
             smap_add(&new, "maintain-vrf", "true");
         }
+        if (smap_get_bool(&op->nbrp->options, "use-netns", false)) {
+            smap_add(&new, "use-netns", "true");
+        }
         if (smap_get_bool(&op->od->nbr->options, "dynamic-routing", false)) {
             smap_add(&new, "dynamic-routing", "true");
         }
diff --git a/ovn-nb.xml b/ovn-nb.xml
index b99a273ec..c1973be39 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -3743,6 +3743,24 @@  or
           of the Logical Router appended to it.
           This vrf will contain all the routes that should be announced from
           this LRP.
+
+          It is mutually exclusive with
+          <ref column="options" key="use-netns"/>.
+      </column>
+
+      <column name="options" key="use-netns" type='{"type": "boolean"}'>
+        Only relevant if <ref column="options" key="dynamic-routing"
+        table="Logical_Router"/> on the respective Logical_Router is set
+          to <code>true</code>.
+
+          If this LRP is bound to a specific chassis then the ovn-controller of
+          this chassis will use a network namespace named "ovnns" with the
+          datapath id of the Logical Router appended to it instead of a vrf.
+          This netns will contain all the routes that should be announced from
+          this LRP in the default vrf.
+
+          It is mutually exclusive with
+          <ref column="options" key="maintain-vrf"/>.
       </column>
 
       <column name="options" key="dynamic-routing-ifname"
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index e9ffaad93..7ae57d406 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -14155,7 +14155,7 @@  AT_CLEANUP
 ])
 
 OVN_FOR_EACH_NORTHD([
-AT_SETUP([dynamic-routing - DGP])
+AT_SETUP([dynamic-routing - DGP - VRF])
 
 VRF_RESERVE([1337])
 
@@ -14386,6 +14386,251 @@  check ovn-nbctl --wait=hv set Logical_Router_Port internet-phys \
 check_row_count Route 1 type=receive
 
 
+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
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([dynamic-routing - DGP - NETNS])
+
+ADD_NAMESPACES([ovnns1337])
+NS_EXEC([ovnns1337], [ip link set lo up])
+
+# This test uses dynamic routing on a simulated multi-tenant internet
+# connection.
+# Tenant 1 (pr1, p1, vif1) is connected to the internet via NAT on pr1.
+# Tenant 2 (pr2, p2, vif2) is connected to the internet via routing.
+# The connections of pr1 and pr2 to public are using DGPs.
+# The connection from internet to phys is also using a DGP.
+# The LR internet is running dynamic-routing.
+# The LS phys is assumed to be used for peering with a router outside OVN
+#
+#
+# +----+       +----+
+# |vif1|       |vif2|
+# +--+-+       +--+-+
+#    |            |
+# +--+--+      +--+--+
+# |LS p1|      |LS p2|
+# +--+--+      +--+--+
+#    |            |
+# +--+---+     +--+---+
+# |LR pr1|     |LR pr2|
+# +-----++     ++-----+
+#       |       |
+#      ++-------++
+#      |LS public|
+#      +-----+---+
+#            |
+#      +-----+-----+
+#      |LR internet|
+#      +-----+-----+
+#            |
+#        +---+---+
+#        |LS phys|
+#        +-------+
+
+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
+
+# LS setup
+
+check ovn-nbctl ls-add public
+check ovn-nbctl ls-add phys
+check ovn-nbctl ls-add p1
+check ovn-nbctl ls-add p2
+
+# LR internet setup
+
+check ovn-nbctl lr-add internet \
+    -- set Logical_Router internet options:dynamic-routing=true \
+                                   options:requested-tnl-key=1337
+
+check ovn-nbctl lrp-add internet internet-public \
+        00:00:02:01:02:03 192.0.2.1/24 \
+    -- set Logical_Router_Port internet-public \
+                             options:dynamic-routing-connected=true \
+                             options:dynamic-routing-static=true
+check ovn-nbctl lsp-add public public-internet \
+    -- set Logical_Switch_Port public-internet type=router \
+                                         options:router-port=internet-public \
+    -- lsp-set-addresses public-internet router
+
+check ovn-nbctl lrp-add internet internet-phys \
+        00:00:ff:00:00:01 192.168.10.1/24 \
+    -- set Logical_Router_Port internet-phys \
+                             options:use-netns=true
+check ovn-nbctl lrp-set-gateway-chassis internet-phys hv1
+check ovn-nbctl lsp-add phys phys-internet \
+    -- set Logical_Switch_Port phys-internet type=router \
+                                         options:router-port=internet-phys \
+    -- lsp-set-addresses phys-internet router
+
+# LR pr1 setup
+
+check ovn-nbctl lr-add pr1 \
+    -- set Logical_Router pr1 options:requested-tnl-key=1338
+
+check ovn-nbctl lrp-add pr1 pr1-public \
+        00:00:02:01:02:04 192.0.2.2/24
+check ovn-nbctl lrp-set-gateway-chassis pr1-public hv1
+check ovn-nbctl lsp-add public public-pr1 \
+    -- set Logical_Switch_Port public-pr1 type=router \
+                                         options:router-port=pr1-public \
+    -- lsp-set-addresses public-pr1 router
+
+check ovn-nbctl lrp-add pr1 pr1-p1 \
+        00:00:03:00:00:01 10.0.0.1/24
+check ovn-nbctl lsp-add p1 p1-pr1 \
+    -- set Logical_Switch_Port p1-pr1 type=router \
+                                         options:router-port=pr1-p1 \
+    -- lsp-set-addresses p1-pr1 router
+
+check ovn-nbctl lr-route-add pr1 0.0.0.0/0 192.0.2.1
+
+# LR pr2 setup
+
+check ovn-nbctl lr-add pr2 \
+    -- set Logical_Router pr2 options:requested-tnl-key=1339
+
+check ovn-nbctl lrp-add pr2 pr2-public \
+        00:00:02:01:02:05 192.0.2.3/24
+check ovn-nbctl lrp-set-gateway-chassis pr2-public hv1
+check ovn-nbctl lsp-add public public-pr2 \
+    -- set Logical_Switch_Port public-pr2 type=router \
+                                         options:router-port=pr2-public \
+    -- lsp-set-addresses public-pr2 router
+
+check ovn-nbctl lrp-add pr2 pr2-p2 \
+        00:00:04:00:00:01 198.51.100.1/24
+check ovn-nbctl lsp-add p2 p2-pr2 \
+    -- set Logical_Switch_Port p2-pr2 type=router \
+                                         options:router-port=pr2-p2 \
+    -- lsp-set-addresses p2-pr2 router
+
+check ovn-nbctl lr-route-add pr2 0.0.0.0/0 192.0.2.1
+
+# Setup lsp "vif1" with NAT
+check ovn-nbctl lsp-add p1 vif1 \
+    -- lsp-set-addresses vif1 "00:00:ff:ff:ff:01 10.0.0.2"
+check ovn-nbctl lr-nat-add pr1 dnat_and_snat 192.0.2.10 10.0.0.2
+
+# Setup lsp "vif2" with a static route on LR internet
+check ovn-nbctl lsp-add p2 vif2 \
+    -- lsp-set-addresses vif2 "00:00:ff:ff:ff:02 198.51.100.10"
+check ovn-nbctl lr-route-add internet 198.51.100.0/24 192.0.2.3
+
+# Configure external connectivity
+check ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext
+check ovn-nbctl lsp-add phys phys1 \
+        -- lsp-set-addresses phys1 unknown \
+        -- lsp-set-type phys1 localnet \
+        -- lsp-set-options phys1 network_name=phynet
+
+check ovn-nbctl --wait=hv sync
+
+# now the ovn-controller should have used the netns "ovnns1337" for the
+# following routes:
+# * 192.0.2.0/24
+# * 198.51.100.0/24
+
+# ip route list output has a trailing space on each line
+# the awk magic removes all trailing spaces.
+OVS_WAIT_UNTIL_EQUAL([ip netns exec ovnns1337 ip route list | awk '{$1=$1};1'], [dnl
+blackhole 192.0.2.0/24 proto 84 metric 1000
+blackhole 198.51.100.0/24 proto 84 metric 1000])
+
+# we now switch to announcing host routes and expect 192.0.2.0/24 to be gone
+# and the following to be added:
+# * 192.0.2.1/32
+# * 192.0.2.2/32
+# * 192.0.2.3/32
+# * 192.0.2.10/32
+# the last 3 of them are local to the current chassis so we expect a better
+# prio.
+check ovn-nbctl --wait=hv set Logical_Router_Port internet-public \
+                         options:dynamic-routing-connected-as-host-routes=true
+
+OVS_WAIT_UNTIL_EQUAL([ip netns exec ovnns1337 ip route list | awk '{$1=$1};1'], [dnl
+blackhole 192.0.2.1 proto 84 metric 1000
+blackhole 192.0.2.2 proto 84 metric 100
+blackhole 192.0.2.3 proto 84 metric 100
+blackhole 192.0.2.10 proto 84 metric 100
+blackhole 198.51.100.0/24 proto 84 metric 1000])
+
+# if the pr1-public lrp is now removed from this hypervisor the route metric
+# will go back to the default.
+# For this we just schedule it on a non existing chassis
+check ovn-nbctl lrp-del-gateway-chassis pr1-public hv1
+check ovn-nbctl --wait=hv lrp-set-gateway-chassis pr1-public hv123
+OVS_WAIT_UNTIL_EQUAL([ip netns exec ovnns1337 ip route list | awk '{$1=$1};1'], [dnl
+blackhole 192.0.2.1 proto 84 metric 1000
+blackhole 192.0.2.2 proto 84 metric 1000
+blackhole 192.0.2.3 proto 84 metric 100
+blackhole 192.0.2.10 proto 84 metric 1000
+blackhole 198.51.100.0/24 proto 84 metric 1000])
+
+# moving pr1-public back will also change the route metrics again
+check ovn-nbctl lrp-del-gateway-chassis pr1-public hv123
+check ovn-nbctl --wait=hv lrp-set-gateway-chassis pr1-public hv1
+OVS_WAIT_UNTIL_EQUAL([ip netns exec ovnns1337 ip route list | awk '{$1=$1};1'], [dnl
+blackhole 192.0.2.1 proto 84 metric 1000
+blackhole 192.0.2.2 proto 84 metric 100
+blackhole 192.0.2.3 proto 84 metric 100
+blackhole 192.0.2.10 proto 84 metric 100
+blackhole 198.51.100.0/24 proto 84 metric 1000])
+
+# now we test route learning
+check_row_count Route 0 type=receive
+NS_EXEC([ovnns1337], [ip route add 233.252.0.0/24 via 192.168.10.10 dev lo onlink])
+# for now we trigger a recompute as route watching is not yet implemented
+check ovn-appctl -t ovn-controller inc-engine/recompute
+check ovn-nbctl --wait=hv sync
+check_row_count Route 1 type=receive
+AT_CHECK([ovn-sbctl --columns ip_prefix,nexthop,logical_port --bare find Route type=receive], [0], [dnl
+233.252.0.0/24
+192.168.10.10
+internet-phys
+])
+
+# by setting a learning interface filter we will now forget about this route
+check ovn-nbctl --wait=hv set Logical_Router_Port internet-phys \
+      options:dynamic-routing-ifname=thisdoesnotexist
+check_row_count Route 0 type=receive
+
+# chaning it to "lo" will allow us to learn the route again
+check ovn-nbctl --wait=hv set Logical_Router_Port internet-phys \
+      options:dynamic-routing-ifname=lo
+check_row_count Route 1 type=receive
+
 OVS_APP_EXIT_AND_WAIT([ovn-controller])
 
 as ovn-sb