mirror of
https://github.com/sshuttle/sshuttle.git
synced 2025-07-05 01:00:37 +02:00
Compare commits
26 Commits
Author | SHA1 | Date | |
---|---|---|---|
abb48f1996 | |||
1c27a6cad0 | |||
8a2d5802c1 | |||
e7d4931b3d | |||
1e364b2c0b | |||
8816dbfd23 | |||
98d052d19e | |||
be4b081a0d | |||
9c5f1f5bbf | |||
33d09ffcaf | |||
45f8cce2f8 | |||
d4001c11f9 | |||
450ad79b18 | |||
5debf1f11a | |||
79181043bc | |||
c0a81353ab | |||
5bdf36152a | |||
a9ee66d905 | |||
094d3d9b97 | |||
19b677892e | |||
319c122861 | |||
f4bd290919 | |||
f353701f24 | |||
3037a91e51 | |||
cdd1e2c538 | |||
eb01c0b184 |
15
CHANGES.rst
15
CHANGES.rst
@ -9,6 +9,21 @@ adheres to `Semantic Versioning`_.
|
|||||||
.. _`Semantic Versioning`: http://semver.org/
|
.. _`Semantic Versioning`: http://semver.org/
|
||||||
|
|
||||||
|
|
||||||
|
1.0.3 - 2020-08-24
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
~~~~~
|
||||||
|
* Allow Mux() flush/fill to work with python < 3.5
|
||||||
|
* Fix parse_hostport to always return string for host.
|
||||||
|
* Require -r/--remote parameter.
|
||||||
|
* Add missing package in OpenWRT documentation.
|
||||||
|
* Fix doc about --listen option.
|
||||||
|
* README: add Ubuntu.
|
||||||
|
* Increase IP4 ttl to 63 hops instead of 42.
|
||||||
|
* Fix formatting in installation.rst
|
||||||
|
|
||||||
|
|
||||||
1.0.3 - 2020-07-12
|
1.0.3 - 2020-07-12
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
@ -30,6 +30,10 @@ common case:
|
|||||||
Obtaining sshuttle
|
Obtaining sshuttle
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Ubuntu 16.04 or later::
|
||||||
|
|
||||||
|
apt-get install sshuttle
|
||||||
|
|
||||||
- Debian stretch or later::
|
- Debian stretch or later::
|
||||||
|
|
||||||
apt-get install sshuttle
|
apt-get install sshuttle
|
||||||
|
@ -6,7 +6,8 @@ Installation
|
|||||||
pip install sshuttle
|
pip install sshuttle
|
||||||
|
|
||||||
- Debain package manager::
|
- Debain package manager::
|
||||||
sudo apt install sshuttle
|
|
||||||
|
sudo apt install sshuttle
|
||||||
|
|
||||||
- Clone::
|
- Clone::
|
||||||
|
|
||||||
@ -18,5 +19,6 @@ Installation
|
|||||||
Optionally after installation
|
Optionally after installation
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
- Add to sudoers file
|
- Add to sudoers file::
|
||||||
sshuttle --sudoers
|
|
||||||
|
sshuttle --sudoers
|
||||||
|
@ -65,7 +65,8 @@ Options
|
|||||||
:program:`sshuttle`, e.g. ``--listen localhost``.
|
:program:`sshuttle`, e.g. ``--listen localhost``.
|
||||||
|
|
||||||
For the tproxy and pf methods this can be an IPv6 address. Use this option
|
For the tproxy and pf methods this can be an IPv6 address. Use this option
|
||||||
twice if required, to provide both IPv4 and IPv6 addresses.
|
with comma separated values if required, to provide both IPv4 and IPv6
|
||||||
|
addresses, e.g. ``--listen 127.0.0.1:0,[::1]:0``.
|
||||||
|
|
||||||
.. option:: -H, --auto-hosts
|
.. option:: -H, --auto-hosts
|
||||||
|
|
||||||
|
@ -3,6 +3,6 @@ OpenWRT
|
|||||||
|
|
||||||
Run::
|
Run::
|
||||||
|
|
||||||
opkg install python3 python3-pip iptables-mod-nat-extra iptables-mod-ipopt
|
opkg install python3 python3-pip iptables-mod-extra iptables-mod-nat-extra iptables-mod-ipopt
|
||||||
python3 /usr/bin/pip3 install sshuttle
|
python3 /usr/bin/pip3 install sshuttle
|
||||||
sshuttle -l 0.0.0.0 -r <IP> -x 192.168.1.1 0/0
|
sshuttle -l 0.0.0.0 -r <IP> -x 192.168.1.1 0/0
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
attrs==19.3.0
|
attrs==20.1.0
|
||||||
pytest==5.4.3
|
pytest==6.0.1
|
||||||
pytest-cov==2.10.0
|
pytest-cov==2.10.1
|
||||||
mock==2.0.0
|
mock==2.0.0
|
||||||
flake8==3.8.3
|
flake8==3.8.3
|
||||||
pyflakes==2.2.0
|
pyflakes==2.2.0
|
||||||
|
@ -557,6 +557,11 @@ def main(listenip_v6, listenip_v4,
|
|||||||
subnets_include, subnets_exclude, daemon, to_nameserver, pidfile,
|
subnets_include, subnets_exclude, daemon, to_nameserver, pidfile,
|
||||||
user, sudo_pythonpath):
|
user, sudo_pythonpath):
|
||||||
|
|
||||||
|
if not remotename:
|
||||||
|
# XXX: We can't make it required at the argparse level,
|
||||||
|
# because sshuttle calls out to itself in FirewallClient.
|
||||||
|
raise Fatal("You must specify -r/--remote.")
|
||||||
|
|
||||||
if daemon:
|
if daemon:
|
||||||
try:
|
try:
|
||||||
check_daemon(pidfile)
|
check_daemon(pidfile)
|
||||||
|
@ -71,10 +71,10 @@ def ipt_ttl(family, *args):
|
|||||||
global _no_ttl_module
|
global _no_ttl_module
|
||||||
if not _no_ttl_module:
|
if not _no_ttl_module:
|
||||||
# we avoid infinite loops by generating server-side connections
|
# we avoid infinite loops by generating server-side connections
|
||||||
# with ttl 42. This makes the client side not recapture those
|
# with ttl 63. This makes the client side not recapture those
|
||||||
# connections, in case client == server.
|
# connections, in case client == server.
|
||||||
try:
|
try:
|
||||||
argsplus = list(args) + ['-m', 'ttl', '!', '--ttl', '42']
|
argsplus = list(args) + ['-m', 'ttl', '!', '--ttl', '63']
|
||||||
ipt(family, *argsplus)
|
ipt(family, *argsplus)
|
||||||
except Fatal:
|
except Fatal:
|
||||||
ipt(family, *args)
|
ipt(family, *args)
|
||||||
|
@ -70,7 +70,7 @@ def ipfw_rule_exists(n):
|
|||||||
found = False
|
found = False
|
||||||
for line in p.stdout:
|
for line in p.stdout:
|
||||||
if line.startswith(b'%05d ' % n):
|
if line.startswith(b'%05d ' % n):
|
||||||
if not ('ipttl 42' in line or 'check-state' in line):
|
if not ('ipttl 63' in line or 'check-state' in line):
|
||||||
log('non-sshuttle ipfw rule: %r\n' % line.strip())
|
log('non-sshuttle ipfw rule: %r\n' % line.strip())
|
||||||
raise Fatal('non-sshuttle ipfw rule #%d already exists!' % n)
|
raise Fatal('non-sshuttle ipfw rule #%d already exists!' % n)
|
||||||
found = True
|
found = True
|
||||||
@ -185,7 +185,7 @@ class Method(BaseMethod):
|
|||||||
sender.setsockopt(socket.SOL_IP, IP_BINDANY, 1)
|
sender.setsockopt(socket.SOL_IP, IP_BINDANY, 1)
|
||||||
sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
||||||
sender.setsockopt(socket.SOL_IP, socket.IP_TTL, 42)
|
sender.setsockopt(socket.SOL_IP, socket.IP_TTL, 63)
|
||||||
sender.bind(srcip)
|
sender.bind(srcip)
|
||||||
sender.sendto(data, dstip)
|
sender.sendto(data, dstip)
|
||||||
sender.close()
|
sender.close()
|
||||||
@ -224,7 +224,7 @@ class Method(BaseMethod):
|
|||||||
ipfw('add', '1', 'fwd', '127.0.0.1,%d' % port,
|
ipfw('add', '1', 'fwd', '127.0.0.1,%d' % port,
|
||||||
'tcp',
|
'tcp',
|
||||||
'from', 'any', 'to', 'table(126)',
|
'from', 'any', 'to', 'table(126)',
|
||||||
'not', 'ipttl', '42', 'keep-state', 'setup')
|
'not', 'ipttl', '63', 'keep-state', 'setup')
|
||||||
|
|
||||||
ipfw_noexit('table', '124', 'flush')
|
ipfw_noexit('table', '124', 'flush')
|
||||||
dnscount = 0
|
dnscount = 0
|
||||||
@ -235,11 +235,11 @@ class Method(BaseMethod):
|
|||||||
ipfw('add', '1', 'fwd', '127.0.0.1,%d' % dnsport,
|
ipfw('add', '1', 'fwd', '127.0.0.1,%d' % dnsport,
|
||||||
'udp',
|
'udp',
|
||||||
'from', 'any', 'to', 'table(124)',
|
'from', 'any', 'to', 'table(124)',
|
||||||
'not', 'ipttl', '42')
|
'not', 'ipttl', '63')
|
||||||
ipfw('add', '1', 'allow',
|
ipfw('add', '1', 'allow',
|
||||||
'udp',
|
'udp',
|
||||||
'from', 'any', 'to', 'any',
|
'from', 'any', 'to', 'any',
|
||||||
'ipttl', '42')
|
'ipttl', '63')
|
||||||
|
|
||||||
if subnets:
|
if subnets:
|
||||||
# create new subnet entries
|
# create new subnet entries
|
||||||
|
@ -50,17 +50,17 @@ class Method(BaseMethod):
|
|||||||
'ip daddr %s/%s' % (snet, swidth), 'return')))
|
'ip daddr %s/%s' % (snet, swidth), 'return')))
|
||||||
else:
|
else:
|
||||||
_nft('add rule', chain, *(tcp_ports + (
|
_nft('add rule', chain, *(tcp_ports + (
|
||||||
'ip daddr %s/%s' % (snet, swidth), 'ip ttl != 42',
|
'ip daddr %s/%s' % (snet, swidth), 'ip ttl != 63',
|
||||||
('redirect to :' + str(port)))))
|
('redirect to :' + str(port)))))
|
||||||
|
|
||||||
for _, ip in [i for i in nslist if i[0] == family]:
|
for _, ip in [i for i in nslist if i[0] == family]:
|
||||||
if family == socket.AF_INET:
|
if family == socket.AF_INET:
|
||||||
_nft('add rule', chain, 'ip protocol udp ip daddr %s' % ip,
|
_nft('add rule', chain, 'ip protocol udp ip daddr %s' % ip,
|
||||||
'udp dport { 53 }', 'ip ttl != 42',
|
'udp dport { 53 }', 'ip ttl != 63',
|
||||||
('redirect to :' + str(dnsport)))
|
('redirect to :' + str(dnsport)))
|
||||||
elif family == socket.AF_INET6:
|
elif family == socket.AF_INET6:
|
||||||
_nft('add rule', chain, 'ip6 protocol udp ip6 daddr %s' % ip,
|
_nft('add rule', chain, 'ip6 protocol udp ip6 daddr %s' % ip,
|
||||||
'udp dport { 53 }', 'ip ttl != 42',
|
'udp dport { 53 }', 'ip ttl != 63',
|
||||||
('redirect to :' + str(dnsport)))
|
('redirect to :' + str(dnsport)))
|
||||||
|
|
||||||
def restore_firewall(self, port, family, udp, user):
|
def restore_firewall(self, port, family, udp, user):
|
||||||
|
@ -195,7 +195,7 @@ class DnsProxy(Handler):
|
|||||||
|
|
||||||
family, sockaddr = self._addrinfo(peer, port)
|
family, sockaddr = self._addrinfo(peer, port)
|
||||||
sock = socket.socket(family, socket.SOCK_DGRAM)
|
sock = socket.socket(family, socket.SOCK_DGRAM)
|
||||||
sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42)
|
sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 63)
|
||||||
sock.connect(sockaddr)
|
sock.connect(sockaddr)
|
||||||
|
|
||||||
self.peers[sock] = peer
|
self.peers[sock] = peer
|
||||||
@ -252,7 +252,7 @@ class UdpProxy(Handler):
|
|||||||
self.chan = chan
|
self.chan = chan
|
||||||
self.sock = sock
|
self.sock = sock
|
||||||
if family == socket.AF_INET:
|
if family == socket.AF_INET:
|
||||||
self.sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42)
|
self.sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 63)
|
||||||
|
|
||||||
def send(self, dstip, data):
|
def send(self, dstip, data):
|
||||||
debug2('UDP: sending to %r port %d\n' % dstip)
|
debug2('UDP: sending to %r port %d\n' % dstip)
|
||||||
|
@ -65,12 +65,12 @@ def parse_hostport(rhostport):
|
|||||||
try:
|
try:
|
||||||
# try to parse host as an IP adress,
|
# try to parse host as an IP adress,
|
||||||
# if that works it is an IPv6 address
|
# if that works it is an IPv6 address
|
||||||
host = ipaddress.ip_address(host)
|
host = str(ipaddress.ip_address(host))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# if that fails parse as URL to get the port
|
# if that fails parse as URL to get the port
|
||||||
parsed = urlparse('//{}'.format(host))
|
parsed = urlparse('//{}'.format(host))
|
||||||
try:
|
try:
|
||||||
host = ipaddress.ip_address(parsed.hostname)
|
host = str(ipaddress.ip_address(parsed.hostname))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# else if both fails, we have a hostname with port
|
# else if both fails, we have a hostname with port
|
||||||
host = parsed.hostname
|
host = parsed.hostname
|
||||||
|
@ -4,6 +4,7 @@ import socket
|
|||||||
import errno
|
import errno
|
||||||
import select
|
import select
|
||||||
import os
|
import os
|
||||||
|
import fcntl
|
||||||
|
|
||||||
from sshuttle.helpers import b, log, debug1, debug2, debug3, Fatal
|
from sshuttle.helpers import b, log, debug1, debug2, debug3, Fatal
|
||||||
|
|
||||||
@ -436,7 +437,13 @@ class Mux(Handler):
|
|||||||
callback(cmd, data)
|
callback(cmd, data)
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
os.set_blocking(self.wfile.fileno(), False)
|
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
|
||||||
|
flags = fcntl.fcntl(self.wfile.fileno(), fcntl.F_SETFL, flags)
|
||||||
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])
|
wrote = _nb_clean(os.write, self.wfile.fileno(), self.outbuf[0])
|
||||||
debug2('mux wrote: %r/%d\n' % (wrote, len(self.outbuf[0])))
|
debug2('mux wrote: %r/%d\n' % (wrote, len(self.outbuf[0])))
|
||||||
@ -446,7 +453,13 @@ class Mux(Handler):
|
|||||||
self.outbuf[0:1] = []
|
self.outbuf[0:1] = []
|
||||||
|
|
||||||
def fill(self):
|
def fill(self):
|
||||||
os.set_blocking(self.rfile.fileno(), False)
|
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
|
||||||
|
flags = fcntl.fcntl(self.rfile.fileno(), fcntl.F_SETFL, flags)
|
||||||
try:
|
try:
|
||||||
read = _nb_clean(os.read, self.rfile.fileno(), LATENCY_BUFFER_SIZE)
|
read = _nb_clean(os.read, self.rfile.fileno(), LATENCY_BUFFER_SIZE)
|
||||||
except OSError:
|
except OSError:
|
||||||
@ -570,7 +583,7 @@ class MuxWrapper(SockWrapper):
|
|||||||
def connect_dst(family, ip, port):
|
def connect_dst(family, ip, port):
|
||||||
debug2('Connecting to %s:%d\n' % (ip, port))
|
debug2('Connecting to %s:%d\n' % (ip, port))
|
||||||
outsock = socket.socket(family)
|
outsock = socket.socket(family)
|
||||||
outsock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42)
|
outsock.setsockopt(socket.SOL_IP, socket.IP_TTL, 63)
|
||||||
return SockWrapper(outsock, outsock,
|
return SockWrapper(outsock, outsock,
|
||||||
connect_to=(ip, port),
|
connect_to=(ip, port),
|
||||||
peername='%s:%d' % (ip, port))
|
peername='%s:%d' % (ip, port))
|
||||||
|
20
tests/ssh/test_parse_hostport.py
Normal file
20
tests/ssh/test_parse_hostport.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from sshuttle.ssh import parse_hostport
|
||||||
|
|
||||||
|
|
||||||
|
def test_host_only():
|
||||||
|
assert parse_hostport("host") == (None, None, None, "host")
|
||||||
|
assert parse_hostport("1.2.3.4") == (None, None, None, "1.2.3.4")
|
||||||
|
assert parse_hostport("2001::1") == (None, None, None, "2001::1")
|
||||||
|
assert parse_hostport("[2001::1]") == (None, None, None, "2001::1")
|
||||||
|
|
||||||
|
|
||||||
|
def test_host_and_port():
|
||||||
|
assert parse_hostport("host:22") == (None, None, 22, "host")
|
||||||
|
assert parse_hostport("1.2.3.4:22") == (None, None, 22, "1.2.3.4")
|
||||||
|
assert parse_hostport("[2001::1]:22") == (None, None, 22, "2001::1")
|
||||||
|
|
||||||
|
|
||||||
|
def test_username_and_host():
|
||||||
|
assert parse_hostport("user@host") == ("user", None, None, "host")
|
||||||
|
assert parse_hostport("user:@host") == ("user", None, None, "host")
|
||||||
|
assert parse_hostport("user:pass@host") == ("user", "pass", None, "host")
|
Reference in New Issue
Block a user