mirror of
https://github.com/sshuttle/sshuttle.git
synced 2024-11-21 23:43:18 +01:00
refactoring to make it better structured
This commit is contained in:
parent
49f46cd528
commit
900acc3ac7
@ -25,5 +25,4 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
default:
|
default:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
enable_ipv6: true
|
# internal: true
|
||||||
internal: true
|
|
@ -1,15 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
|
|
||||||
node=$1
|
|
||||||
|
|
||||||
if [[ ! $node =~ [1-9]+ ]]; then
|
|
||||||
echo "node argument missing. should be '1' , '2' etc"
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
shift
|
|
||||||
|
|
||||||
ip="10.55.$node.77"
|
|
||||||
|
|
||||||
exec iperf3 --client "$ip" --port 5001
|
|
37
hack/exec-tool
Executable file
37
hack/exec-tool
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
tool=${1?:"tool argument missing. should be one of iperf3,ping,curl,ab"}
|
||||||
|
node=${2?:"node argument missing. should be '1' , '2' etc"}
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
ip="10.55.$node.77"
|
||||||
|
connect_timeout_sec=3
|
||||||
|
|
||||||
|
function with_set_x() {
|
||||||
|
set -x
|
||||||
|
"$@"
|
||||||
|
{
|
||||||
|
ec=$?
|
||||||
|
set +x
|
||||||
|
return $ec
|
||||||
|
} 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$tool" in
|
||||||
|
ping)
|
||||||
|
with_set_x exec ping -W $connect_timeout_sec "$@" "$ip"
|
||||||
|
;;
|
||||||
|
iperf3)
|
||||||
|
port=5001
|
||||||
|
with_set_x exec iperf3 --client "$ip" --port=$port --connect-timeout=$connect_timeout_sec "$@"
|
||||||
|
;;
|
||||||
|
curl)
|
||||||
|
port=8080
|
||||||
|
with_set_x exec curl "http://$ip:$port/" -v --connect-timeout $connect_timeout_sec "$@"
|
||||||
|
;;
|
||||||
|
ab)
|
||||||
|
port=8080
|
||||||
|
with_set_x exec ab -n 100 -c 20 -s $connect_timeout_sec "$@" "http://$ip:$port/"
|
||||||
|
;;
|
||||||
|
esac
|
40
hack/run-benchmark
Executable file
40
hack/run-benchmark
Executable file
@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
function with_set_x() {
|
||||||
|
set -x
|
||||||
|
"$@"
|
||||||
|
{ ec=$?; set +x;return $ec; } 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
./test-bed up -d
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
benchmark() {
|
||||||
|
local sshuttle_bin="${1?:}"
|
||||||
|
echo -e "\n======== Benchmarking sshuttle: $sshuttle_bin ========"
|
||||||
|
if [[ "$sshuttle_bin" == dev ]]; then
|
||||||
|
sshuttle_bin="../run"
|
||||||
|
fi
|
||||||
|
SSHUTTLE_BIN=$sshuttle_bin ./exec-sshuttle 1 --listen 55771 &
|
||||||
|
sshuttle_pid=$!
|
||||||
|
trap 'kill -0 $sshuttle_pid &>/dev/null && kill -15 $sshuttle_pid' EXIT
|
||||||
|
while ! nc -z localhost 55771; do sleep 0.1; done
|
||||||
|
sleep 1
|
||||||
|
./exec-tool iperf3 1 --time=4
|
||||||
|
with_set_x kill -15 $sshuttle_pid
|
||||||
|
wait $sshuttle_pid || true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if [[ "$1" ]]; then
|
||||||
|
benchmark "$1"
|
||||||
|
else
|
||||||
|
benchmark "${SSHUTTLE_BIN:-/bin/sshuttle}"
|
||||||
|
benchmark dev
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
@ -5,16 +5,20 @@ set -e
|
|||||||
|
|
||||||
echo -e ">>> Setting up $(hostname) | id: $(id) | $(python --version) \nip: $(ip a)\n route: $(ip r)"
|
echo -e ">>> Setting up $(hostname) | id: $(id) | $(python --version) \nip: $(ip a)\n route: $(ip r)"
|
||||||
|
|
||||||
|
function with_set_x() {
|
||||||
|
set -x
|
||||||
|
"$@"
|
||||||
|
{ ec=$?; set +x;return $ec; } 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
iface="$(ip route | awk '/default/ { print $5 }')"
|
iface="$(ip route | awk '/default/ { print $5 }')"
|
||||||
default_gw="$(ip route | awk '/default/ { print $3 }')"
|
default_gw="$(ip route | awk '/default/ { print $3 }')"
|
||||||
for addr in ${IP_ADDRESSES//,/ }; do
|
for addr in ${IP_ADDRESSES//,/ }; do
|
||||||
echo ">>> Adding $addr to interface $iface"
|
echo ">>> Adding $addr to interface $iface"
|
||||||
net_addr=$(ipcalc -n "$addr" | awk -F= '{print $2}')
|
net_addr=$(ipcalc -n "$addr" | awk -F= '{print $2}')
|
||||||
(
|
with_set_x ip addr add "$addr" dev "$iface"
|
||||||
set -ex
|
with_set_x ip route add "$net_addr" via "$default_gw" dev "$iface" # so that sshuttle -N can discover routes
|
||||||
ip addr add "$addr" dev "$iface"
|
|
||||||
ip route add "$net_addr" via "$default_gw" dev "$iface" # so that sshuttle -N can discover routes
|
|
||||||
)
|
|
||||||
done
|
done
|
||||||
|
|
||||||
echo ">>> Starting iperf3 server"
|
echo ">>> Starting iperf3 server"
|
||||||
|
30
hack/test-bed
Executable file
30
hack/test-bed
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
|
||||||
|
if [[ -z $1 || $1 = -* ]]; then
|
||||||
|
set -- up "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
function with_set_x() {
|
||||||
|
set -x
|
||||||
|
"$@"
|
||||||
|
{ ec=$?; set +x;return $ec; } 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
function build() {
|
||||||
|
# podman build -t ghcr.io/sshuttle/sshuttle-testbed .
|
||||||
|
with_set_x docker build -t ghcr.io/sshuttle/sshuttle-testbed -f Containerfile .
|
||||||
|
}
|
||||||
|
|
||||||
|
function compose() {
|
||||||
|
# podman-compose "$@"
|
||||||
|
with_set_x docker compose "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if [[ $* = *--build* ]]; then
|
||||||
|
build
|
||||||
|
fi
|
||||||
|
compose "$@"
|
@ -1,9 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
|
|
||||||
# podman build -t ghcr.io/sshuttle/sshuttle-testbed .
|
|
||||||
# podman-compose up
|
|
||||||
|
|
||||||
docker build -t ghcr.io/sshuttle/sshuttle-testbed -f Containerfile .
|
|
||||||
docker compose up
|
|
@ -3,6 +3,9 @@ import sys
|
|||||||
import os
|
import os
|
||||||
from sshuttle.cmdline import main
|
from sshuttle.cmdline import main
|
||||||
from sshuttle.helpers import debug3
|
from sshuttle.helpers import debug3
|
||||||
|
from sshuttle import __version__
|
||||||
|
|
||||||
|
debug3("Starting cmd %r (pid:%s) | sshuttle: %s | Python: %s" % (sys.argv, os.getpid(), __version__, sys.version))
|
||||||
exit_code = main()
|
exit_code = main()
|
||||||
debug3("Exiting process %r (pid:%s) with code %s" % (sys.argv, os.getpid(), exit_code,))
|
debug3("Exiting cmd %r (pid:%s) with code %s" % (sys.argv, os.getpid(), exit_code,))
|
||||||
sys.exit(exit_code)
|
sys.exit(exit_code)
|
||||||
|
@ -3,24 +3,27 @@ import zlib
|
|||||||
import types
|
import types
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
verbosity = verbosity # noqa: F821 must be a previously defined global
|
stdin = stdin # type: typing.BinaryIO # noqa: F821 must be a previously defined global
|
||||||
|
verbosity = verbosity # type: int # noqa: F821 must be a previously defined global
|
||||||
if verbosity > 0:
|
if verbosity > 0:
|
||||||
sys.stderr.write(' s: Running server on remote host with %s (version %s)\n'
|
sys.stderr.write(' s: Running server on remote host with %s (version %s)\n'
|
||||||
% (sys.executable, platform.python_version()))
|
% (sys.executable, platform.python_version()))
|
||||||
|
|
||||||
z = zlib.decompressobj()
|
z = zlib.decompressobj()
|
||||||
|
|
||||||
while 1:
|
while 1:
|
||||||
name = sys.stdin.readline().strip()
|
name = stdin.readline().strip()
|
||||||
if name:
|
if name:
|
||||||
# python2 compat: in python2 sys.stdin.readline().strip() -> str
|
# python2 compat: in python2 stdin.readline().strip() -> str
|
||||||
# in python3 sys.stdin.readline().strip() -> bytes
|
# in python3 stdin.readline().strip() -> bytes
|
||||||
# (see #481)
|
# (see #481)
|
||||||
if sys.version_info >= (3, 0):
|
if sys.version_info >= (3, 0):
|
||||||
name = name.decode("ASCII")
|
name = name.decode("ASCII")
|
||||||
nbytes = int(sys.stdin.readline())
|
nbytes = int(stdin.readline())
|
||||||
if verbosity >= 2:
|
if verbosity >= 2:
|
||||||
sys.stderr.write(' s: assembling %r (%d bytes)\n'
|
sys.stderr.write(' s: assembling %r (%d bytes)\n'
|
||||||
% (name, nbytes))
|
% (name, nbytes))
|
||||||
content = z.decompress(sys.stdin.read(nbytes))
|
content = z.decompress(stdin.read(nbytes))
|
||||||
|
|
||||||
module = types.ModuleType(name)
|
module = types.ModuleType(name)
|
||||||
parents = name.rsplit(".", 1)
|
parents = name.rsplit(".", 1)
|
||||||
@ -44,6 +47,7 @@ sshuttle.helpers.verbose = verbosity
|
|||||||
|
|
||||||
import sshuttle.cmdline_options as options # noqa: E402
|
import sshuttle.cmdline_options as options # noqa: E402
|
||||||
from sshuttle.server import main # noqa: E402
|
from sshuttle.server import main # noqa: E402
|
||||||
|
|
||||||
main(options.latency_control, options.latency_buffer_size,
|
main(options.latency_control, options.latency_buffer_size,
|
||||||
options.auto_hosts, options.to_nameserver,
|
options.auto_hosts, options.to_nameserver,
|
||||||
options.auto_nets)
|
options.auto_nets)
|
||||||
|
@ -391,14 +391,14 @@ class FirewallClient:
|
|||||||
'Command=%r' % (skipped_text, self.argv))
|
'Command=%r' % (skipped_text, self.argv))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
method_name = line.strip()[6:]
|
method_name = line[6:-1]
|
||||||
self.method = get_method(method_name.decode("ASCII"))
|
self.method = get_method(method_name.decode("ASCII"))
|
||||||
self.method.set_firewall(self)
|
self.method.set_firewall(self)
|
||||||
success = True
|
success = True
|
||||||
break
|
break
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
raise Fatal("All attempts to run firewall client with elevated privileges were failed.")
|
raise Fatal("All attempts to run firewall client process with elevated privileges were failed.")
|
||||||
|
|
||||||
def setup(self, subnets_include, subnets_exclude, nslist,
|
def setup(self, subnets_include, subnets_exclude, nslist,
|
||||||
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4, udp,
|
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4, udp,
|
||||||
@ -461,9 +461,9 @@ class FirewallClient:
|
|||||||
(udp, user, group, bytes(self.tmark, 'ascii'), os.getpid()))
|
(udp, user, group, bytes(self.tmark, 'ascii'), os.getpid()))
|
||||||
self.pfile.flush()
|
self.pfile.flush()
|
||||||
|
|
||||||
line = self.pfile.readline().strip()
|
line = self.pfile.readline()
|
||||||
self.check()
|
self.check()
|
||||||
if line != b'STARTED':
|
if line != b'STARTED\n':
|
||||||
raise Fatal('%r expected STARTED, got %r' % (self.argv, line))
|
raise Fatal('%r expected STARTED, got %r' % (self.argv, line))
|
||||||
|
|
||||||
def sethostip(self, hostname, ip):
|
def sethostip(self, hostname, ip):
|
||||||
@ -615,7 +615,7 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
|||||||
debug1('Connecting to server...')
|
debug1('Connecting to server...')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
(serverproc, serversock) = ssh.connect(
|
(serverproc, rfile, wfile) = ssh.connect(
|
||||||
ssh_cmd, remotename, python,
|
ssh_cmd, remotename, python,
|
||||||
stderr=ssyslog._p and ssyslog._p.stdin,
|
stderr=ssyslog._p and ssyslog._p.stdin,
|
||||||
add_cmd_delimiter=add_cmd_delimiter,
|
add_cmd_delimiter=add_cmd_delimiter,
|
||||||
@ -630,7 +630,6 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
|
|||||||
raise Fatal("failed to establish ssh session (1)")
|
raise Fatal("failed to establish ssh session (1)")
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
rfile, wfile = serversock.makefile("rb"), serversock.makefile("wb")
|
|
||||||
mux = Mux(rfile, wfile)
|
mux = Mux(rfile, wfile)
|
||||||
handlers.append(mux)
|
handlers.append(mux)
|
||||||
|
|
||||||
@ -887,10 +886,7 @@ def main(listenip_v6, listenip_v4,
|
|||||||
|
|
||||||
# listenip_v4 contains user specified value or it is set to "auto".
|
# listenip_v4 contains user specified value or it is set to "auto".
|
||||||
if listenip_v4 == "auto":
|
if listenip_v4 == "auto":
|
||||||
if sys.platform == 'win32':
|
listenip_v4 = ('127.0.0.1' if avail.loopback_port else '0.0.0.0', 0)
|
||||||
listenip_v4 = ('0.0.0.0', 0) # windivert method won't work with loopback interface
|
|
||||||
else:
|
|
||||||
listenip_v4 = ('127.0.0.1', 0)
|
|
||||||
debug1("Using default IPv4 listen address " + listenip_v4[0])
|
debug1("Using default IPv4 listen address " + listenip_v4[0])
|
||||||
|
|
||||||
# listenip_v6 is...
|
# listenip_v6 is...
|
||||||
@ -901,10 +897,7 @@ def main(listenip_v6, listenip_v4,
|
|||||||
debug1("IPv6 disabled by --disable-ipv6")
|
debug1("IPv6 disabled by --disable-ipv6")
|
||||||
if listenip_v6 == "auto":
|
if listenip_v6 == "auto":
|
||||||
if avail.ipv6:
|
if avail.ipv6:
|
||||||
if sys.platform == 'win32':
|
listenip_v6 = ('::1' if avail.loopback_port else '::', 0)
|
||||||
listenip_v6 = ('::', 0) # windivert method won't work with loopback interface
|
|
||||||
else:
|
|
||||||
listenip_v6 = ('::1', 0)
|
|
||||||
debug1("IPv6 enabled: Using default IPv6 listen address " + listenip_v6[0])
|
debug1("IPv6 enabled: Using default IPv6 listen address " + listenip_v6[0])
|
||||||
else:
|
else:
|
||||||
debug1("IPv6 disabled since it isn't supported by method "
|
debug1("IPv6 disabled since it isn't supported by method "
|
||||||
|
@ -84,14 +84,17 @@ def firewall_exit(signum, frame):
|
|||||||
# the typical exit process as described above.
|
# the typical exit process as described above.
|
||||||
global sshuttle_pid
|
global sshuttle_pid
|
||||||
if sshuttle_pid:
|
if sshuttle_pid:
|
||||||
debug1("Relaying SIGINT to sshuttle process %d\n" % sshuttle_pid)
|
debug1("Relaying interupt signal to sshuttle process %d\n" % sshuttle_pid)
|
||||||
os.kill(sshuttle_pid, signal.SIGINT)
|
if sys.platform == 'win32':
|
||||||
|
sig = signal.CTRL_C_EVENT
|
||||||
|
else:
|
||||||
|
sig = signal.SIGINT
|
||||||
|
os.kill(sshuttle_pid, sig)
|
||||||
|
|
||||||
|
|
||||||
# Isolate function that needs to be replaced for tests
|
def _setup_daemon_for_unix_like():
|
||||||
def _setup_daemon_unix():
|
|
||||||
if not is_admin_user():
|
if not is_admin_user():
|
||||||
raise Fatal('You must be root (or enable su/sudo) to set the firewall')
|
raise Fatal('You must have root privileges (or enable su/sudo) to set the firewall')
|
||||||
|
|
||||||
# don't disappear if our controlling terminal or stdout/stderr
|
# don't disappear if our controlling terminal or stdout/stderr
|
||||||
# disappears; we still have to clean up.
|
# disappears; we still have to clean up.
|
||||||
@ -115,7 +118,7 @@ def _setup_daemon_unix():
|
|||||||
return sys.stdin, sys.stdout
|
return sys.stdin, sys.stdout
|
||||||
|
|
||||||
|
|
||||||
def _setup_daemon_windows():
|
def _setup_daemon_for_windows():
|
||||||
if not is_admin_user():
|
if not is_admin_user():
|
||||||
raise Fatal('You must be administrator to set the firewall')
|
raise Fatal('You must be administrator to set the firewall')
|
||||||
|
|
||||||
@ -128,7 +131,7 @@ def _setup_daemon_windows():
|
|||||||
debug3('Using shared socket for communicating with sshuttle client process')
|
debug3('Using shared socket for communicating with sshuttle client process')
|
||||||
socket_share_data_b64 = line[len(socket_share_data_prefix):]
|
socket_share_data_b64 = line[len(socket_share_data_prefix):]
|
||||||
socket_share_data = base64.b64decode(socket_share_data_b64)
|
socket_share_data = base64.b64decode(socket_share_data_b64)
|
||||||
sock = socket.fromshare(socket_share_data)
|
sock = socket.fromshare(socket_share_data) # type: socket.socket
|
||||||
sys.stdin = io.TextIOWrapper(sock.makefile('rb', buffering=0))
|
sys.stdin = io.TextIOWrapper(sock.makefile('rb', buffering=0))
|
||||||
sys.stdout = io.TextIOWrapper(sock.makefile('wb', buffering=0), write_through=True)
|
sys.stdout = io.TextIOWrapper(sock.makefile('wb', buffering=0), write_through=True)
|
||||||
sock.close()
|
sock.close()
|
||||||
@ -140,10 +143,11 @@ def _setup_daemon_windows():
|
|||||||
return sys.stdin, sys.stdout
|
return sys.stdin, sys.stdout
|
||||||
|
|
||||||
|
|
||||||
|
# Isolate function that needs to be replaced for tests
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
setup_daemon = _setup_daemon_windows
|
setup_daemon = _setup_daemon_for_windows
|
||||||
else:
|
else:
|
||||||
setup_daemon = _setup_daemon_unix
|
setup_daemon = _setup_daemon_for_unix_like
|
||||||
|
|
||||||
|
|
||||||
# Note that we're sorting in a very particular order:
|
# Note that we're sorting in a very particular order:
|
||||||
@ -226,10 +230,9 @@ def main(method_name, syslog):
|
|||||||
try:
|
try:
|
||||||
line = stdin.readline(128)
|
line = stdin.readline(128)
|
||||||
if not line:
|
if not line:
|
||||||
# parent probably exited
|
return # parent probably exited
|
||||||
return
|
except ConnectionResetError as e:
|
||||||
except IOError as e:
|
# On windows, ConnectionResetError is thrown when parent process closes it's socket pair end
|
||||||
# On windows, this ConnectionResetError is thrown when parent process closes it's socket pair end
|
|
||||||
debug3('read from stdin failed: %s' % (e,))
|
debug3('read from stdin failed: %s' % (e,))
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -343,13 +346,13 @@ def main(method_name, syslog):
|
|||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if sys.platform != 'win32':
|
if sys.platform == 'linux':
|
||||||
flush_systemd_dns_cache()
|
flush_systemd_dns_cache()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stdout.write('STARTED\n')
|
stdout.write('STARTED\n')
|
||||||
stdout.flush()
|
stdout.flush()
|
||||||
except IOError as e:
|
except IOError as e: # the parent process probably died
|
||||||
debug3('write to stdout failed: %s' % (e,))
|
debug3('write to stdout failed: %s' % (e,))
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -410,7 +413,7 @@ def main(method_name, syslog):
|
|||||||
except Exception:
|
except Exception:
|
||||||
debug2('An error occurred, ignoring it.')
|
debug2('An error occurred, ignoring it.')
|
||||||
|
|
||||||
if sys.platform != 'win32':
|
if sys.platform == 'linux':
|
||||||
try:
|
try:
|
||||||
flush_systemd_dns_cache()
|
flush_systemd_dns_cache()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -3,6 +3,10 @@ import socket
|
|||||||
import errno
|
import errno
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
if sys.platform != "win32":
|
||||||
|
import fcntl
|
||||||
|
|
||||||
logprefix = ''
|
logprefix = ''
|
||||||
verbose = 0
|
verbose = 0
|
||||||
|
|
||||||
@ -14,10 +18,10 @@ def b(s):
|
|||||||
def log(s):
|
def log(s):
|
||||||
global logprefix
|
global logprefix
|
||||||
try:
|
try:
|
||||||
try:
|
sys.stdout.flush()
|
||||||
sys.stdout.flush()
|
except IOError:
|
||||||
except (IOError, ValueError):
|
pass
|
||||||
pass
|
try:
|
||||||
# Put newline at end of string if line doesn't have one.
|
# Put newline at end of string if line doesn't have one.
|
||||||
if not s.endswith("\n"):
|
if not s.endswith("\n"):
|
||||||
s = s+"\n"
|
s = s+"\n"
|
||||||
@ -234,4 +238,19 @@ def is_admin_user():
|
|||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# TODO(nom3ad): for sys.platform == 'linux', support capabilities check for non-root users. (CAP_NET_ADMIN might be enough?)
|
||||||
return os.getuid() == 0
|
return os.getuid() == 0
|
||||||
|
|
||||||
|
|
||||||
|
def set_non_blocking_io(fd):
|
||||||
|
if sys.platform != "win32":
|
||||||
|
try:
|
||||||
|
os.set_blocking(fd, False)
|
||||||
|
except AttributeError:
|
||||||
|
# python < 3.5
|
||||||
|
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||||
|
flags |= os.O_NONBLOCK
|
||||||
|
fcntl.fcntl(fd, fcntl.F_SETFL, flags)
|
||||||
|
else:
|
||||||
|
_sock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
_sock.setblocking(False)
|
||||||
|
@ -46,6 +46,7 @@ class BaseMethod(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_supported_features():
|
def get_supported_features():
|
||||||
result = Features()
|
result = Features()
|
||||||
|
result.loopback_port = True
|
||||||
result.ipv4 = True
|
result.ipv4 = True
|
||||||
result.ipv6 = False
|
result.ipv6 = False
|
||||||
result.udp = False
|
result.udp = False
|
||||||
|
@ -350,6 +350,7 @@ class Method(BaseMethod):
|
|||||||
|
|
||||||
def get_supported_features(self):
|
def get_supported_features(self):
|
||||||
result = super(Method, self).get_supported_features()
|
result = super(Method, self).get_supported_features()
|
||||||
|
result.loopback_port = False
|
||||||
result.user = False
|
result.user = False
|
||||||
result.dns = False
|
result.dns = False
|
||||||
result.ipv6 = False
|
result.ipv6 = False
|
||||||
@ -444,7 +445,7 @@ class Method(BaseMethod):
|
|||||||
if not ip_filters:
|
if not ip_filters:
|
||||||
raise Fatal("At least ipv4 or ipv6 address is expected")
|
raise Fatal("At least ipv4 or ipv6 address is expected")
|
||||||
filter = f"{direction} and {proto.filter} and ({' or '.join(ip_filters)}) and tcp.SrcPort=={self.proxy_port}"
|
filter = f"{direction} and {proto.filter} and ({' or '.join(ip_filters)}) and tcp.SrcPort=={self.proxy_port}"
|
||||||
debug2(f"[INGRESS] {filter=}")
|
debug1(f"[INGRESS] {filter=}")
|
||||||
with pydivert.WinDivert(filter) as w:
|
with pydivert.WinDivert(filter) as w:
|
||||||
ready_cb()
|
ready_cb()
|
||||||
for pkt in w:
|
for pkt in w:
|
||||||
|
@ -235,9 +235,14 @@ parser.add_argument(
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
method_choices = ["auto", "windivert"]
|
||||||
|
else:
|
||||||
|
method_choices = ["auto", "nat", "tproxy", "pf", "ipfw"]
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--method",
|
"--method",
|
||||||
choices=["auto", "nat", "nft", "tproxy", "pf", "ipfw"] if sys.platform != 'win32' else ["auto", "windivert"],
|
choices=method_choices,
|
||||||
metavar="TYPE",
|
metavar="TYPE",
|
||||||
default="auto",
|
default="auto",
|
||||||
help="""
|
help="""
|
||||||
|
@ -281,7 +281,7 @@ def main(latency_control, latency_buffer_size, auto_hosts, to_nameserver,
|
|||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
handlers = []
|
handlers = []
|
||||||
mux = Mux(sys.stdin, sys.stdout)
|
mux = Mux(sys.stdin.buffer, sys.stdout.buffer)
|
||||||
handlers.append(mux)
|
handlers.append(mux)
|
||||||
|
|
||||||
debug1('auto-nets:' + str(auto_nets))
|
debug1('auto-nets:' + str(auto_nets))
|
||||||
|
@ -115,8 +115,8 @@ def connect(ssh_cmd, rhostport, python, stderr, add_cmd_delimiter, options):
|
|||||||
pyscript = r"""
|
pyscript = r"""
|
||||||
import sys, os;
|
import sys, os;
|
||||||
verbosity=%d;
|
verbosity=%d;
|
||||||
sys.stdin = os.fdopen(0, "rb");
|
stdin = os.fdopen(0, "rb");
|
||||||
exec(compile(sys.stdin.read(%d), "assembler.py", "exec"));
|
exec(compile(stdin.read(%d), "assembler.py", "exec"));
|
||||||
sys.exit(98);
|
sys.exit(98);
|
||||||
""" % (helpers.verbose or 0, len(content))
|
""" % (helpers.verbose or 0, len(content))
|
||||||
pyscript = re.sub(r'\s+', ' ', pyscript.strip())
|
pyscript = re.sub(r'\s+', ' ', pyscript.strip())
|
||||||
@ -213,24 +213,26 @@ def connect(ssh_cmd, rhostport, python, stderr, add_cmd_delimiter, options):
|
|||||||
s2.close()
|
s2.close()
|
||||||
s1.close()
|
s1.close()
|
||||||
|
|
||||||
def get_serversock():
|
def get_server_io():
|
||||||
os.close(pstdin)
|
os.close(pstdin)
|
||||||
os.close(pstdout)
|
os.close(pstdout)
|
||||||
return s2
|
return s2.makefile("rb", buffering=0), s2.makefile("wb", buffering=0)
|
||||||
else:
|
else:
|
||||||
# In Windows python implementation it seems not possible to use sockets as subprocess stdio
|
# In Windows CPython, we can't use BSD sockets as subprocess stdio
|
||||||
# Also select.select() won't work on pipes.
|
# and select.select() used in ssnet.py won't work on Windows pipes.
|
||||||
# So we have to use both socketpair and pipes together along with reader/writer threads to
|
# So we have to use both socketpair (for select.select) and pipes (for subprocess.Popen) together
|
||||||
# stream data between them
|
# along with reader/writer threads to stream data between them
|
||||||
# NOTE: Their can be a way to use sockets as stdio with some hacks.
|
# NOTE: Their could be a better way. Need to investigate further on this.
|
||||||
|
# Either to use sockets as stdio for subprocess. Or to use pipes but with a select() alternative
|
||||||
# https://stackoverflow.com/questions/4993119/redirect-io-of-process-to-windows-socket
|
# https://stackoverflow.com/questions/4993119/redirect-io-of-process-to-windows-socket
|
||||||
|
|
||||||
(s1, s2) = socket.socketpair()
|
(s1, s2) = socket.socketpair()
|
||||||
pstdin = ssubprocess.PIPE
|
pstdin = ssubprocess.PIPE
|
||||||
pstdout = ssubprocess.PIPE
|
pstdout = ssubprocess.PIPE
|
||||||
|
|
||||||
preexec_fn = None
|
preexec_fn = None
|
||||||
|
|
||||||
def get_serversock():
|
def get_server_io():
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
def stream_stdout_to_sock():
|
def stream_stdout_to_sock():
|
||||||
@ -267,7 +269,7 @@ def connect(ssh_cmd, rhostport, python, stderr, add_cmd_delimiter, options):
|
|||||||
p = ssubprocess.Popen(argv, stdin=pstdin, stdout=pstdout, preexec_fn=preexec_fn,
|
p = ssubprocess.Popen(argv, stdin=pstdin, stdout=pstdout, preexec_fn=preexec_fn,
|
||||||
close_fds=close_fds, stderr=stderr, bufsize=0)
|
close_fds=close_fds, stderr=stderr, bufsize=0)
|
||||||
|
|
||||||
serversock = get_serversock()
|
rfile, wfile = get_server_io()
|
||||||
serversock.sendall(content)
|
wfile.write(content)
|
||||||
serversock.sendall(content2)
|
wfile.write(content2)
|
||||||
return p, serversock
|
return p, rfile, wfile
|
||||||
|
@ -5,10 +5,7 @@ import errno
|
|||||||
import select
|
import select
|
||||||
import os
|
import os
|
||||||
|
|
||||||
if sys.platform != "win32":
|
from sshuttle.helpers import b, log, debug1, debug2, debug3, Fatal, set_non_blocking_io
|
||||||
import fcntl
|
|
||||||
|
|
||||||
from sshuttle.helpers import b, log, debug1, debug2, debug3, Fatal
|
|
||||||
|
|
||||||
MAX_CHANNEL = 65535
|
MAX_CHANNEL = 65535
|
||||||
LATENCY_BUFFER_SIZE = 32768
|
LATENCY_BUFFER_SIZE = 32768
|
||||||
@ -215,10 +212,7 @@ class SockWrapper:
|
|||||||
return 0 # still connecting
|
return 0 # still connecting
|
||||||
self.wsock.setblocking(False)
|
self.wsock.setblocking(False)
|
||||||
try:
|
try:
|
||||||
if sys.platform == 'win32':
|
return _nb_clean(self.wsock.send, buf)
|
||||||
return _nb_clean(self.wsock.send, buf)
|
|
||||||
else:
|
|
||||||
return _nb_clean(os.write, self.wsock.fileno(), buf)
|
|
||||||
except OSError:
|
except OSError:
|
||||||
_, e = sys.exc_info()[:2]
|
_, e = sys.exc_info()[:2]
|
||||||
if e.errno == errno.EPIPE:
|
if e.errno == errno.EPIPE:
|
||||||
@ -241,10 +235,7 @@ class SockWrapper:
|
|||||||
return
|
return
|
||||||
self.rsock.setblocking(False)
|
self.rsock.setblocking(False)
|
||||||
try:
|
try:
|
||||||
if sys.platform == 'win32':
|
return _nb_clean(self.rsock.recv, 65536)
|
||||||
return _nb_clean(self.rsock.recv, 65536)
|
|
||||||
else:
|
|
||||||
return _nb_clean(os.read, self.rsock.fileno(), 65536)
|
|
||||||
except OSError:
|
except OSError:
|
||||||
_, e = sys.exc_info()[:2]
|
_, e = sys.exc_info()[:2]
|
||||||
self.seterr('uread: %s' % e)
|
self.seterr('uread: %s' % e)
|
||||||
@ -439,22 +430,9 @@ class Mux(Handler):
|
|||||||
callback(cmd, data)
|
callback(cmd, data)
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
if sys.platform != "win32":
|
set_non_blocking_io(self.wfile.fileno())
|
||||||
try:
|
|
||||||
os.set_blocking(self.wfile.fileno(), False)
|
|
||||||
except AttributeError:
|
|
||||||
# python < 3.5
|
|
||||||
flags = fcntl.fcntl(self.wfile.fileno(), fcntl.F_GETFL)
|
|
||||||
flags |= os.O_NONBLOCK
|
|
||||||
fcntl.fcntl(self.wfile.fileno(), fcntl.F_SETFL, flags)
|
|
||||||
else:
|
|
||||||
self.wfile.raw._sock.setblocking(False)
|
|
||||||
|
|
||||||
if self.outbuf and self.outbuf[0]:
|
if self.outbuf and self.outbuf[0]:
|
||||||
if sys.platform == 'win32':
|
wrote = _nb_clean(os.write, self.wfile.fileno(), self.outbuf[0])
|
||||||
wrote = _nb_clean(self.wfile.raw._sock.send, self.outbuf[0])
|
|
||||||
else:
|
|
||||||
wrote = _nb_clean(os.write, self.wfile.fileno(), self.outbuf[0])
|
|
||||||
debug2('mux wrote: %r/%d' % (wrote, len(self.outbuf[0])))
|
debug2('mux wrote: %r/%d' % (wrote, len(self.outbuf[0])))
|
||||||
if wrote:
|
if wrote:
|
||||||
self.outbuf[0] = self.outbuf[0][wrote:]
|
self.outbuf[0] = self.outbuf[0][wrote:]
|
||||||
@ -462,24 +440,11 @@ class Mux(Handler):
|
|||||||
self.outbuf[0:1] = []
|
self.outbuf[0:1] = []
|
||||||
|
|
||||||
def fill(self):
|
def fill(self):
|
||||||
if sys.platform != "win32":
|
set_non_blocking_io(self.rfile.fileno())
|
||||||
try:
|
|
||||||
os.set_blocking(self.rfile.fileno(), False)
|
|
||||||
except AttributeError:
|
|
||||||
# python < 3.5
|
|
||||||
flags = fcntl.fcntl(self.rfile.fileno(), fcntl.F_GETFL)
|
|
||||||
flags |= os.O_NONBLOCK
|
|
||||||
fcntl.fcntl(self.rfile.fileno(), fcntl.F_SETFL, flags)
|
|
||||||
else:
|
|
||||||
self.rfile.raw._sock.setblocking(False)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# If LATENCY_BUFFER_SIZE is inappropriately large, we will
|
# If LATENCY_BUFFER_SIZE is inappropriately large, we will
|
||||||
# get a MemoryError here. Read no more than 1MiB.
|
# get a MemoryError here. Read no more than 1MiB.
|
||||||
if sys.platform == 'win32':
|
read = _nb_clean(self.rfile.read, min(1048576, LATENCY_BUFFER_SIZE))
|
||||||
read = _nb_clean(self.rfile.raw._sock.recv, min(1048576, LATENCY_BUFFER_SIZE))
|
|
||||||
else:
|
|
||||||
read = _nb_clean(os.read, self.rfile.fileno(), min(1048576, LATENCY_BUFFER_SIZE))
|
|
||||||
except OSError:
|
except OSError:
|
||||||
_, e = sys.exc_info()[:2]
|
_, e = sys.exc_info()[:2]
|
||||||
raise Fatal('other end: %r' % e)
|
raise Fatal('other end: %r' % e)
|
||||||
|
Loading…
Reference in New Issue
Block a user