January 9, 2020

Android Proxy实现分析(未完待续)

序章

最近在校招面试的时候顺手翻了一下TCP/IP详解卷一,然后就想起来VpnService刚出来的时候写过一个移动端抓包应用,写得非常累,还不少bug。其实proxy,sniffer之类的应用,在Android上实现都差不多,所以就趁着这个机会,看了一下圈内知名app shadowsocks Android端的实现。

proxy原理

proxy原理细节就不再赘述,简单地说,proxy其实就是起到了接收请求,返回响应的作用。Android上不是所有的应用都允许配置代理服务器的,所以我们必须实现一个透明代理(Transproxy)那么要在Android上实现一个Transproxy,我们需要做什么呢?首先我们需要实现一个client,负责和proxy server建立连接,转发流量,然后我们需要把整个系统的流量都拦截下来,转发到我们的client处理,最后再把处理完的响应交给系统,由系统分发到各个进程。

流量拦截

先说说流量转发,这一块的实现在Android平台上有一个艰辛的演进之路。在Android 4.0之前,Android SDK是没有提供接口让应用拦截系统流量的,因此需要实现全局透明代理,必须从更底层的Linux下手,Linux内核有一个netfilter模块,提供了包过滤,NAT等功能,上层的iptables就是基于netfilter实现的,4.0之前的流量拦截就是基于iptables实现的。Android 4.0之后,Android提供了VpnService类,允许用户实现自定义的Vpn服务,因此可以用这个类来拦截流量,很多proxy和sniffer都是基于这种方案实现,但是VpnService在这个时候API还不够完善,比如不拦截指定app流量依然无法实现,直到Android 5.0才实现了这个功能。

iptables实现

Android平台的透明代理早期都是基于NAT来实现的,netfilter模块内部有三张表,mangle, filter和nat。我们用的最多的可能是filter,如果要实现地址转换或者端口转发,就需要用到nat表。通俗一点来说,netfilter的存在,允许Linux被当作一个路由器使用,而NAT涉及到在IP包经过路由时,修改其源地址,端口或目标地址,端口。在Android 2.3时代,有一个很知名的代理软件,GAEProxy,全局代理功能就是通过iptables来实现的。shadowsocks早期为了实现分应用代理,也是使用iptables来实现全局代理的。这部分的代码都太过久远,现在很难找了,不过shadowsocks-libev提供了ss-redir的功能,里面也配置了iptables的规则,目的也是一样,为了实现在Linux上的透明代理,我们可以参考一下这部分规则,原理也是一样的。

# Create new chain
iptables -t nat -N SHADOWSOCKS
iptables -t mangle -N SHADOWSOCKS

# Ignore your shadowsocks server's addresses
# It's very IMPORTANT, just be careful.
iptables -t nat -A SHADOWSOCKS -d 123.123.123.123 -j RETURN

# Ignore LANs and any other addresses you'd like to bypass the proxy
# See Wikipedia and RFC5735 for full list of reserved networks.
# See ashi009/bestroutetb for a highly optimized CHN route list.
iptables -t nat -A SHADOWSOCKS -d 0.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 10.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 127.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 169.254.0.0/16 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 172.16.0.0/12 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 192.168.0.0/16 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 224.0.0.0/4 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 240.0.0.0/4 -j RETURN

# Anything else should be redirected to shadowsocks's local port
iptables -t nat -A SHADOWSOCKS -p tcp -j REDIRECT --to-ports 12345

# Add any UDP rules
ip route add local default dev lo table 100
ip rule add fwmark 1 lookup 100
iptables -t mangle -A SHADOWSOCKS -p udp --dport 53 -j TPROXY --on-port 12345 --tproxy-mark 0x01/0x01

# Apply the rules
iptables -t nat -A PREROUTING -p tcp -j SHADOWSOCKS
iptables -t mangle -A PREROUTING -j SHADOWSOCKS

