From 48377b7383319f24f7d5942baf1c92da2f951d38 Mon Sep 17 00:00:00 2001 From: Mark Schouten Date: Wed, 16 May 2018 15:33:42 +0200 Subject: [PATCH 01/10] Gather routes that are active, and allow routes in the config --- ypconfig/config.py | 37 ++++++++++++++++++++++++++++++++++--- ypconfig/netlink.py | 21 +++++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/ypconfig/config.py b/ypconfig/config.py index 81c69b5..6474f74 100644 --- a/ypconfig/config.py +++ b/ypconfig/config.py @@ -63,6 +63,12 @@ def IP(ip): else: return ipv4(ip) + def SingleIP(ip): + if ':' in ip: + return ipv6('%s/128' % (ip)) + else: + return ipv4('%s/32' % (ip)) + def Adminstate(state): if type(int()) == type(state): state = list(['DOWN', 'UP'])[state] @@ -230,16 +236,41 @@ def Interface(iface, iname): ret['lacp_rate'] = 'slow' return ret + def Route(gateways, destination): + # Test the destination, which should be a network or 'default' + + if destination != 'default': + try: + IP(destination) + except ValueError as e: + raise e + + # Now, test all gateways + for gw in gateways: + try: + SingleIP(gw) + except ValueError as e: + raise e + + return gateways + # First, check if any weird values occur olddoc = deepcopy(document) for iface in olddoc.keys(): - if not document[iface]: - raise ValueError("Empty interface configuration for %s" % (iface)) - document[iface] = Interface(document[iface], iface) + if iface == 'routes': + for route in document[iface]: + document[iface][route] = Route(document[iface][route], route) + else: + if not document[iface]: + raise ValueError("Empty interface configuration for %s" % (iface)) + document[iface] = Interface(document[iface], iface) # Then, check if interfaces used in bonds are actually unconfigured olddoc = deepcopy(document) for iface in olddoc.keys(): + if iface == 'routes': + continue + if document[iface]['type'] == 'bond': for s in document[iface]['slaves']: try: diff --git a/ypconfig/netlink.py b/ypconfig/netlink.py index 8d6ee47..cda4b0e 100644 --- a/ypconfig/netlink.py +++ b/ypconfig/netlink.py @@ -2,11 +2,32 @@ from pyroute2 import IPRoute from pyroute2 import IPDB +from socket import AF_INET, AF_INET6 + def GetNow(): ip = IPRoute() ret = {} + for route in ip.get_routes() + ip.get_routes(family=AF_INET6): + if route.get_attr('RTA_GATEWAY'): + try: + ret['routes'] + except KeyError: + ret['routes'] = dict() + + if not route.get_attr('RTA_DST'): + dst = 'default' + else: + dst = '/'.join([str(route.get_attr('RTA_DST')), str(route['dst_len'])]) + + try: + ret['routes'][dst] + except KeyError: + ret['routes'][dst] = list() + + ret['routes'][dst].append(route.get_attr('RTA_GATEWAY')) + for iface in ip.get_links(): iname = iface.get_attr('IFLA_IFNAME') try: From 3edec379c398b583606b3eeda060cd1a00c43435 Mon Sep 17 00:00:00 2001 From: Mark Schouten Date: Fri, 18 May 2018 14:53:09 +0200 Subject: [PATCH 02/10] Change, set and delete routes. The default route is a bitch because it's a specialcase but it's quite hard to differentiate between AF_INET and AF_INET6. This seemed like the most efficient fix for now. We can probably do better. --- ypconfig/netlink.py | 104 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/ypconfig/netlink.py b/ypconfig/netlink.py index cda4b0e..a376578 100644 --- a/ypconfig/netlink.py +++ b/ypconfig/netlink.py @@ -103,10 +103,28 @@ def GetNow(): def Commit(cur, new): global ip + + skiproutes = False + changed = False + + try: + curroutes = cur['routes'] + newroutes = new['routes'] + crouteset = set(cur['routes'].keys()) + nrouteset = set(new['routes'].keys()) + except KeyError: + skiproutes = True + print("No routes configured, skipping routeconfiguration") + + try: + del(cur['routes']) + del(new['routes']) + except: + pass + curif = set(cur.keys()) newif = set(new.keys()) - changed = False with IPDB(mode='implicit') as ip: curif = set(cur.keys()) newif = set(new.keys()) @@ -199,11 +217,95 @@ def Commit(cur, new): pass except Exception as e: raise e + + if not skiproutes: + try: + # These routes should be deleted + for route in crouteset.difference(nrouteset): + changed = True + DelRoute(route) + + # These routes should be created + for route in nrouteset.difference(crouteset): + changed = True + AddRoute(route, newroutes[route]) + + # These routes should be checked + for route in nrouteset.intersection(crouteset): + if curroutes[route] != newroutes[route]: + changed = True + ChangeRoute(route, newroutes[route]) + + except Exception as e: + raise e + ip.commit() ip.release() return changed +def AddRoute(route, gws): + print("Adding route for %s/%s" % (route, gws)) + global ip + for d in gws: + ip.routes.add(dst=route, gateway=d) + +def DelRoute(route): + print("Removing route for %s" % (route)) + global ip + ip.routes.remove(route) + +def DefaultRoute(gws): + global ip + v6new = [ d for d in gws if ':' in d ] + v4new = [ d for d in gws if '.' in d ] + v6cur = list() + v4cur = list() + for r in ip.routes: + if r['dst_len'] != 0: + continue + if r['family'] == 10: + v6cur.append(r['gateway']) + elif r['family'] == 2: + v4cur.append(r['gateway']) + + if len(v6new) > len(v6cur): + AddRoute('default', v6new) + + if len(v4new) > len(v4cur): + AddRoute('default', v4new) + + for r in ip.routes: + if r['dst_len'] != 0: + continue + if r['family'] == 10: + if len(v6new) == 0: + r.remove() + elif len(v6new) == len(v6cur): + if v6new[0] == v6cur[0]: + continue + r.remove() + ip.commit() + AddRoute('default', v6new) + elif r['family'] == 2: + if len(v4new) == 0: + r.remove() + elif len(v4new) == len(v4cur): + if v4new[0] == v4cur[0]: + continue + r.remove() + ip.commit() + AddRoute('default', v4new) + +def ChangeRoute(route, gws): + print("Changing route for %s" % (route)) + global ip + if route == 'default': + return DefaultRoute(gws) + + for d in gws: + ip.routes[route].gateway = d + def Delif(iface): print("Removing interface %s" % (iface)) global ip From afed2d7a7e39d7c9161a84e320eb227ea1171e2a Mon Sep 17 00:00:00 2001 From: Mark Schouten Date: Fri, 18 May 2018 14:55:21 +0200 Subject: [PATCH 03/10] Fix path of default configfile in documentation --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index befeef9..9eb7cb2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,4 +1,4 @@ -ypconfig reads a yaml-file (defaults to /etc/ypconfig/interfaces.yml) and tries to configure the system accordingly. The configfile has the following syntax: +ypconfig reads a yaml-file (defaults to /etc/ypconfig/ypconfig.yml) and tries to configure the system accordingly. The configfile has the following syntax: An array of interfaces, where each interface has the following options: - description: From 89380ebbfbdde3c9bd4a46633eb41a31f38f0598 Mon Sep 17 00:00:00 2001 From: Mark Schouten Date: Fri, 18 May 2018 15:00:44 +0200 Subject: [PATCH 04/10] Add documentation about routes --- docs/configuration.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 9eb7cb2..8e3a259 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -39,7 +39,20 @@ An array of interfaces, where each interface has the following options: - lacp_rate: Only valid if mode is ```802.3ad```. Can be ```slow``` or ```fast```, defaults to ```slow```. - +ROUTES +====== +There is a special 'interface' called ```routes```. If the configfile does not have a ```routes```, routes remain ***untouched***. If you do want ypconfig to handle routes, here's how: + + routes: + default: + - 192.168.1.1 + - fd00::192:168:1:1 + 9.9.9.9/32: + - 192.168.2.1 + 2620:fe::fe/128: + - fd00::192:168:2:1 + +Although the routes use an array for nexthop, only one nexthop is currently supported. EXAMPLE 1 ========= From f7852e92e534c831d06d07a3730e13b7f88aca46 Mon Sep 17 00:00:00 2001 From: Mark Schouten Date: Fri, 18 May 2018 15:19:42 +0200 Subject: [PATCH 05/10] This was a too easy way. On a freshly booted machine, cur['routes'] is empty... --- ypconfig/netlink.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ypconfig/netlink.py b/ypconfig/netlink.py index a376578..85aeded 100644 --- a/ypconfig/netlink.py +++ b/ypconfig/netlink.py @@ -109,16 +109,21 @@ def Commit(cur, new): try: curroutes = cur['routes'] - newroutes = new['routes'] crouteset = set(cur['routes'].keys()) + except KeyError: + crouteset = set() + print("No routes configured, skipping routeconfiguration") + + try: + newroutes = new['routes'] nrouteset = set(new['routes'].keys()) except KeyError: skiproutes = True print("No routes configured, skipping routeconfiguration") try: - del(cur['routes']) del(new['routes']) + del(cur['routes']) except: pass From 6d37a57e22818cc71c62241e74e58971a9a7452e Mon Sep 17 00:00:00 2001 From: Mark Schouten Date: Thu, 31 May 2018 14:17:28 +0200 Subject: [PATCH 06/10] The gateway can be a link-local. Allow linklocal as a gatewat --- ypconfig/config.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ypconfig/config.py b/ypconfig/config.py index 6474f74..7220ddd 100644 --- a/ypconfig/config.py +++ b/ypconfig/config.py @@ -45,8 +45,8 @@ def ipv4(teststring): else: raise ValueError("This is not a valid IPv4 address: %s" % (teststring)) - def ipv6(teststring): - if teststring.startswith('fe80:'): + def ipv6(teststring, allowlinklocal=False): + if not allowlinklocal and teststring.startswith('fe80:'): raise ValueError("Do not configure link-local addresses") try: @@ -63,9 +63,9 @@ def IP(ip): else: return ipv4(ip) - def SingleIP(ip): + def SingleIP(ip, allowlinklocal=False): if ':' in ip: - return ipv6('%s/128' % (ip)) + return ipv6('%s/128' % (ip), allowlinklocal) else: return ipv4('%s/32' % (ip)) @@ -248,7 +248,7 @@ def Route(gateways, destination): # Now, test all gateways for gw in gateways: try: - SingleIP(gw) + SingleIP(gw, allowlinklocal=True) except ValueError as e: raise e From 11dfe953b27b39e4337a08904fcf11ed7e12df93 Mon Sep 17 00:00:00 2001 From: Mark Schouten Date: Thu, 31 May 2018 14:27:52 +0200 Subject: [PATCH 07/10] Remove real ip addresses from documentation --- docs/configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 8e3a259..26c28cb 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -47,9 +47,9 @@ There is a special 'interface' called ```routes```. If the configfile does not h default: - 192.168.1.1 - fd00::192:168:1:1 - 9.9.9.9/32: + 172.16.0.0/24: - 192.168.2.1 - 2620:fe::fe/128: + fd08::172:16:0:0/64 - fd00::192:168:2:1 Although the routes use an array for nexthop, only one nexthop is currently supported. From b402634202c07060f52705243a8eb993aaf18a49 Mon Sep 17 00:00:00 2001 From: Mark Schouten Date: Thu, 31 May 2018 14:32:35 +0200 Subject: [PATCH 08/10] Remove fake interface 'routes' seperatly. If either one doesn't exist weird stuff happens --- ypconfig/netlink.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ypconfig/netlink.py b/ypconfig/netlink.py index 85aeded..09b4c0d 100644 --- a/ypconfig/netlink.py +++ b/ypconfig/netlink.py @@ -123,6 +123,10 @@ def Commit(cur, new): try: del(new['routes']) + except: + pass + + try: del(cur['routes']) except: pass From a9935ce2ca7e99b00a7878b49dce5b16143f77ff Mon Sep 17 00:00:00 2001 From: Mark Schouten Date: Thu, 31 May 2018 14:35:38 +0200 Subject: [PATCH 09/10] Remove faulty output --- ypconfig/netlink.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ypconfig/netlink.py b/ypconfig/netlink.py index 09b4c0d..b07cd07 100644 --- a/ypconfig/netlink.py +++ b/ypconfig/netlink.py @@ -112,7 +112,6 @@ def Commit(cur, new): crouteset = set(cur['routes'].keys()) except KeyError: crouteset = set() - print("No routes configured, skipping routeconfiguration") try: newroutes = new['routes'] From 8593116657c9e1dd04555c5d8395a7279c09b1b4 Mon Sep 17 00:00:00 2001 From: Mark Schouten Date: Thu, 31 May 2018 15:21:40 +0200 Subject: [PATCH 10/10] We need to commit in the middle of things. Netlink is confused otherwise --- ypconfig/netlink.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ypconfig/netlink.py b/ypconfig/netlink.py index b07cd07..2ff948e 100644 --- a/ypconfig/netlink.py +++ b/ypconfig/netlink.py @@ -257,6 +257,7 @@ def AddRoute(route, gws): global ip for d in gws: ip.routes.add(dst=route, gateway=d) + ip.commit() def DelRoute(route): print("Removing route for %s" % (route))