|
|
@ -35,35 +35,57 @@ import random |
|
|
|
from shadowsocks import encrypt, eventloop, utils, common |
|
|
|
from shadowsocks.common import parse_header |
|
|
|
|
|
|
|
|
|
|
|
# we clear at most TIMEOUTS_CLEAN_SIZE timeouts each time |
|
|
|
TIMEOUTS_CLEAN_SIZE = 512 |
|
|
|
|
|
|
|
# we check timeouts every TIMEOUT_PRECISION seconds |
|
|
|
TIMEOUT_PRECISION = 4 |
|
|
|
|
|
|
|
MSG_FASTOPEN = 0x20000000 |
|
|
|
|
|
|
|
# SOCKS CMD defination |
|
|
|
CMD_CONNECT = 1 |
|
|
|
CMD_BIND = 2 |
|
|
|
CMD_UDP_ASSOCIATE = 3 |
|
|
|
|
|
|
|
# TCP Relay can be either sslocal or ssserver |
|
|
|
# for sslocal it is called is_local=True |
|
|
|
|
|
|
|
# for each opening port, we have a TCP Relay |
|
|
|
# for each connection, we have a TCP Relay Handler to handle the connection |
|
|
|
|
|
|
|
# for each handler, we have 2 sockets: |
|
|
|
# local: connected to the client |
|
|
|
# remote: connected to remote server |
|
|
|
|
|
|
|
# for each handler, we have 2 streams: |
|
|
|
# upstream: from client to server direction |
|
|
|
# read local and write to remote |
|
|
|
# downstream: from server to client direction |
|
|
|
# read remote and write to local |
|
|
|
|
|
|
|
# for each handler, it could be at one of several stages: |
|
|
|
|
|
|
|
# local: |
|
|
|
# stage 0 init |
|
|
|
# stage 1 hello received, hello sent |
|
|
|
# stage 0 SOCKS hello received from local, send hello to local |
|
|
|
# stage 1 addr received from local, query DNS for remote |
|
|
|
# stage 2 UDP assoc |
|
|
|
# stage 3 DNS |
|
|
|
# stage 4 addr received, reply sent |
|
|
|
# stage 5 remote connected |
|
|
|
# stage 3 DNS resolved, connect to remote |
|
|
|
# stage 4 still connecting, more data from local received |
|
|
|
# stage 5 remote connected, piping local and remote |
|
|
|
|
|
|
|
# remote: |
|
|
|
# stage 0 init |
|
|
|
# stage 3 DNS |
|
|
|
# stage 4 addr received, reply sent |
|
|
|
# stage 5 remote connected |
|
|
|
# stage 0 just jump to stage 1 |
|
|
|
# stage 1 addr received from local, query DNS for remote |
|
|
|
# stage 3 DNS resolved, connect to remote |
|
|
|
# stage 4 still connecting, more data from local received |
|
|
|
# stage 5 remote connected, piping local and remote |
|
|
|
|
|
|
|
STAGE_INIT = 0 |
|
|
|
STAGE_HELLO = 1 |
|
|
|
STAGE_ADDR = 1 |
|
|
|
STAGE_UDP_ASSOC = 2 |
|
|
|
STAGE_DNS = 3 |
|
|
|
STAGE_REPLY = 4 |
|
|
|
STAGE_CONNECTING = 4 |
|
|
|
STAGE_STREAM = 5 |
|
|
|
STAGE_DESTROYED = -1 |
|
|
|
|
|
|
@ -71,7 +93,7 @@ STAGE_DESTROYED = -1 |
|
|
|
STREAM_UP = 0 |
|
|
|
STREAM_DOWN = 1 |
|
|
|
|
|
|
|
# stream wait status |
|
|
|
# stream wait status, indicating it's waiting for reading, etc |
|
|
|
WAIT_STATUS_INIT = 0 |
|
|
|
WAIT_STATUS_READING = 1 |
|
|
|
WAIT_STATUS_WRITING = 2 |
|
|
@ -128,9 +150,15 @@ class TCPRelayHandler(object): |
|
|
|
return server, server_port |
|
|
|
|
|
|
|
def _update_activity(self): |
|
|
|
# tell the TCP Relay we have activities recently |
|
|
|
# else it will think we are inactive and timed out |
|
|
|
self._server.update_activity(self) |
|
|
|
|
|
|
|
def _update_stream(self, stream, status): |
|
|
|
# update a stream to a new waiting status |
|
|
|
|
|
|
|
# check if status is changed |
|
|
|
# only update if dirty |
|
|
|
dirty = False |
|
|
|
if stream == STREAM_DOWN: |
|
|
|
if self._downstream_status != status: |
|
|
@ -157,6 +185,9 @@ class TCPRelayHandler(object): |
|
|
|
self._loop.modify(self._remote_sock, event) |
|
|
|
|
|
|
|
def _write_to_sock(self, data, sock): |
|
|
|
# write data to sock |
|
|
|
# if only some of the data are written, put remaining in the buffer |
|
|
|
# and update the stream to wait for writing |
|
|
|
if not data or not sock: |
|
|
|
return False |
|
|
|
uncomplete = False |
|
|
@ -195,13 +226,16 @@ class TCPRelayHandler(object): |
|
|
|
logging.error('write_all_to_sock:unknown socket') |
|
|
|
return True |
|
|
|
|
|
|
|
def _handle_stage_reply(self, data): |
|
|
|
def _handle_stage_connecting(self, data): |
|
|
|
if self._is_local: |
|
|
|
data = self._encryptor.encrypt(data) |
|
|
|
self._data_to_write_to_remote.append(data) |
|
|
|
if self._is_local and not self._fastopen_connected and \ |
|
|
|
self._config['fast_open']: |
|
|
|
# for sslocal and fastopen, we basically wait for data and use |
|
|
|
# sendto to connect |
|
|
|
try: |
|
|
|
# only connect once |
|
|
|
self._fastopen_connected = True |
|
|
|
remote_sock = \ |
|
|
|
self._create_remote_socket(self._chosen_server[0], |
|
|
@ -231,7 +265,7 @@ class TCPRelayHandler(object): |
|
|
|
traceback.print_exc() |
|
|
|
self.destroy() |
|
|
|
|
|
|
|
def _handle_stage_hello(self, data): |
|
|
|
def _handle_stage_addr(self, data): |
|
|
|
try: |
|
|
|
if self._is_local: |
|
|
|
cmd = common.ord(data[1]) |
|
|
@ -312,7 +346,7 @@ class TCPRelayHandler(object): |
|
|
|
ip = result[1] |
|
|
|
if ip: |
|
|
|
try: |
|
|
|
self._stage = STAGE_REPLY |
|
|
|
self._stage = STAGE_CONNECTING |
|
|
|
remote_addr = ip |
|
|
|
if self._is_local: |
|
|
|
remote_port = self._chosen_server[1] |
|
|
@ -320,11 +354,15 @@ class TCPRelayHandler(object): |
|
|
|
remote_port = self._remote_address[1] |
|
|
|
|
|
|
|
if self._is_local and self._config['fast_open']: |
|
|
|
# for fastopen: |
|
|
|
# wait for more data to arrive and send them in one SYN |
|
|
|
self._stage = STAGE_REPLY |
|
|
|
self._stage = STAGE_CONNECTING |
|
|
|
# we don't have to wait for remote since it's not |
|
|
|
# created |
|
|
|
self._update_stream(STREAM_UP, WAIT_STATUS_READING) |
|
|
|
# TODO when there is already data in this packet |
|
|
|
else: |
|
|
|
# else do connect |
|
|
|
remote_sock = self._create_remote_socket(remote_addr, |
|
|
|
remote_port) |
|
|
|
try: |
|
|
@ -335,7 +373,7 @@ class TCPRelayHandler(object): |
|
|
|
pass |
|
|
|
self._loop.add(remote_sock, |
|
|
|
eventloop.POLL_ERR | eventloop.POLL_OUT) |
|
|
|
self._stage = STAGE_REPLY |
|
|
|
self._stage = STAGE_CONNECTING |
|
|
|
self._update_stream(STREAM_UP, WAIT_STATUS_READWRITING) |
|
|
|
self._update_stream(STREAM_DOWN, WAIT_STATUS_READING) |
|
|
|
return |
|
|
@ -346,6 +384,8 @@ class TCPRelayHandler(object): |
|
|
|
self.destroy() |
|
|
|
|
|
|
|
def _on_local_read(self): |
|
|
|
# handle all local read events and dispatch them to methods for |
|
|
|
# each stage |
|
|
|
self._update_activity() |
|
|
|
if not self._local_sock: |
|
|
|
return |
|
|
@ -372,15 +412,16 @@ class TCPRelayHandler(object): |
|
|
|
elif is_local and self._stage == STAGE_INIT: |
|
|
|
# TODO check auth method |
|
|
|
self._write_to_sock(b'\x05\00', self._local_sock) |
|
|
|
self._stage = STAGE_HELLO |
|
|
|
self._stage = STAGE_ADDR |
|
|
|
return |
|
|
|
elif self._stage == STAGE_REPLY: |
|
|
|
self._handle_stage_reply(data) |
|
|
|
elif (is_local and self._stage == STAGE_HELLO) or \ |
|
|
|
elif self._stage == STAGE_CONNECTING: |
|
|
|
self._handle_stage_connecting(data) |
|
|
|
elif (is_local and self._stage == STAGE_ADDR) or \ |
|
|
|
(not is_local and self._stage == STAGE_INIT): |
|
|
|
self._handle_stage_hello(data) |
|
|
|
self._handle_stage_addr(data) |
|
|
|
|
|
|
|
def _on_remote_read(self): |
|
|
|
# handle all remote read events |
|
|
|
self._update_activity() |
|
|
|
data = None |
|
|
|
try: |
|
|
@ -406,6 +447,7 @@ class TCPRelayHandler(object): |
|
|
|
self.destroy() |
|
|
|
|
|
|
|
def _on_local_write(self): |
|
|
|
# handle local writable event |
|
|
|
if self._data_to_write_to_local: |
|
|
|
data = b''.join(self._data_to_write_to_local) |
|
|
|
self._data_to_write_to_local = [] |
|
|
@ -414,6 +456,7 @@ class TCPRelayHandler(object): |
|
|
|
self._update_stream(STREAM_DOWN, WAIT_STATUS_READING) |
|
|
|
|
|
|
|
def _on_remote_write(self): |
|
|
|
# handle remote writable event |
|
|
|
self._stage = STAGE_STREAM |
|
|
|
if self._data_to_write_to_remote: |
|
|
|
data = b''.join(self._data_to_write_to_remote) |
|
|
@ -435,6 +478,7 @@ class TCPRelayHandler(object): |
|
|
|
self.destroy() |
|
|
|
|
|
|
|
def handle_event(self, sock, event): |
|
|
|
# handle all events in this handler and dispatch them to methods |
|
|
|
if self._stage == STAGE_DESTROYED: |
|
|
|
logging.debug('ignore handle_event: destroyed') |
|
|
|
return |
|
|
@ -465,7 +509,15 @@ class TCPRelayHandler(object): |
|
|
|
logging.warn('unknown socket') |
|
|
|
|
|
|
|
def destroy(self): |
|
|
|
# destroy the handler and release any resources |
|
|
|
# promises: |
|
|
|
# 1. destroy won't make another destroy() call inside |
|
|
|
# 2. destroy releases resources so it prevents future call to destroy |
|
|
|
# 3. destroy won't raise any exceptions |
|
|
|
# if any of the promises are broken, it indicates a bug have been |
|
|
|
# introduced! mostly likely memory leaks, etc |
|
|
|
if self._stage == STAGE_DESTROYED: |
|
|
|
# this couldn't happen |
|
|
|
logging.debug('already destroyed') |
|
|
|
return |
|
|
|
self._stage = STAGE_DESTROYED |
|
|
@ -552,7 +604,7 @@ class TCPRelay(object): |
|
|
|
del self._handler_to_timeouts[hash(handler)] |
|
|
|
|
|
|
|
def update_activity(self, handler): |
|
|
|
""" set handler to active """ |
|
|
|
# set handler to active |
|
|
|
now = int(time.time()) |
|
|
|
if now - handler.last_activity < TIMEOUT_PRECISION: |
|
|
|
# thus we can lower timeout modification frequency |
|
|
@ -601,6 +653,7 @@ class TCPRelay(object): |
|
|
|
self._timeout_offset = pos |
|
|
|
|
|
|
|
def _handle_events(self, events): |
|
|
|
# handle events and dispatch to handlers |
|
|
|
for sock, fd, event in events: |
|
|
|
if sock: |
|
|
|
logging.log(utils.VERBOSE_LEVEL, 'fd %d %s', fd, |
|
|
|