Skip to content

Commit 8be9270

Browse files
committed
Merge pull request apenwarr#4 from seanzxx/yosemite_support
Yosemite support
2 parents 6121a6d + 10dc229 commit 8be9270

File tree

3 files changed

+125
-3
lines changed

3 files changed

+125
-3
lines changed

src/client.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import sys
1313
from ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
1414
from helpers import log, debug1, debug2, debug3, Fatal, islocal
15+
from fcntl import ioctl
16+
from ctypes import c_char, c_uint8, c_uint16, c_uint32, Union, Structure, sizeof, addressof, memmove
1517

1618
recvmsg = None
1719
try:
@@ -185,6 +187,79 @@ def daemon_cleanup():
185187
raise
186188

187189

190+
class pf_state_xport(Union):
191+
_fields_ = [("port", c_uint16),
192+
("call_id", c_uint16),
193+
("spi", c_uint32)]
194+
195+
class pf_addr(Structure):
196+
class _pfa(Union):
197+
_fields_ = [("v4", c_uint32), # struct in_addr
198+
("v6", c_uint32 * 4), # struct in6_addr
199+
("addr8", c_uint8 * 16),
200+
("addr16", c_uint16 * 8),
201+
("addr32", c_uint32 * 4)]
202+
203+
_fields_ = [("pfa", _pfa)]
204+
_anonymous_ = ("pfa",)
205+
206+
class pfioc_natlook(Structure):
207+
_fields_ = [("saddr", pf_addr),
208+
("daddr", pf_addr),
209+
("rsaddr", pf_addr),
210+
("rdaddr", pf_addr),
211+
("sxport", pf_state_xport),
212+
("dxport", pf_state_xport),
213+
("rsxport", pf_state_xport),
214+
("rdxport", pf_state_xport),
215+
("af", c_uint8), # sa_family_t
216+
("proto", c_uint8),
217+
("proto_variant", c_uint8),
218+
("direction", c_uint8)]
219+
220+
DIOCNATLOOK = ((0x40000000L | 0x80000000L) | ((sizeof(pfioc_natlook) & 0x1fff) << 16) | ((ord('D')) << 8) | (23))
221+
PF_OUT = 2
222+
223+
_pf_fd = None
224+
225+
def pf_dst(sock):
226+
global _pf_fd
227+
try:
228+
peer = sock.getpeername()
229+
proxy = sock.getsockname()
230+
231+
pnl = pfioc_natlook()
232+
pnl.proto = socket.IPPROTO_TCP
233+
pnl.direction = PF_OUT
234+
if sock.family == socket.AF_INET:
235+
pnl.af = socket.AF_INET
236+
memmove(addressof(pnl.saddr), socket.inet_pton(socket.AF_INET, peer[0]), 4)
237+
pnl.sxport.port = socket.htons(peer[1])
238+
memmove(addressof(pnl.daddr), socket.inet_pton(socket.AF_INET, proxy[0]), 4)
239+
pnl.dxport.port = socket.htons(proxy[1])
240+
elif sock.family == socket.AF_INET6:
241+
pnl.af = socket.AF_INET6
242+
memmove(addressof(pnl.saddr), socket.inet_pton(socket.AF_INET6, peer[0]), 16)
243+
pnl.sxport.port = socket.htons(peer[1])
244+
memmove(addressof(pnl.daddr), socket.inet_pton(socket.AF_INET6, proxy[0]), 16)
245+
pnl.dxport.port = socket.htons(proxy[1])
246+
247+
if _pf_fd == None:
248+
_pf_fd = open('/dev/pf', 'r')
249+
250+
ioctl(_pf_fd, DIOCNATLOOK, (c_char * sizeof(pnl)).from_address(addressof(pnl)))
251+
252+
if pnl.af == socket.AF_INET:
253+
ip = socket.inet_ntop(socket.AF_INET, (c_char * 4).from_address(addressof(pnl.rdaddr)))
254+
elif pnl.af == socket.AF_INET6:
255+
ip = socket.inet_ntop(socket.AF_INET6, (c_char * 16).from_address(addressof(pnl.rdaddr)))
256+
port = socket.ntohs(pnl.rdxport.port)
257+
return (ip, port)
258+
except IOError, e:
259+
return sock.getsockname()
260+
raise
261+
262+
188263
def original_dst(sock):
189264
try:
190265
SO_ORIGINAL_DST = 80
@@ -381,6 +456,8 @@ def onaccept_tcp(listener, method, mux, handlers):
381456
raise
382457
if method == "tproxy":
383458
dstip = sock.getsockname()
459+
elif method == "pf":
460+
dstip = pf_dst(sock)
384461
else:
385462
dstip = original_dst(sock)
386463
debug1('Accept TCP: %s:%r -> %s:%r.\n' % (srcip[0], srcip[1],

src/firewall.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,47 @@ def do_wait():
463463
return do_wait
464464

465465

466+
def pfctl(*args):
467+
argv = ['pfctl'] + list(args)
468+
debug1('>> %s\n' % ' '.join(argv))
469+
rv = ssubprocess.Popen(argv, stderr=ssubprocess.PIPE).wait()
470+
if rv:
471+
raise Fatal('%r returned %d' % (argv, rv))
472+
473+
474+
def do_pf(port, dnsport, family, subnets, udp):
475+
tables = []
476+
translating_rules = []
477+
filtering_rules = []
478+
479+
if subnets:
480+
include_subnets = filter(lambda s:not s[2], sorted(subnets, reverse=True))
481+
if include_subnets:
482+
tables.append('table <include_subnets> {%s}' % ','.join(["%s/%s" % (n[3], n[1]) for n in include_subnets]))
483+
translating_rules.append('rdr pass on lo0 proto tcp to <include_subnets> -> 127.0.0.1 port %r' % port)
484+
filtering_rules.append('pass out route-to lo0 inet proto tcp to <include_subnets> keep state')
485+
486+
exclude_subnets = filter(lambda s:s[2], sorted(subnets, reverse=True))
487+
if exclude_subnets:
488+
tables.append('table <exclude_subnets> {%s}' % ','.join(["%s/%s" % (n[3], n[1]) for n in exclude_subnets]))
489+
filtering_rules.append('pass out route-to lo0 inet proto tcp to <exclude_subnets> keep state')
490+
491+
if dnsport:
492+
nslist = resolvconf_nameservers()
493+
tables.append('table <dns_servers> {%s}' % ','.join([ns[1] for ns in nslist]))
494+
translating_rules.append('rdr pass on lo0 proto udp to <dns_servers> port 53 -> 127.0.0.1 port %r' % dnsport)
495+
filtering_rules.append('pass out route-to lo0 inet proto udp to <dns_servers> port 53 keep state')
496+
497+
pf_config_file = '/etc/pf-sshuttle.conf'
498+
with open(pf_config_file, 'w+') as f:
499+
f.write('\n'.join(tables + translating_rules + filtering_rules) + '\n')
500+
501+
pfctl('-Ef', pf_config_file)
502+
os.remove(pf_config_file)
503+
else:
504+
pfctl('-dF', 'all')
505+
506+
466507
def program_exists(name):
467508
paths = (os.getenv('PATH') or os.defpath).split(os.pathsep)
468509
for p in paths:
@@ -541,15 +582,19 @@ def main(port_v6, port_v4, dnsport_v6, dnsport_v4, method, udp, syslog):
541582
method = "ipfw"
542583
elif program_exists('iptables'):
543584
method = "nat"
585+
elif program_exists('pfctl'):
586+
method = "pf"
544587
else:
545-
raise Fatal("can't find either ipfw or iptables; check your PATH")
588+
raise Fatal("can't find either ipfw, iptables or pfctl; check your PATH")
546589

547590
if method == "nat":
548591
do_it = do_iptables_nat
549592
elif method == "tproxy":
550593
do_it = do_iptables_tproxy
551594
elif method == "ipfw":
552595
do_it = do_ipfw
596+
elif method == "pf":
597+
do_it = do_pf
553598
else:
554599
raise Exception('Unknown method "%s"' % method)
555600

src/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def parse_ipport6(s):
116116
H,auto-hosts scan for remote hostnames and update local /etc/hosts
117117
N,auto-nets automatically determine subnets to route
118118
dns capture local DNS requests and forward to the remote DNS server
119-
method= auto, nat, tproxy, or ipfw
119+
method= auto, nat, tproxy, pf or ipfw
120120
python= path to python interpreter on the remote server
121121
r,remote= ssh hostname (and optional username) of remote sshuttle server
122122
x,exclude= exclude this subnet (can be used more than once)
@@ -183,7 +183,7 @@ def parse_ipport6(s):
183183
includes = parse_subnet_file(opt.subnets)
184184
if not opt.method:
185185
method = "auto"
186-
elif opt.method in ["auto", "nat", "tproxy", "ipfw"]:
186+
elif opt.method in ["auto", "nat", "tproxy", "ipfw", "pf"]:
187187
method = opt.method
188188
else:
189189
o.fatal("method %s not supported" % opt.method)

0 commit comments

Comments
 (0)