def do_iptables_nat(port, dnsport, family, subnets, udp): # only ipv4 supported with NAT if family != socket.AF_INET: raise Exception( 'Address family "%s" unsupported by nat method' % family_to_string(family)) if udp: raise Exception("UDP not supported by nat method") table = "nat" def ipt(*args): return _ipt(family, table, *args) def ipt_ttl(*args): return _ipt_ttl(family, table, *args) chain = 'sshuttle-%s' % port # basic cleanup/setup of chains if ipt_chain_exists(family, table, chain): nonfatal(ipt, '-D', 'OUTPUT', '-j', chain) nonfatal(ipt, '-D', 'PREROUTING', '-j', chain) nonfatal(ipt, '-F', chain) ipt('-X', chain) if subnets or dnsport: ipt('-N', chain) ipt('-F', chain) ipt('-I', 'OUTPUT', '1', '-j', chain) ipt('-I', 'PREROUTING', '1', '-j', chain) if subnets: # create new subnet entries. Note that we're sorting in a very # particular order: we need to go from most-specific (largest swidth) # to least-specific, and at any given level of specificity, we want # excludes to come first. That's why the columns are in such a non- # intuitive order. for f, swidth, sexclude, snet \ in sorted(subnets, key=lambda s: s[1], reverse=True): if sexclude: ipt('-A', chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), '-p', 'tcp') else: ipt_ttl('-A', chain, '-j', 'REDIRECT', '--dest', '%s/%s' % (snet, swidth), '-p', 'tcp', '--to-ports', str(port)) if dnsport: nslist = resolvconf_nameservers() for f, ip in filter(lambda i: i[0] == family, nslist): ipt_ttl('-A', chain, '-j', 'REDIRECT', '--dest', '%s/32' % ip, '-p', 'udp', '--dport', '53', '--to-ports', str(dnsport))
def do_pf(port, dnsport, family, subnets, udp): global _pf_started_by_sshuttle tables = [] translating_rules = [] filtering_rules = [] if subnets: includes = [] # If a given subnet is both included and excluded, list the exclusion # first; the table will ignore the second, opposite definition for f, swidth, sexclude, snet \ in sorted(subnets, key=lambda s: (s[1], s[2]), reverse=True): includes.append("%s%s/%s" % ("!" if sexclude else "", snet, swidth)) tables.append('table <forward_subnets> {%s}' % ','.join(includes)) translating_rules.append( 'rdr pass on lo0 proto tcp to <forward_subnets> -> 127.0.0.1 port %r' % port) filtering_rules.append( 'pass out route-to lo0 inet proto tcp to <forward_subnets> keep state' ) if dnsport: nslist = resolvconf_nameservers() tables.append('table <dns_servers> {%s}' % ','.join([ns[1] for ns in nslist])) translating_rules.append( 'rdr pass on lo0 proto udp to <dns_servers> port 53 -> 127.0.0.1 port %r' % dnsport) filtering_rules.append( 'pass out route-to lo0 inet proto udp to <dns_servers> port 53 keep state' ) rules = '\n'.join(tables + translating_rules + filtering_rules) + '\n' pf_status = pfctl('-s all')[0] if not '\nrdr-anchor "sshuttle" all\n' in pf_status: pf_add_anchor_rule(PF_RDR, "sshuttle") if not '\nanchor "sshuttle" all\n' in pf_status: pf_add_anchor_rule(PF_PASS, "sshuttle") pfctl('-a sshuttle -f /dev/stdin', rules) if sys.platform == "darwin": o = pfctl('-E') _pf_context['Xtoken'] = re.search(r'Token : (.+)', o[1]).group(1) elif 'INFO:\nStatus: Disabled' in pf_status: pfctl('-e') _pf_context['started_by_sshuttle'] = True else: pfctl('-a sshuttle -F all') if sys.platform == "darwin": pfctl('-X %s' % _pf_context['Xtoken']) elif _pf_context['started_by_sshuttle']: pfctl('-d')
def do_pf(port, dnsport, family, subnets, udp): global _pf_started_by_sshuttle tables = [] translating_rules = [] filtering_rules = [] if subnets: include_subnets = filter(lambda s:not s[2], sorted(subnets, reverse=True)) if include_subnets: tables.append('table <include_subnets> {%s}' % ','.join(["%s/%s" % (n[3], n[1]) for n in include_subnets])) translating_rules.append('rdr pass on lo0 proto tcp to <include_subnets> -> 127.0.0.1 port %r' % port) filtering_rules.append('pass out route-to lo0 inet proto tcp to <include_subnets> keep state') exclude_subnets = filter(lambda s:s[2], sorted(subnets, reverse=True)) if exclude_subnets: tables.append('table <exclude_subnets> {%s}' % ','.join(["%s/%s" % (n[3], n[1]) for n in exclude_subnets])) filtering_rules.append('pass out quick proto tcp from any to <exclude_subnets> keep state') if dnsport: nslist = resolvconf_nameservers() tables.append('table <dns_servers> {%s}' % ','.join([ns[1] for ns in nslist])) translating_rules.append('rdr pass on lo0 proto udp to <dns_servers> port 53 -> 127.0.0.1 port %r' % dnsport) filtering_rules.append('pass out route-to lo0 inet proto udp to <dns_servers> port 53 keep state') rules = '\n'.join(tables + translating_rules + filtering_rules) + '\n' pf_status = pfctl('-s all')[0] if not '\nrdr-anchor "sshuttle" all\n' in pf_status: pf_add_anchor_rule(PF_RDR, "sshuttle") if not '\nanchor "sshuttle" all\n' in pf_status: pf_add_anchor_rule(PF_PASS, "sshuttle") pfctl('-a sshuttle -f /dev/stdin', rules) if sys.platform == "darwin": o = pfctl('-E') _pf_context['Xtoken'] = re.search(r'Token : (.+)', o[1]).group(1) elif 'INFO:\nStatus: Disabled' in pf_status: pfctl('-e') _pf_context['started_by_sshuttle'] = True else: pfctl('-a sshuttle -F all') if sys.platform == "darwin": pfctl('-X %s' % _pf_context['Xtoken']) elif _pf_context['started_by_sshuttle']: pfctl('-d')
def do_pf(port, dnsport, family, subnets, udp): global _pf_started_by_sshuttle tables = [] translating_rules = [] filtering_rules = [] if subnets: includes=[] # If a given subnet is both included and excluded, list the exclusion # first; the table will ignore the second, opposite definition for f, swidth, sexclude, snet \ in sorted(subnets, key=lambda s: (s[1], s[2]), reverse=True): includes.append("%s%s/%s" % ("!" if sexclude else "", snet, swidth)) tables.append('table <forward_subnets> {%s}' % ','.join(includes)) translating_rules.append('rdr pass on lo0 proto tcp to <forward_subnets> -> 127.0.0.1 port %r' % port) filtering_rules.append('pass out route-to lo0 inet proto tcp to <forward_subnets> keep state') if dnsport: nslist = resolvconf_nameservers() tables.append('table <dns_servers> {%s}' % ','.join([ns[1] for ns in nslist])) translating_rules.append('rdr pass on lo0 proto udp to <dns_servers> port 53 -> 127.0.0.1 port %r' % dnsport) filtering_rules.append('pass out route-to lo0 inet proto udp to <dns_servers> port 53 keep state') rules = '\n'.join(tables + translating_rules + filtering_rules) + '\n' pf_status = pfctl('-s all')[0] if not '\nrdr-anchor "sshuttle" all\n' in pf_status: pf_add_anchor_rule(PF_RDR, "sshuttle") if not '\nanchor "sshuttle" all\n' in pf_status: pf_add_anchor_rule(PF_PASS, "sshuttle") pfctl('-a sshuttle -f /dev/stdin', rules) if sys.platform == "darwin": o = pfctl('-E') _pf_context['Xtoken'] = re.search(r'Token : (.+)', o[1]).group(1) elif 'INFO:\nStatus: Disabled' in pf_status: pfctl('-e') _pf_context['started_by_sshuttle'] = True else: pfctl('-a sshuttle -F all') if sys.platform == "darwin": pfctl('-X %s' % _pf_context['Xtoken']) elif _pf_context['started_by_sshuttle']: pfctl('-d')
def do_pf(port, dnsport, family, subnets, udp): exclude_set = [] redirect_set = [] ns_set = [] if dnsport: nslist = resolvconf_nameservers() for f, ip in nslist: ns_set.append(ip) if subnets: for f, swidth, sexclude, snet in sorted(subnets, reverse=True): if sexclude: exclude_set.append("%s/%s" % (snet, swidth)) else: redirect_set.append("%s/%s" % (snet, swidth)) ruleset = [ 'packets = "proto tcp to {%s}"' % ','.join(redirect_set), 'rdr pass on lo0 $packets -> 127.0.0.1 port %s' % port, 'pass out route-to lo0 inet $packets keep state' ] if len(ns_set) > 0: ns_ruleset = [ 'dnspackets = "proto udp to {%s} port 53"' % ','.join(ns_set), 'rdr pass on lo0 $dnspackets -> 127.0.0.1 port %s' % port, 'pass out route-to lo0 inet $dnspackets keep state' ] ruleset = list(sum(zip(ruleset, ns_ruleset), ())) if len(exclude_set) > 0: ruleset.append('pass out quick proto tcp to {%s}' % ','.join(exclude_set)) f = open('/etc/sshuttle.pf.conf', 'w+') f.write('\n'.join(ruleset) + '\n') f.close() pfctl('-f', '/etc/sshuttle.pf.conf', '-E') else: pfctl('-d') pfctl('-F', 'all')
def do_pf(port, dnsport, family, subnets, udp): exclude_set = [] redirect_set = [] ns_set = [] if dnsport: nslist = resolvconf_nameservers() for f, ip in nslist: ns_set.append(ip) if subnets: for f, swidth, sexclude, snet in sorted(subnets, reverse=True): if sexclude: exclude_set.append("%s/%s" % (snet,swidth)) else: redirect_set.append("%s/%s" % (snet,swidth)) ruleset = [ 'packets = "proto tcp to {%s}"' % ','.join(redirect_set), 'rdr pass on lo0 $packets -> 127.0.0.1 port %s' % port, 'pass out route-to lo0 inet $packets keep state' ] if len(ns_set) > 0: ns_ruleset = [ 'dnspackets = "proto udp to {%s} port 53"' % ','.join(ns_set), 'rdr pass on lo0 $dnspackets -> 127.0.0.1 port %s' % port, 'pass out route-to lo0 inet $dnspackets keep state' ] ruleset = list(sum(zip(ruleset, ns_ruleset), ())) if len(exclude_set) > 0: ruleset.append('pass out quick proto tcp to {%s}' % ','.join(exclude_set)) f = open('/etc/sshuttle.pf.conf', 'w+') f.write('\n'.join(ruleset) + '\n') f.close() pfctl('-f', '/etc/sshuttle.pf.conf', '-E') else: pfctl('-d') pfctl('-F', 'all')
def do_ipfw(port, dnsport, family, subnets, udp): # IPv6 not supported if family not in [socket.AF_INET, ]: raise Exception( 'Address family "%s" unsupported by ipfw method' % family_to_string(family)) if udp: raise Exception("UDP not supported by ipfw method") sport = str(port) xsport = str(port + 1) # cleanup any existing rules if ipfw_rule_exists(port): ipfw('delete', sport) while _changedctls: name = _changedctls.pop() oldval = _oldctls[name] _sysctl_set(name, oldval) if subnets or dnsport: sysctl_set('net.inet.ip.fw.enable', 1) changed = sysctl_set('net.inet.ip.scopedroute', 0, permanent=True) if changed: log("\n" " WARNING: ONE-TIME NETWORK DISRUPTION:\n" " =====================================\n" "sshuttle has changed a MacOS kernel setting to work around\n" "a bug in MacOS 10.6. This will cause your network to drop\n" "within 5-10 minutes unless you restart your network\n" "interface (change wireless networks or unplug/plug the\n" "ethernet port) NOW, then restart sshuttle. The fix is\n" "permanent; you only have to do this once.\n\n") sys.exit(1) ipfw('add', sport, 'check-state', 'ip', 'from', 'any', 'to', 'any') if subnets: # create new subnet entries for f, swidth, sexclude, snet \ in sorted(subnets, key=lambda s: s[1], reverse=True): if sexclude: ipfw('add', sport, 'skipto', xsport, 'tcp', 'from', 'any', 'to', '%s/%s' % (snet, swidth)) else: ipfw('add', sport, 'fwd', '127.0.0.1,%d' % port, 'tcp', 'from', 'any', 'to', '%s/%s' % (snet, swidth), 'not', 'ipttl', '42', 'keep-state', 'setup') # This part is much crazier than it is on Linux, because MacOS (at least # 10.6, and probably other versions, and maybe FreeBSD too) doesn't # correctly fixup the dstip/dstport for UDP packets when it puts them # through a 'fwd' rule. It also doesn't fixup the srcip/srcport in the # response packet. In Linux iptables, all that happens magically for us, # so we just redirect the packets and relax. # # On MacOS, we have to fix the ports ourselves. For that, we use a # 'divert' socket, which receives raw packets and lets us mangle them. # # Here's how it works. Let's say the local DNS server is 1.1.1.1:53, # and the remote DNS server is 2.2.2.2:53, and the local transproxy port # is 10.0.0.1:12300, and a client machine is making a request from # 10.0.0.5:9999. We see a packet like this: # 10.0.0.5:9999 -> 1.1.1.1:53 # Since the destip:port matches one of our local nameservers, it will # match a 'fwd' rule, thus grabbing it on the local machine. However, # the local kernel will then see a packet addressed to *:53 and # not know what to do with it; there's nobody listening on port 53. Thus, # we divert it, rewriting it into this: # 10.0.0.5:9999 -> 10.0.0.1:12300 # This gets proxied out to the server, which sends it to 2.2.2.2:53, # and the answer comes back, and the proxy sends it back out like this: # 10.0.0.1:12300 -> 10.0.0.5:9999 # But that's wrong! The original machine expected an answer from # 1.1.1.1:53, so we have to divert the *answer* and rewrite it: # 1.1.1.1:53 -> 10.0.0.5:9999 # # See? Easy stuff. if dnsport: divertsock = socket.socket(socket.AF_INET, socket.SOCK_RAW, IPPROTO_DIVERT) divertsock.bind(('0.0.0.0', port)) # IP field is ignored nslist = resolvconf_nameservers() for f, ip in filter(lambda i: i[0] == family, nslist): # relabel and then catch outgoing DNS requests ipfw('add', sport, 'divert', sport, 'udp', 'from', 'any', 'to', '%s/32' % ip, '53', 'not', 'ipttl', '42') # relabel DNS responses ipfw('add', sport, 'divert', sport, 'udp', 'from', 'any', str(dnsport), 'to', 'any', 'not', 'ipttl', '42') def do_wait(): while 1: r, w, x = select.select([sys.stdin, divertsock], [], []) if divertsock in r: _handle_diversion(divertsock, dnsport) if sys.stdin in r: return else: do_wait = None return do_wait
def do_iptables_tproxy(port, dnsport, family, subnets, udp): if family not in [socket.AF_INET, socket.AF_INET6]: raise Exception( 'Address family "%s" unsupported by tproxy method' % family_to_string(family)) table = "mangle" def ipt(*args): return _ipt(family, table, *args) def ipt_ttl(*args): return _ipt_ttl(family, table, *args) mark_chain = 'sshuttle-m-%s' % port tproxy_chain = 'sshuttle-t-%s' % port divert_chain = 'sshuttle-d-%s' % port # basic cleanup/setup of chains if ipt_chain_exists(family, table, mark_chain): ipt('-D', 'OUTPUT', '-j', mark_chain) ipt('-F', mark_chain) ipt('-X', mark_chain) if ipt_chain_exists(family, table, tproxy_chain): ipt('-D', 'PREROUTING', '-j', tproxy_chain) ipt('-F', tproxy_chain) ipt('-X', tproxy_chain) if ipt_chain_exists(family, table, divert_chain): ipt('-F', divert_chain) ipt('-X', divert_chain) if subnets or dnsport: ipt('-N', mark_chain) ipt('-F', mark_chain) ipt('-N', divert_chain) ipt('-F', divert_chain) ipt('-N', tproxy_chain) ipt('-F', tproxy_chain) ipt('-I', 'OUTPUT', '1', '-j', mark_chain) ipt('-I', 'PREROUTING', '1', '-j', tproxy_chain) ipt('-A', divert_chain, '-j', 'MARK', '--set-mark', '1') ipt('-A', divert_chain, '-j', 'ACCEPT') ipt('-A', tproxy_chain, '-m', 'socket', '-j', divert_chain, '-m', 'tcp', '-p', 'tcp') if subnets and udp: ipt('-A', tproxy_chain, '-m', 'socket', '-j', divert_chain, '-m', 'udp', '-p', 'udp') if dnsport: nslist = resolvconf_nameservers() for f, ip in filter(lambda i: i[0] == family, nslist): ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1', '--dest', '%s/32' % ip, '-m', 'udp', '-p', 'udp', '--dport', '53') ipt('-A', tproxy_chain, '-j', 'TPROXY', '--tproxy-mark', '0x1/0x1', '--dest', '%s/32' % ip, '-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', str(dnsport)) if subnets: for f, swidth, sexclude, snet \ in sorted(subnets, key=lambda s: s[1], reverse=True): if sexclude: ipt('-A', mark_chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), '-m', 'tcp', '-p', 'tcp') ipt('-A', tproxy_chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), '-m', 'tcp', '-p', 'tcp') else: ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1', '--dest', '%s/%s' % (snet, swidth), '-m', 'tcp', '-p', 'tcp') ipt('-A', tproxy_chain, '-j', 'TPROXY', '--tproxy-mark', '0x1/0x1', '--dest', '%s/%s' % (snet, swidth), '-m', 'tcp', '-p', 'tcp', '--on-port', str(port)) if sexclude and udp: ipt('-A', mark_chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), '-m', 'udp', '-p', 'udp') ipt('-A', tproxy_chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), '-m', 'udp', '-p', 'udp') elif udp: ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1', '--dest', '%s/%s' % (snet, swidth), '-m', 'udp', '-p', 'udp') ipt('-A', tproxy_chain, '-j', 'TPROXY', '--tproxy-mark', '0x1/0x1', '--dest', '%s/%s' % (snet, swidth), '-m', 'udp', '-p', 'udp', '--on-port', str(port))