2020-05-12 03:26:44 -04:00
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import socket
|
|
|
|
logger = logging.getLogger()
|
|
|
|
##
|
|
|
|
import requests
|
2020-05-12 18:13:42 -04:00
|
|
|
import requests.auth
|
2020-05-12 03:26:44 -04:00
|
|
|
from pyroute2 import IPRoute
|
|
|
|
##
|
|
|
|
from . import config
|
|
|
|
|
|
|
|
|
|
|
|
class TunnelBroker(object):
|
|
|
|
url_ip = 'https://ipv4.clientinfo.square-r00t.net/'
|
|
|
|
params_ip = {'raw': '1'}
|
|
|
|
url_api = 'https://ipv4.tunnelbroker.net/nic/update'
|
2020-05-12 20:03:18 -04:00
|
|
|
def_rt_ip = '::192.88.99.1'
|
2020-05-12 03:26:44 -04:00
|
|
|
|
|
|
|
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))
|
|
|
|
logger.debug('Using config path: {0}'.format(self.conf_file))
|
|
|
|
self._conf = config.Config(self.conf_file)
|
|
|
|
if tun_id:
|
2020-05-12 04:11:58 -04:00
|
|
|
self.tun = self._conf.tunnels[int(tun_id)]
|
2020-05-12 03:26:44 -04:00
|
|
|
else:
|
|
|
|
tun_id = list(self._conf.tunnels.keys())[0]
|
2020-05-12 04:11:58 -04:00
|
|
|
self.tun = self._conf.tunnels[tun_id]
|
|
|
|
self.iface_name = 'he-{0}'.format(self.tun.id)
|
2020-05-12 03:26:44 -04:00
|
|
|
self.wan = wan_ip
|
2020-05-12 04:11:58 -04:00
|
|
|
self.force_update = update
|
2020-05-12 03:26:44 -04:00
|
|
|
self.my_ip = None
|
2020-05-12 04:11:58 -04:00
|
|
|
self.iface_idx = None
|
2020-05-12 03:26:44 -04:00
|
|
|
|
|
|
|
def _get_my_ip(self):
|
|
|
|
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)
|
|
|
|
logger.debug('Set my_ip to {0}.'.format(self.my_ip.str))
|
|
|
|
else:
|
|
|
|
logger.debug('WAN IP tunneling disabled; fetching LAN IP.')
|
|
|
|
ipr = IPRoute()
|
|
|
|
_defrt = ipr.get_default_routes(family = socket.AF_INET)
|
|
|
|
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)
|
2020-05-12 04:11:58 -04:00
|
|
|
ipr.close()
|
2020-05-12 03:26:44 -04:00
|
|
|
logger.debug('Set my_ip to {0}.'.format(self.my_ip.str))
|
|
|
|
return(None)
|
2020-05-12 04:11:58 -04:00
|
|
|
|
|
|
|
def start(self):
|
|
|
|
if self.force_update:
|
|
|
|
logger.debug('IP update forced; updating.')
|
|
|
|
self._get_my_ip()
|
|
|
|
self.update()
|
2020-05-12 21:02:25 -04:00
|
|
|
logger.debug('Attempting to clean up any pre-existing config')
|
|
|
|
try:
|
|
|
|
self.stop()
|
|
|
|
logger.debug('Pre-existing config removed, continuing')
|
|
|
|
except Exception as e:
|
|
|
|
logger.debug('Config seems to not exist, continuing')
|
2020-05-12 04:11:58 -04:00
|
|
|
ipr = IPRoute()
|
|
|
|
try:
|
|
|
|
ipr.link('add',
|
|
|
|
ifname = self.iface_name,
|
|
|
|
kind = 'sit',
|
|
|
|
sit_local = self.my_ip.str,
|
|
|
|
sit_remote = self.tun.server.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))
|
|
|
|
ipr.close()
|
|
|
|
raise e
|
|
|
|
try:
|
|
|
|
self.iface_idx = ipr.link_lookup(ifname = self.iface_name)[0]
|
|
|
|
logger.debug('Found link {0} at index {1}.'.format(self.iface_name, self.iface_idx))
|
|
|
|
except Exception as e:
|
|
|
|
logger.error('Could not set iface_idx for iface name {0}: {1}'.format(self.iface_name, e))
|
|
|
|
ipr.close()
|
|
|
|
raise e
|
|
|
|
try:
|
|
|
|
ipr.addr('add',
|
|
|
|
index = self.iface_idx,
|
|
|
|
address = self.tun.client.str,
|
|
|
|
mask = self.tun.client.prefix,
|
|
|
|
family = socket.AF_INET6)
|
|
|
|
logger.debug('Added address {0} to link {1} with prefix {2}.'.format(self.tun.client.str,
|
|
|
|
self.iface_name,
|
|
|
|
self.tun.client.prefix))
|
|
|
|
except Exception as e:
|
|
|
|
logger.error(('Could not add address {0} on link {1}: '
|
|
|
|
'{2}').format(self.tun.client.str, self.iface_name, e))
|
|
|
|
ipr.close()
|
|
|
|
raise e
|
|
|
|
try:
|
|
|
|
ipr.link('set', index = self.iface_idx, state = 'up', mtu = 1480)
|
|
|
|
logger.debug('Set link {0} status to UP.'.format(self.iface_name))
|
|
|
|
except Exception as e:
|
|
|
|
logger.error(('Could not bring up link for iface name {0} at index {1}: '
|
|
|
|
'{2}').format(self.iface_name, self.iface_idx, 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
|
|
|
|
try:
|
2020-05-12 20:03:18 -04:00
|
|
|
ipr.route('add',
|
|
|
|
dst = 'default',
|
|
|
|
gateway = self.def_rt_ip,
|
|
|
|
oif = self.iface_idx,
|
|
|
|
family = socket.AF_INET6)
|
2020-05-12 20:56:57 -04:00
|
|
|
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))
|
|
|
|
ipr.close()
|
|
|
|
raise e
|
|
|
|
try:
|
2020-05-12 20:03:18 -04:00
|
|
|
ipr.route('add',
|
|
|
|
dst = '::/96',
|
|
|
|
gateway = '::',
|
|
|
|
oif = self.iface_idx,
|
|
|
|
family = socket.AF_INET6)
|
2020-05-12 04:11:58 -04:00
|
|
|
except Exception as e:
|
2020-05-12 20:56:57 -04:00
|
|
|
logger.error(('Could not add ::/96 on link {0}: {1}'.format(self.iface_name, e)))
|
2020-05-12 04:11:58 -04:00
|
|
|
ipr.close()
|
|
|
|
return(None)
|
|
|
|
|
|
|
|
def stop(self):
|
2020-05-12 18:13:42 -04:00
|
|
|
ipr = IPRoute()
|
|
|
|
try:
|
|
|
|
self.iface_idx = ipr.link_lookup(ifname = self.iface_name)[0]
|
|
|
|
logger.debug('Found link {0} at index {1}.'.format(self.iface_name, self.iface_idx))
|
|
|
|
except Exception as e:
|
|
|
|
ipr.close()
|
|
|
|
logger.error('Could not set iface_idx for link {0}: {1}'.format(self.iface_name, e))
|
|
|
|
raise e
|
|
|
|
try:
|
2020-05-12 20:03:18 -04:00
|
|
|
ipr.route('del',
|
|
|
|
dst = 'default',
|
|
|
|
gateway = self.def_rt_ip,
|
|
|
|
oif = self.iface_idx,
|
|
|
|
family = socket.AF_INET6)
|
2020-05-12 18:13:42 -04:00
|
|
|
logger.debug('Removed default route for link {0}.'.format(self.iface_name))
|
|
|
|
except Exception as e:
|
|
|
|
logger.error(('Could not remove default IPv6 route on link {0}: '
|
|
|
|
'{1} (continuing anyways)').format(self.iface_name, e))
|
|
|
|
try:
|
|
|
|
ipr.link('set', index = self.iface_idx, state = 'down')
|
|
|
|
except Exception as e:
|
|
|
|
logger.error('Could not bring down link {0}: {1} (continuing anyways)'.format(self.iface_name, e))
|
|
|
|
try:
|
|
|
|
ipr.link('del', index = self.iface_idx)
|
|
|
|
logger.debug('Deleted link {0}.'.format(self.iface_name))
|
|
|
|
except Exception as e:
|
|
|
|
logger.error('Could not delete link {0}: {1}'.format(self.iface_name, e))
|
|
|
|
ipr.close()
|
|
|
|
raise e
|
|
|
|
ipr.close()
|
|
|
|
return(None)
|
2020-05-12 04:11:58 -04:00
|
|
|
|
2020-05-12 18:13:42 -04:00
|
|
|
def update(self, oneshot = False):
|
|
|
|
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.')
|
|
|
|
req = requests.get(self.url_api,
|
|
|
|
params = {'hostname': str(self.tun.id),
|
|
|
|
'myip': self.my_ip},
|
|
|
|
auth = auth_handler)
|
|
|
|
if not req.ok:
|
|
|
|
logger.error('Could not update IP at provider. Request returned {0}.'.format(req.status_code))
|
|
|
|
raise RuntimeError('Could not update client IP in tunnel')
|
|
|
|
status = req.content.decode('utf-8').split()[0].strip()
|
|
|
|
if status.lower() not in ('good', 'nochg'):
|
|
|
|
logger.error('Returned following failure message: {0}'.format(req.content.decode('utf-8')))
|
|
|
|
raise RuntimeError('Client IP update returned failure')
|
|
|
|
else:
|
|
|
|
logger.debug('Returned success message: {0}'.format(req.content.decode('utf-8')))
|
|
|
|
return(None)
|