holy shit, more restructuring

This commit is contained in:
brent s 2020-05-15 18:01:03 -04:00
parent 429cf7b155
commit 363cdc712e
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
7 changed files with 226 additions and 162 deletions

View File

@ -1,5 +1,5 @@
from . import args
from . import radvd
from . import ra
from . import tunnel
from . import config
from . import logger

View File

@ -41,12 +41,10 @@
<!--
Where to assign your allocations. The default allocation prefix is a /64 (prefix="64"), since that's what SLAAC
recommends.
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.
It has one optional attribute, "raProvider", which can be "dnsmasq" or "radvd". Further system configuration may
be required. If not specified, the default is to not send router advertisements.
-->
<assignments radvd="true" radvdDns="true">
<assignments raProvider="dnsmasq">
<!--
Each assignment has the following required attributes:
* "prefix" - the size of the subnet to assign to an interface, "64" (/64) by default since that's what SLAAC
@ -60,9 +58,20 @@
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 prefix.
-->
<assign prefix="64" alloc="64" iface="eth0"/>
<assign prefix="64" alloc="64" iface="eth0">
<!--
Each assignment can have an "ra" child. The default is to not implement RA for this interface if not
present.
-->
<ra>
<dns>true</dns>
<dhcpv6>true</dhcpv6>
</ra>
</assign>
<assign prefix="64" alloc="48" iface="eth0"/>
<assign prefix="64" alloc="48" iface="eth1"/>
<assign prefix="64" alloc="48" iface="eth1">

</assign>
<assign prefix="64" alloc="48" iface="eth2"/>
</assignments>
</tunnel>

164
utils/he_ipv6/ra.py Normal file
View File

@ -0,0 +1,164 @@
import logging
import os
import subprocess
import warnings
##
import jinja2


logger = logging.getLogger()
def_tpl_dir = os.path.join(os.path.dirname(os.path.abspath(os.path.expanduser(__file__))), 'tpl')


class RA(object):
def __init__(self, conf = None, tpl_name = None, tpl_dir = None, *args, **kwargs):
self.conf = RAConf(conf = conf, tpl_name = tpl_name, tpl_dir = tpl_dir, *args, **kwargs)
self.svc = RASvc()


class RAConf(object):
def_tpl_name = None
def_conf = None
cfgstr = None

def __init__(self, conf = None, tpl_name = None, tpl_dir = None, *args, **kwargs):
for k in ('name', 'dir'):
n = 'tpl_{0}'.format(k)
d = 'def_tpl_{0}'.format(k)
v = locals()[k]
if not v:
setattr(self, n, getattr(self, d))
else:
setattr(self, n, v)
if not conf:
self.conf = self.def_conf
else:
self.conf = os.path.abspath(os.path.expanduser(conf))

def ext_init(self):
self.tpl_dir = os.path.abspath(os.path.expanduser(self.tpl_dir))
self.loader = jinja2.FileSystemLoader(self.tpl_dir)
self.tpl_env = jinja2.Environment(loader = self.loader)
self.tpl = self.tpl_env.get_template(self.tpl_name)
return(None)

def generate(self, assignments):
ns = {}
for a in assignments:
if len(a.iface_addrs) > 3:
ns_addrs = a.iface_addrs[:3]
else:
ns_addrs = a.iface_addrs
ns[a.iface] = ns_addrs
self.cfgstr = self.tpl.render(assignments = assignments, nameservers = ns)

def write(self):
if not self.cfgstr:
raise RuntimeError('Must run .generate() first')
os.makedirs(os.path.dirname(self.conf), exist_ok = True, mode = 0o0700)
with open(self.conf, 'w') as fh:
fh.write(self.cfgstr)


class RASvc(object):
name = None
is_systemd = False
cmd_tpl = None
has_pkill = False
start_cmd = None
stop_cmd = None
restart_cmd = None

def __init__(self):
self._get_manager()

