diff --git a/shadowsocks/encrypt.py b/shadowsocks/encrypt.py index 09a6db6..f3c2d24 100644 --- a/shadowsocks/encrypt.py +++ b/shadowsocks/encrypt.py @@ -26,6 +26,7 @@ import string import struct import logging +logger = logging.getLogger('encrypt') def random_string(length): import M2Crypto.Rand @@ -53,7 +54,7 @@ def init_table(key, method=None): try: __import__('M2Crypto') except ImportError: - logging.error('M2Crypto is required to use encryption other than default method') + logger.error('M2Crypto is required to use encryption other than default method') sys.exit(1) if not method: global encrypt_table, decrypt_table @@ -63,7 +64,7 @@ def init_table(key, method=None): try: Encryptor(key, method) # make an Encryptor to test if the settings if OK except Exception as e: - logging.error(e) + logger.error(e) sys.exit(1) @@ -140,7 +141,7 @@ class Encryptor(object): self.cipher_iv = iv[:m[1]] # this iv is for cipher, not decipher return M2Crypto.EVP.Cipher(method.replace('-', '_'), key, iv, op, key_as_bytes=0, d='md5', salt=None, i=1, padding=1) - logging.error('method %s not supported' % method) + logger.error('method %s not supported' % method) sys.exit(1) def encrypt(self, buf): diff --git a/shadowsocks/local.py b/shadowsocks/local.py index b3061e6..a8ac1cd 100755 --- a/shadowsocks/local.py +++ b/shadowsocks/local.py @@ -48,6 +48,8 @@ import encrypt import utils +logger = logging.getLogger('local') + def send_all(sock, data): bytes_sent = 0 while True: @@ -64,6 +66,7 @@ class ThreadingTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): class Socks5Server(SocketServer.StreamRequestHandler): + def getServer(self): aPort = REMOTE_PORT aServer = SERVER @@ -77,7 +80,8 @@ class Socks5Server(SocketServer.StreamRequestHandler): r = re.match(r'^(.*)\:(\d+)$', aServer) if r: # support config like "server": "123.123.123.1:8381" - # or "server": ["123.123.123.1:8381", "123.123.123.2:8381", "123.123.123.2:8382"] + # or "server": ["123.123.123.1:8381", "123.123.123.2:8381", + # "123.123.123.2:8382"] aServer = r.group(1) aPort = int(r.group(2)) return (aServer, aPort) @@ -124,7 +128,7 @@ class Socks5Server(SocketServer.StreamRequestHandler): data = self.rfile.read(4) or '\x00' * 4 mode = ord(data[1]) if mode != 1: - logging.warn('mode != 1') + logger.warn('mode != 1') return addrtype = ord(data[3]) addr_to_send = data[3] @@ -141,7 +145,7 @@ class Socks5Server(SocketServer.StreamRequestHandler): addr = socket.inet_ntop(socket.AF_INET6, addr_ip) addr_to_send += addr_ip else: - logging.warn('addr_type not supported') + logger.warn('addr_type not supported') # not supported return addr_port = self.rfile.read(2) @@ -155,99 +159,141 @@ class Socks5Server(SocketServer.StreamRequestHandler): aServer, aPort = self.getServer() remote = socket.create_connection((aServer, aPort)) self.send_encrypt(remote, addr_to_send) - logging.info('connecting %s:%d' % (addr, port[0])) + logger.info('connecting %s:%d' % (addr, port[0])) except socket.error, e: - logging.warn(e) + logger.warn(e) return self.handle_tcp(sock, remote) except socket.error, e: - logging.warn(e) + logger.warn(e) -def main(): - global SERVER, REMOTE_PORT, KEY, METHOD +class ShadowSocksServer(object): - logging.basicConfig(level=logging.DEBUG, - format='%(asctime)s %(levelname)-8s %(message)s', - datefmt='%Y-%m-%d %H:%M:%S', filemode='a+') + def __init__(self): - # fix py2exe - if hasattr(sys, "frozen") and sys.frozen in \ - ("windows_exe", "console_exe"): - p = os.path.dirname(os.path.abspath(sys.executable)) - os.chdir(p) - version = '' - try: - import pkg_resources - version = pkg_resources.get_distribution('shadowsocks').version - except: - pass - print 'shadowsocks %s' % version + self.options = self.default_options() - KEY = None - METHOD = None - LOCAL = '' - IPv6 = False + def default_options(self): + return { + "server":"localhost", + "server_port":8388, + "local_port":1080, + "password":"barfoo!", + "timeout":600, + "method":"table", + "IPv6": False + } - config_path = utils.find_config() - optlist, args = getopt.getopt(sys.argv[1:], 's:b:p:k:l:m:c:6') - for key, value in optlist: - if key == '-c': - config_path = value + def serve_forever(self): + global SERVER, REMOTE_PORT, KEY, METHOD + + self.set_logging() + self.run_info() + self.set_options() + self.check_config() - if config_path: - logging.info('loading config from %s' % config_path) - with open(config_path, 'rb') as f: - try: - config = json.load(f) - except ValueError as e: - logging.error('found an error in config.json: %s', e.message) - sys.exit(1) - else: - config = {} + SERVER = self.options['server'] + REMOTE_PORT = self.options['server_port'] + PORT = self.options['local_port'] + KEY = self.options['password'] + METHOD = self.options.get('method', None) + LOCAL = self.options.get('local', '') - optlist, args = getopt.getopt(sys.argv[1:], 's:b:p:k:l:m:c:6') - for key, value in optlist: - if key == '-p': - config['server_port'] = int(value) - elif key == '-k': - config['password'] = value - elif key == '-l': - config['local_port'] = int(value) - elif key == '-s': - config['server'] = value - elif key == '-m': - config['method'] = value - elif key == '-b': - config['local'] = value - elif key == '-6': - IPv6 = True + encrypt.init_table(KEY, METHOD) - SERVER = config['server'] - REMOTE_PORT = config['server_port'] - PORT = config['local_port'] - KEY = config['password'] - METHOD = config.get('method', None) - LOCAL = config.get('local', '') + try: + if self.options['IPv6']: + ThreadingTCPServer.address_family = socket.AF_INET6 + server = ThreadingTCPServer((LOCAL, PORT), Socks5Server) + logger.info("starting local at %s:%d" % + tuple(server.server_address[:2])) + server.serve_forever() + except socket.error, e: + logger.error(e) + except KeyboardInterrupt: + server.shutdown() + sys.exit(0) + self.server.serve_forever() - if not KEY and not config_path: - sys.exit('config not specified, please read https://github.com/clowwindy/shadowsocks') + def check_config(self): + utils.check_config(self.options) - utils.check_config(config) + def set_logging(self): + logfmt = '[%%(levelname)s] %s%%(message)s' % '%(name)s - ' + config = lambda x: logging.basicConfig(level=x, + format='[%(asctime)s] ' + logfmt, datefmt='%Y%m%d %H:%M:%S') + if self.options.get('debug'): + config(logging.DEBUG) + else: + config(logging.INFO) + # logging.basicConfig(level=logging.DEBUG, + # format='%(asctime)s %(levelname)-8s %(message)s', + # datefmt='%Y-%m-%d %H:%M:%S', filemode='a+') - encrypt.init_table(KEY, METHOD) + def set_options(self): + config_path = self._find_options() + config = self._parse_file_options(config_path) + config = self._parse_cmd_options(config) + self.options.update(config) + + def _parse_file_options(self, config_path): + if config_path: + logger.info('loading config from %s' % config_path) + with open(config_path, 'rb') as f: + try: + config = json.load(f) + except ValueError as e: + logger.error( + 'found an error in config.json: %s', e.message) + sys.exit(1) + else: + config = {} + + return config + + def _find_options(self): + config_path = utils.find_config() + print config_path + optlist, args = getopt.getopt(sys.argv[1:], 's:b:p:k:l:m:c:6') + for key, value in optlist: + if key == '-c': + config_path = value + return config_path + + def _parse_cmd_options(self, config): + optlist, args = getopt.getopt(sys.argv[1:], 's:b:p:k:l:m:c:6') + for key, value in optlist: + if key == '-p': + config['server_port'] = int(value) + elif key == '-k': + self.options['password'] = value + elif key == '-l': + config['local_port'] = int(value) + elif key == '-s': + config['server'] = value + elif key == '-m': + config['method'] = value + elif key == '-b': + config['local'] = value + elif key == '-6': + config['IPv6'] = True + return config + + def run_info(self): + + if hasattr(sys, "frozen") and sys.frozen in \ + ("windows_exe", "console_exe"): + p = os.path.dirname(os.path.abspath(sys.executable)) + os.chdir(p) + version = '' + try: + import pkg_resources + version = pkg_resources.get_distribution('shadowsocks').version + except: + pass + logger.info('shadowsocks %s' % version) - try: - if IPv6: - ThreadingTCPServer.address_family = socket.AF_INET6 - server = ThreadingTCPServer((LOCAL, PORT), Socks5Server) - logging.info("starting local at %s:%d" % tuple(server.server_address[:2])) - server.serve_forever() - except socket.error, e: - logging.error(e) - except KeyboardInterrupt: - server.shutdown() - sys.exit(0) if __name__ == '__main__': - main() + ShadowSocksServer().serve_forever() diff --git a/shadowsocks/server.py b/shadowsocks/server.py index d045d5e..fade4ee 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -46,6 +46,7 @@ import getopt import encrypt import utils +logger = logging.getLogger('server') def send_all(sock, data): bytes_sent = 0 @@ -63,6 +64,7 @@ class ThreadingTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): class Socks5Server(SocketServer.StreamRequestHandler): + def handle_tcp(self, sock, remote): try: fdset = [sock, remote] @@ -95,7 +97,8 @@ class Socks5Server(SocketServer.StreamRequestHandler): def handle(self): try: - self.encryptor = encrypt.Encryptor(self.server.key, self.server.method) + self.encryptor = encrypt.Encryptor( + self.server.key, self.server.method) sock = self.connection iv_len = self.encryptor.iv_len() if iv_len: @@ -111,99 +114,152 @@ class Socks5Server(SocketServer.StreamRequestHandler): self.decrypt(self.rfile.read(16))) else: # not supported - logging.warn('addr_type not supported, maybe wrong password') + logger.warn('addr_type not supported, maybe wrong password') return port = struct.unpack('>H', self.decrypt(self.rfile.read(2))) try: - logging.info('connecting %s:%d' % (addr, port[0])) + logger.info('connecting %s:%d' % (addr, port[0])) remote = socket.create_connection((addr, port[0])) except socket.error, e: # Connection refused - logging.warn(e) + logger.warn(e) return self.handle_tcp(sock, remote) except socket.error, e: - logging.warn(e) - -def main(): - logging.basicConfig(level=logging.DEBUG, - format='%(asctime)s %(levelname)-8s %(message)s', - datefmt='%Y-%m-%d %H:%M:%S', filemode='a+') + logger.warn(e) - version = '' - try: - import pkg_resources - version = pkg_resources.get_distribution('shadowsocks').version - except: - pass - print 'shadowsocks %s' % version +class ShadowSocksServer(object): - KEY = None - METHOD = None - IPv6 = False + def __init__(self): - config_path = utils.find_config() - optlist, args = getopt.getopt(sys.argv[1:], 's:p:k:m:c:6') - for key, value in optlist: - if key == '-c': - config_path = value + self.options = self.default_options() - if config_path: - logging.info('loading config from %s' % config_path) - with open(config_path, 'rb') as f: - try: - config = json.load(f) - except ValueError as e: - logging.error('found an error in config.json: %s', e.message) - sys.exit(1) - logging.info('loading config from %s' % config_path) - else: - config = {} + def default_options(self): + return { + "server":"localhost", + "server_port":8388, + "local_port":1080, + "password":"barfoo!", + "timeout":600, + "method":"table", + "IPv6": False + } - optlist, args = getopt.getopt(sys.argv[1:], 's:p:k:m:c:6') - for key, value in optlist: - if key == '-p': - config['server_port'] = int(value) - elif key == '-k': - config['password'] = value - elif key == '-s': - config['server'] = value - elif key == '-m': - config['method'] = value - elif key == '-6': - IPv6 = True + def serve_forever(self): - SERVER = config['server'] - PORT = config['server_port'] - KEY = config['password'] - METHOD = config.get('method', None) - PORTPASSWORD = config.get('port_password', None) - TIMEOUT = config.get('timeout', 600) + self.set_logging() + self.run_info() + self.set_options() + self.check_config() - if not KEY and not config_path: - sys.exit('config not specified, please read https://github.com/clowwindy/shadowsocks') + SERVER = self.options['server'] + PORT = self.options['server_port'] + KEY = self.options['password'] + METHOD = self.options.get('method', None) + PORTPASSWORD = self.options.get('port_password', None) + TIMEOUT = self.options.get('timeout', 600) - utils.check_config(config) + if PORTPASSWORD: + if PORT or KEY: + logger.warn( + 'warning: port_password should not be used with server_port and password. server_port and password will be ignored') + else: + PORTPASSWORD = {} + PORTPASSWORD[str(PORT)] = KEY - if PORTPASSWORD: - if PORT or KEY: - logging.warn('warning: port_password should not be used with server_port and password. server_port and password will be ignored') - else: - PORTPASSWORD = {} - PORTPASSWORD[str(PORT)] = KEY + encrypt.init_table(KEY, METHOD) + if self.options['IPv6']: + ThreadingTCPServer.address_family = socket.AF_INET6 + for port, key in PORTPASSWORD.items(): + server = ThreadingTCPServer((SERVER, int(port)), Socks5Server) + server.key, server.method, server.timeout = key, METHOD, int( + TIMEOUT) + logger.info("starting server at %s:%d" % + tuple(server.server_address[:2])) + threading.Thread(target=server.serve_forever).start() + + def check_config(self): + utils.check_config(self.options) + + def set_logging(self): + logfmt = '[%%(levelname)s] %s%%(message)s' % '%(name)s - ' + config = lambda x: logging.basicConfig(level=x, + format='[%(asctime)s] ' + logfmt, datefmt='%Y%m%d %H:%M:%S') + if self.options.get('debug'): + config(logging.DEBUG) + else: + config(logging.INFO) + # logging.basicConfig(level=logging.DEBUG, + # format='%(asctime)s %(levelname)-8s %(message)s', + # datefmt='%Y-%m-%d %H:%M:%S', filemode='a+') + + def set_options(self): + config_path = self._find_options() + config = self._parse_file_options(config_path) + config = self._parse_cmd_options(config) + self.options.update(config) + + def _parse_file_options(self, config_path): + if config_path: + logger.info('loading config from %s' % config_path) + with open(config_path, 'rb') as f: + try: + config = json.load(f) + except ValueError as e: + logger.error( + 'found an error in config.json: %s', e.message) + sys.exit(1) + else: + config = {} + + return config + + def _find_options(self): + config_path = utils.find_config() + print config_path + optlist, args = getopt.getopt(sys.argv[1:], 's:b:p:k:l:m:c:6') + for key, value in optlist: + if key == '-c': + config_path = value + return config_path + + def _parse_cmd_options(self, config): + optlist, args = getopt.getopt(sys.argv[1:], 's:b:p:k:l:m:c:6') + for key, value in optlist: + if key == '-p': + config['server_port'] = int(value) + elif key == '-k': + self.options['password'] = value + elif key == '-l': + config['local_port'] = int(value) + elif key == '-s': + config['server'] = value + elif key == '-m': + config['method'] = value + elif key == '-b': + config['local'] = value + elif key == '-6': + config['IPv6'] = True + return config + + def run_info(self): + + if hasattr(sys, "frozen") and sys.frozen in \ + ("windows_exe", "console_exe"): + p = os.path.dirname(os.path.abspath(sys.executable)) + os.chdir(p) + version = '' + try: + import pkg_resources + version = pkg_resources.get_distribution('shadowsocks').version + except: + pass + logger.info('shadowsocks %s' % version) - encrypt.init_table(KEY, METHOD) - if IPv6: - ThreadingTCPServer.address_family = socket.AF_INET6 - for port, key in PORTPASSWORD.items(): - server = ThreadingTCPServer((SERVER, int(port)), Socks5Server) - server.key, server.method, server.timeout = key, METHOD, int(TIMEOUT) - logging.info("starting server at %s:%d" % tuple(server.server_address[:2])) - threading.Thread(target=server.serve_forever).start() if __name__ == '__main__': try: - main() + ShadowSocksServer().serve_forever() except socket.error, e: - logging.error(e) + logger.error(e) diff --git a/shadowsocks/utils.py b/shadowsocks/utils.py index 237ad88..178bc35 100644 --- a/shadowsocks/utils.py +++ b/shadowsocks/utils.py @@ -4,6 +4,7 @@ import os import logging +logger = logging.getLogger('utils') def find_config(): config_path = 'config.json' @@ -16,7 +17,7 @@ def find_config(): def check_config(config): if config.get('server', '') in ['127.0.0.1', 'localhost']: - logging.warn('Server is set to "%s", maybe it\'s not correct' % config['server']) - logging.warn('Notice server will listen at %s:%s' % (config['server'], config['server_port'])) + logger.warn('Server is set to "%s", maybe it\'s not correct' % config['server']) + logger.warn('Notice server will listen at %s:%s' % (config['server'], config['server_port'])) if (config.get('method', '') or '').lower() == 'rc4': - logging.warn('RC4 is not safe; please use a safer cipher, like AES-256-CFB') + logger.warn('RC4 is not safe; please use a safer cipher, like AES-256-CFB')