首先创建了一个叫做SHADOWSOCKS的链,然后把服务端地址排除,否则会造成死循环,接下来一段过滤掉局域网地址,然后把其他所有TCP流量都重定向到shadowsocks本地监听的端口。这里要注意的是,iptables规则匹配是从前往后进行的,一旦匹配上一条就不再匹配后面的了,所以可以这么写。
接下来一段是关于UDP的规则,UDP是由TPROXY来处理的,这是因为通过NAT转发的包会修改目标地址,而代理软件是需要知道原始目标地址的,TCP包能获取到原始地址是因为netfilter的连接跟踪机制,而UDP包不行,所以需要由TPROXY来处理,TPROXY也是内核的一个模块,就是用来实现透明代理的,细节可以参考官方文档:https://www.kernel.org/doc/Documentation/networking/tproxy.txt
最后两行应用配置好的规则,综上,全局透明代理在Android早期版本就是这么实现的,因为配置iptables需要root权限,所以早期要实现全局透明代理必须root。

VpnService实现

早期的Android版本只支持内置的VPN连接,VPN服务器必须是标准的PPTP或者L2TP/IPSec实现,从Android 4.0开始,SDK提供了VpnService系列的接口,允许开发者实现自定义的VPN协议。这套API提供了拦截IP包的能力,给开发者提供了高度的可定制性。从官网摘了一张图,很清楚的描述了VpnService的实现原理。
VpnService
可以看到,系统为VpnService实现了一个TUN设备,TUN设备可以理解为一个工作在OSI参考模型第三层的虚拟网卡,说得形象一点,物理网卡是通过网线收发数据包,而TUN设备通过/dev/tun这个文件来收发数据包。因为TUN设备工作在网络层,所以它没有MAC地址,其他方面与物理网卡没有区别。
从图中可以很清晰地看到,自定义的VpnService直接与TUN设备交互,当VpnService运行的时候,对于应用的出口流量,系统会通过TUN设备转发给VpnService,VpnService再通过一个被保护的连接(不再经过VpnService,否则会死循环)与VPN服务端交互,当产生回包时,VpnService把回包写入TUN设备,再由系统分发给应用程序。
在Android 4.0之后,全局透明代理基本都改为了这种方式实现,相比于iptables的实现,VpnService更稳定,而且不需要root,大大降低了使用门槛。
VpnService有很高的权限,可以获取系统的所有IP数据包,所以任何VpnService在启用前,都需要弹窗让用户确认,并且在建立连接后会在系统通知栏出现一条无法划掉的通知。任何时刻最多只能有一个VpnService被启用,后启动的VpnService会替代前一个。

分应用代理

上一节说到了VpnService是不需要root即可实现透明代理的,但是shadowsocks在VpnService出来之后很长一段时间还是保留了需要root的NAT方案,这显然不是作者忘了删除,而是因为VpnService早期API不全,分应用代理的功能无法实现。总不能更新版本后,原有的功能反而不支持了吧,所以NAT实现必须留着。直到Android 5.0,VpnService.Builder才加上了如下两个接口:

public VpnService.Builder addAllowedApplication (String packageName)
public VpnService.Builder addDisallowedApplication (String packageName)

这两个接口类似于黑名单和白名单,这两个接口是互斥的,调用addAllowedApplication会导致只有指定app通过VPN通信,其他的app是直连的,就像没有VPN一样,调用addDisallowedApplication会导致只有指定app直连,其他的app流量都通过VPN。
那么Android 5.0之前是通过什么方式实现分应用代理的呢?还是ipbtales,iptables有通过uid和gid匹配的扩展,可以筛选指定用户和用户组的流量。我们知道,Android上每个安装的app都有自己独立的uid,通过iptables很容易配置匹配规则,这里从redsocks里摘了两行样例:

# Any tcp connection made by `luser' should be redirected.
root# iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner luser -j REDSOCKS

# You can also control that in more precise way using `gid-owner` from
# iptables.
root# groupadd socksified
root# usermod --append --groups socksified luser
root# iptables -t nat -A OUTPUT -p tcp -m owner --gid-owner socksified -j REDSOCKS

这里的OUTPUT代表所有本机产生的流量,上面两段分别演示了按uid筛选和按gid筛选的方法。

shadowsocks-android源码分析

整体架构

要了解shadowsocks-android的整体架构,可以先从安装后的文件结构入手,这里可以看到,安装后的lib目录下有三个so文件。