def _exec(self, cmd):
cmd_exec = subprocess.run(cmd, stdin = subprocess.PIPE, stdout = subprocess.PIPE)
if cmd_exec.returncode != 0:
logger.warning('Could not execute {0}; returned status {1}'.format(' '.join(cmd),
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))
return(None)

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
if self.is_systemd:
self.cmd_tpl = 'systemctl {op} {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.
self.cmd_tpl = None
self.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:
self.has_pkill = True
if 'service' in bins: # CentOS/RHEL pre-7.x
self.cmd_tpl = 'service {name} {op}'
break
elif 'initctl' in bins: # older Ubuntu and other Upstart distros
self.cmd_tpl = 'initctl {op} {name}'
break
elif 'rc-service' in bins: # OpenRC
self.cmd_tpl = 'rc-service {name} {op}'
break
# That wasn't even all of them.
if not self.cmd_tpl and not self.has_pkill:
logger.error('Could not find which service manager this system is using.')
raise RuntimeError('Could not determine service manager')
elif self.has_pkill: # Last-ditch effort.
self.start_cmd = [self.name]
self.stop_cmd = ['pkill', self.name]
self.restart_cmd = ['pkill', '-HUP', self.name]
else:
for k in ('start', 'stop', 'restart'):
setattr(self,
'{0}_cmd'.format(k),
self.cmd_tpl.format(name = self.name, op = k).split())
return(None)

def restart(self):
cmd = self.restart_cmd
self._exec(cmd)
return(None)

def start(self):
cmd = self.start_cmd
self._exec(cmd)
return(None)

def stop(self):
cmd = self.stop_cmd
self._exec(cmd)
return(None)


class RADVD(RA):
name = 'radvd'
def_conf = '/etc/radvd.conf'
def_tpl_name = 'radvd.conf.j2'

def __init__(self, conf = None, tpl_name = None, tpl_dir = None):
super().__init__(conf = conf, tpl_name = tpl_name, tpl_dir = tpl_dir)
self.conf.ext_init()


class DNSMasq(RA):
name = 'dnsmasq'
def_conf = '/etc/dnsmasq.d/ra.conf'
def_tpl_name = 'dnsmasq.include.j2'

def __init__(self, conf = None, tpl_name = None, tpl_dir = None):
super().__init__(conf = conf, tpl_name = tpl_name, tpl_dir = tpl_dir)
self.conf.ext_init()

View File

@ -1,143 +0,0 @@
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.cmd_tpl = None
self.has_pkill = 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
if self.is_systemd:
self.cmd_tpl = 'systemctl {op} {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.
self.cmd_tpl = None
self.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:
self.has_pkill = True
if 'service' in bins: # CentOS/RHEL pre-7.x
self.cmd_tpl = 'service {name} {op}'
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.
# This doesn't make sense since we template the command now.
# if not self.cmd_tpl and self.has_pkill: # last-ditch effort.
# cmd = ['pkill', '-HUP', self.name]
if not self.cmd_tpl:
logger.error('Could not find which service manager this system is using.')
raise RuntimeError('Could not determine service manager')
return(None)

def restart(self):
self.stop()
self.start()
return(None)

def start(self):
cmd = self.cmd_tpl.format(op = 'start', name = self.name).split()
cmd_exec = subprocess.run(cmd, stdin = subprocess.PIPE, stdout = subprocess.PIPE)
if cmd_exec.returncode != 0:
logger.warning('Could not successfully start {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 start successfully')
return(None)

