i'm an idiot. i spent like 15 minutes debugging this.

This commit is contained in:
brent s. 2020-05-14 03:46:55 -04:00
parent 9a8bae0ba8
commit fb89feb046
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
4 changed files with 122 additions and 48 deletions

View File

@ -6,6 +6,7 @@ import re
## ##
import netaddr import netaddr
import requests import requests
import requests.auth
from lxml import etree from lxml import etree
from pyroute2 import IPRoute from pyroute2 import IPRoute
## ##
@ -182,7 +183,7 @@ class Allocation(object):




class Tunnel(object): class Tunnel(object):
def __init__(self, tun_xml): def __init__(self, tun_xml, he_config):
self.xml = tun_xml self.xml = tun_xml
self.id = None self.id = None
self.client = None self.client = None
@ -194,6 +195,7 @@ class Tunnel(object):
self.radvd_dns = None self.radvd_dns = None
self.allocations = {} # This is a dict of {}[alloc.id] = Allocation obj self.allocations = {} # This is a dict of {}[alloc.id] = Allocation obj
self.assignments = [] # This is a list of Assignment objs self.assignments = [] # This is a list of Assignment objs
self.heconf = he_config
self.parse() self.parse()


def _allocations(self): def _allocations(self):
@ -251,39 +253,19 @@ class Tunnel(object):
return(None) return(None)




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


def __init__(self, xml_path, *args, **kwargs): def __init__(self, xml_raw, *args, **kwargs):
self.xml_path = os.path.abspath(os.path.expanduser(xml_path)) self.raw = xml_raw
if not os.path.isfile(self.xml_path):
raise ValueError('xml_path does not exist')
self.tree = None self.tree = None
self.ns_tree = None self.ns_tree = None
self.xml = None self.xml = None
self.ns_xml = None self.ns_xml = None
self.raw = None
self.xsd = None self.xsd = None
self.defaults_parser = None self.defaults_parser = None
self.obj = None self.obj = None
self.tunnels = collections.OrderedDict() self.parse_xml()
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)


def get_xsd(self): def get_xsd(self):
raw_xsd = None raw_xsd = None
@ -310,18 +292,14 @@ class Config(object):
self.xsd = etree.XMLSchema(etree.XML(raw_xsd, base_url = base_url)) self.xsd = etree.XMLSchema(etree.XML(raw_xsd, base_url = base_url))
return(None) return(None)


def parse(self): def parse_xml(self):
self.parse_raw() self.parse_raw()
self.get_xsd() self.get_xsd()
self.populate_defaults() self.populate_defaults()
self.validate() self.validate()
self.subparse()
return(None) return(None)


