some updates to how i handle address allocation for he_ipv6

This commit is contained in:
brent s. 2020-05-13 13:31:16 -04:00
parent 71df39ca11
commit fc8eda8198
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
5 changed files with 253 additions and 27 deletions

View File

@ -1,4 +1,5 @@
from . import args
from . import radvd
from . import config
from . import logger
from . import tunnelbroker

View File

@ -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)



View File

@ -45,41 +45,76 @@
-->
<server>192.0.2.1</server>
<!--
Allocations that are handed to your tunnel.
-->
<!--
Section: Routed IPv6 Prefixes
-->
<allocs>
<!--
Each alloc has (in addition to a "prefix" attribute) an "iface" attribute. This is the network interface on
this machine that the allocation should be added to.
Value Name: Routed /64
-->
<alloc prefix="64" iface="eth0">2001:DB8:1:2::</alloc>
<!--
You may not have a /48 as it's opt-in.
Value Name: Routed /48
-->
<alloc prefix="48" iface="eth0">2001:DB8:2::</alloc>
</allocs>
<!--
The "client" element is the local SIT endpoint.
Section: IPv6 Tunnel Endpoints
Value Name: Client IPv6 Address
-->
<client prefix="64">2001:DB8:3::2</client>
<!--
Allocations that are handed to your tunnel.
Section: Routed IPv6 Prefixes
-->
<allocations>
<!--
Each alloc has the following attributes:
* "prefix" - the prefix size specified by your tunnelbroker.
* "id" - an identifier for each allocation to be used in assignments/assign items.
Value Name: Routed /64
-->
<alloc prefix="64" id="lan">2001:DB8:1:2::</alloc>
<!--
You may not have a /48 as it's opt-in. It's highly recommended, though, so you can provide global IPv6 addresses
to the rest of your LAN(s).
Value Name: Routed /48
-->
<alloc prefix="48" id="multilan">2001:DB8:2::</alloc>
</allocations>
<!--
Where to assign the allocations. The default allocation prefix is a /64 (prefix="64"), since that's what SLAAC
recommends.
Note that if you use your /64 allocation, and don't specify a longer prefix to it, you can only have one
assignment.
It has two optional attributes:
* "radvd" - a boolean; if true, /etc/radvd.conf will be automatically.
* "radvdDns" - a boolean, only used if radvd is true; if true, will specify the server's IP as an RDSS.
generated and restarted.
-->
<assignments radvd="true" radvdDns="true">
<!--
Each assignment has the following required attributes:
* "prefix" - the size of the subnet, "64" (/64) by default since that's what SLAAC recommends. Note that if
you use your /64 allocation and don't specify a longer prefix, you can only have one assignment
for that allocation.
* "alloc" - this should match an "id" attribute of an allocations/alloc item.
* "iface" - which network interface on the machine that the allocation should be added to.
Sections of the alloc referenced in the "alloc" attribute will then be carved out. Make sure you don't exceed
your allocation size! (A /48 has 65536 /64s in it.)
The interface will be assigned :1 (the first host in the subnet) as well, so it is recommended that you do not
assign a /128.
-->
<assign prefix="64" alloc="lan" iface="eth0"/>
<assign prefix="64" alloc="multilan" iface="eth0"/>
<assign prefix="64" alloc="multilan" iface="eth1"/>
<assign prefix="64" alloc="multilan" iface="eth2"/>
</assignments>
</tunnel>
<!--
And you can, of course, specify multiple tunnels.
-->
<tunnel id="54321" creds="ipv6user">
<server>192.0.2.1</server>
<allocs>
<alloc prefix="64" iface="eth1">2001:DB8:4:2:</alloc>
<alloc prefix="48" iface="eth1">2001:DB8:5::</alloc>
</allocs>
<client prefix="64">2001:DB8:6::2</client>
<allocations>
<alloc prefix="64" id="lan">2001:DB8:4:2:</alloc>
<alloc prefix="48" id="biglan">2001:DB8:5::</alloc>
</allocations>
<assignments>
<!-- Uses the default prefix of /64 each. -->
<assign alloc="lan" iface="eth0"/>
<assign alloc="biglan" iface="eth1"/>
<assign alloc="biglan" iface="eth1"/>
<assign alloc="biglan" iface="eth1"/>
</assignments>
</tunnel>
</tunnels>
</heIPv6>

115
utils/he_ipv6/radvd.py Normal file
View File

@ -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')

View File

@ -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