From fc8eda81981c933a9169a6e8a4bc078777b42c7d Mon Sep 17 00:00:00 2001 From: brent s Date: Wed, 13 May 2020 13:31:16 -0400 Subject: [PATCH] some updates to how i handle address allocation for he_ipv6 --- utils/he_ipv6/__init__.py | 1 + utils/he_ipv6/config.py | 78 ++++++++++++++++- utils/he_ipv6/example.tunnelbroker.xml | 81 ++++++++++++----- utils/he_ipv6/radvd.py | 115 +++++++++++++++++++++++++ utils/he_ipv6/ref | 5 +- 5 files changed, 253 insertions(+), 27 deletions(-) create mode 100644 utils/he_ipv6/radvd.py diff --git a/utils/he_ipv6/__init__.py b/utils/he_ipv6/__init__.py index 0ca8cd6..3e859d4 100644 --- a/utils/he_ipv6/__init__.py +++ b/utils/he_ipv6/__init__.py @@ -1,4 +1,5 @@ from . import args +from . import radvd from . import config from . import logger from . import tunnelbroker diff --git a/utils/he_ipv6/config.py b/utils/he_ipv6/config.py index 4bceac8..e13c0a3 100644 --- a/utils/he_ipv6/config.py +++ b/utils/he_ipv6/config.py @@ -8,6 +8,20 @@ import netaddr import requests from lxml import etree from pyroute2 import IPRoute +## +from . import radvd + + +def xml2bool(xml_str): + if xml_str is None: + return(None) + xml_str = xml_str.lower()[0] + if xml_str == ('t', '1'): + return(True) + elif xml_str == ('f', '0'): + return(False) + else: + raise ValueError('Not a boolean value') class IP(object): @@ -50,17 +64,60 @@ class IP6(IP): def __init__(self, ip, prefix, *args, **kwargs): super().__init__(ip, prefix, *args, **kwargs) self._ext_init() + self.alloc_block = netaddr.SubnetSplitter(self.net_net) + + +class Assignment(object): + def __init__(self, assign_xml, radvd = False, dns = False): + self.xml = assign_xml + self.do_radvd = radvd + self.radvd_dns = dns + self.iface = None + self.alloc = None # This must be set externally to a mapped Allocation instance + self.alloc_name = None + self.prefix = None + self.iface_ip = None + self.alloc_block = None + self.parse() + + def _alloc(self): + self.alloc_name = self.xml.attrib['alloc'].strip() + return(None) + + def _iface(self): + self.iface = self.xml.attrib['iface'].strip() + return(None) + + def _prefix(self): + self.prefix = int(self.xml.attrib.get('prefix', 64).strip()) + return(None) + + def parse(self): + self._iface() + self._alloc() + self._prefix() + return(None) + + def parse_alloc(self): + self.iface_ip = IP6(str(next(self.alloc.ip.net.hosts())), 128) + self.alloc_block = self.alloc.ip.alloc_block + return(None) class Allocation(object): def __init__(self, alloc_xml): self.xml = alloc_xml + self.id = None self.prefix = None self.ip = None self.iface = None self.iface_idx = None self.parse() + def _id(self): + self.id = self.xml.attrib['id'].strip() + return(None) + def _iface(self): _iface_txt = self.xml.attrib['iface'] self.iface = _iface_txt.strip() @@ -77,6 +134,7 @@ class Allocation(object): return(None) def parse(self): + self._id() self._iface() self._ip() return(None) @@ -90,13 +148,26 @@ class Tunnel(object): self.server = None self.creds = None # This should be handled externally and map to a Cred obj self.creds_id = None - self.allocations = [] + self.allocations = {} + self.assignments = [] self.parse() def _allocations(self): - _allocs_xml = self.xml.find('allocs') + _allocs_xml = self.xml.find('allocations') for _allocation_xml in _allocs_xml.findall('alloc'): - self.allocations.append(Allocation(_allocation_xml)) + alloc = Allocation(_allocation_xml) + self.allocations[alloc.id] = alloc + return(None) + + def _assignments(self): + _assigns_xml = self.xml.find('assignments') + radvd = xml2bool(_assigns_xml.attrib.get('radvd', 'false')) + radvd_dns = xml2bool(_assigns_xml.attrib.get('radvdDns', 'false')) + for _assign_xml in _assigns_xml.findall('assign'): + assign = Assignment(_assign_xml, radvd = radvd, dns = radvd_dns) + assign.alloc = self.allocations[assign.alloc_name] + assign.parse_alloc() + self.assignments.append(assign) return(None) def _client(self): @@ -126,6 +197,7 @@ class Tunnel(object): self._client() self._server() self._allocations() + self._assignments() return(None) diff --git a/utils/he_ipv6/example.tunnelbroker.xml b/utils/he_ipv6/example.tunnelbroker.xml index 7b2c685..1a13420 100644 --- a/utils/he_ipv6/example.tunnelbroker.xml +++ b/utils/he_ipv6/example.tunnelbroker.xml @@ -45,41 +45,76 @@ --> 192.0.2.1 - - - - 2001:DB8:1:2:: - - 2001:DB8:2:: - - 2001:DB8:3::2 + + + + 2001:DB8:1:2:: + + 2001:DB8:2:: + + + + + + + + + 192.0.2.1 - - 2001:DB8:4:2: - 2001:DB8:5:: - 2001:DB8:6::2 + + 2001:DB8:4:2: + 2001:DB8:5:: + + + + + + + + diff --git a/utils/he_ipv6/radvd.py b/utils/he_ipv6/radvd.py new file mode 100644 index 0000000..052d204 --- /dev/null +++ b/utils/he_ipv6/radvd.py @@ -0,0 +1,115 @@ +import logging +import os +import subprocess +import warnings + + +logger = logging.getLogger() + + +class RADVDSvc(object): + svc_name = 'radvd' + + def __init__(self): + self.name = self.svc_name + self.is_systemd = False + self._get_manager() + + def _get_manager(self): + chkpaths = ('/run/systemd/system', + '/dev/.run/systemd', + '/dev/.systemd') + for _ in chkpaths: + if os.path.exists(_): + self.is_systemd = True + break + return(None) + + def restart(self): + if self.is_systemd: + cmd = ['systemd', 'restart', self.name] + else: + # Systemd haters, if you don't understand the benefits of unified service management across all linux + # distros, you've obviously never done wide-platform management or scripting. + # Let this else block be a learning experience for you. + cmd = None + has_pkill = False + for p in os.environ.get('PATH', '/usr/bin').split(':'): + fpath = os.path.abspath(os.path.expanduser(p)) + bins = os.listdir(fpath) + if 'pkill' in bins: + has_pkill = True + if 'service' in bins: # CentOS/RHEL pre-7.x + cmd = ['service', self.name, 'restart'] + break + elif 'initctl' in bins: # older Ubuntu and other Upstart distros + cmd = ['initctl', 'restart', self.name] + break + elif 'rc-service' in bins: # OpenRC + cmd = ['rc-service', self.name, 'restart'] + break + # That wasn't even all of them. + if not cmd and has_pkill: # last-ditch effort. + cmd = ['pkill', '-HUP', self.name] + if not cmd: + logger.error('Could not find which service manager this system is using.') + raise RuntimeError('Could not determine service manager') + cmd_exec = subprocess.run(cmd, stdin = subprocess.PIPE, stdout = subprocess.PIPE) + if cmd_exec.returncode != 0: + logger.warning('Could not successfully restart {0}; returned status {1}'.format(self.name, + cmd_exec.returncode)) + for i in ('stdout', 'stderr'): + s = getattr(cmd_exec, i).decode('utf-8') + if s.strip() != '': + logger.warning('{0}: {1}'.format(i.upper(), s)) + warnings.warn('Service did not restart successfully') + return(None) + + +class RADVDConf(object): + path = '/etc/radvd.conf' + tpl = ('interface {iface} {{\n' + '\tAdvSendAdvert on;\n' + # '\tAdvLinkMTU 1280;\n' # Is it 1480 or 1280? Arch wiki says 1480, but everything else (older) says 1280. + '\tAdvLinkMTU 1480;\n' + '\tMinRtrAdvInterval 60;\n' + '\tMaxRtrAdvInterval 600;\n' + '\tAdvDefaultLifetime 9000;\n' + '{prefix}' + 'route ::/0 {{\n' + '\t\tAdvRouteLifetime infinity;' + '}};\n' + '{rdnss}' + '}};\n\n') + tpl_prefix = ('\tprefix {subnet} {{\n' + '\t\tAdvOnLink on;' + '\t\tAdvAutonomous on;' + '\t\tAdvRouterAddr off;\n' + '}};\n') + tpl_rdnss = ('\tRDNSS {client_ip} {{\n' + '\t\tAdvRDNSSOpen on;\n' + '\t\tAdvRDNSSPreference 2;\n' + '}};\n') + + def __init__(self, cfg = None): + if not cfg: + self.cfg = self.path + else: + self.cfg = os.path.abspath(os.path.expanduser(cfg)) + self.cfgStr = None + + def generate(self, assign_objs): + self.cfgStr = '' + for assign_obj in assign_objs: + prefix = self.tpl_prefix.format(subnet = str(assign_obj.net)) + if assign_obj.dns: + dns = self.tpl_rdnss.format(client_ip = assign_obj.iface_ip.str) + else: + dns = '' + self.cfgStr += self.tpl.format(prefix = prefix, rdnss = dns) + + +class RADVD(object): + def __init__(self): + self.svc = RADVDSvc() + self.conf = RADVDConf(cfg = '/etc/radvd.conf') diff --git a/utils/he_ipv6/ref b/utils/he_ipv6/ref index 4919504..5862a51 100644 --- a/utils/he_ipv6/ref +++ b/utils/he_ipv6/ref @@ -1,7 +1,10 @@ # https://wiki.archlinux.org/index.php/IPv6_tunnel_broker_setup # https://forums.he.net/index.php?topic=3153.0 # https://gist.github.com/pklaus/960672 -# https://shorewall.org/6to4.htm#idm143 # https://genneko.github.io/playing-with-bsd/networking/freebsd-tunnelv6-he # https://journeymangeek.com/?p=228 # https://superuser.com/questions/1441598/using-a-hurricane-electric-tunnel-to-provide-ips-to-a-network-with-dnsmasq/1441604#1441604 +# https://wiki.gentoo.org/wiki/IPv6_router_guide +# https://shorewall.org/6to4.htm#idm143 +# https://shorewall.org/6to4.htm#SixInFour +# https://wiki.ubuntu.com/IPv6#Configure_your_Ubuntu_box_as_a_IPv6_router