Skip to content

Commit bc065e3

Browse files
committed
Remove ttl hack & require -r option.
Previously, it was possible to run sshuttle locally without using ssh and connecting to a remote server. In this configuration, traffic was redirected to the sshuttle server running on the localhost. However, the firewall needed to distinguish between traffic leaving the sshuttle server and traffic that originated from the machine that still needed to be routed through the sshuttle server. The TTL of the packets leaving the sshuttle server were manipulated to indicate to the firewall what should happen. The TTL was adjusted for all packets leaving the sshuttle server (even if it wasn't necessary because the server and client were running on different machines). Changing the TTL caused trouble and some machines, and the --ttl option was added as a workaround to change how the TTL was set for traffic leaving sshuttle. All of this added complexity to the code for a feature (running the server on localhost) that is likely only used for testing and rarely used by others. This commit updates the associated documentation, but doesn't fully fix the ipfw method since I am unable to test that. This change will also make sshuttle fail to work if -r is used to specify a localhost. Pull request sshuttle#610 partially addresses that issue. For example, see: sshuttle#240, sshuttle#490, sshuttle#660, sshuttle#606.
1 parent 6ae0b51 commit bc065e3

20 files changed

+66
-160
lines changed

docs/manpage.rst

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ sshuttle
44

55
Synopsis
66
--------
7-
**sshuttle** [*options*] [**-r** *[username@]sshserver[:port]*] \<*subnets* ...\>
7+
**sshuttle** [*options*] **-r** *[username@]sshserver[:port]* \<*subnets* ...\>
88

99

1010
Description
@@ -441,9 +441,7 @@ Example configuration file::
441441
Discussion
442442
----------
443443
When it starts, :program:`sshuttle` creates an ssh session to the
444-
server specified by the ``-r`` option. If ``-r`` is omitted,
445-
it will start both its client and server locally, which is
446-
sometimes useful for testing.
444+
server specified by the ``-r`` option.
447445

448446
After connecting to the remote server, :program:`sshuttle` uploads its
449447
(python) source code to the remote end and executes it

docs/requirements.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Supports:
2020

2121
Requires:
2222

23-
* iptables DNAT, REDIRECT, and ttl modules. ip6tables for IPv6.
23+
* iptables DNAT and REDIRECT modules. ip6tables for IPv6.
2424

2525
Linux with nft method
2626
~~~~~~~~~~~~~~~~~~~~~

sshuttle/assembler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,4 @@
4242
from sshuttle.server import main # noqa: E402
4343
main(options.latency_control, options.latency_buffer_size,
4444
options.auto_hosts, options.to_nameserver,
45-
options.auto_nets, options.ttl)
45+
options.auto_nets)

sshuttle/client.py

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def got_signal(signum, frame):
4444
sys.exit(1)
4545

4646

47+
# Filename of the pidfile created by the sshuttle client.
4748
_pidname = None
4849

4950

@@ -198,7 +199,7 @@ def print_listening(self, what):
198199

199200
class FirewallClient:
200201

201-
def __init__(self, method_name, sudo_pythonpath, ttl):
202+
def __init__(self, method_name, sudo_pythonpath):
202203
self.auto_nets = []
203204

204205
argvbase = ([sys.executable, sys.argv[0]] +
@@ -260,7 +261,7 @@ def setup():
260261

261262
def setup(self, subnets_include, subnets_exclude, nslist,
262263
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4, udp,
263-
user, ttl, tmark):
264+
user, tmark):
264265
self.subnets_include = subnets_include
265266
self.subnets_exclude = subnets_exclude
266267
self.nslist = nslist
@@ -271,7 +272,6 @@ def setup(self, subnets_include, subnets_exclude, nslist,
271272
self.udp = udp
272273
self.user = user
273274
self.tmark = tmark
274-
self.ttl = ttl
275275

276276
def check(self):
277277
rv = self.p.poll()
@@ -310,9 +310,8 @@ def start(self):
310310
else:
311311
user = b'%d' % self.user
312312

313-
self.pfile.write(b'GO %d %s %d %s\n' %
314-
(udp, user, self.ttl,
315-
bytes(self.tmark, 'ascii')))
313+
self.pfile.write(b'GO %d %s %s\n' %
314+
(udp, user, bytes(self.tmark, 'ascii')))
316315
self.pfile.flush()
317316

318317
line = self.pfile.readline()
@@ -457,7 +456,7 @@ def ondns(listener, method, mux, handlers):
457456
def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
458457
python, latency_control, latency_buffer_size,
459458
dns_listener, seed_hosts, auto_hosts, auto_nets, daemon,
460-
to_nameserver, ttl):
459+
to_nameserver):
461460

