Skip to content

Instantly share code, notes, and snippets.

@lightsing
Last active February 20, 2018 08:56
Show Gist options
  • Save lightsing/5ef503ad924c239e13ad85fc2300b576 to your computer and use it in GitHub Desktop.
Save lightsing/5ef503ad924c239e13ad85fc2300b576 to your computer and use it in GitHub Desktop.
tinc route table setting
#!/bin/python
# -*- coding: utf-8 -*-
# pywin32 required
# pip install pypiwin32
import os
import sys
import types
import math
import ftplib
import traceback
import tempfile
import ipaddress
from subprocess import check_output
INTERFACE = 'tinc tun/tap device name'
PEER = 'tine connect peer'
GATE = 'tinc gateway'
SUB = '/32'
CMD = {
'show': 'netsh int ipv4 show route',
'add': 'netsh int ipv4 add route {cidr} "{device}" {gateway} metric={metric} store={store}',
'del': 'netsh int ipv4 delete route {cidr} "{device}"'
}
def get_default_gateway() -> str:
route = check_output(CMD['show']).decode('GBK')
lines = route.split('\n')
gateway = None
metric = 256
for line in [y.split() for y in filter(lambda x: x.find('0.0.0.0') != -1, lines)]:
if line[-1] == GATE:
continue
line[2] = int(line[2])
if line[2] < metric:
metric = line[2]
gateway = ipaddress.IPv4Address(line[-1])
if gateway is not None:
for line in [y.split() for y in filter(lambda x: x.find('0.0.0.0') == -1, lines)]:
try:
if gateway in ipaddress.IPv4Network(line[3]):
return gateway, ' '.join(line[5:])
except (IndexError, ipaddress.AddressValueError):
pass
return gateway, None
def _set(config):
return CMD['add'].format(**config)
def _del(config):
return CMD['del'].format(**config)
def _gen():
default_gateway, default_device = get_default_gateway()
if default_gateway is None:
print("Cannot Find Default Gateway")
return
with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as tmp:
tmp.write('@echo off\r\n')
tmp.write('rem Generated Route Batch\r\n')
tmp.write('echo Apply APINC Generated CN Route Batch\r\n')
tmp.write('pause\r\n')
set_route = lambda x: tmp.write(_set(x) + '\r\n')
del_route = lambda x: tmp.write(_del(x) + '\r\n')
set_route({
'cidr': PEER + SUB,
'gateway': default_gateway,
'device': default_device,
'metric': 1,
'store': 'active'
})
del_route({
'cidr': '0.0.0.0/0',
'device': default_device,
})
set_route({
'cidr': '0.0.0.0/0',
'gateway': GATE,
'device': INTERFACE,
'metric': 1,
'store': 'active'
})
set_route({
'cidr': '0.0.0.0/0',
'gateway': default_gateway,
'device': default_device,
'metric': 256,
'store': 'active'
})
tmp.write('ipconfig /flushdns\r\n')
with ftplib.FTP('ftp.apnic.net') as ftp:
ftp.login()
ftp.cwd('pub/stats/apnic/')
def handler(record):
if not record.startswith('apnic|CN|ipv4|'):
return
fields = record[14:].split('|')
set_route({
'cidr': '{0}/{1}'.format(fields[0], 32 - int(math.log(int(fields[1]), 2))),
'gateway': default_gateway,
'device': default_device,
'metric': 1,
'store': 'active'
})
ftp.retrlines('RETR delegated-apnic-latest', handler)
return tmp.name
def isUserAdmin():
"""@return: True if the current user is an 'Admin' whatever that
means (root on Unix), otherwise False.
Warning: The inner function fails unless you have Windows XP SP2 or
higher. The failure causes a traceback to be printed and this
function to return False.
"""
if os.name == 'nt':
import ctypes
# WARNING: requires Windows XP SP2 or higher!
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
print("Admin check failed, assuming not an admin.")
return False
else:
# Check for root on Posix
return os.getuid() == 0
# elevate a Python process with UAC on Windows
# https://gist.github.com/Preston-Landers/267391562bc96959eb41
def runAsAdmin(cmdLine=None, wait=True):
"""Attempt to relaunch the current script as an admin using the same
command line parameters. Pass cmdLine in to override and set a new
command. It must be a list of [command, arg1, arg2...] format.
Set wait to False to avoid waiting for the sub-process to finish. You
will not be able to fetch the exit code of the process if wait is
False.
Returns the sub-process return code, unless wait is False in which
case it returns None.
@WARNING: this function only works on Windows.
"""
if os.name != 'nt':
raise RuntimeError("This function is only implemented on Windows.")
import win32api, win32con, win32event, win32process
from win32com.shell.shell import ShellExecuteEx
from win32com.shell import shellcon
python_exe = sys.executable
if cmdLine is None:
cmdLine = [python_exe] + sys.argv
elif type(cmdLine) not in (types.TupleType,types.ListType):
raise(ValueError, "cmdLine is not a sequence.")
cmd = '"%s"' % (cmdLine[0],)
# XXX TODO: isn't there a function or something we can call to massage command line params?
params = " ".join(['"%s"' % (x,) for x in cmdLine[1:]])
cmdDir = ''
showCmd = win32con.SW_SHOWNORMAL
lpVerb = 'runas' # causes UAC elevation prompt.
# print "Running", cmd, params
# ShellExecute() doesn't seem to allow us to fetch the PID or handle
# of the process, so we can't get anything useful from it. Therefore
# the more complex ShellExecuteEx() must be used.
# procHandle = win32api.ShellExecute(0, lpVerb, cmd, params, cmdDir, showCmd)
procInfo = ShellExecuteEx(nShow=showCmd,
fMask=shellcon.SEE_MASK_NOCLOSEPROCESS,
lpVerb=lpVerb,
lpFile=cmd,
lpParameters=params)
if wait:
procHandle = procInfo['hProcess']
obj = win32event.WaitForSingleObject(procHandle, win32event.INFINITE)
rc = win32process.GetExitCodeProcess(procHandle)
#print "Process handle %s returned code %s" % (procHandle, rc)
else:
rc = None
return rc
def main():
if not isUserAdmin() and runAsAdmin() != 0:
return
bat = _gen()
os.system(bat)
os.remove(bat)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment