browsertime 14.21.1 → 15.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/vendor/tsproxy.py DELETED
@@ -1,842 +0,0 @@
1
- #!/usr/bin/env python
2
- """
3
- Copyright 2016 Google Inc. All Rights Reserved.
4
-
5
- Licensed under the Apache License, Version 2.0 (the "License");
6
- you may not use this file except in compliance with the License.
7
- You may obtain a copy of the License at
8
-
9
- http://www.apache.org/licenses/LICENSE-2.0
10
-
11
- Unless required by applicable law or agreed to in writing, software
12
- distributed under the License is distributed on an "AS IS" BASIS,
13
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- See the License for the specific language governing permissions and
15
- limitations under the License.
16
- """
17
- import asyncore
18
- import gc
19
- import logging
20
- import platform
21
- try:
22
- from Queue import Queue
23
- from Queue import Empty
24
- except ImportError:
25
- from queue import Queue
26
- from queue import Empty
27
- import re
28
- import signal
29
- import socket
30
- import sys
31
- import threading
32
- import time
33
-
34
- server = None
35
- in_pipe = None
36
- out_pipe = None
37
- must_exit = False
38
- options = None
39
- dest_addresses = None
40
- connections = {}
41
- dns_cache = {}
42
- port_mappings = None
43
- map_localhost = False
44
- needs_flush = False
45
- flush_pipes = False
46
- last_activity = None
47
- last_client_disconnected = None
48
- REMOVE_TCP_OVERHEAD = 1460.0 / 1500.0
49
- lock = threading.Lock()
50
- background_activity_count = 0
51
- current_time = time.clock if sys.platform == "win32" else time.time
52
- try:
53
- import monotonic
54
- current_time = monotonic.monotonic
55
- except Exception:
56
- pass
57
-
58
-
59
- def PrintMessage(msg):
60
- # Print the message to stdout & flush to make sure that the message is not
61
- # buffered when tsproxy is run as a subprocess.
62
- sys.stdout.write(msg)
63
- sys.stdout.flush()
64
-
65
- ########################################################################################################################
66
- # Traffic-shaping pipe (just passthrough for now)
67
- ########################################################################################################################
68
- class TSPipe():
69
- PIPE_IN = 0
70
- PIPE_OUT = 1
71
-
72
- def __init__(self, direction, latency, kbps):
73
- self.direction = direction
74
- self.latency = latency
75
- self.kbps = kbps
76
- self.queue = Queue()
77
- self.last_tick = current_time()
78
- self.next_message = None
79
- self.available_bytes = .0
80
- self.peer = 'server'
81
- if self.direction == self.PIPE_IN:
82
- self.peer = 'client'
83
-
84
- def SendMessage(self, message, main_thread = True):
85
- global connections, in_pipe, out_pipe
86
- message_sent = False
87
- now = current_time()
88
- if message['message'] == 'closed':
89
- message['time'] = now
90
- else:
91
- message['time'] = current_time() + self.latency
92
- message['size'] = .0
93
- if 'data' in message:
94
- message['size'] = float(len(message['data']))
95
- try:
96
- connection_id = message['connection']
97
- # Send messages directly, bypassing the queues is throttling is disabled and we are on the main thread
98
- if main_thread and connection_id in connections and self.peer in connections[connection_id]and self.latency == 0 and self.kbps == .0:
99
- message_sent = self.SendPeerMessage(message)
100
- except:
101
- pass
102
- if not message_sent:
103
- try:
104
- self.queue.put(message)
105
- except:
106
- pass
107
-
108
- def SendPeerMessage(self, message):
109
- global last_activity, last_client_disconnected
110
- last_activity = current_time()
111
- message_sent = False
112
- connection_id = message['connection']
113
- if connection_id in connections:
114
- if self.peer in connections[connection_id]:
115
- try:
116
- connections[connection_id][self.peer].handle_message(message)
117
- message_sent = True
118
- except:
119
- # Clean up any disconnected connections
120
- try:
121
- connections[connection_id]['server'].close()
122
- except:
123
- pass
124
- try:
125
- connections[connection_id]['client'].close()
126
- except:
127
- pass
128
- del connections[connection_id]
129
- if not connections:
130
- last_client_disconnected = current_time()
131
- logging.info('[{0:d}] Last connection closed'.format(self.client_id))
132
- return message_sent
133
-
134
- def tick(self):
135
- global connections
136
- global flush_pipes
137
- next_packet_time = None
138
- processed_messages = False
139
- now = current_time()
140
- try:
141
- if self.next_message is None:
142
- self.next_message = self.queue.get_nowait()
143
-
144
- # Accumulate bandwidth if an available packet/message was waiting since our last tick
145
- if self.next_message is not None and self.kbps > .0 and self.next_message['time'] <= now:
146
- elapsed = now - self.last_tick
147
- accumulated_bytes = elapsed * self.kbps * 1000.0 / 8.0
148
- self.available_bytes += accumulated_bytes
149
-
150
- # process messages as long as the next message is sendable (latency or available bytes)
151
- while (self.next_message is not None) and\
152
- (flush_pipes or ((self.next_message['time'] <= now) and
153
- (self.kbps <= .0 or self.next_message['size'] <= self.available_bytes))):
154
- processed_messages = True
155
- if self.kbps > .0:
156
- self.available_bytes -= self.next_message['size']
157
- self.SendPeerMessage(self.next_message)
158
- self.next_message = None
159
- self.next_message = self.queue.get_nowait()
160
- except Empty:
161
- pass
162
- except Exception as e:
163
- logging.exception('Tick Exception')
164
-
165
- # Only accumulate bytes while we have messages that are ready to send
166
- if self.next_message is None or self.next_message['time'] > now:
167
- self.available_bytes = .0
168
- self.last_tick = now
169
-
170
- # Figure out how long until the next packet can be sent
171
- if self.next_message is not None:
172
- # First, just the latency
173
- next_packet_time = self.next_message['time'] - now
174
- # Additional time for bandwidth
175
- if self.kbps > .0:
176
- accumulated_bytes = self.available_bytes + next_packet_time * self.kbps * 1000.0 / 8.0
177
- needed_bytes = self.next_message['size'] - accumulated_bytes
178
- if needed_bytes > 0:
179
- needed_time = needed_bytes / (self.kbps * 1000.0 / 8.0)
180
- next_packet_time += needed_time
181
-
182
- return next_packet_time
183
-
184
-
185
- ########################################################################################################################
186
- # Threaded DNS resolver
187
- ########################################################################################################################
188
- class AsyncDNS(threading.Thread):
189
- def __init__(self, client_id, hostname, port, is_localhost, result_pipe):
190
- threading.Thread.__init__(self)
191
- self.hostname = hostname
192
- self.port = port
193
- self.client_id = client_id
194
- self.is_localhost = is_localhost
195
- self.result_pipe = result_pipe
196
-
197
- def run(self):
198
- global lock, background_activity_count
199
- try:
200
- logging.debug('[{0:d}] AsyncDNS - calling getaddrinfo for {1}:{2:d}'.format(self.client_id, self.hostname, self.port))
201
- addresses = socket.getaddrinfo(self.hostname, self.port)
202
- logging.info('[{0:d}] Resolving {1}:{2:d} Completed'.format(self.client_id, self.hostname, self.port))
203
- except:
204
- addresses = ()
205
- logging.info('[{0:d}] Resolving {1}:{2:d} Failed'.format(self.client_id, self.hostname, self.port))
206
- message = {'message': 'resolved', 'connection': self.client_id, 'addresses': addresses, 'localhost': self.is_localhost}
207
- self.result_pipe.SendMessage(message, False)
208
- lock.acquire()
209
- if background_activity_count > 0:
210
- background_activity_count -= 1
211
- lock.release()
212
- # open and close a local socket which will interrupt the long polling loop to process the message
213
- s = socket.socket()
214
- s.connect((server.ipaddr, server.port))
215
- s.close()
216
-
217
-
218
- ########################################################################################################################
219
- # TCP Client
220
- ########################################################################################################################
221
- class TCPConnection(asyncore.dispatcher):
222
- STATE_ERROR = -1
223
- STATE_IDLE = 0
224
- STATE_RESOLVING = 1
225
- STATE_CONNECTING = 2
226
- STATE_CONNECTED = 3
227
-
228
- def __init__(self, client_id):
229
- global options
230
- asyncore.dispatcher.__init__(self)
231
- self.client_id = client_id
232
- self.state = self.STATE_IDLE
233
- self.buffer = ''
234
- self.addr = None
235
- self.dns_thread = None
236
- self.hostname = None
237
- self.port = None
238
- self.needs_config = True
239
- self.needs_close = False
240
- self.did_resolve = False
241
-
242
- def SendMessage(self, type, message):
243
- message['message'] = type
244
- message['connection'] = self.client_id
245
- in_pipe.SendMessage(message)
246
-
247
- def handle_message(self, message):
248
- if message['message'] == 'data' and 'data' in message and len(message['data']):
249
- self.buffer += message['data']
250
- if self.state == self.STATE_CONNECTED:
251
- self.handle_write()
252
- elif message['message'] == 'resolve':
253
- self.HandleResolve(message)
254
- elif message['message'] == 'connect':
255
- self.HandleConnect(message)
256
- elif message['message'] == 'closed':
257
- if len(self.buffer) == 0:
258
- self.handle_close()
259
- else:
260
- self.needs_close = True
261
-
262
- def handle_error(self):
263
- logging.warning('[{0:d}] Error'.format(self.client_id))
264
- if self.state == self.STATE_CONNECTING:
265
- self.SendMessage('connected', {'success': False, 'address': self.addr})
266
-
267
- def handle_close(self):
268
- global last_client_disconnected
269
- logging.info('[{0:d}] Server Connection Closed'.format(self.client_id))
270
- self.state = self.STATE_ERROR
271
- self.close()
272
- try:
273
- if self.client_id in connections:
274
- if 'server' in connections[self.client_id]:
275
- del connections[self.client_id]['server']
276
- if 'client' in connections[self.client_id]:
277
- self.SendMessage('closed', {})
278
- else:
279
- del connections[self.client_id]
280
- if not connections:
281
- last_client_disconnected = current_time()
282
- logging.info('[{0:d}] Last Browser disconnected'.format(self.client_id))
283
- except:
284
- pass
285
-
286
- def handle_connect(self):
287
- if self.state == self.STATE_CONNECTING:
288
- self.state = self.STATE_CONNECTED
289
- self.SendMessage('connected', {'success': True, 'address': self.addr})
290
- logging.info('[{0:d}] Connected'.format(self.client_id))
291
- self.handle_write()
292
-
293
- def writable(self):
294
- if self.state == self.STATE_CONNECTING:
295
- return True
296
- return len(self.buffer) > 0
297
-
298
- def handle_write(self):
299
- if self.needs_config:
300
- self.needs_config = False
301
- self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
302
- self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 128 * 1024)
303
- self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 128 * 1024)
304
- if len(self.buffer) > 0:
305
- sent = self.send(self.buffer)
306
- logging.debug('[{0:d}] TCP => {1:d} byte(s)'.format(self.client_id, sent))
307
- self.buffer = self.buffer[sent:]
308
- if self.needs_close and len(self.buffer) == 0:
309
- self.needs_close = False
310
- self.handle_close()
311
-
312
- def handle_read(self):
313
- try:
314
- while True:
315
- data = self.recv(1460)
316
- if data:
317
- if self.state == self.STATE_CONNECTED:
318
- logging.debug('[{0:d}] TCP <= {1:d} byte(s)'.format(self.client_id, len(data)))
319
- self.SendMessage('data', {'data': data})
320
- else:
321
- return
322
- except:
323
- pass
324
-
325
- def HandleResolve(self, message):
326
- global in_pipe, map_localhost, lock, background_activity_count
327
- self.did_resolve = True
328
- is_localhost = False
329
- if 'hostname' in message:
330
- self.hostname = message['hostname']
331
- self.port = 0
332
- if 'port' in message:
333
- self.port = message['port']
334
- logging.info('[{0:d}] Resolving {1}:{2:d}'.format(self.client_id, self.hostname, self.port))
335
- if self.hostname == 'localhost':
336
- self.hostname = '127.0.0.1'
337
- if self.hostname == '127.0.0.1':
338
- logging.info('[{0:d}] Connection to localhost detected'.format(self.client_id))
339
- is_localhost = True
340
- if (dest_addresses is not None) and (not is_localhost or map_localhost):
341
- logging.info('[{0:d}] Resolving {1}:{2:d} to mapped address {3}'.format(self.client_id, self.hostname, self.port, dest_addresses))
342
- self.SendMessage('resolved', {'addresses': dest_addresses, 'localhost': False})
343
- else:
344
- lock.acquire()
345
- background_activity_count += 1
346
- lock.release()
347
- self.state = self.STATE_RESOLVING
348
- self.dns_thread = AsyncDNS(self.client_id, self.hostname, self.port, is_localhost, in_pipe)
349
- self.dns_thread.start()
350
-
351
- def HandleConnect(self, message):
352
- global map_localhost
353
- if 'addresses' in message and len(message['addresses']):
354
- self.state = self.STATE_CONNECTING
355
- is_localhost = False
356
- if 'localhost' in message:
357
- is_localhost = message['localhost']
358
- elif not self.did_resolve and message['addresses'][0] == '127.0.0.1':
359
- logging.info('[{0:d}] Connection to localhost detected'.format(self.client_id))
360
- is_localhost = True
361
- if (dest_addresses is not None) and (not is_localhost or map_localhost):
362
- self.addr = dest_addresses[0]
363
- else:
364
- self.addr = message['addresses'][0]
365
- self.create_socket(self.addr[0], socket.SOCK_STREAM)
366
- addr = self.addr[4][0]
367
- if not is_localhost or map_localhost:
368
- port = GetDestPort(message['port'])
369
- else:
370
- port = message['port']
371
- logging.info('[{0:d}] Connecting to {1}:{2:d}'.format(self.client_id, addr, port))
372
- self.connect((addr, port))
373
-
374
-
375
- ########################################################################################################################
376
- # Socks5 Server
377
- ########################################################################################################################
378
- class Socks5Server(asyncore.dispatcher):
379
-
380
- def __init__(self, host, port):
381
- asyncore.dispatcher.__init__(self)
382
- self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
383
- try:
384
- self.set_reuse_addr()
385
- self.bind((host, port))
386
- self.listen(socket.SOMAXCONN)
387
- self.ipaddr, self.port = self.socket.getsockname()
388
- self.current_client_id = 0
389
- except:
390
- PrintMessage("Unable to listen on {0}:{1}. Is the port already in use?".format(host, port))
391
- exit(1)
392
-
393
- def handle_accept(self):
394
- global connections, last_client_disconnected
395
- pair = self.accept()
396
- if pair is not None:
397
- last_client_disconnected = None
398
- sock, addr = pair
399
- self.current_client_id += 1
400
- logging.info('[{0:d}] Incoming connection from {1}'.format(self.current_client_id, repr(addr)))
401
- connections[self.current_client_id] = {
402
- 'client' : Socks5Connection(sock, self.current_client_id),
403
- 'server' : None
404
- }
405
-
406
-
407
- # Socks5 reference: https://en.wikipedia.org/wiki/SOCKS#SOCKS5
408
- class Socks5Connection(asyncore.dispatcher):
409
- STATE_ERROR = -1
410
- STATE_WAITING_FOR_HANDSHAKE = 0
411
- STATE_WAITING_FOR_CONNECT_REQUEST = 1
412
- STATE_RESOLVING = 2
413
- STATE_CONNECTING = 3
414
- STATE_CONNECTED = 4
415
-
416
- def __init__(self, connected_socket, client_id):
417
- global options
418
- asyncore.dispatcher.__init__(self, connected_socket)
419
- self.client_id = client_id
420
- self.state = self.STATE_WAITING_FOR_HANDSHAKE
421
- self.ip = None
422
- self.addresses = None
423
- self.hostname = None
424
- self.port = None
425
- self.requested_address = None
426
- self.buffer = ''
427
- self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
428
- self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 128 * 1024)
429
- self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 128 * 1024)
430
- self.needs_close = False
431
-
432
- def SendMessage(self, type, message):
433
- message['message'] = type
434
- message['connection'] = self.client_id
435
- out_pipe.SendMessage(message)
436
-
437
- def handle_message(self, message):
438
- if message['message'] == 'data' and 'data' in message and len(message['data']) > 0:
439
- self.buffer += message['data']
440
- if self.state == self.STATE_CONNECTED:
441
- self.handle_write()
442
- elif message['message'] == 'resolved':
443
- self.HandleResolved(message)
444
- elif message['message'] == 'connected':
445
- self.HandleConnected(message)
446
- self.handle_write()
447
- elif message['message'] == 'closed':
448
- if len(self.buffer) == 0:
449
- logging.info('[{0:d}] Server connection close being processed, closing Browser connection'.format(self.client_id))
450
- self.handle_close()
451
- else:
452
- logging.info('[{0:d}] Server connection close being processed, queuing browser connection close'.format(self.client_id))
453
- self.needs_close = True
454
-
455
- def writable(self):
456
- return len(self.buffer) > 0
457
-
458
- def handle_write(self):
459
- if len(self.buffer) > 0:
460
- sent = self.send(self.buffer)
461
- logging.debug('[{0:d}] SOCKS <= {1:d} byte(s)'.format(self.client_id, sent))
462
- self.buffer = self.buffer[sent:]
463
- if self.needs_close and len(self.buffer) == 0:
464
- logging.info('[{0:d}] queued browser connection close being processed, closing Browser connection'.format(self.client_id))
465
- self.needs_close = False
466
- self.handle_close()
467
-
468
- def handle_read(self):
469
- global connections
470
- global dns_cache
471
- try:
472
- while True:
473
- # Consume in up-to packet-sized chunks (TCP packet payload as 1460 bytes from 1500 byte ethernet frames)
474
- data = self.recv(1460)
475
- if data:
476
- data_len = len(data)
477
- if self.state == self.STATE_CONNECTED:
478
- logging.debug('[{0:d}] SOCKS => {1:d} byte(s)'.format(self.client_id, data_len))
479
- self.SendMessage('data', {'data': data})
480
- elif self.state == self.STATE_WAITING_FOR_HANDSHAKE:
481
- self.state = self.STATE_ERROR #default to an error state, set correctly if things work out
482
- if data_len >= 2 and ord(data[0]) == 0x05:
483
- supports_no_auth = False
484
- auth_count = ord(data[1])
485
- if data_len == auth_count + 2:
486
- for i in range(auth_count):
487
- offset = i + 2
488
- if ord(data[offset]) == 0:
489
- supports_no_auth = True
490
- if supports_no_auth:
491
- # Respond with a message that "No Authentication" was agreed to
492
- logging.info('[{0:d}] New Socks5 client'.format(self.client_id))
493
- response = chr(0x05) + chr(0x00)
494
- self.state = self.STATE_WAITING_FOR_CONNECT_REQUEST
495
- self.buffer += response
496
- self.handle_write()
497
- elif self.state == self.STATE_WAITING_FOR_CONNECT_REQUEST:
498
- self.state = self.STATE_ERROR #default to an error state, set correctly if things work out
499
- if data_len >= 10 and ord(data[0]) == 0x05 and ord(data[2]) == 0x00:
500
- if ord(data[1]) == 0x01: #TCP connection (only supported method for now)
501
- connections[self.client_id]['server'] = TCPConnection(self.client_id)
502
- self.requested_address = data[3:]
503
- port_offset = 0
504
- if ord(data[3]) == 0x01:
505
- port_offset = 8
506
- self.ip = '{0:d}.{1:d}.{2:d}.{3:d}'.format(ord(data[4]), ord(data[5]), ord(data[6]), ord(data[7]))
507
- elif ord(data[3]) == 0x03:
508
- name_len = ord(data[4])
509
- if data_len >= 6 + name_len:
510
- port_offset = 5 + name_len
511
- self.hostname = data[5:5 + name_len]
512
- elif ord(data[3]) == 0x04 and data_len >= 22:
513
- port_offset = 20
514
- self.ip = ''
515
- for i in range(16):
516
- self.ip += '{0:02x}'.format(ord(data[4 + i]))
517
- if i % 2 and i < 15:
518
- self.ip += ':'
519
- if port_offset and connections[self.client_id]['server'] is not None:
520
- self.port = 256 * ord(data[port_offset]) + ord(data[port_offset + 1])
521
- if self.port:
522
- if self.ip is None and self.hostname is not None:
523
- if dns_cache is not None and self.hostname in dns_cache:
524
- self.state = self.STATE_CONNECTING
525
- cache_entry = dns_cache[self.hostname]
526
- self.addresses = cache_entry['addresses']
527
- self.SendMessage('connect', {'addresses': self.addresses, 'port': self.port, 'localhost': cache_entry['localhost']})
528
- else:
529
- self.state = self.STATE_RESOLVING
530
- self.SendMessage('resolve', {'hostname': self.hostname, 'port': self.port})
531
- elif self.ip is not None:
532
- self.state = self.STATE_CONNECTING
533
- logging.debug('[{0:d}] Socks Connect - calling getaddrinfo for {1}:{2:d}'.format(self.client_id, self.ip, self.port))
534
- self.addresses = socket.getaddrinfo(self.ip, self.port)
535
- self.SendMessage('connect', {'addresses': self.addresses, 'port': self.port})
536
- else:
537
- return
538
- except:
539
- pass
540
-
541
- def handle_close(self):
542
- global last_client_disconnected
543
- logging.info('[{0:d}] Browser Connection Closed by browser'.format(self.client_id))
544
- self.state = self.STATE_ERROR
545
- self.close()
546
- try:
547
- if self.client_id in connections:
548
- if 'client' in connections[self.client_id]:
549
- del connections[self.client_id]['client']
550
- if 'server' in connections[self.client_id]:
551
- self.SendMessage('closed', {})
552
- else:
553
- del connections[self.client_id]
554
- if not connections:
555
- last_client_disconnected = current_time()
556
- logging.info('[{0:d}] Last Browser disconnected'.format(self.client_id))
557
- except:
558
- pass
559
-
560
- def HandleResolved(self, message):
561
- global dns_cache
562
- if self.state == self.STATE_RESOLVING:
563
- if 'addresses' in message and len(message['addresses']):
564
- self.state = self.STATE_CONNECTING
565
- self.addresses = message['addresses']
566
- if dns_cache is not None:
567
- dns_cache[self.hostname] = {'addresses': self.addresses, 'localhost': message['localhost']}
568
- logging.debug('[{0:d}] Resolved {1}, Connecting'.format(self.client_id, self.hostname))
569
- self.SendMessage('connect', {'addresses': self.addresses, 'port': self.port, 'localhost': message['localhost']})
570
- else:
571
- # Send host unreachable error
572
- self.state = self.STATE_ERROR
573
- self.buffer += chr(0x05) + chr(0x04) + self.requested_address
574
- self.handle_write()
575
-
576
- def HandleConnected(self, message):
577
- if 'success' in message and self.state == self.STATE_CONNECTING:
578
- response = chr(0x05)
579
- if message['success']:
580
- response += chr(0x00)
581
- logging.debug('[{0:d}] Connected to {1}'.format(self.client_id, self.hostname))
582
- self.state = self.STATE_CONNECTED
583
- else:
584
- response += chr(0x04)
585
- self.state = self.STATE_ERROR
586
- response += chr(0x00)
587
- response += self.requested_address
588
- self.buffer += response
589
- self.handle_write()
590
-
591
-
592
- ########################################################################################################################
593
- # stdin command processor
594
- ########################################################################################################################
595
- class CommandProcessor():
596
- def __init__(self):
597
- thread = threading.Thread(target = self.run, args=())
598
- thread.daemon = True
599
- thread.start()
600
-
601
- def run(self):
602
- global must_exit
603
- while not must_exit:
604
- for line in iter(sys.stdin.readline, ''):
605
- self.ProcessCommand(line.strip())
606
-
607
- def ProcessCommand(self, input):
608
- global in_pipe
609
- global out_pipe
610
- global needs_flush
611
- global REMOVE_TCP_OVERHEAD
612
- global port_mappings
613
- global server
614
- if len(input):
615
- ok = False
616
- try:
617
- command = input.split()
618
- if len(command) and len(command[0]):
619
- if command[0].lower() == 'flush':
620
- ok = True
621
- elif command[0].lower() == 'set' and len(command) >= 3:
622
- if command[1].lower() == 'rtt' and len(command[2]):
623
- rtt = float(command[2])
624
- latency = rtt / 2000.0
625
- in_pipe.latency = latency
626
- out_pipe.latency = latency
627
- ok = True
628
- elif command[1].lower() == 'inkbps' and len(command[2]):
629
- in_pipe.kbps = float(command[2]) * REMOVE_TCP_OVERHEAD
630
- ok = True
631
- elif command[1].lower() == 'outkbps' and len(command[2]):
632
- out_pipe.kbps = float(command[2]) * REMOVE_TCP_OVERHEAD
633
- ok = True
634
- elif command[1].lower() == 'mapports' and len(command[2]):
635
- SetPortMappings(command[2])
636
- ok = True
637
- elif command[0].lower() == 'reset' and len(command) >= 2:
638
- if command[1].lower() == 'rtt' or command[1].lower() == 'all':
639
- in_pipe.latency = 0
640
- out_pipe.latency = 0
641
- ok = True
642
- if command[1].lower() == 'inkbps' or command[1].lower() == 'all':
643
- in_pipe.kbps = 0
644
- ok = True
645
- if command[1].lower() == 'outkbps' or command[1].lower() == 'all':
646
- out_pipe.kbps = 0
647
- ok = True
648
- if command[1].lower() == 'mapports' or command[1].lower() == 'all':
649
- port_mappings = {}
650
- ok = True
651
-
652
- if ok:
653
- needs_flush = True
654
- except:
655
- pass
656
- if not ok:
657
- PrintMessage('ERROR')
658
- # open and close a local socket which will interrupt the long polling loop to process the flush
659
- if needs_flush:
660
- s = socket.socket()
661
- s.connect((server.ipaddr, server.port))
662
- s.close()
663
-
664
-
665
- ########################################################################################################################
666
- # Main Entry Point
667
- ########################################################################################################################
668
- def main():
669
- global server
670
- global options
671
- global in_pipe
672
- global out_pipe
673
- global dest_addresses
674
- global port_mappings
675
- global map_localhost
676
- global dns_cache
677
- import argparse
678
- global REMOVE_TCP_OVERHEAD
679
- parser = argparse.ArgumentParser(description='Traffic-shaping socks5 proxy.',
680
- prog='tsproxy')
681
- parser.add_argument('-v', '--verbose', action='count', help="Increase verbosity (specify multiple times for more). -vvvv for full debug output.")
682
- parser.add_argument('--logfile', help="Write log messages to given file instead of stdout.")
683
- parser.add_argument('-b', '--bind', default='localhost', help="Server interface address (defaults to localhost).")
684
- parser.add_argument('-p', '--port', type=int, default=1080, help="Server port (defaults to 1080, use 0 for randomly assigned).")
685
- parser.add_argument('-r', '--rtt', type=float, default=.0, help="Round Trip Time Latency (in ms).")
686
- parser.add_argument('-i', '--inkbps', type=float, default=.0, help="Download Bandwidth (in 1000 bits/s - Kbps).")
687
- parser.add_argument('-o', '--outkbps', type=float, default=.0, help="Upload Bandwidth (in 1000 bits/s - Kbps).")
688
- parser.add_argument('-w', '--window', type=int, default=10, help="Emulated TCP initial congestion window (defaults to 10).")
689
- parser.add_argument('-d', '--desthost', help="Redirect all outbound connections to the specified host.")
690
- parser.add_argument('-m', '--mapports', help="Remap outbound ports. Comma-separated list of original:new with * as a wildcard. --mapports '443:8443,*:8080'")
691
- parser.add_argument('-l', '--localhost', action='store_true', default=False,
692
- help="Include connections already destined for localhost/127.0.0.1 in the host and port remapping.")
693
- parser.add_argument('-n', '--nodnscache', action='store_true', default=False, help="Disable internal DNS cache.")
694
- parser.add_argument('-f', '--flushdnscache', action='store_true', default=False, help="Automatically flush the DNS cache 500ms after the last client disconnects.")
695
- options = parser.parse_args()
696
-
697
- # Set up logging
698
- log_level = logging.CRITICAL
699
- if options.verbose == 1:
700
- log_level = logging.ERROR
701
- elif options.verbose == 2:
702
- log_level = logging.WARNING
703
- elif options.verbose == 3:
704
- log_level = logging.INFO
705
- elif options.verbose >= 4:
706
- log_level = logging.DEBUG
707
- if options.logfile is not None:
708
- logging.basicConfig(filename=options.logfile, level=log_level,
709
- format="%(asctime)s.%(msecs)03d - %(message)s", datefmt="%H:%M:%S")
710
- else:
711
- logging.basicConfig(level=log_level, format="%(asctime)s.%(msecs)03d - %(message)s", datefmt="%H:%M:%S")
712
-
713
- # Parse any port mappings
714
- if options.mapports:
715
- SetPortMappings(options.mapports)
716
-
717
- if options.nodnscache:
718
- dns_cache = None
719
-
720
- map_localhost = options.localhost
721
-
722
- # Resolve the address for a rewrite destination host if one was specified
723
- if options.desthost:
724
- logging.debug('Startup - calling getaddrinfo for {0}:{1:d}'.format(options.desthost, GetDestPort(80)))
725
- dest_addresses = socket.getaddrinfo(options.desthost, GetDestPort(80))
726
-
727
- # Set up the pipes. 1/2 of the latency gets applied in each direction (and /1000 to convert to seconds)
728
- in_pipe = TSPipe(TSPipe.PIPE_IN, options.rtt / 2000.0, options.inkbps * REMOVE_TCP_OVERHEAD)
729
- out_pipe = TSPipe(TSPipe.PIPE_OUT, options.rtt / 2000.0, options.outkbps * REMOVE_TCP_OVERHEAD)
730
-
731
- signal.signal(signal.SIGINT, signal_handler)
732
- server = Socks5Server(options.bind, options.port)
733
- command_processor = CommandProcessor()
734
- PrintMessage('Started Socks5 proxy server on {0}:{1:d}\nHit Ctrl-C to exit.'.format(server.ipaddr, server.port))
735
- run_loop()
736
-
737
- def signal_handler(signal, frame):
738
- global server
739
- global must_exit
740
- logging.error('Exiting...')
741
- must_exit = True
742
- del server
743
-
744
-
745
- # Wrapper around the asyncore loop that lets us poll the in/out pipes every 1ms
746
- def run_loop():
747
- global must_exit
748
- global in_pipe
749
- global out_pipe
750
- global needs_flush
751
- global flush_pipes
752
- global last_activity
753
- global last_client_disconnected
754
- global dns_cache
755
- winmm = None
756
-
757
- # increase the windows timer resolution to 1ms
758
- if platform.system() == "Windows":
759
- try:
760
- import ctypes
761
- winmm = ctypes.WinDLL('winmm')
762
- winmm.timeBeginPeriod(1)
763
- except:
764
- pass
765
-
766
- last_activity = current_time()
767
- last_check = current_time()
768
- # disable gc to avoid pauses during traffic shaping/proxying
769
- gc.disable()
770
- out_interval = None
771
- in_interval = None
772
- while not must_exit:
773
- # Tick every 1ms if traffic-shaping is enabled and we have data or are doing background dns lookups, every 1 second otherwise
774
- lock.acquire()
775
- tick_interval = 0.001
776
- if out_interval is not None:
777
- tick_interval = max(tick_interval, out_interval)
778
- if in_interval is not None:
779
- tick_interval = max(tick_interval, in_interval)
780
- if background_activity_count == 0:
781
- if in_pipe.next_message is None and in_pipe.queue.empty() and out_pipe.next_message is None and out_pipe.queue.empty():
782
- tick_interval = 1.0
783
- elif in_pipe.kbps == .0 and in_pipe.latency == 0 and out_pipe.kbps == .0 and out_pipe.latency == 0:
784
- tick_interval = 1.0
785
- lock.release()
786
- logging.debug("Tick Time: %0.3f", tick_interval)
787
- asyncore.poll(tick_interval, asyncore.socket_map)
788
- if needs_flush:
789
- flush_pipes = True
790
- dns_cache = {}
791
- needs_flush = False
792
- out_interval = out_pipe.tick()
793
- in_interval = in_pipe.tick()
794
- if flush_pipes:
795
- PrintMessage('OK')
796
- flush_pipes = False
797
- now = current_time()
798
- # Clear the DNS cache 500ms after the last client disconnects
799
- if options.flushdnscache and last_client_disconnected is not None and dns_cache:
800
- if now - last_client_disconnected >= 0.5:
801
- dns_cache = {}
802
- last_client_disconnected = None
803
- logging.debug("Flushed DNS cache")
804
- # Every 500 ms check to see if it is a good time to do a gc
805
- if now - last_check >= 0.5:
806
- last_check = now
807
- # manually gc after 5 seconds of idle
808
- if now - last_activity >= 5:
809
- last_activity = now
810
- logging.debug("Triggering manual GC")
811
- gc.collect()
812
-
813
- if winmm is not None:
814
- winmm.timeEndPeriod(1)
815
-
816
- def GetDestPort(port):
817
- global port_mappings
818
- if port_mappings is not None:
819
- src_port = str(port)
820
- if src_port in port_mappings:
821
- return port_mappings[src_port]
822
- elif 'default' in port_mappings:
823
- return port_mappings['default']
824
- return port
825
-
826
-
827
- def SetPortMappings(map_string):
828
- global port_mappings
829
- port_mappings = {}
830
- map_string = map_string.strip('\'" \t\r\n')
831
- for pair in map_string.split(','):
832
- (src, dest) = pair.split(':')
833
- if src == '*':
834
- port_mappings['default'] = int(dest)
835
- logging.debug("Default port mapped to port {0}".format(dest))
836
- else:
837
- logging.debug("Port {0} mapped to port {1}".format(src, dest))
838
- port_mappings[src] = int(dest)
839
-
840
-
841
- if '__main__' == __name__:
842
- main()