@@ -144,6 +144,8 @@ def parse_header(data): | |||
dest_addr = None | |||
dest_port = None | |||
header_length = 0 | |||
connecttype = (addrtype & 8) and 1 or 0 | |||
addrtype &= ~8 | |||
if addrtype == ADDRTYPE_IPV4: | |||
if len(data) >= 7: | |||
dest_addr = socket.inet_ntoa(data[1:5]) | |||
@@ -171,11 +173,11 @@ def parse_header(data): | |||
else: | |||
logging.warn('header is too short') | |||
else: | |||
logging.warn('unsupported addrtype %d, maybe wrong password or ' | |||
'encryption method' % addrtype) | |||
logging.warn('unsupported addrtype %d, maybe wrong password' % | |||
addrtype) | |||
if dest_addr is None: | |||
return None | |||
return addrtype, to_bytes(dest_addr), dest_port, header_length | |||
return connecttype, to_bytes(dest_addr), dest_port, header_length | |||
class IPNetwork(object): |
@@ -0,0 +1,135 @@ | |||
#!/usr/bin/env python | |||
# Copyright (c) 2014 clowwindy | |||
# | |||
# 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. | |||
from __future__ import absolute_import, division, print_function, \ | |||
with_statement | |||
import logging | |||
from ctypes import CDLL, c_char_p, c_int, c_ulonglong, byref, \ | |||
create_string_buffer, c_void_p | |||
__all__ = ['ciphers'] | |||
libsodium = None | |||
loaded = False | |||
buf_size = 2048 | |||
# for salsa20 and chacha20 | |||
BLOCK_SIZE = 64 | |||
def load_libsodium(): | |||
global loaded, libsodium, buf | |||
from ctypes.util import find_library | |||
for p in ('sodium',): | |||
libsodium_path = find_library(p) | |||
if libsodium_path: | |||
break | |||
else: | |||
raise Exception('libsodium not found') | |||
logging.info('loading libsodium from %s', libsodium_path) | |||
libsodium = CDLL(libsodium_path) | |||
libsodium.sodium_init.restype = c_int | |||
libsodium.crypto_stream_salsa20_xor_ic.restype = c_int | |||
libsodium.crypto_stream_salsa20_xor_ic.argtypes = (c_void_p, c_char_p, | |||
c_ulonglong, | |||
c_char_p, c_ulonglong, | |||
c_char_p) | |||
libsodium.crypto_stream_chacha20_xor_ic.restype = c_int | |||
libsodium.crypto_stream_chacha20_xor_ic.argtypes = (c_void_p, c_char_p, | |||
c_ulonglong, | |||
c_char_p, c_ulonglong, | |||
c_char_p) | |||
libsodium.sodium_init() | |||
buf = create_string_buffer(buf_size) | |||
loaded = True | |||
class Salsa20Crypto(object): | |||
def __init__(self, cipher_name, key, iv, op): | |||
if not loaded: | |||
load_libsodium() | |||
self.key = key | |||
self.iv = iv | |||
self.key_ptr = c_char_p(key) | |||
self.iv_ptr = c_char_p(iv) | |||
if cipher_name == b'salsa20': | |||
self.cipher = libsodium.crypto_stream_salsa20_xor_ic | |||
elif cipher_name == b'chacha20': | |||
self.cipher = libsodium.crypto_stream_chacha20_xor_ic | |||
else: | |||
raise Exception('Unknown cipher') | |||
# byte counter, not block counter | |||
self.counter = 0 | |||
def update(self, data): | |||
global buf_size, buf | |||
l = len(data) | |||
# we can only prepend some padding to make the encryption align to | |||
# blocks | |||
padding = self.counter % BLOCK_SIZE | |||
if buf_size < padding + l: | |||
buf_size = (padding + l) * 2 | |||
buf = create_string_buffer(buf_size) | |||
if padding: | |||
data = (b'\0' * padding) + data | |||
self.cipher(byref(buf), c_char_p(data), padding + l, | |||
self.iv_ptr, int(self.counter / BLOCK_SIZE), self.key_ptr) | |||
self.counter += l | |||
# buf is copied to a str object when we access buf.raw | |||
# strip off the padding | |||
return buf.raw[padding:padding + l] | |||
ciphers = { | |||
b'salsa20': (32, 8, Salsa20Crypto), | |||
b'chacha20': (32, 8, Salsa20Crypto), | |||
} | |||
def test_salsa20(): | |||
from shadowsocks.crypto import util | |||
cipher = Salsa20Crypto(b'salsa20', b'k' * 32, b'i' * 16, 1) | |||
decipher = Salsa20Crypto(b'salsa20', b'k' * 32, b'i' * 16, 0) | |||
util.run_cipher(cipher, decipher) | |||
def test_chacha20(): | |||
from shadowsocks.crypto import util | |||
cipher = Salsa20Crypto(b'chacha20', b'k' * 32, b'i' * 16, 1) | |||
decipher = Salsa20Crypto(b'chacha20', b'k' * 32, b'i' * 16, 0) | |||
util.run_cipher(cipher, decipher) | |||
if __name__ == '__main__': | |||
test_chacha20() | |||
test_salsa20() |
@@ -0,0 +1,188 @@ | |||
#!/usr/bin/env python | |||
# Copyright (c) 2014 clowwindy | |||
# | |||
# 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. | |||
from __future__ import absolute_import, division, print_function, \ | |||
with_statement | |||
import logging | |||
from ctypes import CDLL, c_char_p, c_int, c_long, byref,\ | |||
create_string_buffer, c_void_p | |||
__all__ = ['ciphers'] | |||
libcrypto = None | |||
loaded = False | |||
buf_size = 2048 | |||
def load_openssl(): | |||
global loaded, libcrypto, buf | |||
from ctypes.util import find_library | |||
for p in ('crypto', 'eay32', 'libeay32'): | |||
libcrypto_path = find_library(p) | |||
if libcrypto_path: | |||
break | |||
else: | |||
raise Exception('libcrypto(OpenSSL) not found') | |||
logging.info('loading libcrypto from %s', libcrypto_path) | |||
libcrypto = CDLL(libcrypto_path) | |||
libcrypto.EVP_get_cipherbyname.restype = c_void_p | |||
libcrypto.EVP_CIPHER_CTX_new.restype = c_void_p | |||
libcrypto.EVP_CipherInit_ex.argtypes = (c_void_p, c_void_p, c_char_p, | |||
c_char_p, c_char_p, c_int) | |||
libcrypto.EVP_CipherUpdate.argtypes = (c_void_p, c_void_p, c_void_p, | |||
c_char_p, c_int) | |||
libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,) | |||
libcrypto.EVP_CIPHER_CTX_free.argtypes = (c_void_p,) | |||
if hasattr(libcrypto, 'OpenSSL_add_all_ciphers'): | |||
libcrypto.OpenSSL_add_all_ciphers() | |||
buf = create_string_buffer(buf_size) | |||
loaded = True | |||
def load_cipher(cipher_name): | |||
func_name = b'EVP_' + cipher_name.replace(b'-', b'_') | |||
if bytes != str: | |||
func_name = str(func_name, 'utf-8') | |||
cipher = getattr(libcrypto, func_name, None) | |||
if cipher: | |||
cipher.restype = c_void_p | |||
return cipher() | |||
return None | |||
class CtypesCrypto(object): | |||
def __init__(self, cipher_name, key, iv, op): | |||
if not loaded: | |||
load_openssl() | |||
self._ctx = None | |||
cipher = libcrypto.EVP_get_cipherbyname(cipher_name) | |||
if not cipher: | |||
cipher = load_cipher(cipher_name) | |||
if not cipher: | |||
raise Exception('cipher %s not found in libcrypto' % cipher_name) | |||
key_ptr = c_char_p(key) | |||
iv_ptr = c_char_p(iv) | |||
self._ctx = libcrypto.EVP_CIPHER_CTX_new() | |||
if not self._ctx: | |||
raise Exception('can not create cipher context') | |||
r = libcrypto.EVP_CipherInit_ex(self._ctx, cipher, None, | |||
key_ptr, iv_ptr, c_int(op)) | |||
if not r: | |||
self.clean() | |||
raise Exception('can not initialize cipher context') | |||
def update(self, data): | |||
global buf_size, buf | |||
cipher_out_len = c_long(0) | |||
l = len(data) | |||
if buf_size < l: | |||
buf_size = l * 2 | |||
buf = create_string_buffer(buf_size) | |||
libcrypto.EVP_CipherUpdate(self._ctx, byref(buf), | |||
byref(cipher_out_len), c_char_p(data), l) | |||
# buf is copied to a str object when we access buf.raw | |||
return buf.raw[:cipher_out_len.value] | |||
def __del__(self): | |||
self.clean() | |||
def clean(self): | |||
if self._ctx: | |||
libcrypto.EVP_CIPHER_CTX_cleanup(self._ctx) | |||
libcrypto.EVP_CIPHER_CTX_free(self._ctx) | |||
ciphers = { | |||
b'aes-128-cfb': (16, 16, CtypesCrypto), | |||
b'aes-192-cfb': (24, 16, CtypesCrypto), | |||
b'aes-256-cfb': (32, 16, CtypesCrypto), | |||
b'aes-128-ofb': (16, 16, CtypesCrypto), | |||
b'aes-192-ofb': (24, 16, CtypesCrypto), | |||
b'aes-256-ofb': (32, 16, CtypesCrypto), | |||
b'aes-128-ctr': (16, 16, CtypesCrypto), | |||
b'aes-192-ctr': (24, 16, CtypesCrypto), | |||
b'aes-256-ctr': (32, 16, CtypesCrypto), | |||
b'aes-128-cfb8': (16, 16, CtypesCrypto), | |||
b'aes-192-cfb8': (24, 16, CtypesCrypto), | |||
b'aes-256-cfb8': (32, 16, CtypesCrypto), | |||
b'aes-128-cfb1': (16, 16, CtypesCrypto), | |||
b'aes-192-cfb1': (24, 16, CtypesCrypto), | |||
b'aes-256-cfb1': (32, 16, CtypesCrypto), | |||
b'bf-cfb': (16, 8, CtypesCrypto), | |||
b'camellia-128-cfb': (16, 16, CtypesCrypto), | |||
b'camellia-192-cfb': (24, 16, CtypesCrypto), | |||
b'camellia-256-cfb': (32, 16, CtypesCrypto), | |||
b'cast5-cfb': (16, 8, CtypesCrypto), | |||
b'des-cfb': (8, 8, CtypesCrypto), | |||
b'idea-cfb': (16, 8, CtypesCrypto), | |||
b'rc2-cfb': (16, 8, CtypesCrypto), | |||
b'rc4': (16, 0, CtypesCrypto), | |||
b'seed-cfb': (16, 16, CtypesCrypto), | |||
} | |||
def run_method(method): | |||
from shadowsocks.crypto import util | |||
cipher = CtypesCrypto(method, b'k' * 32, b'i' * 16, 1) | |||
decipher = CtypesCrypto(method, b'k' * 32, b'i' * 16, 0) | |||
util.run_cipher(cipher, decipher) | |||
def test_aes_128_cfb(): | |||
run_method(b'aes-128-cfb') | |||
def test_aes_256_cfb(): | |||
run_method(b'aes-256-cfb') | |||
def test_aes_128_cfb8(): | |||
run_method(b'aes-128-cfb8') | |||
def test_aes_256_ofb(): | |||
run_method(b'aes-256-ofb') | |||
def test_aes_256_ctr(): | |||
run_method(b'aes-256-ctr') | |||
def test_bf_cfb(): | |||
run_method(b'bf-cfb') | |||
def test_rc4(): | |||
run_method(b'rc4') | |||
if __name__ == '__main__': | |||
test_aes_128_cfb() |
@@ -0,0 +1,117 @@ | |||
#!/usr/bin/env python | |||
# Copyright (c) 2014 clowwindy | |||
# | |||
# 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. | |||
from __future__ import absolute_import, division, print_function, \ | |||
with_statement | |||
import sys | |||
import logging | |||
__all__ = ['ciphers'] | |||
has_m2 = True | |||
try: | |||
__import__('M2Crypto') | |||
except ImportError: | |||
has_m2 = False | |||
def create_cipher(alg, key, iv, op, key_as_bytes=0, d=None, salt=None, i=1, | |||
padding=1): | |||
import M2Crypto.EVP | |||
return M2Crypto.EVP.Cipher(alg.replace('-', '_'), key, iv, op, | |||
key_as_bytes=0, d='md5', salt=None, i=1, | |||
padding=1) | |||
def err(alg, key, iv, op, key_as_bytes=0, d=None, salt=None, i=1, padding=1): | |||
logging.error(('M2Crypto is required to use %s, please run' | |||
' `apt-get install python-m2crypto`') % alg) | |||
sys.exit(1) | |||
if has_m2: | |||
ciphers = { | |||
b'aes-128-cfb': (16, 16, create_cipher), | |||
b'aes-192-cfb': (24, 16, create_cipher), | |||
b'aes-256-cfb': (32, 16, create_cipher), | |||
b'bf-cfb': (16, 8, create_cipher), | |||
b'camellia-128-cfb': (16, 16, create_cipher), | |||
b'camellia-192-cfb': (24, 16, create_cipher), | |||
b'camellia-256-cfb': (32, 16, create_cipher), | |||
b'cast5-cfb': (16, 8, create_cipher), | |||
b'des-cfb': (8, 8, create_cipher), | |||
b'idea-cfb': (16, 8, create_cipher), | |||
b'rc2-cfb': (16, 8, create_cipher), | |||
b'rc4': (16, 0, create_cipher), | |||
b'seed-cfb': (16, 16, create_cipher), | |||
} | |||
else: | |||
ciphers = {} | |||
def run_method(method): | |||
from shadowsocks.crypto import util | |||
cipher = create_cipher(method, b'k' * 32, b'i' * 16, 1) | |||
decipher = create_cipher(method, b'k' * 32, b'i' * 16, 0) | |||
util.run_cipher(cipher, decipher) | |||
def check_env(): | |||
# skip this test on pypy and Python 3 | |||
try: | |||
import __pypy__ | |||
del __pypy__ | |||
from nose.plugins.skip import SkipTest | |||
raise SkipTest | |||
except ImportError: | |||
pass | |||
if bytes != str: | |||
from nose.plugins.skip import SkipTest | |||
raise SkipTest | |||
def test_aes_128_cfb(): | |||
check_env() | |||
run_method(b'aes-128-cfb') | |||
def test_aes_256_cfb(): | |||
check_env() | |||
run_method(b'aes-256-cfb') | |||
def test_bf_cfb(): | |||
check_env() | |||
run_method(b'bf-cfb') | |||
def test_rc4(): | |||
check_env() | |||
run_method(b'rc4') | |||
if __name__ == '__main__': | |||
test_aes_128_cfb() |
@@ -47,6 +47,8 @@ def try_cipher(key, method=None): | |||
def EVP_BytesToKey(password, key_len, iv_len): | |||
# equivalent to OpenSSL's EVP_BytesToKey() with count 1 | |||
# so that we make the same key and iv as nodejs version | |||
if hasattr(password, 'encode'): | |||
password = password.encode('utf-8') | |||
cached_key = '%s-%d-%d' % (password, key_len, iv_len) | |||
r = cached_keys.get(cached_key, None) | |||
if r: |
@@ -101,6 +101,7 @@ class TCPRelayHandler(object): | |||
self._loop = loop | |||
self._local_sock = local_sock | |||
self._remote_sock = None | |||
self._remote_udp = False | |||
self._config = config | |||
self._dns_resolver = dns_resolver | |||
@@ -190,6 +191,27 @@ class TCPRelayHandler(object): | |||
# and update the stream to wait for writing | |||
if not data or not sock: | |||
return False | |||
#logging.debug("_write_to_sock %s %s %s" % (self._remote_sock, sock, self._remote_udp)) | |||
if self._remote_sock == sock and self._remote_udp: | |||
try: | |||
#TODO | |||
data = data[3:] | |||
header_result = parse_header(data) | |||
if header_result is None: | |||
return False | |||
connecttype, dest_addr, dest_port, header_length = header_result | |||
addrs = socket.getaddrinfo(dest_addr, dest_port, 0, | |||
socket.SOCK_DGRAM, socket.SOL_UDP) | |||
if addrs: | |||
af, socktype, proto, canonname, server_addr = addrs[0] | |||
data = data[header_length:] | |||
sock.sendto(data, server_addr) | |||
except Exception as e: | |||
trace = traceback.format_exc() | |||
logging.error(trace) | |||
return True | |||
uncomplete = False | |||
try: | |||
l = len(data) | |||
@@ -203,6 +225,7 @@ class TCPRelayHandler(object): | |||
errno.EWOULDBLOCK): | |||
uncomplete = True | |||
else: | |||
#traceback.print_exc() | |||
shell.print_exception(e) | |||
self.destroy() | |||
return False | |||
@@ -291,11 +314,13 @@ class TCPRelayHandler(object): | |||
header_result = parse_header(data) | |||
if header_result is None: | |||
raise Exception('can not parse header') | |||
addrtype, remote_addr, remote_port, header_length = header_result | |||
logging.info('connecting %s:%d from %s:%d' % | |||
(common.to_str(remote_addr), remote_port, | |||
self._client_address[0], self._client_address[1])) | |||
connecttype, remote_addr, remote_port, header_length = header_result | |||
logging.info('%s connecting %s:%d from %s:%d' % | |||
((connecttype == 0) and 'tcp' or 'udp', | |||
common.to_str(remote_addr), remote_port, | |||
self._client_address[0], self._client_address[1])) | |||
self._remote_address = (common.to_str(remote_addr), remote_port) | |||
self._remote_udp = (connecttype != 0) | |||
# pause reading | |||
self._update_stream(STREAM_UP, WAIT_STATUS_WRITING) | |||
self._stage = STAGE_DNS | |||
@@ -323,8 +348,10 @@ class TCPRelayHandler(object): | |||
self.destroy() | |||
def _create_remote_socket(self, ip, port): | |||
addrs = socket.getaddrinfo(ip, port, 0, socket.SOCK_STREAM, | |||
socket.SOL_TCP) | |||
if self._remote_udp: | |||
addrs = socket.getaddrinfo(ip, port, 0, socket.SOCK_DGRAM, socket.SOL_UDP) | |||
else: | |||
addrs = socket.getaddrinfo(ip, port, 0, socket.SOCK_STREAM, socket.SOL_TCP) | |||
if len(addrs) == 0: | |||
raise Exception("getaddrinfo failed for %s:%d" % (ip, port)) | |||
af, socktype, proto, canonname, sa = addrs[0] | |||
@@ -336,7 +363,10 @@ class TCPRelayHandler(object): | |||
self._remote_sock = remote_sock | |||
self._fd_to_handlers[remote_sock.fileno()] = self | |||
remote_sock.setblocking(False) | |||
remote_sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) | |||
if self._remote_udp: | |||
pass | |||
else: | |||
remote_sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) | |||
return remote_sock | |||
def _handle_dns_resolved(self, result, error): | |||
@@ -368,14 +398,18 @@ class TCPRelayHandler(object): | |||
# else do connect | |||
remote_sock = self._create_remote_socket(remote_addr, | |||
remote_port) | |||
try: | |||
remote_sock.connect((remote_addr, remote_port)) | |||
except (OSError, IOError) as e: | |||
if eventloop.errno_from_exception(e) == \ | |||
errno.EINPROGRESS: | |||
pass | |||
self._loop.add(remote_sock, | |||
eventloop.POLL_ERR | eventloop.POLL_OUT) | |||
if self._remote_udp: | |||
self._loop.add(remote_sock, | |||
eventloop.POLL_IN) | |||
else: | |||
try: | |||
remote_sock.connect((remote_addr, remote_port)) | |||
except (OSError, IOError) as e: | |||
if eventloop.errno_from_exception(e) == \ | |||
errno.EINPROGRESS: | |||
pass | |||
self._loop.add(remote_sock, | |||
eventloop.POLL_ERR | eventloop.POLL_OUT) | |||
self._stage = STAGE_CONNECTING | |||
self._update_stream(STREAM_UP, WAIT_STATUS_READWRITING) | |||
self._update_stream(STREAM_DOWN, WAIT_STATUS_READING) | |||
@@ -407,6 +441,7 @@ class TCPRelayHandler(object): | |||
data = self._encryptor.decrypt(data) | |||
if not data: | |||
return | |||
self._server.server_transfer_ul += len(data) | |||
if self._stage == STAGE_STREAM: | |||
if self._is_local: | |||
data = self._encryptor.encrypt(data) | |||
@@ -428,14 +463,26 @@ class TCPRelayHandler(object): | |||
self._update_activity() | |||
data = None | |||
try: | |||
data = self._remote_sock.recv(BUF_SIZE) | |||
if self._remote_udp: | |||
data, addr = self._remote_sock.recvfrom(BUF_SIZE) | |||
port = struct.pack('>H', addr[1]) | |||
try: | |||
ip = socket.inet_aton(addr[0]) | |||
data = '\x00\x00\x00\x01' + ip + port + data | |||
except Exception as e: | |||
ip = socket.inet_pton(socket.AF_INET6, addr[0]) | |||
data = '\x00\x00\x00\x04' + ip + port + data | |||
logging.info('udp recvfrom %s:%d %d bytes to %s:%d' % (addr[0], addr[1], len(data), self._client_address[0], self._client_address[1])) | |||
else: | |||
data = self._remote_sock.recv(BUF_SIZE) | |||
except (OSError, IOError) as e: | |||
if eventloop.errno_from_exception(e) in \ | |||
(errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK): | |||
(errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK, 10035): #errno.WSAEWOULDBLOCK | |||
return | |||
if not data: | |||
self.destroy() | |||
return | |||
self._server.server_transfer_dl += len(data) | |||
if self._is_local: | |||
data = self._encryptor.decrypt(data) | |||
else: | |||
@@ -558,6 +605,8 @@ class TCPRelay(object): | |||
self._eventloop = None | |||
self._fd_to_handlers = {} | |||
self._last_time = time.time() | |||
self.server_transfer_ul = 0L | |||
self.server_transfer_dl = 0L | |||
self._timeout = config['timeout'] | |||
self._timeouts = [] # a list for all the handlers |
@@ -162,7 +162,7 @@ class UDPRelay(object): | |||
header_result = parse_header(data) | |||
if header_result is None: | |||
return | |||
addrtype, dest_addr, dest_port, header_length = header_result | |||
connecttype, dest_addr, dest_port, header_length = header_result | |||
if self._is_local: | |||
server_addr, server_port = self._get_a_server() | |||
@@ -173,6 +173,7 @@ class UDPRelay(object): | |||
client = self._cache.get(key, None) | |||
if not client: | |||
# TODO async getaddrinfo | |||
logging.info('UDP handle_server %s:%d from %s:%d' % (common.to_str(server_addr), server_port, self._listen_addr, self._listen_port)) | |||
addrs = socket.getaddrinfo(server_addr, server_port, 0, | |||
socket.SOCK_DGRAM, socket.SOL_UDP) | |||
if addrs: | |||
@@ -233,7 +234,7 @@ class UDPRelay(object): | |||
header_result = parse_header(data) | |||
if header_result is None: | |||
return | |||
# addrtype, dest_addr, dest_port, header_length = header_result | |||
# connecttype, dest_addr, dest_port, header_length = header_result | |||
response = b'\x00\x00\x00' + data | |||
client_addr = self._client_fd_to_server_addr.get(sock.fileno()) | |||
if client_addr: |