i think i got it

This commit is contained in:
brent s. 2020-05-13 21:10:09 -04:00
parent fc8eda8198
commit cb1a877ddc
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
3 changed files with 240 additions and 192 deletions

View File

@ -16,9 +16,9 @@ def xml2bool(xml_str):
if xml_str is None: if xml_str is None:
return(None) return(None)
xml_str = xml_str.lower()[0] xml_str = xml_str.lower()[0]
if xml_str == ('t', '1'): if xml_str in ('t', '1'):
return(True) return(True)
elif xml_str == ('f', '0'): elif xml_str in ('f', '0'):
return(False) return(False)
else: else:
raise ValueError('Not a boolean value') raise ValueError('Not a boolean value')
@ -67,140 +67,6 @@ class IP6(IP):
self.alloc_block = netaddr.SubnetSplitter(self.net_net) 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()
ipr = IPRoute()
self.iface_idx = ipr.link_lookup(ifname = self.iface)[0]
ipr.close()
return(None)

def _ip(self):
_ip_txt = self.xml.text.strip()
_prefix_txt = self.xml.attrib['prefix'].strip()
self.ip = IP6(_ip_txt, _prefix_txt)
self.prefix = self.ip.prefix
return(None)

def parse(self):
self._id()
self._iface()
self._ip()
return(None)


class Tunnel(object):
def __init__(self, tun_xml):
self.xml = tun_xml
self.id = None
self.client = None
self.server = None
self.creds = None # This should be handled externally and map to a Cred obj
self.creds_id = None
self.allocations = {}
self.assignments = []
self.parse()

def _allocations(self):
_allocs_xml = self.xml.find('allocations')
for _allocation_xml in _allocs_xml.findall('alloc'):
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):
_client_xml = self.xml.find('client')
_ip_txt = _client_xml.text.strip()
_prefix_txt = _client_xml.attrib['prefix'].strip()
self.client = IP6(_ip_txt, _prefix_txt)
return(None)

def _creds(self):
self.creds_id = self.xml.attrib['creds'].strip()
return(None)

def _id(self):
self.id = int(self.xml.attrib['id'].strip())
return(None)

def _server(self):
_server_xml = self.xml.find('server')
_ip_text = _server_xml.text.strip()
self.server = IP4(_ip_text, 32)
return(None)

def parse(self):
self._id()
self._creds()
self._client()
self._server()
self._allocations()
self._assignments()
return(None)


class Credential(object): class Credential(object):
def __init__(self, cred_xml): def __init__(self, cred_xml):
self.xml = cred_xml self.xml = cred_xml
@ -243,6 +109,148 @@ class Credential(object):
return(None) return(None)




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.iface_idx = None
self.iface_addrs = []
self.iface_blocks = []
self.alloc = None # This must be set externally to a mapped Allocation instance
self.alloc_name = None
self.prefix = None
self.alloc_block = None
self.parse()

def _alloc(self):
self.alloc_name = self.xml.attrib['alloc'].strip()
return(None)

def _iface(self):
_iface_txt = self.xml.attrib['iface'].strip()
self.iface = _iface_txt.strip()
ipr = IPRoute()
self.iface_idx = ipr.link_lookup(ifname = self.iface)[0]
ipr.close()
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.alloc_block = self.alloc.ip.alloc_block
self.iface_blocks = self.alloc_block.extract_subnet(self.prefix, count = 1)
for i in self.iface_blocks:
self.iface_addrs.append(IP6(str(next(i.iter_hosts())), 128))
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 _ip(self):
_ip_txt = self.xml.text.strip()
_prefix_txt = self.xml.attrib['prefix'].strip()
self.ip = IP6(_ip_txt, _prefix_txt)
self.prefix = self.ip.prefix
return(None)

def parse(self):
self._id()
self._ip()
return(None)


class Tunnel(object):
def __init__(self, tun_xml):
self.xml = tun_xml
self.id = None
self.client = None
self.server = None
self.creds = None # This should be handled externally and map to a Cred obj
self.creds_id = None
self.radvd = None
self.enable_radvd = None
self.radvd_dns = None
self.allocations = {} # This is a dict of {}[alloc.id] = Allocation obj
self.assignments = [] # This is a list of Assignment objs
self.parse()

def _allocations(self):
_allocs_xml = self.xml.find('allocations')
for _allocation_xml in _allocs_xml.findall('alloc'):
alloc = Allocation(_allocation_xml)
self.allocations[alloc.id] = alloc
return(None)

def _assignments(self):
_assigns_xml = self.xml.find('assignments')
self.enable_radvd = xml2bool(_assigns_xml.attrib.get('radvd', 'false'))
self.radvd_dns = 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.alloc = self.allocations[assign.alloc_name]
assign.parse_alloc()
self.assignments.append(assign)
return(None)

def _client(self):
_client_xml = self.xml.find('client')
_ip_txt = _client_xml.text.strip()
_prefix_txt = _client_xml.attrib['prefix'].strip()
self.client = IP6(_ip_txt, _prefix_txt)
return(None)

