summaryrefslogtreecommitdiffstats
path: root/sshuttle/methods/__init__.py
blob: 84a0453623498c5729dd6c276f872acecab7dd7d (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
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)