def write_pid_file(pid_file, pid): import fcntl import stat try: fd = os.open(pid_file, os.O_RDWR | os.O_CREAT, stat.S_IRUSR | stat.S_IWUSR) except OSError as e: shell.print_exception(e) return -1 flags = fcntl.fcntl(fd, fcntl.F_GETFD) assert flags != -1 flags |= fcntl.FD_CLOEXEC r = fcntl.fcntl(fd, fcntl.F_SETFD, flags) assert r != -1 # There is no platform independent way to implement fcntl(fd, F_SETLK, &fl) # via fcntl.fcntl. So use lockf instead try: fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB, 0, 0, os.SEEK_SET) except IOError: r = os.read(fd, 32) if r: logging.error('already started at pid %s' % common.to_str(r)) else: logging.error('already started') os.close(fd) return -1 os.ftruncate(fd, 0) os.write(fd, common.to_bytes(str(pid))) return 0
def get_host_from_http_header(self, buf): ret_buf = b'' lines = buf.split(b'\r\n') if lines and len(lines) > 1: for line in lines: if match_begin(line, b"Host: "): return common.to_str(line[6:])
def __init__(self, method): method = common.to_str(method) self.method = method self._method_info = self.get_method_info(method) if self._method_info: self.obfs = self.get_obfs(method) else: raise Exception('obfs plugin [%s] not supported' % method)
def push(self, ch): ch = ord(ch) if self.state == 0: if ch == ord('"'): self.state = 1 return to_str(chr(ch)) elif ch == ord('/'): self.state = 3 else: return to_str(chr(ch)) elif self.state == 1: if ch == ord('"'): self.state = 0 return to_str(chr(ch)) elif ch == ord('\\'): self.state = 2 return to_str(chr(ch)) elif self.state == 2: self.state = 1 if ch == ord('"'): return to_str(chr(ch)) return "\\" + to_str(chr(ch)) elif self.state == 3: if ch == ord('/'): self.state = 4 else: return "/" + to_str(chr(ch)) elif self.state == 4: if ch == ord('\n'): self.state = 0 return "\n" return ""
def _parse_command(self, data): # commands: # add: {"server_port": 8000, "password": "******"} # remove: {"server_port": 8000"} data = common.to_str(data) parts = data.split(':', 1) if len(parts) < 2: return data, None command, config_json = parts try: config = shell.parse_json_in_str(config_json) return command, config except Exception as e: logging.error(e) return None
def check_config(config, is_local): if config.get('daemon', None) == 'stop': # no need to specify configuration for daemon stop return if is_local and not config.get('password', None): logging.error('password not specified') print_help(is_local) sys.exit(2) if not is_local and not config.get('password', None) \ and not config.get('port_password', None): logging.error('password or port_password not specified') print_help(is_local) sys.exit(2) if 'local_port' in config: config['local_port'] = int(config['local_port']) if 'server_port' in config and type(config['server_port']) != list: config['server_port'] = int(config['server_port']) if config.get('local_address', '') in [b'0.0.0.0']: logging.warning( 'warning: local set to listen on 0.0.0.0, it\'s not safe') if config.get('server', '') in ['127.0.0.1', 'localhost']: logging.warning( 'warning: server set to listen on %s:%s, are you sure?' % (to_str(config['server']), config['server_port'])) if config.get('timeout', 300) < 100: logging.warning('warning: your timeout %d seems too short' % int(config.get('timeout'))) if config.get('timeout', 300) > 600: logging.warning('warning: your timeout %d seems too long' % int(config.get('timeout'))) if config.get('password') in [b'mypassword']: logging.error('DON\'T USE DEFAULT PASSWORD! Please change it in your ' 'config.json!') sys.exit(1) if config.get('user', None) is not None: if os.name != 'posix': logging.error('user can be used only on Unix') sys.exit(1) encrypt.try_cipher(config['password'], config['method'])
def daemon_stop(pid_file): import errno try: with open(pid_file) as f: buf = f.read() pid = common.to_str(buf) if not buf: logging.error('not running') except IOError as e: shell.print_exception(e) if e.errno == errno.ENOENT: # always exit 0 if we are sure daemon is not running logging.error('not running') return sys.exit(1) pid = int(pid) if pid > 0: try: os.kill(pid, signal.SIGTERM) except OSError as e: if e.errno == errno.ESRCH: logging.error('not running') # always exit 0 if we are sure daemon is not running return shell.print_exception(e) sys.exit(1) else: logging.error('pid is not positive: %d', pid) # sleep for maximum 10s for i in range(0, 200): try: # query for the pid os.kill(pid, 0) except OSError as e: if e.errno == errno.ESRCH: break time.sleep(0.05) else: logging.error('timed out when stopping pid %d', pid) sys.exit(1) print('stopped') os.unlink(pid_file)
def __init__(self, config): self._config = config self._relays = {} # (tcprelay, udprelay) self._loop = eventloop.EventLoop() self._dns_resolver = asyncdns.DNSResolver() self._dns_resolver.add_to_loop(self._loop) self._statistics = collections.defaultdict(int) self._control_client_addr = None try: manager_address = common.to_str(config['manager_address']) if ':' in manager_address: addr = manager_address.rsplit(':', 1) addr = addr[0], int(addr[1]) addrs = socket.getaddrinfo(addr[0], addr[1]) if addrs: family = addrs[0][0] else: logging.error('invalid address: %s', manager_address) exit(1) else: addr = manager_address family = socket.AF_UNIX self._control_socket = socket.socket(family, socket.SOCK_DGRAM) self._control_socket.bind(addr) self._control_socket.setblocking(False) except (OSError, IOError) as e: logging.error(e) logging.error('can not bind to manager address') exit(1) self._loop.add(self._control_socket, eventloop.POLL_IN, self) self._loop.add_periodic(self.handle_periodic) port_password = config['port_password'] del config['port_password'] for port, password in port_password.items(): a_config = config.copy() a_config['server_port'] = int(port) a_config['password'] = password self.add_port(a_config)
def test(): import time import threading import struct from ssr import encrypt logging.basicConfig(level=5, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S') enc = [] eventloop.TIMEOUT_PRECISION = 1 def run_server(): config = shell.get_config(True) config = config.copy() a_config = { 'server': '127.0.0.1', 'local_port': 1081, 'port_password': { '8381': 'foobar1', '8382': 'foobar2' }, 'method': 'aes-256-cfb', 'manager_address': '127.0.0.1:6001', 'timeout': 60, 'fast_open': False, 'verbose': 2 } config.update(a_config) manager = Manager(config) enc.append(manager) manager.run() t = threading.Thread(target=run_server) t.start() time.sleep(1) manager = enc[0] cli = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) cli.connect(('127.0.0.1', 6001)) # test add and remove time.sleep(1) cli.send(b'add: {"server_port":7001, "password":"******"}') time.sleep(1) assert 7001 in manager._relays data, addr = cli.recvfrom(1506) assert b'ok' in data cli.send(b'remove: {"server_port":8381}') time.sleep(1) assert 8381 not in manager._relays data, addr = cli.recvfrom(1506) assert b'ok' in data logging.info('add and remove test passed') # test statistics for TCP header = common.pack_addr(b'google.com') + struct.pack('>H', 80) data = encrypt.encrypt_all(b'asdfadsfasdf', 'aes-256-cfb', 1, header + b'GET /\r\n\r\n') tcp_cli = socket.socket() tcp_cli.connect(('127.0.0.1', 7001)) tcp_cli.send(data) tcp_cli.recv(4096) tcp_cli.close() data, addr = cli.recvfrom(1506) data = common.to_str(data) assert data.startswith('stat: ') data = data.split('stat:')[1] stats = shell.parse_json_in_str(data) assert '7001' in stats logging.info('TCP statistics test passed') # test statistics for UDP header = common.pack_addr(b'127.0.0.1') + struct.pack('>H', 80) data = encrypt.encrypt_all(b'foobar2', 'aes-256-cfb', 1, header + b'test') udp_cli = socket.socket(type=socket.SOCK_DGRAM) udp_cli.sendto(data, ('127.0.0.1', 8382)) tcp_cli.close() data, addr = cli.recvfrom(1506) data = common.to_str(data) assert data.startswith('stat: ') data = data.split('stat:')[1] stats = json.loads(data) assert '8382' in stats logging.info('UDP statistics test passed') manager._loop.stop() t.join()
def _handle_server_dns_resolved(self, error, remote_addr, server_addr, params): if error: return data, r_addr, uid, header_length = params user_id = self._listen_port try: server_port = remote_addr[1] addrs = socket.getaddrinfo(server_addr, server_port, 0, socket.SOCK_DGRAM, socket.SOL_UDP) if not addrs: # drop return af, socktype, proto, canonname, sa = addrs[0] server_addr = sa[0] key = client_key(r_addr, af) client_pair = self._cache.get(key, None) if client_pair is None: client_pair = self._cache_dns_client.get(key, None) if client_pair is None: if self._forbidden_iplist: if common.to_str(sa[0]) in self._forbidden_iplist: logging.debug('IP %s is in forbidden list, drop' % common.to_str(sa[0])) # drop return if self._forbidden_portset: if sa[1] in self._forbidden_portset: logging.debug('Port %d is in forbidden list, reject' % sa[1]) # drop return client = socket.socket(af, socktype, proto) client_uid = uid client.setblocking(False) self._socket_bind_addr(client, af) is_dns = False if len(data) > header_length + 13 and data[ header_length + 4:header_length + 12] == b"\x00\x01\x00\x00\x00\x00\x00\x00": is_dns = True else: pass if sa[1] == 53 and is_dns: #DNS logging.debug("DNS query %s from %s:%d" % (common.to_str(sa[0]), r_addr[0], r_addr[1])) self._cache_dns_client[key] = (client, uid) else: self._cache[key] = (client, uid) self._client_fd_to_server_addr[client.fileno()] = (r_addr, af) self._sockets.add(client.fileno()) self._eventloop.add(client, eventloop.POLL_IN, self) logging.debug('UDP port %5d sockets %d' % (self._listen_port, len(self._sockets))) if uid is not None: user_id = struct.unpack('<I', client_uid)[0] else: client, client_uid = client_pair self._cache.clear(self._udp_cache_size) self._cache_dns_client.clear(16) if self._is_local: ref_iv = [encrypt.encrypt_new_iv(self._method)] self._protocol.obfs.server_info.iv = ref_iv[0] data = self._protocol.client_udp_pre_encrypt(data) #logging.debug("%s" % (binascii.hexlify(data),)) data = encrypt.encrypt_all_iv( self._protocol.obfs.server_info.key, self._method, 1, data, ref_iv) if not data: return else: data = data[header_length:] if not data: return except Exception as e: shell.print_exception(e) logging.error("exception from user %d" % (user_id, )) try: client.sendto(data, (server_addr, server_port)) self.add_transfer_u(client_uid, len(data)) if client_pair is None: # new request addr, port = client.getsockname()[:2] common.connect_log( 'UDP data to %s(%s):%d from %s:%d by user %d' % (common.to_str(remote_addr[0]), common.to_str(server_addr), server_port, addr, port, user_id)) except IOError as e: err = eventloop.errno_from_exception(e) logging.warning('IOError sendto %s:%d by user %d' % (server_addr, server_port, user_id)) if err in (errno.EINPROGRESS, errno.EAGAIN): pass else: shell.print_exception(e)
def get_config(is_local): global verbose config = {} config_path = None logging.basicConfig(level=logging.INFO, format='%(levelname)-s: %(message)s') if is_local: shortopts = 'hd:s:b:p:k:l:m:O:o:G:g:c:t:vq' longopts = [ 'help', 'fast-open', 'pid-file=', 'log-file=', 'user='******'version' ] else: shortopts = 'hd:s:p:k:m:O:o:G:g:c:t:vq' longopts = [ 'help', 'fast-open', 'pid-file=', 'log-file=', 'workers=', 'forbidden-ip=', 'user='******'manager-address=', 'version' ] try: optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts) for key, value in optlist: if key == '-c': config_path = value elif key in ('-h', '--help'): print_help(is_local) sys.exit(0) elif key == '--version': print_shadowsocks() sys.exit(0) else: continue if config_path is None: config_path = find_config() if config_path: logging.debug('loading config from %s' % config_path) with open(config_path, 'rb') as f: try: config = parse_json_in_str( remove_comment(f.read().decode('utf8'))) except ValueError as e: logging.error('found an error in config.json: %s', str(e)) sys.exit(1) v_count = 0 for key, value in optlist: if key == '-p': config['server_port'] = int(value) elif key == '-k': config['password'] = to_bytes(value) elif key == '-l': config['local_port'] = int(value) elif key == '-s': config['server'] = to_str(value) elif key == '-m': config['method'] = to_str(value) elif key == '-O': config['protocol'] = to_str(value) elif key == '-o': config['obfs'] = to_str(value) elif key == '-G': config['protocol_param'] = to_str(value) elif key == '-g': config['obfs_param'] = to_str(value) elif key == '-b': config['local_address'] = to_str(value) elif key == '-v': v_count += 1 # '-vv' turns on more verbose mode config['verbose'] = v_count elif key == '-t': config['timeout'] = int(value) elif key == '--fast-open': config['fast_open'] = True elif key == '--workers': config['workers'] = int(value) elif key == '--manager-address': config['manager_address'] = value elif key == '--user': config['user'] = to_str(value) elif key == '--forbidden-ip': config['forbidden_ip'] = to_str(value) elif key == '-d': config['daemon'] = to_str(value) elif key == '--pid-file': config['pid-file'] = to_str(value) elif key == '--log-file': config['log-file'] = to_str(value) elif key == '-q': v_count -= 1 config['verbose'] = v_count else: continue except getopt.GetoptError as e: print(e, file=sys.stderr) print_help(is_local) sys.exit(2) if not config: logging.error('config not specified') print_help(is_local) sys.exit(2) config['password'] = to_bytes(config.get('password', b'')) config['method'] = to_str(config.get('method', 'aes-256-cfb')) config['protocol'] = to_str(config.get('protocol', 'origin')) config['protocol_param'] = to_str(config.get('protocol_param', '')) config['obfs'] = to_str(config.get('obfs', 'plain')) config['obfs_param'] = to_str(config.get('obfs_param', '')) config['port_password'] = config.get('port_password', None) config['additional_ports'] = config.get('additional_ports', {}) config['additional_ports_only'] = config.get('additional_ports_only', False) config['timeout'] = int(config.get('timeout', 300)) config['udp_timeout'] = int(config.get('udp_timeout', 120)) config['udp_cache'] = int(config.get('udp_cache', 64)) config['fast_open'] = config.get('fast_open', False) config['workers'] = config.get('workers', 1) config['pid-file'] = config.get('pid-file', '/var/run/shadowsocksr.pid') config['log-file'] = config.get('log-file', '/var/log/shadowsocksr.log') config['verbose'] = config.get('verbose', False) config['connect_verbose_info'] = config.get('connect_verbose_info', 0) config['local_address'] = to_str(config.get('local_address', '127.0.0.1')) config['local_port'] = config.get('local_port', 1080) if is_local: if config.get('server', None) is None: logging.error('server addr not specified') print_local_help() sys.exit(2) else: config['server'] = to_str(config['server']) else: config['server'] = to_str(config.get('server', '0.0.0.0')) try: config['forbidden_ip'] = \ IPNetwork(config.get('forbidden_ip', '127.0.0.0/8,::1/128')) except Exception as e: logging.error(e) sys.exit(2) try: config['forbidden_port'] = PortRange( config.get('forbidden_port', '')) except Exception as e: logging.error(e) sys.exit(2) try: config['ignore_bind'] = \ IPNetwork(config.get('ignore_bind', '127.0.0.0/8,::1/128,10.0.0.0/8,192.168.0.0/16')) except Exception as e: logging.error(e) sys.exit(2) config['server_port'] = config.get('server_port', 8388) logging.getLogger('').handlers = [] logging.addLevelName(VERBOSE_LEVEL, 'VERBOSE') if config['verbose'] >= 2: level = VERBOSE_LEVEL elif config['verbose'] == 1: level = logging.DEBUG elif config['verbose'] == -1: level = logging.WARN elif config['verbose'] <= -2: level = logging.ERROR else: level = logging.INFO verbose = config['verbose'] logging.basicConfig( level=level, format= '%(asctime)s %(levelname)-8s %(filename)s:%(lineno)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S') check_config(config, is_local) return config
def main(): shell.check_python() config = shell.get_config(False) shell.log_shadowsocks_version() daemon.daemon_exec(config) try: import resource logging.info( 'current process RLIMIT_NOFILE resource: soft %d hard %d' % resource.getrlimit(resource.RLIMIT_NOFILE)) except ImportError: pass if config['port_password']: pass else: config['port_password'] = {} server_port = config['server_port'] if type(server_port) == list: for a_server_port in server_port: config['port_password'][a_server_port] = config['password'] else: config['port_password'][str(server_port)] = config['password'] if not config.get('dns_ipv6', False): asyncdns.IPV6_CONNECTION_SUPPORT = False if config.get('manager_address', 0): logging.info('entering manager mode') manager.run(config) return tcp_servers = [] udp_servers = [] dns_resolver = asyncdns.DNSResolver() if int(config['workers']) > 1: stat_counter_dict = None else: stat_counter_dict = {} port_password = config['port_password'] config_password = config.get('password', 'm') del config['port_password'] for port, password_obfs in port_password.items(): method = config["method"] protocol = config.get("protocol", 'origin') protocol_param = config.get("protocol_param", '') obfs = config.get("obfs", 'plain') obfs_param = config.get("obfs_param", '') bind = config.get("out_bind", '') bindv6 = config.get("out_bindv6", '') if type(password_obfs) == list: password = password_obfs[0] obfs = common.to_str(password_obfs[1]) if len(password_obfs) > 2: protocol = common.to_str(password_obfs[2]) elif type(password_obfs) == dict: password = password_obfs.get('password', config_password) method = common.to_str(password_obfs.get('method', method)) protocol = common.to_str(password_obfs.get('protocol', protocol)) protocol_param = common.to_str( password_obfs.get('protocol_param', protocol_param)) obfs = common.to_str(password_obfs.get('obfs', obfs)) obfs_param = common.to_str( password_obfs.get('obfs_param', obfs_param)) bind = password_obfs.get('out_bind', bind) bindv6 = password_obfs.get('out_bindv6', bindv6) else: password = password_obfs a_config = config.copy() ipv6_ok = False logging.info( "server start with protocol[%s] password [%s] method [%s] obfs [%s] obfs_param [%s]" % (protocol, password, method, obfs, obfs_param)) if 'server_ipv6' in a_config: try: if len(a_config['server_ipv6'] ) > 2 and a_config['server_ipv6'][ 0] == "[" and a_config['server_ipv6'][-1] == "]": a_config['server_ipv6'] = a_config['server_ipv6'][1:-1] a_config['server_port'] = int(port) a_config['password'] = password a_config['method'] = method a_config['protocol'] = protocol a_config['protocol_param'] = protocol_param a_config['obfs'] = obfs a_config['obfs_param'] = obfs_param a_config['out_bind'] = bind a_config['out_bindv6'] = bindv6 a_config['server'] = a_config['server_ipv6'] logging.info("starting server at [%s]:%d" % (a_config['server'], int(port))) tcp_servers.append( tcprelay.TCPRelay(a_config, dns_resolver, False, stat_counter=stat_counter_dict)) udp_servers.append( udprelay.UDPRelay(a_config, dns_resolver, False, stat_counter=stat_counter_dict)) if a_config['server_ipv6'] == b"::": ipv6_ok = True except Exception as e: shell.print_exception(e) try: a_config = config.copy() a_config['server_port'] = int(port) a_config['password'] = password a_config['method'] = method a_config['protocol'] = protocol a_config['protocol_param'] = protocol_param a_config['obfs'] = obfs a_config['obfs_param'] = obfs_param a_config['out_bind'] = bind a_config['out_bindv6'] = bindv6 logging.info("starting server at %s:%d" % (a_config['server'], int(port))) tcp_servers.append( tcprelay.TCPRelay(a_config, dns_resolver, False, stat_counter=stat_counter_dict)) udp_servers.append( udprelay.UDPRelay(a_config, dns_resolver, False, stat_counter=stat_counter_dict)) except Exception as e: if not ipv6_ok: shell.print_exception(e) def run_server(): def child_handler(signum, _): logging.warn('received SIGQUIT, doing graceful shutting down..') list( map(lambda s: s.close(next_tick=True), tcp_servers + udp_servers)) signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM), child_handler) def int_handler(signum, _): sys.exit(1) signal.signal(signal.SIGINT, int_handler) try: loop = eventloop.EventLoop() dns_resolver.add_to_loop(loop) list(map(lambda s: s.add_to_loop(loop), tcp_servers + udp_servers)) daemon.set_user(config.get('user', None)) loop.run() except Exception as e: shell.print_exception(e) sys.exit(1) if int(config['workers']) > 1: if os.name == 'posix': children = [] is_child = False for i in range(0, int(config['workers'])): r = os.fork() if r == 0: logging.info('worker started') is_child = True run_server() break else: children.append(r) if not is_child: def handler(signum, _): for pid in children: try: os.kill(pid, signum) os.waitpid(pid, 0) except OSError: # child may already exited pass sys.exit() signal.signal(signal.SIGTERM, handler) signal.signal(signal.SIGQUIT, handler) signal.signal(signal.SIGINT, handler) # master for a_tcp_server in tcp_servers: a_tcp_server.close() for a_udp_server in udp_servers: a_udp_server.close() dns_resolver.close() for child in children: os.waitpid(child, 0) else: logging.warn('worker is only available on Unix/Linux') run_server() else: run_server()