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 requests
import requests.auth
from lxml import etree
from pyroute2 import IPRoute
##
@ -182,7 +183,7 @@ class Allocation(object):


class Tunnel(object):
def __init__(self, tun_xml):
def __init__(self, tun_xml, he_config):
self.xml = tun_xml
self.id = None
self.client = None
@ -194,6 +195,7 @@ class Tunnel(object):
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.heconf = he_config
self.parse()

def _allocations(self):
@ -251,39 +253,19 @@ class Tunnel(object):
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
@ -310,18 +292,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()
@ -359,13 +337,88 @@ 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/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>
<!--
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.
You can find the updateKey in the "Advanced" tab of your tunnel's configuration on your tunnelbroker.net panel.
-->
<cred id="ipv6user">
<user>ipv6user</user>
<password>someSecretPassword</password>
<updateKey>xXxXxXxXxXxXxXXX</updateKey>
</cred>
<cred id="anotheruser">
<user>someotheruser</user>
<password>anotherPassword</password>
<updateKey>0000000000000000</updateKey>
</cred>
</creds>
@ -28,14 +29,11 @@
<!--
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">
<!--

View File

@ -1,5 +1,5 @@
## 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://genneko.github.io/playing-with-bsd/networking/freebsd-tunnelv6-he
# https://journeymangeek.com/?p=228
@ -8,3 +8,8 @@
# 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

## 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 os
import socket
@ -14,6 +16,7 @@ class TunnelBroker(object):
url_ip = 'https://ipv4.clientinfo.square-r00t.net/'
params_ip = {'raw': '1'}
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):
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.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]),
config.IP4(i[1], 32)) for i in json.loads(fh.read())]
if self.wan:
logger.debug('WAN IP tunneling enabled; fetching WAN 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)
ipr.close()
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)

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:
@ -184,8 +201,9 @@ class TunnelBroker(object):
self.tun.radvd.svc.stop()
return(None)

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