shadowsocks-libev源码分析 1.什么是shadowsocks? shadowsocks-libev 是一个轻量的socks5代理,可用于伪装ip访问服务器,底层基于libev事件驱动库,非常轻量高效
2.shadowsocks协议解析
类型
目标ip地址
目标端口
数据
长度(Byte)
1
可变
2
可变
ATYP: 协议类型
* 0x01 ipv4的ip地址
* 0x03 域名
* 0x04 ipv6的ip地址
当目标服务器使用域名时
类型
域名长度
域名
目标端口
数据
长度(Byte)
1
1
2
2
可变
3.shadowsocks-libev核心文件
4.shadowsocks-libev源码解析 shadowsocks-libev分为客户端和服务端,因此我们需要两份代码,一个客户端代码一个服务段代码。
4.1.ss-server(服务端) 1.我们先看shadowsocks-libev ss-server服务端程序开始监听客户端连接的程序代码段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 if (mode != UDP_ONLY) { int num_listen_ctx = 0 ; for (int i = 0 ; i < server_num; i++) { const char *host = server_addr[i].host; const char *port = server_addr[i].port ? server_addr[i].port : server_port; if (plugin != NULL ) { host = plugin_host; } if (host && ss_is_ipv6addr(host)) LOGI("tcp server listening at [%s]:%s" , host, port); else LOGI("tcp server listening at %s:%s" , host ? host : "0.0.0.0" , port); int listenfd; listenfd = create_and_bind(host, port, mptcp); if (listenfd == -1 ) { continue ; } if (listen(listenfd, SSMAXCONN) == -1 ) { ERROR("listen()" ); continue ; } setfastopen(listenfd); setnonblocking(listenfd); listen_ctx_t *listen_ctx = &listen_ctx_list[i]; listen_ctx->timeout = atoi(timeout); listen_ctx->fd = listenfd; listen_ctx->iface = iface; listen_ctx->loop = loop; ev_io_init(&listen_ctx->io, accept_cb, listenfd, EV_READ); ev_io_start(loop, &listen_ctx->io); num_listen_ctx++; if (plugin != NULL ) break ; } if (num_listen_ctx == 0 ) { FATAL("failed to listen on any address" ); } }
我们可以看到这里服务端绑定本机接口并开始监听,等待来自客户端的连接。
2.我们来看看客户端连接ss-server 后的回调函数accept_cb
1 2 3 4 5 6 7 8 9 listen_ctx_t *listener = (listen_ctx_t *)w; int serverfd = accept(listener->fd, NULL , NULL ); if (serverfd == -1 ) { ERROR("accept" ); return ; } ... server_t *server = new_server(serverfd, listener); ev_io_start(EV_A_ & server->recv_ctx->io);
这里回调函数accept_cb函数调用了我们常用的accept函数接收客户端的连接,得到和客户端数据交互的socket,并使用new_server函数创建了一个新的任务,开始等待接收客户端发来的数据
3.我们继续看new_server函数干了什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 static server_t *new_server (int fd, listen_ctx_t *listener) { if (verbose) { server_conn++; LOGI("new connection from client, %d opened client connections" , server_conn); } server_t *server; server = ss_malloc(sizeof (server_t )); memset (server, 0 , sizeof (server_t )); server->recv_ctx = ss_malloc(sizeof (server_ctx_t )); server->send_ctx = ss_malloc(sizeof (server_ctx_t )); server->buf = ss_malloc(sizeof (buffer_t )); memset (server->recv_ctx, 0 , sizeof (server_ctx_t )); memset (server->send_ctx, 0 , sizeof (server_ctx_t )); balloc(server->buf, SOCKET_BUF_SIZE); server->fd = fd; server->recv_ctx->server = server; server->recv_ctx->connected = 0 ; server->send_ctx->server = server; server->send_ctx->connected = 0 ; server->stage = STAGE_INIT; server->frag = 0 ; server->query = NULL ; server->listen_ctx = listener; server->remote = NULL ; server->e_ctx = ss_malloc(sizeof (cipher_ctx_t )); server->d_ctx = ss_malloc(sizeof (cipher_ctx_t )); crypto->ctx_init(crypto->cipher, server->e_ctx, 1 ); crypto->ctx_init(crypto->cipher, server->d_ctx, 0 ); int timeout = max(MIN_TCP_IDLE_TIMEOUT, server->listen_ctx->timeout); ev_io_init(&server->recv_ctx->io, server_recv_cb, fd, EV_READ); ev_io_init(&server->send_ctx->io, server_send_cb, fd, EV_WRITE); ev_timer_init(&server->recv_ctx->watcher, server_timeout_cb, timeout, timeout); cork_dllist_add(&connections, &server->entries); return server; }
这里可以看到new_server函数设置了接收数据后的回调函数server_recv_cb,和发送数据的处理函数server_send_cb
1 2 ev_io_init(&server->recv_ctx->io, server_recv_cb, fd, EV_READ); ev_io_init(&server->send_ctx->io, server_send_cb, fd, EV_WRITE);
4.我们继续看server_recv_cb函数如何对数据进行处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 server_ctx_t *server_recv_ctx = (server_ctx_t *)w; server_t *server = server_recv_ctx->server; remote_t *remote = NULL ; buffer_t *buf = server->buf; if (server->stage == STAGE_STREAM) { remote = server->remote; buf = remote->buf; ev_timer_again(EV_A_ & server->recv_ctx->watcher); } ssize_t r = recv(server->fd, buf->data, SOCKET_BUF_SIZE, 0 ); if (r == 0 ) { close_and_free_remote(EV_A_ remote); close_and_free_server(EV_A_ server); return ; } else if (r == -1 ) { if (errno == EAGAIN || errno == EWOULDBLOCK) { return ; } else { ERROR("server recv" ); close_and_free_remote(EV_A_ remote); close_and_free_server(EV_A_ server); return ; } } if (server->stage == STAGE_STOP) { return ; } tx += r; buf->len = r; int err = crypto->decrypt(buf, server->d_ctx, SOCKET_BUF_SIZE); if (err == CRYPTO_ERROR) { report_addr(server->fd, "authentication error" ); stop_server(EV_A_ server); return ; } else if (err == CRYPTO_NEED_MORE) { if (server->stage != STAGE_STREAM) { server->frag++; } return ; }if (server->stage == STAGE_STREAM) { int s = send(remote->fd, remote->buf->data, remote->buf->len, 0 ); if (s == -1 ) { if (errno == EAGAIN || errno == EWOULDBLOCK) { remote->buf->idx = 0 ; ev_io_stop(EV_A_ & server_recv_ctx->io); ev_io_start(EV_A_ & remote->send_ctx->io); } else { ERROR("server_recv_send" ); close_and_free_remote(EV_A_ remote); close_and_free_server(EV_A_ server); } } else if (s < remote->buf->len) { remote->buf->len -= s; remote->buf->idx = s; ev_io_stop(EV_A_ & server_recv_ctx->io); ev_io_start(EV_A_ & remote->send_ctx->io); } return ; } else if (server->stage == STAGE_INIT) {
这里我们可以看到首先程序先将数据读取到缓冲区
1 ssize_t r = recv(server->fd, buf->data, SOCKET_BUF_SIZE, 0 );
其次对数据包进行了解密
1 int err = crypto->decrypt(buf, server->d_ctx, SOCKET_BUF_SIZE);
然后对数据包进行了初步分析,判断数据包的类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 if (server->stage == STAGE_STOP) { return ; }else if (server->stage == STAGE_INIT) { ... } else if (server->stage == STAGE_STREAM) { ... if (!need_query) { remote_t *remote = connect_to_remote(EV_A_ & info, server); if (remote == NULL ) { LOGE("connect error" ); close_and_free_server(EV_A_ server); return ; } else { server->remote = remote; remote->server = server; if (server->buf->len > 0 ) { brealloc(remote->buf, server->buf->len, SOCKET_BUF_SIZE); memcpy (remote->buf->data, server->buf->data + server->buf->idx, server->buf->len); remote->buf->len = server->buf->len; remote->buf->idx = 0 ; server->buf->len = 0 ; server->buf->idx = 0 ; } ev_io_stop(EV_A_ & server_recv_ctx->io); ev_io_start(EV_A_ & remote->send_ctx->io); } }
这里我们看到当数据包类型为STAGE_STREAM类型时,首先服务器和目标服务器建立了tcp连接
1 remote_t *remote = connect_to_remote(EV_A_ & info, server);
并且如果开始停止接收数据的任务,开始处理发送任务
1 2 3 ev_io_stop(EV_A_ & server_recv_ctx->io); ev_io_start(EV_A_ & remote->send_ctx->io);
5.我们再来看看server_send_cb函数做了什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 static void server_send_cb (EV_P_ ev_io *w, int revents) { server_ctx_t *server_send_ctx = (server_ctx_t *)w; server_t *server = server_send_ctx->server; remote_t *remote = server->remote; if (remote == NULL ) { LOGE("invalid server" ); close_and_free_server(EV_A_ server); return ; } if (server->buf->len == 0 ) { close_and_free_remote(EV_A_ remote); close_and_free_server(EV_A_ server); return ; } else { ssize_t s = send(server->fd, server->buf->data + server->buf->idx, server->buf->len, 0 ); if (s == -1 ) { if (errno != EAGAIN && errno != EWOULDBLOCK) { ERROR("server_send_send" ); close_and_free_remote(EV_A_ remote); close_and_free_server(EV_A_ server); } return ; } else if (s < server->buf->len) { server->buf->len -= s; server->buf->idx += s; return ; } else { server->buf->len = 0 ; server->buf->idx = 0 ; ev_io_stop(EV_A_ & server_send_ctx->io); if (remote != NULL ) { ev_io_start(EV_A_ & remote->recv_ctx->io); return ; } else { LOGE("invalid remote" ); close_and_free_remote(EV_A_ remote); close_and_free_server(EV_A_ server); return ; } } }
这里程序对缓冲区的程序进行了发送
1 2 ssize_t s = send(server->fd, server->buf->data + server->buf->idx, server->buf->len, 0 );
并且开始停止发送任务,开始处理接收任务
1 2 3 4 ev_io_stop(EV_A_ & server_send_ctx->io);if (remote != NULL ) { ev_io_start(EV_A_ & remote->recv_ctx->io); return ;
我们从以上分析结果得出shadowsocks服务器工作的核心函数有以下几个
4.1.1.核心函数 1 2 3 4 static void remote_recv_cb (EV_P_ ev_io *w, int revents) ; static void remote_send_cb (EV_P_ ev_io *w,int revents) ; static void server_recv_cb (EV_P_ ev_io *w, int revents) ; static void server_send_cb (EV_P_ ev_io *w,int revents) ;
4.1.2.函数调用关系 ss-server接收到客户端的数据,处理数据的函数调用关系图
1 server_recv_cb---->decrypto---send/remote_send_cb----->recv/remote_recv_cb------>crypto---->server_send_cb
其实从函数调用关系图不难得出整个ss-server的工作流程
1.接收数据
2.解密数据
3.将数据发送给目标服务器
4.接收来自远程服务器发送的数据包
5.加密数据
6.将服务器发送的数据包发送给客户端
4.2.ss-local(客户端) 4.2.1.核心函数 1 2 3 4 5 void server_recv_cb (EV_P_ ev_io *w, int revents) ; void server_send_cb (EV_P_ ev_io *w, int revents) ; void remote_recv_cb (EV_P_ ev_io *w, int revents) ; void remote_send_cb (EV_P_ ev_io *w, int revents) ; void server_stream (EV_P_ ev_io *w, buffer_t *buf) ;
1.先看主函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ...if (mode != UDP_ONLY) { int listenfd; #ifdef HAVE_LAUNCHD listenfd = launch_or_create(local_addr, local_port); #else listenfd = create_and_bind(local_addr, local_port); #endif if (listenfd == -1 ) { FATAL("bind() error" ); } if (listen(listenfd, SOMAXCONN) == -1 ) { FATAL("listen() error" ); } setnonblocking(listenfd); listen_ctx.fd = listenfd; ev_io_init(&listen_ctx.io, accept_cb, listenfd, EV_READ); ev_io_start(loop, &listen_ctx.io); } ...
这里很明显可以看清楚,客户端首先创建了socket并绑定端口开始监听连接,从函数名我们很容易看出 accept_cb为客户端接收连接的回调函数 我们耿总accept_cb函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 listen_ctx_t *listener = (listen_ctx_t *)w; int serverfd = accept(listener->fd, NULL , NULL ); if (serverfd == -1 ) { ERROR("accept" ); return ; } setnonblocking(serverfd); int opt = 1 ; setsockopt(serverfd, SOL_TCP, TCP_NODELAY, &opt, sizeof (opt)); #ifdef SO_NOSIGPIPE setsockopt(serverfd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof (opt)); #endif if (tcp_incoming_sndbuf > 0 ) { setsockopt(serverfd, SOL_SOCKET, SO_SNDBUF, &tcp_incoming_sndbuf, sizeof (int )); } if (tcp_incoming_rcvbuf > 0 ) { setsockopt(serverfd, SOL_SOCKET, SO_RCVBUF, &tcp_incoming_rcvbuf, sizeof (int )); } server_t *server = new_server(serverfd); server->listener = listener; ev_io_start(EV_A_ & server->recv_ctx->io);
这里发现客户端接收连接后使用new_server函数创建了一个server对象,继续跟踪new_server函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 server_t *server; server = ss_malloc(sizeof (server_t )); memset (server, 0 , sizeof (server_t )); server->recv_ctx = ss_malloc(sizeof (server_ctx_t )); server->send_ctx = ss_malloc(sizeof (server_ctx_t )); server->buf = ss_malloc(sizeof (buffer_t )); server->abuf = ss_malloc(sizeof (buffer_t )); balloc(server->buf, SOCKET_BUF_SIZE); balloc(server->abuf, SOCKET_BUF_SIZE); memset (server->recv_ctx, 0 , sizeof (server_ctx_t )); memset (server->send_ctx, 0 , sizeof (server_ctx_t )); server->stage = STAGE_INIT; server->recv_ctx->connected = 0 ; server->send_ctx->connected = 0 ; server->fd = fd; server->recv_ctx->server = server; server->send_ctx->server = server; server->e_ctx = ss_malloc(sizeof (cipher_ctx_t )); server->d_ctx = ss_malloc(sizeof (cipher_ctx_t )); crypto->ctx_init(crypto->cipher, server->e_ctx, 1 ); crypto->ctx_init(crypto->cipher, server->d_ctx, 0 ); ev_io_init(&server->recv_ctx->io, server_recv_cb, fd, EV_READ); ev_io_init(&server->send_ctx->io, server_send_cb, fd, EV_WRITE); ev_timer_init(&server->delayed_connect_watcher, delayed_connect_cb, 0.05 , 0 ); cork_dllist_add(&connections, &server->entries);
这里发现server->recv_ctx绑定了函数server_recv_cb和server_send_cb,这个函数从名称上不难猜测分别是服务端接收信息后的回调函数和服务端发送信息后的回调函数。 分别跟踪server_recv_cb函数和server_send_cb函数 server_recv_cb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 if (revents != EV_TIMER) { r = recv(server->fd, buf->data + buf->len, SOCKET_BUF_SIZE - buf->len, 0 ); if (r == 0 ) { close_and_free_remote(EV_A_ remote); close_and_free_server(EV_A_ server); return ; } else if (r == -1 ) { if (errno == EAGAIN || errno == EWOULDBLOCK) { } else { if (verbose) ERROR("server_recv_cb_recv" ); close_and_free_remote(EV_A_ remote); close_and_free_server(EV_A_ server); return ; } } buf->len += r; } while (1 ) { if (server->stage == STAGE_STREAM) { server_stream(EV_A_ w, buf); return ; } else if (server->stage == STAGE_INIT) { .....
这里看到回调函数首先调用了recv函数用于接收请求,然后通过判断server->stage参数的值,看到当server->stage=STAGE_STREAM的时候,会调用server_stream函数,从这里不容易看出server_stream函数到底做了什么,我们跟踪一下server_stream函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 server_ctx_t *server_recv_ctx = (server_ctx_t *)w; server_t *server = server_recv_ctx->server; remote_t *remote = server->remote; if (remote == NULL ) { LOGE("invalid remote" ); close_and_free_server(EV_A_ server); return ; } if (!remote->direct) { #ifdef __ANDROID__ tx += remote->buf->len; #endif int err = crypto->encrypt(remote->buf, server->e_ctx, SOCKET_BUF_SIZE); if (err) { LOGE("invalid password or cipher" ); close_and_free_remote(EV_A_ remote); close_and_free_server(EV_A_ server); return ; } if (server->abuf) { bprepend(remote->buf, server->abuf, SOCKET_BUF_SIZE); bfree(server->abuf); ss_free(server->abuf); server->abuf = NULL ; } } if (!remote->send_ctx->connected) { #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 remote->buf->idx = 0 ; if (!fast_open || remote->direct) { int r = connect(remote->fd, (struct sockaddr *)&(remote->addr), remote->addr_len); if (r == -1 && errno != CONNECT_IN_PROGRESS) { ERROR("connect" ); close_and_free_remote(EV_A_ remote); close_and_free_server(EV_A_ server); return ; } ev_io_stop(EV_A_ & server_recv_ctx->io); ev_io_start(EV_A_ & remote->send_ctx->io); ev_timer_start(EV_A_ & remote->send_ctx->watcher); ....
这里看到,server_stream函数首先调用了encrypto函数将数据进行了加密,然后调用connect函数连接了远程shadowsocks服务器,然后暂时禁用了接收客户端数据后的处理任务,并启动一个任务等待将数据发送给远程服务器 跟踪完server_recv_cb函数我们来看server_send_cb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 server_ctx_t *server_send_ctx = (server_ctx_t *)w; server_t *server = server_send_ctx->server; remote_t *remote = server->remote; if (server->buf->len == 0 ) { close_and_free_remote(EV_A_ remote); close_and_free_server(EV_A_ server); return ;} else { ssize_t s = send(server->fd, server->buf->data + server->buf->idx, server->buf->len, 0 ); if (s == -1 ) { if (errno != EAGAIN && errno != EWOULDBLOCK) { ERROR("server_send_cb_send" ); close_and_free_remote(EV_A_ remote); close_and_free_server(EV_A_ server); } return ; } else if (s < (ssize_t )(server->buf->len)) { server->buf->len -= s; server->buf->idx += s; return ; } else { server->buf->len = 0 ; server->buf->idx = 0 ; ev_io_stop(EV_A_ & server_send_ctx->io); ev_io_start(EV_A_ & remote->recv_ctx->io); return ;
这里发现server_send_cb函数首先调用了send函数将数据返回给客户端,然后停止给客户端发送数据,等待接收来自远程服务器发送来的数据
4.2.2.函数调用关系 1 server_recv_cb-->recv-->encrypto--->remote_send_cb--->remote_recv_cb--->decrypto--->server_send_cb
我们可以总结出整个shadowsock客户端ss-local在运行时做的事情
1.通过socks5协议接收来自需要代理程序发来的socks5流量包,并解包解析里面的数据
2.将实际的数据封装成shadowsocks包,并将其加密发送给远程shadowsocks服务器
3.等待服务器响应数据包,由于接收来自服务器的相应包也是shadowsocks包,因此首先先对包进行解密
4.将解密后的数据包重新封装成socks5数据包,并将数据包返回给客户端。
4.3.shadowsocks工作原理 经过源码分析和各个函数调用关系我们不难得出shadowsocksocks的工作原理
1 client---socks5--->ss-local---crypto----->shadowsocks---->ss-server--->decrypto--->server
1.客户端将数据包用socks5协议重新封装,发送给ss-local,
2.ss-local将数据包重新封装成shadowsock协议格式的数据包,
3.ss-local将数据包加密后发送给ss-server,
4.ss-server收到数据包后将数据包进行解密,按照shadowsock协议解析出实际的payload
5.ss-server开始和目标服务器建立连接,并将数据包发送给目标服务器,
6.目标服务器返回数据给ss-server
7.将目标服务器响应的数据封装成shadowsocks协议
8.ss-server将数据包加密,通过之前和ss-local建立连接将数据包返回给ss-local,
9.ss-local将数据包解密,按照shadowsocks协议规则解析出原始响应报文
10.ss-local将原始响应报文封装成socks5协议发挥个客户端
5.小结 整个过程如果不深究细节,分析shadowsocks原理还是非常简单的,核心点就在于网络编程和libev的理解,因此在分析shadowsocks-libev之前一定要先理解网络编程和libev事件驱动库的使用。