udp over tcp
This commit is contained in:
@@ -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:
|
||||
|
||||
+66
-17
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user