462461
helpers.logprefix = 'c : '
463462
debug1('Starting client with Python version %s'
@@ -476,8 +475,7 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
476475
latency_buffer_size=latency_buffer_size,
477476
auto_hosts=auto_hosts,
478477
to_nameserver=to_nameserver,
479-
auto_nets=auto_nets,
480-
ttl=ttl))
478+
auto_nets=auto_nets))
481479
except socket.error as e:
482480
if e.args[0] == errno.EPIPE:
483481
raise Fatal("failed to establish ssh session (1)")
@@ -587,6 +585,7 @@ def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
587585
% (expected, initstring))
588586
log('Connected to server.')
589587
sys.stdout.flush()
588+
590589
if daemon:
591590
daemonize()
592591
log('daemonizing (%s).' % _pidname)
@@ -673,12 +672,11 @@ def main(listenip_v6, listenip_v4,
673672
latency_buffer_size, dns, nslist,
674673
method_name, seed_hosts, auto_hosts, auto_nets,
675674
subnets_include, subnets_exclude, daemon, to_nameserver, pidfile,
676-
user, sudo_pythonpath, tmark, ttl):
675+
user, sudo_pythonpath, tmark):
677676

678677
if not remotename:
679-
print("WARNING: You must specify -r/--remote to securely route "
680-
"traffic to a remote machine. Running without -r/--remote "
681-
"is only recommended for testing.")
678+
raise Fatal("You must use -r/--remote to specify a remote "
679+
"host to route traffic through.")
682680

683681
if daemon:
684682
try:
@@ -689,7 +687,7 @@ def main(listenip_v6, listenip_v4,
689687
debug1('Starting sshuttle proxy (version %s).' % __version__)
690688
helpers.logprefix = 'c : '
691689

692-
fw = FirewallClient(method_name, sudo_pythonpath, ttl)
690+
fw = FirewallClient(method_name, sudo_pythonpath)
693691

694692
# nslist is the list of name severs to intercept. If --dns is
695693
# used, we add all DNS servers in resolv.conf. Otherwise, the list
@@ -1006,14 +1004,14 @@ def feature_status(label, enabled, available):
10061004
# start the firewall
10071005
fw.setup(subnets_include, subnets_exclude, nslist,
10081006
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4,
1009-
required.udp, user, ttl, tmark)
1007+
required.udp, user, tmark)
10101008

10111009
# start the client process
10121010
try:
10131011
return _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
10141012
python, latency_control, latency_buffer_size,
10151013
dns_listener, seed_hosts, auto_hosts, auto_nets,
1016-
daemon, to_nameserver, ttl)
1014+
daemon, to_nameserver)
10171015
finally:
10181016
try:
10191017
if daemon:

sshuttle/cmdline.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def main():
4343
if opt.firewall:
4444
if opt.subnets or opt.subnets_file:
4545
parser.error('exactly zero arguments expected')
46-
return firewall.main(opt.method, opt.syslog, opt.ttl)
46+
return firewall.main(opt.method, opt.syslog)
4747
elif opt.hostwatch:
4848
return hostwatch.hw_main(opt.subnets, opt.auto_hosts)
4949
else:
@@ -116,8 +116,7 @@ def main():
116116
opt.pidfile,
117117
opt.user,
118118
opt.sudo_pythonpath,
119-
opt.tmark,
120-
opt.ttl)
119+
opt.tmark)
121120

122121
if return_code == 0:
123122
log('Normal exit code, exiting...')

sshuttle/firewall.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def flush_systemd_dns_cache():
121121
# exit. In case that fails, it's not the end of the world; future runs will
122122
# supercede it in the transproxy list, at least, so the leftover rules
123123
# are hopefully harmless.
124-
def main(method_name, syslog, ttl):
124+
def main(method_name, syslog):
125125
helpers.logprefix = 'fw: '
126126
stdin, stdout = setup_daemon()
127127
hostmap = {}
@@ -223,13 +223,12 @@ def main(method_name, syslog, ttl):
223223
raise Fatal('expected GO but got %r' % line)
224224

225225
_, _, args = line.partition(" ")
226-
udp, user, ttl, tmark = args.strip().split(" ", 3)
226+
udp, user, tmark = args.strip().split(" ", 2)
227227
udp = bool(int(udp))
228228
if user == '-':
229229
user = None
230-
ttl = int(ttl)
231-
debug2('Got udp: %r, user: %r, ttl: %s, tmark: %s' %
232-
(udp, user, ttl, tmark))
230+
debug2('Got udp: %r, user: %r, tmark: %s' %
231+
(udp, user, tmark))
233232

234233
subnets_v6 = [i for i in subnets if i[0] == socket.AF_INET6]
235234
nslist_v6 = [i for i in nslist if i[0] == socket.AF_INET6]
@@ -244,14 +243,14 @@ def main(method_name, syslog, ttl):
244243
method.setup_firewall(
245244
port_v6, dnsport_v6, nslist_v6,
246245
socket.AF_INET6, subnets_v6, udp,
247-
user, ttl, tmark)
246+
user, tmark)
248247

