deepinss

Deep in shadowsocks.

View on GitHub
16 December 2017

启动🚀 local.py

by

Debugger

我一直坚信学习代码之前应该先学习 debugger.

选用 VSCode 来调试 python 代码非常的方便, 只要安装 python 的 debugger 插件就好.

解读 local.py

local.py 的开始指定了文件的运行环境 /usr/bin/env python, 第二行指定了文件的编码格式为utf8 -*- coding: utf-8 -*-

import sys
import os
import logging
import signal
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../'))
# __filename__: /Users/jiangxuan/loveTech/shadowsocks/shadowsocks/local.py

将项目根目录添加到模块搜寻目录中.

from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, asyncdns

从 shadowsocks 模块引入需要的

页内目录

main 函数

@shell.exception_handle(self_=False, exit_code=1) # shell 模块中捕获错误的工具
def main(): # local.py 主函数
    shell.check_python() # 检查 python 版本

    # fix py2exe 处理 window 上的问题, 暂时不予解释
    if hasattr(sys, "frozen") and sys.frozen in \
            ("windows_exe", "console_exe"):
        p = os.path.dirname(os.path.abspath(sys.executable))
        os.chdir(p)
    # 获取配置文件 传入参数, 表明是 local
    config = shell.get_config(True)
    # 是否启动守护模式, 暂不解释
    daemon.daemon_exec(config)
    # log 启动地址
    logging.info("starting local at %s:%d" %
                 (config['local_address'], config['local_port']))
    # 创建 dns_resover
    dns_resolver = asyncdns.DNSResolver()
    # 创建 tcp 服务, 并且传入 dns_resover 服务, 和表明自己是 local 的True
    tcp_server = tcprelay.TCPRelay(config, dns_resolver, True)
    # 创建 udp 服务, 并且传入 dns_resover 服务, 和表明自己是 local 的True
    udp_server = udprelay.UDPRelay(config, dns_resolver, True)
    # 创建事件轮询
    loop = eventloop.EventLoop()
    # 将 dns_resover 加入事件轮询
    dns_resolver.add_to_loop(loop)
    # 将 tcp 服务 加入事件轮询
    tcp_server.add_to_loop(loop)
    # 将 ucp 服务 加入事件轮询
    udp_server.add_to_loop(loop)

    # 处理系统信号 <https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html>
    def handler(signum, _):
        logging.warn('received SIGQUIT, doing graceful shutting down..')
        tcp_server.close(next_tick=True)
        udp_server.close(next_tick=True)
    signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM), handler)

    def int_handler(signum, _):
        sys.exit(1)
    signal.signal(signal.SIGINT, int_handler)
    # 处理系统信号 END

    daemon.set_user(config.get('user', None))
    # 启动轮询
    loop.run()

# 如果当前模块被直接运行, 执行 main 函数
# <https://stackoverflow.com/questions/419163/what-does-if-name-main-do>
if __name__ == '__main__':
    main()

来用动画看一下该函数的执行流程:

@shell.exception_handle(self_=False, exit_code=1)
def main():
    shell.check_python()

    if hasattr(sys, "frozen") and sys.frozen in \
            ("windows_exe", "console_exe"):
        p = os.path.dirname(os.path.abspath(sys.executable))
        os.chdir(p)
    config = shell.get_config(True)
    daemon.daemon_exec(config)
    logging.info("starting local at %s:%d" %
                 (config['local_address'], config['local_port']))
    dns_resolver = asyncdns.DNSResolver()
    tcp_server = tcprelay.TCPRelay(config, dns_resolver, True)
    udp_server = udprelay.UDPRelay(config, dns_resolver, True)
    loop = eventloop.EventLoop()
    dns_resolver.add_to_loop(loop)
    tcp_server.add_to_loop(loop)
    udp_server.add_to_loop(loop)

    def handler(signum, _):
        logging.warn('received SIGQUIT, doing graceful shutting down..')
        tcp_server.close(next_tick=True)
        udp_server.close(next_tick=True)
    signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM), handler)

    def int_handler(signum, _):
        sys.exit(1)
    signal.signal(signal.SIGINT, int_handler)

    daemon.set_user(config.get('user', None))
    loop.run()

if __name__ == '__main__':
    main()
					
