summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAvery Pennarun <apenwarr@gmail.com>2011-01-26 02:00:19 -0800
committerAvery Pennarun <apenwarr@gmail.com>2011-01-26 02:34:46 -0800
commita2fcb08a2d4622092eeffc44ae154896ec304a56 (patch)
tree5555d06a431927e7d917a60a5c60e889c50e8330
parente7a19890aa579e94dc99cbd662bd5ed598d642fd (diff)
Extremely basic, but functional, DNS proxying support (--dns option)
Limitations: - uses a hardcoded DNS server IP on both client and server - never expires request/response objects, so leaks memory and sockets - works only with iptables, not with ipfw
-rw-r--r--client.py42
-rw-r--r--firewall.py19
-rwxr-xr-xmain.py8
-rw-r--r--server.py25
-rw-r--r--ssnet.py10
5 files changed, 89 insertions, 15 deletions
diff --git a/client.py b/client.py
index e584933..7872bae 100644
--- a/client.py
+++ b/client.py
@@ -111,14 +111,15 @@ def original_dst(sock):
class FirewallClient:
- def __init__(self, port, subnets_include, subnets_exclude):
+ def __init__(self, port, subnets_include, subnets_exclude, dnsport):
self.port = port
self.auto_nets = []
self.subnets_include = subnets_include
self.subnets_exclude = subnets_exclude
+ self.dnsport = dnsport
argvbase = ([sys.argv[0]] +
['-v'] * (helpers.verbose or 0) +
- ['--firewall', str(port)])
+ ['--firewall', str(port), str(dnsport)])
if ssyslog._p:
argvbase += ['--syslog']
argv_tries = [
@@ -190,7 +191,7 @@ class FirewallClient:
def _main(listener, fw, ssh_cmd, remotename, python, latency_control,
- seed_hosts, auto_nets,
+ dnslistener, seed_hosts, auto_nets,
syslog, daemon):
handlers = []
if helpers.verbose >= 1:
@@ -292,6 +293,25 @@ def _main(listener, fw, ssh_cmd, remotename, python, latency_control,
handlers.append(Proxy(SockWrapper(sock, sock), outwrap))
handlers.append(Handler([listener], onaccept))
+ dnspeers = {}
+ def dns_done(chan, data):
+ peer = dnspeers.get(chan)
+ debug1('dns_done: channel=%r peer=%r\n' % (chan, peer))
+ if peer:
+ del dnspeers[chan]
+ debug1('doing sendto %r\n' % (peer,))
+ dnslistener.sendto(data, peer)
+ def ondns():
+ pkt,peer = dnslistener.recvfrom(4096)
+ if pkt:
+ debug1('Got DNS request from %r: %d bytes\n' % (peer, len(pkt)))
+ chan = mux.next_channel()
+ dnspeers[chan] = peer
+ mux.send(chan, ssnet.CMD_DNS_REQ, pkt)
+ mux.channels[chan] = lambda cmd,data: dns_done(chan,data)
+ if dnslistener:
+ handlers.append(Handler([dnslistener], ondns))
+
if seed_hosts != None:
debug1('seed_hosts: %r\n' % seed_hosts)
mux.send(0, ssnet.CMD_HOST_REQ, '\n'.join(seed_hosts))
@@ -307,7 +327,7 @@ def _main(listener, fw, ssh_cmd, remotename, python, latency_control,
mux.callback()
-def main(listenip, ssh_cmd, remotename, python, latency_control,
+def main(listenip, ssh_cmd, remotename, python, latency_control, dns,
seed_hosts, auto_nets,
subnets_include, subnets_exclude, syslog, daemon, pidfile):
if syslog:
@@ -319,6 +339,7 @@ def main(listenip, ssh_cmd, remotename, python, latency_control,
log("%s\n" % e)
return 5
debug1('Starting sshuttle proxy.\n')
+
listener = socket.socket()
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if listenip[1]:
@@ -344,11 +365,20 @@ def main(listenip, ssh_cmd, remotename, python, latency_control,
listenip = listener.getsockname()
debug1('Listening on %r.\n' % (listenip,))
- fw = FirewallClient(listenip[1], subnets_include, subnets_exclude)
+ dnsport = 0
+ dnslistener = None
+ if dns:
+ dnslistener = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ dnslistener.bind((listenip[0], 0))
+ dnsip = dnslistener.getsockname()
+ debug1('DNS listening on %r.\n' % (dnsip,))
+ dnsport = dnsip[1]
+
+ fw = FirewallClient(listenip[1], subnets_include, subnets_exclude, dnsport)
try:
return _main(listener, fw, ssh_cmd, remotename,
- python, latency_control,
+ python, latency_control, dnslistener,
seed_hosts, auto_nets, syslog, daemon)
finally:
try:
diff --git a/firewall.py b/firewall.py
index b63bffa..8ec67bc 100644
--- a/firewall.py
+++ b/firewall.py
@@ -49,7 +49,7 @@ def ipt_ttl(*args):
# multiple copies shouldn't have overlapping subnets, or only the most-
# recently-started one will win (because we use "-I OUTPUT 1" instead of
# "-A OUTPUT").
-def do_iptables(port, subnets):
+def do_iptables(port, dnsport, subnets):
chain = 'sshuttle-%s' % port
# basic cleanup/setup of chains
@@ -80,6 +80,13 @@ def do_iptables(port, subnets):
'--dest', '%s/%s' % (snet,swidth),
'-p', 'tcp',
'--to-ports', str(port))
+
+ if dnsport:
+ ipt_ttl('-A', chain, '-j', 'REDIRECT',
+ '--dest', '192.168.42.1/32',
+ '-p', 'udp',
+ '--dport', '53',
+ '--to-ports', str(dnsport))
def ipfw_rule_exists(n):
@@ -145,7 +152,7 @@ def ipfw(*args):
raise Fatal('%r returned %d' % (argv, rv))
-def do_ipfw(port, subnets):
+def do_ipfw(port, dnsport, subnets):
sport = str(port)
xsport = str(port+1)
@@ -235,9 +242,11 @@ def restore_etc_hosts(port):
# exit. In case that fails, it's not the end of the world; future runs will
# supercede it in the transproxy list, at least, so the leftover rules
# are hopefully harmless.
-def main(port, syslog):
+def main(port, dnsport, syslog):
assert(port > 0)
assert(port <= 65535)
+ assert(dnsport >= 0)
+ assert(dnsport <= 65535)
if os.getuid() != 0:
raise Fatal('you must be root (or enable su/sudo) to set the firewall')
@@ -291,7 +300,7 @@ def main(port, syslog):
try:
if line:
debug1('firewall manager: starting transproxy.\n')
- do_it(port, subnets)
+ do_it(port, dnsport, subnets)
sys.stdout.write('STARTED\n')
try:
@@ -319,5 +328,5 @@ def main(port, syslog):
debug1('firewall manager: undoing changes.\n')
except:
pass
- do_it(port, [])
+ do_it(port, 0, [])
restore_etc_hosts(port)
diff --git a/main.py b/main.py
index 5597177..e76e596 100755
--- a/main.py
+++ b/main.py
@@ -54,6 +54,7 @@ sshuttle --hostwatch
l,listen= transproxy to this ip address and port number [127.0.0.1:0]
H,auto-hosts scan for remote hostnames and update local /etc/hosts
N,auto-nets automatically determine subnets to route
+dns capture local DNS requests and forward to the remote DNS server
python= path to python interpreter on the remote server [python]
r,remote= ssh hostname (and optional username) of remote sshuttle server
x,exclude= exclude this subnet (can be used more than once)
@@ -82,9 +83,9 @@ try:
server.latency_control = opt.latency_control
sys.exit(server.main())
elif opt.firewall:
- if len(extra) != 1:
- o.fatal('exactly one argument expected')
- sys.exit(firewall.main(int(extra[0]), opt.syslog))
+ if len(extra) != 2:
+ o.fatal('exactly two arguments expected')
+ sys.exit(firewall.main(int(extra[0]), int(extra[1]), opt.syslog))
elif opt.hostwatch:
sys.exit(hostwatch.hw_main(extra))
else:
@@ -111,6 +112,7 @@ try:
remotename,
opt.python,
opt.latency_control,
+ opt.dns,
sh,
opt.auto_nets,
parse_subnets(includes),
diff --git a/server.py b/server.py
index ae7a921..3395c9e 100644
--- a/server.py
+++ b/server.py
@@ -106,6 +106,23 @@ class Hostwatch:
self.sock = None
+class DnsProxy(Handler):
+ def __init__(self, mux, chan, request):
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ Handler.__init__(self, [sock])
+ self.sock = sock
+ self.mux = mux
+ self.chan = chan
+ self.sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42)
+ self.sock.connect(('192.168.42.1', 53))
+ self.sock.send(request)
+
+ def callback(self):
+ data = self.sock.recv(4096)
+ debug2('dns response: %d bytes\n' % len(data))
+ self.mux.send(self.chan, ssnet.CMD_DNS_RESPONSE, data)
+
+
def main():
if helpers.verbose >= 1:
helpers.logprefix = ' s: '
@@ -165,6 +182,14 @@ def main():
handlers.append(Proxy(MuxWrapper(mux, channel), outwrap))
mux.new_channel = new_channel
+ dnshandlers = {}
+ def dns_req(channel, data):
+ debug1('got dns request!\n')
+ h = DnsProxy(mux, channel, data)
+ handlers.append(h)
+ dnshandlers[channel] = h
+ mux.got_dns_req = dns_req
+
while mux.ok:
if hw.pid:
assert(hw.pid > 0)
diff --git a/ssnet.py b/ssnet.py
index 62fa378..554d870 100644
--- a/ssnet.py
+++ b/ssnet.py
@@ -21,6 +21,8 @@ CMD_DATA = 0x4206
CMD_ROUTES = 0x4207
CMD_HOST_REQ = 0x4208
CMD_HOST_LIST = 0x4209
+CMD_DNS_REQ = 0x420a
+CMD_DNS_RESPONSE = 0x420b
cmd_to_name = {
CMD_EXIT: 'EXIT',
@@ -33,6 +35,8 @@ cmd_to_name = {
CMD_ROUTES: 'ROUTES',
CMD_HOST_REQ: 'HOST_REQ',
CMD_HOST_LIST: 'HOST_LIST',
+ CMD_DNS_REQ: 'DNS_REQ',
+ CMD_DNS_RESPONSE: 'DNS_RESPONSE',
}
@@ -281,7 +285,7 @@ class Mux(Handler):
Handler.__init__(self, [rsock, wsock])
self.rsock = rsock
self.wsock = wsock
- self.new_channel = self.got_routes = None
+ self.new_channel = self.got_dns_req = self.got_routes = None
self.got_host_req = self.got_host_list = None
self.channels = {}
self.chani = 0
@@ -343,6 +347,10 @@ class Mux(Handler):
assert(not self.channels.get(channel))
if self.new_channel:
self.new_channel(channel, data)
+ elif cmd == CMD_DNS_REQ:
+ assert(not self.channels.get(channel))
+ if self.got_dns_req:
+ self.got_dns_req(channel, data)
elif cmd == CMD_ROUTES:
if self.got_routes:
self.got_routes(data)