def parse_raw(self, parser = 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.xml = etree.fromstring(self.raw, parser = parser)
self.ns_xml = etree.fromstring(self.raw, parser = parser) self.ns_xml = etree.fromstring(self.raw, parser = parser)
self.tree = self.xml.getroottree() self.tree = self.xml.getroottree()
@ -359,13 +337,88 @@ class Config(object):
raise ValueError('Did not know how to parse obj parameter') raise ValueError('Did not know how to parse obj parameter')
return(None) return(None)


def subparse(self):
self._creds()
self._tunnels()
return(None)

def validate(self): def validate(self):
if not self.xsd: if not self.xsd:
self.get_xsd() self.get_xsd()
self.xsd.assertValid(self.ns_tree) self.xsd.assertValid(self.ns_tree)
return(None) return(None)


class Config(BaseConfig):
default_xsd = 'http://schema.xml.r00t2.io/projects/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):
raise ValueError('xml_path does not exist')
else:
with open(xml_path, 'rb') as fh:
raw_xml = fh.read()
super().__init__(raw_xml, *args, **kwargs)
self.heconf = None
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 _heconf(self):
self.heconf = HEConfig(self.creds)
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 = Tunnel(tun_xml, self.heconf.tunnels[tun_id])
tun.creds = self.creds.get(tun.creds_id)
self.tunnels[tun_id] = tun
return(None)

def subparse(self):
self._creds()
self._heconf()
self._tunnels()
return(None)


class HEConfig(BaseConfig):
default_xsd = 'http://schema.xml.r00t2.io/projects/tunnelbroker.xsd'
nsmap = {None: 'https://tunelbroker.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)

def __init__(self, creds, xml_url = 'https://tunnelbroker.net/tunnelInfo.php', *args, **kwargs):
self.creds = creds
self.url = xml_url
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)
super().__init__(raw_xml, *args, **kwargs)
self.tunnels = collections.OrderedDict()
self.subparse()

def subparse(self):
pass

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

View File

@ -12,15 +12,16 @@
<creds> <creds>
<!-- <!--
Credentials are kept separate from tunnel configuration because you can have multiple (up to 5) tunnels per user. 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 You can find the updateKey in the "Advanced" tab of your tunnel's configuration on your tunnelbroker.net panel.
your tunnelbroker.net panel.
--> -->
<cred id="ipv6user"> <cred id="ipv6user">
<user>ipv6user</user> <user>ipv6user</user>
<password>someSecretPassword</password>
<updateKey>xXxXxXxXxXxXxXXX</updateKey> <updateKey>xXxXxXxXxXxXxXXX</updateKey>
</cred> </cred>
<cred id="anotheruser"> <cred id="anotheruser">
<user>someotheruser</user> <user>someotheruser</user>
<password>anotherPassword</password>
<updateKey>0000000000000000</updateKey> <updateKey>0000000000000000</updateKey>
</cred> </cred>
</creds> </creds>
@ -28,14 +29,11 @@
<!-- <!--
Each tunnel MUST have an "id" and a "creds" attribute. The "creds" attribute should reference an "id" of a Each tunnel MUST have an "id" and a "creds" attribute. The "creds" attribute should reference an "id" of a
creds/cred object. 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. looking at the URL in your browser.
It is in the format of https://www.tunnelbroker.net/tunnel_detail.php?tid=[TUNNEL ID] 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 So if it takes you to e.g. https://www.tunnelbroker.net/tunnel_detail.php?tid=12345, your tunnel ID would
be "12345". 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"> <tunnel id="12345" creds="ipv6user">
<!-- <!--

View File

@ -1,5 +1,5 @@
## General Info/Networking Configuration
# https://wiki.archlinux.org/index.php/IPv6_tunnel_broker_setup # 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://gist.github.com/pklaus/960672
# https://genneko.github.io/playing-with-bsd/networking/freebsd-tunnelv6-he # https://genneko.github.io/playing-with-bsd/networking/freebsd-tunnelv6-he
# https://journeymangeek.com/?p=228 # https://journeymangeek.com/?p=228
@ -8,3 +8,8 @@
# https://shorewall.org/6to4.htm#idm143 # https://shorewall.org/6to4.htm#idm143
# https://shorewall.org/6to4.htm#SixInFour # https://shorewall.org/6to4.htm#SixInFour
# https://wiki.ubuntu.com/IPv6#Configure_your_Ubuntu_box_as_a_IPv6_router # https://wiki.ubuntu.com/IPv6#Configure_your_Ubuntu_box_as_a_IPv6_router

## 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/
#

View File

@ -1,3 +1,5 @@
import datetime
import json
import logging import logging
import os import os
import socket import socket
@ -14,6 +16,7 @@ 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'
ip_cache = '~/.cache/he_tunnelbroker.my_ip.json'


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))
@ -26,11 +29,18 @@ class TunnelBroker(object):
self.tun = self._conf.tunnels[tun_id] self.tun = self._conf.tunnels[tun_id]
self.iface_name = 'he-{0}'.format(self.tun.id) self.iface_name = 'he-{0}'.format(self.tun.id)
self.wan = wan_ip self.wan = wan_ip
self.needs_update = False
self.force_update = update self.force_update = update
self.ip_cache = os.path.abspath(os.path.expanduser(self.ip_cache))
self.cached_ips = []
self.my_ip = None self.my_ip = None
self.iface_idx = None self.iface_idx = None


def _get_my_ip(self): 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]),
config.IP4(i[1], 32)) for i in json.loads(fh.read())]
if self.wan: if self.wan:
logger.debug('WAN IP tunneling enabled; fetching WAN IP.') logger.debug('WAN IP tunneling enabled; fetching WAN IP.')
req = requests.get(self.url_ip, params = self.params_ip) req = requests.get(self.url_ip, params = self.params_ip)
@ -49,12 +59,19 @@ class TunnelBroker(object):
self.my_ip = config.IP4(_defrt[0]['attrs']['RTA_PREFSRC'], 32) self.my_ip = config.IP4(_defrt[0]['attrs']['RTA_PREFSRC'], 32)
ipr.close() ipr.close()
logger.debug('Set my_ip to {0}.'.format(self.my_ip.str)) logger.debug('Set my_ip to {0}.'.format(self.my_ip.str))
chk_tuple = (datetime.datetime.utcnow(), self.my_ip)
if self.my_ip.str != self.cached_ips[-1][1].str:
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) return(None)


def start(self): 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() self.update()
logger.debug('Attempting to clean up any pre-existing config') logger.debug('Attempting to clean up any pre-existing config')
try: try:
@ -184,7 +201,8 @@ class TunnelBroker(object):
self.tun.radvd.svc.stop() self.tun.radvd.svc.stop()
return(None) return(None)


def update(self, oneshot = False): def update(self):
if not self.my_ip:
self._get_my_ip() self._get_my_ip()
auth_handler = requests.auth.HTTPBasicAuth(self.tun.creds.user, self.tun.creds.key) auth_handler = requests.auth.HTTPBasicAuth(self.tun.creds.user, self.tun.creds.key)
logger.debug('Set auth handler.') logger.debug('Set auth handler.')