Compare commits
66 Commits
pre_assign
...
master
Author | SHA1 | Date |
---|---|---|
brent s. | d13ecf8d20 | |
brent s. | cd422fc7d6 | |
brent s. | e589e00100 | |
brent s. | 01acb18f5f | |
brent s. | 50fd503ce6 | |
brent s. | 753d5007d8 | |
brent s. | 4942e7d662 | |
brent s. | befdc9e99b | |
brent s. | 361303e87a | |
brent s. | 39c8cdc171 | |
brent s. | 51184c299d | |
brent s. | 1b82e67c93 | |
brent s. | 8ad2fbff22 | |
brent s. | 3d238dda22 | |
brent s. | d1a9972786 | |
brent s. | 3ec21c4fa9 | |
brent s. | a992fac902 | |
brent s. | bdcbc09dc7 | |
brent s. | 8cb29762a7 | |
brent s. | ca594b09dd | |
brent s. | afd839a195 | |
brent s. | 92f857d967 | |
brent s. | bc12a6ad84 | |
brent s. | 0868b18de8 | |
brent s. | 7b48e6813c | |
brent s. | 87dfe6a543 | |
brent s. | 1655dd1e89 | |
brent s. | 80765e58ed | |
brent s. | 4df9287abd | |
brent s. | 5f2883a698 | |
brent s. | a0d5071a8d | |
brent s | 363cdc712e | |
brent s. | 429cf7b155 | |
brent s. | 85cdc0c52a | |
brent s. | b5b47e92fd | |
brent s. | 32c78201e8 | |
brent s. | 51dadf421e | |
brent s. | c877868c33 | |
brent s. | 881a8c9317 | |
brent s. | 1d27ee0556 | |
brent s. | 754fa3eb25 | |
brent s. | 1b090c76ce | |
brent s. | 6248422962 | |
brent s. | 375a8c8427 | |
brent s. | 92fdee435a | |
brent s. | c45754b1a3 | |
brent s. | a8475d9001 | |
brent s. | 58e495bf41 | |
brent s. | 9246afa9f7 | |
brent s. | f37d26572a | |
brent s. | 05ef5b078c | |
brent s. | 58cbbb06cd | |
brent s. | cf51e96852 | |
brent s. | b00351f762 | |
brent s. | 66561c51d8 | |
brent s. | 742a0b55d5 | |
brent s | efb53be81b | |
brent s | 315af935ac | |
brent s. | 676aa8d5b6 | |
brent s. | 8a5d484883 | |
brent s. | c8c4957120 | |
brent s. | c23c803a20 | |
brent s. | fb89feb046 | |
brent s. | 9a8bae0ba8 | |
brent s. | cb1a877ddc | |
brent s. | fc8eda8198 |
|
@ -0,0 +1,3 @@
|
|||
DHCPv6:
|
||||
* NTP server in <ra>? (dnsmasq: option6:ntpserver,...)
|
||||
* bootfile-(url|param) in <ra>? (dnsmasq: option6:*)
|
|
@ -1,4 +1,6 @@
|
|||
from . import args
|
||||
from . import ra
|
||||
from . import tunnel
|
||||
from . import config
|
||||
from . import logger
|
||||
from . import tunnelbroker
|
||||
|
|
|
@ -12,7 +12,7 @@ def parseArgs():
|
|||
args.add_argument('-c', '--config',
|
||||
dest = 'conf_xml',
|
||||
default = '~/.config/he_tunnelbroker.xml',
|
||||
help = ('The path to the config. See example.tunnelbroker.xml'
|
||||
help = ('The path to the config. See example.tunnelbroker.xml '
|
||||
'Default: ~/.config/he_tunnelbroker.xml'))
|
||||
args.add_argument('-t', '--tunnel-id',
|
||||
dest = 'tun_id',
|
||||
|
|
|
@ -1,132 +1,71 @@
|
|||
import collections
|
||||
import copy
|
||||
import ipaddress
|
||||
import os
|
||||
import re
|
||||
##
|
||||
import netaddr
|
||||
import requests
|
||||
import requests.auth
|
||||
from lxml import etree
|
||||
from pyroute2 import IPRoute
|
||||
##
|
||||
from . import tunnel
|
||||
|
||||
|
||||
class IP(object):
|
||||
type = None
|
||||
version = None
|
||||
_ip = ipaddress.ip_address
|
||||
_net = ipaddress.ip_network
|
||||
_net_ip = netaddr.IPAddress
|
||||
_net_net = netaddr.IPNetwork
|
||||
|
||||
def __init__(self, ip, prefix, *args, **kwargs):
|
||||
self.str = ip
|
||||
self.prefix = int(prefix)
|
||||
self.net_ip = self._net_ip(self.str)
|
||||
self.net_net = self._net_net('{0}/{1}'.format(self.str, self.prefix))
|
||||
|
||||
def _ext_init(self):
|
||||
self.ip = self._ip(self.str)
|
||||
self.net = self._net('{0}/{1}'.format(self.str, self.prefix), strict = False)
|
||||
return(None)
|
||||
|
||||
|
||||
class IP4(IP):
|
||||
type = 'IPv4'
|
||||
version = 4
|
||||
_ip = ipaddress.IPv4Address
|
||||
_net = ipaddress.IPv4Network
|
||||
|
||||
def __init__(self, ip, prefix, *args, **kwargs):
|
||||
super().__init__(ip, prefix, *args, **kwargs)
|
||||
self._ext_init()
|
||||
|
||||
|
||||
class IP6(IP):
|
||||
type = 'IPv6'
|
||||
version = 6
|
||||
_ip = ipaddress.IPv6Address
|
||||
_net = ipaddress.IPv6Network
|
||||
|
||||
def __init__(self, ip, prefix, *args, **kwargs):
|
||||
super().__init__(ip, prefix, *args, **kwargs)
|
||||
self._ext_init()
|
||||
|
||||
|
||||
class Allocation(object):
|
||||
def __init__(self, alloc_xml):
|
||||
self.xml = alloc_xml
|
||||
self.prefix = None
|
||||
self.ip = None
|
||||
self.iface = None
|
||||
self.iface_idx = None
|
||||
self.parse()
|
||||
|
||||
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._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.parse()
|
||||
|
||||
def _allocations(self):
|
||||
_allocs_xml = self.xml.find('allocs')
|
||||
for _allocation_xml in _allocs_xml.findall('alloc'):
|
||||
self.allocations.append(Allocation(_allocation_xml))
|
||||
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()
|
||||
return(None)
|
||||
def create_default_cfg():
|
||||
# Create a stripped sample config.
|
||||
ws_re = re.compile(r'^\s*$')
|
||||
cur_dir = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
|
||||
xtbxml = os.path.join(cur_dir, 'example.tunnelbroker.xml')
|
||||
with open(xtbxml, 'rb') as fh:
|
||||
xml = etree.fromstring(fh.read())
|
||||
# Create a stripped sample config.
|
||||
# First we strip comments (and fix the ensuing whitespace).
|
||||
# etree has a .canonicalize(), but it chokes on a default namespace.
|
||||
# https://bugs.launchpad.net/lxml/+bug/1869455
|
||||
# So everything we do is kind of a hack.
|
||||
# for c in xml.xpath("//comment()"):
|
||||
# parent = c.getparent()
|
||||
# parent.remove(c)
|
||||
xmlstr = etree.tostring(xml, with_comments = False, method = 'c14n', pretty_print = True).decode('utf-8')
|
||||
newstr = []
|
||||
for line in xmlstr.splitlines():
|
||||
r = ws_re.search(line)
|
||||
if not r:
|
||||
newstr.append(line.strip())
|
||||
xml = etree.fromstring(''.join(newstr).encode('utf-8'))
|
||||
# Remove text and attr text.
|
||||
xpathq = "descendant-or-self::*[namespace-uri()!='']"
|
||||
for e in xml.xpath(xpathq):
|
||||
if e.tag == '{{{0}}}heIPv6'.format(xml.nsmap[None]):
|
||||
continue
|
||||
if e.text is not None and e.text.strip() != '':
|
||||
e.text = ''
|
||||
for k, v in e.attrib.items():
|
||||
if v is not None:
|
||||
e.attrib[k] = ''
|
||||
# Remove multiple children of same type to simplify.
|
||||
for e in xml.xpath(xpathq):
|
||||
if e.tag == '{{{0}}}heIPv6'.format(xml.nsmap[None]):
|
||||
continue
|
||||
parent = e.getparent()
|
||||
try:
|
||||
for idx, child in enumerate(parent.findall(e.tag)):
|
||||
if idx == 0:
|
||||
continue
|
||||
parent.remove(child)
|
||||
except AttributeError:
|
||||
pass
|
||||
# And add a comment pointing them to the fully commented config.
|
||||
xml.insert(0, etree.Comment(('\n Please reference the fully commented example.tunnelbroker.xml found either '
|
||||
'at:\n '
|
||||
' * {0}\n * https://git.square-r00t.net/RouterBox/tree/utils/he_ipv6/'
|
||||
'example.tunnelbroker.xml\n and then configure this according to those '
|
||||
'instructions.\n ').format(xtbxml)))
|
||||
return(etree.tostring(xml,
|
||||
pretty_print = True,
|
||||
with_comments = True,
|
||||
with_tail = True,
|
||||
encoding = 'UTF-8',
|
||||
xml_declaration = True))
|
||||
|
||||
|
||||
class Credential(object):
|
||||
|
@ -134,7 +73,7 @@ class Credential(object):
|
|||
self.xml = cred_xml
|
||||
self.id = None
|
||||
self.user = None
|
||||
self.key = None
|
||||
self.password = None
|
||||
self.parse()
|
||||
|
||||
def _id(self):
|
||||
|
@ -144,14 +83,14 @@ class Credential(object):
|
|||
self.id = _id.strip()
|
||||
return(None)
|
||||
|
||||
def _update_key(self):
|
||||
_key_xml = self.xml.find('updateKey')
|
||||
def _password(self):
|
||||
_key_xml = self.xml.find('password')
|
||||
if _key_xml is None:
|
||||
raise ValueError('Missing required updateKey element')
|
||||
raise ValueError('Missing required password element')
|
||||
_key_txt = _key_xml.text
|
||||
if not _key_txt:
|
||||
raise ValueError('updateKey element is empty')
|
||||
self.key = _key_txt.strip()
|
||||
raise ValueError('password element is empty')
|
||||
self.password = _key_txt.strip()
|
||||
return(None)
|
||||
|
||||
def _user(self):
|
||||
|
@ -167,43 +106,23 @@ class Credential(object):
|
|||
def parse(self):
|
||||
self._id()
|
||||
self._user()
|
||||
self._update_key()
|
||||
self._password()
|
||||
return(None)
|
||||
|
||||
|
||||
class Config(object):
|
||||
default_xsd = 'http://schema.xml.r00t2.io/projects/he_ipv6.xsd'
|
||||
class BaseConfig(object):
|
||||
default_xsd = None
|
||||
|
||||
def __init__(self, xml_path, *args, **kwargs):
|
||||
self.xml_path = os.path.abspath(os.path.expanduser(xml_path))
|
||||
if not os.path.isfile(self.xml_path):
|
||||
raise ValueError('xml_path does not exist')
|
||||
def __init__(self, xml_raw, *args, **kwargs):
|
||||
self.raw = xml_raw
|
||||
self.tree = None
|
||||
self.ns_tree = None
|
||||
self.xml = None
|
||||
self.ns_xml = None
|
||||
self.raw = None
|
||||
self.xsd = None
|
||||
self.defaults_parser = None
|
||||
self.obj = None
|
||||
self.tunnels = collections.OrderedDict()
|
||||
self.creds = {}
|
||||
self.parse()
|
||||
|
||||
def _creds(self):
|
||||
creds_xml = self.xml.find('creds')
|
||||
for cred_xml in creds_xml.findall('cred'):
|
||||
cred = Credential(cred_xml)
|
||||
self.creds[cred.id] = cred
|
||||
return(None)
|
||||
|
||||
def _tunnels(self):
|
||||
tunnels_xml = self.xml.find('tunnels')
|
||||
for tun_xml in tunnels_xml.findall('tunnel'):
|
||||
tun = Tunnel(tun_xml)
|
||||
tun.creds = self.creds.get(tun.creds_id)
|
||||
self.tunnels[tun.id] = tun
|
||||
return(None)
|
||||
self.parse_xml()
|
||||
|
||||
def get_xsd(self):
|
||||
raw_xsd = None
|
||||
|
@ -230,18 +149,14 @@ class Config(object):
|
|||
self.xsd = etree.XMLSchema(etree.XML(raw_xsd, base_url = base_url))
|
||||
return(None)
|
||||
|
||||
def parse(self):
|
||||
def parse_xml(self):
|
||||
self.parse_raw()
|
||||
self.get_xsd()
|
||||
self.populate_defaults()
|
||||
self.validate()
|
||||
self.subparse()
|
||||
return(None)
|
||||
|
||||
def parse_raw(self, parser = None):
|
||||
if not self.raw:
|
||||
with open(self.xml_path, 'rb') as fh:
|
||||
self.raw = fh.read()
|
||||
self.xml = etree.fromstring(self.raw, parser = parser)
|
||||
self.ns_xml = etree.fromstring(self.raw, parser = parser)
|
||||
self.tree = self.xml.getroottree()
|
||||
|
@ -279,13 +194,198 @@ class Config(object):
|
|||
raise ValueError('Did not know how to parse obj parameter')
|
||||
return(None)
|
||||
|
||||
def subparse(self):
|
||||
self._creds()
|
||||
self._tunnels()
|
||||
return(None)
|
||||
|
||||
def validate(self):
|
||||
if not self.xsd:
|
||||
self.get_xsd()
|
||||
self.xsd.assertValid(self.ns_tree)
|
||||
return(None)
|
||||
|
||||
|
||||
class Config(BaseConfig):
|
||||
default_xsd = 'http://schema.xml.r00t2.io/projects/router/he_ipv6.xsd'
|
||||
|
||||
def __init__(self, xml_path, *args, **kwargs):
|
||||
self.xml_path = os.path.abspath(os.path.expanduser(xml_path))
|
||||
if not os.path.isfile(self.xml_path):
|
||||
with open(self.xml_path, 'wb') as fh:
|
||||
fh.write(create_default_cfg())
|
||||
raise ValueError('xml_path does not exist; '
|
||||
'a sample configuration has been generated (be sure to configure it)')
|
||||
else:
|
||||
with open(xml_path, 'rb') as fh:
|
||||
raw_xml = fh.read()
|
||||
super().__init__(raw_xml, *args, **kwargs)
|
||||
self.creds = {}
|
||||
self.tunnels = collections.OrderedDict()
|
||||
self.subparse()
|
||||
|
||||
def _creds(self):
|
||||
creds_xml = self.xml.find('creds')
|
||||
for cred_xml in creds_xml.findall('cred'):
|
||||
cred = Credential(cred_xml)
|
||||
self.creds[cred.id] = cred
|
||||
return(None)
|
||||
|
||||
def _tunnels(self):
|
||||
tunnels_xml = self.xml.find('tunnels')
|
||||
for tun_xml in tunnels_xml.findall('tunnel'):
|
||||
tun_id = int(tun_xml.attrib['id'].strip())
|
||||
tun_creds_id = tun_xml.attrib['creds']
|
||||
creds = self.creds[tun_creds_id]
|
||||
update_key = tun_xml.find('updateKey').text.strip()
|
||||
# TODO: do I instead want to use HEConfig() and fetch the single unified config?
|
||||
# Pros:
|
||||
# * I wouldn't completely die on a misconfigured tunnel in the user config.
|
||||
# Cons:
|
||||
# * We'd have to skip missing tunnels (bad auth at HE, etc.)
|
||||
# * We would use more memory and take more time during init.
|
||||
he_conf = HETunnelConfig(tun_id, creds, update_key)
|
||||
tun = tunnel.Tunnel(tun_xml, he_conf, self.creds[tun_creds_id])
|
||||
self.tunnels[tun_id] = tun
|
||||
return(None)
|
||||
|
||||
def subparse(self):
|
||||
self._creds()
|
||||
self._tunnels()
|
||||
return(None)
|
||||
|
||||
|
||||
class HEBaseConfig(BaseConfig):
|
||||
default_xsd = 'http://schema.xml.r00t2.io/projects/tunnelbroker.xsd'
|
||||
nsmap = {None: 'https://tunnelbroker.net/tunnelInfo.php',
|
||||
'xsi': 'http://www.w3.org/2001/XMLSchema-instance'}
|
||||
attr_qname = etree.QName('http://www.w3.org/2001/XMLSchema-instance', 'schemaLocation')
|
||||
schema_loc = 'https://tunnelbroker.net/tunnelInfo.php {0}'.format(default_xsd)
|
||||
url = ''
|
||||
|
||||
def __init__(self, creds, *args, **kwargs):
|
||||
self.creds = creds
|
||||
super().__init__(self._fetch(), *args, **kwargs)
|
||||
|
||||
def _add_ns(self, raw_xml):
|
||||
# https://mailman-mail5.webfaction.com/pipermail/lxml/20100323/013260.html
|
||||
_xml = etree.fromstring(raw_xml)
|
||||
_nsmap = copy.deepcopy(_xml.nsmap)
|
||||
_nsmap.update(self.nsmap)
|
||||
mod_xml = etree.Element(_xml.tag,
|
||||
{self.attr_qname: self.schema_loc},
|
||||
nsmap = _nsmap)
|
||||
mod_xml[:] = _xml[:]
|
||||
return(etree.tostring(mod_xml,
|
||||
encoding = 'UTF-8',
|
||||
xml_declaration = True,
|
||||
pretty_print = True,
|
||||
with_tail = True,
|
||||
with_comments = True))
|
||||
|
||||
def _fetch(self):
|
||||
req = requests.get(self.url,
|
||||
auth = requests.auth.HTTPBasicAuth(self.creds.user,
|
||||
self.creds.password))
|
||||
if not req.ok:
|
||||
raise RuntimeError('Could not fetch remote tunnel information')
|
||||
raw_xml = self._add_ns(req.content)
|
||||
return(raw_xml)
|
||||
|
||||
|
||||
# This isn't really used.
|
||||
class HEConfig(HEBaseConfig):
|
||||
default_xsd = 'http://schema.xml.r00t2.io/projects/tunnelbroker.xsd'
|
||||
nsmap = {None: 'https://tunnelbroker.net/tunnelInfo.php',
|
||||
'xsi': 'http://www.w3.org/2001/XMLSchema-instance'}
|
||||
attr_qname = etree.QName('http://www.w3.org/2001/XMLSchema-instance', 'schemaLocation')
|
||||
schema_loc = 'https://tunnelbroker.net/tunnelInfo.php {0}'.format(default_xsd)
|
||||
url = 'https://tunnelbroker.net/tunnelInfo.php'
|
||||
|
||||
def __init__(self, creds, *args, **kwargs):
|
||||
super().__init__(creds, *args, **kwargs)
|
||||
self.tunnels = {}
|
||||
|
||||
def add_tunnel(self, tun_id, update_key):
|
||||
self.tunnels[tun_id] = HETunnelConfig(tun_id, self.creds, update_key)
|
||||
return(None)
|
||||
|
||||
|
||||
class HETunnelConfig(HEBaseConfig):
|
||||
# default_xsd = 'http://schema.xml.r00t2.io/projects/tunnelbroker.tun.xsd'
|
||||
# nsmap = {None: 'https://tunnelbroker.net/tunnelInfo.php?tid',
|
||||
# 'xsi': 'http://www.w3.org/2001/XMLSchema-instance'}
|
||||
# attr_qname = etree.QName('http://www.w3.org/2001/XMLSchema-instance', 'schemaLocation')
|
||||
# schema_loc = 'https://tunnelbroker.net/tunnelInfo.php?tid {0}'.format(default_xsd)
|
||||
url = 'https://tunnelbroker.net/tunnelInfo.php?tid={0}'
|
||||
|
||||
def __init__(self, tun_id, creds, update_key, *args, **kwargs):
|
||||
self.tun_id = int(tun_id)
|
||||
self.url = self.url.format(self.tun_id)
|
||||
self.creds = copy.deepcopy(creds)
|
||||
self.creds.password = update_key
|
||||
super().__init__(self.creds, *args, **kwargs)
|
||||
self.id = None
|
||||
self.description = None
|
||||
self.client = None # Client IPv6
|
||||
self.server = None # Server IPv6
|
||||
self.endpoint = None # Server IPv4
|
||||
self.my_ip = None # Client IPv4 (not necessary; we locally cache Tunnel.my_ip)
|
||||
self.allocations = {} # keys are 64 and 48
|
||||
self.rdns = [] # Also not necessary, but it's in the XML so why not.
|
||||
# Will only return a single <tunnel> for this URL.
|
||||
# TODO: I can probably consolidate all this into HECond instead?
|
||||
self.tun_xml = self.xml.find('tunnel')
|
||||
self.parse()
|
||||
|
||||
def _alloc(self):
|
||||
for a in ('64', '48'):
|
||||
_alloc = self.tun_xml.find('routed{0}'.format(a))
|
||||
if _alloc is not None and _alloc.text.strip() != '':
|
||||
self.allocations[int(a)] = tunnel.Allocation(_alloc.text.strip())
|
||||
return(None)
|
||||
|
||||
def _client(self):
|
||||
_client = self.tun_xml.find('clientv6').text
|
||||
if _client is not None and _client.strip() != '':
|
||||
self.client = tunnel.IP6(_client.strip(), 64)
|
||||
return(None)
|
||||
|
||||
def _desc(self):
|
||||
_desc = self.tun_xml.find('description').text
|
||||
if _desc is not None and _desc.strip() != '':
|
||||
self.description = _desc.strip()
|
||||
return(None)
|
||||
|
||||
def _endpoint(self):
|
||||
self.endpoint = tunnel.IP4(self.tun_xml.find('serverv4').text.strip(), 32)
|
||||
return(None)
|
||||
|
||||
def _id(self):
|
||||
self.id = int(self.tun_xml.attrib['id'])
|
||||
return(None)
|
||||
|
||||
def _my_ip(self):
|
||||
_ip = self.tun_xml.find('clientv4').text
|
||||
if _ip is not None and _ip.strip() != '':
|
||||
self.my_ip = tunnel.IP4(_ip.strip(), 32)
|
||||
return(None)
|
||||
|
||||
def _rdns(self):
|
||||
self.rdns = []
|
||||
for r in range(1, 6):
|
||||
_rdns = self.tun_xml.find('rdns{0}'.format(r))
|
||||
if _rdns is not None and _rdns.text.strip() != '':
|
||||
self.rdns.append(_rdns.text.strip())
|
||||
self.rdns = tuple(self.rdns)
|
||||
return(None)
|
||||
|
||||
def _server(self):
|
||||
self.server = tunnel.IP6(self.tun_xml.find('serverv6').text.strip(), 128)
|
||||
return(None)
|
||||
|
||||
def parse(self):
|
||||
self._id()
|
||||
self._client()
|
||||
self._desc()
|
||||
self._endpoint()
|
||||
self._server()
|
||||
self._my_ip()
|
||||
self._alloc()
|
||||
self._rdns()
|
||||
return(None)
|
||||
|
|
|
@ -1,85 +1,152 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<heIPv6 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="https://tunnelbroker.net/"
|
||||
xsi:schemaLocation="https://tunnelbroker.net/ http://schema.xml.r00t2.io/projects/he_ipv6.xsd">
|
||||
xsi:schemaLocation="https://tunnelbroker.net/ http://schema.xml.r00t2.io/projects/router/he_ipv6.xsd">
|
||||
<!--
|
||||
This is a sample XML configuration file to use with he_ipv6.py.
|
||||
If you do not yet have an IPv6 Tunnelbroker.net allocation, you can get one (for free!) at:
|
||||
https://www.tunnelbroker.net/tunnel_detail.php?tid=584532
|
||||
I highly recommend their (free) certification as well if you're brand-new to IPv6:
|
||||
https://ipv6.he.net/certification/
|
||||
**It is VERY highly encouraged to only use one tunnel at a time on a machine. Completely unpredictable results will
|
||||
incur if this is not heeded.**
|
||||
-->
|
||||
<creds>
|
||||
<!--
|
||||
Credentials are kept separate from tunnel configuration because you can have multiple (up to 5) tunnels per user.
|
||||
The updateKey is *not* your password! You can find it in the "Advanced" tab of your tunnel's configuration on
|
||||
your tunnelbroker.net panel.
|
||||
-->
|
||||
<cred id="ipv6user">
|
||||
<user>ipv6user</user>
|
||||
<updateKey>xXxXxXxXxXxXxXXX</updateKey>
|
||||
<password>someSecretPassword</password>
|
||||
</cred>
|
||||
<cred id="anotheruser">
|
||||
<user>someotheruser</user>
|
||||
<updateKey>0000000000000000</updateKey>
|
||||
<password>anotherPassword</password>
|
||||
</cred>
|
||||
</creds>
|
||||
<tunnels>
|
||||
<!--
|
||||
Each tunnel MUST have an "id" and a "creds" attribute. The "creds" attribute should reference an "id" of a
|
||||
creds/cred object.
|
||||
The tunnel ID can be found by logging into your tunnelbroker.net pannel, clicking on the tunnel you wish to use, and
|
||||
The tunnel ID can be found by logging into your tunnelbroker.net panel, clicking on the tunnel you wish to use, and
|
||||
looking at the URL in your browser.
|
||||
It is in the format of https://www.tunnelbroker.net/tunnel_detail.php?tid=[TUNNEL ID]
|
||||
So if it takes you to e.g. https://www.tunnelbroker.net/tunnel_detail.php?tid=12345, your tunnel ID would
|
||||
be "12345".
|
||||
The below directives give you a Section and Value Name. This refers to the tunnelbroker.net panel page for the
|
||||
specific tunnel you're configuring. e.g. To use the above example, this information is found at
|
||||
https://www.tunnelbroker.net/tunnel_detail.php?tid=12345
|
||||
-->
|
||||
<tunnel id="12345" creds="ipv6user">
|
||||
<!--
|
||||
The "server" element is the remote SIT endpoint.
|
||||
Section: IPv6 Tunnel Endpoints
|
||||
Value Name: Server IPv4 Address
|
||||
You can find the updateKey in the "Advanced" tab of your tunnel's configuration on your tunnelbroker.net panel.
|
||||
-->
|
||||
<server>192.0.2.1</server>
|
||||
<updateKey>xXxXxXxXxXxXxXXX</updateKey>
|
||||
<!--
|
||||
Allocations that are handed to your tunnel.
|
||||
Where to assign your allocations. The default allocation prefix is a /64 (prefix="64"), since that's what
|
||||
SLAAC (RFC 2462) recommends.
|
||||
It has one optional attribute, "raProvider", which can be "dnsmasq" or "radvd". Its configuration file will be
|
||||
regenerated and the service restarted after the addresses are allocated to interfaces. Further system
|
||||
configuration may be required. If not specified, the default is to not send router advertisements (RFC 4861). See
|
||||
the "ra" child element under <assign> for further details.
|
||||
If you are using dnsmasq, you will want to edit dnsmasq.conf to *include* the generated file, most likely, as it
|
||||
only generates configuration for IPv6 options.
|
||||
If this is not specified, NO RA/DHCPv6 management will be done *regardless* of any "re" child elements for below
|
||||
"assign" objects.
|
||||
-->
|
||||
<!--
|
||||
Section: Routed IPv6 Prefixes
|
||||
-->
|
||||
<allocs>
|
||||
<assignments raProvider="dnsmasq">
|
||||
<!--
|
||||
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
|
||||
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
|
||||
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 the prefix of the allocation. Hurricane Electric only allows you one /64 and,
|
||||
optionally, one /48. Use "alloc" to reference which allocation you want to use. Uses "64" (/64)
|
||||
by default.
|
||||
* "iface" - which network interface on this machine the allocation should be added to.
|
||||
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 prefix.
|
||||
-->
|
||||
<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>
|
||||
<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 an ra element
|
||||
is not present.
|
||||
It takes one optional attribute, which is only used for raProvider="dnsmasq", "tag", which is the tag name for
|
||||
the interface (this should be set in an earlier included conf/the main dnsmasq.conf).
|
||||
-->
|
||||
<ra tag="main">
|
||||
<!--
|
||||
Specify RDNSS (RFC 8106). If specified, this allocation's "router IP" (<PREFIX>::1) will be passed as a
|
||||
resolver via RDNSS.
|
||||
It takes one (optional) attribute, "domains", which is a space-separated list of search domains, referred
|
||||
to in IPv6 as DNSSL (RFC 6106).
|
||||
Note that Windows does not support DNSSL properly, and as such you must use dnsmasq as your RA provider if
|
||||
you wish to send search domains.
|
||||
If "domains" is specified but the element is false, the configuration will only advertise DNSSL and not
|
||||
RDNSS.
|
||||
If you also specify dhcpv6 below and are using dnsmasq as your raProvider, then:
|
||||
* the same domains will be sent via DHCPv6 option 24
|
||||
* the same RDNSS resolver will be passed via DHCPv6 option 23
|
||||
-->
|
||||
<dns domains="foo.com bar.com">true</dns>
|
||||
<!--
|
||||
Enable DHCPv6 for this assignment.
|
||||
|
||||
RADVD:
|
||||
If you're using radvd, this will only enable the "AdvManagedFlag" and/or "AdvOtherConfigFlag" flags
|
||||
(the "MO" bits). *No actual DHCPv6 address assignment will, or can, occur via radvd, only SLAAC.*
|
||||
|
||||
DNSMASQ:
|
||||
To ensure maximum compatability with SLAAC, addresses will be served in the fixed range of:
|
||||
<PREFIX>:dead:beef:cafe:[0000-FFFF]
|
||||
(65535 addresses per prefix assignment, a.k.a. a /112).
|
||||
Obviously your assignment's prefix length *must* be smaller than /112 (but should be at LEAST a /64 anyways
|
||||
per RFC specification). Regardless of settings below, SLAAC *will* be offered if an "ra" element is
|
||||
defined ("A" bit). Since we entirely deal with local links, the L bit is also always set.
|
||||
|
||||
It has an optional attribute, "advOther", which controls the "Other Configuration" bit.
|
||||
The default is "false".
|
||||
The "MO" bits (RFC 4861 § 4.2) are set accordingly:
|
||||
===========================================================================================================
|
||||
| Condition | M | O | A | L | Will addresses be assigned via DHCPv6 (if dnsmasq)? |
|
||||
===========================================================================================================
|
||||
| advOther="true", dhcpv6 is true | 1 | 1 | 1 | 1 | Yes |
|
||||
| advOther="true", dhcpv6 is false | 0 | 1 | 1 | 1 | No |
|
||||
| advOther="false", dhcpv6 is false | 0 | 0 | 1 | 1 | No |
|
||||
| advOther="false", dhcpv6 is true | 1 | 0 | 1 | 1 | Yes (but O = 0 is pointless) |
|
||||
===========================================================================================================
|
||||
-->
|
||||
<dhcpv6 advOther="true">true</dhcpv6>
|
||||
</ra>
|
||||
</assign>
|
||||
<!-- Disable RA for this set (no "ra" child specified). -->
|
||||
<assign prefix="64" alloc="48" iface="eth0"/>
|
||||
<assign prefix="64" alloc="48" iface="eth1">
|
||||
<ra tag="vmlan">
|
||||
<!-- This will use strictly SLAAC (if using dnsmasq, obviously - radvd only does SLAAC). -->
|
||||
<dhcpv6 advOther="false">false</dhcpv6>
|
||||
<!-- And let clients choose their own resolver. -->
|
||||
<dns>false</dns>
|
||||
</ra>
|
||||
</assign>
|
||||
<assign prefix="64" alloc="48" iface="eth2">
|
||||
<ra tag="wlan">
|
||||
<!-- Only pass RDNSS resolvers. -->
|
||||
<dns>true</dns>
|
||||
<dhcpv6 advOther="false">false</dhcpv6>
|
||||
</ra>
|
||||
</assign>
|
||||
</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>
|
||||
<!-- And you can, of course, specify multiple tunnels. -->
|
||||
<tunnel id="54321" creds="anotheruser">
|
||||
<updateKey>0000000000000000</updateKey>
|
||||
<assignments>
|
||||
<!--
|
||||
Uses the default prefix of /64 from your standard /64 allocation from Hurricane Electric.
|
||||
Most users probably want this if they just want IPv6 for their local computer unless they're running an IPv6
|
||||
router.
|
||||
-->
|
||||
<assign iface="eth0"/>
|
||||
</assignments>
|
||||
</tunnel>
|
||||
</tunnels>
|
||||
</heIPv6>
|
||||
|
|
|
@ -8,8 +8,10 @@ try:
|
|||
except ImportError:
|
||||
_has_journald = False
|
||||
|
||||
|
||||
logfile = '/var/log/tunnelbroker_manager.log'
|
||||
if os.geteuid() == 0:
|
||||
logfile = '/var/log/tunnelbroker_manager.log'
|
||||
else:
|
||||
logfile = '~/.cache/tunnelbroker_manager.log'
|
||||
# Prep the log file.
|
||||
logfile = os.path.abspath(os.path.expanduser(logfile))
|
||||
os.makedirs(os.path.dirname(logfile), exist_ok = True, mode = 0o0700)
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import warnings
|
||||
##
|
||||
import jinja2
|
||||
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class RAConf(object):
|
||||
cfgstr = None
|
||||
tpl_dir = os.path.join(os.path.dirname(os.path.abspath(os.path.expanduser(__file__))), 'tpl')
|
||||
|
||||
def __init__(self, conf = None, tpl_name = None, tpl_dir = None, *args, **kwargs):
|
||||
for k in ('name', 'dir'):
|
||||
n = 'tpl_{0}'.format(k)
|
||||
v = locals()[n]
|
||||
if v:
|
||||
setattr(self, n, v)
|
||||
if conf:
|
||||
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, extensions = ['jinja2.ext.do'])
|
||||
self.tpl = self.tpl_env.get_template(self.tpl_name)
|
||||
return(None)
|
||||
|
||||
def generate(self, assignments):
|
||||
self.cfgstr = self.tpl.render(assignments = assignments)
|
||||
return(None)
|
||||
|
||||
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):
|
||||
is_systemd = False
|
||||
cmd_tpl = None
|
||||
has_pkill = False
|
||||
start_cmd = None
|
||||
stop_cmd = None
|
||||
restart_cmd = None
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
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)
|
||||
if s and s.decode('utf-8').strip() != '':
|
||||
logger.warning('{0}: {1}'.format(i.upper(), s.decode('utf-8')))
|
||||
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'
|
||||
conf = '/etc/radvd.conf'
|
||||
tpl_name = 'radvd.conf.j2'
|
||||
|
||||
def __init__(self, conf = None, tpl_name = None, tpl_dir = None):
|
||||
if not conf:
|
||||
conf = self.conf
|
||||
if not tpl_name:
|
||||
tpl_name = self.tpl_name
|
||||
super().__init__(conf = conf, tpl_name = tpl_name, tpl_dir = tpl_dir)
|
||||
self.svc = RASvc(self.name)
|
||||
self.conf.ext_init()
|
||||
|
||||
|
||||
class DNSMasq(RA):
|
||||
name = 'dnsmasq'
|
||||
conf = '/etc/dnsmasq.d/ra.conf'
|
||||
tpl_name = 'dnsmasq.include.j2'
|
||||
|
||||
def __init__(self, conf = None, tpl_name = None, tpl_dir = None):
|
||||
if not conf:
|
||||
conf = self.conf
|
||||
if not tpl_name:
|
||||
tpl_name = self.tpl_name
|
||||
super().__init__(conf = conf, tpl_name = tpl_name, tpl_dir = tpl_dir)
|
||||
self.svc = RASvc(self.name)
|
||||
self.conf.ext_init()
|
|
@ -1,7 +1,41 @@
|
|||
## General Info/Networking Configuration ##
|
||||
# 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
|
||||
# http://koo.fi/blog/2013/03/20/linux-ipv6-router-radvd-dhcpv6/
|
||||
|
||||
## Tunnelbroker API ##
|
||||
# https://forums.he.net/index.php?topic=3153.0 ("The following scripts conform to the Dyn DNS Update API (as documented at http://dyn.com/support/developers/api/).")
|
||||
# https://help.dyn.com/remote-access-api/return-codes/
|
||||
|
||||
## DNSMASQ ##
|
||||
# https://hveem.no/using-dnsmasq-for-dhcpv6
|
||||
|
||||
## RFCs ##
|
||||
# DNSSL #
|
||||
# https://tools.ietf.org/html/rfc6106
|
||||
# https://tools.ietf.org/html/rfc8106
|
||||
# RDNSS #
|
||||
# https://tools.ietf.org/html/rfc5006 (see also 6106, 8106)
|
||||
# SLAAC
|
||||
# https://tools.ietf.org/html/rfc2462
|
||||
# https://tools.ietf.org/html/rfc4862
|
||||
# https://tools.ietf.org/html/rfc8064
|
||||
# Router Advertisements
|
||||
# https://tools.ietf.org/html/rfc4861
|
||||
# https://tools.ietf.org/html/rfc5175
|
||||
# https://tools.ietf.org/html/rfc6104
|
||||
# https://tools.ietf.org/html/rfc7772
|
||||
# DHCPv6
|
||||
# https://tools.ietf.org/html/rfc3315
|
||||
# https://tools.ietf.org/html/rfc3646
|
||||
# https://tools.ietf.org/html/rfc4649
|
||||
# https://tools.ietf.org/html/rfc8415
|
||||
##
|
||||
# https://www.cisco.com/c/en/us/td/docs/ios-xml/ios/ipv6_fhsec/configuration/xe-3s/ip6f-xe-3s-book/ip6-rfcs.pdf
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{#- This is a set of common options between DNSMasq and RADVD. Or they're easier to just define here. -#}
|
||||
{#- ## SLAAC OPTIONS ## -#}
|
||||
{#- Is it 1480 or 1280? Arch wiki says 1480, but everything else (older) says 1280. -#}
|
||||
{#- set mtu = 1280 -#}
|
||||
{%- set mtu = 1480 -%}
|
||||
{#- Minimum seconds allowed between sending unsolicited multicast RAs. 3 < x < (0.75 * max_inter) -#}
|
||||
{#- If using Mobile Extensions, 0.33 < x (0.75 * max_inter) -#}
|
||||
{%- set min_inter = 10 -%}
|
||||
{#- Maximum seconds allowed between sending unsolicited multicast RAs. 4 < x < 1800 -#}
|
||||
{#- If using Mobile Extensions, 0.07 < x 1800 -#}
|
||||
{%- set max_inter = 60 -%}
|
||||
{#- Minimum seconds between sending multicast RAs (solicited and unsolicited). -#}
|
||||
{#- If using Mobile Extensions, 0.03 < x -#}
|
||||
{%- set min_delay = 3 -%}
|
||||
{#- The lifetime associated with the default router in units of seconds. 0 OR max_inter < x < 9000 -#}
|
||||
{%- set lifetime = 9000 -%}
|
||||
{#- ## DHCPv6 OPTIONS ## -#}
|
||||
{#- Obviously, these only work for DNSMasq. -#}
|
||||
{#- How long the lease should last until a new one is requested. -#}
|
||||
{#- This is also used for *SLAAC addresses* in radvd. -#}
|
||||
{%- set lease_life = 21600 -%}{#- 6 hours -#}
|
||||
{#- How long should the options be valid for. -#}
|
||||
{%- set opts_life = lease_life -%}
|
|
@ -0,0 +1,55 @@
|
|||
{%- import '_common.j2' as common_opts with context -%}
|
||||
# This file should be *included* in your dnsmasq configuration.
|
||||
# Generated by he_ipv6.
|
||||
# See "dnsmasq --help dhcp6" for matching option identifers ("dhcp-option = ..., option6: <option>").
|
||||
enable-ra
|
||||
{% for assignment in assignments %}
|
||||
{%- set assign_loop = loop -%}
|
||||
{%- set ra_opts = [] -%}
|
||||
{%- if assignment.ra_tag -%}
|
||||
{%- set id_set = 'tag:' + assignment.ra_tag -%}
|
||||
{%- set identifier = id_set -%}
|
||||
{%- set do_listen = false -%}
|
||||
{%- else -%}
|
||||
{%- set id_set = 'set:' + assignment.iface -%}
|
||||
{%- set identifier = 'tag:' + assignment.iface -%}
|
||||
{%- set do_listen = true -%}
|
||||
{%- endif -%}
|
||||
{%- if assignment.ra_dhcp is false -%}
|
||||
{%- do ra_opts.append('ra-only') -%}
|
||||
{%- if assignment.ra_other is true -%}
|
||||
{%- do ra_opts.append('ra-stateless') -%}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- do ra_opts.append('slaac') -%}
|
||||
{%- do ra_opts.append('ra-names') -%}
|
||||
# {{ assignment.iface }} assignment
|
||||
# Assignment blocks:
|
||||
{%- for b in assignment.iface_blocks %}
|
||||
# * {{ b|string }}
|
||||
{%- endfor %}
|
||||
{%- if do_listen %}
|
||||
listen-address = {{ assignment.iface_ll }}
|
||||
{%- endif %}
|
||||
ra-param = {{ assignment.iface }}, mtu:{{ common_opts.mtu }}, high, {{ common_opts.min_delay }}, {{ common_opts.lifetime }}
|
||||
{%- if assignment.ra_dhcp %}
|
||||
{%- for block in assignment.assign_objs %}
|
||||
{%- set dhcp_range = block.dhcp6_range|join(', ') -%}
|
||||
{%- if loop.index0 == 0 %}
|
||||
dhcp-range = {{ id_set }}, {{ dhcp_range }}, {{ ra_opts|join(', ') }}, {{ common_opts.lease_life }}
|
||||
{%- else %}
|
||||
dhcp-range = {{ identifier }}, {{ dhcp_range }}, {{ ra_opts|join(', ') }}, {{ common_opts.lease_life }}
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
{%- else %}
|
||||
dhcp-range = {{ id_set }}, ::, {{ ra_opts|join(', ') }}, {{ common_opts.lease_life }}{#- TODO: check this. #}
|
||||
{%- endif %}
|
||||
dhcp-option = {{ identifier }}, option6:information-refresh-time, {{ common_opts.opts_life }}
|
||||
{%- if assignment.ra_dns %}
|
||||
dhcp-option = {{ identifier }}, option6:dns-server, [{{ assignment.iface_ll }}]
|
||||
{%- endif %}
|
||||
{%- if assignment.ra_domains %}
|
||||
dhcp-option = {{ identifier }}, option6:domain-search, {{ assignment.ra_domains|join(',') }}
|
||||
{%- endif %}
|
||||
|
||||
{% endfor %}
|
|
@ -0,0 +1,44 @@
|
|||
{%- import '_common.j2' as common_opts with context -%}
|
||||
# Generated by he_ipv6.
|
||||
# This may go wonky with multiple assignments on the same iface.
|
||||
{% for assignment in assignments %}
|
||||
interface {{ assignment.iface }} {
|
||||
AdvSendAdvert on;
|
||||
AdvLinkMTU {{ common_opts.mtu }};
|
||||
MinRtrAdvInterval {{ common_opts.min_inter }};
|
||||
MaxRtrAdvInterval {{ common_opts.max_inter }};
|
||||
MinDelayBetweenRAs {{ common_opts.min_delay }};
|
||||
AdvDefaultLifetime {{ common_opts.lifetime }};
|
||||
{%- if assignment.ra_dhcp is true -%}
|
||||
AdvManagedFlag on;
|
||||
{%- endif %}
|
||||
{%- if assignment.ra_other is true -%}
|
||||
AdvOtherConfigFlag on;
|
||||
{%- endif %}
|
||||
|
||||
{%- for block in assignment.iface_blocks %}
|
||||
prefix {{ block|string }} {
|
||||
AdvOnLink on;
|
||||
{%- if block.prefixlen <= 64 %}
|
||||
AdvAutonomous on;
|
||||
{%- endif %}
|
||||
AdvValidLifetime {{ common_opts.lease_life }};
|
||||
AdvPreferredLifetime {{ common_opts.lease_life }};
|
||||
AdvRouterAddr off;
|
||||
};
|
||||
{%- endfor %}
|
||||
|
||||
{%- if assign.ra_dns is true %}
|
||||
RDNSS {{ assignment.iface_ll }} {
|
||||
AdvRDNSSLifetime {{ common_opts.opts_life }};
|
||||
};
|
||||
{%- endif %}
|
||||
|
||||
{%- if assign.ra_domains %}
|
||||
DNSSL {{ assignment.ra_domains|join(' ') }} {
|
||||
AdvDNSSLLifetime {{ common_opts.opts_life }};
|
||||
};
|
||||
{%- endif %}
|
||||
};
|
||||
|
||||
{%- endfor %}
|
|
@ -0,0 +1,252 @@
|
|||
import ipaddress
|
||||
import logging
|
||||
import re
|
||||
import socket
|
||||
##
|
||||
import netaddr
|
||||
from pyroute2 import IPRoute
|
||||
##
|
||||
from . import ra
|
||||
from . import utils
|
||||
|
||||
|
||||
# TODO: more logging
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class IP(object):
|
||||
type = None
|
||||
version = None
|
||||
_ip = ipaddress.ip_address
|
||||
_net = ipaddress.ip_network
|
||||
_net_ip = netaddr.IPAddress
|
||||
_net_net = netaddr.IPNetwork
|
||||
|
||||
def __init__(self, ip, prefix, *args, **kwargs):
|
||||
self.str = ip
|
||||
self.prefix = int(prefix)
|
||||
self.net_ip = self._net_ip(self.str)
|
||||
self.net_net = self._net_net('{0}/{1}'.format(self.str, self.prefix))
|
||||
|
||||
def _ext_init(self):
|
||||
self.ip = self._ip(self.str)
|
||||
self.net = self._net('{0}/{1}'.format(self.str, self.prefix), strict = False)
|
||||
return(None)
|
||||
|
||||
|
||||
class IP4(IP):
|
||||
type = 'IPv4'
|
||||
version = 4
|
||||
_ip = ipaddress.IPv4Address
|
||||
_net = ipaddress.IPv4Network
|
||||
|
||||
def __init__(self, ip, prefix, *args, **kwargs):
|
||||
super().__init__(ip, prefix, *args, **kwargs)
|
||||
self._ext_init()
|
||||
|
||||
|
||||
class IP6(IP):
|
||||
type = 'IPv6'
|
||||
version = 6
|
||||
_ip = ipaddress.IPv6Address
|
||||
_net = ipaddress.IPv6Network
|
||||
|
||||
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,
|
||||
ra_dns = False,
|
||||
ra_dhcp = False,
|
||||
ra_other = False,
|
||||
ra_tag = None,
|
||||
ra_domains = None):
|
||||
self.xml = assign_xml
|
||||
self.ra_dns = ra_dns
|
||||
self.ra_tag = ra_tag
|
||||
self.ra_other = ra_other
|
||||
self.ra_dhcp = ra_dhcp
|
||||
for a in ('dns', 'tag', 'other', 'dhcp'):
|
||||
k = 'ra_{0}'.format(a)
|
||||
v = getattr(self, k)
|
||||
logger.debug('{0}: {1}'.format(k, v))
|
||||
self.ra_domains = set()
|
||||
if isinstance(ra_domains, list):
|
||||
self.ra_domains.update(ra_domains)
|
||||
elif isinstance(ra_domains, str):
|
||||
self.ra_domains.update([i.lower().strip() for i in ra_domains if i.strip() != ''])
|
||||
logger.debug('ra_domains: {0}'.format(', '.join(self.ra_domains)))
|
||||
self.iface = None
|
||||
self.iface_ll = None
|
||||
self.iface_idx = None
|
||||
self.iface_blocks = []
|
||||
self.assign_objs = []
|
||||
self.alloc = None # This must be set externally to a mapped Allocation instance
|
||||
self.alloc_id = None
|
||||
self.prefix = None
|
||||
self.alloc_block = None
|
||||
self.parse()
|
||||
|
||||
def _alloc(self):
|
||||
self.alloc_id = int(self.xml.attrib['alloc'].strip())
|
||||
return(None)
|
||||
|
||||
def _iface(self):
|
||||
_iface_txt = self.xml.attrib['iface'].strip()
|
||||
self.iface = _iface_txt.strip()
|
||||
ipr = IPRoute()
|
||||
logger.debug('Looking for iface {0}'.format(self.iface))
|
||||
self.iface_idx = ipr.link_lookup(ifname = self.iface)[0]
|
||||
logger.debug('Found iface {0} at idx {1}'.format(self.iface, self.iface_idx))
|
||||
# Link-Local address
|
||||
ll = ipr.get_addr(index = self.iface_idx,
|
||||
family = socket.AF_INET6,
|
||||
scope = 253)[0]['attrs']
|
||||
addrs = dict(ll)['IFA_ADDRESS']
|
||||
logger.debug('Link-Local address for {0}: {1}'.format(self.iface, addrs))
|
||||
if isinstance(addrs, (list, tuple)):
|
||||
addr = addrs[0]
|
||||
else:
|
||||
addr = addrs
|
||||
self.iface_ll = addr
|
||||
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
|
||||
for block in self.alloc_block.extract_subnet(self.prefix, count = 1):
|
||||
self.iface_blocks.append(block)
|
||||
self.assign_objs.append(AsssignmentBlock(block))
|
||||
logger.debug('Allocation blocks for {0}: {1}'.format(self.iface, ','.join([str(i) for i in self.iface_blocks])))
|
||||
return(None)
|
||||
|
||||
|
||||
class AsssignmentBlock(object):
|
||||
def __init__(self, net_net):
|
||||
self.ip, self.prefix = str(net_net).split('/')
|
||||
self.prefix = int(self.prefix)
|
||||
self.ip = IP6(self.ip, self.prefix)
|
||||
if self.prefix > 64:
|
||||
raise ValueError('Allocation/Assignment block must be a /64 or larger (i.e. a smaller prefix)')
|
||||
# DHCPv6 range.
|
||||
# We need to do some funky things here.
|
||||
_base = self.ip.ip
|
||||
_base = ipaddress.IPv6Address(re.sub(r'(:0000){4}$', r':dead:beef:cafe::', str(_base.exploded)))
|
||||
self.base = re.sub(r':0$', r'', str(_base))
|
||||
start = '{0}:0'.format(self.base)
|
||||
stop = '{0}:ffff'.format(self.base)
|
||||
self.dhcp6_range = (start, stop)
|
||||
|
||||
|
||||
class Allocation(object):
|
||||
def __init__(self, alloc_net):
|
||||
_ip, _prefix = alloc_net.split('/')
|
||||
self.id = int(_prefix.strip())
|
||||
self.prefix = self.id
|
||||
self.ip = IP6(_ip.strip(), self.prefix)
|
||||
|
||||
|
||||
class Tunnel(object):
|
||||
def __init__(self, tun_xml, he_tunnel, creds):
|
||||
self.xml = tun_xml
|
||||
self.creds = creds
|
||||
self.he = he_tunnel
|
||||
self.update_key = self.he.creds.password
|
||||
self.id = None
|
||||
self.client = None
|
||||
self.server = None
|
||||
self.endpoint = None
|
||||
self.ra = None
|
||||
self.ra_provider = None
|
||||
self.allocations = {} # This is a dict of {}[alloc.id] = Allocation obj (as provided by HE)
|
||||
self.assignments = [] # This is a list of Assignment objs
|
||||
self.parse()
|
||||
|
||||
def _allocations(self):
|
||||
self.allocations = self.he.allocations
|
||||
return(None)
|
||||
|
||||
def _assignments(self):
|
||||
_assigns_xml = self.xml.find('assignments')
|
||||
self.ra_provider = _assigns_xml.attrib.get('raProvider')
|
||||
for _assign_xml in _assigns_xml.findall('assign'):
|
||||
do_dns = False
|
||||
domains = []
|
||||
do_dhcp = False
|
||||
ra_other = False
|
||||
tag = None
|
||||
_ra_xml = _assign_xml.find('ra')
|
||||
if _ra_xml is not None and self.ra_provider:
|
||||
tag = _ra_xml.attrib.get('tag', None)
|
||||
_dns_xml = _ra_xml.find('dns')
|
||||
_dhcp_xml = _ra_xml.find('dhcpv6')
|
||||
if _dns_xml is not None:
|
||||
do_dns = utils.xml2bool(_dns_xml.text.strip())
|
||||
domains = [i.strip() for i in _dns_xml.attrib.get('domains', '').split() if i.strip() != '']
|
||||
if _dhcp_xml is not None:
|
||||
do_dhcp = utils.xml2bool(_dhcp_xml.text.strip())
|
||||
ra_other = utils.xml2bool(_dhcp_xml.attrib.get('advOther', 'false').strip())
|
||||
assign = Assignment(_assign_xml,
|
||||
ra_dns = do_dns,
|
||||
ra_dhcp = do_dhcp,
|
||||
ra_other = ra_other,
|
||||
ra_tag = tag,
|
||||
ra_domains = domains)
|
||||
assign.alloc = self.allocations[assign.alloc_id]
|
||||
assign.parse_alloc()
|
||||
self.assignments.append(assign)
|
||||
return(None)
|
||||
|
||||
def _client(self):
|
||||
self.client = self.he.client
|
||||
return(None)
|
||||
|
||||
def _creds(self):
|
||||
self.creds_id = self.xml.attrib['creds'].strip()
|
||||
return(None)
|
||||
|
||||
def _endpoint(self):
|
||||
self.endpoint = self.he.endpoint
|
||||
return(None)
|
||||
|
||||
def _id(self):
|
||||
self.id = int(self.xml.attrib['id'].strip())
|
||||
return(None)
|
||||
|
||||
def _ra(self):
|
||||
# TODO: support conf path override via config XML?
|
||||
if self.ra_provider.strip().lower() == 'dnsmasq':
|
||||
self.ra = ra.DNSMasq()
|
||||
elif self.ra_provider.strip().lower() == 'radvd':
|
||||
self.ra = ra.RADVD()
|
||||
self.ra.conf.generate(self.assignments)
|
||||
return(None)
|
||||
|
||||
def _server(self):
|
||||
self.server = self.he.server
|
||||
return(None)
|
||||
|
||||
def parse(self):
|
||||
self._id()
|
||||
self._creds()
|
||||
self._client()
|
||||
self._server()
|
||||
self._endpoint()
|
||||
self._allocations()
|
||||
self._assignments()
|
||||
self._ra()
|
||||
return(None)
|
|
@ -1,3 +1,5 @@
|
|||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
|
@ -8,13 +10,14 @@ import requests.auth
|
|||
from pyroute2 import IPRoute
|
||||
##
|
||||
from . import config
|
||||
from . import tunnel
|
||||
|
||||
|
||||
class TunnelBroker(object):
|
||||
url_ip = 'https://ipv4.clientinfo.square-r00t.net/'
|
||||
params_ip = {'raw': '1'}
|
||||
url_api = 'https://ipv4.tunnelbroker.net/nic/update'
|
||||
def_rt_ip = '::192.88.99.1'
|
||||
ip_cache = '~/.cache/he_tunnelbroker.my_ip.json'
|
||||
|
||||
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))
|
||||
|
@ -27,18 +30,27 @@ class TunnelBroker(object):
|
|||
self.tun = self._conf.tunnels[tun_id]
|
||||
self.iface_name = 'he-{0}'.format(self.tun.id)
|
||||
self.wan = wan_ip
|
||||
self.needs_update = False
|
||||
self.force_update = update
|
||||
self.ip_cache = os.path.abspath(os.path.expanduser(self.ip_cache))
|
||||
self.cached_ips = []
|
||||
self.my_ip = None
|
||||
self.iface_idx = None
|
||||
|
||||
def _get_my_ip(self):
|
||||
if os.path.isfile(self.ip_cache):
|
||||
with open(self.ip_cache, 'r') as fh:
|
||||
self.cached_ips = [(datetime.datetime.fromtimestamp(i[0]),
|
||||
tunnel.IP4(i[1], 32)) for i in json.loads(fh.read())]
|
||||
else:
|
||||
os.makedirs(os.path.dirname(self.ip_cache), exist_ok = True, mode = 0o0700)
|
||||
if self.wan:
|
||||
logger.debug('WAN IP tunneling enabled; fetching WAN IP.')
|
||||
req = requests.get(self.url_ip, params = self.params_ip)
|
||||
if not req.ok:
|
||||
logger.error('Could not fetch self IP. Request returned {0}.'.format(req.status_code))
|
||||
raise RuntimeError('Could not fetch self IP')
|
||||
self.my_ip = config.IP4(req.json()['ip'], 32)
|
||||
self.my_ip = tunnel.IP4(req.json()['ip'], 32)
|
||||
logger.debug('Set my_ip to {0}.'.format(self.my_ip.str))
|
||||
else:
|
||||
logger.debug('WAN IP tunneling disabled; fetching LAN IP.')
|
||||
|
@ -47,15 +59,24 @@ class TunnelBroker(object):
|
|||
if len(_defrt) != 1: # This (probably) WILL fail on multipath systems.
|
||||
logger.error('Could not determine default route. Does this machine have a single default route?')
|
||||
raise RuntimeError('Could not determine default IPv4 route')
|
||||
self.my_ip = config.IP4(_defrt[0]['attrs']['RTA_PREFSRC'], 32)
|
||||
self.my_ip = tunnel.IP4(_defrt[0]['attrs']['RTA_PREFSRC'], 32)
|
||||
ipr.close()
|
||||
logger.debug('Set my_ip to {0}.'.format(self.my_ip.str))
|
||||
chk_tuple = (datetime.datetime.utcnow(), self.my_ip)
|
||||
if len(self.cached_ips) >= 1 and self.my_ip.str != self.cached_ips[-1][1].str:
|
||||
self.needs_update = True
|
||||
elif len(self.cached_ips) == 0:
|
||||
self.needs_update = True
|
||||
if self.needs_update:
|
||||
self.cached_ips.append(chk_tuple)
|
||||
with open(self.ip_cache, 'w') as fh:
|
||||
fh.write(json.dumps([(i[0].timestamp(), i[1].str) for i in self.cached_ips], indent = 4))
|
||||
return(None)
|
||||
|
||||
def start(self):
|
||||
if self.force_update:
|
||||
logger.debug('IP update forced; updating.')
|
||||
self._get_my_ip()
|
||||
self._get_my_ip()
|
||||
if any((self.force_update, self.needs_update)):
|
||||
logger.debug('IP update forced or needed; updating.')
|
||||
self.update()
|
||||
logger.debug('Attempting to clean up any pre-existing config')
|
||||
try:
|
||||
|
@ -69,12 +90,15 @@ class TunnelBroker(object):
|
|||
ifname = self.iface_name,
|
||||
kind = 'sit',
|
||||
sit_local = self.my_ip.str,
|
||||
sit_remote = self.tun.server.str,
|
||||
sit_remote = self.tun.endpoint.str,
|
||||
sit_ttl = 255)
|
||||
logger.debug('Added link {0} successfully.'.format(self.iface_name))
|
||||
except Exception as e:
|
||||
logger.error('Could not create link for link {0} '
|
||||
'(maybe it already exists?): {1}'.format(self.iface_name, e))
|
||||
'(maybe it already exists?) with local {1} and remote {2}: {3}'.format(self.iface_name,
|
||||
self.my_ip.str,
|
||||
self.tun.endpoint.str,
|
||||
e))
|
||||
ipr.close()
|
||||
raise e
|
||||
try:
|
||||
|
@ -109,36 +133,44 @@ class TunnelBroker(object):
|
|||
try:
|
||||
ipr.route('add',
|
||||
dst = 'default',
|
||||
# gateway = self.def_rt_ip,
|
||||
gateway = self.tun.server.str,
|
||||
oif = self.iface_idx,
|
||||
family = socket.AF_INET6)
|
||||
logger.debug('Added default route for link {0}.'.format(self.iface_name))
|
||||
except Exception as e:
|
||||
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} with '
|
||||
'gateway {1}: {2}').format(self.iface_name, self.tun.server.str, e))
|
||||
ipr.close()
|
||||
raise e
|
||||
for alloc in self.tun.allocations:
|
||||
try:
|
||||
ipr.addr('add',
|
||||
index = alloc.iface_idx,
|
||||
address = alloc.ip.str,
|
||||
mask = alloc.ip.prefix,
|
||||
family = socket.AF_INET6)
|
||||
except Exception as e:
|
||||
logger.error(('Could not add address {0} on link {1}: '
|
||||
'{2}').format(str(alloc.ip.str), alloc.iface_idx, e))
|
||||
ipr.close()
|
||||
raise e
|
||||
# Is this necessary?
|
||||
# try:
|
||||
# ipr.route('add',
|
||||
# dst = '::/96',
|
||||
# gateway = '::',
|
||||
# oif = self.iface_idx,
|
||||
# family = socket.AF_INET6)
|
||||
# except Exception as e:
|
||||
# logger.error(('Could not add ::/96 on link {0}: {1}'.format(self.iface_name, e)))
|
||||
for assignment in self.tun.assignments:
|
||||
# The SLAAC prefixes.
|
||||
for b in assignment.iface_blocks:
|
||||
# Try to remove first in case it's already assigned.
|
||||
try:
|
||||
ipr.addr('del',
|
||||
index = assignment.iface_idx,
|
||||
address = str(b.ip),
|
||||
mask = b.prefixlen,
|
||||
family = socket.AF_INET6)
|
||||
logger.debug('Removed {0} with prefix {1} from {2}.'.format(str(b), b.prefixlen, assignment.iface))
|
||||
except Exception as e:
|
||||
pass
|
||||
try:
|
||||
ipr.addr('add',
|
||||
index = assignment.iface_idx,
|
||||
address = str(b.ip),
|
||||
mask = b.prefixlen,
|
||||
family = socket.AF_INET6)
|
||||
logger.debug('Added {0} with prefix {1} to {2}.'.format(str(b.ip), b.prefixlen, assignment.iface))
|
||||
except Exception as e:
|
||||
logger.error(('Could not add address block {0} with prefix {1} on {2}: '
|
||||
'{3}').format(str(b.ip), b.prefixlen, assignment.iface, e))
|
||||
ipr.close()
|
||||
raise e
|
||||
ipr.close()
|
||||
if self.tun.ra:
|
||||
self.tun.ra.conf.write()
|
||||
self.tun.ra.svc.restart()
|
||||
return(None)
|
||||
|
||||
def stop(self):
|
||||
|
@ -172,16 +204,20 @@ class TunnelBroker(object):
|
|||
ipr.close()
|
||||
raise e
|
||||
ipr.close()
|
||||
self.tun.ra.svc.stop()
|
||||
return(None)
|
||||
|
||||
def update(self, oneshot = False):
|
||||
self._get_my_ip()
|
||||
auth_handler = requests.auth.HTTPBasicAuth(self.tun.creds.user, self.tun.creds.key)
|
||||
def update(self):
|
||||
if not self.my_ip:
|
||||
self._get_my_ip()
|
||||
if not self.needs_update:
|
||||
return(None)
|
||||
auth_handler = requests.auth.HTTPBasicAuth(self.tun.creds.user, self.tun.update_key)
|
||||
logger.debug('Set auth handler.')
|
||||
logger.debug('Requesting IP update at provider.')
|
||||
req = requests.get(self.url_api,
|
||||
params = {'hostname': str(self.tun.id),
|
||||
'myip': self.my_ip},
|
||||
'myip': self.my_ip.str},
|
||||
auth = auth_handler)
|
||||
if not req.ok:
|
||||
logger.error('Could not update IP at provider. Request returned {0}.'.format(req.status_code))
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
def xml2bool(xml_str):
|
||||
if xml_str is None:
|
||||
return(None)
|
||||
xml_str = xml_str.lower()[0]
|
||||
if xml_str in ('t', '1'):
|
||||
return(True)
|
||||
elif xml_str in ('f', '0'):
|
||||
return(False)
|
||||
else:
|
||||
raise ValueError('Not a boolean value')
|
|
@ -1,10 +1,11 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import he_ipv6
|
||||
import he_ipv6.logger
|
||||
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger()
|
||||
logger = logging.getLogger('HE Tunnelbroker Manager')
|
||||
|
||||
|
||||
def main():
|
||||
|
@ -16,7 +17,7 @@ def main():
|
|||
elif _args.oper == 'stop':
|
||||
tb.stop()
|
||||
elif _args.oper == 'update':
|
||||
tb.update(oneshot = True)
|
||||
tb.update()
|
||||
return(None)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue