Browse Source

python 3 support; not stable yet

auth
clowwindy 10 years ago
parent
commit
2a53b67c65
  1. 23
      shadowsocks/__init__.py
  2. 43
      shadowsocks/asyncdns.py
  3. 37
      shadowsocks/common.py
  4. 3
      shadowsocks/crypto/__init__.py
  5. 57
      shadowsocks/crypto/ctypes_openssl.py
  6. 29
      shadowsocks/crypto/m2.py
  7. 4
      shadowsocks/crypto/rc4_md5.py
  8. 13
      shadowsocks/crypto/salsa20_ctr.py
  9. 21
      shadowsocks/encrypt.py
  10. 4
      shadowsocks/eventloop.py
  11. 4
      shadowsocks/local.py
  12. 3
      shadowsocks/lru_cache.py
  13. 10
      shadowsocks/server.py
  14. 17
      shadowsocks/tcprelay.py
  15. 6
      shadowsocks/udprelay.py
  16. 34
      shadowsocks/utils.py
  17. 7
      tests/test.py

23
shadowsocks/__init__.py

@ -1 +1,24 @@
#!/usr/bin/python #!/usr/bin/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

43
shadowsocks/asyncdns.py

@ -21,6 +21,9 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from __future__ import absolute_import, division, print_function, \
with_statement
import time import time
import os import os
import socket import socket
@ -33,7 +36,7 @@ from shadowsocks import common, lru_cache, eventloop
CACHE_SWEEP_INTERVAL = 30 CACHE_SWEEP_INTERVAL = 30
VALID_HOSTNAME = re.compile(r"(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE) VALID_HOSTNAME = re.compile(br"(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
common.patch_socket() common.patch_socket()
@ -77,17 +80,17 @@ QCLASS_IN = 1
def build_address(address): def build_address(address):
address = address.strip('.') address = address.strip(b'.')
labels = address.split('.') labels = address.split(b'.')
results = [] results = []
for label in labels: for label in labels:
l = len(label) l = len(label)
if l > 63: if l > 63:
return None return None
results.append(chr(l)) results.append(common.chr(l))
results.append(label) results.append(label)
results.append('\0') results.append(b'\0')
return ''.join(results) return b''.join(results)
def build_request(address, qtype, request_id): def build_request(address, qtype, request_id):
@ -111,7 +114,7 @@ def parse_ip(addrtype, data, length, offset):
def parse_name(data, offset): def parse_name(data, offset):
p = offset p = offset
labels = [] labels = []
l = ord(data[p]) l = common.ord(data[p])
while l > 0: while l > 0:
if (l & (128 + 64)) == (128 + 64): if (l & (128 + 64)) == (128 + 64):
# pointer # pointer
@ -121,12 +124,12 @@ def parse_name(data, offset):
labels.append(r[1]) labels.append(r[1])
p += 2 p += 2
# pointer is the end # pointer is the end
return p - offset, '.'.join(labels) return p - offset, b'.'.join(labels)
else: else:
labels.append(data[p + 1:p + 1 + l]) labels.append(data[p + 1:p + 1 + l])
p += 1 + l p += 1 + l
l = ord(data[p]) l = common.ord(data[p])
return p - offset + 1, '.'.join(labels) return p - offset + 1, b'.'.join(labels)
# rfc1035 # rfc1035
@ -198,20 +201,20 @@ def parse_response(data):
qds = [] qds = []
ans = [] ans = []
offset = 12 offset = 12
for i in xrange(0, res_qdcount): for i in range(0, res_qdcount):
l, r = parse_record(data, offset, True) l, r = parse_record(data, offset, True)
offset += l offset += l
if r: if r:
qds.append(r) qds.append(r)
for i in xrange(0, res_ancount): for i in range(0, res_ancount):
l, r = parse_record(data, offset) l, r = parse_record(data, offset)
offset += l offset += l
if r: if r:
ans.append(r) ans.append(r)
for i in xrange(0, res_nscount): for i in range(0, res_nscount):
l, r = parse_record(data, offset) l, r = parse_record(data, offset)
offset += l offset += l
for i in xrange(0, res_arcount): for i in range(0, res_arcount):
l, r = parse_record(data, offset) l, r = parse_record(data, offset)
offset += l offset += l
response = DNSResponse() response = DNSResponse()
@ -232,6 +235,8 @@ def parse_response(data):
def is_ip(address): def is_ip(address):
for family in (socket.AF_INET, socket.AF_INET6): for family in (socket.AF_INET, socket.AF_INET6):
try: try:
if type(address) != str:
address = address.decode('utf8')
socket.inet_pton(family, address) socket.inet_pton(family, address)
return family return family
except (TypeError, ValueError, OSError, IOError): except (TypeError, ValueError, OSError, IOError):
@ -242,9 +247,9 @@ def is_ip(address):
def is_valid_hostname(hostname): def is_valid_hostname(hostname):
if len(hostname) > 255: if len(hostname) > 255:
return False return False
if hostname[-1] == ".": if hostname[-1] == b'.':
hostname = hostname[:-1] hostname = hostname[:-1]
return all(VALID_HOSTNAME.match(x) for x in hostname.split(".")) return all(VALID_HOSTNAME.match(x) for x in hostname.split(b'.'))
class DNSResponse(object): class DNSResponse(object):
@ -287,11 +292,13 @@ class DNSResolver(object):
for line in content: for line in content:
line = line.strip() line = line.strip()
if line: if line:
if line.startswith('nameserver'): if line.startswith(b'nameserver'):
parts = line.split() parts = line.split()
if len(parts) >= 2: if len(parts) >= 2:
server = parts[1] server = parts[1]
if is_ip(server) == socket.AF_INET: if is_ip(server) == socket.AF_INET:
if type(server) != str:
server = server.decode('utf8')
self._servers.append(server) self._servers.append(server)
except IOError: except IOError:
pass pass
@ -310,7 +317,7 @@ class DNSResolver(object):
if len(parts) >= 2: if len(parts) >= 2:
ip = parts[0] ip = parts[0]
if is_ip(ip): if is_ip(ip):
for i in xrange(1, len(parts)): for i in range(1, len(parts)):
hostname = parts[i] hostname = parts[i]
if hostname: if hostname:
self._hosts[hostname] = ip self._hosts[hostname] = ip

37
shadowsocks/common.py

@ -21,16 +21,37 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from __future__ import absolute_import, division, print_function, \
with_statement
import socket import socket
import struct import struct
import logging import logging
def compat_ord(s):
if type(s) == int:
return s
return _ord(s)
def compat_chr(d):
if bytes == str:
return _chr(d)
return bytes([d])
_ord = ord
_chr = chr
ord = compat_ord
chr = compat_chr
def inet_ntop(family, ipstr): def inet_ntop(family, ipstr):
if family == socket.AF_INET: if family == socket.AF_INET:
return socket.inet_ntoa(ipstr) return socket.inet_ntoa(ipstr)
elif family == socket.AF_INET6: elif family == socket.AF_INET6:
v6addr = ':'.join(('%02X%02X' % (ord(i), ord(j))) v6addr = b':'.join((b'%02X%02X' % (ord(i), ord(j)))
for i, j in zip(ipstr[::2], ipstr[1::2])) for i, j in zip(ipstr[::2], ipstr[1::2]))
return v6addr return v6addr
@ -39,15 +60,15 @@ def inet_pton(family, addr):
if family == socket.AF_INET: if family == socket.AF_INET:
return socket.inet_aton(addr) return socket.inet_aton(addr)
elif family == socket.AF_INET6: elif family == socket.AF_INET6:
if '.' in addr: # a v4 addr if b'.' in addr: # a v4 addr
v4addr = addr[addr.rindex(':') + 1:] v4addr = addr[addr.rindex(b':') + 1:]
v4addr = socket.inet_aton(v4addr) v4addr = socket.inet_aton(v4addr)
v4addr = map(lambda x: ('%02X' % ord(x)), v4addr) v4addr = map(lambda x: (b'%02X' % ord(x)), v4addr)
v4addr.insert(2, ':') v4addr.insert(2, b':')
newaddr = addr[:addr.rindex(':') + 1] + ''.join(v4addr) newaddr = addr[:addr.rindex(b':') + 1] + b''.join(v4addr)
return inet_pton(family, newaddr) return inet_pton(family, newaddr)
dbyts = [0] * 8 # 8 groups dbyts = [0] * 8 # 8 groups
grps = addr.split(':') grps = addr.split(b':')
for i, v in enumerate(grps): for i, v in enumerate(grps):
if v: if v:
dbyts[i] = int(v, 16) dbyts[i] = int(v, 16)
@ -58,7 +79,7 @@ def inet_pton(family, addr):
else: else:
break break
break break
return ''.join((chr(i // 256) + chr(i % 256)) for i in dbyts) return b''.join((chr(i // 256) + chr(i % 256)) for i in dbyts)
else: else:
raise RuntimeError("What family?") raise RuntimeError("What family?")

3
shadowsocks/crypto/__init__.py

@ -19,3 +19,6 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # 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 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from __future__ import absolute_import, division, print_function, \
with_statement

57
shadowsocks/crypto/ctypes_openssl.py

@ -20,6 +20,9 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from __future__ import absolute_import, division, print_function, \
with_statement
import logging import logging
from ctypes import CDLL, c_char_p, c_int, c_long, byref,\ from ctypes import CDLL, c_char_p, c_int, c_long, byref,\
create_string_buffer, c_void_p create_string_buffer, c_void_p
@ -117,31 +120,31 @@ class CtypesCrypto(object):
ciphers = { ciphers = {
'aes-128-cfb': (16, 16, CtypesCrypto), b'aes-128-cfb': (16, 16, CtypesCrypto),
'aes-192-cfb': (24, 16, CtypesCrypto), b'aes-192-cfb': (24, 16, CtypesCrypto),
'aes-256-cfb': (32, 16, CtypesCrypto), b'aes-256-cfb': (32, 16, CtypesCrypto),
'aes-128-ofb': (16, 16, CtypesCrypto), b'aes-128-ofb': (16, 16, CtypesCrypto),
'aes-192-ofb': (24, 16, CtypesCrypto), b'aes-192-ofb': (24, 16, CtypesCrypto),
'aes-256-ofb': (32, 16, CtypesCrypto), b'aes-256-ofb': (32, 16, CtypesCrypto),
'aes-128-ctr': (16, 16, CtypesCrypto), b'aes-128-ctr': (16, 16, CtypesCrypto),
'aes-192-ctr': (24, 16, CtypesCrypto), b'aes-192-ctr': (24, 16, CtypesCrypto),
'aes-256-ctr': (32, 16, CtypesCrypto), b'aes-256-ctr': (32, 16, CtypesCrypto),
'aes-128-cfb8': (16, 16, CtypesCrypto), b'aes-128-cfb8': (16, 16, CtypesCrypto),
'aes-192-cfb8': (24, 16, CtypesCrypto), b'aes-192-cfb8': (24, 16, CtypesCrypto),
'aes-256-cfb8': (32, 16, CtypesCrypto), b'aes-256-cfb8': (32, 16, CtypesCrypto),
'aes-128-cfb1': (16, 16, CtypesCrypto), b'aes-128-cfb1': (16, 16, CtypesCrypto),
'aes-192-cfb1': (24, 16, CtypesCrypto), b'aes-192-cfb1': (24, 16, CtypesCrypto),
'aes-256-cfb1': (32, 16, CtypesCrypto), b'aes-256-cfb1': (32, 16, CtypesCrypto),
'bf-cfb': (16, 8, CtypesCrypto), b'bf-cfb': (16, 8, CtypesCrypto),
'camellia-128-cfb': (16, 16, CtypesCrypto), b'camellia-128-cfb': (16, 16, CtypesCrypto),
'camellia-192-cfb': (24, 16, CtypesCrypto), b'camellia-192-cfb': (24, 16, CtypesCrypto),
'camellia-256-cfb': (32, 16, CtypesCrypto), b'camellia-256-cfb': (32, 16, CtypesCrypto),
'cast5-cfb': (16, 8, CtypesCrypto), b'cast5-cfb': (16, 8, CtypesCrypto),
'des-cfb': (8, 8, CtypesCrypto), b'des-cfb': (8, 8, CtypesCrypto),
'idea-cfb': (16, 8, CtypesCrypto), b'idea-cfb': (16, 8, CtypesCrypto),
'rc2-cfb': (16, 8, CtypesCrypto), b'rc2-cfb': (16, 8, CtypesCrypto),
'rc4': (16, 0, CtypesCrypto), b'rc4': (16, 0, CtypesCrypto),
'seed-cfb': (16, 16, CtypesCrypto), b'seed-cfb': (16, 16, CtypesCrypto),
} }
@ -167,7 +170,7 @@ def test():
# decipher = Salsa20Cipher('salsa20-ctr', 'k' * 32, 'i' * 8, 1) # decipher = Salsa20Cipher('salsa20-ctr', 'k' * 32, 'i' * 8, 1)
results = [] results = []
pos = 0 pos = 0
print 'salsa20 test start' print('salsa20 test start')
start = time.time() start = time.time()
while pos < len(plain): while pos < len(plain):
l = random.randint(100, 32768) l = random.randint(100, 32768)
@ -182,7 +185,7 @@ def test():
results.append(decipher.update(c[pos:pos + l])) results.append(decipher.update(c[pos:pos + l]))
pos += l pos += l
end = time.time() end = time.time()
print 'speed: %d bytes/s' % (BLOCK_SIZE * rounds / (end - start)) print('speed: %d bytes/s' % (BLOCK_SIZE * rounds / (end - start)))
assert ''.join(results) == plain assert ''.join(results) == plain

29
shadowsocks/crypto/m2.py

@ -20,6 +20,9 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from __future__ import absolute_import, division, print_function, \
with_statement
import sys import sys
import logging import logging
@ -49,19 +52,19 @@ def err(alg, key, iv, op, key_as_bytes=0, d=None, salt=None, i=1, padding=1):
if has_m2: if has_m2:
ciphers = { ciphers = {
'aes-128-cfb': (16, 16, create_cipher), b'aes-128-cfb': (16, 16, create_cipher),
'aes-192-cfb': (24, 16, create_cipher), b'aes-192-cfb': (24, 16, create_cipher),
'aes-256-cfb': (32, 16, create_cipher), b'aes-256-cfb': (32, 16, create_cipher),
'bf-cfb': (16, 8, create_cipher), b'bf-cfb': (16, 8, create_cipher),
'camellia-128-cfb': (16, 16, create_cipher), b'camellia-128-cfb': (16, 16, create_cipher),
'camellia-192-cfb': (24, 16, create_cipher), b'camellia-192-cfb': (24, 16, create_cipher),
'camellia-256-cfb': (32, 16, create_cipher), b'camellia-256-cfb': (32, 16, create_cipher),
'cast5-cfb': (16, 8, create_cipher), b'cast5-cfb': (16, 8, create_cipher),
'des-cfb': (8, 8, create_cipher), b'des-cfb': (8, 8, create_cipher),
'idea-cfb': (16, 8, create_cipher), b'idea-cfb': (16, 8, create_cipher),
'rc2-cfb': (16, 8, create_cipher), b'rc2-cfb': (16, 8, create_cipher),
'rc4': (16, 0, create_cipher), b'rc4': (16, 0, create_cipher),
'seed-cfb': (16, 16, create_cipher), b'seed-cfb': (16, 16, create_cipher),
} }
else: else:
ciphers = {} ciphers = {}

4
shadowsocks/crypto/rc4_md5.py

@ -20,6 +20,8 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from __future__ import absolute_import, division, print_function, \
with_statement
import hashlib import hashlib
@ -50,5 +52,5 @@ def create_cipher(alg, key, iv, op, key_as_bytes=0, d=None, salt=None,
ciphers = { ciphers = {
'rc4-md5': (16, 16, create_cipher), b'rc4-md5': (16, 16, create_cipher),
} }

13
shadowsocks/crypto/salsa20_ctr.py

@ -20,6 +20,9 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from __future__ import absolute_import, division, print_function, \
with_statement
import time import time
import struct import struct
import logging import logging
@ -39,13 +42,13 @@ def run_imports():
if not imported: if not imported:
imported = True imported = True
try: try:
__import__('numpy') numpy = __import__('numpy')
except ImportError: except ImportError:
logging.error('can not import numpy, using SLOW XOR') logging.error('can not import numpy, using SLOW XOR')
logging.error('please install numpy if you use salsa20') logging.error('please install numpy if you use salsa20')
slow_xor = True slow_xor = True
try: try:
__import__('salsa20') salsa20 = __import__('salsa20')
except ImportError: except ImportError:
logging.error('you have to install salsa20 before you use salsa20') logging.error('you have to install salsa20 before you use salsa20')
sys.exit(1) sys.exit(1)
@ -116,7 +119,7 @@ class Salsa20Cipher(object):
ciphers = { ciphers = {
'salsa20-ctr': (32, 8, Salsa20Cipher), b'salsa20-ctr': (32, 8, Salsa20Cipher),
} }
@ -138,7 +141,7 @@ def test():
decipher = Salsa20Cipher('salsa20-ctr', 'k' * 32, 'i' * 8, 1) decipher = Salsa20Cipher('salsa20-ctr', 'k' * 32, 'i' * 8, 1)
results = [] results = []
pos = 0 pos = 0
print 'salsa20 test start' print('salsa20 test start')
start = time.time() start = time.time()
while pos < len(plain): while pos < len(plain):
l = random.randint(100, 32768) l = random.randint(100, 32768)
@ -153,7 +156,7 @@ def test():
results.append(decipher.update(c[pos:pos + l])) results.append(decipher.update(c[pos:pos + l]))
pos += l pos += l
end = time.time() end = time.time()
print 'speed: %d bytes/s' % (BLOCK_SIZE * rounds / (end - start)) print('speed: %d bytes/s' % (BLOCK_SIZE * rounds / (end - start)))
assert ''.join(results) == plain assert ''.join(results) == plain

21
shadowsocks/encrypt.py

@ -20,6 +20,9 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from __future__ import absolute_import, division, print_function, \
with_statement
import os import os
import sys import sys
import hashlib import hashlib
@ -74,23 +77,20 @@ def init_table(key, method=None):
string.maketrans('', '')) string.maketrans('', ''))
cached_tables[key] = [encrypt_table, decrypt_table] cached_tables[key] = [encrypt_table, decrypt_table]
else: else:
try:
Encryptor(key, method) # test if the settings if OK Encryptor(key, method) # test if the settings if OK
except Exception as e:
logging.error(e)
sys.exit(1)
def EVP_BytesToKey(password, key_len, iv_len): def EVP_BytesToKey(password, key_len, iv_len):
# equivalent to OpenSSL's EVP_BytesToKey() with count 1 # equivalent to OpenSSL's EVP_BytesToKey() with count 1
# so that we make the same key and iv as nodejs version # so that we make the same key and iv as nodejs version
password = str(password) if hasattr(password, 'encode'):
password = password.encode('utf-8')
r = cached_keys.get(password, None) r = cached_keys.get(password, None)
if r: if r:
return r return r
m = [] m = []
i = 0 i = 0
while len(''.join(m)) < (key_len + iv_len): while len(b''.join(m)) < (key_len + iv_len):
md5 = hashlib.md5() md5 = hashlib.md5()
data = password data = password
if i > 0: if i > 0:
@ -98,7 +98,7 @@ def EVP_BytesToKey(password, key_len, iv_len):
md5.update(data) md5.update(data)
m.append(md5.digest()) m.append(md5.digest())
i += 1 i += 1
ms = ''.join(m) ms = b''.join(m)
key = ms[:key_len] key = ms[:key_len]
iv = ms[key_len:key_len + iv_len] iv = ms[key_len:key_len + iv_len]
cached_keys[password] = (key, iv) cached_keys[password] = (key, iv)
@ -107,13 +107,13 @@ def EVP_BytesToKey(password, key_len, iv_len):
class Encryptor(object): class Encryptor(object):
def __init__(self, key, method=None): def __init__(self, key, method=None):
if method == 'table': if method == b'table':
method = None method = None
self.key = key self.key = key
self.method = method self.method = method
self.iv = None self.iv = None
self.iv_sent = False self.iv_sent = False
self.cipher_iv = '' self.cipher_iv = b''
self.decipher = None self.decipher = None
if method: if method:
self.cipher = self.get_cipher(key, method, 1, iv=random_string(32)) self.cipher = self.get_cipher(key, method, 1, iv=random_string(32))
@ -130,6 +130,7 @@ class Encryptor(object):
return len(self.cipher_iv) return len(self.cipher_iv)
def get_cipher(self, password, method, op, iv=None): def get_cipher(self, password, method, op, iv=None):
if hasattr(password, 'encode'):
password = password.encode('utf-8') password = password.encode('utf-8')
method = method.lower() method = method.lower()
m = self.get_cipher_param(method) m = self.get_cipher_param(method)
@ -176,7 +177,7 @@ class Encryptor(object):
def encrypt_all(password, method, op, data): def encrypt_all(password, method, op, data):
if method is not None and method.lower() == 'table': if method is not None and method.lower() == b'table':
method = None method = None
if not method: if not method:
[encrypt_table, decrypt_table] = init_table(password) [encrypt_table, decrypt_table] = init_table(password)

4
shadowsocks/eventloop.py

@ -24,6 +24,8 @@
# from ssloop # from ssloop
# https://github.com/clowwindy/ssloop # https://github.com/clowwindy/ssloop
from __future__ import absolute_import, division, print_function, \
with_statement
import os import os
import socket import socket
@ -100,7 +102,7 @@ class KqueueLoop(object):
results[fd] |= POLL_IN results[fd] |= POLL_IN
elif e.filter == select.KQ_FILTER_WRITE: elif e.filter == select.KQ_FILTER_WRITE:
results[fd] |= POLL_OUT results[fd] |= POLL_OUT
return results.iteritems() return results.items()
def add_fd(self, fd, mode): def add_fd(self, fd, mode):
self._fds[fd] = mode self._fds[fd] = mode

4
shadowsocks/local.py

@ -21,11 +21,15 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from __future__ import absolute_import, division, print_function, \
with_statement
import sys import sys
import os import os
import logging import logging
import signal import signal
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../'))
from shadowsocks import utils, encrypt, eventloop, tcprelay, udprelay, asyncdns from shadowsocks import utils, encrypt, eventloop, tcprelay, udprelay, asyncdns

3
shadowsocks/lru_cache.py

@ -1,6 +1,9 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function, \
with_statement
import collections import collections
import logging import logging
import time import time

10
shadowsocks/server.py

@ -21,11 +21,15 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from __future__ import absolute_import, division, print_function, \
with_statement
import sys import sys
import os import os
import logging import logging
import signal import signal
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../'))
from shadowsocks import utils, encrypt, eventloop, tcprelay, udprelay, asyncdns from shadowsocks import utils, encrypt, eventloop, tcprelay, udprelay, asyncdns
@ -66,13 +70,13 @@ def main():
def run_server(): def run_server():
def child_handler(signum, _): def child_handler(signum, _):
logging.warn('received SIGQUIT, doing graceful shutting down..') logging.warn('received SIGQUIT, doing graceful shutting down..')
map(lambda s: s.close(next_tick=True), tcp_servers + udp_servers) list(map(lambda s: s.close(next_tick=True), tcp_servers + udp_servers))
signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM), signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM),
child_handler) child_handler)
try: try:
loop = eventloop.EventLoop() loop = eventloop.EventLoop()
dns_resolver.add_to_loop(loop) dns_resolver.add_to_loop(loop)
map(lambda s: s.add_to_loop(loop), tcp_servers + udp_servers) list(map(lambda s: s.add_to_loop(loop), tcp_servers + udp_servers))
loop.run() loop.run()
except (KeyboardInterrupt, IOError, OSError) as e: except (KeyboardInterrupt, IOError, OSError) as e:
logging.error(e) logging.error(e)
@ -85,7 +89,7 @@ def main():
if os.name == 'posix': if os.name == 'posix':
children = [] children = []
is_child = False is_child = False
for i in xrange(0, int(config['workers'])): for i in range(0, int(config['workers'])):
r = os.fork() r = os.fork()
if r == 0: if r == 0:
logging.info('worker started') logging.info('worker started')

17
shadowsocks/tcprelay.py

@ -21,6 +21,9 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from __future__ import absolute_import, division, print_function, \
with_statement
import time import time
import socket import socket
import errno import errno
@ -29,7 +32,7 @@ import logging
import traceback import traceback
import random import random
from shadowsocks import encrypt, eventloop, utils from shadowsocks import encrypt, eventloop, utils, common
from shadowsocks.common import parse_header from shadowsocks.common import parse_header
@ -231,13 +234,13 @@ class TCPRelayHandler(object):
def _handle_stage_hello(self, data): def _handle_stage_hello(self, data):
try: try:
if self._is_local: if self._is_local:
cmd = ord(data[1]) cmd = common.ord(data[1])
if cmd == CMD_UDP_ASSOCIATE: if cmd == CMD_UDP_ASSOCIATE:
logging.debug('UDP associate') logging.debug('UDP associate')
if self._local_sock.family == socket.AF_INET6: if self._local_sock.family == socket.AF_INET6:
header = '\x05\x00\x00\x04' header = b'\x05\x00\x00\x04'
else: else:
header = '\x05\x00\x00\x01' header = b'\x05\x00\x00\x01'
addr, port = self._local_sock.getsockname() addr, port = self._local_sock.getsockname()
addr_to_send = socket.inet_pton(self._local_sock.family, addr_to_send = socket.inet_pton(self._local_sock.family,
addr) addr)
@ -265,7 +268,7 @@ class TCPRelayHandler(object):
self._stage = STAGE_DNS self._stage = STAGE_DNS
if self._is_local: if self._is_local:
# forward address to remote # forward address to remote
self._write_to_sock('\x05\x00\x00\x01\x00\x00\x00\x00\x10\x10', self._write_to_sock(b'\x05\x00\x00\x01\x00\x00\x00\x00\x10\x10',
self._local_sock) self._local_sock)
data_to_send = self._encryptor.encrypt(data) data_to_send = self._encryptor.encrypt(data)
self._data_to_write_to_remote.append(data_to_send) self._data_to_write_to_remote.append(data_to_send)
@ -366,7 +369,7 @@ class TCPRelayHandler(object):
return return
elif is_local and self._stage == STAGE_INIT: elif is_local and self._stage == STAGE_INIT:
# TODO check auth method # TODO check auth method
self._write_to_sock('\x05\00', self._local_sock) self._write_to_sock(b'\x05\00', self._local_sock)
self._stage = STAGE_HELLO self._stage = STAGE_HELLO
return return
elif self._stage == STAGE_REPLY: elif self._stage == STAGE_REPLY:
@ -411,7 +414,7 @@ class TCPRelayHandler(object):
def _on_remote_write(self): def _on_remote_write(self):
self._stage = STAGE_STREAM self._stage = STAGE_STREAM
if self._data_to_write_to_remote: if self._data_to_write_to_remote:
data = ''.join(self._data_to_write_to_remote) data = b''.join(self._data_to_write_to_remote)
self._data_to_write_to_remote = [] self._data_to_write_to_remote = []
self._write_to_sock(data, self._remote_sock) self._write_to_sock(data, self._remote_sock)
else: else:

