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