-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxy.py
More file actions
356 lines (303 loc) · 11.8 KB
/
proxy.py
File metadata and controls
356 lines (303 loc) · 11.8 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
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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# Computer Networks - Project 2 (Proxy server)
# Student Name: Daeyeol Ryu
import urlparse, socket, threading, sys, datetime
# HTTP packet class
# Manage packet data and provide related functions
class HTTPPacket:
# Constructer
def __init__(self, line, header, body):
self.line = line # Packet first line(String)
self.header = header # Headers(Dict.{Field:Value})
self.body = body # Body(Bytes)
# Make encoded packet data
def pack(self):
# Concat first line
ret = self.line + '\r\n'
# Concat headers
for field in self.header:
ret += field + ': ' + self.header[field] + '\r\n'
ret += '\r\n'
# String -> Bytes
ret = ret.encode()
# Concat body
ret += self.body
return ret
# Get HTTP header value
def getHeader(self, field):
# Return header value by field.
# If not exist, return empty string as default value
return self.header.get(field, '')
# Set HTTP header value
def setHeader(self, field, value):
if not value:
# If value is empty string, remove field
try:
del self.header[field]
except KeyError:
pass
else:
# Add or update value of field
self.header[field] = value
# Get URL from request packet line
def getURL(self):
# HTTP Header's first line format => 'METHOD' SP 'URL' SP 'VERSION'
return self.line.split(' ')[1]
def isChunked(self):
return 'chunked' in self.getHeader('Transfer-Encoding')
# Receive HTTP packet with socket
def recvHttpData(conn):
# Set time out for error or persistent connection end
conn.settimeout(TIMEOUT)
# Get HTTP header from the socket (some piece of HTTP body can be received together)
data = conn.recv(BUFSIZE)
if not data:
raise Exception('Disconnected')
while b'\r\n\r\n' not in data:
data += conn.recv(BUFSIZE)
packet = parseHTTP(data)
body = packet.body
# Get HTTP body from the socket
# Chunked-Encoding
if packet.isChunked():
readed = 0
# Read and merge chunked HTTP body
while True:
while b'\r\n' not in body[readed:len(body)]:
d = conn.recv(BUFSIZE)
body += d
size_str = body[readed:len(body)].split(b'\r\n')[0]
size = int(size_str, 16)
readed += len(size_str) + 2
while len(body) - readed < size + 2:
d = conn.recv(BUFSIZE)
body += d
readed += size + 2
if size == 0: break
# Normalize chunked body to non-chunked body to use 'Content-Length' instead of 'Transfer-Encoding: chunked'
# Only odd index carries real message (even index include 0 indicates the bytes count)
bodyChunks = body.split(b'\r\n')[1::2]
# Remove last chunk because it is empty string
del bodyChunks[-1]
# Join all chunks
body = b''.join(bodyChunks)
packet.setHeader('Transfer-Encoding', '')
packet.setHeader('Content-Length', str(len(body)))
# Content-Length
elif packet.getHeader('Content-Length'):
# Read HTTP body
received = 0
expected = packet.getHeader('Content-Length')
if not expected:
expected = '0'
expected = int(expected)
received += len(body)
while received < expected:
d = conn.recv(BUFSIZE)
received += len(d)
body += d
# Finally HTTP body normalized to normal HTTP body not chunked
packet.body = body
return packet
# Dissect HTTP header into line(first line), header(second line to end), body
def parseHTTP(data):
# Get the first line
endIndex = data.find(b'\r\n', 0)
firstLine = data[:endIndex].decode()
# Get the header lines
startIndex = endIndex + len(b'\r\n')
endIndex = data.find(b'\r\n\r\n', startIndex)
headerLines = data[startIndex:endIndex].decode()
# Convert header lines to header dictionary
header = dict()
for line in headerLines.split('\r\n'):
# Get header and make it to dictionary
arr = list(map(lambda x: x.strip(), line.split(':')))
header[arr[0]] = arr[1]
# Get the body lines
startIndex = endIndex + len(b'\r\n\r\n')
body = data[startIndex:]
return HTTPPacket(firstLine, header, body)
# Proxy handler function
def handleProxy(clientSock, clientAddr):
# CONNECTION_NUM is global variable which can be modified in this function
global CONNECTION_NUM
serverSock = None
prevHostname = ''
while IS_PROXY_RUNNING:
try:
# Client -> Proxy (Intercept HTTP request from client)
# Receive data from the client
req = recvHttpData(clientSock)
# Parse URL
url = urlparse.urlparse(req.getURL())
# Handle only http. So other schemes are ignored
if url.scheme != 'http':
# Get connection number
clientNum = CONNECTION_NUM
CONNECTION_NUM += 1
# Print logs
print('[' + str(clientNum) + '] ' + datetime.datetime.now().strftime('%d/%b/%Y %H:%M:%S.%f') + '\n'
+ '[' + str(clientNum) + '] > Connection from ' + clientAddr[0] + ':' + str(clientAddr[1]) + '\n'
+ '[' + str(clientNum) + '] > ' + req.line + '\n'
)
raise NotImplementedError('Wrong scheme. Only HTTP is supported.')
# Remove proxy infomation (Elite anonymity level proxy)
req.setHeader('Proxy-Connection', '')
if OPT_PC:
# Use keep-alive for persistent connection
req.setHeader('Connection', 'keep-alive')
else:
# Use close for non-persistent connection
req.setHeader('Connection', 'close')
# Proxy -> Server
# If Persistent connection is enabled, server socket is connected and prev host name is same as current one,
# then use same server socket as prev
if not OPT_PC or serverSock == None or prevHostname != url.hostname:
# Close previous server socket
if serverSock != None:
try:
serverSock.shutdown(socket.SHUT_RDWR)
serverSock.close()
except:
pass
# Establish new server socket connection
serverSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serverSock.connect((url.hostname, 80))
prevHostname = url.hostname
# Send HTTP request to server
serverSock.sendall(req.pack())
# Receive data from the server
res = recvHttpData(serverSock)
if OPT_PC:
# Use keep-alive for persistent connection
res.setHeader('Connection', 'keep-alive')
else:
# Use close for non-persistent connection
res.setHeader('Connection', 'close')
# Close the server socket
try:
serverSock.shutdown(socket.SHUT_RDWR)
serverSock.close()
except:
pass
# Proxy -> Client (Send back HTTP response from the server to client)
clientSock.sendall(res.pack())
# Get connection number
clientNum = CONNECTION_NUM
CONNECTION_NUM += 1
# Print logs
print('[' + str(clientNum) + '] ' + datetime.datetime.now().strftime('%d/%b/%Y %H:%M:%S.%f') + '\n'
+ '[' + str(clientNum) + '] > Connection from ' + clientAddr[0] + ':' + str(clientAddr[1]) + '\n'
+ '[' + str(clientNum) + '] > ' + req.line + '\n'
+ '[' + str(clientNum) + '] > ' + res.line + '\n'
+ '[' + str(clientNum) + '] < ' + res.getHeader('Content-Type') + ' ' + res.getHeader('Content-Length') + 'bytes' + '\n'
)
if not OPT_PC:
raise Exception('Non-persistent')
except Exception as e:
# print('Proxy handling error: ' + str(e))
break
# Close server socket
if serverSock != None:
try:
serverSock.shutdown(socket.SHUT_RDWR)
serverSock.close()
except:
pass
# Close client socket
try:
clientSock.shutdown(socket.SHUT_RDWR)
clientSock.close()
CLIENT_SOCKS.remove(clientSock)
except:
pass
# Constants
PROXY_HOST = '0.0.0.0'
BUFSIZE = 2048
TIMEOUT = 5
# Flags for thread to safely finished
IS_PROXY_RUNNING = True
# Optional arguments
OPT_MT = False
OPT_PC = False
# Connection number
CONNECTION_NUM = 1
# Client sockets
CLIENT_SOCKS = []
def main():
# Get proxy port
proxyPort = int(sys.argv[1])
# OPT_MT, OPT_PC, and IS_PROXY_RUNNING are global variable which can be modified in main function
global OPT_MT
global OPT_PC
global IS_PROXY_RUNNING
# Get either MT or PC option if exists
if len(sys.argv) > 2:
if sys.argv[2] == '-mt':
OPT_MT = True
elif sys.argv[2] == '-pc':
OPT_PC = True
if len(sys.argv) > 3:
if sys.argv[3] == '-mt':
OPT_MT = True
elif sys.argv[3] == '-pc':
OPT_PC = True
# Make TCP socket for proxy and bind it
proxySock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
proxySock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
proxySock.bind((PROXY_HOST, proxyPort))
# Listen for connections
proxySock.listen(20)
# Print logs
print('Proxy Server started on port ' + str(proxyPort) + ' at ' + datetime.datetime.now().strftime('%d/%b/%Y %H:%M:%S.%f'))
if OPT_MT:
print('* Multithreading - [ON]')
else:
print('* Multithreading - [OFF]')
if OPT_PC:
print('* Persistent Connection - [ON]')
else:
print('* Persistent Connection - [OFF]')
print('')
try:
while True:
# New client connection established
clientSock, clientAddr = proxySock.accept()
CLIENT_SOCKS.append(clientSock)
if OPT_MT:
# Use multi-thread if multithreading is enabled
proxyThread = threading.Thread(target=handleProxy, args=(clientSock, clientAddr))
proxyThread.start()
else:
# Use main thread if multithreading is disabled
handleProxy(clientSock, clientAddr)
except KeyboardInterrupt:
print('KeyboardInterrupt')
# Set proxy running flag as false
IS_PROXY_RUNNING = False
# Wait until background threads are exited safely
mainThread = threading.currentThread()
for thread in threading.enumerate():
if thread is not mainThread:
try:
thread.join()
except:
pass
# Close client sockets remained
for clientSock in CLIENT_SOCKS:
try:
clientSock.shutdown(socket.SHUT_RDWR)
clientSock.close()
except:
pass
# Close proxy sockets
try:
proxySock.shutdown(socket.SHUT_RDWR)
proxySock.close()
except:
pass
# Exit program
sys.exit()
# Start main function
if __name__ == '__main__':
main()