mirror of
https://github.com/sshuttle/sshuttle.git
synced 2024-11-24 17:04:36 +01:00
code cleanup and small refactoring
This commit is contained in:
parent
de8a19ce69
commit
72060abbef
@ -58,7 +58,7 @@ else
|
||||
host=$node
|
||||
fi
|
||||
|
||||
if [[ "${args[$((${#args[@]} - 1))]}" != *.* && "${args[$((${#args[@]} - 1))]}" != *:* ]]; then
|
||||
if [[ "${#args[@]}" -ne 0 && "${args[$((${#args[@]} - 1))]}" != *.* && "${args[$((${#args[@]} - 1))]}" != *:* ]]; then
|
||||
echo "No subnet specified. Using -N" >&2
|
||||
args+=('-N')
|
||||
fi
|
||||
|
@ -211,8 +211,8 @@ class FirewallClient:
|
||||
self.auto_nets = []
|
||||
|
||||
argv0 = sys.argv[0]
|
||||
# if argv0 is a not python script, it shall be an executable.
|
||||
# In windows it will be a .exe file and other platforms it will be a shebang script
|
||||
# argv0 is either be a normal python file or an executable.
|
||||
# After installed as a package, sshuttle command points to an .exe in Windows and python shebang script elsewhere.
|
||||
argvbase = (([sys.executable, sys.argv[0]] if argv0.endswith('.py') else [argv0]) +
|
||||
['-v'] * (helpers.verbose or 0) +
|
||||
['--method', method_name] +
|
||||
@ -234,43 +234,43 @@ class FirewallClient:
|
||||
# Because underlying ShellExecute() Windows api does not allow child process to inherit stdio.
|
||||
# TODO(nom3ad): Try to implement another way to achieve this.
|
||||
raise Fatal("Privilege elevation for Windows is not yet implemented. Please run from an administrator shell")
|
||||
|
||||
# Linux typically uses sudo; OpenBSD uses doas. However, some
|
||||
# Linux distributions are starting to use doas.
|
||||
sudo_cmd = ['sudo', '-p', '[local sudo] Password: ']
|
||||
doas_cmd = ['doas']
|
||||
|
||||
# For clarity, try to replace executable name with the
|
||||
# full path.
|
||||
doas_path = which("doas")
|
||||
if doas_path:
|
||||
doas_cmd[0] = doas_path
|
||||
sudo_path = which("sudo")
|
||||
if sudo_path:
|
||||
sudo_cmd[0] = sudo_path
|
||||
|
||||
# sudo_pythonpath indicates if we should set the
|
||||
# PYTHONPATH environment variable when elevating
|
||||
# privileges. This can be adjusted with the
|
||||
# --no-sudo-pythonpath option.
|
||||
if sudo_pythonpath:
|
||||
pp_prefix = ['/usr/bin/env',
|
||||
'PYTHONPATH=%s' %
|
||||
os.path.dirname(os.path.dirname(__file__))]
|
||||
sudo_cmd = sudo_cmd + pp_prefix
|
||||
doas_cmd = doas_cmd + pp_prefix
|
||||
|
||||
# Final order should be: sudo/doas command, env
|
||||
# pythonpath, and then argvbase (sshuttle command).
|
||||
sudo_cmd = sudo_cmd + argvbase
|
||||
doas_cmd = doas_cmd + argvbase
|
||||
|
||||
# If we can find doas and not sudo or if we are on
|
||||
# OpenBSD, try using doas first.
|
||||
if (doas_path and not sudo_path) or platform.platform().startswith('OpenBSD'):
|
||||
argv_tries = [doas_cmd, sudo_cmd, argvbase]
|
||||
else:
|
||||
# Linux typically uses sudo; OpenBSD uses doas. However, some
|
||||
# Linux distributions are starting to use doas.
|
||||
sudo_cmd = ['sudo', '-p', '[local sudo] Password: ']
|
||||
doas_cmd = ['doas']
|
||||
|
||||
# For clarity, try to replace executable name with the
|
||||
# full path.
|
||||
doas_path = which("doas")
|
||||
if doas_path:
|
||||
doas_cmd[0] = doas_path
|
||||
sudo_path = which("sudo")
|
||||
if sudo_path:
|
||||
sudo_cmd[0] = sudo_path
|
||||
|
||||
# sudo_pythonpath indicates if we should set the
|
||||
# PYTHONPATH environment variable when elevating
|
||||
# privileges. This can be adjusted with the
|
||||
# --no-sudo-pythonpath option.
|
||||
if sudo_pythonpath:
|
||||
pp_prefix = ['/usr/bin/env',
|
||||
'PYTHONPATH=%s' %
|
||||
os.path.dirname(os.path.dirname(__file__))]
|
||||
sudo_cmd = sudo_cmd + pp_prefix
|
||||
doas_cmd = doas_cmd + pp_prefix
|
||||
|
||||
# Final order should be: sudo/doas command, env
|
||||
# pythonpath, and then argvbase (sshuttle command).
|
||||
sudo_cmd = sudo_cmd + argvbase
|
||||
doas_cmd = doas_cmd + argvbase
|
||||
|
||||
# If we can find doas and not sudo or if we are on
|
||||
# OpenBSD, try using doas first.
|
||||
if (doas_path and not sudo_path) or platform.platform().startswith('OpenBSD'):
|
||||
argv_tries = [doas_cmd, sudo_cmd, argvbase]
|
||||
else:
|
||||
argv_tries = [sudo_cmd, doas_cmd, argvbase]
|
||||
argv_tries = [sudo_cmd, doas_cmd, argvbase]
|
||||
|
||||
# Try all commands in argv_tries in order. If a command
|
||||
# produces an error, try the next one. If command is
|
||||
@ -874,7 +874,7 @@ def main(listenip_v6, listenip_v4,
|
||||
|
||||
# listenip_v4 contains user specified value or it is set to "auto".
|
||||
if listenip_v4 == "auto":
|
||||
listenip_v4 = ('127.0.0.1' if avail.loopback_port else '0.0.0.0', 0)
|
||||
listenip_v4 = ('127.0.0.1' if avail.loopback_proxy_port else '0.0.0.0', 0)
|
||||
debug1("Using default IPv4 listen address " + listenip_v4[0])
|
||||
|
||||
# listenip_v6 is...
|
||||
@ -885,7 +885,7 @@ def main(listenip_v6, listenip_v4,
|
||||
debug1("IPv6 disabled by --disable-ipv6")
|
||||
if listenip_v6 == "auto":
|
||||
if avail.ipv6:
|
||||
listenip_v6 = ('::1' if avail.loopback_port else '::', 0)
|
||||
listenip_v6 = ('::1' if avail.loopback_proxy_port else '::', 0)
|
||||
debug1("IPv6 enabled: Using default IPv6 listen address " + listenip_v6[0])
|
||||
else:
|
||||
debug1("IPv6 disabled since it isn't supported by method "
|
||||
|
@ -242,7 +242,7 @@ def is_admin_user():
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
# TODO(nom3ad): for sys.platform == 'linux', support capabilities check for non-root users. (CAP_NET_ADMIN might be enough?)
|
||||
# TODO(nom3ad): for sys.platform == 'linux', check capabilities for non-root users. (CAP_NET_ADMIN might be enough?)
|
||||
return os.getuid() == 0
|
||||
|
||||
|
||||
|
@ -46,7 +46,7 @@ class BaseMethod(object):
|
||||
@staticmethod
|
||||
def get_supported_features():
|
||||
result = Features()
|
||||
result.loopback_port = True
|
||||
result.loopback_proxy_port = True
|
||||
result.ipv4 = True
|
||||
result.ipv6 = False
|
||||
result.udp = False
|
||||
|
@ -15,7 +15,7 @@ import traceback
|
||||
|
||||
|
||||
from sshuttle.methods import BaseMethod
|
||||
from sshuttle.helpers import debug3, log, debug1, debug2, get_verbose_level, Fatal
|
||||
from sshuttle.helpers import debug3, debug1, debug2, get_verbose_level, Fatal
|
||||
|
||||
try:
|
||||
# https://reqrypt.org/windivert-doc.html#divert_iphdr
|
||||
@ -47,6 +47,10 @@ class IPFamily(IntEnum):
|
||||
IPv4 = socket.AF_INET
|
||||
IPv6 = socket.AF_INET6
|
||||
|
||||
@staticmethod
|
||||
def from_ip_version(version):
|
||||
return IPFamily.IPv6 if version == 4 else IPFamily.IPv4
|
||||
|
||||
@property
|
||||
def filter(self):
|
||||
return "ip" if self == socket.AF_INET else "ipv6"
|
||||
@ -280,7 +284,7 @@ class Method(BaseMethod):
|
||||
def __init__(self, name):
|
||||
super().__init__(name)
|
||||
|
||||
def _get_bind_addresses_for_port(self, port, family):
|
||||
def _get_bind_address_for_port(self, port, family):
|
||||
proto = "TCPv6" if family.version == 6 else "TCP"
|
||||
for line in subprocess.check_output(["netstat", "-a", "-n", "-p", proto]).decode().splitlines():
|
||||
try:
|
||||
@ -293,7 +297,7 @@ class Method(BaseMethod):
|
||||
raise Fatal("Could not find listening address for {}/{}".format(port, proto))
|
||||
|
||||
def setup_firewall(self, proxy_port, dnsport, nslist, family, subnets, udp, user, group, tmark):
|
||||
log(f"{proxy_port=}, {dnsport=}, {nslist=}, {family=}, {subnets=}, {udp=}, {user=}, {tmark=}")
|
||||
debug2(f"{proxy_port=}, {dnsport=}, {nslist=}, {family=}, {subnets=}, {udp=}, {user=}, {tmark=}")
|
||||
|
||||
if nslist or user or udp:
|
||||
raise NotImplementedError()
|
||||
@ -304,18 +308,21 @@ class Method(BaseMethod):
|
||||
# using loopback only proxy binding won't work with windivert.
|
||||
# See: https://github.com/basil00/Divert/issues/17#issuecomment-341100167 https://github.com/basil00/Divert/issues/82)
|
||||
# As a workaround, finding another interface ip instead. (client should not bind proxy to loopback address)
|
||||
local_addr = self._get_bind_addresses_for_port(proxy_port, family)
|
||||
for addr in (ip_address(info[4][0]) for info in socket.getaddrinfo(socket.gethostname(), None)):
|
||||
if addr.version != family.version or addr.is_loopback or addr.is_link_local:
|
||||
continue
|
||||
if local_addr.is_unspecified or local_addr == addr:
|
||||
proxy_ip = addr.exploded
|
||||
debug2("Found non loopback address to connect to proxy: " + proxy_ip)
|
||||
break
|
||||
proxy_bind_addr = self._get_bind_address_for_port(proxy_port, family)
|
||||
if proxy_bind_addr.is_loopback:
|
||||
raise Fatal("Windivert method requires proxy to be reachable by a non loopback address.")
|
||||
if not proxy_bind_addr.is_unspecified:
|
||||
proxy_ip = proxy_bind_addr.exploded
|
||||
else:
|
||||
raise Fatal("Windivert method requires proxy to be reachable by a non loopback address."
|
||||
f"No addersss found for {family.name}")
|
||||
|
||||
local_addresses = [ip_address(info[4][0]) for info in socket.getaddrinfo(socket.gethostname(), 0, family=family)]
|
||||
for addr in local_addresses:
|
||||
if not addr.is_loopback and not addr.is_link_local:
|
||||
proxy_ip = addr.exploded
|
||||
break
|
||||
else:
|
||||
raise Fatal("Windivert method requires proxy to be reachable by a non loopback address."
|
||||
f"No address found for {family.name} in {local_addresses}")
|
||||
debug2("Found non loopback address to connect to proxy: " + proxy_ip)
|
||||
subnet_addresses = []
|
||||
for (_, mask, exclude, network_addr, fport, lport) in subnets:
|
||||
if exclude:
|
||||
@ -357,9 +364,11 @@ class Method(BaseMethod):
|
||||
|
||||
def get_supported_features(self):
|
||||
result = super(Method, self).get_supported_features()
|
||||
result.loopback_port = False
|
||||
result.loopback_proxy_port = False
|
||||
result.user = False
|
||||
result.dns = False
|
||||
# ipv6 only able to support with Windivert 2.x due to bugs in filter parsing
|
||||
# TODO(nom3ad): Enable ipv6 once https://github.com/ffalcinelli/pydivert/pull/57 merged
|
||||
result.ipv6 = False
|
||||
return result
|
||||
|
||||
@ -463,19 +472,20 @@ class Method(BaseMethod):
|
||||
for pkt in w:
|
||||
verbose >= 3 and debug3("[INGRESS] " + repr_pkt(pkt))
|
||||
if pkt.tcp.syn and pkt.tcp.ack:
|
||||
# SYN+ACK received (connection established)
|
||||
# SYN+ACK received (connection established from proxy
|
||||
conn = self.conntrack.update(IPProtocol.TCP, pkt.dst_addr, pkt.dst_port, ConnState.TCP_ESTABLISHED)
|
||||
elif pkt.tcp.rst:
|
||||
# RST received - Abrupt connection teardown initiated by otherside. We don't expect anymore packets
|
||||
# RST received - Abrupt connection teardown initiated by proxy. Don't expect anymore packets
|
||||
conn = self.conntrack.remove(IPProtocol.TCP, pkt.dst_addr, pkt.dst_port)
|
||||
# https://wiki.wireshark.org/TCP-4-times-close.md
|
||||
elif pkt.tcp.fin and pkt.tcp.ack:
|
||||
# FIN+ACK received (Passive close by otherside. We don't expect any more packets. Otherside expects an ACK)
|
||||
# FIN+ACK received (Passive close by proxy. Don't expect any more packets. proxy expects an ACK)
|
||||
conn = self.conntrack.remove(IPProtocol.TCP, pkt.dst_addr, pkt.dst_port)
|
||||
elif pkt.tcp.fin:
|
||||
# FIN received (Otherside initiated graceful close. We expects a final ACK for a FIN packet)
|
||||
# FIN received (proxy initiated graceful close. Expect a final ACK for a FIN packet)
|
||||
conn = self.conntrack.update(IPProtocol.TCP, pkt.dst_addr, pkt.dst_port, ConnState.TCP_CLOSE_WAIT)
|
||||
else:
|
||||
# data fragments and ACKs
|
||||
conn = self.conntrack.get(socket.IPPROTO_TCP, pkt.dst_addr, pkt.dst_port)
|
||||
if not conn:
|
||||
verbose >= 2 and debug2("Unexpected packet: " + repr_pkt(pkt))
|
||||
|
@ -262,7 +262,7 @@ def connect(ssh_cmd, rhostport, python, stderr, add_cmd_delimiter, options):
|
||||
threading.Thread(target=stream_sock_to_stdin, name='stream_sock_to_stdin', daemon=True).start()
|
||||
return s2.makefile("rb", buffering=0), s2.makefile("wb", buffering=0)
|
||||
|
||||
# https://stackoverflow.com/questions/48671215/howto-workaround-of-close-fds-true-and-redirect-stdout-stderr-on-windows
|
||||
# See: stackoverflow.com/questions/48671215/howto-workaround-of-close-fds-true-and-redirect-stdout-stderr-on-windows
|
||||
close_fds = False if sys.platform == 'win32' else True
|
||||
|
||||
debug2("executing: %r" % argv)
|
||||
|
Loading…
Reference in New Issue
Block a user