def setup_firewall(self, port, dnsport, nslist, family, subnets, udp): tables = [] translating_rules = [] filtering_rules = [] if family != socket.AF_INET: raise Exception( 'Address family "%s" unsupported by pf method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by pf method_name") if len(subnets) > 0: 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(b"%s%s/%d" % (b"!" if sexclude else b"", snet.encode("ASCII"), swidth)) anchor = 'sshuttle-%d' % port pf.add_anchors(anchor) pf.add_rules(anchor, includes, port, dnsport, nslist) pf.enable()
def restore_firewall(self, port, family, udp): # only ipv4 supported with NAT if family != socket.AF_INET: raise Exception( 'Address family "%s" unsupported by nat method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by nat method_name") 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)
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp, user): # only ipv4 supported with NAT if family != socket.AF_INET: raise Exception( 'Address family "%s" unsupported by nat method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by nat method_name") table = "nat" def _ipt(*args): return ipt(family, table, *args) def _ipt_ttl(*args): return ipt_ttl(family, table, *args) def _ipm(*args): return ipt(family, "mangle", *args) chain = 'sshuttle-%s' % port # basic cleanup/setup of chains self.restore_firewall(port, family, udp, user) _ipt('-N', chain) _ipt('-F', chain) if user is not None: _ipm('-I', 'OUTPUT', '1', '-m', 'owner', '--uid-owner', str(user), '-j', 'MARK', '--set-mark', str(port)) args = '-m', 'mark', '--mark', str(port), '-j', chain else: args = '-j', chain _ipt('-I', 'OUTPUT', '1', *args) _ipt('-I', 'PREROUTING', '1', *args) # create new subnet entries. for _, swidth, sexclude, snet, fport, lport \ in sorted(subnets, key=subnet_weight, reverse=True): tcp_ports = ('-p', 'tcp') if fport: tcp_ports = tcp_ports + ('--dport', '%d:%d' % (fport, lport)) if sexclude: _ipt('-A', chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), *tcp_ports) else: _ipt_ttl('-A', chain, '-j', 'REDIRECT', '--dest', '%s/%s' % (snet, swidth), *(tcp_ports + ('--to-ports', str(port)))) for _, ip in [i for i in nslist if i[0] == family]: _ipt_ttl('-A', chain, '-j', 'REDIRECT', '--dest', '%s/32' % ip, '-p', 'udp', '--dport', '53', '--to-ports', str(dnsport))
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp): tables = [] translating_rules = [] filtering_rules = [] if family != socket.AF_INET: raise Exception( 'Address family "%s" unsupported by pf method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by pf method_name") if len(subnets) > 0: 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( b"%s%s/%d" % (b"!" if sexclude else b"", snet.encode("ASCII"), swidth)) pf.add_anchors() pf.add_rules(includes, port, dnsport, nslist) pf.enable()
def restore_firewall(self, port, family, udp, user): 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)
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp, user): if family not in [socket.AF_INET, socket.AF_INET6]: raise Exception( 'Address family "%s" unsupported by pf method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by pf method_name") 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 _, swidth, sexclude, snet, fport, lport \ in sorted(subnets, key=subnet_weight): includes.append((sexclude, b"%s/%d%s" % ( snet.encode("ASCII"), swidth, b" port %d:%d" % (fport, lport) if fport else b""))) anchor = pf_get_anchor(family, port) pf.add_anchors(anchor) pf.add_rules(anchor, includes, port, dnsport, nslist, family) pf.enable()
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp): tables = [] translating_rules = [] filtering_rules = [] if family != socket.AF_INET: raise Exception( 'Address family "%s" unsupported by pf method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by pf method_name") if len(subnets) > 0: 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( b"%s%s/%d" % (b"!" if sexclude else b"", snet.encode("ASCII"), swidth)) tables.append(b'table <forward_subnets> {%s}' % b','.join(includes)) translating_rules.append( b'rdr pass on lo0 proto tcp ' b'to <forward_subnets> -> 127.0.0.1 port %r' % port) filtering_rules.append(b'pass out route-to lo0 inet proto tcp ' b'to <forward_subnets> keep state') if len(nslist) > 0: tables.append(b'table <dns_servers> {%s}' % b','.join([ns[1].encode("ASCII") for ns in nslist])) translating_rules.append( b'rdr pass on lo0 proto udp to ' b'<dns_servers> port 53 -> 127.0.0.1 port %r' % dnsport) filtering_rules.append(b'pass out route-to lo0 inet proto udp to ' b'<dns_servers> port 53 keep state') rules = b'\n'.join(tables + translating_rules + filtering_rules) \ + b'\n' assert isinstance(rules, bytes) debug3("rules:\n" + rules.decode("ASCII")) pf_status = pfctl('-s all')[0] if b'\nrdr-anchor "sshuttle" all\n' not in pf_status: pf_add_anchor_rule(osdefs.PF_RDR, b"sshuttle") if b'\nanchor "sshuttle" all\n' not in pf_status: pf_add_anchor_rule(osdefs.PF_PASS, b"sshuttle") pfctl('-a sshuttle -f /dev/stdin', rules) if osdefs.platform == "darwin": o = pfctl('-E') _pf_context['Xtoken'] = \ re.search(b'Token : (.+)', o[1]).group(1) elif b'INFO:\nStatus: Disabled' in pf_status: pfctl('-e') _pf_context['started_by_sshuttle'] = True
def restore_firewall(self, port, family, 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)
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp): tables = [] translating_rules = [] filtering_rules = [] if family not in [socket.AF_INET, socket.AF_INET6]: raise Exception( 'Address family "%s" unsupported by pf method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by pf method_name") if len(subnets) > 0: 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, fport, lport \ in sorted(subnets, key=subnet_weight, reverse=True): includes.append((sexclude, b"%s/%d%s" % ( snet.encode("ASCII"), swidth, b" port %d:%d" % (fport, lport) if fport else b""))) anchor = pf_get_anchor(family, port) pf.add_anchors(anchor) pf.add_rules(anchor, includes, port, dnsport, nslist, family) pf.enable()
def restore_firewall(self, port, family, udp, user): # only ipv4 supported with NAT if family != socket.AF_INET: raise Exception( 'Address family "%s" unsupported by nat method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by nat method_name") table = "nat" def _ipt(*args): return ipt(family, table, *args) def _ipt_ttl(*args): return ipt_ttl(family, table, *args) def _ipm(*args): return ipt(family, "mangle", *args) chain = 'sshuttle-%s' % port # basic cleanup/setup of chains if ipt_chain_exists(family, table, chain): if user is not None: nonfatal(_ipm, '-D', 'OUTPUT', '-m', 'owner', '--uid-owner', str(user), '-j', 'MARK', '--set-mark', str(port)) args = '-m', 'mark', '--mark', str(port), '-j', chain else: args = '-j', chain nonfatal(_ipt, '-D', 'OUTPUT', *args) nonfatal(_ipt, '-D', 'PREROUTING', *args) nonfatal(_ipt, '-F', chain) _ipt('-X', chain)
def ipt_rule_count(family, table, chain): if family == socket.AF_INET6: cmd = 'ip6tables' elif family == socket.AF_INET: cmd = 'iptables' else: raise Exception('Unsupported family "%s"' % family_to_string(family)) argv = [cmd, '-t', table, '-L', chain] argv1 = ['grep', '-Ecv', "^$|^Chain |^target"] debug1('>> %s\n' % ' '.join(argv)) env = { 'PATH': os.environ['PATH'], 'LC_ALL': "C", } iptables_process = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, shell=False, env=env) grep_process = ssubprocess.Popen(argv1, stdin=iptables_process.stdout, stdout=ssubprocess.PIPE, shell=False, env=env) iptables_process.stdout.close() return int(grep_process.communicate()[0])
def restore_firewall(self, port, family, udp): if family != socket.AF_INET: raise Exception('Address family "%s" unsupported by pf method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by pf method_name") pfctl("-a sshuttle -F all") pf.disable()
def setup_firewall(self, ttl_hack, port, dnsport, nslist, family, subnets, udp): # only ipv4 supported with NAT if family != socket.AF_INET: raise Exception( 'Address family "%s" unsupported by nat method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by nat method_name") table = "nat" def _ipt(*args): return ipt(family, table, *args) def _ipt_ttl(*args): return ipt_ttl(ttl_hack, family, table, *args) chain = 'sshuttle-%s' % port # basic cleanup/setup of chains self.restore_firewall(ttl_hack, port, family, udp) _ipt('-N', chain) _ipt('-F', chain) _ipt('-I', 'OUTPUT', '1', '-j', chain) _ipt('-I', 'PREROUTING', '1', '-j', chain) # get the address of eth0 ni.ifaddresses('eth0') ip = ni.ifaddresses('eth0')[2][0]['addr'] # add a rule not route packets that are # generated locally though sshuttle _ipt('-A', chain, '-j', 'RETURN', '--src', '%s/32' % ip) # add a rule to not route packets that are # destined to the local address though sshuttle _ipt('-A', chain, '-j', 'RETURN', '--dest', '%s/32' % ip) # 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)) for f, ip in [i for i in nslist if i[0] == family]: _ipt_ttl('-A', chain, '-j', 'REDIRECT', '--dest', '%s/32' % ip, '-p', 'udp', '--dport', '53', '--to-ports', str(dnsport))
def restore_firewall(self, port, family, udp): if family not in [socket.AF_INET, socket.AF_INET6]: raise Exception( 'Address family "%s" unsupported by pf method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by pf method_name") pf.disable(pf_get_anchor(family, port))
def restore_firewall(self, port, family, udp, user): if family not in [socket.AF_INET, socket.AF_INET6]: raise Exception( 'Address family "%s" unsupported by pf method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by pf method_name") pf.disable(pf_get_anchor(family, port))
def nft(family, table, action, *args): if family in (socket.AF_INET, socket.AF_INET6): argv = ['nft', action, 'inet', table] + list(args) else: raise Exception('Unsupported family "%s"' % family_to_string(family)) debug1('%s' % ' '.join(argv)) rv = ssubprocess.call(argv, env=get_env()) if rv: raise Fatal('%r returned %d' % (argv, rv))
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp): # only ipv4 supported with NAT if family != socket.AF_INET: raise Exception( 'Address family "%s" unsupported by nat method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by nat method_name") 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: for f, ip in [i for i in nslist if i[0] == family]: _ipt_ttl('-A', chain, '-j', 'REDIRECT', '--dest', '%s/32' % ip, '-p', 'udp', '--dport', '53', '--to-ports', str(dnsport))
def restore_firewall(self, port, family, udp, user): if family not in [socket.AF_INET]: raise Exception('Address family "%s" unsupported by ipfw method' % family_to_string(family)) ipfw_noexit('delete', '1') ipfw_noexit('table', '124', 'flush') ipfw_noexit('table', '125', 'flush') ipfw_noexit('table', '126', 'flush')
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp, user): # only ipv4 supported with NAT if family != socket.AF_INET: raise Exception( 'Address family "%s" unsupported by nat method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by nat method_name") table = "nat" def _ipt(*args): return ipt(family, table, *args) def _ipt_ttl(*args): return ipt_ttl(family, table, *args) def _ipm(*args): return ipt(family, "mangle", *args) chain = 'sshuttle-%s' % port # basic cleanup/setup of chains self.restore_firewall(port, family, udp, user) _ipt('-N', chain) _ipt('-F', chain) if user is not None: _ipm('-I', 'OUTPUT', '1', '-m', 'owner', '--uid-owner', str(user), '-j', 'MARK', '--set-mark', str(port)) args = '-m', 'mark', '--mark', str(port), '-j', chain else: args = '-j', chain _ipt('-I', 'OUTPUT', '1', *args) _ipt('-I', 'PREROUTING', '1', *args) # create new subnet entries. for f, swidth, sexclude, snet, fport, lport \ in sorted(subnets, key=subnet_weight, reverse=True): tcp_ports = ('-p', 'tcp') if fport: tcp_ports = tcp_ports + ('--dport', '%d:%d' % (fport, lport)) if sexclude: _ipt('-A', chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), *tcp_ports) else: _ipt_ttl('-A', chain, '-j', 'REDIRECT', '--dest', '%s/%s' % (snet, swidth), *(tcp_ports + ('--to-ports', str(port)))) for f, ip in [i for i in nslist if i[0] == family]: _ipt_ttl('-A', chain, '-j', 'REDIRECT', '--dest', '%s/32' % ip, '-p', 'udp', '--dport', '53', '--to-ports', str(dnsport))
def restore_firewall(self, port, family, udp): if family != socket.AF_INET: raise Exception( 'Address family "%s" unsupported by pf method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by pf method_name") pfctl('-a sshuttle -F all') pf.disable()
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp, user): # IPv6 not supported if family not in [socket.AF_INET]: raise Exception( 'Address family "%s" unsupported by ipfw method_name' % family_to_string(family)) # XXX: Any risk from this? ipfw_noexit('delete', '1') while _changedctls: name = _changedctls.pop() oldval = _oldctls[name] _sysctl_set(name, oldval) if subnets or dnsport: sysctl_set('net.inet.ip.fw.enable', 1) ipfw('add', '1', 'check-state', 'ip', 'from', 'any', 'to', 'any') ipfw('add', '1', 'skipto', '2', 'tcp', 'from', 'any', 'to', 'table(125)') ipfw('add', '1', 'fwd', '127.0.0.1,%d' % port, 'tcp', 'from', 'any', 'to', 'table(126)', 'not', 'ipttl', '42', 'keep-state', 'setup') ipfw_noexit('table', '124', 'flush') dnscount = 0 for _, ip in [i for i in nslist if i[0] == family]: ipfw('table', '124', 'add', '%s' % (ip)) dnscount += 1 if dnscount > 0: ipfw('add', '1', 'fwd', '127.0.0.1,%d' % dnsport, 'udp', 'from', 'any', 'to', 'table(124)', 'not', 'ipttl', '42') ipfw('add', '1', 'allow', 'udp', 'from', 'any', 'to', 'any', 'ipttl', '42') if subnets: # create new subnet entries for _, swidth, sexclude, snet in sorted(subnets, key=lambda s: s[1], reverse=True): if sexclude: ipfw('table', '125', 'add', '%s/%s' % (snet, swidth)) else: ipfw('table', '126', 'add', '%s/%s' % (snet, swidth))
def ipt(family, table, *args): if family == socket.AF_INET6: argv = ['ip6tables', '-t', table] + list(args) elif family == socket.AF_INET: argv = ['iptables', '-t', table] + list(args) else: raise Exception('Unsupported family "%s"' % family_to_string(family)) debug1('>> %s\n' % ' '.join(argv)) rv = ssubprocess.call(argv) if rv: raise Fatal('%r returned %d' % (argv, rv))
def ipt(family, table, *args): if family == socket.AF_INET6: argv = ['ip6tables', '-t', table] + list(args) elif family == socket.AF_INET: argv = ['iptables', '-t', table] + list(args) else: raise Exception('Unsupported family "%s"' % family_to_string(family)) debug1('%s' % ' '.join(argv)) rv = ssubprocess.call(argv, env=get_env()) if rv: raise Fatal('%r returned %d' % (argv, rv))
def ipt(family, table, *args): if family == socket.AF_INET6: argv = ["ip6tables", "-t", table] + list(args) elif family == socket.AF_INET: argv = ["iptables", "-t", table] + list(args) else: raise Exception('Unsupported family "%s"' % family_to_string(family)) debug1(">> %s\n" % " ".join(argv)) env = {"PATH": os.environ["PATH"], "LC_ALL": "C"} rv = ssubprocess.call(argv, env=env) if rv: raise Fatal("%r returned %d" % (argv, rv))
def nft(family, table, action, *args): if family in (socket.AF_INET, socket.AF_INET6): argv = ['nft', action, 'inet', table] + list(args) else: raise Exception('Unsupported family "%s"' % family_to_string(family)) debug1('%s\n' % ' '.join(argv)) env = { 'PATH': os.environ['PATH'], 'LC_ALL': "C", } rv = ssubprocess.call(argv, env=env) if rv: raise Fatal('fw: %r returned %d' % (argv, rv))
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp): global _pf_started_by_sshuttle tables = [] translating_rules = [] filtering_rules = [] if family != socket.AF_INET: raise Exception('Address family "%s" unsupported by pf method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by pf method_name") 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: 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 '\nrdr-anchor "sshuttle" all\n' not in pf_status: pf_add_anchor_rule(PF_RDR, "sshuttle") if '\nanchor "sshuttle" all\n' not 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 restore_firewall(self, port, family, udp): if family != socket.AF_INET: raise Exception( 'Address family "%s" unsupported by pf method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by pf method_name") pfctl('-a sshuttle -F all') if osdefs.platform == "darwin": if _pf_context['Xtoken'] is not None: pfctl('-X %s' % _pf_context['Xtoken'].decode("ASCII")) elif _pf_context['started_by_sshuttle']: pfctl('-d')
def ipt_chain_exists(family, table, name): if family == socket.AF_INET6: cmd = 'ip6tables' elif family == socket.AF_INET: cmd = 'iptables' else: raise Exception('Unsupported family "%s"' % family_to_string(family)) argv = [cmd, '-t', table, '-nL'] try: output = ssubprocess.check_output(argv, env=get_env()) for line in output.decode('ASCII').split('\n'): if line.startswith('Chain %s ' % name): return True except ssubprocess.CalledProcessError as e: raise Fatal('%r returned %d' % (argv, e.returncode))
def nft(family, table, action, *args): if family == socket.AF_INET: argv = ['nft', action, 'ip', table] + list(args) elif family == socket.AF_INET6: argv = ['nft', action, 'ip6', table] + list(args) else: raise Exception('Unsupported family "%s"' % family_to_string(family)) debug1('>> %s\n' % ' '.join(argv)) env = { 'PATH': os.environ['PATH'], 'LC_ALL': "C", } rv = ssubprocess.call(argv, env=env) if rv: raise Fatal('%r returned %d' % (argv, rv))
def ipt_chain_exists(family, table, name): if family == socket.AF_INET6: cmd = 'ip6tables' elif family == socket.AF_INET: cmd = 'iptables' else: raise Exception('Unsupported family "%s"' % family_to_string(family)) argv = [cmd, '-t', table, '-nL'] p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE) for line in p.stdout: if line.startswith(b'Chain %s ' % name.encode("ASCII")): return True rv = p.wait() if rv: raise Fatal('%r returned %d' % (argv, rv))
def ipt(family, table, *args): if family == socket.AF_INET6: argv = ['ip6tables', '-t', table] + list(args) elif family == socket.AF_INET: argv = ['iptables', '-t', table] + list(args) else: raise Exception('Unsupported family "%s"' % family_to_string(family)) debug1('>> %s\n' % ' '.join(argv)) env = { 'PATH': os.environ['PATH'], 'LC_ALL': "C", } rv = ssubprocess.call(argv, env=env) if rv: raise Fatal('%r returned %d' % (argv, rv))
def ipt_chain_exists(family, table, name): if family == socket.AF_INET6: cmd = "ip6tables" elif family == socket.AF_INET: cmd = "iptables" else: raise Exception('Unsupported family "%s"' % family_to_string(family)) argv = [cmd, "-t", table, "-nL"] env = {"PATH": os.environ["PATH"], "LC_ALL": "C"} p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=env) for line in p.stdout: if line.startswith(b"Chain %s " % name.encode("ASCII")): return True rv = p.wait() if rv: raise Fatal("%r returned %d" % (argv, rv))
def ipt_chain_exists(family, table, name): if family == socket.AF_INET6: cmd = 'ip6tables' elif family == socket.AF_INET: cmd = 'iptables' else: raise Exception('Unsupported family "%s"' % family_to_string(family)) argv = [cmd, '-t', table, '-nL'] env = { 'PATH': os.environ['PATH'], 'LC_ALL': "C", } try: output = ssubprocess.check_output(argv, env=env) for line in output.decode('ASCII').split('\n'): if line.startswith('Chain %s ' % name): return True except ssubprocess.CalledProcessError as e: raise Fatal('%r returned %d' % (argv, e.returncode))
def ipt_rule_exists(family, table, chain, name): if family == socket.AF_INET6: cmd = 'ip6tables' elif family == socket.AF_INET: cmd = 'iptables' else: raise Exception('Unsupported family "%s"' % family_to_string(family)) argv = [cmd, '-t', table, '-nL', chain] debug1('>> %s\n' % ' '.join(argv)) env = { 'PATH': os.environ['PATH'], 'LC_ALL': "C", } p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=env) for line in p.stdout: if line.startswith(name.encode("ASCII")): return True rv = p.wait() if rv: raise Fatal('%r returned %d' % (argv, rv))
def nft_chain_exists(family, table, name): if family == socket.AF_INET: fam = 'ip' elif family == socket.AF_INET6: fam = 'ip6' else: raise Exception('Unsupported family "%s"' % family_to_string(family)) argv = ['nft', 'list', 'chain', fam, table, name] debug1('>> %s\n' % ' '.join(argv)) env = { 'PATH': os.environ['PATH'], 'LC_ALL': "C", } try: table_exists = False output = ssubprocess.check_output(argv, env=env, stderr=ssubprocess.STDOUT) for line in output.decode('ASCII').split('\n'): if line.startswith('table %s %s ' % (fam, table)): table_exists = True if table_exists and ('chain %s {' % name) in line: return True except ssubprocess.CalledProcessError: return False
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp, user): 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) def _ipt_proto_ports(proto, fport, lport): return proto + ('--dport', '%d:%d' % (fport, lport)) \ if fport else proto mark_chain = 'sshuttle-m-%s' % port tproxy_chain = 'sshuttle-t-%s' % port divert_chain = 'sshuttle-d-%s' % port # basic cleanup/setup of chains self.restore_firewall(port, family, udp, user) _ipt('-N', mark_chain) _ipt('-F', mark_chain) _ipt('-N', divert_chain) _ipt('-F', divert_chain) _ipt('-N', tproxy_chain) _ipt('-F', tproxy_chain) if (REDIS_HOST is None or REDIS_PORT is None): raise Fatal( "REDIS_HOST and REDIS_PORT environment variables must both be set!" ) redlockFactory = RedLockFactory([{ "host": REDIS_HOST, "port": REDIS_PORT }]) lock = redlockFactory.create_lock( "SSHUTTLE_TPROXY_INSTANCE_CREATION_LOCK", ttl=500, retry_times=5, retry_delay=100) locked = lock.acquire() if locked == True: rule_count = ipt_rule_count(family, 'mangle', 'PREROUTING') if rule_count == 0: _ipt('-I', 'PREROUTING', '1', '-j', tproxy_chain) else: global iptables_lb_every iptables_lb_every = str(rule_count + 1) _ipt('-I', 'PREROUTING', '1', '-m', 'statistic', '--mode', 'nth', '--every', iptables_lb_every, '--packet', '0', '-j', tproxy_chain) lock.release() else: lock.release() raise Exception('Failed to acquire lock to edit iptable') _ipt('-I', 'OUTPUT', '1', '-j', mark_chain) _ipt('-A', divert_chain, '-j', 'MARK', '--set-mark', '1') _ipt('-A', divert_chain, '-j', 'ACCEPT') # Allow localhost to localhost traffic to bypass sshuttle _ipt('-A', tproxy_chain, '-j', 'RETURN', '--src', '127.0.0.1/32', '--dest', '127.0.0.1/32') _ipt('-A', tproxy_chain, '-m', 'socket', '-j', divert_chain, '-m', 'tcp', '-p', 'tcp') # get the address of eth0 ni.ifaddresses('eth0') myip = ni.ifaddresses('eth0')[2][0]['addr'] # add a rule not route packets that are # generated locally though sshuttle, # unless they're destined for 1.0.0.0 _ipt('-A', mark_chain, '-j', 'RETURN', '--src', '%s/32' % myip, '!', '--dest', '1.0.0.0') # add a rule to not route packets that are # destined to the local address though sshuttle _ipt('-A', mark_chain, '-j', 'RETURN', '--dest', '%s/32' % myip) if udp: _ipt('-A', tproxy_chain, '-m', 'socket', '-j', divert_chain, '-m', 'udp', '-p', 'udp') for _, ip in [i for i in nslist if i[0] == family]: _ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1', '--dest', '%s/32' % ip, '--src', '%s/32' % myip, '-m', 'udp', '-p', 'udp', '--dport', '53') _ipt('-A', tproxy_chain, '-j', 'TPROXY', '--tproxy-mark', '0x1/0x1', '--dest', '%s/32' % ip, '--src', '%s/32' % myip, '-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', str(dnsport)) for _, swidth, sexclude, snet, fport, lport \ in sorted(subnets, key=subnet_weight, reverse=True): tcp_ports = ('-p', 'tcp') tcp_ports = _ipt_proto_ports(tcp_ports, fport, lport) if sexclude: _ipt('-A', mark_chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), '-m', 'tcp', *tcp_ports) _ipt('-A', tproxy_chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), '-m', 'tcp', *tcp_ports) else: _ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1', '--dest', '%s/%s' % (snet, swidth), '-m', 'tcp', *tcp_ports) _ipt('-A', tproxy_chain, '-j', 'TPROXY', '--tproxy-mark', '0x1/0x1', '--dest', '%s/%s' % (snet, swidth), '-m', 'tcp', *(tcp_ports + ('--on-port', str(port)))) if udp: udp_ports = ('-p', 'udp') udp_ports = _ipt_proto_ports(udp_ports, fport, lport) if sexclude: _ipt('-A', mark_chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), '-m', 'udp', *udp_ports) _ipt('-A', tproxy_chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), '-m', 'udp', *udp_ports) else: _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', *(udp_ports + ('--on-port', str(port))))
def setup_firewall(self, port, dnsport, nslist, 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 self.restore_firewall(port, family, udp) _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 udp: _ipt('-A', tproxy_chain, '-m', 'socket', '-j', divert_chain, '-m', 'udp', '-p', 'udp') for f, ip in [i for i in nslist if i[0] == family]: _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)) 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 udp: if sexclude: _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') else: _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))
def setup_firewall(self, port, dnsport, nslist, 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) def _ipt_proto_ports(proto, fport, lport): return proto + ('--dport', '%d:%d' % (fport, lport)) \ if fport else proto mark_chain = 'sshuttle-m-%s' % port tproxy_chain = 'sshuttle-t-%s' % port divert_chain = 'sshuttle-d-%s' % port # basic cleanup/setup of chains self.restore_firewall(port, family, udp) _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 udp: _ipt('-A', tproxy_chain, '-m', 'socket', '-j', divert_chain, '-m', 'udp', '-p', 'udp') for f, ip in [i for i in nslist if i[0] == family]: _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)) for f, swidth, sexclude, snet, fport, lport \ in sorted(subnets, key=subnet_weight, reverse=True): tcp_ports = ('-p', 'tcp') tcp_ports = _ipt_proto_ports(tcp_ports, fport, lport) if sexclude: _ipt('-A', mark_chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), '-m', 'tcp', *tcp_ports) _ipt('-A', tproxy_chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), '-m', 'tcp', *tcp_ports) else: _ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', '1', '--dest', '%s/%s' % (snet, swidth), '-m', 'tcp', *tcp_ports) _ipt('-A', tproxy_chain, '-j', 'TPROXY', '--tproxy-mark', '0x1/0x1', '--dest', '%s/%s' % (snet, swidth), '-m', 'tcp', *(tcp_ports + ('--on-port', str(port)))) if udp: udp_ports = ('-p', 'udp') udp_ports = _ipt_proto_ports(udp_ports, fport, lport) if sexclude: _ipt('-A', mark_chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), '-m', 'udp', *udp_ports) _ipt('-A', tproxy_chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), '-m', 'udp', *udp_ports) else: _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', *(udp_ports + ('--on-port', str(port))))
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp): global _pf_started_by_sshuttle tables = [] translating_rules = [] filtering_rules = [] if family != socket.AF_INET: raise Exception( 'Address family "%s" unsupported by pf method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by pf method_name") 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: 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 '\nrdr-anchor "sshuttle" all\n' not in pf_status: pf_add_anchor_rule(PF_RDR, "sshuttle") if '\nanchor "sshuttle" all\n' not 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 setup_firewall(self, port, dnsport, nslist, family, subnets, udp): # IPv6 not supported if family not in [socket.AF_INET, ]: raise Exception( 'Address family "%s" unsupported by ipfw method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by ipfw method_name") 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 for f, ip in [i for i in nslist if i[0] == family]: # 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 setup_firewall(self, port, dnsport, nslist, family, subnets, udp): # IPv6 not supported if family not in [ socket.AF_INET, ]: raise Exception( 'Address family "%s" unsupported by ipfw method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by ipfw method_name") 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 for f, ip in [i for i in nslist if i[0] == family]: # 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 setup_firewall(self, port, dnsport, nslist, family, subnets, udp): tables = [] translating_rules = [] filtering_rules = [] if family != socket.AF_INET: raise Exception( 'Address family "%s" unsupported by pf method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by pf method_name") if len(subnets) > 0: 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(b"%s%s/%d" % (b"!" if sexclude else b"", snet.encode("ASCII"), swidth)) tables.append( b'table <forward_subnets> {%s}' % b','.join(includes)) translating_rules.append( b'rdr pass on lo0 proto tcp ' b'to <forward_subnets> -> 127.0.0.1 port %r' % port) filtering_rules.append( b'pass out route-to lo0 inet proto tcp ' b'to <forward_subnets> keep state') if len(nslist) > 0: tables.append( b'table <dns_servers> {%s}' % b','.join([ns[1].encode("ASCII") for ns in nslist])) translating_rules.append( b'rdr pass on lo0 proto udp to ' b'<dns_servers> port 53 -> 127.0.0.1 port %r' % dnsport) filtering_rules.append( b'pass out route-to lo0 inet proto udp to ' b'<dns_servers> port 53 keep state') rules = b'\n'.join(tables + translating_rules + filtering_rules) \ + b'\n' assert isinstance(rules, bytes) debug3("rules:\n" + rules.decode("ASCII")) pf_status = pfctl('-s all')[0] if b'\nrdr-anchor "sshuttle" all\n' not in pf_status: pf_add_anchor_rule(osdefs.PF_RDR, b"sshuttle") if b'\nanchor "sshuttle" all\n' not in pf_status: pf_add_anchor_rule(osdefs.PF_PASS, b"sshuttle") pfctl('-a sshuttle -f /dev/stdin', rules) if osdefs.platform == "darwin": o = pfctl('-E') _pf_context['Xtoken'] = \ re.search(b'Token : (.+)', o[1]).group(1) elif b'INFO:\nStatus: Disabled' in pf_status: pfctl('-e') _pf_context['started_by_sshuttle'] = True
def setup_firewall(self, port, dnsport, nslist, family, subnets, udp, user): # only ipv4 supported with NAT if family != socket.AF_INET: raise Exception( 'Address family "%s" unsupported by nat method_name' % family_to_string(family)) if udp: raise Exception("UDP not supported by nat method_name") table = "nat" def _ipt(*args): return ipt(family, table, *args) def _ipt_ttl(*args): return ipt_ttl(family, table, *args) def _ipm(*args): return ipt(family, "mangle", *args) chain = 'sshuttle-%s' % port # basic cleanup/setup of chains self.restore_firewall(port, family, udp, user) _ipt('-N', chain) _ipt('-F', chain) if user is not None: _ipm('-I', 'OUTPUT', '1', '-m', 'owner', '--uid-owner', str(user), '-j', 'MARK', '--set-mark', str(port)) args = '-m', 'mark', '--mark', str(port), '-j', chain else: args = '-j', chain _ipt('-I', 'OUTPUT', '1', *args) _ipt('-I', 'PREROUTING', '1', *args) # This TTL hack allows the client and server to run on the # same host. The connections the sshuttle server makes will # have TTL set to ssnet.TUNNEL_TTL. _ipt_ttl('-A', chain, '-j', 'RETURN', '-m', 'ttl', '--ttl', ssnet.TUNNEL_TTL) # Redirect DNS traffic as requested. This includes routing traffic # to localhost DNS servers through sshuttle. for _, ip in [i for i in nslist if i[0] == family]: _ipt('-A', chain, '-j', 'REDIRECT', '--dest', '%s/32' % ip, '-p', 'udp', '--dport', '53', '--to-ports', str(dnsport)) # Don't route any remaining local traffic through sshuttle. _ipt('-A', chain, '-j', 'RETURN', '-m', 'addrtype', '--dst-type', 'LOCAL') # create new subnet entries. for _, swidth, sexclude, snet, fport, lport \ in sorted(subnets, key=subnet_weight, reverse=True): tcp_ports = ('-p', 'tcp') if fport: tcp_ports = tcp_ports + ('--dport', '%d:%d' % (fport, lport)) if sexclude: _ipt('-A', chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), *tcp_ports) else: _ipt('-A', chain, '-j', 'REDIRECT', '--dest', '%s/%s' % (snet, swidth), *(tcp_ports + ('--to-ports', str(port))))
def setup_firewall_tproxy(self, port, dnsport, nslist, family, subnets, udp, user, tmark): 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) def _ipt_proto_ports(proto, fport, lport): return proto + ('--dport', '%d:%d' % (fport, lport)) \ if fport else proto mark_chain = 'sshuttle-m-%s' % port tproxy_chain = 'sshuttle-t-%s' % port divert_chain = 'sshuttle-d-%s' % port # basic cleanup/setup of chains self.restore_firewall(port, family, udp, user) _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', tmark, '-j', mark_chain) _ipt('-I', 'PREROUTING', tmark, '-j', tproxy_chain) # Don't have packets sent to any of our local IP addresses go # through the tproxy or mark chains. # # Without this fix, if a large subnet is redirected through # sshuttle (i.e., 0/0), then the user may be unable to receive # UDP responses or connect to their own machine using an IP # besides (127.0.0.1). Prior to including these lines, the # documentation reminded the user to use -x to exclude their # own IP addresses to receive UDP responses if they are # redirecting a large subnet through sshuttle (i.e., 0/0). _ipt('-A', tproxy_chain, '-j', 'RETURN', '-m', 'addrtype', '--dst-type', 'LOCAL') _ipt('-A', mark_chain, '-j', 'RETURN', '-m', 'addrtype', '--dst-type', 'LOCAL') _ipt('-A', divert_chain, '-j', 'MARK', '--set-mark', tmark) _ipt('-A', divert_chain, '-j', 'ACCEPT') _ipt('-A', tproxy_chain, '-m', 'socket', '-j', divert_chain, '-m', 'tcp', '-p', 'tcp') if udp: _ipt('-A', tproxy_chain, '-m', 'socket', '-j', divert_chain, '-m', 'udp', '-p', 'udp') for _, ip in [i for i in nslist if i[0] == family]: _ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', tmark, '--dest', '%s/32' % ip, '-m', 'udp', '-p', 'udp', '--dport', '53') _ipt('-A', tproxy_chain, '-j', 'TPROXY', '--tproxy-mark', '0x' + tmark + '/0x' + tmark, '--dest', '%s/32' % ip, '-m', 'udp', '-p', 'udp', '--dport', '53', '--on-port', str(dnsport)) for _, swidth, sexclude, snet, fport, lport \ in sorted(subnets, key=subnet_weight, reverse=True): tcp_ports = ('-p', 'tcp') tcp_ports = _ipt_proto_ports(tcp_ports, fport, lport) if sexclude: _ipt('-A', mark_chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), '-m', 'tcp', *tcp_ports) _ipt('-A', tproxy_chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), '-m', 'tcp', *tcp_ports) else: _ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', tmark, '--dest', '%s/%s' % (snet, swidth), '-m', 'tcp', *tcp_ports) _ipt('-A', tproxy_chain, '-j', 'TPROXY', '--tproxy-mark', '0x' + tmark + '/0x' + tmark, '--dest', '%s/%s' % (snet, swidth), '-m', 'tcp', *(tcp_ports + ('--on-port', str(port)))) if udp: udp_ports = ('-p', 'udp') udp_ports = _ipt_proto_ports(udp_ports, fport, lport) if sexclude: _ipt('-A', mark_chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), '-m', 'udp', *udp_ports) _ipt('-A', tproxy_chain, '-j', 'RETURN', '--dest', '%s/%s' % (snet, swidth), '-m', 'udp', *udp_ports) else: _ipt('-A', mark_chain, '-j', 'MARK', '--set-mark', tmark, '--dest', '%s/%s' % (snet, swidth), '-m', 'udp', *udp_ports) _ipt('-A', tproxy_chain, '-j', 'TPROXY', '--tproxy-mark', '0x' + tmark + '/0x' + tmark, '--dest', '%s/%s' % (snet, swidth), '-m', 'udp', *(udp_ports + ('--on-port', str(port))))
def setup_firewall(self, port, dnsport, nslist, 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: for f, ip in [i for i in nslist if i[0] == family]: _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))