def _creds(self):
self.creds_id = self.xml.attrib['creds'].strip()
return(None)

def _id(self):
self.id = int(self.xml.attrib['id'].strip())
return(None)

def _radvd(self):
self.radvd = radvd.RADVD()
self.radvd.conf.generate(self.assignments)
return(None)

def _server(self):
_server_xml = self.xml.find('server')
_ip_text = _server_xml.text.strip()
self.server = IP4(_ip_text, 32)
return(None)

def parse(self):
self._id()
self._creds()
self._client()
self._server()
self._allocations()
self._assignments()
self._radvd()
return(None)


class Config(object): class Config(object):
default_xsd = 'http://schema.xml.r00t2.io/projects/he_ipv6.xsd' default_xsd = 'http://schema.xml.r00t2.io/projects/he_ipv6.xsd'



View File

@ -14,6 +14,8 @@ class RADVDSvc(object):
self.name = self.svc_name self.name = self.svc_name
self.is_systemd = False self.is_systemd = False
self._get_manager() self._get_manager()
self.cmd_tpl = None
self.has_pkill = False


def _get_manager(self): def _get_manager(self):
chkpaths = ('/run/systemd/system', chkpaths = ('/run/systemd/system',
@ -23,24 +25,21 @@ class RADVDSvc(object):
if os.path.exists(_): if os.path.exists(_):
self.is_systemd = True self.is_systemd = True
break break
return(None)

def restart(self):
if self.is_systemd: if self.is_systemd:
cmd = ['systemd', 'restart', self.name] self.cmd_tpl = 'systemctl {op} {name}'
else: else:
# Systemd haters, if you don't understand the benefits of unified service management across all linux # 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. # distros, you've obviously never done wide-platform management or scripting.
# Let this else block be a learning experience for you. # Let this else block be a learning experience for you.
cmd = None self.cmd_tpl = None
has_pkill = False self.has_pkill = False
for p in os.environ.get('PATH', '/usr/bin').split(':'): for p in os.environ.get('PATH', '/usr/bin').split(':'):
fpath = os.path.abspath(os.path.expanduser(p)) fpath = os.path.abspath(os.path.expanduser(p))
bins = os.listdir(fpath) bins = os.listdir(fpath)
if 'pkill' in bins: if 'pkill' in bins:
has_pkill = True self.has_pkill = True
if 'service' in bins: # CentOS/RHEL pre-7.x if 'service' in bins: # CentOS/RHEL pre-7.x
cmd = ['service', self.name, 'restart'] self.cmd_tpl = 'service {name} {op}'
break break
elif 'initctl' in bins: # older Ubuntu and other Upstart distros elif 'initctl' in bins: # older Ubuntu and other Upstart distros
cmd = ['initctl', 'restart', self.name] cmd = ['initctl', 'restart', self.name]
@ -49,20 +48,43 @@ class RADVDSvc(object):
cmd = ['rc-service', self.name, 'restart'] cmd = ['rc-service', self.name, 'restart']
break break
# That wasn't even all of them. # That wasn't even all of them.
if not cmd and has_pkill: # last-ditch effort. # This doesn't make sense since we template the command now.
cmd = ['pkill', '-HUP', self.name] # if not self.cmd_tpl and self.has_pkill: # last-ditch effort.
if not cmd: # cmd = ['pkill', '-HUP', self.name]
if not self.cmd_tpl:
logger.error('Could not find which service manager this system is using.') logger.error('Could not find which service manager this system is using.')
raise RuntimeError('Could not determine service manager') raise RuntimeError('Could not determine service manager')
cmd_exec = subprocess.run(cmd, stdin = subprocess.PIPE, stdout = subprocess.PIPE) return(None)
if cmd_exec.returncode != 0:
logger.warning('Could not successfully restart {0}; returned status {1}'.format(self.name, def restart(self):
cmd_exec.returncode)) self.stop()
for i in ('stdout', 'stderr'): self.start()
s = getattr(cmd_exec, i).decode('utf-8') return(None)
if s.strip() != '':
logger.warning('{0}: {1}'.format(i.upper(), s)) def start(self):
warnings.warn('Service did not restart successfully') 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) return(None)




@ -76,20 +98,20 @@ class RADVDConf(object):
'\tMaxRtrAdvInterval 600;\n' '\tMaxRtrAdvInterval 600;\n'
'\tAdvDefaultLifetime 9000;\n' '\tAdvDefaultLifetime 9000;\n'
'{prefix}' '{prefix}'
'route ::/0 {{\n' '\troute ::/0 {{\n'
'\t\tAdvRouteLifetime infinity;' '\t\tAdvRouteLifetime infinity;\n'
'}};\n' '\t}};\n'
'{rdnss}' '{rdnss}'
'}};\n\n') '}};\n\n')
tpl_prefix = ('\tprefix {subnet} {{\n' tpl_prefix = ('\tprefix {subnet} {{\n'
'\t\tAdvOnLink on;' '\t\tAdvOnLink on;\n'
'\t\tAdvAutonomous on;' '\t\tAdvAutonomous on;\n'
'\t\tAdvRouterAddr off;\n' '\t\tAdvRouterAddr off;\n'
'}};\n') '\t}};\n')
tpl_rdnss = ('\tRDNSS {client_ip} {{\n' tpl_rdnss = ('\tRDNSS {client_ip} {{\n'
'\t\tAdvRDNSSOpen on;\n' '\t\tAdvRDNSSOpen on;\n'
'\t\tAdvRDNSSPreference 2;\n' '\t\tAdvRDNSSPreference 2;\n'
'}};\n') '\t}};\n')


