summaryrefslogtreecommitdiffstats
path: root/sshuttle/linux.py
blob: 76ae7e002b606773729573a63f5cf2d7d855d00e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import re
import os
import socket
import subprocess as ssubprocess

from sshuttle.helpers import log, debug1, Fatal, family_to_string


def nonfatal(func, *args):
    try:
        func(*args)
    except Fatal as e:
        log('error: %s\n' % e)


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(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 == 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_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 nft_get_handle(expression, chain):
    cmd = 'nft'
    argv = [cmd, 'list', expression, '-a']
    env = {
        'PATH': os.environ['PATH'],
        'LC_ALL': "C",
    }
    try:
        output = ssubprocess.check_output(argv, env=env)
        for line in output.decode('utf-8').split('\n'):
            if ('jump %s' % chain) in line:
                return re.sub('.*# ', '', line)
    except ssubprocess.CalledProcessError as e:
        raise Fatal('%r returned %d' % (argv, e.returncode))


_no_ttl_module = False


def ipt_ttl(family, *args):
    global _no_ttl_module
    if not _no_ttl_module:
        # we avoid infinite loops by generating server-side connections
        # with ttl 42.  This makes the client side not recapture those
        # connections, in case client == server.
        try:
            argsplus = list(args) + ['-m', 'ttl', '!', '--ttl', '42']
            ipt(family, *argsplus)
        except Fatal:
            ipt(family, *args)
            # we only get here if the non-ttl attempt succeeds
            log('sshuttle: warning: your iptables is missing '
                'the ttl module.\n')
            _no_ttl_module = True
    else:
        ipt(family, *args)