experimental windows method

This commit is contained in:
nom3ad 2022-09-07 12:26:21 +05:30 committed by Brian May
parent 2408563f3b
commit 5a64c81b5b
11 changed files with 380 additions and 110 deletions

View File

@ -65,8 +65,7 @@ Requires:
Windows Windows
~~~~~~~ ~~~~~~~
Not officially supported, however can be made to work with Vagrant. Requires Experimental built-in support available. See :doc:`windows` for more information.
cmd.exe with Administrator access. See :doc:`windows` for more information.
Server side Requirements Server side Requirements

View File

@ -1,7 +1,13 @@
Microsoft Windows Microsoft Windows
================= =================
Currently there is no built in support for running sshuttle directly on
Microsoft Windows. Experimental support::
Experimental built-in support for Windows is availble through `windivert` method.
You have to install https://pypi.org/project/pydivert pacakge. You need Administrator privileges to use windivert method
Use Linux VM on Windows::
What we can really do is to create a Linux VM with Vagrant (or simply What we can really do is to create a Linux VM with Vagrant (or simply
Virtualbox if you like). In the Vagrant settings, remember to turn on bridged Virtualbox if you like). In the Vagrant settings, remember to turn on bridged

View File

@ -1,4 +1,8 @@
"""Coverage.py's main entry point.""" """Coverage.py's main entry point."""
import sys import sys
import os
from sshuttle.cmdline import main from sshuttle.cmdline import main
sys.exit(main()) from sshuttle.helpers import debug3
exit_code=main()
debug3("Exiting process %r (pid:%s) with code %s" % (sys.argv, os.getpid(), exit_code,))
sys.exit(exit_code)

View File

