-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProxyServer.py
More file actions
194 lines (142 loc) · 6.27 KB
/
ProxyServer.py
File metadata and controls
194 lines (142 loc) · 6.27 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 socket
import threading
import signal
import ssl
HEADER = 800 # The first message from the client to the server is going to be a header of 64 byes
# the first recieved message will specify the number of bytes the next recieved message is going to contain
PORT = 5050
FORMAT = 'utf-8'
DISCONNECT_MESSAGE = "!DISCONNECT"
#print(socket.gethostname())# gets device name
SERVER = socket.gethostbyname(socket.gethostname()) #Get's the current devices name and returns it's ip-address via its name
ADDR = (SERVER, PORT)
serverInfo = {'BLACKLISTDOMAINS':["tiktok.com"]}
class proxyServer:
def __init__(self):
# shutdown on Ctrl+c
signal.signal(signal.SIGINT, self.exit_gracefully)
#create a TCP socket
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#Re-use the socket
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
#bind the socket to a public host, and a port
self.server.bind(ADDR)
self.server.listen(10)
print(f"[LISTENING] Server is listening on {SERVER}")
self.__clients = {}
def exit_gracefully(self,signum,frame):
"""
Handle the signal for a graceful shutdown.
Parameters:
signum (int): The signal number.
frame (frame object): The current stack frame.
"""
print("Shutdown signal received")
self.server.close()
#shutting down other threads
for t in threading.enumerate():
if t is threading.main_thread():
continue
t.join()
exit(0)
def handle_http(self, address, conn, request):
webserver,port = address
print(f'Webserver:{webserver}, Port:{port}')
remote_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
remote_server.settimeout(10.0)
remote_server.connect((webserver, port)) # connects the tcp socket to the remote server
remote_server.sendall(request.encode(FORMAT))
# Blocked portion of code, waits for the server to send back the info and sends it to client
self.forward(remote_server, conn)
conn.close()
remote_server.close()
def handle_https(self, address, conn, request):
webserver,port = address
if port == 80:
port = 443
# wraps in ssl required for https
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)#--> need CA certificate and server keys to function
remote_server = socket.create_connection((webserver,port))
remote_server = context.wrap_socket(remote_server,server_hostname=webserver)
remote_server.sendall(request.encode(FORMAT))
# Blocked portion of code, waits for the server to send back the info and sends it to client-> thats why there is another thread
# We don't restrict the flow of data for one lane, rather have bidirectional transfer of data
#threading.Thread(target=self.forward, args =(conn,remote_server)).start()
# Blocked portion of code, waits for the server to send back the info and sends it to client
self.forward(remote_server,conn)
conn.close()
remote_server.close()
def forward(self,source,destination):
data = source.recv(4096)
destination.sendall(data)
'''
while True:
try:
if len(data) == 0:
break
destination.sendall(data)
except Exception as e:
print(f"Data transfer from source to destination failed :{e}")
break
'''
def extractADDR(self,url):
# Finding Destination address tuple -> (addr,port) from url
http_pos = url.find("://")
if http_pos == -1:
temp = url
else:
temp = url[(http_pos + 3):]
port_pos = temp.find(":")
webserver_pos = temp.find("/")
if webserver_pos == -1:
webserver_pos = len(temp)
webserver = ""
port = -1
if port_pos == -1 or webserver_pos < port_pos:
# No specific port, default to port 80
port = 80 #80
webserver = temp[:webserver_pos]
else:
port = int(temp[(port_pos + 1):webserver_pos])
webserver = temp[:port_pos]
return (webserver, port)
def handle_client(self,conn, addr):
print(f"[NEW CONNECTION] {addr} connected.")
try:
request = conn.recv(HEADER).decode(FORMAT)
if not request:
return
print(f'Addr:{addr}, Request:{request}')
first_line = request.split('\n')[0]
method = first_line.split(' ')[0]
url = first_line.split(' ')[1]
remote_server_info = self.extractADDR(url)
if self._checkDomainInBlackList(url):
conn.close()
return
if method == 'CONNECT':
self.handle_https(remote_server_info, conn, request)
else:
self.handle_http(remote_server_info, conn, request)
except Exception as e:
print(f'Error handling client {addr}: {e}')
finally:
conn.close()
def _getClientName(self,client_address):
return f"Client-{client_address}"
def _checkDomainInBlackList(self,url):
for blackListedDomain in serverInfo["BLACKLISTDOMAINS"]:
if blackListedDomain in url:
return True
return False
def start(self):
while True:
conn, addr = self.server.accept() # server.accept()-> we wait for a new connection, when it occurs we save that connection
# through its address and conn, an object which we can send data back to the connection
thread = threading.Thread(name = self._getClientName(addr) ,target=self.handle_client, args=(conn,addr)) # makes a thread for each connection so we can process each client conncurently, b/c we utilize blocking lines of code
thread.start()
print(f"[ACTIVE CONNECTIONS] {threading.active_count() - 1}") # we do -1, because our main server thread is counted threading.active_count()
if __name__ == '__main__':
pserver = proxyServer()
pserver.start()
print("[STARTING]")