@@ -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
--------------------------
@@ -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);
}
@@ -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 */
@@ -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,
@@ -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");
}
@@ -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"
@@ -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
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(-)