@ -14,7 +14,7 @@ import sshuttle.ssyslog as ssyslog
import sshuttle.sdnotify as sdnotify import sshuttle.sdnotify as sdnotify
from sshuttle.ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper from sshuttle.ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
from sshuttle.helpers import log, debug1, debug2, debug3, Fatal, islocal, \ from sshuttle.helpers import log, debug1, debug2, debug3, Fatal, islocal, \
resolvconf_nameservers, which resolvconf_nameservers, which, is_admin_user
from sshuttle.methods import get_method, Features from sshuttle.methods import get_method, Features
from sshuttle import __version__ from sshuttle import __version__
try: try:
@ -219,67 +219,97 @@ class FirewallClient:
# A list of commands that we can try to run to start the firewall. # A list of commands that we can try to run to start the firewall.
argv_tries = [] argv_tries = []
if os.getuid() == 0: # No need to elevate privileges if is_admin_user(): # No need to elevate privileges
argv_tries.append(argvbase) argv_tries.append(argvbase)
else: else:
# Linux typically uses sudo; OpenBSD uses doas. However, some if sys.platform == 'win32':
# Linux distributions are starting to use doas. argv_tries.append(argvbase)
sudo_cmd = ['sudo', '-p', '[local sudo] Password: '] # runas_path = which("runas")
doas_cmd = ['doas'] # if runas_path:
# argv_tries.append(['runas' , '/noprofile', '/user:Administrator', 'python'])
# 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: else:
argv_tries = [sudo_cmd, doas_cmd, argvbase] # 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]
# Try all commands in argv_tries in order. If a command # Try all commands in argv_tries in order. If a command
# produces an error, try the next one. If command is # produces an error, try the next one. If command is
# successful, set 'success' variable and break. # successful, set 'success' variable and break.
success = False success = False
for argv in argv_tries: for argv in argv_tries:
# we can't use stdin/stdout=subprocess.PIPE here, as we
# normally would, because stupid Linux 'su' requires that
# stdin be attached to a tty. Instead, attach a
# *bidirectional* socket to its stdout, and use that for
# talking in both directions.
(s1, s2) = socket.socketpair()
def setup(): if sys.platform != 'win32':
# run in the child process # we can't use stdin/stdout=subprocess.PIPE here, as we
s2.close() # normally would, because stupid Linux 'su' requires that
# stdin be attached to a tty. Instead, attach a
# *bidirectional* socket to its stdout, and use that for
# talking in both directions.
(s1, s2) = socket.socketpair()
pstdout = s1
pstdin = s1
penv = None
def preexec_fn():
# run in the child process
s2.close()
def get_pfile():
s1.close()
return s2.makefile('rwb')
else:
(s1, s2) = socket.socketpair()
pstdout = None
pstdin = ssubprocess.PIPE
preexec_fn = None
penv = os.environ.copy()
penv['PYTHONPATH'] = os.path.dirname(os.path.dirname(__file__))
def get_pfile():
import base64
socket_share_data = s1.share(self.p.pid)
s1.close()
socket_share_data_b64 = base64.b64encode(socket_share_data)
# debug3(f"{socket_share_data_b64=}")
self.p.stdin.write(socket_share_data_b64 + b'\n')
self.p.stdin.flush()
return s2.makefile('rwb')
try: try:
debug1("Starting firewall manager with command: %r" % argv) debug1("Starting firewall manager with command: %r" % argv)
self.p = ssubprocess.Popen(argv, stdout=s1, stdin=s1, self.p = ssubprocess.Popen(argv, stdout=pstdout, stdin=pstdin, env=penv,
preexec_fn=setup) preexec_fn=preexec_fn)
# No env: Talking to `FirewallClient.start`, which has no i18n. # No env: Talking to `FirewallClient.start`, which has no i18n.
except OSError as e: except OSError as e:
# This exception will occur if the program isn't # This exception will occur if the program isn't
@ -287,11 +317,15 @@ class FirewallClient:
debug1('Unable to start firewall manager. Popen failed. ' debug1('Unable to start firewall manager. Popen failed. '
'Command=%r Exception=%s' % (argv, e)) 'Command=%r Exception=%s' % (argv, e))
continue continue
self.argv = argv self.argv = argv
s1.close()
self.pfile = s2.makefile('rwb') self.pfile = get_pfile()
line = self.pfile.readline()
try:
line = self.pfile.readline()
except ConnectionResetError:
# happens in Windows, when subprocess exists
line=''
rv = self.p.poll() # Check if process is still running rv = self.p.poll() # Check if process is still running
if rv: if rv:
@ -327,14 +361,14 @@ class FirewallClient:
'Command=%r' % (skipped_text, self.argv)) 'Command=%r' % (skipped_text, self.argv))
continue continue
method_name = line[6:-1] method_name = line.strip()[6:]
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 elevate privileges failed.") raise Fatal("All attempts to run firewall client 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,
@ -397,9 +431,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() line = self.pfile.readline().strip()
self.check() self.check()
if line != b'STARTED\n': if line != b'STARTED':
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):
@ -562,24 +596,26 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
auto_nets=auto_nets)) auto_nets=auto_nets))
except socket.error as e: except socket.error as e:
if e.args[0] == errno.EPIPE: if e.args[0] == errno.EPIPE:
debug3('Error: EPIPE: ' + repr(e))
raise Fatal("failed to establish ssh session (1)") raise Fatal("failed to establish ssh session (1)")
else: else:
raise raise
mux = Mux(serversock.makefile("rb"), serversock.makefile("wb")) rfile, wfile = serversock.makefile("rb"), serversock.makefile("wb")
mux = Mux(rfile, wfile)
handlers.append(mux) handlers.append(mux)
expected = b'SSHUTTLE0001' expected = b'SSHUTTLE0001'
try: try:
v = 'x' v = 'x'
while v and v != b'\0': while v and v != b'\0':
v = serversock.recv(1) v = rfile.read(1)
v = 'x' v = 'x'
while v and v != b'\0': while v and v != b'\0':
v = serversock.recv(1) v = rfile.read(1)
initstring = serversock.recv(len(expected)) initstring = rfile.read(len(expected))
except socket.error as e: except socket.error as e:
if e.args[0] == errno.ECONNRESET: if e.args[0] == errno.ECONNRESET:
debug3('Error: ECONNRESET ' + repr(e))
raise Fatal("failed to establish ssh session (2)") raise Fatal("failed to establish ssh session (2)")
else: else:
raise raise

