summaryrefslogtreecommitdiffstats
path: root/sshuttle/methods/__init__.py
blob: 36a3f79da09714569b0af80b5dbbe5295fc44d51 (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
import os
import importlib
import socket
import struct
import errno
from sshuttle.helpers import Fatal, debug3


def original_dst(sock):
    try:
        SO_ORIGINAL_DST = 80
        SOCKADDR_MIN = 16
        sockaddr_in = sock.getsockopt(socket.SOL_IP,
                                      SO_ORIGINAL_DST, SOCKADDR_MIN)
        (proto, port, a, b, c, d) = struct.unpack('!HHBBBB', sockaddr_in[:8])
        # FIXME: decoding is IPv4 only.
        assert(socket.htons(proto) == socket.AF_INET)
        ip = '%d.%d.%d.%d' % (a, b, c, d)
        return (ip, port)
    except socket.error as e:
        if e.args[0] == errno.ENOPROTOOPT:
            return sock.getsockname()
        raise


class Features(object):
    pass


class BaseMethod(object):
    def __init__(self, name):
        self.firewall = None
        self.name = name

    def set_firewall(self, firewall):
        self.firewall = firewall

    @staticmethod
    def get_supported_features():
        result = Features()
        result.ipv6 = False
        result.udp = False
        result.dns = True
        result.user = False
        return result

    @staticmethod
    def get_tcp_dstip(sock):
        return original_dst(sock)

    @staticmethod
    def recv_udp(udp_listener, bufsize):
        debug3('Accept UDP using recvfrom.\n')
        data, srcip = udp_listener.recvfrom(bufsize)
        return (srcip, None, data)

    def send_udp(self, sock, srcip, dstip, data):
        if srcip is not None:
            Fatal("Method %s send_udp does not support setting srcip to %r"
                  % (self.name, srcip))
        sock.sendto(data, dstip)

    def setup_tcp_listener(self, tcp_listener):
        pass

    def setup_udp_listener(self, udp_listener):
        pass

    def assert_features(self, features):
        avail = self.get_supported_features()
        for key in ["udp", "dns", "ipv6", "user"]:
            if getattr(features, key) and not getattr(avail, key):
                raise Fatal(
                    "Feature %s not supported with method %s.\n" %
                    (key, self.name))

    def setup_firewall(self, port, dnsport, nslist, family, subnets, udp,
                       user):
        raise NotImplementedError()

    def restore_firewall(self, port, family, udp, user):
        raise NotImplementedError()

    @staticmethod
    def firewall_command(line):
        return False


def _program_exists(name):
    paths = (os.getenv('PATH') or os.defpath).split(os.pathsep)
    for p in paths:
        fn = '%s/%s' % (p, name)
        if os.path.exists(fn):
            return not os.path.isdir(fn) and os.access(fn, os.X_OK)


def get_method(method_name):
    module = importlib.import_module("sshuttle.methods.%s" % method_name)
    return module.Method(method_name)


def get_auto_method():
    if _program_exists('iptables'):
        method_name = "nat"
    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")

    return get_method(method_name)