:/data/app/com.github.shadowsocks-D1d-hk6w1vgWFxEyIY-e3g==/lib/arm64 # 
ls -l
total 1228
-rwxr-xr-x 1 system system 274296 1979-11-30 00:00 libredsocks.so
-rwxr-xr-x 1 system system 642440 1979-11-30 00:00 libss-local.so
-rwxr-xr-x 1 system system 324152 1979-11-30 00:00 libtun2socks.so

翻一下编译脚本,会发现这三个so后缀的文件,其实全部都是ELF可执行文件。Android.mk里有这么一句:

BUILD_SHARED_EXECUTABLE := $(LOCAL_PATH)/build-shared-executable.mk

然后build-shared-executable.mk文件内容如下:

LOCAL_BUILD_SCRIPT := BUILD_EXECUTABLE
LOCAL_MAKEFILE     := $(local-makefile)
$(call check-defined-LOCAL_MODULE,$(LOCAL_BUILD_SCRIPT))
$(call check-LOCAL_MODULE,$(LOCAL_MAKEFILE))
$(call check-LOCAL_MODULE_FILENAME)
# we are building target objects
my := TARGET_
$(call handle-module-filename,lib,$(TARGET_SONAME_EXTENSION))
$(call handle-module-built)
LOCAL_MODULE_CLASS := EXECUTABLE
include $(BUILD_SYSTEM)/build-module.mk

相比BUILD_EXECUTABLE,改动只有一行$(call handle-module-filename,lib,$(TARGET_SONAME_EXTENSION)),把可执行文件按so来命名。其实这么改主要是为了省事,后缀改为so,后续直接被当成so打包进apk,实际上还是可执行文件。
这三个文件,libredsocks.so主要用来实现透明代理,目前已经废弃,我们先不看,libss-local.so既是一个SOCKS5服务器,也是用来和remote通信的客户端,是整个shadowsocks-android的核心,libtun2socks.so用来实现从tun设备读取数据包到SOCKS5协议的转换。我们先上一张整体的架构图表明各个部分的关系,后面详细讲各个部分的逻辑。
shadowsocks
从图上可以看到,上层的VpnService只用来获取tun设备的fd,获取到fd之后通过sock_path这个LocalSocket传递给tun2socks,tun2socks获取到fd之后从tun读取数据包,然后转发给ss-local,ss-local负责与远程服务端建立连接,并把这个fd通过protect_path回传给VpnService,VpnService再把这个fd protect起来,防止死循环。

VpnService实现

VpnService的逻辑写得相当简单,逻辑都在startProcesses方法里。

override suspend fun startProcesses(hosts: HostsFile) {
    worker = ProtectWorker().apply { start() }
    super.startProcesses(hosts)
    sendFd(startVpn())
}

这里一共做了四件事情:

  1. 启动libss-local.so,也就是本地的SOCKS5服务器
  2. 启动libtun2socks.so,用来做协议转换
  3. 启动一个线程,用于监听和remote建连成功的fd,并protect起来
  4. 将tun设备的fd发送给tun2socks
    我这里debug了一下,前两步执行的命令是:
#/lib/arm64/libss-local.so -b 127.0.0.1 -l 1080 -t 600 -S /data/user_de/0/com.github.shadowsocks/no_backup/stat_main -c /data/user/0/com.github.shadowsocks/no_backup/shadowsocks.conf -V -u -D --fast-open

#/lib/arm64/libtun2socks.so --netif-ipaddr 172.19.0.2 --socks-server-addr 127.0.0.1:1080 --tunmtu 1500 --sock-path sock_path --dnsgw 127.0.0.1:5450 --loglevel warning --netif-ip6addr fdfe:dcba:9876::2 --enable-udprelay

命令具体的参数我们后面的章节慢慢分析,这里只需要注意,两条命令中127.0.0.1:1080是相同的。tun2socks和ss-local正是通过这个socket通信的。
这里需要特别注意的是第三和第四步,这两个fd的传递体现了VpnService的关键作用。

fd的传递

这里传递的有两个fd,分别通过sock_path和protect_path两个local socket传递,我们从Java到native code来分析一下两个fd的传递过程以及作用。

VpnService -> tun2socks

VpnService中通过sock_path发送出去了一个fd,这里传入的fd其实就是VpnService.Builder.establish()方法返回的fd,也就是tun设备的fd。