def stop(self):
cmd = self.cmd_tpl.format(op = 'stop', name = self.name).split()
cmd_exec = subprocess.run(cmd, stdin = subprocess.PIPE, stdout = subprocess.PIPE)
if cmd_exec.returncode != 0:
logger.warning('Could not successfully stop {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 stop 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}'
'\troute ::/0 {{\n'
'\t\tAdvRouteLifetime infinity;\n'
'\t}};\n'
'{rdnss}'
'}};\n\n')
tpl_prefix = ('\tprefix {subnet} {{\n'
'\t\tAdvOnLink on;\n'
'\t\tAdvAutonomous on;\n'
'\t\tAdvRouterAddr off;\n'
'\t}};\n')
tpl_rdnss = ('\tRDNSS {client_ip} {{}};\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:
if not assign_obj.do_radvd:
continue
for b in assign_obj.iface_blocks:
prefix = self.tpl_prefix.format(subnet = str(b))
if assign_obj.radvd_dns:
dns = self.tpl_rdnss.format(client_ip = str(next(b.iter_hosts())))
else:
dns = ''
self.cfgStr += self.tpl.format(prefix = prefix, rdnss = dns, iface = assign_obj.iface)
return(None)

def write(self):
with open(self.cfg, 'w') as fh:
fh.write(self.cfgStr)
return(None)


class RADVD(object):
def __init__(self):
self.svc = RADVDSvc()
self.conf = RADVDConf(cfg = '/etc/radvd.conf')

View File

@ -0,0 +1,2 @@
# This file should be *included* in your dnsmasq configuration.

View File

@ -0,0 +1,30 @@
# Generated by he_ipv6
{% for assign in assignments %}
{% for assignment in assign.iface_blocks %}
interface {{ assignment.iface }} {
AdvSendAdvert on;
# Is it 1480 or 1280? Arch wiki says 1480, but everything else (older) says 1280.
# AdvLinkMTU 1280;
AdvLinkMTU 1480;
MinRtrAdvInterval 60;
MaxRtrAdvInterval 600;
AdvDefaultLifetime 9000;

{%- for block in assignment.iface_blocks %}
prefix {{ block|string }} {
AdvOnLink on;
{%- if block.prefixlen == 64 %}
AdvAutonomous on;
{%- endif %}
AdvRouterAddr off;
};
{%- endfor %}

{%- if ra.dns is true %}
RDNSS {{ nameservers[assignment.iface]|join(' ') }} {
};
{%- endif %}
};

{%- endfor %}
{%- endfor %}

View File

@ -4,7 +4,7 @@ import netaddr
from pyroute2 import IPRoute
##
from . import utils
from . import radvd
from . import ra


class IP(object):
@ -51,10 +51,10 @@ class IP6(IP):


class Assignment(object):
def __init__(self, assign_xml, radvd = False, dns = False):
def __init__(self, assign_xml, ra = False, dns = False, ra_provider = 'dnsmasq'):
self.xml = assign_xml
self.do_radvd = radvd
self.radvd_dns = dns
self.ra = ra
self.dns = dns
self.iface = None
self.iface_idx = None
self.iface_addrs = []
@ -114,9 +114,10 @@ class Tunnel(object):
self.client = None
self.server = None
self.endpoint = None
self.radvd = None
self.enable_radvd = None
self.radvd_dns = None
self.ra = False
self.ra_provider = None
self.ra_dns = False
self.ra_dhcp = False
self.allocations = {} # This is a dict of {}[alloc.id] = Allocation obj
self.assignments = [] # This is a list of Assignment objs
self.parse()
@ -127,10 +128,11 @@ class Tunnel(object):

def _assignments(self):
_assigns_xml = self.xml.find('assignments')
self.enable_radvd = utils.xml2bool(_assigns_xml.attrib.get('radvd', 'false'))
self.radvd_dns = utils.xml2bool(_assigns_xml.attrib.get('radvdDns', 'false'))

self.enable_ra = utils.xml2bool(_assigns_xml.attrib.get('radvd', 'false'))
self.ra_dns = utils.xml2bool(_assigns_xml.attrib.get('radvdDns', 'false'))
for _assign_xml in _assigns_xml.findall('assign'):
assign = Assignment(_assign_xml, radvd = self.enable_radvd, dns = self.radvd_dns)
assign = Assignment(_assign_xml, ra = self.enable_ra, dns = self.ra_dns)
assign.alloc = self.allocations[assign.alloc_id]
assign.parse_alloc()
self.assignments.append(assign)
@ -153,7 +155,7 @@ class Tunnel(object):
return(None)

def _radvd(self):
self.radvd = radvd.RADVD()

self.radvd.conf.generate(self.assignments)
return(None)