def __init__(self, cfg = None): def __init__(self, cfg = None):
if not cfg: if not cfg:
@ -101,12 +123,21 @@ class RADVDConf(object):
def generate(self, assign_objs): def generate(self, assign_objs):
self.cfgStr = '' self.cfgStr = ''
for assign_obj in assign_objs: for assign_obj in assign_objs:
prefix = self.tpl_prefix.format(subnet = str(assign_obj.net)) if not assign_obj.do_radvd:
if assign_obj.dns: continue
dns = self.tpl_rdnss.format(client_ip = assign_obj.iface_ip.str) for b in assign_obj.iface_blocks:
else: prefix = self.tpl_prefix.format(subnet = str(b))
dns = '' if assign_obj.radvd_dns:
self.cfgStr += self.tpl.format(prefix = prefix, rdnss = 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): class RADVD(object):

View File

@ -14,7 +14,6 @@ class TunnelBroker(object):
url_ip = 'https://ipv4.clientinfo.square-r00t.net/' url_ip = 'https://ipv4.clientinfo.square-r00t.net/'
params_ip = {'raw': '1'} params_ip = {'raw': '1'}
url_api = 'https://ipv4.tunnelbroker.net/nic/update' url_api = 'https://ipv4.tunnelbroker.net/nic/update'
def_rt_ip = '::192.88.99.1'


def __init__(self, conf_xml, tun_id = None, wan_ip = True, update = True, *args, **kwargs): def __init__(self, conf_xml, tun_id = None, wan_ip = True, update = True, *args, **kwargs):
self.conf_file = os.path.abspath(os.path.expanduser(conf_xml)) self.conf_file = os.path.abspath(os.path.expanduser(conf_xml))
@ -109,7 +108,7 @@ class TunnelBroker(object):
try: try:
ipr.route('add', ipr.route('add',
dst = 'default', dst = 'default',
# gateway = self.def_rt_ip, gateway = self.tun.server.str,
oif = self.iface_idx, oif = self.iface_idx,
family = socket.AF_INET6) family = socket.AF_INET6)
logger.debug('Added default route for link {0}.'.format(self.iface_name)) logger.debug('Added default route for link {0}.'.format(self.iface_name))
@ -117,28 +116,37 @@ class TunnelBroker(object):
logger.error(('Could not add default IPv6 route on link {0}: {1}').format(self.iface_name, e)) logger.error(('Could not add default IPv6 route on link {0}: {1}').format(self.iface_name, e))
ipr.close() ipr.close()
raise e raise e
for alloc in self.tun.allocations: for assignment in self.tun.assignments:
try: for a in assignment.iface_addrs:
ipr.addr('add', # The interface-specific ":1" addrs.
index = alloc.iface_idx, try:
address = alloc.ip.str, ipr.addr('add',
mask = alloc.ip.prefix, index = assignment.iface_idx,
family = socket.AF_INET6) address = a.ip.str,
except Exception as e: mask = a.ip.prefix,
logger.error(('Could not add address {0} on link {1}: ' family = socket.AF_INET6)
'{2}').format(str(alloc.ip.str), alloc.iface_idx, e)) except Exception as e:
ipr.close() logger.error(('Could not add address {0} on link {1}: '
raise e '{2}').format(str(a.ip.str), assignment.iface_idx, e))
# Is this necessary? ipr.close()
# try: raise e
# ipr.route('add', # The SLAAC prefixes.
# dst = '::/96', for b in assignment.iface_blocks:
# gateway = '::', try:
# oif = self.iface_idx, ipr.addr('add',
# family = socket.AF_INET6) index = assignment.iface_idx,
# except Exception as e: address = str(b.ip),
# logger.error(('Could not add ::/96 on link {0}: {1}'.format(self.iface_name, e))) mask = b.prefixlen,
family = socket.AF_INET6)
except Exception as e:
logger.error(('Could not add address block {0} on link {1}: '
'{2}').format(str(b), assignment.iface_idx, e))
ipr.close()
raise e
ipr.close() ipr.close()
if self.tun.enable_radvd:
self.tun.radvd.conf.write()
self.tun.radvd.svc.restart()
return(None) return(None)


def stop(self): def stop(self):
@ -172,6 +180,7 @@ class TunnelBroker(object):
ipr.close() ipr.close()
raise e raise e
ipr.close() ipr.close()
self.tun.radvd.svc.stop()
return(None) return(None)


def update(self, oneshot = False): def update(self, oneshot = False):