Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 38 additions & 3 deletions sshuttle/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ def ondns(listener, method, mux, handlers):
def _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
python, latency_control, latency_buffer_size,
dns_listener, seed_hosts, auto_hosts, auto_nets, daemon,
to_nameserver, add_cmd_delimiter, remote_shell):
to_nameserver, add_cmd_delimiter, remote_shell, unresolved_subnets):

helpers.logprefix = 'c : '
debug1('Starting client with Python version %s'
Expand Down Expand Up @@ -753,6 +753,39 @@ def onroutes(routestr):
# set --auto-nets, we might as well wait for the message first, then
# ignore its contents.
mux.got_routes = None
if unresolved_subnets:
unresolved_map = {}
for h, c, fp, lp in unresolved_subnets:
unresolved_map.setdefault(h, []).append((h, c, fp, lp))

resolved_entries = []

def onresolve(hostlist):
for line in hostlist.strip().split():
if not line:
continue
name, ip = line.split(b',', 1)
name = name.decode('ascii')
ip = ip.decode('ascii')
for h, c, fp, lp in unresolved_map.get(name, []):
fam = socket.AF_INET6 if ':' in ip else socket.AF_INET
max_cidr = 128 if fam == socket.AF_INET6 else 32
cidr_to_use = max_cidr if c is None else int(c)
resolved_entries.append((fam, ip, cidr_to_use,
int(fp or 0),
int(lp or fp or 0)))
mux.got_host_list = None

mux.got_host_list = onresolve
mux.send(0, ssnet.CMD_HOST_REQ,
'\n'.join(unresolved_map.keys()).encode('ascii'))
end = time.time() + 10
while mux.got_host_list and time.time() < end:
ssnet.runonce(handlers, mux)
if latency_control:
mux.check_fullness()
fw.auto_nets.extend(resolved_entries)

serverready()

mux.got_routes = onroutes
Expand Down Expand Up @@ -810,7 +843,8 @@ def main(listenip_v6, listenip_v4,
latency_buffer_size, dns, nslist,
method_name, seed_hosts, auto_hosts, auto_nets,
subnets_include, subnets_exclude, daemon, to_nameserver, pidfile,
user, group, sudo_pythonpath, add_cmd_delimiter, remote_shell, tmark):
user, group, sudo_pythonpath, add_cmd_delimiter, remote_shell, tmark,
unresolved_subnets):

if not remotename:
raise Fatal("You must use -r/--remote to specify a remote "
Expand Down Expand Up @@ -1159,7 +1193,8 @@ def feature_status(label, enabled, available):
return _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
python, latency_control, latency_buffer_size,
dns_listener, seed_hosts, auto_hosts, auto_nets,
daemon, to_nameserver, add_cmd_delimiter, remote_shell)
daemon, to_nameserver, add_cmd_delimiter, remote_shell,
unresolved_subnets)
finally:
try:
if daemon:
Expand Down
8 changes: 6 additions & 2 deletions sshuttle/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import sshuttle.firewall as firewall
import sshuttle.hostwatch as hostwatch
import sshuttle.ssyslog as ssyslog
from sshuttle.options import parser, parse_ipport
from sshuttle.options import parser, parse_ipport, configure_dns_flag, get_unresolved_subnets
from sshuttle.helpers import family_ip_tuple, log, Fatal
from sshuttle.sudoers import sudoers
from sshuttle.namespace import enter_namespace
Expand All @@ -20,6 +20,7 @@ def main():
else:
env_args = []
args = [*env_args, *sys.argv[1:]]
configure_dns_flag(args)

opt = parser.parse_args(args)

Expand Down Expand Up @@ -66,6 +67,8 @@ def main():
for item in sublist]
excludes = [item for sublist in opt.exclude for item in sublist]

unresolved = get_unresolved_subnets()

if not includes and not opt.auto_nets:
parser.error('at least one subnet, subnet file, '
'or -N expected')
Expand Down Expand Up @@ -128,7 +131,8 @@ def main():
opt.sudo_pythonpath,
opt.add_cmd_delimiter,
opt.remote_shell,
opt.tmark)
opt.tmark,
unresolved)

if return_code == 0:
log('Normal exit code, exiting...')
Expand Down
24 changes: 24 additions & 0 deletions sshuttle/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,27 @@
import sys
from argparse import ArgumentParser, Action, ArgumentTypeError as Fatal

# When --dns is supplied on the command line, hostnames passed as
# subnets might only be resolvable on the remote host. During the
# argument parsing stage we therefore record such hostnames for later
# resolution instead of failing immediately.
DNS_IN_ARGS = False
UNRESOLVED_SUBNETS = []


def configure_dns_flag(args):
"""Set DNS_IN_ARGS based on parsed command line args list."""
global DNS_IN_ARGS
DNS_IN_ARGS = "--dns" in args


def get_unresolved_subnets():
"""Return and clear the list of unresolved subnets."""
global UNRESOLVED_SUBNETS
lst = UNRESOLVED_SUBNETS
UNRESOLVED_SUBNETS = []
return lst

from sshuttle import __version__


Expand Down Expand Up @@ -54,6 +75,9 @@ def parse_subnetport(s):
try:
addrinfo = socket.getaddrinfo(host, 0, 0, socket.SOCK_STREAM)
except socket.gaierror:
if DNS_IN_ARGS:
UNRESOLVED_SUBNETS.append((host, cidr, fport, lport))
return []
raise Fatal('Unable to resolve address: %s' % host)

# If the address is a domain with multiple IPs and a mask is also
Expand Down
8 changes: 8 additions & 0 deletions tests/client/test_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,14 @@ def test_parse_subnetport_host_with_port(mock_getaddrinfo):
])


@patch('sshuttle.options.socket.getaddrinfo', side_effect=socket.gaierror)
def test_parse_subnetport_unresolved_with_dns(mock_getaddrinfo):
sshuttle.options.DNS_IN_ARGS = True
sshuttle.options.UNRESOLVED_SUBNETS = []
assert sshuttle.options.parse_subnetport('no.resolve') == []
assert ('no.resolve', None, None, None) in sshuttle.options.UNRESOLVED_SUBNETS


def test_parse_namespace():
valid_namespaces = [
'my_namespace',
Expand Down