-
Notifications
You must be signed in to change notification settings - Fork 69
/
Copy pathpacketselector.py
235 lines (194 loc) · 7.37 KB
/
packetselector.py
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# MIT License
# Copyright (c) 2017 Balazs Bucsay
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import sys
if "packetselector.py" in sys.argv[0]:
print("[-] Instead of poking around just try: python xfltreat.py --help")
sys.exit(-1)
# This is the engine of the whole communication. Every packet that arrives to
# the tunnel will be carefully selected. In case the destination IP matches, it
# will be redirected (written) to the appropriate client pipe.
import threading
import os
import select
import struct
import socket
#local files
import common
from client import Client
class PacketSelector(threading.Thread):
clients = None
def __init__(self, tunnel):
threading.Thread.__init__(self)
self.timeout = 1.0 # seems to be a good value for timeout
self.clients = []
self.tunnel = tunnel
self._stop = False
self.os_type = common.get_os_type()
if self.os_type == common.OS_WINDOWS:
self.run_ps_mainloop = self.run_windows
else:
self.run_ps_mainloop = self.run_unix
# return client list
def get_clients(self):
return self.clients
# add new client to the client list
def add_client(self, client):
self.clients.append(client)
return
# This function is called when a client object has to be replaced.
# That could happen when the client connection was reset, or there is a
# duplicated config with the same private IP.
def replace_client(self, old_client, new_client):
if old_client in self.clients:
self.clients.remove(old_client)
self.clients.append(new_client)
try:
old_client.get_pipe_w_fd().close()
except:
pass
try:
old_client.get_pipe_r_fd().close()
except:
pass
try:
socket.close(old_client.get_socket())
except:
pass
# removing client from the client list
def delete_client(self, client):
if client in self.clients:
if self.os_type == common.OS_WINDOWS:
import win32file
try:
win32file.CloseHandle(client.get_pipe_r())
win32file.CloseHandle(client.get_pipe_w())
except Exception as e:
common.internal_print("Remove authenticated client: CloseHandle exception: {0}".format(e), -1)
else:
try:
client.get_pipe_r_fd().close()
client.get_pipe_w_fd().close()
except Exception as e:
common.internal_print("Remove authenticated client: os.close exception: {0}".format(e), -1)
client.call_stopfp()
self.clients.remove(client)
return
# This function should run from the point when the framework was started.
# It runs as an infinite loop to read the packets off the tunnel.
# When an IPv4 packet was found that will be selected and checked whether
# it addresses a client in the client list. If a client was found, then the
# packet will be written on that pipe.
def run(self):
return self.run_ps_mainloop()
def run_unix(self):
rlist = [self.tunnel]
wlist = []
xlist = []
while not self._stop:
try:
readable, writable, exceptional = select.select(rlist, wlist, xlist, self.timeout)
except select.error as e:
print(e)
break
for s in readable:
# is there anything on the tunnel interface?
if s is self.tunnel:
# yes there is, read the packet or packets off the tunnel
message = os.read(self.tunnel, 4096)
if self.os_type == common.OS_MACOSX:
message = message[4:]
while True:
# dumb check, but seems to be working. The packet has
# to be longer than 4 and it must be IPv4
if (len(message) < 4) or (message[0:1] != "\x45"): #Only care about IPv4
break
packetlen = struct.unpack(">H", message[2:4])[0]
if packetlen == 0:
break
# is the rest less than the packet length?
if packetlen > len(message):
# in case it is less, we need to read more
message += os.read(self.tunnel, 4096)
readytogo = message[0:packetlen]
message = message[packetlen:]
# looking for client
for c in self.clients:
if c.get_private_ip_addr() == readytogo[16:20]:
# client found, writing packet on client's pipe
try:
os.write(c.get_pipe_w(), readytogo)
# flushing, no buffering please
c.get_pipe_w_fd().flush()
except:
# it can break if there is a race condition
# the client was found above but in the
# same time the client left and the pipe
# got closed. Broken pipe would be raised
pass
return
# some ideas were taken from: https://github.com/boytm/minivtun-win/
def run_windows(self):
import win32file
import win32event
import pywintypes
import winerror
import win32api
# creating events, overlapped structures and a buffer for reading and writing
hEvent_read = win32event.CreateEvent(None, 0, 0, None)
overlapped_read = pywintypes.OVERLAPPED()
overlapped_read.hEvent = hEvent_read
overlapped_write = pywintypes.OVERLAPPED()
message = win32file.AllocateReadBuffer(4096)
while not self._stop:
try:
# Overlapped/async read, it either blocks or returns pending
hr, _ = win32file.ReadFile(self.tunnel, message, overlapped_read)
if (hr == winerror.ERROR_IO_PENDING):
# when the event gets signalled or timeout happens it will return
rc = win32event.WaitForSingleObject(hEvent_read, int(self.timeout*1000))
if rc == winerror.WAIT_TIMEOUT:
# timed out, just rerun read
continue
if rc == win32event.WAIT_OBJECT_0:
# read happened, packet is in "message"
if (overlapped_read.InternalHigh < 4) or (message[0:1] != "\x45"): #Only care about IPv4
# too small which should not happen or not IPv4, so we just drop it.
continue
# reading out the packet from the buffer and discarding the rest
readytogo = message[0:overlapped_read.InternalHigh]
# looking for client
for c in self.clients:
if c.get_private_ip_addr() == readytogo[16:20]:
# client found, writing packet on client's pipe
# ignoring outcome, it is async so it will happen when it will happen ;)
win32file.WriteFile(c.get_pipe_w(), readytogo, overlapped_write)
except win32api.error as e:
if e.args[0] == 995:
common.internal_print("Interface disappered, exiting PS thread: {0}".format(e), -1)
self.stop()
continue
if e.args[0] == 1453:
common.internal_print("OS Internal error: {0}".format(e), -1)
self.stop()
continue
common.internal_print("PS Exception: {0}".format(e), -1)
return
# stop the so called infinite loop
def stop(self):
self._stop = True
return