跳转函数列表

shell.check_python

def check_python():
    info = sys.version_info # 获取 version
    if info[0] == 2 and not info[1] >= 6: # 如果处于 2.x 版本, 但是
        print('Python 2.6+ required')
        sys.exit(1) # 退出
    elif info[0] == 3 and not info[1] >= 3:
        print('Python 3.3+ required')
        sys.exit(1)
    elif info[0] not in [2, 3]:
        print('Python version not supported')
        sys.exit(1)

来用动画看一下该函数的执行流程:

def check_python():
    info = sys.version_info
    if info[0] == 2 and not info[1] >= 6: # 如果处于 2.x 版本, 但是
        print('Python 2.6+ required')
        sys.exit(1) # 退出
    elif info[0] == 3 and not info[1] >= 3:
        print('Python 3.3+ required')
        sys.exit(1)
    elif info[0] not in [2, 3]:
        print('Python version not supported')
        sys.exit(1)
					
跳转函数列表

shell.get_config


def get_config(is_local):
    global verbose # shell 模块的全局变量
    # 配置 logging
    logging.basicConfig(level=logging.DEBUG,
                        format='%(levelname)-s: %(message)s')
    if is_local: # 分析 local 端, 这里 一定是 True
        shortopts = 'hd:s:b:p:k:l:m:c:t:vqa'
        longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'user=',
                    'libopenssl=', 'libmbedtls=', 'libsodium=', 'version']
    # else:
    #     shortopts = 'hd:s:p:k:m:c:t:vqa'
    #     longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers=',
    #                 'forbidden-ip=', 'user=', 'manager-address=', 'version',
    #                 'libopenssl=', 'libmbedtls=', 'libsodium=', 'prefer-ipv6']
    try:
        config_path = find_config() # find_config 用来获取默认的项目内的配置文件
        # 获取命令行参数
        optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
        for key, value in optlist:
            if key == '-c':
                config_path = value # 如果命令行中指定了配置文件目录, 覆盖默认

        # 配置目录存在, 则从配置文件中读取配置
        if config_path:
            # log 从哪里读取的配置文件
            logging.info('loading config from %s' % config_path)
            with open(config_path, 'rb') as f:
                try:
                    # 解析配置文件
                    config = parse_json_in_str(f.read().decode('utf8'))
                except ValueError as e:
                    # 解析配置文件出错, 退出程序
                    logging.error('found an error in config.json: %s',
                                  e.message)
                    sys.exit(1)
        else:
            # 配置文件路径不存在, 默认为空
            config = {}

        # 记录日志输出详细等级
        v_count = 0
        # 格式化参数
        for key, value in optlist:
            if key == '-p': # 服务器端口 int 类型
                config['server_port'] = int(value)
            elif key == '-k': # 秘钥 bytes 类型
                config['password'] = to_bytes(value)
            elif key == '-l': # 本地端口 int 类型
                config['local_port'] = int(value)
            elif key == '-s': # 服务器地址 str 类型
                config['server'] = to_str(value)
            elif key == '-m': # 加密方式 str 类型
                config['method'] = 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 == '-a': # 是否启用一次性验证 bool 类型
                config['one_time_auth'] = True
            elif key == '-t': # 过期时间 int 类型
                config['timeout'] = int(value)
            elif key == '--fast-open': # 是否启用 fast open bool 类型
                config['fast_open'] = True
            elif key == '--libopenssl': # openssl 库地址 str 类型
                config['libopenssl'] = to_str(value)
            elif key == '--libmbedtls': # mbedtls 库地址 str 类型
                config['libmbedtls'] = to_str(value)
            elif key == '--libsodium': # sodium 库地址 str 类型
                config['libsodium'] = to_str(value)
            elif key == '--workers': # worker 数量 int 类型
                config['workers'] = int(value)
            elif key == '--manager-address': # 管理地址 str 类型
                config['manager_address'] = to_str(value)
            elif key == '--user': # 启动用户 str 类型
                config['user'] = to_str(value)
            elif key == '--forbidden-ip': # 禁止 IP str 类型, 切割成 list 类型
                config['forbidden_ip'] = to_str(value).split(',')
            elif key in ('-h', '--help'): # 输出帮助信息并退出
                if is_local:
                    print_local_help()
                # else:
                #     print_server_help()
                sys.exit(0)
            elif key == '--version': # 输出 shadowsocks 的版本信息
                print_shadowsocks()
                sys.exit(0)
            elif key == '-d': # 守护模式命令 str 类型
                config['daemon'] = to_str(value)
            elif key == '--pid-file': # pid file 路径 str 类型
                config['pid-file'] = to_str(value)
            elif key == '--log-file': # log file 日志路径 str 类型
                config['log-file'] = to_str(value)
            elif key == '-q': # 降低日志输出详细等级
                v_count -= 1
                config['verbose'] = v_count
            elif key == '--prefer-ipv6': # 更偏爱 ipv6
                config['prefer_ipv6'] = True
    except getopt.GetoptError as e: # 如果命令行参数获取出错, 打印 local 帮助信息, 退出程序
        print(e, file=sys.stderr)
        print_help(is_local)
        sys.exit(2)

    if not config: # 如果 config 没有任何属性, 打印 local 帮助信息, 退出程序
        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['port_password'] = config.get('port_password', None)
    config['timeout'] = int(config.get('timeout', 300))
    config['fast_open'] = config.get('fast_open', False)
    config['workers'] = config.get('workers', 1)
    config['pid-file'] = config.get('pid-file', '/var/run/shadowsocks.pid')
    config['log-file'] = config.get('log-file', '/var/log/shadowsocks.log')
    config['verbose'] = config.get('verbose', False)
    config['local_address'] = to_str(config.get('local_address', '127.0.0.1'))
    config['local_port'] = config.get('local_port', 1080)
    config['one_time_auth'] = config.get('one_time_auth', False)
    config['prefer_ipv6'] = config.get('prefer_ipv6', False)
    config['server_port'] = config.get('server_port', 8388)
    config['dns_server'] = config.get('dns_server', None)
    config['libopenssl'] = config.get('libopenssl', None)
    config['libmbedtls'] = config.get('libmbedtls', None)
    config['libsodium'] = config.get('libsodium', None)

    config['tunnel_remote'] = to_str(config.get('tunnel_remote', '8.8.8.8'))
    config['tunnel_remote_port'] = config.get('tunnel_remote_port', 53)
    config['tunnel_port'] = config.get('tunnel_port', 53)

    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']
    # 根据config参数配置logging
    logging.basicConfig(level=level,
                        format='%(asctime)s %(levelname)-8s %(message)s',
                        datefmt='%Y-%m-%d %H:%M:%S')

    # 检查config文件, 主要是检查配置的加密算法是否由本机支持
    check_config(config, is_local)

    # 返回config参数, 运行到这里说明所有的参数都是符合要求的
    return config

