Skip to content
Permalink
Browse files

Add advanced routing configuration option to routed_bridge

Context
-------
Virtualization host with several mixed guests (kvm, lxc, docker, podman)
Multiple network interfaces on different networks
Multiple ip subnets served through a single nic (like OVH vrack setup)
Bridged setup to provide network connectivity to guests

Issue
-----
Depending on guest subnet, network traffic must be routed through the correct gateway

Solution
--------
iproute2 provides multiple routing tables features

OpenSVC interaction
-------------------
When private backend networks are used across the cluster, each routing table
must be aware of private ip segments

Example
-------

eth1@hypervisor <=> br0 <=> veth1 <=> eth1@container1 10.0.0.0/24 [routing table custom1]
                        <=> veth2 <=> eth2@container2 20.0.0.0/24 [routing table custom2]

Considering the private network below :

    [network#backend1]
    type = routed_bridge
    network = 10.11.0.0/16
    ips_per_node = 1024
    tunnel = always
    tables = main custom1 custom2

agent will manage network routes configuration for routing tables main, custom1, custom2

    root@node:~# ip route show table custom1
    default via 10.0.0.1 dev br0
    10.11.0.0/22 dev obr_backend1 scope link
    10.11.4.0/22 dev tun6023548126 scope link src 10.11.0.1

    root@node:~# ip route show table custom2
    default via 20.0.0.1 dev br0
    10.11.0.0/22 dev obr_backend1 scope link
    10.11.4.0/22 dev tun6023548126 scope link src 10.11.0.1
  • Loading branch information...
arnaudveron committed Aug 14, 2019
1 parent ff14886 commit fa77b96fdbf154517e10d66b342fb91c07a10e4d
Showing with 41 additions and 20 deletions.
  1. +27 −16 lib/node.py
  2. +4 −4 lib/nodeLinux.py
  3. +10 −0 lib/nodedict.py
@@ -5087,10 +5087,12 @@ def networks_data(self):
nets[name] = {}
nets[name]["config"] = config
nets[name]["routes"] = self.routes(name, config)
nets[name]["tables"] = self.tables(name)
nets["lo"] = {
"config": {
"type": "loopback",
"network": "127.0.0.1/32",
"tables": ["main"],
},
}
return nets
@@ -5134,6 +5136,12 @@ def node_subnet(self, name, nodename=None, config=None):
subnet = next(summarize_address_range(first, last))
return subnet

def tables(self, name):
try:
return self.oget("network#"+name, "tables")
except:
return

def routes(self, name, config=None):
routes = []
if not config:
@@ -5158,26 +5166,29 @@ def routes(self, name, config=None):
self.log.warning("node %s is not resolvable", rcEnv.nodename)
return routes
for nodename in self.cluster_nodes:
if nodename == rcEnv.nodename:
for table in config["tables"]:
if nodename == rcEnv.nodename:
routes.append({
"dst": str(self.node_subnet(name, nodename, config=config)),
"dev": "obr_"+name,
"brdev": "obr_"+name,
"table": table,
})
continue
try:
gw = socket.getaddrinfo(nodename, None)[0][4][0]
except socket.gaierror:
self.log.warning("node %s is not resolvable", nodename)
continue
routes.append({
"local_ip": local_ip,
"dst": str(self.node_subnet(name, nodename, config=config)),
"dev": "obr_"+name,
"gw": gw,
"brdev": "obr_"+name,
"brip": self.network_bridge_ip(name, config=config),
"table": table,
"tunnel": config["tunnel"],
})
continue
try:
gw = socket.getaddrinfo(nodename, None)[0][4][0]
except socket.gaierror:
self.log.warning("node %s is not resolvable", nodename)
continue
routes.append({
"local_ip": local_ip,
"dst": str(self.node_subnet(name, nodename, config=config)),
"gw": gw,
"brdev": "obr_"+name,
"brip": self.network_bridge_ip(name, config=config),
"tunnel": config["tunnel"],
})
return routes

def network_overlaps(self, name, nets=None):
@@ -107,20 +107,20 @@ def pid_mem_total(self, pid):
return sum(float(time) for time in
islice(stat_line.split(), 2, 5))

def network_route_add(self, dst=None, gw=None, dev=None, local_ip=None, brdev=None, brip=None, tunnel="auto"):
def network_route_add(self, dst=None, gw=None, dev=None, local_ip=None, brdev=None, brip=None, table=None, tunnel="auto"):
if dst is None:
return
if tunnel == "auto":
if gw is not None:
cmd = ["ip", "route", "replace", dst, "via", gw]
cmd = ["ip", "route", "replace", dst, "via", gw, "table", table]
elif dev is not None:
cmd = ["ip", "route", "replace", dst, "dev", dev]
cmd = ["ip", "route", "replace", dst, "dev", dev, "table", table]
out, err, ret = justcall(cmd)
else:
err = ""
if tunnel == "always" or "invalid gateway" in err or "is unreachable" in err:
tun = self.network_tunnel_ipip_add(local_ip, gw)
cmd = ["ip", "route", "replace", dst, "dev", tun["dev"], "src", brip.split("/")[0]]
cmd = ["ip", "route", "replace", dst, "dev", tun["dev"], "src", brip.split("/")[0], "table", table]
self.vcall(cmd)
else:
self.log.info(" ".join(cmd))
@@ -1090,6 +1090,16 @@
"default": "1024",
"text": "The number of allocatable ips per node on the network. Converted to the closest power of two."
},
{
"section": "network",
"rtype": "routed_bridge",
"keyword": "tables",
"default": ["main"],
"default_text": "main",
"convert": "list",
"text": "The list of routing tables to add the backend network routes to. The list of available tables is in ``/etc/iproute2/rt_tables``.",
"example": "main custom1 custom2"
},
{
"section": "network",
"rtype": "routed_bridge",

0 comments on commit fa77b96

Please sign in to comment.
You can’t perform that action at this time.