routerbox/utils/he_ipv6/tunnelbroker.py

232 lines
11 KiB
Python
Raw Permalink Normal View History

import datetime
import json
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
2020-05-14 23:46:03 -04:00
from . import tunnel
2020-05-12 03:26:44 -04:00
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'
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
self.needs_update = False
2020-05-12 04:11:58 -04:00
self.force_update = update
self.ip_cache = os.path.abspath(os.path.expanduser(self.ip_cache))
self.cached_ips = []
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 os.path.isfile(self.ip_cache):
with open(self.ip_cache, 'r') as fh:
self.cached_ips = [(datetime.datetime.fromtimestamp(i[0]),
2020-05-14 23:46:03 -04:00
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)
2020-05-12 03:26:44 -04:00
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')
2020-05-14 23:46:03 -04:00
self.my_ip = tunnel.IP4(req.json()['ip'], 32)
2020-05-12 03:26:44 -04:00
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')
2020-05-14 23:46:03 -04:00
self.my_ip = tunnel.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))
chk_tuple = (datetime.datetime.utcnow(), self.my_ip)
2020-05-15 01:48:26 -04:00
if len(self.cached_ips) >= 1 and self.my_ip.str != self.cached_ips[-1][1].str:
self.needs_update = True
2020-05-15 01:53:37 -04:00
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))
2020-05-12 03:26:44 -04:00
return(None)
2020-05-12 04:11:58 -04:00
def start(self):
self._get_my_ip()
if any((self.force_update, self.needs_update)):
logger.debug('IP update forced or needed; updating.')
2020-05-12 04:11:58 -04:00
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,
2020-05-15 02:48:55 -04:00
sit_remote = self.tun.endpoint.str,
2020-05-12 04:11:58 -04:00
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} '
2020-05-15 02:47:08 -04:00
'(maybe it already exists?) with local {1} and remote {2}: {3}'.format(self.iface_name,
self.my_ip.str,
2020-05-15 02:48:55 -04:00
self.tun.endpoint.str,
2020-05-15 02:47:08 -04:00
e))
2020-05-12 04:11:58 -04:00
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
2020-05-12 23:12:55 -04:00
try:
ipr.route('add',
dst = 'default',
2020-05-13 21:10:09 -04:00
gateway = self.tun.server.str,
2020-05-12 23:12:55 -04:00
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} with '
'gateway {1}: {2}').format(self.iface_name, self.tun.server.str, e))
2020-05-12 23:12:55 -04:00
ipr.close()
raise e
2020-05-13 21:10:09 -04:00
for assignment in self.tun.assignments:
# The SLAAC prefixes.
for b in assignment.iface_blocks:
2020-05-15 03:20:04 -04:00
# Try to remove first in case it's already assigned.
2020-05-15 03:17:53 -04:00
try:
ipr.addr('del',
index = assignment.iface_idx,
2020-05-15 03:23:53 -04:00
address = str(b.ip),
2020-05-15 03:17:53 -04:00
mask = b.prefixlen,
family = socket.AF_INET6)
2020-05-15 03:20:04 -04:00
logger.debug('Removed {0} with prefix {1} from {2}.'.format(str(b), b.prefixlen, assignment.iface))
2020-05-15 03:17:53 -04:00
except Exception as e:
pass
2020-05-13 21:10:09 -04:00
try:
ipr.addr('add',
index = assignment.iface_idx,
2020-05-15 03:23:53 -04:00
address = str(b.ip),
2020-05-13 21:10:09 -04:00
mask = b.prefixlen,
family = socket.AF_INET6)
2020-05-15 03:23:53 -04:00
logger.debug('Added {0} with prefix {1} to {2}.'.format(str(b.ip), b.prefixlen, assignment.iface))
2020-05-13 21:10:09 -04:00
except Exception as e:
2020-05-15 03:23:53 -04:00
logger.error(('Could not add address block {0} with prefix {1} on {2}: '
'{3}').format(str(b.ip), b.prefixlen, assignment.iface, e))
2020-05-13 21:10:09 -04:00
ipr.close()
raise e
2020-05-12 04:11:58 -04:00
ipr.close()
2020-05-18 05:48:40 -04:00
if self.tun.ra:
self.tun.ra.conf.write()
self.tun.ra.svc.restart()
2020-05-12 04:11:58 -04:00
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',
2020-05-12 23:12:55 -04:00
# gateway = self.def_rt_ip,
2020-05-12 20:03:18 -04:00
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()
2020-05-18 06:11:14 -04:00
self.tun.ra.svc.stop()
2020-05-12 18:13:42 -04:00
return(None)
2020-05-12 04:11:58 -04:00
def update(self):
if not self.my_ip:
self._get_my_ip()
2020-05-15 02:15:45 -04:00
if not self.needs_update:
return(None)
2020-05-15 01:49:23 -04:00
auth_handler = requests.auth.HTTPBasicAuth(self.tun.creds.user, self.tun.update_key)
2020-05-12 18:13:42 -04:00
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.str},
2020-05-12 18:13:42 -04:00
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)