From 00a0fc1fcaf32e4bf937b35e543aea70caf1be26 Mon Sep 17 00:00:00 2001 From: "black.box (Unzoner) team@belodetek.io" Date: Fri, 1 Dec 2023 15:09:41 -0800 Subject: [PATCH] fix: reset loop if any tunnel interface is not passing traffic --- unzoner/.balena/balena.yml | 6 +- unzoner/requirements.txt | 1 - unzoner/src/main.py | 410 ++----- unzoner/src/tests/requirements.txt | 1 - unzoner/src/utils.py | 41 +- unzoner/src/vpn.py | 1644 +++++++++++++--------------- 6 files changed, 904 insertions(+), 1199 deletions(-) diff --git a/unzoner/.balena/balena.yml b/unzoner/.balena/balena.yml index 8e8bba7..6a16100 100644 --- a/unzoner/.balena/balena.yml +++ b/unzoner/.balena/balena.yml @@ -7,14 +7,14 @@ build-variables: - BUILD_BIRD=0 # https://github.com/openssl/openssl - BUILD_OPENSSL_VERSION=3.0.5 - - BUILD_OPENSSL=1 + - BUILD_OPENSSL=0 # https://gcc.gnu.org/onlinedocs/gcc/AArch64-Options.html - AARCH64_OPTIMISE_FLAGS=-O3 -mtune=cortex-a72 -march=armv8-a # https://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html - ARM_OPTIMISE_FLAGS=-O3 -mtune=cortex-a53 -mcpu=cortex-a53+crypto -mfpu=crypto-neon-fp-armv8 # https://github.com/OpenVPN/openvpn - BUILD_OPENVPN_VERSION=2.5.7 - - BUILD_OPENVPN=1 + - BUILD_OPENVPN=0 # FIXME: https://sources.debian.org/patches/sniproxy/0.6.0-2/ - BUILD_SNIPROXY_VERSION=0.6.0 - BUILD_SNIPROXY=0 @@ -22,7 +22,7 @@ build-variables: - BUILD_WANPROXY_VERSION=0.8.0 - BUILD_WANPROXY=0 # https://nuitka.net/ - - COMPILE_CODE=1 + - COMPILE_CODE=0 # (e.g.) dig +short us.{{ DNS_SUB_DOMAIN }}.{{ DNS_DOMAIN }} - DNS_SUB_DOMAIN=blackbox diff --git a/unzoner/requirements.txt b/unzoner/requirements.txt index 2497877..710d699 100644 --- a/unzoner/requirements.txt +++ b/unzoner/requirements.txt @@ -2,6 +2,5 @@ Pyjwt pyasn ipaddr requests[security] -pingparsing dnspython passlib diff --git a/unzoner/src/main.py b/unzoner/src/main.py index cdbce1f..417e7dd 100755 --- a/unzoner/src/main.py +++ b/unzoner/src/main.py @@ -23,8 +23,6 @@ def main(): kill_thread = Thread() connected = False connecting = False - c_gwip = None - c_loss = 0 c_proc = None c_proto = None c_pid = None @@ -50,8 +48,6 @@ def main(): w_clnts = 0 started = [False, False] # [udp, tcp] starting = [False, False] - s_local = [None, None] - s_loss = [0, 0] s_conns = [0, 0] s_proc = [None, None] s_pid = [None, None] @@ -61,6 +57,7 @@ def main(): s_lineout = [None, None] s_lineerr = [None, None] s_status_line = None + this = stack()[0][3] if SUPPRESS_TS: p1 = re.compile('^(.*)$') @@ -70,42 +67,28 @@ def main(): ) group = p1.groups - 1 - if bool(re.search('^2\.3\.', OPENVPN_VERSION)): - p2 = re.compile( - '^.* dev ([\w\d]+) local ([\d]+\.[\d]+\.[\d]+\.[\d]+) peer ([\d]+\.[\d]+\.[\d]+\.[\d]+)$' - ) - - if bool(re.search('^2\.[4-5]\.', OPENVPN_VERSION)): - p2 = re.compile( - '^.*ifconfig ([\w\d]+) ([\d]+\.[\d]+\.[\d]+\.[\d]+) pointopoint ([\d]+\.[\d]+\.[\d]+\.[\d]+) mtu [\d]+$' - ) - - if DEVICE_TYPE == 5: - p2 = re.compile( - '^.*get_ping_host: route_network_[0-9]+=([\d]+\.[\d]+\.[\d]+\.[\d]+)$' - ) - p3 = re.compile('^.*remote=(.*) country=(.*)$') + p2 = re.compile('^.*remote=(.*) country=(.*)$') # hdparm tests - p4 = re.compile( + p3 = re.compile( '^\s+(.*):\s+(\d+\s+.*)\s+in\s+([\d\.]+\s+.*)\s+=\s+(.*)\n$' ) # dd write - p5 = re.compile( + p4 = re.compile( '^(\d+\s+.*)\s+\((.*),.*\)\s+.*,\s+(.*),\s+(.*)$' ) mgmt_ipaddr = None if TUN_MGMT: mgmt_ipaddr = get_ip_address(MGMT_IFACE) + if DEBUG: print('os.environ: {}'.format(os.environ)) + while True: if DEVICE_TYPE == 0: - log('{}: device={}'.format(stack()[0][3], GUID)) + log('{}: device={}'.format(this, GUID)) sys.exit(0) - if DEBUG: print('os.environ: {}'.format(os.environ)) - for i in range(1, LOOP_CYCLE + 1): ########################### # server mode(s) or mixed # @@ -119,9 +102,7 @@ def main(): s_lineerr = [None, None] if s_stderrq[idx]: try: - s_lineerr[idx] = s_stderrq[idx].get( - timeout=LOOP_TIMER - ).decode() + s_lineerr[idx] = s_stderrq[idx].get(timeout=LOOP_TIMER).decode() log('{}: {}'.format(proto, s_lineerr[idx])) except Empty: pass @@ -132,9 +113,7 @@ def main(): if s_stdoutq[idx]: try: - s_lineout[idx] = s_stdoutq[idx].get( - timeout=LOOP_TIMER - ).decode() + s_lineout[idx] = s_stdoutq[idx].get(timeout=LOOP_TIMER).decode() log('{}: {}'.format(proto, s_lineout[idx])) except Empty: pass @@ -150,78 +129,37 @@ def main(): try: log_server_stats(status=started) except: + print('exception-handler in {}: {}'.format(this, repr(e))) if DEBUG: print_exc() - try: - m2 = p2.search(s_msg[idx]).groups() - dev = m2[0] - s_local[idx] = m2[1] - s_peer = m2[2] - log( - '{}: iface={} local={} peer={} proto={}'.format( - stack()[0][3], - dev, - s_local[idx], - s_peer, - proto - ) - ) - except (IndexError, TypeError, AttributeError): - pass - - if not started[idx] and starting[idx]: - if i % LOOP_CYCLE == 0 and not started[idx]: - log( - '{}: cycle={} started={} starting={} proto={} s_pid={}'.format( - stack()[0][3], - i, - started[idx], - starting[idx], - proto, - s_pid[idx] - ) - ) - started[idx] = False - starting[idx] = False - s_proc[idx].terminate() - s_pid[idx] = None if not started[idx] and not starting[idx]: if i == 1: # once per loop (start) starting[idx] = True - log( - '{}: cycle={} starting={} proto={}'.format( - stack()[0][3], - i, - starting[idx], - proto - ) - ) + log('{}: cycle={} starting={} proto={}'.format( + this, + i, + starting[idx], + proto + )) try: - (s_stdoutq[idx], s_stderrq[idx], s_proc[idx]) = start_server( - proto=proto - ) + (s_stdoutq[idx], s_stderrq[idx], s_proc[idx]) = start_server(proto=proto) s_pid[idx] = s_proc[idx].pid except Exception as e: starting[idx] = False - print(repr(e)) + print('exception-handler in {}: {}'.format(this, repr(e))) if DEBUG: print_exc() if started[idx] and not starting[idx]: if not kill_thread.is_alive(): - kill_thread = Thread( - target=disconnect_clients, - args=() - ) + kill_thread = Thread(target=disconnect_clients, args=()) kill_thread.daemon = True kill_thread.start() - log( - 'disconnect_clients: name={} alive={} daemon={}'.format( - kill_thread.name, - kill_thread.is_alive(), - kill_thread.daemon - ) - ) + log('disconnect_clients: name={} alive={} daemon={}'.format( + kill_thread.name, + kill_thread.is_alive(), + kill_thread.daemon + )) if not auth_thread.is_alive(): if BITCOIN_PAYMENT_CHECK: @@ -231,49 +169,19 @@ def main(): ) auth_thread.daemon = True auth_thread.start() - log( - 'reauthenticate_clients: name={} alive={} daemon={}'.format( - auth_thread.name, - auth_thread.is_alive(), - auth_thread.daemon - ) - ) + log('(re)authenticate_clients: name={} alive={} daemon={}'.format( + auth_thread.name, + auth_thread.is_alive(), + auth_thread.daemon + )) if i == 1 or i % LOOP_CYCLE == 0: # twice per loop (start and end) - if DEBUG: log( - 'get_status: cycle={} proto={} status={}'.format( - i, - proto, - get_status(proto=proto) - ) - ) - s_conns[idx] = int( - get_server_conns_by( - proto=proto - ) - ) - - if i % POLL_FREQ == 0 and s_local[idx]: # every x cycles per loop - s_loss[idx] = 100 - try: - s_loss[idx] = ping_host(host=s_local[idx]) - except AssertionError as e: - print(repr(e)) - if DEBUG: print_exc() - log( - '{}: cycle={} proto={} s_local={} s_loss={}'.format( - i, - stack()[0][3], - proto, - s_local[idx], - s_loss[idx] - ) - ) - started[idx] = False - starting[idx] = False - s_proc[idx].terminate() - s_loss[idx] = 0 - break + if DEBUG: log('event-loop-status: cycle={} proto={} status={}'.format( + i, + proto, + get_status(proto=proto) + )) + s_conns[idx] = int(get_server_conns_by(proto=proto)) # flush the stderr queue if not debugging if not DEBUG and s_stderrq[idx]: @@ -281,58 +189,32 @@ def main(): s_stderrq[idx].queue.clear() if i % LOOP_CYCLE == 0: - if DEVICE_TYPE == 3: # test if double-vpn is up + if DEVICE_TYPE == 3 and connected: # test if double-vpn is up try: - shell_check_output_cmd( - 'ip link | grep %s' % TUN_IFACE - ) - gwip = shell_check_output_cmd( - "ip route | grep %s | grep -E '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ via [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ dev %s' | awk '{print $1}'" % ( - TUN_IFACE, - TUN_IFACE - ) - ) - log( - 'iface={} gwip={}'.format( - TUN_IFACE, - gwip - ) - ) - ping_host( - host=gwip.strip('\n'), - timeout=1, - count=3 - ) - try: - # server up only if double-vpn is up - log_server_stats(status=started) - except: - if DEBUG: print_exc() + geo_result = get_geo_location() + print('double-vpn: {}'.format(geo_result)) + assert geo_result, '{}: double-vpn not ready'.format(this) + log_server_stats(status=started) # server up only if double-vpn is up except: + print('exception-handler in {}: {}'.format(this, repr(e))) if DEBUG: print_exc() - log( - '{}: double-vpn not ready'.format( - stack()[0][3] - ) - ) + try: - # force server down if double-vpn is down - log_server_stats() + log_server_stats() # force server down if double-vpn is down except: + print('exception-handler in {}: {}'.format(this, repr(e))) if DEBUG: print_exc() else: try: log_server_stats(status=started) except: + print('exception-handler in {}: {}'.format(this, repr(e))) if DEBUG: print_exc() - s_status_line = '{}: cycle={} started={} starting={} ip={} loss={} pid={} conns={} mgmt_tun={} af={} hostapd={} upnp={}'.format( - stack()[0][3], + s_status_line = 'server-status: cycle={} started={} starting={} s_pid={} s_conns={} mgmt_ipaddr={} AF={} hostAPd={} UPNP={}'.format( i, started, starting, - s_local, - s_loss, s_pid, s_conns, mgmt_ipaddr, @@ -348,13 +230,11 @@ def main(): if DEVICE_TYPE in [2, 3, 5]: # iperf timeout if c_stp and now - c_stimer > (LOOP_TIMER * LOOP_CYCLE * 5): - log( - '{}: pid={} elapsed={}'.format( - stack()[0][3], - c_stp.pid, - now - c_stimer - ) - ) + log('{}: pid={} elapsed={}'.format( + this, + c_stp.pid, + now - c_stimer + )) c_stp.terminate() c_stp = None c_stq = None @@ -371,19 +251,17 @@ def main(): res = update_speedtest(data=result) log('update_speedtest: {}'.format(res)) except Exception as e: - print(repr(e)) + print('exception-handler in {}: {}'.format(this, repr(e))) if DEBUG: print_exc() # hdparm or dd timeout if c_iotp and now - c_iotimer > (LOOP_TIMER * LOOP_CYCLE * 3): - log( - '{}: pid={} elapsed={} test={}'.format( - stack()[0][3], - c_iotp.pid, - now - c_iotimer, - c_iott - ) - ) + log('{}: pid={} elapsed={} test={}'.format( + this, + c_iotp.pid, + now - c_iotimer, + c_iott + )) try: result = { 'status': 1, @@ -392,14 +270,12 @@ def main(): } log('run_iotest: result={}'.format(result)) res = update_iotest(data=result) - log( - 'update_iotest({}): {}'.format( - res[0], - res[1] - ) - ) + log('update_iotest({}): {}'.format( + res[0], + res[1] + )) except Exception as e: - print(repr(e)) + print('exception-handler in {}: {}'.format(this, repr(e))) if DEBUG: print_exc() c_iotp.terminate() c_iotp = None @@ -461,7 +337,7 @@ def main(): res = update_speedtest(data=result) log('update_speedtest: {}'.format(res)) except Exception as e: - print(repr(e)) + print('exception-handler in {}: {}'.format(this, repr(e))) if DEBUG: print_exc() c_str = list() @@ -475,7 +351,7 @@ def main(): try: c_iotl = c_iotq.get(timeout=LOOP_TIMER).decode() log(c_iotl) - if p4.search(c_iotl) or p5.search(c_iotl): + if p3.search(c_iotl) or p4.search(c_iotl): result = { 'status': 0, 'test': c_iott, @@ -484,14 +360,9 @@ def main(): log('run_iotest: result={}'.format(result)) try: res = update_iotest(data=result) - log( - 'update_iotest({}): {}'.format( - res[0], - res[1] - ) - ) + log('update_iotest({}): {}'.format(res[0], res[1])) except Exception as e: - print(repr(e)) + print('exception-handler in {}: {}'.format(this, repr(e))) if DEBUG: print_exc() c_iotimer = 0 c_iott = None @@ -507,92 +378,37 @@ def main(): except (IndexError, TypeError, AttributeError): pass - if c_msg in [ - 'SIGTERM[soft,tls-error] received, process exiting', - 'SIGUSR1[soft,init_instance] received, process restarting', - 'SIGUSR1[soft,connection-reset] received, process restarting', - 'SIGTERM[hard,] received, process exiting', - 'Exiting due to fatal error' - ]: - log('terminate: c_pid={}'.format(c_pid)) - connected = False - connecting = False - c_proc.terminate() - c_pid = None - if c_msg in ['Initialization Sequence Completed']: connected = True connecting = False try: - log_client_stats( - status=connected, - country=c_country - ) + log_client_stats(status=connected, country=c_country) except: + print('exception-handler in {}: {}'.format(this, repr(e))) if DEBUG: print_exc() - try: - m2 = p2.search(c_msg).groups() - dev = m2[0] - c_local = m2[1] - c_peer = m2[2] - c_gwip = c_peer.split('.') - c_gwip[3] = '1' - c_gwip = '.'.join(c_gwip) - log( - '{}: iface={} local={} peer={} gwip={} family={}'.format( - stack()[0][3], - dev, - c_local, - c_peer, - c_gwip, - AF - ) - ) - except (IndexError, TypeError, AttributeError): - pass if DEVICE_TYPE == 5: try: - m3 = p3.search(c_msg).groups() + m3 = p2.search(c_msg).groups() c_remote = m3[0] c_country = m3[1] - log( - '{}: remote={} country={}'.format( - stack()[0][3], - c_remote, - c_country - ) - ) + log('{}: remote={} country={}'.format( + this, + c_remote, + c_country + )) except (IndexError, TypeError, AttributeError): pass - if not connected and connecting: - if i % LOOP_CYCLE == 0 and not connected: - log( - '{}: cycle={} connected={} connecting={} c_pid={}'.format( - stack()[0][3], - i, - connected, - connecting, - c_pid - ) - ) - connected = False - connecting = False - c_proc.terminate() - c_pid = None - if not connected and not connecting: if i == 1: connecting = True - log( - '{}: cycle={} connecting={} family={}'.format( - stack()[0][3], - i, - connecting, - AF - ) - ) + log('{}: cycle={} connecting={} family={}'.format( + this, + i, + connecting, + AF + )) try: (c_stdoutq, c_stderrq, c_proc, c_proto) = connect_node( family=AF @@ -600,7 +416,7 @@ def main(): c_pid = c_proc.pid except AssertionError as e: connecting = False - print(repr(e)) + print('exception-handler in {}: {}'.format(this, repr(e))) if DEBUG: print_exc() if connected and not connecting: @@ -612,16 +428,14 @@ def main(): c_str = list() (c_stq, c_stp) = run_speedtest() except Exception as e: - print(repr(e)) + print('exception-handler in {}: {}'.format(this, repr(e))) if DEBUG: print_exc() if c_stimer: - log( - 'run_speedtest: pid={} elapsed={} results={}'.format( - c_stp.pid, - now - c_stimer, - len(c_str) - ) - ) + log('run_speedtest: pid={} elapsed={} results={}'.format( + c_stp.pid, + now - c_stimer, + len(c_str) + )) # run hdparm or dd try: @@ -635,57 +449,43 @@ def main(): c_iotimer = now (c_iotq, c_iotp) = run_iotest(test=c_iott) except Exception as e: - print(repr(e)) + print('exception-handler in {}: {}'.format(this, repr(e))) if DEBUG: print_exc() if c_iotimer: - log( - 'run_iotest: pid={} elapsed={} test={} result={}'.format( - c_iotp.pid, - now - c_iotimer, - c_iott, - c_iotl - ) - ) - - if i % POLL_FREQ == 0 and c_gwip: - c_loss = 100 + log('run_iotest: pid={} elapsed={} test={} result={}'.format( + c_iotp.pid, + now - c_iotimer, + c_iott, + c_iotl + )) + + # check if client VPN tunnel is still passing traffic + if i % POLL_FREQ == 0: # every x cycles per loop try: - c_loss = ping_host(host=c_gwip) + geo_result = get_geo_location() + print('client-vpn: {}'.format(geo_result)) + assert geo_result, '{}: client tunnel not passing traffic'.format(this) except AssertionError as e: - print(repr(e)) + print('exception-handler in {}: {}'.format(this, repr(e))) if DEBUG: print_exc() - log( - '{}: c_gwip={} c_loss={}'.format( - stack()[0][3], - c_gwip, - c_loss - ) - ) connected = False connecting = False c_proc.terminate() - c_loss = 0 c_pid = None - break if i % LOOP_CYCLE == 0: try: - log_client_stats( - status=connected, - country=c_country - ) + log_client_stats(status=connected, country=c_country) except: + print('exception-handler in {}: {}'.format(this, repr(e))) if DEBUG: print_exc() w_clnts = get_stations() - c_status_line = '{}: cycle={} connected={} connecting={} proto={} ip={} loss={} pid={} clients={} mgmt_tun={} af={} hostapd={} upnp={}'.format( - stack()[0][3], + c_status_line = 'client-status: cycle={} connected={} connecting={} c_proto={} c_pid={} w_clnts={} mgmt_ipaddr={} AF={} hostAPd={} UPNP={}'.format( i, connected, connecting, c_proto, - c_gwip, - c_loss, c_pid, w_clnts, mgmt_ipaddr, diff --git a/unzoner/src/tests/requirements.txt b/unzoner/src/tests/requirements.txt index ec67947..ac7c18c 100644 --- a/unzoner/src/tests/requirements.txt +++ b/unzoner/src/tests/requirements.txt @@ -2,7 +2,6 @@ Pyjwt pyasn ipaddr requests[security] -pingparsing dnspython passlib diff --git a/unzoner/src/utils.py b/unzoner/src/utils.py index fb63124..2665ff7 100755 --- a/unzoner/src/utils.py +++ b/unzoner/src/utils.py @@ -9,7 +9,6 @@ import json import jwt import dns.resolver -import pingparsing from time import time, sleep from inspect import stack @@ -22,26 +21,6 @@ from config import * -def ping_host(host='localhost', timeout=PING_TIMEOUT, count=PING_COUNT): - ping_parser = pingparsing.PingParsing() - transmitter = pingparsing.PingTransmitter() - transmitter.destination = host - transmitter.count = count - result = transmitter.ping() - ping_stats = ping_parser.parse(result).as_dict() - if DEBUG: print( - '{}: ping_stats={}'.format( - stack()[0][3], - ping_stats - ) - ) - assert ping_stats['packet_loss_rate'] < THRESHOLD, '{}: timeout={}'.format( - stack()[0][3], - host - ) - return ping_stats['packet_loss_rate'] - - def shell_check_output_cmd(cmd): if DEBUG: print('{}: cmd={}'.format(stack()[0][3], cmd)) return check_output( @@ -153,7 +132,7 @@ def get_hostname(): hostname = 'localhost' try: hostname = socket.gethostname() - except Exception: + except: pass return hostname @@ -172,9 +151,7 @@ def get_geo_location(family=AF): # get the VPN server location, not the location of the device if DEVICE_TYPE in [3, 5]: try: - shell_check_output_cmd( - 'ip link | grep %s' % TUN_IFACE - ) + shell_check_output_cmd('ip link | grep %s' % TUN_IFACE) cmd.append('--interface') cmd.append(TUN_IFACE) except: @@ -186,14 +163,12 @@ def get_geo_location(family=AF): assert result[0] == 0 data = json.loads(result[1]) except Exception as e: - print( - '{}: family={} data={} e={}'.format( - stack()[0][3], - family, - data, - repr(e) - ) - ) + print('{}: family={} data={} e={}'.format( + stack()[0][3], + family, + data, + repr(e) + )) return data diff --git a/unzoner/src/vpn.py b/unzoner/src/vpn.py index f772f5e..8ce9662 100755 --- a/unzoner/src/vpn.py +++ b/unzoner/src/vpn.py @@ -11,7 +11,7 @@ from time import sleep from inspect import stack from traceback import print_exc -from threading import Thread +from threading import Thread from subprocess import Popen, PIPE from queue import Queue @@ -38,953 +38,885 @@ @retry(Exception, cdata='method={}'.format(stack()[0][3])) def run_openvpn_mgmt_cmd(host=VPN_HOST, port=VPN_UDP_MGMT_PORT, cmd='status 2'): - s = None - data = str() - header = None - - try: - for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM): - af, socktype, proto, canonname, sa = res - if DEBUG: log( - 'getaddrinfo: res={} af={} socktype={} proto={} canonname={} sa={}'.format( - res, - af, - socktype, - proto, - canonname, - sa - ) - ) - try: - s = socket.socket(af, socktype, proto) - except socket.error as msg: - log( - 'socket: msg={} af={} socktype={} proto={} canonname={} sa={}'.format( - msg, - af, - socktype, - proto, - canonname, - sa - ) - ) - s = None - continue - try: - s.connect(sa) - except socket.error as msg: - s.close() - s = None - continue - break - - header = recv_with_timeout(s) - if DEBUG: print(header) - s.sendall(b'%s\n' % cmd) - data = recv_with_timeout(s) - if DEBUG: print(data) - s.sendall(b'quit\n') - s.close() - except: - pass - return data.split('\r\n') + s = None + data = str() + header = None + + try: + for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + if DEBUG: log('getaddrinfo: res={} af={} socktype={} proto={} canonname={} sa={}'.format( + res, + af, + socktype, + proto, + canonname, + sa + )) + try: + s = socket.socket(af, socktype, proto) + except socket.error as msg: + log('socket: msg={} af={} socktype={} proto={} canonname={} sa={}'.format( + msg, + af, + socktype, + proto, + canonname, + sa + )) + s = None + continue + try: + s.connect(sa) + except socket.error as msg: + s.close() + s = None + continue + break + + header = recv_with_timeout(s) + if DEBUG: print('header: {}', format(header)) + s.sendall(b'{}\n'.format(cmd)) + data = recv_with_timeout(s) + if DEBUG: print('data: {}', format(data)) + s.sendall(b'quit\n') + s.close() + except: + pass + return data.split('\r\n') @retry(Exception, cdata='method={}'.format(stack()[0][3])) def get_openvpn_version(default='2.3.10'): - version = default - try: - version = run_shell_cmd(['/usr/bin/env', 'openvpn', '--version'])[1].split()[1] - except: - pass - return version + version = default + try: + version = run_shell_cmd(['/usr/bin/env', 'openvpn', '--version'])[1].split()[1] + except: + pass + return version @retry(Exception, cdata='method={}'.format(stack()[0][3])) def get_openvpn_binary(default='/usr/sbin/openvpn'): - binary = default - try: - binary = run_shell_cmd(['/usr/bin/which', 'openvpn'])[1].split()[0] - except: - pass - return binary + binary = default + try: + binary = run_shell_cmd(['/usr/bin/which', 'openvpn'])[1].split()[0] + except: + pass + return binary @retry(Exception, cdata='method={}'.format(stack()[0][3])) def connect_node(family=AF): - global PROTOS - global c_proto - global c_wpid - global s_wpid - global s_wport - global ipaddr - global country - global host - global qtype - - try: - log('os.kill: {}'.format(c_wpid)) - os.kill(int(c_wpid), signal.SIGKILL) - except: - pass - - c_wpid = None - - try: - log( - 'kill_remote_pid: {}'.format( - kill_remote_pid( - ipaddr=ipaddr, - family=family, - pid=s_wpid - ) - ) - ) - except: - pass - - s_wpid = None - s_wport = None - ipaddr = None - - if len(PROTOS) <= 0: PROTOS = list(TUN_PROTO) # start again - c_proto = PROTOS.pop() - tun_proto = c_proto - - env = os.environ.copy() - if DEBUG: print(env) - - qtype = 'A' - if family == 6: - if get_geo_location(family=6): - qtype = 'AAAA' - else: - family = 4 - - cmd = [OPENVPN_BINARY, '--config', CLIENT_CONFIG, '--dev', TUN_IFACE] - port = OPENVPN_PORT - - ############ - # VPN mode # - ############ - if DEVICE_TYPE == 5 or ( - DEVICE_TYPE == 3 - and VPN_PROVIDER - and VPN_LOCATION_GROUP - and VPN_LOCATION - and VPN_USERNAME - and VPN_PASSWD - ): - log( - '{}: mode={} provider={} group={} location={} username={} password={}'.format( - stack()[0][3], - DEVICE_TYPE, - VPN_PROVIDER, - VPN_LOCATION_GROUP, - VPN_LOCATION, - VPN_USERNAME, - VPN_PASSWD - ) - ) - - assert ( - VPN_PROVIDER - and VPN_LOCATION_GROUP - and VPN_LOCATION - and VPN_USERNAME - and VPN_PASSWD - ) - - else: - ######################### - # pairing mode override # - ######################### - if PAIRED_DEVICE_GUID: - try: - ipaddr = get_node_by_guid(family=family) - log( - 'get_node_by_guid: ipaddr={} af={} guid={}'.format( - ipaddr, - family, - PAIRED_DEVICE_GUID - ) - ) - assert ipaddr - except: - if DEBUG: print_exc() - try: - assert family == 6 # fall-back to IPv4 if not already - ipaddr = get_node_by_guid(family=4) - log( - 'get_node_by_guid: ipaddr={} af={} guid={}'.format( - ipaddr, - 4, - PAIRED_DEVICE_GUID - ) - ) - assert ipaddr - except AssertionError as e: - if DEBUG: print_exc() - log( - 'get_node_by_guid: e={} af={}'.format( - repr(e), - family - ) - ) - - ################### - # unblocking mode # - ################### - else: - if family == 6 and not SKIP_DNS: - try: - # try DNS resolution (IPv6) - country = get_alpha(TARGET_COUNTRY).lower() - host = '{}.{}'.format(country, DNS_HOST) - ipaddr = resolve_dns(host=host, record=qtype, family=family) - assert ipaddr - except Exception as e: - log( - 'resolve_dns: e={} qtype={} family={}'.format( - repr(e), - qtype, - family - ) - ) - family = 4 - qtype = 'A' - if DEBUG: print_exc() - - if family == 4 and not SKIP_DNS: - try: - # try DNS resolution (IPv4) - country = get_alpha(TARGET_COUNTRY).lower() - host = '{}.{}'.format(country, DNS_HOST) - ipaddr = resolve_dns(host=host, record=qtype, family=family) - assert ipaddr - except Exception as e: - log( - 'resolve_dns: e={} qtype={} family={}'.format( - repr(e), - qtype, - family - ) - ) - if DEBUG: print_exc() - - log( - '{}: qtype={} country={} host={} ipaddr={}'.format( - stack()[0][3], - qtype, - country, - host, - ipaddr - ) - ) - - if not ipaddr: - if family == 6: - try: - # fall-back to API (IPv6) - ipaddr = get_node_by_country(family=family) - log( - 'get_node_by_country: ipaddr={} af={}'.format( - stack()[0][3], - ipaddr, - family - ) - ) - except AssertionError as e: - log( - 'get_node_by_country: e={} af={}'.format( - repr(e), - family - ) - ) - if DEBUG: print_exc() - family = 4 - qtype = 'A' - - if family == 4: - try: - # fall-back to API (IPv4) - ipaddr = get_node_by_country(family=family) - log( - 'get_node_by_country: ipaddr={} af={}'.format( - ipaddr, - family - ) - ) - except AssertionError as e: - log( - 'get_node_by_country: e={} af={}'.format( - repr(e), - family - ) - ) - if DEBUG: print_exc() - - assert ipaddr - - if family == 6: tun_proto = '{}{}'.format(c_proto, family) - log( - '{}: mode={} remote={} port={} proto={} protos={} cipher={} auth={}'.format( - stack()[0][3], - DEVICE_TYPE, - ipaddr, - port, - tun_proto, - TUN_PROTO, - CIPHER, - AUTH - ) - ) - - ######################################## - # stunnel override for unblocking mode # - ######################################## - if STUNNEL: - result = conf_stunnel_client(node=ipaddr) - if DEBUG: print(result) - - result = restart_stunnel_client() - if DEBUG: print(result) - - if result[0] == 0: - ipaddr = 'localhost' - tun_proto = 'tcp' - if family == 6: tun_proto = '{}{}'.format(tun_proto, family) - log( - '{}: stunnel={} proto={} ipaddr={}'.format( - stack()[0][3], - STUNNEL, - tun_proto, - ipaddr - ) - ) - - ############################## - # (legacy) WANProxy override # - ############################## - if WANPROXY: - (c_wpid, s_wpid, s_wport) = start_wanproxy_server( - ipaddr=ipaddr, - family=family, - local_pid=c_wpid, - remote_pid=s_wpid, - port=s_wport - ) - - log( - 'start_wanproxy_server: ipaddr={} af={} local_pid={} remote_pid={} remote_port={} transport={}'.format( - ipaddr, - family, - c_wpid, - s_wpid, - s_wport, - WANPROXY - ) - ) - - port = WANPROXY_PORT - ipaddr = 'localhost' - tun_proto = 'tcp' - if family == 6: tun_proto = '{}{}'.format(tun_proto, family) - log( - '{}: wan_proxy={} proto={} ipaddr={} port={}'.format( - stack()[0][3], - WANPROXY, - tun_proto, - ipaddr, - port - ) - ) - - if FRAGMENT and tun_proto == 'udp': - cmd.append('--tun-mtu') - cmd.append(TUN_MTU) - cmd.append('--mssfix') - cmd.append('--fragment') - cmd.append(FRAGMENT) - - if EXPLICIT_EXIT_NOTIFY in [1, 2] and tun_proto == 'udp': - cmd.append('--explicit-exit-notify') - cmd.append(str(EXPLICIT_EXIT_NOTIFY)) - - cmd.append('--remote') - cmd.append(ipaddr) - cmd.append(port) - cmd.append(tun_proto) - cmd.append('--cipher') - cmd.append(CIPHER) - cmd.append('--auth') - cmd.append(AUTH) - - #################################### - # stunnel override for mixed modes # - #################################### - if DEVICE_TYPE in [5, 3]: - if STUNNEL: - try: - ipaddr = REMOTE_OVERRIDE.split(' ')[1] - port = REMOTE_OVERRIDE.split(' ')[2] - tun_proto = REMOTE_OVERRIDE.split(' ')[3] - - log('{}: stunnel={} proto={} ipaddr={} port={}'.format( - stack()[0][3], - STUNNEL, - tun_proto, - ipaddr, - port - )) - - result = conf_stunnel_client(node=ipaddr, port=port) - if DEBUG: print(result) - - result = openvpn_remote_override() - if DEBUG: print(result) - - result = restart_stunnel_client() - if DEBUG: print(result) - except: - if DEBUG: print_exc() - - log( - '{}: ipaddr={} af={} qtype={} proto={} port={} country={} guid={} provider={} group={} location={} username={}'.format( - stack()[0][3], - ipaddr, - family, - qtype, - tun_proto, - port, - TARGET_COUNTRY, - PAIRED_DEVICE_GUID, - VPN_PROVIDER, - VPN_LOCATION_GROUP, - VPN_LOCATION, - VPN_USERNAME - ) - ) - - p = run_shell_cmd_nowait(cmd) - stdoutq = Queue() - stderrq = Queue() - stdoutt = Thread(target=enqueue_output, args=(p.stdout, stdoutq)) - stderrt = Thread(target=enqueue_output, args=(p.stderr, stderrq)) - stdoutt.daemon = True - stdoutt.start() - stderrt.daemon = True - stderrt.start() - return stdoutq, stderrq, p, c_proto + global PROTOS + global c_proto + global c_wpid + global s_wpid + global s_wport + global ipaddr + global country + global host + global qtype + + try: + log('os.kill: {}'.format(c_wpid)) + os.kill(int(c_wpid), signal.SIGKILL) + except: + pass + + c_wpid = None + + try: + log('kill_remote_pid: {}'.format( + kill_remote_pid( + ipaddr=ipaddr, + family=family, + pid=s_wpid + ) + )) + except: + pass + + s_wpid = None + s_wport = None + ipaddr = None + + if len(PROTOS) <= 0: PROTOS = list(TUN_PROTO) # start again + c_proto = PROTOS.pop() + tun_proto = c_proto + + env = os.environ.copy() + if DEBUG: print(env) + + qtype = 'A' + if family == 6: + if get_geo_location(family=6): + qtype = 'AAAA' + else: + family = 4 + + cmd = [OPENVPN_BINARY, '--config', CLIENT_CONFIG, '--dev', TUN_IFACE] + port = OPENVPN_PORT + + ############ + # VPN mode # + ############ + if DEVICE_TYPE == 5 or ( + DEVICE_TYPE == 3 + and VPN_PROVIDER + and VPN_LOCATION_GROUP + and VPN_LOCATION + and VPN_USERNAME + and VPN_PASSWD + ): + log('{}: mode={} provider={} group={} location={} username={} password={}'.format( + stack()[0][3], + DEVICE_TYPE, + VPN_PROVIDER, + VPN_LOCATION_GROUP, + VPN_LOCATION, + VPN_USERNAME, + VPN_PASSWD + )) + + assert ( + VPN_PROVIDER + and VPN_LOCATION_GROUP + and VPN_LOCATION + and VPN_USERNAME + and VPN_PASSWD + ) + + else: + ######################### + # pairing mode override # + ######################### + if PAIRED_DEVICE_GUID: + try: + ipaddr = get_node_by_guid(family=family) + log('get_node_by_guid: ipaddr={} af={} guid={}'.format( + ipaddr, + family, + PAIRED_DEVICE_GUID + )) + assert ipaddr + except: + if DEBUG: print_exc() + try: + assert family == 6 # fall-back to IPv4 if not already + ipaddr = get_node_by_guid(family=4) + log('get_node_by_guid: ipaddr={} af={} guid={}'.format( + ipaddr, + 4, + PAIRED_DEVICE_GUID + )) + assert ipaddr + except AssertionError as e: + if DEBUG: print_exc() + log('get_node_by_guid: e={} af={}'.format( + repr(e), + family + )) + + ################### + # unblocking mode # + ################### + else: + if family == 6 and not SKIP_DNS: + try: + # try DNS resolution (IPv6) + country = get_alpha(TARGET_COUNTRY).lower() + host = '{}.{}'.format(country, DNS_HOST) + ipaddr = resolve_dns(host=host, record=qtype, family=family) + assert ipaddr + except Exception as e: + log('resolve_dns: e={} qtype={} family={}'.format( + repr(e), + qtype, + family + )) + family = 4 + qtype = 'A' + if DEBUG: print_exc() + + if family == 4 and not SKIP_DNS: + try: + # try DNS resolution (IPv4) + country = get_alpha(TARGET_COUNTRY).lower() + host = '{}.{}'.format(country, DNS_HOST) + ipaddr = resolve_dns(host=host, record=qtype, family=family) + assert ipaddr + except Exception as e: + log('resolve_dns: e={} qtype={} family={}'.format( + repr(e), + qtype, + family + )) + if DEBUG: print_exc() + + log('{}: qtype={} country={} host={} ipaddr={}'.format( + stack()[0][3], + qtype, + country, + host, + ipaddr + )) + + if not ipaddr: + if family == 6: + try: + # fall-back to API (IPv6) + ipaddr = get_node_by_country(family=family) + log('get_node_by_country: ipaddr={} af={}'.format( + stack()[0][3], + ipaddr, + family + )) + except AssertionError as e: + log('get_node_by_country: e={} af={}'.format( + repr(e), + family + )) + if DEBUG: print_exc() + family = 4 + qtype = 'A' + + if family == 4: + try: + # fall-back to API (IPv4) + ipaddr = get_node_by_country(family=family) + log('get_node_by_country: ipaddr={} af={}'.format( + ipaddr, + family + )) + except AssertionError as e: + log('get_node_by_country: e={} af={}'.format( + repr(e), + family + )) + if DEBUG: print_exc() + + assert ipaddr + + if family == 6: tun_proto = '{}{}'.format(c_proto, family) + log('{}: mode={} remote={} port={} proto={} protos={} cipher={} auth={}'.format( + stack()[0][3], + DEVICE_TYPE, + ipaddr, + port, + tun_proto, + TUN_PROTO, + CIPHER, + AUTH + )) + + ######################################## + # stunnel override for unblocking mode # + ######################################## + if STUNNEL: + result = conf_stunnel_client(node=ipaddr) + if DEBUG: print(result) + + result = restart_stunnel_client() + if DEBUG: print(result) + + if result[0] == 0: + ipaddr = 'localhost' + tun_proto = 'tcp' + if family == 6: tun_proto = '{}{}'.format(tun_proto, family) + log('{}: stunnel={} proto={} ipaddr={}'.format( + stack()[0][3], + STUNNEL, + tun_proto, + ipaddr + )) + + ############################## + # (legacy) WANProxy override # + ############################## + if WANPROXY: + (c_wpid, s_wpid, s_wport) = start_wanproxy_server( + ipaddr=ipaddr, + family=family, + local_pid=c_wpid, + remote_pid=s_wpid, + port=s_wport + ) + + log('start_wanproxy_server: ipaddr={} af={} local_pid={} remote_pid={} remote_port={} transport={}'.format( + ipaddr, + family, + c_wpid, + s_wpid, + s_wport, + WANPROXY + )) + + port = WANPROXY_PORT + ipaddr = 'localhost' + tun_proto = 'tcp' + if family == 6: tun_proto = '{}{}'.format(tun_proto, family) + log('{}: wan_proxy={} proto={} ipaddr={} port={}'.format( + stack()[0][3], + WANPROXY, + tun_proto, + ipaddr, + port + )) + + if FRAGMENT and tun_proto == 'udp': + cmd.append('--tun-mtu') + cmd.append(TUN_MTU) + cmd.append('--mssfix') + cmd.append('--fragment') + cmd.append(FRAGMENT) + + if EXPLICIT_EXIT_NOTIFY in [1, 2] and tun_proto == 'udp': + cmd.append('--explicit-exit-notify') + cmd.append(str(EXPLICIT_EXIT_NOTIFY)) + + cmd.append('--remote') + cmd.append(ipaddr) + cmd.append(port) + cmd.append(tun_proto) + cmd.append('--cipher') + cmd.append(CIPHER) + cmd.append('--auth') + cmd.append(AUTH) + + #################################### + # stunnel override for mixed modes # + #################################### + if DEVICE_TYPE in [5, 3]: + if STUNNEL: + try: + ipaddr = REMOTE_OVERRIDE.split(' ')[1] + port = REMOTE_OVERRIDE.split(' ')[2] + tun_proto = REMOTE_OVERRIDE.split(' ')[3] + + log('{}: stunnel={} proto={} ipaddr={} port={}'.format( + stack()[0][3], + STUNNEL, + tun_proto, + ipaddr, + port + )) + + result = conf_stunnel_client(node=ipaddr, port=port) + if DEBUG: print(result) + + result = openvpn_remote_override() + if DEBUG: print(result) + + result = restart_stunnel_client() + if DEBUG: print(result) + except: + if DEBUG: print_exc() + + log('{}: ipaddr={} af={} qtype={} proto={} port={} country={} guid={} provider={} group={} location={} username={}'.format( + stack()[0][3], + ipaddr, + family, + qtype, + tun_proto, + port, + TARGET_COUNTRY, + PAIRED_DEVICE_GUID, + VPN_PROVIDER, + VPN_LOCATION_GROUP, + VPN_LOCATION, + VPN_USERNAME + )) + + p = run_shell_cmd_nowait(cmd) + stdoutq = Queue() + stderrq = Queue() + stdoutt = Thread(target=enqueue_output, args=(p.stdout, stdoutq)) + stderrt = Thread(target=enqueue_output, args=(p.stderr, stderrq)) + stdoutt.daemon = True + stdoutt.start() + stderrt.daemon = True + stderrt.start() + return stdoutq, stderrq, p, c_proto @retry(Exception, cdata='method={}'.format(stack()[0][3])) def start_server(proto='udp'): - iface = TUN_IFACE_UDP - if proto == 'tcp': iface = TUN_IFACE_TCP - - config = '{}/openvpn/{}_server.conf'.format(WORKDIR, proto) - - cmd = [ - OPENVPN_BINARY, - '--config', config, - '--dev', iface, - '--proto', '{}6'.format(proto), '--port', OPENVPN_PORT, - '--cipher', CIPHER, - '--auth', AUTH - ] - - if FRAGMENT and proto == 'udp': - cmd.append('--tun-mtu') - cmd.append(TUN_MTU) - cmd.append('--mssfix') - cmd.append('--fragment') - cmd.append(FRAGMENT) - - if EXPLICIT_EXIT_NOTIFY in [1, 2]\ - and proto == 'udp'\ - and bool(re.search('^2\.[4-5]\.', OPENVPN_VERSION)): - cmd.append('--explicit-exit-notify') - cmd.append(str(EXPLICIT_EXIT_NOTIFY)) - - p = run_shell_cmd_nowait(cmd) - stdoutq = Queue() - stderrq = Queue() - stdoutt = Thread(target=enqueue_output, args=(p.stdout, stdoutq)) - stderrt = Thread(target=enqueue_output, args=(p.stderr, stderrq)) - stdoutt.daemon = True - stdoutt.start() - stderrt.daemon = True - stderrt.start() - return stdoutq, stderrq, p + iface = TUN_IFACE_UDP + if proto == 'tcp': iface = TUN_IFACE_TCP + + config = '{}/openvpn/{}_server.conf'.format(WORKDIR, proto) + + cmd = [ + OPENVPN_BINARY, + '--config', config, + '--dev', iface, + '--proto', '{}6'.format(proto), '--port', OPENVPN_PORT, + '--cipher', CIPHER, + '--auth', AUTH + ] + + if FRAGMENT and proto == 'udp': + cmd.append('--tun-mtu') + cmd.append(TUN_MTU) + cmd.append('--mssfix') + cmd.append('--fragment') + cmd.append(FRAGMENT) + + if EXPLICIT_EXIT_NOTIFY in [1, 2]\ + and proto == 'udp'\ + and bool(re.search('^2\.[4-5]\.', OPENVPN_VERSION)): + cmd.append('--explicit-exit-notify') + cmd.append(str(EXPLICIT_EXIT_NOTIFY)) + + p = run_shell_cmd_nowait(cmd) + stdoutq = Queue() + stderrq = Queue() + stdoutt = Thread(target=enqueue_output, args=(p.stdout, stdoutq)) + stderrt = Thread(target=enqueue_output, args=(p.stderr, stderrq)) + stdoutt.daemon = True + stdoutt.start() + stderrt.daemon = True + stderrt.start() + return stdoutq, stderrq, p @retry(Exception, cdata='method={}'.format(stack()[0][3])) def _get_client_conns(user=GUID, proto='udp'): - conns = 0 - stats = open( - '{}/openvpn.{}.status'.format( - DATADIR, - proto - ) - ).read().split('\n') - for lines in stats: - line = lines.split(',') - if 'CLIENT_LIST' in line and line[0] in ['CLIENT_LIST'] and line[-1] and line[1] == user: - conns = conns + 1 - return conns + conns = 0 + stats = open('{}/openvpn.{}.status'.format( + DATADIR, + proto + )).read().split('\n') + for lines in stats: + line = lines.split(',') + if 'CLIENT_LIST' in line and line[0] in ['CLIENT_LIST'] and line[-1] and line[1] == user: + conns = conns + 1 + return conns @retry(Exception, cdata='method={}'.format(stack()[0][3])) def get_client_conns(user=GUID): - total_conns = 0 - for proto in TUN_PROTO: - try: - proto_conns = _get_client_conns(user=user, proto=proto) - total_conns = total_conns + proto_conns - except: - proto_conns = 0 - return total_conns + total_conns = 0 + for proto in TUN_PROTO: + try: + proto_conns = _get_client_conns(user=user, proto=proto) + total_conns = total_conns + proto_conns + except: + proto_conns = 0 + return total_conns @retry(Exception, cdata='method={}'.format(stack()[0][3])) def get_server_conns_by(proto='udp'): - conns = 0 - stats = open( - '{}/openvpn.{}.status'.format( - DATADIR, - proto - ) - ).read().split('\n') - for lines in stats: - line = lines.split(',') - if 'CLIENT_LIST' in line and line[0] in ['CLIENT_LIST'] and line[-1]: conns = conns + 1 - return int(conns) + conns = 0 + stats = open('{}/openvpn.{}.status'.format( + DATADIR, + proto + )).read().split('\n') + for lines in stats: + line = lines.split(',') + if 'CLIENT_LIST' in line and line[0] in ['CLIENT_LIST'] and line[-1]: conns = conns + 1 + return int(conns) @retry(Exception, cdata='method={}'.format(stack()[0][3])) def get_server_conns(status=[True, True]): - total_conns = 0 - for proto in TUN_PROTO: - try: - proto_conns = int(get_server_conns_by(proto=proto)) - total_conns = total_conns + proto_conns - except: - proto_conns = 0 - return int(total_conns) + total_conns = 0 + for proto in TUN_PROTO: + try: + proto_conns = int(get_server_conns_by(proto=proto)) + total_conns = total_conns + proto_conns + except: + proto_conns = 0 + return int(total_conns) @retry(Exception, cdata='method={}'.format(stack()[0][3])) def get_load_stats(host=VPN_HOST): - total_stats = (0, 0, 0) - for port in [VPN_UDP_MGMT_PORT, VPN_TCP_MGMT_PORT]: - try: - load_stats = run_openvpn_mgmt_cmd(host=host, port=port, cmd='load-stats') - p = re.compile('^SUCCESS: nclients=([\d]+),bytesin=([\d]+),bytesout=([\d]+)$') - m = p.search(load_stats[0]) - try: - port_stats = m.groups() - except: - port_stats = (0, 0, 0) - except: - port_stats = (0, 0, 0) - total_stats = list(map(lambda x,y:int(x)+int(y), total_stats, port_stats)) - return total_stats + total_stats = (0, 0, 0) + for port in [VPN_UDP_MGMT_PORT, VPN_TCP_MGMT_PORT]: + try: + load_stats = run_openvpn_mgmt_cmd(host=host, port=port, cmd='load-stats') + p = re.compile('^SUCCESS: nclients=([\d]+),bytesin=([\d]+),bytesout=([\d]+)$') + m = p.search(load_stats[0]) + try: + port_stats = m.groups() + except: + port_stats = (0, 0, 0) + except: + port_stats = (0, 0, 0) + total_stats = list(map(lambda x,y:int(x)+int(y), total_stats, port_stats)) + return total_stats @retry(Exception, cdata='method={}'.format(stack()[0][3])) def get_status(user=GUID, proto='udp'): - status = list() - status = open( - '{}/openvpn.{}.status'.format( - DATADIR, - proto - ) - ).read().split('\n') - return status + status = list() + status = open('{}/openvpn.{}.status'.format( + DATADIR, + proto + )).read().split('\n') + return status @retry(Exception, cdata='method={}'.format(stack()[0][3])) def _get_status(host=VPN_HOST, port=VPN_UDP_MGMT_PORT): - status = list() - status = run_openvpn_mgmt_cmd(host=host, port=port) - return status + status = list() + status = run_openvpn_mgmt_cmd(host=host, port=port) + return status @retry(Exception, cdata='method={}'.format(stack()[0][3])) def get_client_status(): - bytesin = 0 - bytesout = 0 - try: - status = open( - '{}/client.status'.format( - TEMPDIR - ) - ).read().split('\n') - bytesin = [line.split(',')[1] for line in status if line.split(',')[0] == 'TCP/UDP read bytes'][0] - bytesout = [line.split(',')[1] for line in status if line.split(',')[0] == 'TCP/UDP write bytes'][0] - except IndexError: - pass - return (bytesin, bytesout) + bytesin = 0 + bytesout = 0 + try: + status = open('{}/client.status'.format( + TEMPDIR + )).read().split('\n') + bytesin = [line.split(',')[1] for line in status if line.split(',')[0] == 'TCP/UDP read bytes'][0] + bytesout = [line.split(',')[1] for line in status if line.split(',')[0] == 'TCP/UDP write bytes'][0] + except IndexError: + pass + return (bytesin, bytesout) @retry(Exception, cdata='method={}'.format(stack()[0][3])) def get_clients(): - clients = [] - for proto in TUN_PROTO: - try: - status_lines = get_status(proto=proto) - for lines in status_lines: - line = lines.split(',') - if 'CLIENT_LIST' in line and line[0] in ['CLIENT_LIST'] and line[-1]: - clients.append(line[1]) - except: - status_lines = [] - return clients + clients = [] + for proto in TUN_PROTO: + try: + status_lines = get_status(proto=proto) + for lines in status_lines: + line = lines.split(',') + if 'CLIENT_LIST' in line and line[0] in ['CLIENT_LIST'] and line[-1]: + clients.append(line[1]) + except: + status_lines = [] + return clients @retry(Exception, cdata='method={}'.format(stack()[0][3])) def reauthenticate_clients(): - count = 0 - while True: - clients = get_clients() - killed = list() - print( - '{}: count={} clients={}'.format( - stack()[0][3], - count, - len(clients) - ) - ) - for client in clients: - # password check (resin.io Data API) - jwtoken = None - try: - password = get_device_env_by_name(guid=client, name='TUN_PASSWD')[:16] - assert password - except Exception as e: - # authenticate against JWT recorded against PayPal billing agreement - try: - jwtoken = decode_jwt_payload(encoded=get_jwt_payload_from_paypal(baid=client)) - password = jwtoken['p'][:16] - assert password - except: - password = None - - if not password: break - - result = auth.authenticate(username=client, password=password) - print( - 'get_device_env_by_name: client={} password={}'.format( - client, - password - ) - ) - if not result and client != 'UNDEF': - for port in [VPN_UDP_MGMT_PORT, VPN_TCP_MGMT_PORT]: - try: - print( - 'run_openvpn_mgmt_cmd: port={} cmd=kill client={} result={}'.format( - port, - client, - result - ) - ) - print( - run_openvpn_mgmt_cmd( - port=port, - cmd='kill {}'.format( - client - ) - ) - ) - killed.append(client) - except: - pass - print( - '{}: count={} killed={}'.format( - stack()[0][3], - count, - len(killed) - ) - ) - sleep(3600) - count = count + 1 + count = 0 + while True: + clients = get_clients() + killed = list() + print('{}: count={} clients={}'.format( + stack()[0][3], + count, + len(clients) + )) + for client in clients: + # password check (resin.io Data API) + jwtoken = None + try: + password = get_device_env_by_name(guid=client, name='TUN_PASSWD')[:16] + assert password + except Exception as e: + # authenticate against JWT recorded against PayPal billing agreement + try: + jwtoken = decode_jwt_payload(encoded=get_jwt_payload_from_paypal(baid=client)) + password = jwtoken['p'][:16] + assert password + except: + password = None + + if not password: break + + result = auth.authenticate(username=client, password=password) + print('get_device_env_by_name: client={} password={}'.format( + client, + password + )) + if not result and client != 'UNDEF': + for port in [VPN_UDP_MGMT_PORT, VPN_TCP_MGMT_PORT]: + try: + print('run_openvpn_mgmt_cmd: port={} cmd=kill client={} result={}'.format( + port, + client, + result + )) + print(run_openvpn_mgmt_cmd( + port=port, + cmd='kill {}'.format(client) + )) + killed.append(client) + except: + pass + print('{}: count={} killed={}'.format( + stack()[0][3], + count, + len(killed) + )) + sleep(3600) + count = count + 1 def log_client_stats(status=False, country=TARGET_COUNTRY): - if LOG_CLIENT_STATS: - for family in AF_INETS: - data = get_geo_location(family=family) - if not data: break + if LOG_CLIENT_STATS: + for family in AF_INETS: + data = get_geo_location(family=family) + if not data: break - # 1 = up - # 0 = down - data['status'] = sum([int(el) for el in [status]]) + # 1 = up + # 0 = down + data['status'] = sum([int(el) for el in [status]]) - data['bytesin'] = 0 - data['bytesout'] = 0 - data['conns'] = 0 - data['hostapd'] = AP - data['weight'] = MAX_CONNS_CLIENT + data['bytesin'] = 0 + data['bytesout'] = 0 + data['conns'] = 0 + data['hostapd'] = AP + data['weight'] = MAX_CONNS_CLIENT - if DEVICE_TYPE == 5: - data['country'] = country + if DEVICE_TYPE == 5: + data['country'] = country - if COUNTRY_OVERRIDE: - log('{}: country={}'.format(stack()[0][3], COUNTRY_OVERRIDE)) - data['country'] = COUNTRY_OVERRIDE + if COUNTRY_OVERRIDE: + log('{}: country={}'.format(stack()[0][3], COUNTRY_OVERRIDE)) + data['country'] = COUNTRY_OVERRIDE - if GEOIP_OVERRIDE: - log('{}: ip={}'.format(stack()[0][3], GEOIP_OVERRIDE)) - data['ip'] = GEOIP_OVERRIDE + if GEOIP_OVERRIDE: + log('{}: ip={}'.format(stack()[0][3], GEOIP_OVERRIDE)) + data['ip'] = GEOIP_OVERRIDE - if DEVICE_TYPE in CLIENT_DEVICE_TYPES: - try: - data['conns'] = get_stations() # wireless client count - data['bytesin'] = get_client_status()[0] - data['bytesout'] = get_client_status()[1] - except: - pass + if DEVICE_TYPE in CLIENT_DEVICE_TYPES: + try: + data['conns'] = get_stations() # wireless client count + data['bytesin'] = get_client_status()[0] + data['bytesout'] = get_client_status()[1] + except: + pass - log('{}: af={} data={}'.format(stack()[0][3], family, data)) - print(put_device(family=family, data=data)) + log('{}: af={} data={}'.format(stack()[0][3], family, data)) + print(put_device(family=family, data=data)) - # client logging plug-in(s) - log('{}: plugin={}'.format(stack()[0][3], DNS_SUB_DOMAIN)) - if 'log_plugin_client' in dir(plugin): - result = plugin.log_plugin_client(status=status) + # client logging plug-in(s) + log('{}: plugin={}'.format(stack()[0][3], DNS_SUB_DOMAIN)) + if 'log_plugin_client' in dir(plugin): + result = plugin.log_plugin_client(status=status) def log_server_stats(status=[False, False]): - if LOG_SERVER_STATS: - for family in AF_INETS: - data = get_geo_location(family=family) - if not data: break - - # 2 = udp+tcp; 1 = udp or tcp; 0 = down - data['status'] = sum([int(el) for el in status]) - data['conns'] = 0 - data['bytesin'] = 0 - data['bytesout'] = 0 - data['cipher'] = CIPHER - data['auth'] = AUTH - data['upnp'] = UPNP - data['weight'] = MAX_CONNS_SERVER - - if COUNTRY_OVERRIDE: - log('{}: country={}'.format(stack()[0][3], COUNTRY_OVERRIDE)) - data['country'] = COUNTRY_OVERRIDE - - if GEOIP_OVERRIDE: - log('{}: ip={}'.format(stack()[0][3], GEOIP_OVERRIDE)) - data['ip'] = GEOIP_OVERRIDE - - if DEVICE_TYPE in SERVER_DEVICE_TYPES: - data['conns'] = int(get_server_conns()) - data['bytesin'] = int(get_load_stats()[1]) - data['bytesout'] = int(get_load_stats()[2]) - - log('{}: af={} data={}'.format(stack()[0][3], family, data)) - print(put_device(family=family, data=data)) - - # additional server logging plug-in(s) - log('{}: plugin={}'.format(stack()[0][3], DNS_SUB_DOMAIN)) - if 'log_plugin_server' in dir(plugin): - result = plugin.log_plugin_server(status=status) + if LOG_SERVER_STATS: + for family in AF_INETS: + data = get_geo_location(family=family) + if not data: break + + # 2 = udp+tcp; 1 = udp or tcp; 0 = down + data['status'] = sum([int(el) for el in status]) + data['conns'] = 0 + data['bytesin'] = 0 + data['bytesout'] = 0 + data['cipher'] = CIPHER + data['auth'] = AUTH + data['upnp'] = UPNP + data['weight'] = MAX_CONNS_SERVER + + if COUNTRY_OVERRIDE: + log('{}: country={}'.format(stack()[0][3], COUNTRY_OVERRIDE)) + data['country'] = COUNTRY_OVERRIDE + + if GEOIP_OVERRIDE: + log('{}: ip={}'.format(stack()[0][3], GEOIP_OVERRIDE)) + data['ip'] = GEOIP_OVERRIDE + + if DEVICE_TYPE in SERVER_DEVICE_TYPES: + data['conns'] = int(get_server_conns()) + data['bytesin'] = int(get_load_stats()[1]) + data['bytesout'] = int(get_load_stats()[2]) + + log('{}: af={} data={}'.format(stack()[0][3], family, data)) + print(put_device(family=family, data=data)) + + # additional server logging plug-in(s) + log('{}: plugin={}'.format(stack()[0][3], DNS_SUB_DOMAIN)) + if 'log_plugin_server' in dir(plugin): + result = plugin.log_plugin_server(status=status) def openvpn_remote_override(conf='/mnt/{}/client.ovpn'.format(DNS_SUB_DOMAIN)): - f = open(conf, 'r') - text = f.read() - f.close() - p = re.compile('remote\s+(.*)\s+(.*)\s+([tcpud46]+)') - m = p.search(text) - try: - groups = m.groups() - host = groups[0] - port = groups[1] - proto = groups[2] - search = 'remote {} {} {}'.format(host, port, proto) - replace = 'remote {} {} {}'.format('localhost', port, proto) - text = text.replace(search, replace) - f = open(conf, 'w') - f.write(text) - f.close() - except: - if DEBUG: print_exc() - - return text + f = open(conf, 'r') + text = f.read() + f.close() + p = re.compile('remote\s+(.*)\s+(.*)\s+([tcpud46]+)') + m = p.search(text) + try: + groups = m.groups() + host = groups[0] + port = groups[1] + proto = groups[2] + search = 'remote {} {} {}'.format(host, port, proto) + replace = 'remote {} {} {}'.format('localhost', port, proto) + text = text.replace(search, replace) + f = open(conf, 'w') + f.write(text) + f.close() + except: + if DEBUG: print_exc() + + return text def conf_stunnel_client( - node=None, - port=OPENVPN_PORT, - conf='/etc/stunnel/stunnel-client.conf', - template='/etc/stunnel/stunnel-client.template.conf' + node=None, + port=OPENVPN_PORT, + conf='/etc/stunnel/stunnel-client.conf', + template='/etc/stunnel/stunnel-client.template.conf' ): - if not node: return None - f = open(template, 'r') - template = f.read() - f.close() - template = template.replace('{{OPENVPN_SERVER}}', str(node)) - template = template.replace('{{OPENVPN_PORT}}', str(port)) - f = open(conf, 'w') - f.write(template) - f.close() - return template + if not node: return None + f = open(template, 'r') + template = f.read() + f.close() + template = template.replace('{{OPENVPN_SERVER}}', str(node)) + template = template.replace('{{OPENVPN_PORT}}', str(port)) + f = open(conf, 'w') + f.write(template) + f.close() + return template def restart_stunnel_client( - stunnel_bin='/usr/bin/stunnel', - conf='/etc/stunnel/stunnel-client.conf' + stunnel_bin='/usr/bin/stunnel', + conf='/etc/stunnel/stunnel-client.conf' ): - print( - run_shell_cmd( - [ - '/usr/bin/pkill', - '-f', - '%s %s' % (stunnel_bin, conf) - ] - ) - ) - return run_shell_cmd(['%s' % stunnel_bin, '%s' % conf]) + print(run_shell_cmd( + [ + '/usr/bin/pkill', + '-f', + '%s %s' % (stunnel_bin, conf) + ] + )) + return run_shell_cmd(['%s' % stunnel_bin, '%s' % conf]) def start_wanproxy_server( - ipaddr=None, - family=4, - local_pid=None, - remote_pid=None, - port=None, - ssh_port=DOCKER_SSH_PORT + ipaddr=None, + family=4, + local_pid=None, + remote_pid=None, + port=None, + ssh_port=DOCKER_SSH_PORT ): - if WANPROXY == 'SSH': - remote_guid = get_guid_by_public_ipaddr(ipaddr=ipaddr, family=family) - assert remote_guid - if DEBUG: print( - 'get_guid_by_public_ipaddr: ipaddr={} family={} remote_guid={}'.format( - ipaddr, - family, - remote_guid - ) - ) - - if not (port and remote_pid): - if WANPROXY == 'SSH': - # start WANProxy server instance and get remote port for forwarding - result = run_shell_cmd( - [ - 'ssh', '-i', '%s/id_rsa' % WORKDIR, - '-o StrictHostKeyChecking=no', - 'root@%s' % ipaddr, - '-p', '%s' % str(ssh_port), - 'bash', - '-c', - '"source %s/functions; source %s/.env; start_wanproxy_server"' % ( - WORKDIR, - TEMPDIR - ) - ] - ) - if DEBUG: print(result) - remote_pid = result[1].split()[0] - port = result[1].split()[1] - - if not local_pid: - if WANPROXY == 'SSH': - # start SSH tunnel - result = run_background_shell_cmd( - [ - 'ssh', '-i', '%s/id_rsa' % WORKDIR, - '-fN','-o StrictHostKeyChecking=no', - '-L 3301:localhost:%s' % str(port), - '-p', '%s' % str(ssh_port), - 'root@%s' % ipaddr - ] - ) - if DEBUG: print(result.__dict__) - local_pid = str(int(result.pid) + 1) - - if WANPROXY == 'SOCAT': - # start SOCAT local listener - result = run_shell_cmd_nowait( - [ - '/usr/bin/socat', - 'TCP%s-LISTEN:3301,bind=localhost,su=nobody,fork,reuseaddr' % str( - family - ), - 'TCP%s:%s:%s' % (str(family), ipaddr, SOCAT_PORT) - ] - ) - if DEBUG: print(result.__dict__) - local_pid = str(result.pid) - - return (local_pid, remote_pid, port) + if WANPROXY == 'SSH': + remote_guid = get_guid_by_public_ipaddr(ipaddr=ipaddr, family=family) + assert remote_guid + if DEBUG: print( 'get_guid_by_public_ipaddr: ipaddr={} family={} remote_guid={}'.format( + ipaddr, + family, + remote_guid + )) + + if not (port and remote_pid): + if WANPROXY == 'SSH': + # start WANProxy server instance and get remote port for forwarding + result = run_shell_cmd( + [ + 'ssh', '-i', '%s/id_rsa' % WORKDIR, + '-o StrictHostKeyChecking=no', + 'root@%s' % ipaddr, + '-p', '%s' % str(ssh_port), + 'bash', + '-c', + '"source %s/functions; source %s/.env; start_wanproxy_server"' % ( + WORKDIR, + TEMPDIR + ) + ] + ) + if DEBUG: print(result) + remote_pid = result[1].split()[0] + port = result[1].split()[1] + + if not local_pid: + if WANPROXY == 'SSH': + # start SSH tunnel + result = run_background_shell_cmd( + [ + 'ssh', '-i', '%s/id_rsa' % WORKDIR, + '-fN','-o StrictHostKeyChecking=no', + '-L 3301:localhost:%s' % str(port), + '-p', '%s' % str(ssh_port), + 'root@%s' % ipaddr + ] + ) + if DEBUG: print(result.__dict__) + local_pid = str(int(result.pid) + 1) + + if WANPROXY == 'SOCAT': + # start SOCAT local listener + result = run_shell_cmd_nowait( + [ + '/usr/bin/socat', + 'TCP%s-LISTEN:3301,bind=localhost,su=nobody,fork,reuseaddr' % str( + family + ), + 'TCP%s:%s:%s' % (str(family), ipaddr, SOCAT_PORT) + ] + ) + if DEBUG: print(result.__dict__) + local_pid = str(result.pid) + + return (local_pid, remote_pid, port) def kill_remote_pid(ipaddr=None, family=4, pid=None, ssh_port=DOCKER_SSH_PORT): - if WANPROXY == 'SSH': - remote_guid = get_guid_by_public_ipaddr(ipaddr=ipaddr, family=family) - assert remote_guid - if DEBUG: print( - 'get_guid_by_public_ipaddr: ipaddr={} family={} remote_guid={}'.format( - ipaddr, - family, - remote_guid - ) - ) - - result = None - if pid and WANPROXY == 'SSH': - # kill remote pid - result = run_shell_cmd( - [ - 'ssh', '-i', '%s/id_rsa' % WORKDIR, - '-o StrictHostKeyChecking=no', - 'root@%s' % ipaddr, - '-p', '%s' % str(ssh_port), - 'bash', '-c', '"kill -9 %s"' % str(pid) - ] - ) - if DEBUG: print(result) - return result + if WANPROXY == 'SSH': + remote_guid = get_guid_by_public_ipaddr(ipaddr=ipaddr, family=family) + assert remote_guid + if DEBUG: print('get_guid_by_public_ipaddr: ipaddr={} family={} remote_guid={}'.format( + ipaddr, + family, + remote_guid + )) + + result = None + if pid and WANPROXY == 'SSH': + # kill remote pid + result = run_shell_cmd( + [ + 'ssh', '-i', '%s/id_rsa' % WORKDIR, + '-o StrictHostKeyChecking=no', + 'root@%s' % ipaddr, + '-p', '%s' % str(ssh_port), + 'bash', '-c', '"kill -9 %s"' % str(pid) + ] + ) + if DEBUG: print(result) + return result @retry(Exception, cdata='method={}'.format(stack()[0][3])) def disconnect_clients(): - disconnected = list() - while True: - if os.path.exists('{}/disconnect_clients'.format(DATADIR)): - if not 'client_disconnect' in dir(plugin): break - clients = get_clients() - print('{}: clients={}'.format(stack()[0][3], len(clients))) - for client in clients: - if client not in ['UNDEF']: - try: - result = plugin.client_disconnect(client) - disconnected.append(client) - print( - 'client_disconnect: client={} result={}'.format( - client, - result - ) - ) - except: - pass - - if os.path.exists('{}/disconnect_clients'.format(DATADIR)): - os.remove('{}/disconnect_clients'.format(DATADIR)) - - print( - '{}: plugin={} disconnected={}'.format( - stack()[0][3], - DNS_SUB_DOMAIN, - len(disconnected) - ) - ) - sleep(1) - sleep(3600) + disconnected = list() + while True: + if os.path.exists('{}/disconnect_clients'.format(DATADIR)): + if not 'client_disconnect' in dir(plugin): break + clients = get_clients() + print('{}: clients={}'.format(stack()[0][3], len(clients))) + for client in clients: + if client not in ['UNDEF']: + try: + result = plugin.client_disconnect(client) + disconnected.append(client) + print('client_disconnect: client={} result={}'.format( + client, + result + )) + except: + pass + + if os.path.exists('{}/disconnect_clients'.format(DATADIR)): + os.remove('{}/disconnect_clients'.format(DATADIR)) + + print('{}: plugin={} disconnected={}'.format( + stack()[0][3], + DNS_SUB_DOMAIN, + len(disconnected) + )) + sleep(1) + sleep(3600) if not OPENVPN_VERSION: OPENVPN_VERSION = get_openvpn_version()