forked from guardicore/monkey
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtunnel.py
More file actions
194 lines (157 loc) · 6.66 KB
/
tunnel.py
File metadata and controls
194 lines (157 loc) · 6.66 KB
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import logging
import socket
import struct
import time
from difflib import get_close_matches
from threading import Thread
from model import VictimHost
from network.firewall import app as firewall
from network.info import local_ips, get_free_tcp_port
from network.tools import check_tcp_port
from transport.base import get_last_serve_time
__author__ = 'hoffer'
LOG = logging.getLogger(__name__)
MCAST_GROUP = '224.1.1.1'
MCAST_PORT = 5007
BUFFER_READ = 1024
DEFAULT_TIMEOUT = 10
QUIT_TIMEOUT = 60 * 10 # 10 minutes
def _set_multicast_socket(timeout=DEFAULT_TIMEOUT, adapter=''):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.settimeout(timeout)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((adapter, MCAST_PORT))
sock.setsockopt(socket.IPPROTO_IP,
socket.IP_ADD_MEMBERSHIP,
struct.pack("4sl", socket.inet_aton(MCAST_GROUP), socket.INADDR_ANY))
return sock
def _check_tunnel(address, port, existing_sock=None):
if not existing_sock:
sock = _set_multicast_socket()
else:
sock = existing_sock
LOG.debug("Checking tunnel %s:%s", address, port)
is_open, _ = check_tcp_port(address, int(port))
if not is_open:
LOG.debug("Could not connect to %s:%s", address, port)
if not existing_sock:
sock.close()
return False
try:
sock.sendto("+", (address, MCAST_PORT))
except Exception as exc:
LOG.debug("Caught exception in tunnel registration: %s", exc)
if not existing_sock:
sock.close()
return True
def find_tunnel(default=None, attempts=3, timeout=DEFAULT_TIMEOUT):
l_ips = local_ips()
if default:
if default.find(':') != -1:
address, port = default.split(':', 1)
if _check_tunnel(address, port):
return address, port
for adapter in l_ips:
for attempt in range(0, attempts):
try:
LOG.info("Trying to find using adapter %s", adapter)
sock = _set_multicast_socket(timeout, adapter)
sock.sendto("?", (MCAST_GROUP, MCAST_PORT))
tunnels = []
while True:
try:
answer, address = sock.recvfrom(BUFFER_READ)
if answer not in ['?', '+', '-']:
tunnels.append(answer)
except socket.timeout:
break
for tunnel in tunnels:
if tunnel.find(':') != -1:
address, port = tunnel.split(':', 1)
if address in l_ips:
continue
if _check_tunnel(address, port, sock):
sock.close()
return address, port
except Exception as exc:
LOG.debug("Caught exception in tunnel lookup: %s", exc)
continue
return None
def quit_tunnel(address, timeout=DEFAULT_TIMEOUT):
try:
sock = _set_multicast_socket(timeout)
sock.sendto("-", (address, MCAST_PORT))
sock.close()
LOG.debug("Success quitting tunnel")
except Exception as exc:
LOG.debug("Exception quitting tunnel: %s", exc)
return
class MonkeyTunnel(Thread):
def __init__(self, proxy_class, target_addr=None, target_port=None, timeout=DEFAULT_TIMEOUT):
self._target_addr = target_addr
self._target_port = target_port
self._proxy_class = proxy_class
self._broad_sock = None
self._timeout = timeout
self._stopped = False
self._clients = []
self.local_port = None
super(MonkeyTunnel, self).__init__()
self.daemon = True
self.l_ips = None
def run(self):
self._broad_sock = _set_multicast_socket(self._timeout)
self.l_ips = local_ips()
self.local_port = get_free_tcp_port()
if not self.local_port:
return
if not firewall.listen_allowed(localport=self.local_port):
LOG.info("Machine firewalled, listen not allowed, not running tunnel.")
return
proxy = self._proxy_class(local_port=self.local_port, dest_host=self._target_addr, dest_port=self._target_port)
LOG.info("Running tunnel using proxy class: %s, listening on port %s, routing to: %s:%s",
proxy.__class__.__name__,
self.local_port,
self._target_addr,
self._target_port)
proxy.start()
while not self._stopped:
try:
search, address = self._broad_sock.recvfrom(BUFFER_READ)
if '?' == search:
ip_match = get_close_matches(address[0], self.l_ips) or self.l_ips
if ip_match:
answer = '%s:%d' % (ip_match[0], self.local_port)
LOG.debug("Got tunnel request from %s, answering with %s", address[0], answer)
self._broad_sock.sendto(answer, (address[0], MCAST_PORT))
elif '+' == search:
if not address[0] in self._clients:
LOG.debug("Tunnel control: Added %s to watchlist", address[0])
self._clients.append(address[0])
elif '-' == search:
LOG.debug("Tunnel control: Removed %s from watchlist", address[0])
self._clients = [client for client in self._clients if client != address[0]]
except socket.timeout:
continue
LOG.info("Stopping tunnel, waiting for clients: %s" % repr(self._clients))
# wait till all of the tunnel clients has been disconnected, or no one used the tunnel in QUIT_TIMEOUT seconds
while self._clients and (time.time() - get_last_serve_time() < QUIT_TIMEOUT):
try:
search, address = self._broad_sock.recvfrom(BUFFER_READ)
if '-' == search:
LOG.debug("Tunnel control: Removed %s from watchlist", address[0])
self._clients = [client for client in self._clients if client != address[0]]
except socket.timeout:
continue
LOG.info("Closing tunnel")
self._broad_sock.close()
proxy.stop()
proxy.join()
def set_tunnel_for_host(self, host):
assert isinstance(host, VictimHost)
if not self.local_port:
return
ip_match = get_close_matches(host.ip_addr, local_ips()) or self.l_ips
host.default_tunnel = '%s:%d' % (ip_match[0], self.local_port)
def stop(self):
self._stopped = True