private suspend fun sendFd(fd: FileDescriptor) {
    var tries = 0
    val path = File(Core.deviceStorage.noBackupFilesDir, "sock_path").absolutePath
    while (true) try {
        delay(50L shl tries)
        LocalSocket().use { localSocket ->
            localSocket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM))
            localSocket.setFileDescriptorsForSend(arrayOf(fd))
            localSocket.outputStream.write(42)
        }
        return
    } catch (e: IOException) {
        if (tries > 5) throw e
        tries += 1
    }
}

启动tun2socks的命令中,也通过--sock-path sock_path参数指定了这个local socket,那么看一下native code中对sock_path的处理。
tun2socks的入口是tun2socks.c的main方法,解析命令行参数的方法是

int parse_arguments (int argc, char *argv[])

命令行参数最后会被解析到如下结构体:

// command-line options
struct {
    int help;
    int version;
    int logger;
    #ifndef BADVPN_USE_WINAPI
    char *logger_syslog_facility;
    char *logger_syslog_ident;
    #endif
    int loglevel;
    int loglevels[BLOG_NUM_CHANNELS];
    char *netif_ipaddr;
    char *netif_netmask;
    char *netif_ip6addr;
    char *socks_server_addr;
    char *username;
    char *password;
    char *password_file;
    int append_source_to_username;
    char *udpgw_remote_server_addr;
    int udpgw_max_connections;
    int udpgw_connection_buffer_size;
    int udpgw_transparent_dns;
#ifdef __ANDROID__
    int tun_mtu;
    int fake_proc;
    char *sock_path;
    char *pid;
    char *dnsgw;
#else
    char *tundev;
#endif
} options;

那么再看一下对char *sock_path的引用,整个方法比较简短:

#ifdef __ANDROID__
int wait_for_fd()
{
    int fd, sock;
    struct sockaddr_un addr;

    if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        BLog(BLOG_ERROR, "socket() failed: %s (socket sock = %d)\n", strerror(errno), sock);
        return -1;
    }

    int flags;
    if (-1 == (flags = fcntl(fd, F_GETFL, 0))) {
            flags = 0;
    }
    fcntl(sock, F_SETFL, flags | O_NONBLOCK);

    char *path = "/data/data/com.github.shadowsocks/sock_path";
    if (options.sock_path != NULL) {
        path = options.sock_path;
    }
    unlink(path);
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, path, sizeof(addr.sun_path)-1);

    if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        BLog(BLOG_ERROR, "bind() failed: %s (sock = %d)\n", strerror(errno), sock);
        close(sock);
        return -1;
    }

    if (listen(sock, 5) == -1) {
        BLog(BLOG_ERROR, "listen() failed: %s (sock = %d)\n", strerror(errno), sock);
        close(sock);
        return -1;
    }

    fd_set set;
    FD_ZERO (&set);
    FD_SET (sock, &set);

    struct timeval tv = {10, 0};

    for (;;) {
        if (select(sock + 1, &set, NULL, NULL, &tv) < 0) {
            BLog(BLOG_ERROR, "select() failed: %s\n", strerror(errno));
            break;
        }

        int sock2;
        struct sockaddr_un remote;
        int t = sizeof(remote);
        if ((sock2 = accept(sock, (struct sockaddr *)&remote, &t)) == -1) {
            BLog(BLOG_ERROR, "accept() failed: %s (sock = %d)\n", strerror(errno), sock);
            break;
        }

        if (ancil_recv_fd(sock2, &fd)) {
            BLog(BLOG_ERROR, "ancil_recv_fd: %s (sock = %d)\n", strerror(errno), sock2);
            close(sock2);
            break;
        } else {
            close(sock2);
            BLog(BLOG_INFO, "received fd = %d", fd);
            break;
        }
    }

    close(sock);

    return fd;
}
#endif

这一段做的事情就是创建了一个UNIX域socket,对应VpnService中的LocalSocket,通过ancil_recv_fd接收VpnService发送过来的tun fd。ancil_recv_fd方法是libancillary提供的,这个库的作用就是通过UNIX域socket跨进程传递fd,库的实现就不展开分析。这个方法最后返回了接收到的fd,这个fd如何使用,我们在tun2socks一节里分析。

ss-local -> VpnService

还有一个fd,是由ss-local发送给VpnService,同样是通过libancillary发送,实现如下:

