-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11 from gmacon/feature/macos
Add macOS support
- Loading branch information
Showing
7 changed files
with
514 additions
and
165 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
from .provider import FirewallProvider, TunnelPrepProvider | ||
|
||
|
||
class NoFirewallProvider(FirewallProvider): | ||
def configure_firewall(self, device): | ||
pass | ||
|
||
def deconfigure_firewall(self, device): | ||
pass | ||
|
||
|
||
class NoTunnelPrepProvider(TunnelPrepProvider): | ||
def prepare_tunnel(self): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,102 +1,98 @@ | ||
import os, fcntl | ||
import subprocess as sp | ||
from shutil import which | ||
from ipaddress import ip_address | ||
|
||
def find_paths(): | ||
global DIG, IPROUTE, HOSTS, IPTABLES | ||
DIG = which('dig') or '/usr/bin/dig' | ||
IPROUTE = which('ip') or '/sbin/ip' | ||
IPTABLES = which('iptables') or '/sbin/iptables' | ||
HOSTS = '/etc/hosts' | ||
|
||
for binary in (DIG, IPROUTE, IPTABLES): | ||
if not os.access(binary, os.X_OK): | ||
raise OSError("cannot execute %s" % binary) | ||
if not os.access(HOSTS, os.R_OK | os.W_OK): | ||
raise OSError("cannot read/write %s" % HOSTS) | ||
|
||
def pid2exe(pid): | ||
try: | ||
return os.readlink('/proc/%d/exe' % pid) | ||
except (OSError, IOError): | ||
return None | ||
|
||
def ppidof(pid): | ||
try: | ||
return int(next(open('/proc/%d/stat'%pid)).split()[3]) | ||
except (OSError, ValueError, IOError): | ||
pass | ||
|
||
def check_tun(): | ||
if not os.access('/dev/net/tun', os.R_OK|os.W_OK): | ||
raise OSError("can't read and write /dev/net/tun") | ||
|
||
def write_hosts(host_map, tag): | ||
global HOSTS | ||
with open(HOSTS,'r+') as hostf: | ||
fcntl.flock(hostf, fcntl.LOCK_EX) # POSIX only, obviously | ||
lines = hostf.readlines() | ||
keeplines = [l for l in lines if not l.endswith('# %s\n'%tag)] | ||
hostf.seek(0,0) | ||
hostf.writelines(keeplines) | ||
for ip, names in host_map: | ||
print('%s %s\t\t# %s' % (ip, ' '.join(names), tag), file=hostf) | ||
hostf.truncate() | ||
return len(host_map) or len(lines)-len(keeplines) | ||
|
||
def dig(bind, host, dns, domains=None, reverse=False): | ||
global DIG | ||
host, dns = str(host), map(str, dns) | ||
basecl = [DIG,'+short','+noedns']+(['-b'+str(bind)] if bind else [])+['@'+s for s in dns] | ||
if reverse: | ||
extras = (['-x'],) | ||
if domains is None: | ||
extras = ([],) | ||
elif isinstance(domains, str): | ||
extras = (['+domain=' + d],) | ||
else: | ||
extras = (['+domain=' + d] for d in domains) | ||
|
||
out = set() | ||
for extra in extras: | ||
#print cl | ||
p = sp.Popen(basecl + extra + [host], stdout=sp.PIPE) | ||
lines = [l.strip() for l in p.communicate()[0].decode().splitlines()] | ||
if lines and p.wait()==0: | ||
for line in lines: | ||
line = line.rstrip('\n.') | ||
if reverse: | ||
n, d = line.split('.',1) | ||
out.add(n if d in domains else line) | ||
else: | ||
try: | ||
out.add(ip_address(line)) | ||
except ValueError: | ||
pass # didn't return an IP address! | ||
return out or None | ||
|
||
def iproute(*args): | ||
global IPROUTE | ||
cl = [IPROUTE] | ||
for arg in args: | ||
if isinstance(arg, dict): | ||
for k,v in arg.items(): | ||
cl += [k] if v is None else [k, str(v)] | ||
import os | ||
import subprocess | ||
from signal import SIGTERM | ||
|
||
from .provider import FirewallProvider, ProcessProvider, RouteProvider, TunnelPrepProvider | ||
from .util import get_executable | ||
|
||
|
||
class ProcfsProvider(ProcessProvider): | ||
def pid2exe(self, pid): | ||
try: | ||
return os.readlink('/proc/%d/exe' % pid) | ||
except (OSError, IOError): | ||
return None | ||
|
||
def ppid_of(self, pid=None): | ||
if pid is None: | ||
return os.getppid() | ||
try: | ||
return int(next(open('/proc/%d/stat' % pid)).split()[3]) | ||
except (OSError, ValueError, IOError): | ||
return None | ||
|
||
def kill(self, pid, signal=SIGTERM): | ||
os.kill(pid, signal) | ||
|
||
|
||
class Iproute2Provider(RouteProvider): | ||
def __init__(self): | ||
self.iproute = get_executable('/sbin/ip') | ||
|
||
def _iproute(self, *args, **kwargs): | ||
cl = [self.iproute] | ||
cl.extend(str(v) for v in args if v is not None) | ||
for k, v in kwargs.items(): | ||
if v is not None: | ||
cl.extend((k, str(v))) | ||
|
||
if args[:2]==('route','get'): | ||
output_start, keys = 1, ('via', 'dev', 'src', 'mtu') | ||
elif args[:2]==('link','show'): | ||
output_start, keys = 3, ('state', 'mtu') | ||
else: | ||
output_start = None | ||
|
||
if output_start is not None: | ||
words = subprocess.check_output(cl).decode().split() | ||
return {words[i]: words[i + 1] for i in range(output_start, len(words), 2) if words[i] in keys} | ||
else: | ||
cl.append(str(arg)) | ||
|
||
if args[:2]==('route','get'): get, start, keys = 'route', 1, ('via','dev','src','mtu') | ||
elif args[:2]==('link','show'): get, start, keys = 'link', 3, ('mtu','state') | ||
else: get = None | ||
|
||
if get is None: | ||
sp.check_call(cl) | ||
else: | ||
w = sp.check_output(cl).decode().split() | ||
return {w[ii]:w[ii+1] for ii in range(start, len(w), 2) if w[ii] in keys} | ||
|
||
def iptables(*args): | ||
global IPTABLES | ||
cl = [IPTABLES] + list(args) | ||
sp.check_call(cl) | ||
subprocess.check_call(cl) | ||
|
||
def add_route(self, destination, *, via=None, dev=None, src=None, mtu=None): | ||
self._iproute('route', 'add', destination, via=via, dev=dev, src=src, mtu=mtu) | ||
|
||
def replace_route(self, destination, *, via=None, dev=None, src=None, mtu=None): | ||
self._iproute('route', 'replace', destination, via=via, dev=dev, src=src, mtu=mtu) | ||
|
||
def remove_route(self, destination): | ||
self._iproute('route', 'del', destination) | ||
|
||
def get_route(self, destination): | ||
return self._iproute('route', 'get', destination) | ||
|
||
def flush_cache(self): | ||
self._iproute('route', 'flush', 'cache') | ||
|
||
def get_link_info(self, device): | ||
return self._iproute('link', 'show', device) | ||
|
||
def set_link_info(self, device, state, mtu=None): | ||
self._iproute('link', 'set', state, dev=device, mtu=mtu) | ||
|
||
def add_address(self, device, address): | ||
self._iproute('address', 'add', address, dev=device) | ||
|
||
|
||
class IptablesProvider(FirewallProvider): | ||
def __init__(self): | ||
self.iptables = get_executable('/sbin/iptables') | ||
|
||
def _iptables(self, *args): | ||
cl = [self.iptables] | ||
cl.extend(args) | ||
subprocess.check_call(cl) | ||
|
||
def configure_firewall(self, device): | ||
self._iptables('-A', 'INPUT', '-i', device, '-m', 'state', '--state', 'RELATED,ESTABLISHED', '-j', 'ACCEPT') | ||
self._iptables('-A', 'INPUT', '-i', device, '-j', 'DROP') | ||
|
||
def deconfigure_firewall(self, device): | ||
self._iptables('-D', 'INPUT', '-i', device, '-j', 'DROP') | ||
self._iptables('-D', 'INPUT', '-i', device, '-m', 'state', '--state', 'RELATED,ESTABLISHED', '-j', 'ACCEPT') | ||
|
||
|
||
class CheckTunDevProvider(TunnelPrepProvider): | ||
def prepare_tunnel(self): | ||
if not os.access('/dev/net/tun', os.R_OK | os.W_OK): | ||
raise OSError("can't read and write /dev/net/tun") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import os | ||
import re | ||
import subprocess | ||
from ipaddress import ip_network | ||
from signal import SIGTERM | ||
|
||
from .provider import ProcessProvider, RouteProvider | ||
from .util import get_executable | ||
|
||
|
||
class PsProvider(ProcessProvider): | ||
def __init__(self): | ||
self.lsof = get_executable('/usr/sbin/lsof') | ||
self.ps = get_executable('/bin/ps') | ||
|
||
def pid2exe(self, pid): | ||
info = subprocess.check_output([self.lsof, '-p', str(pid)]).decode() | ||
for line in info.splitlines(): | ||
parts = line.split() | ||
if parts[3] == 'txt': | ||
return parts[8] | ||
|
||
def ppid_of(self, pid=None): | ||
if pid is None: | ||
return os.getppid() | ||
try: | ||
return int(subprocess.check_output([self.ps, '-p', str(pid), '-o', 'ppid=']).decode().strip()) | ||
except ValueError: | ||
return None | ||
|
||
def kill(self, pid, signal=SIGTERM): | ||
os.kill(pid, signal) | ||
|
||
|
||
class BSDRouteProvider(RouteProvider): | ||
def __init__(self): | ||
self.route = get_executable('/sbin/route') | ||
self.ifconfig = get_executable('/sbin/ifconfig') | ||
|
||
def _route(self, *args): | ||
return subprocess.check_output([self.route, '-n'] + list(map(str, args))).decode() | ||
|
||
def _ifconfig(self, *args): | ||
return subprocess.check_output([self.ifconfig] + list(map(str, args))).decode() | ||
|
||
def add_route(self, destination, *, via=None, dev=None, src=None, mtu=None): | ||
args = ['add'] | ||
if mtu is not None: | ||
args.extend(('-mtu', str(mtu))) | ||
if via is not None: | ||
args.extend((destination, via)) | ||
elif dev is not None: | ||
args.extend(('-interface', destination, dev)) | ||
self._route(*args) | ||
|
||
replace_route = add_route | ||
|
||
def remove_route(self, destination): | ||
self._route('delete', destination) | ||
|
||
def get_route(self, destination): | ||
info = self._route('get', destination) | ||
lines = iter(info.splitlines()) | ||
info_d = {} | ||
for line in lines: | ||
if ':' not in line: | ||
break | ||
key, _, val = line.partition(':') | ||
info_d[key.strip()] = val.strip() | ||
keys = line.split() | ||
vals = next(lines).split() | ||
info_d.update(zip(keys, vals)) | ||
return { | ||
'via': info_d['gateway'], | ||
'dev': info_d['interface'], | ||
'mtu': info_d['mtu'], | ||
} | ||
|
||
def flush_cache(self): | ||
pass | ||
|
||
_LINK_INFO_RE = re.compile(r'flags=\d<(.*?)>\smtu\s(\d+)$') | ||
|
||
def get_link_info(self, device): | ||
info = self._ifconfig(device) | ||
match = self._LINK_INFO_RE.search(info) | ||
if match: | ||
flags = match.group(1).split(',') | ||
mtu = int(match.group(2)) | ||
return { | ||
'state': 'up' if 'UP' in flags else 'down', | ||
'mtu': mtu, | ||
} | ||
return None | ||
|
||
def set_link_info(self, device, state, mtu=None): | ||
args = [device] | ||
if state is not None: | ||
args.append(state) | ||
if mtu is not None: | ||
args.extend(('mtu', str(mtu))) | ||
self._ifconfig(*args) | ||
|
||
def add_address(self, device, address): | ||
if address.version == 6: | ||
family = 'inet6' | ||
else: | ||
family = 'inet' | ||
self._ifconfig(device, family, ip_network(address), address) |
Oops, something went wrong.