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


def original_dst(sock):
    ip = "0.0.0.0"
    port = -1
    try:
        family = sock.family
        SO_ORIGINAL_DST = 80

        if family == socket.AF_INET:
            SOCKADDR_MIN = 16
            sockaddr_in = sock.getsockopt(socket.SOL_IP,
                                          SO_ORIGINAL_DST, SOCKADDR_MIN)
            port, raw_ip = struct.unpack_from('!2xH4s', sockaddr_in[:8])
            ip = str(ipaddress.IPv4Address(raw_ip))
        elif family == socket.AF_INET6:
            sockaddr_in = sock.getsockopt(41, SO_ORIGINAL_DST, 64)
            port, raw_ip = struct.unpack_from("!2xH4x16s", sockaddr_in)
            ip = str(ipaddress.IPv6Address(raw_ip))
        else:
            raise Fatal("fw: Unknown family type.")
    except socket.error as e:
        if e.args[0] == errno.ENOPROTOOPT:
            return sock.getsockname()
        raise
    return (ip, port)


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.ipv4 = True
        result.ipv6 = False
        result.udp = False
        result.dns = True
        result.user = False
        return result

    @staticmethod
    def is_supported():
        """Returns true if it appears that this method will work on this
        machine."""
        return False

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

    @staticmethod
    def recv_udp(udp_listener, bufsize):
        debug3('Accept UDP using recvfrom.')
        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", "ipv4", "user"]:
            if getattr(features, key) and not getattr(avail, key):
                raise Fatal(
                    "Feature %s not supported with method %s." %
                    (key, self.name))

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

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

    @staticmethod
    def firewall_command(line):
        return False


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


def get_auto_method():
    debug3("Selecting a method automatically...")
    # Try these methods, in order:
    methods_to_try = ["nat", "nft", "pf", "ipfw"]
    for m in methods_to_try:
        method = get_method(m)
        if method.is_supported():
            debug3("Method '%s' was automatically selected." % m)
            return method

    raise Fatal("Unable to automatically find a supported method. Check that "
                "the appropriate programs are in your PATH. We tried "
                "methods: %s" % str(methods_to_try))