shell.find_config

def find_config():
    config_path = 'config.json' # 当前目录下面的 配置文件
    if os.path.exists(config_path): # 如果存在则返回路径
        return config_path
    config_path = os.path.join(os.path.dirname(__file__), '../', 'config.json') # 寻找项目根目录下面的配置文件, 如果找到就返回路径
    if os.path.exists(config_path):
        return config_path
    return None # 没有找到任何项目内的配置文件, 返回 None

daemon.daemon_exec

这里主要是用来守护 SS 的程序, 但是不影响主流程, 现在不做解释

dns_resolver = asyncdns.DNSResolver()

这一行代码是为了创建 关于 DNS 的处理, 在 local 端我们一般填写的服务器的地址是 IP 地址, 所以我们暂时也不做解释

tcp_server = tcprelay.TCPRelay(config, dns_resolver, True)

这一行代码是最重要的, 用来创建 TCP 服务, 这一点将是我们接下来讲解的重点

udp_server = udprelay.UDPRelay(config, dns_resolver, True)

这一行是 udp 服务的核心, 但是我们最常用的不是这个, 先行不解释, 我们把整个 local 端的基础服务先解释一遍

loop = eventloop.EventLoop()

这一行是创建事件轮询器, 它处理者来自系统的各种事件. 我们接下来也会重点的解释着个模块, 这个模块加上 tcprelay 模块, 构成了我们使用频率最高的服务

接下来

接下来将会详细的解释 eventloop, tcpreply, 有了这两个我们基本可以走通整个 local 的流程

tags: