Add nat-like method using nftables instead of iptables

This commit is contained in:
Julian Wollrath 2018-03-04 17:32:08 +01:00 committed by Brian May
parent d11f5b9d16
commit 1940b524f1
4 changed files with 119 additions and 3 deletions

View File

@ -1,3 +1,4 @@
import re
import os
import socket
import subprocess as ssubprocess
@ -49,6 +50,39 @@ def ipt(family, table, *args):
raise Fatal('%r returned %d' % (argv, rv))
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 nft_get_handle(expression, chain):
cmd = 'nft'
argv = [cmd, 'list', expression, '-a']
env = {
'PATH': os.environ['PATH'],
'LC_ALL': "C",
}
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, env=env)
for line in p.stdout:
if (b'jump %s' % chain.encode('utf-8')) in line:
return re.sub('.*# ', '', line.decode('utf-8'))
rv = p.wait()
if rv:
raise Fatal('%r returned %d' % (argv, rv))
_no_ttl_module = False

View File

@ -102,12 +102,14 @@ def get_method(method_name):
def get_auto_method():
if _program_exists('iptables'):
method_name = "nat"
elif _program_exists('nft'):
method_name = "nft"
elif _program_exists('pfctl'):
method_name = "pf"
elif _program_exists('ipfw'):
method_name = "ipfw"
else:
raise Fatal(
"can't find either iptables or pfctl; check your PATH")
"can't find either iptables, nft or pfctl; check your PATH")
return get_method(method_name)

80
sshuttle/methods/nft.py Normal file
View File

@ -0,0 +1,80 @@
import socket
from sshuttle.firewall import subnet_weight
from sshuttle.linux import nft, nft_get_handle, nonfatal
from sshuttle.methods import BaseMethod
class Method(BaseMethod):
# We name the chain based on the transproxy port number so that it's
# possible to run multiple copies of sshuttle at the same time. Of course,
# the 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 setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
user):
if udp:
raise Exception("UDP not supported by nft")
table = "nat"
def _nft(action, *args):
return nft(family, table, action, *args)
chain = 'sshuttle-%s' % port
# basic cleanup/setup of chains
_nft('add table', '')
_nft('add chain', 'prerouting',
'{ type nat hook prerouting priority -100; policy accept; }')
_nft('add chain', 'postrouting',
'{ type nat hook postrouting priority 100; policy accept; }')
_nft('add chain', 'output',
'{ type nat hook output priority -100; policy accept; }')
_nft('add chain', chain)
_nft('flush chain', chain)
_nft('add rule', 'output jump %s' % chain)
_nft('add rule', 'prerouting jump %s' % chain)
# create new subnet entries.
for _, swidth, sexclude, snet, fport, lport \
in sorted(subnets, key=subnet_weight, reverse=True):
tcp_ports = ('ip', 'protocol', 'tcp')
if fport:
tcp_ports = tcp_ports + ('dport { %d-%d }' % (fport, lport))
if sexclude:
_nft('add rule', chain, *(tcp_ports + (
'ip daddr %s/%s' % (snet, swidth), 'return')))
else:
_nft('add rule', chain, *(tcp_ports + (
'ip daddr %s/%s' % (snet, swidth), 'ip ttl != 42',
('redirect to :' + str(port)))))
for _, ip in [i for i in nslist if i[0] == family]:
if family == socket.AF_INET:
_nft('add rule', chain, 'ip protocol udp ip daddr %s' % ip,
'udp dport { 53 }', 'ip ttl != 42',
('redirect to :' + str(dnsport)))
elif family == socket.AF_INET6:
_nft('add rule', chain, 'ip6 protocol udp ip6 daddr %s' % ip,
'udp dport { 53 }', 'ip ttl != 42',
('redirect to :' + str(dnsport)))
def restore_firewall(self, port, family, udp, user):
if udp:
raise Exception("UDP not supported by nft method_name")
table = "nat"
def _nft(action, *args):
return nft(family, table, action, *args)
chain = 'sshuttle-%s' % port
# basic cleanup/setup of chains
handle = nft_get_handle('chain ip nat output', chain)
nonfatal(_nft, 'delete rule', 'output', handle)
handle = nft_get_handle('chain ip nat prerouting', chain)
nonfatal(_nft, 'delete rule', 'prerouting', handle)
nonfatal(_nft, 'delete chain', chain)

View File

@ -160,7 +160,7 @@ parser.add_argument(
parser.add_argument(
"--method",
choices=["auto", "nat", "tproxy", "pf", "ipfw"],
choices=["auto", "nat", "nft", "tproxy", "pf", "ipfw"],
metavar="TYPE",
default="auto",
help="""
@ -230,7 +230,7 @@ parser.add_argument(
metavar="HOSTNAME[,HOSTNAME]",
default=[],
help="""
comma-separated list of hostnames for initial scan (may be used with
comma-separated list of hostnames for initial scan (may be used with
or without --auto-hosts)
"""
)