249248
if subnets_v4 or nslist_v4:
250249
debug2('setting up IPv4.')
251250
method.setup_firewall(
252251
port_v4, dnsport_v4, nslist_v4,
253252
socket.AF_INET, subnets_v4, udp,
254-
user, ttl, tmark)
253+
user, tmark)
255254

256255
flush_systemd_dns_cache()
257256
stdout.write('STARTED\n')

sshuttle/linux.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -49,25 +49,3 @@ def nft(family, table, action, *args):
4949
rv = ssubprocess.call(argv, env=get_env())
5050
if rv:
5151
raise Fatal('%r returned %d' % (argv, rv))
52-
53-
54-
_no_ttl_module = False
55-
56-
57-
def ipt_ttl(family, *args):
58-
global _no_ttl_module
59-
if not _no_ttl_module:
60-
# we avoid infinite loops by generating server-side connections
61-
# with ttl 63. This makes the client side not recapture those
62-
# connections, in case client == server.
63-
try:
64-
argsplus = list(args)
65-
ipt(family, *argsplus)
66-
except Fatal:
67-
ipt(family, *args)
68-
# we only get here if the non-ttl attempt succeeds
69-
log('WARNING: your iptables is missing '
70-
'the ttl module.')
71-
_no_ttl_module = True
72-
else:
73-
ipt(family, *args)

sshuttle/methods/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def assert_features(self, features):
9191
(key, self.name))
9292

9393
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
94-
user, ttl, tmark):
94+
user, tmark):
9595
raise NotImplementedError()
9696

9797
def restore_firewall(self, port, family, udp, user):

sshuttle/methods/ipfw.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,6 @@ def send_udp(self, sock, srcip, dstip, data):
177177
sender.setsockopt(socket.SOL_IP, IP_BINDANY, 1)
178178
sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
179179
sender.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
180-
sender.setsockopt(socket.SOL_IP, socket.IP_TTL, 63)
181180
sender.bind(srcip)
182181
sender.sendto(data, dstip)
183182
sender.close()
@@ -189,7 +188,12 @@ def setup_udp_listener(self, udp_listener):
189188
# udp_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVDSTADDR, 1)
190189

191190
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
192-
user, ttl, tmark):
191+
user, tmark):
192+
# TODO: The ttl hack to allow the host and server to run on
193+
# the same machine has been removed but this method hasn't
194+
# been updated yet.
195+
ttl = 63
196+
193197
# IPv6 not supported
194198
if family not in [socket.AF_INET]:
195199
raise Exception(

sshuttle/methods/nat.py

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import socket
22
from sshuttle.firewall import subnet_weight
33
from sshuttle.helpers import family_to_string, which, debug2
4-
from sshuttle.linux import ipt, ipt_ttl, ipt_chain_exists, nonfatal
4+
from sshuttle.linux import ipt, ipt_chain_exists, nonfatal
55
from sshuttle.methods import BaseMethod
66

77

@@ -13,7 +13,7 @@ class Method(BaseMethod):
1313
# recently-started one will win (because we use "-I OUTPUT 1" instead of
1414
# "-A OUTPUT").
1515
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
16-
user, ttl, tmark):
16+
user, tmark):
1717
if family != socket.AF_INET and family != socket.AF_INET6:
1818
raise Exception(
1919
'Address family "%s" unsupported by nat method_name'
@@ -25,9 +25,6 @@ def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
2525
def _ipt(*args):
2626
return ipt(family, table, *args)
2727

28-
def _ipt_ttl(*args):
29-
return ipt_ttl(family, table, *args)
30-
3128
def _ipm(*args):
3229
return ipt(family, "mangle", *args)
3330

@@ -48,16 +45,6 @@ def _ipm(*args):
4845
_ipt('-I', 'OUTPUT', '1', *args)
4946
_ipt('-I', 'PREROUTING', '1', *args)
5047

51-
# This TTL hack allows the client and server to run on the
52-
# same host. The connections the sshuttle server makes will
53-
# have TTL set to 63.
54-
if family == socket.AF_INET:
55-
_ipt_ttl('-A', chain, '-j', 'RETURN', '-m', 'ttl', '--ttl',
56-
'%s' % ttl)
57-
else: # ipv6, ttl is renamed to 'hop limit'
58-
_ipt_ttl('-A', chain, '-j', 'RETURN', '-m', 'hl', '--hl-eq',
59-
'%s' % ttl)
60-
6148
# Redirect DNS traffic as requested. This includes routing traffic
6249
# to localhost DNS servers through sshuttle.
6350
for _, ip in [i for i in nslist if i[0] == family]:
@@ -102,9 +89,6 @@ def restore_firewall(self, port, family, udp, user):
10289
def _ipt(*args):
10390
return ipt(family, table, *args)
10491

105-
def _ipt_ttl(*args):
106-
return ipt_ttl(family, table, *args)
107-
10892
def _ipm(*args):
10993
return ipt(family, "mangle", *args)
11094

0 commit comments

Comments
 (0)