int
protect_socket(int fd)
{
    int sock;
    struct sockaddr_un addr;

    if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        LOGE("[android] socket() failed: %s (socket fd = %d)\n", strerror(errno), sock);
        return -1;
    }

    // Set timeout to 3s
    struct timeval tv;
    tv.tv_sec  = 3;
    tv.tv_usec = 0;
    setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval));
    setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(struct timeval));

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, "protect_path", sizeof(addr.sun_path) - 1);

    if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        LOGE("[android] connect() failed for protect_path: %s (socket fd = %d)\n",
             strerror(errno), sock);
        close(sock);
        return -1;
    }

    if (ancil_send_fd(sock, fd)) {
        ERROR("[android] ancil_send_fd");
        close(sock);
        return -1;
    }

    char ret = 0;

    if (recv(sock, &ret, 1, 0) == -1) {
        ERROR("[android] recv");
        close(sock);
        return -1;
    }

    close(sock);
    return ret;
}

查看调用发现在如下方法中。

static void
server_stream(EV_P_ ev_io *w, buffer_t *buf) {
    // ...
    #ifdef __ANDROID__
        if (vpn) {
            int not_protect = 0;
            if (remote->addr.ss_family == AF_INET) {
                struct sockaddr_in *s = (struct sockaddr_in *)&remote->addr;
                if (s->sin_addr.s_addr == inet_addr("127.0.0.1"))
                    not_protect = 1;
            }
            if (!not_protect) {
                if (protect_socket(remote->fd) == -1) {
                    ERROR("protect_socket");
                    close_and_free_remote(EV_A_ remote);
                    close_and_free_server(EV_A_ server);
                    return;
                }
            }
        }
    #endif
    // ...
}

这个方法太长,只贴了相关的一部分,这里涉及到shadowsocks基于libev实现的事件驱动,在ss-local一节会详细分析,这里我们只需要知道,通过libancillary发送给VpnService的,其实是ss-local和远程服务端建立连接的fd。我们看VpnService对fd的处理:

private inner class ProtectWorker : ConcurrentLocalSocketListener("ShadowsocksVpnThread",
        File(Core.deviceStorage.noBackupFilesDir, "protect_path")) {
    override fun acceptInternal(socket: LocalSocket) {
        socket.inputStream.read()
        val fd = socket.ancillaryFileDescriptors!!.single()!!
        CloseableFd(fd).use {
            socket.outputStream.write(if (underlyingNetwork.let { network ->
                        if (network != null && Build.VERSION.SDK_INT >= 23) try {
                            network.bindSocket(fd)
                            true
                        } catch (e: IOException) {
                            // suppress ENONET (Machine is not on the network)
                            if ((e.cause as? ErrnoException)?.errno != 64) printLog(e)
                            false
                        } else protect(getInt.invoke(fd) as Int)
                    }) 0 else 1)
        }
    }
}

上层通过LocalSocket接收了ss-local发过来的fd,并通过平台API给fd添加了例外,使得这个fd的数据读写不经过tun设备。

tun2socks实现

上一节说到,tun2socks在wait_for_fd方法中通过UNIX域socket获取到了fd,我们来看一下接下来对fd的处理。
首先是一堆初始化操作:

// initialize network
if (!BNetwork_GlobalInit()) {
    BLog(BLOG_ERROR, "BNetwork_GlobalInit failed");
    goto fail1;
}

// process arguments
if (!process_arguments()) {
    BLog(BLOG_ERROR, "Failed to process arguments");
    goto fail1;
}

// init time
BTime_Init();

// init reactor
if (!BReactor_Init(&ss)) {
    BLog(BLOG_ERROR, "BReactor_Init failed");
    goto fail1;
}

// set not quitting
quitting = 0;

// setup signal handler
if (!BSignal_Init(&ss, signal_handler, NULL)) {
    BLog(BLOG_ERROR, "BSignal_Init failed");
    goto fail2;
}

这一段里最关键的是BReactor_Init方法,它初始化了一个BReactor结构,我们看一下这个结构定义:

typedef struct {
    int exiting;
    int exit_code;
    
    // jobs
    BPendingGroup pending_jobs;
    
    // timers
    BReactor__TimersTree timers_tree;
    LinkedList1 timers_expired_list;
    
    // limits
    LinkedList1 active_limits_list;
    
    #ifdef BADVPN_USE_WINAPI
    LinkedList1 iocp_list;
    HANDLE iocp_handle;
    LinkedList1 iocp_ready_list;
    #endif
    
    #ifdef BADVPN_USE_EPOLL
    int efd; // epoll fd
    struct epoll_event epoll_results[BSYSTEM_MAX_RESULTS]; // epoll returned events buffer
    int epoll_results_num; // number of events in the array
    int epoll_results_pos; // number of events processed so far
    #endif
    
    #ifdef BADVPN_USE_KEVENT
    int kqueue_fd;
    struct kevent kevent_results[BSYSTEM_MAX_RESULTS];
    int kevent_prev_event[BSYSTEM_MAX_RESULTS];
    int kevent_results_num;
    int kevent_results_pos;
    #endif
    
    #ifdef BADVPN_USE_POLL
    LinkedList1 poll_enabled_fds_list;
    int poll_num_enabled_fds;
    int poll_results_num;
    int poll_results_pos;
    struct pollfd *poll_results_pollfds;
    BFileDescriptor **poll_results_bfds;
    #endif
    
    DebugObject d_obj;
    #ifndef BADVPN_USE_WINAPI
    DebugCounter d_fds_counter;
    #endif
    #ifdef BADVPN_USE_KEVENT
    DebugCounter d_kevent_ctr;
    #endif
    DebugCounter d_limits_ctr;
} BReactor;

BReactor其实是一个封装了各平台差异的结构,上层可以直接通过BReactor结构来实现事件驱动,而不用关心各平台底层的实现,无论是epoll,poll还是kevent。shadowsocks-android在编译的时候传入了BADVPN_USE_EPOLL,因此我们只要关心epoll相关的实现。
efd在BReactor_Init中被初始化,就是epoll_create返回的fd,这样就简单了,我们只需要看一下epoll_ctl的调用,看看哪些fd被挂到了epoll上,就能理清整体的逻辑。
epoll_ctl一共在三个方法被调用,三个方法分别是:

int BReactor_AddFileDescriptor (BReactor *bsys, BFileDescriptor *bs)
void BReactor_RemoveFileDescriptor (BReactor *bsys, BFileDescriptor *bs)
void BReactor_SetFileDescriptorEvents (BReactor *bsys, BFileDescriptor *bs, int events)

分别对应EPOLL_CTL_ADD,EPOLL_CTL_DEL和EPOLL_CTL_MOD操作。这里我们重点关注一下EPOLL_CTL_ADD,查一下BReactor_AddFileDescriptor的调用,每个BReactor_AddFileDescriptor都是和BFileDescriptor_Init成对出现的,BFileDescriptor_Init的实现:

void BFileDescriptor_Init (BFileDescriptor *bs, int fd, BFileDescriptor_handler handler, void *user)
{
    bs->fd = fd;
    bs->handler = handler;
    bs->user = user;
    bs->active = 0;
}

结合BFileDescriptor的定义:

/**
 * File descriptor object used with {@link BReactor}.
 */
typedef struct BFileDescriptor_t {
    int fd;
    BFileDescriptor_handler handler;
    void *user;
    int active;
    int waitEvents;
    
    #ifdef BADVPN_USE_EPOLL
    struct BFileDescriptor_t **epoll_returned_ptr;
    #endif
    
    #ifdef BADVPN_USE_KEVENT
    int kevent_tag;
    int kevent_last_event;
    #endif
    
    #ifdef BADVPN_USE_POLL
    LinkedList1Node poll_enabled_fds_list_node;
    int poll_returned_index;
    #endif
} BFileDescriptor;

不难看出,BFileDescriptor_Init把真正的fd和处理事件的callback绑定起来了,BFileDescriptor则是用于封装底层实现的结构体。所以只要找到各个fd对应的BFileDescriptor_handler实现,就能理清tun2socks的整体逻辑。

ss-local实现

libev

LocalDnsService实现

redsocks实现透明代理

UDP协议代理

加解密实现

gfwlist

郑重声明

本文所涉及到的所有内容仅用于技术学习研究,任何非法用途产生的后果自负,与本人无关