View File

@ -7,10 +7,12 @@ import os
import platform import platform
import traceback import traceback
import subprocess as ssubprocess import subprocess as ssubprocess
import base64
import io
import sshuttle.ssyslog as ssyslog import sshuttle.ssyslog as ssyslog
import sshuttle.helpers as helpers import sshuttle.helpers as helpers
from sshuttle.helpers import log, debug1, debug2, Fatal from sshuttle.helpers import is_admin_user, log, debug1, debug2, Fatal
from sshuttle.methods import get_auto_method, get_method from sshuttle.methods import get_auto_method, get_method
HOSTSFILE = '/etc/hosts' HOSTSFILE = '/etc/hosts'
@ -87,8 +89,8 @@ def firewall_exit(signum, frame):
# Isolate function that needs to be replaced for tests # Isolate function that needs to be replaced for tests
def setup_daemon(): def _setup_daemon_unix():
if os.getuid() != 0: if not is_admin_user():
raise Fatal('You must be root (or enable su/sudo) to set the firewall') raise Fatal('You must be root (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
@ -113,6 +115,25 @@ def setup_daemon():
return sys.stdin, sys.stdout return sys.stdin, sys.stdout
def _setup_daemon_windows():
if not is_admin_user():
raise Fatal('You must be administrator to set the firewall')
signal.signal(signal.SIGTERM, firewall_exit)
signal.signal(signal.SIGINT, firewall_exit)
socket_share_data_b64 = sys.stdin.readline()
# debug3(f'FROM_SHARE ${socket_share_data_b64=}')
socket_share_data = base64.b64decode(socket_share_data_b64)
sock = socket.fromshare(socket_share_data)
sys.stdin = io.TextIOWrapper(sock.makefile('rb'))
sys.stdout = io.TextIOWrapper(sock.makefile('wb'))
return sys.stdin, sys.stdout
if sys.platform == 'win32':
setup_daemon = _setup_daemon_windows
else:
setup_daemon = _setup_daemon_unix
# Note that we're sorting in a very particular order: # Note that we're sorting in a very particular order:
# we need to go from smaller, more specific, port ranges, to larger, # we need to go from smaller, more specific, port ranges, to larger,
# less-specific, port ranges. At each level, we order by subnet # less-specific, port ranges. At each level, we order by subnet
@ -190,9 +211,13 @@ def main(method_name, syslog):
# we wait until we get some input before creating the rules. That way, # we wait until we get some input before creating the rules. That way,
# sshuttle can launch us as early as possible (and get sudo password # sshuttle can launch us as early as possible (and get sudo password
# authentication as early in the startup process as possible). # authentication as early in the startup process as possible).
line = stdin.readline(128) try:
if not line: line = stdin.readline(128)
return # parent died; nothing to do if not line:
return # parent died; nothing to do
except ConnectionResetError:
# On windows, this is thrown when parent process closes it's socket pair end
return
subnets = [] subnets = []
if line != 'ROUTES\n': if line != 'ROUTES\n':

View File

@ -220,3 +220,14 @@ def which(file, mode=os.F_OK | os.X_OK):
else: else:
debug2("which() could not find '%s' in %s" % (file, path)) debug2("which() could not find '%s' in %s" % (file, path))
return rv return rv
def is_admin_user():
if sys.platform == 'win32':
import ctypes
# https://stackoverflow.com/questions/130763/request-uac-elevation-from-within-a-python-script/41930586#41930586
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
return os.getuid() != 0

View File

@ -1,6 +1,7 @@
import importlib import importlib
import socket import socket
import struct import struct
import sys
import errno import errno
import ipaddress import ipaddress
from sshuttle.helpers import Fatal, debug3 from sshuttle.helpers import Fatal, debug3
@ -109,7 +110,7 @@ def get_method(method_name):
def get_auto_method(): def get_auto_method():
debug3("Selecting a method automatically...") debug3("Selecting a method automatically...")
# Try these methods, in order: # Try these methods, in order:
methods_to_try = ["nat", "nft", "pf", "ipfw"] methods_to_try = ["nat", "nft", "pf", "ipfw"] if sys.platform != "win32" else ["windivert"]
for m in methods_to_try: for m in methods_to_try:
method = get_method(m) method = get_method(m)
if method.is_supported(): if method.is_supported():

View File

@ -0,0 +1,126 @@
import sys
import ipaddress
import threading
from collections import namedtuple
try:
import pydivert
except ImportError:
raise Fatal('Could not import pydivert module. windivert requires https://pypi.org/project/pydivert')
from sshuttle.methods import BaseMethod
from sshuttle.helpers import log, debug1, debug2, Fatal
# https://reqrypt.org/windivert-doc.html#divert_iphdr
ConnectionTuple = namedtuple(
"ConnectionTuple", ["protocol", "src_addr", "src_port", "dst_addr", "dst_port"]
)
class ConnectionTracker:
def __init__(self) -> None:
self.d = {}
def add_tcp(self, src_addr, src_port, dst_addr, dst_port):
k = ("TCP", src_addr, src_port)
v = (dst_addr, dst_port)
if self.d.get(k) != v:
debug1("Adding tcp connection to tracker:" + repr((src_addr, src_port, dst_addr, dst_port)))
self.d[k] = v
def get_tcp(self, src_addr, src_port):
try:
return ConnectionTuple(
"TCP", src_addr, src_port, *self.d[("TCP", src_addr, src_port)]
)
except KeyError:
return None
class Method(BaseMethod):
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
user, tmark):
log( f"{port=}, {dnsport=}, {nslist=}, {family=}, {subnets=}, {udp=}, {user=}, {tmark=}")
# port=12300, dnsport=0, nslist=[], family=<AddressFamily.AF_INET: 2>,
# subnets=[(2, 24, False, '10.111.10.0', 0, 0), (2, 16, False, '169.254.0.0', 0, 0), (2, 24, False, '172.31.0.0', 0, 0), (2, 16, False, '192.168.0.0', 0, 0), (2, 32, True, '0.0.0.0', 0, 0)],
# udp=False, user=None, tmark='0x01'
self.conntrack = ConnectionTracker()
proxy_addr = "10.0.2.15"
subnet_addreses = []
for (_, mask, exclude, network_addr, fport, lport) in subnets:
if exclude:
continue
assert fport == 0, 'custom port range not supported'
assert lport == 0, 'custom port range not supported'
subnet_addreses.append("%s/%s" % (network_addr, mask))
debug2("subnet_addreses=%s proxy_addr=%s:%s" % (subnet_addreses,proxy_addr,port))
# check permission
with pydivert.WinDivert('false'):
pass
threading.Thread(name='outbound_divert', target=self._outbound_divert, args=(subnet_addreses, proxy_addr, port), daemon=True).start()
threading.Thread(name='inbound_divert', target=self._inbound_divert, args=(proxy_addr, port), daemon=True).start()
def restore_firewall(self, port, family, udp, user):
pass
def get_supported_features(self):
result = super(Method, self).get_supported_features()
result.user = False
result.dns = False
result.ipv6 = False
return result
def get_tcp_dstip(self, sock):
return ('172.31.0.141', 80)
def is_supported(self):
if sys.platform == 'win32':
return True
return False
def _outbound_divert(self, subnets, proxy_addr, proxy_port):
# with pydivert.WinDivert(f"outbound and tcp and ip.DstAddr == {subnet}") as w:
filter = "outbound and ip and tcp"
subnet_selectors = []
for cidr in subnets:
ip_network = ipaddress.ip_network(cidr)
first_ip = ip_network.network_address
last_ip = ip_network.broadcast_address
subnet_selectors.append(f"(ip.DstAddr >= {first_ip} and ip.DstAddr <= {last_ip})")
filter = f"{filter} and ({'or'.join(subnet_selectors)}) "
debug1(f"[OUTBOUND] {filter=}")
with pydivert.WinDivert(filter) as w:
for pkt in w:
# debug3(repr(pkt))
self.conntrack.add_tcp(pkt.src_addr, pkt.src_port, pkt.dst_addr, pkt.dst_port)
pkt.ipv4.dst_addr = proxy_addr
pkt.tcp.dst_port = proxy_port
w.send(pkt, recalculate_checksum=True)
def _inbound_divert(self, proxy_addr, proxy_port):
filter = f"inbound and ip and tcp and ip.SrcAddr == {proxy_addr} and tcp.SrcPort == {proxy_port}"
debug2(f"[INBOUND] {filter=}")
with pydivert.WinDivert(filter) as w:
for pkt in w:
# debug2(repr(conntrack.d))
# debug2(repr((pkt.src_addr, pkt.src_port, pkt.dst_addr, pkt.dst_port)))
conn = self.conntrack.get_tcp(pkt.dst_addr, pkt.dst_port)
if not conn:
debug2("Unexpcted packet:" + repr((pkt.protocol,pkt.src_addr,pkt.src_port,pkt.dst_addr,pkt.dst_port)))
continue
pkt.ipv4.src_addr = conn.dst_addr
pkt.tcp.src_port = conn.dst_port
w.send(pkt, recalculate_checksum=True)

View File

@ -1,5 +1,6 @@
import re import re
import socket import socket
import sys
from argparse import ArgumentParser, Action, ArgumentTypeError as Fatal from argparse import ArgumentParser, Action, ArgumentTypeError as Fatal
from sshuttle import __version__ from sshuttle import __version__
@ -236,7 +237,7 @@ parser.add_argument(
parser.add_argument( parser.add_argument(
"--method", "--method",
choices=["auto", "nat", "nft", "tproxy", "pf", "ipfw"], choices=["auto", "nat", "nft", "tproxy", "pf", "ipfw"] if sys.platform != 'win32' else ["auto", "windivert"],
metavar="TYPE", metavar="TYPE",
default="auto", default="auto",
help=""" help="""

View File

@ -175,9 +175,10 @@ def connect(ssh_cmd, rhostport, python, stderr, add_cmd_delimiter, options):
# case, sshuttle might not work at all since it is not # case, sshuttle might not work at all since it is not
# possible to run python on the remote machine---even if # possible to run python on the remote machine---even if
# it is present. # it is present.
devnull='/dev/null'
pycmd = ("P=python3; $P -V 2>%s || P=python; " pycmd = ("P=python3; $P -V 2>%s || P=python; "
"exec \"$P\" -c %s; exit 97") % \ "exec \"$P\" -c %s; exit 97") % \
(os.devnull, quote(pyscript)) (devnull, quote(pyscript))
pycmd = ("/bin/sh -c {}".format(quote(pycmd))) pycmd = ("/bin/sh -c {}".format(quote(pycmd)))
if password is not None: if password is not None:
@ -203,19 +204,56 @@ def connect(ssh_cmd, rhostport, python, stderr, add_cmd_delimiter, options):
raise Fatal("Failed to find '%s' in path %s" % (argv[0], get_path())) raise Fatal("Failed to find '%s' in path %s" % (argv[0], get_path()))
argv[0] = abs_path argv[0] = abs_path
(s1, s2) = socket.socketpair()
def setup(): if sys.platform != 'win32':
# runs in the child process (s1, s2) = socket.socketpair()
s2.close() def preexec_fn():
s1a, s1b = os.dup(s1.fileno()), os.dup(s1.fileno()) # runs in the child process
s1.close() s2.close()
pstdin, pstdout = os.dup(s1.fileno()), os.dup(s1.fileno())
s1.close()
debug2('executing: %r' % argv) def get_serversock():
p = ssubprocess.Popen(argv, stdin=s1a, stdout=s1b, preexec_fn=setup, os.close(pstdin)
close_fds=True, stderr=stderr) os.close(pstdout)
os.close(s1a) return s2
os.close(s1b) else:
s2.sendall(content) (s1, s2) = socket.socketpair()
s2.sendall(content2) preexec_fn = None
return p, s2 pstdin = ssubprocess.PIPE
pstdout = ssubprocess.PIPE
def get_serversock():
import threading
def steam_stdout_to_sock():
while True:
data = p.stdout.read(1)
if not data:
debug2("EOF on ssh process stdout. Process probably exited")
break
n = s1.sendall(data)
print("<<<<< p.stdout.read()", len(data), '->', n, data[:min(32,len(data))])
def stream_sock_to_stdin():
while True:
data = s1.recv(16384)
if not data:
print(">>>>>> EOF stream_sock_to_stdin")
break
n = p.stdin.write(data)
print(">>>>>> s1.recv()", len(data) , "->" , n , data[:min(32,len(data))])
p.communicate
threading.Thread(target=steam_stdout_to_sock, name='steam_stdout_to_sock', daemon=True).start()
threading.Thread(target=stream_sock_to_stdin, name='stream_sock_to_stdin', daemon=True).start()
# s2.setblocking(False)
return s2
# https://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)
p = ssubprocess.Popen(argv, stdin=pstdin, stdout=pstdout, preexec_fn=preexec_fn,
close_fds=close_fds, stderr=stderr, bufsize=0)
serversock = get_serversock()
serversock.sendall(content)
serversock.sendall(content2)
return p, serversock

View File

@ -4,7 +4,9 @@ import socket
import errno import errno
import select import select
import os import os
import fcntl
if sys.platform != "win32":
import fcntl
from sshuttle.helpers import b, log, debug1, debug2, debug3, Fatal from sshuttle.helpers import b, log, debug1, debug2, debug3, Fatal
@ -213,7 +215,10 @@ class SockWrapper:
return 0 # still connecting return 0 # still connecting
self.wsock.setblocking(False) self.wsock.setblocking(False)
try: try:
return _nb_clean(os.write, self.wsock.fileno(), buf) if sys.platform == 'win32':
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:
@ -236,7 +241,10 @@ class SockWrapper:
return return
self.rsock.setblocking(False) self.rsock.setblocking(False)
try: try:
return _nb_clean(os.read, self.rsock.fileno(), 65536) if sys.platform == 'win32':
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)
@ -431,15 +439,22 @@ class Mux(Handler):
callback(cmd, data) callback(cmd, data)
def flush(self): def flush(self):
try: if sys.platform != "win32":
os.set_blocking(self.wfile.fileno(), False) try:
except AttributeError: os.set_blocking(self.wfile.fileno(), False)
# python < 3.5 except AttributeError:
flags = fcntl.fcntl(self.wfile.fileno(), fcntl.F_GETFL) # python < 3.5
flags |= os.O_NONBLOCK flags = fcntl.fcntl(self.wfile.fileno(), fcntl.F_GETFL)
fcntl.fcntl(self.wfile.fileno(), fcntl.F_SETFL, flags) 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]:
wrote = _nb_clean(os.write, self.wfile.fileno(), self.outbuf[0]) if sys.platform == 'win32':
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:]
@ -447,18 +462,26 @@ class Mux(Handler):
self.outbuf[0:1] = [] self.outbuf[0:1] = []
def fill(self): def fill(self):
try: if sys.platform != "win32":
os.set_blocking(self.rfile.fileno(), False) try:
except AttributeError: os.set_blocking(self.rfile.fileno(), False)
# python < 3.5 except AttributeError:
flags = fcntl.fcntl(self.rfile.fileno(), fcntl.F_GETFL) # python < 3.5
flags |= os.O_NONBLOCK flags = fcntl.fcntl(self.rfile.fileno(), fcntl.F_GETFL)
fcntl.fcntl(self.rfile.fileno(), fcntl.F_SETFL, flags) 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.
read = _nb_clean(os.read, self.rfile.fileno(), if sys.platform == 'win32':
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)