6
shadowsocks/udprelay.py

@ -65,6 +65,8 @@
# `client` means UDP clients that connects to other servers # `client` means UDP clients that connects to other servers
# `server` means the UDP server that handles user requests # `server` means the UDP server that handles user requests
from __future__ import absolute_import, division, print_function, \
with_statement
import time import time
import socket import socket
@ -73,7 +75,7 @@ import struct
import errno import errno
import random import random
from shadowsocks import encrypt, eventloop, lru_cache from shadowsocks import encrypt, eventloop, lru_cache, common
from shadowsocks.common import parse_header, pack_addr from shadowsocks.common import parse_header, pack_addr
@ -146,7 +148,7 @@ class UDPRelay(object):
if not data: if not data:
logging.debug('UDP handle_server: data is empty') logging.debug('UDP handle_server: data is empty')
if self._is_local: if self._is_local:
frag = ord(data[2]) frag = common.ord(data[2])
if frag != 0: if frag != 0:
logging.warn('drop a message since frag is not 0') logging.warn('drop a message since frag is not 0')
return return

34
shadowsocks/utils.py

@ -21,6 +21,9 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from __future__ import absolute_import, division, print_function, \
with_statement
import os import os
import json import json
import sys import sys
@ -33,9 +36,9 @@ VERBOSE_LEVEL = 5
def check_python(): def check_python():
info = sys.version_info info = sys.version_info
if not (info[0] == 2 and info[1] >= 6): # if not (info[0] == 2 and info[1] >= 6):
print 'Python 2.6 or 2.7 required' # print('Python 2.6 or 2.7 required')
sys.exit(1) # sys.exit(1)
def print_shadowsocks(): def print_shadowsocks():
@ -45,7 +48,7 @@ def print_shadowsocks():
version = pkg_resources.get_distribution('shadowsocks').version version = pkg_resources.get_distribution('shadowsocks').version
except Exception: except Exception:
pass pass
print 'shadowsocks %s' % version print('shadowsocks %s' % version)
def find_config(): def find_config():
@ -76,7 +79,7 @@ def check_config(config):
if config.get('timeout', 300) > 600: if config.get('timeout', 300) > 600:
logging.warn('warning: your timeout %d seems too long' % logging.warn('warning: your timeout %d seems too long' %
int(config.get('timeout'))) int(config.get('timeout')))
if config.get('password') in ['mypassword', 'barfoo!']: if config.get('password') in ['mypassword']:
logging.error('DON\'T USE DEFAULT PASSWORD! Please change it in your ' logging.error('DON\'T USE DEFAULT PASSWORD! Please change it in your '
'config.json!') 'config.json!')
exit(1) exit(1)
@ -102,7 +105,8 @@ def get_config(is_local):
logging.info('loading config from %s' % config_path) logging.info('loading config from %s' % config_path)
with open(config_path, 'rb') as f: with open(config_path, 'rb') as f:
try: try:
config = json.load(f, object_hook=_decode_dict) config = json.loads(f.read().decode('utf8'),
object_hook=_decode_dict)
except ValueError as e: except ValueError as e:
logging.error('found an error in config.json: %s', logging.error('found an error in config.json: %s',
e.message) e.message)
@ -145,7 +149,7 @@ def get_config(is_local):
v_count -= 1 v_count -= 1
config['verbose'] = v_count config['verbose'] = v_count
except getopt.GetoptError as e: except getopt.GetoptError as e:
print >>sys.stderr, e print(e, file=sys.stderr)
print_help(is_local) print_help(is_local)
sys.exit(2) sys.exit(2)
@ -218,7 +222,7 @@ def print_help(is_local):
def print_local_help(): def print_local_help():
print '''usage: sslocal [-h] -s SERVER_ADDR [-p SERVER_PORT] print('''usage: sslocal [-h] -s SERVER_ADDR [-p SERVER_PORT]
[-b LOCAL_ADDR] [-l LOCAL_PORT] -k PASSWORD [-m METHOD] [-b LOCAL_ADDR] [-l LOCAL_PORT] -k PASSWORD [-m METHOD]
[-t TIMEOUT] [-c CONFIG] [--fast-open] [-v] [-q] [-t TIMEOUT] [-c CONFIG] [--fast-open] [-v] [-q]
@ -237,11 +241,11 @@ optional arguments:
-q, -qq quiet mode, only show warnings/errors -q, -qq quiet mode, only show warnings/errors
Online help: <https://github.com/clowwindy/shadowsocks> Online help: <https://github.com/clowwindy/shadowsocks>
''' ''')
def print_server_help(): def print_server_help():
print '''usage: ssserver [-h] [-s SERVER_ADDR] [-p SERVER_PORT] -k PASSWORD print('''usage: ssserver [-h] [-s SERVER_ADDR] [-p SERVER_PORT] -k PASSWORD
-m METHOD [-t TIMEOUT] [-c CONFIG] [--fast-open] -m METHOD [-t TIMEOUT] [-c CONFIG] [--fast-open]
[--workers WORKERS] [-v] [-q] [--workers WORKERS] [-v] [-q]
@ -259,13 +263,13 @@ optional arguments:
-q, -qq quiet mode, only show warnings/errors -q, -qq quiet mode, only show warnings/errors
Online help: <https://github.com/clowwindy/shadowsocks> Online help: <https://github.com/clowwindy/shadowsocks>
''' ''')
def _decode_list(data): def _decode_list(data):
rv = [] rv = []
for item in data: for item in data:
if isinstance(item, unicode): if hasattr(item, 'encode'):
item = item.encode('utf-8') item = item.encode('utf-8')
elif isinstance(item, list): elif isinstance(item, list):
item = _decode_list(item) item = _decode_list(item)
@ -277,10 +281,8 @@ def _decode_list(data):
def _decode_dict(data): def _decode_dict(data):
rv = {} rv = {}
for key, value in data.iteritems(): for key, value in data.items():
if isinstance(key, unicode): if hasattr(value, 'encode'):
key = key.encode('utf-8')
if isinstance(value, unicode):
value = value.encode('utf-8') value = value.encode('utf-8')
elif isinstance(value, list): elif isinstance(value, list):
value = _decode_list(value) value = _decode_list(value)

7
tests/test.py

@ -1,6 +1,9 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function, \
with_statement
import sys import sys
import os import os
import signal import signal
@ -22,7 +25,7 @@ else:
if 'salsa20' in sys.argv[-1]: if 'salsa20' in sys.argv[-1]:
from shadowsocks.crypto import salsa20_ctr from shadowsocks.crypto import salsa20_ctr
salsa20_ctr.test() salsa20_ctr.test()
print 'encryption test passed' print('encryption test passed')
p1 = Popen(['python', 'shadowsocks/server.py', '-c', server_config], p1 = Popen(['python', 'shadowsocks/server.py', '-c', server_config],
stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
@ -69,7 +72,7 @@ try:
sys.exit(r) sys.exit(r)
else: else:
sys.exit(1) sys.exit(1)
print 'test passed' print('test passed')
finally: finally:
for p in [p1, p2]: for p in [p1, p2]:

Loading…
Cancel
Save