移动端 | 加入收藏 | 设为首页 | 最新ss | 赞助本站 | RSS
 

freefq.comfree——免费、自由fq——翻墙

困在墙内,请发邮件到freefqcom#gmail.com获得最新免费翻墙方法!
您当前的位置:首页 > 网络翻墙技巧

VPN-WS源码解析

时间:2016-07-13  来源:  作者:FWTO_O 条评论

 Fal免费翻墙网

 Fal免费翻墙网

vpn-ws一个开源的,承载协议使用Websocket的VPN实现。代码简单易读,其中对于tuntap/SSL/socket/event的C API使用,有三个平台的版本(Win、Linux和MacOSX1),具有参考价值。

参考

词汇

  • peer,VPN中的节点,对于服务器而言包括自己的tuntap和每个客户端socket
  • tuntap,Linux/Unix中创建的虚拟网卡,读取和写入数据和其它文件设备如socket、file差不多

实现

VPN 的实现无非是客户端创建一个虚拟网卡tun/tap,从tun/tap中读取数据包,发送到服务端,服务端写入到它的tun/tap虚拟网卡中,从而让两 端像是在同一个局域网内。发送数据无论是走UDP/TCP还是websocket,都只是为了转发虚拟网卡中接收到的数据包(以太包或者ip包)。Fal免费翻墙网

客户端与服务端的交互如下:Fal免费翻墙网

vpn-ws client <—HTTP/websocket—> Nginx <—uWSGI—> vpn-ws server

VPN- WS的客户端与Nginx Web交互,通过HTTP/HTTPS传送HTTP数据,而后Nginx通过uWSGI接口协议发送至应用服务即VPN-WS服务端。在完成了 websocket的upgrade协商后,整个通路变成了websocket数据帧的转发通路。Fal免费翻墙网

用Nginx作前端的好处是,可以直接在现有的基于Nginx的服务上添加入口布置这个VPN服务器。客户端有重连机制,但是只有一条连接到服务端,如果拿来翻墙,可就太弱了Fal免费翻墙网

服务端实现

创建tun/tap虚拟网卡

因为Linux视一切设备为File,所以其与file fd(文件句柄),与socket fd使用上没有区别。以此创建一个peer放到全局数组vpn_ws_conf.peers里。Fal免费翻墙网

tuntap_fd = vpn_ws_tuntap(vpn_ws_conf.tuntap_name);Fal免费翻墙网
vpn_ws_peer_create(event_queue, tuntap_fd, vpn_ws_conf.tuntap_mac);Fal免费翻墙网
Fal免费翻墙网
void vpn_ws_peer_create(int queue, vpn_ws_fd client_fd, Fal免费翻墙网
uint8_t *mac) {Fal免费翻墙网
Fal免费翻墙网
vpn_ws_event_add_read(queue, client_fd)Fal免费翻墙网
vpn_ws_peer *peer = vpn_ws_calloc(sizeof(vpn_ws_peer));Fal免费翻墙网
peer->fd = client_fd;Fal免费翻墙网
vpn_ws_conf.peers[client_fd] = peer;Fal免费翻墙网
if (mac) {Fal免费翻墙网
memcpy(peer->mac, mac, 6);Fal免费翻墙网
peer->mac_collected = 1;Fal免费翻墙网
//只有创建虚拟网卡时,peer的raw属性才置为1,这个值Fal免费翻墙网
//决定了websocket数据包是直接转发还是还原成原始数据包再转发Fal免费翻墙网
peer->handshake = 1;Fal免费翻墙网
peer->raw = 1;Fal免费翻墙网
}

创建服务端口,以接收客户端连接

server_fd = vpn_ws_bind(vpn_ws_conf.server_addr);Fal免费翻墙网
vpn_ws_event_add_read(event_queue, server_fd);

接收新客户端连接并分配一个peer节点

为走web socket而来的客户端创建的peer,其raw属性为0,也就是说从这个peer读取出的数据包非原始包(而是websocket数据帧格式)。而handshake属性也为0,则需要与服务端作协议认证交互后才能让这个peer正常使用,即接收和转发数据包。Fal免费翻墙网

int ret = vpn_ws_event_wait(event_queue, events);Fal免费翻墙网
for(int i=0;i<ret;i++) {Fal免费翻墙网
int fd = vpn_ws_event_fd(events, i);Fal免费翻墙网
if (fd == server_fd) {Fal免费翻墙网
vpn_ws_peer_accept(event_queue, server_fd);Fal免费翻墙网
continue;Fal免费翻墙网
}Fal免费翻墙网
Fal免费翻墙网
if (vpn_ws_manage_fd(event_queue, fd)) break;Fal免费翻墙网
}

服务端添加一个peer到peer数组里:Fal免费翻墙网

void vpn_ws_peer_accept(int queue, int fd) {Fal免费翻墙网
int client_fd = accept(fd, (struct sockaddr *) &s_un, &s_len);Fal免费翻墙网
Fal免费翻墙网
vpn_ws_peer *peer = vpn_ws_calloc(sizeof(vpn_ws_peer));Fal免费翻墙网
peer->fd = client_fd;Fal免费翻墙网
if (mac) {Fal免费翻墙网
//只有虚拟网卡才会进这里Fal免费翻墙网
memcpy(peer->mac, mac, 6);Fal免费翻墙网
peer->mac_collected = 1;Fal免费翻墙网
peer->handshake = 1;Fal免费翻墙网
peer->raw = 1;Fal免费翻墙网
}Fal免费翻墙网
vpn_ws_conf.peers[client_fd] = peer;Fal免费翻墙网
}

处理每一个peer事件(发送数据或接收数据)

这里只解释读取的代码。读取部分,主要是做三件事:Fal免费翻墙网

1, 完成HTTP到websocket的协议升级协商。 Fal免费翻墙网
2, 读取websocket的数据帧。忽略掉其中一些ping/pong等无用类型数据。并解出里面的以太网帧格式的数据包Fal免费翻墙网
3, 跟据包的目标MAC地址,进行数据包转发。转发的目标peer在已登录了的所有的peer中查找。

1, 如参考文档所示,The WebSocket Handshake是请求web服务进行HTTP->Websocket协议升级的过程(其实跟HTTP->HTTP2的升级协商几乎是一样的)。因为Nginx与server是通过uWSGI交互的,所以这里HTTP请求头通过uWSGI的api解释出来的。Fal免费翻墙网

请求头除了标准的升级要求的字段,还有自定义字段HTTP_X_VPN_WS_MAC/HTTP_X_VPN_WS_BRIDGE,用来传送客户端peer的MAC地址和是不是bridge的属性:Fal免费翻墙网

#define HTTP_RESPONSE "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "Fal免费翻墙网
Fal免费翻墙网
int64_t vpn_ws_handshake(int queue, vpn_ws_peer *peer) {Fal免费翻墙网
ssize_t rlen = vpn_ws_uwsgi_parse(peer, &modifier1, &modifier2);Fal免费翻墙网
Fal免费翻墙网
char *ws_mac = vpn_ws_peer_get_var(peer,Fal免费翻墙网
"HTTP_X_VPN_WS_MAC", Fal免费翻墙网
17, &ws_mac_len);Fal免费翻墙网
Fal免费翻墙网
char *ws_bridge = vpn_ws_peer_get_var(peer,Fal免费翻墙网
"HTTP_X_VPN_WS_BRIDGE", Fal免费翻墙网
20, &ws_bridge_len);Fal免费翻墙网
Fal免费翻墙网
Fal免费翻墙网
int ret = vpn_ws_write(Fal免费翻墙网
peer, Fal免费翻墙网
http_response, Fal免费翻墙网
sizeof(HTTP_RESPONSE)-1 + ws_accept_len + 4);Fal免费翻墙网
Fal免费翻墙网
}

2, 如果来源peer是raw的(即tuntap/本地虚拟网卡),直接将数据包读取出来就好了。如果这peer是raw的,那么就没有上面的第1步:Fal免费翻墙网

if (peer->raw) {Fal免费翻墙网
data = peer->buf;Fal免费翻墙网
data_len = peer->pos;Fal免费翻墙网
mac = data;Fal免费翻墙网
ws_ret = data_len;Fal免费翻墙网
goto parsed;Fal免费翻墙网
}

不然就是远端的peer,那么还要解码websocket的数据帧格式,得到原始的以太网数据包:Fal免费翻墙网

int64_t vpn_ws_websocket_parse(vpn_ws_peer *peer, uint16_t *ws_header) {Fal免费翻墙网
Fal免费翻墙网
uint8_t byte1 = peer->buf[0];Fal免费翻墙网
uint8_t opcode = byte1 & 0xf;Fal免费翻墙网
uint64_t pktsize = byte2 & 0x7f;Fal免费翻墙网
Fal免费翻墙网
Fal免费翻墙网
switch(opcode) {Fal免费翻墙网
case 0:Fal免费翻墙网
case 1:Fal免费翻墙网
case 2:Fal免费翻墙网
return needed + pktsize;Fal免费翻墙网
case 8:Fal免费翻墙网
return -1;Fal免费翻墙网
case 9:Fal免费翻墙网
case 10:Fal免费翻墙网
*ws_header = 0;Fal免费翻墙网
return needed + pktsize;Fal免费翻墙网
default:Fal免费翻墙网
return -1;Fal免费翻墙网
}Fal免费翻墙网
}

这段代码主要是解释头部格式,找出Payload在websocket数据包中的范围。跟据RFC文档,websocket数据帧里的opcode取值如下,注意binary frame和ping/pong的处理就行了:Fal免费翻墙网

*  %x0 denotes a continuation frameFal免费翻墙网
* %x1 denotes a text frameFal免费翻墙网
* %x2 denotes a binary frameFal免费翻墙网
* %x3-7 are reserved for further non-control framesFal免费翻墙网
* %x8 denotes a connection closeFal免费翻墙网
* %x9 denotes a pingFal免费翻墙网
* %xA denotes a pongFal免费翻墙网
* %xB-F are reserved for further control frames

综上,第1和2步旨在解释出以太网数据包,代码简要如下:Fal免费翻墙网

int vpn_ws_manage_fd(int queue, vpn_ws_fd fd) {Fal免费翻墙网
int ret = vpn_ws_read(peer, 8192);Fal免费翻墙网
Fal免费翻墙网
if (!peer->handshake) {Fal免费翻墙网
int64_t hret = vpn_ws_handshake(queue, peer);Fal免费翻墙网
}Fal免费翻墙网
Fal免费翻墙网
ws_ret = vpn_ws_websocket_parse(peer, &ws_header);Fal免费翻墙网
uint8_t *ws = peer->buf + ws_header;Fal免费翻墙网
uint64_t ws_len = ws_ret - ws_header;Fal免费翻墙网
// 用以整个websocket包进行转发Fal免费翻墙网
data = peer->buf;Fal免费翻墙网
data_len = ws_ret;

3, 转发非多播目标MAC地址的数据包Fal免费翻墙网

目标MAC地址是某个peer的MAC地址或者其bridge下的某个MAC:Fal免费翻墙网

if (b_peer->raw && !peer->raw) {Fal免费翻墙网
//从远程节点的websocket中取数据包写入到虚拟网卡Fal免费翻墙网
wret = vpn_ws_write(Fal免费翻墙网
b_peer, Fal免费翻墙网
peer->buf+ws_header, Fal免费翻墙网
ws_ret-ws_header);Fal免费翻墙网
}Fal免费翻墙网
else if (!b_peer->raw && peer->raw) {Fal免费翻墙网
//从虚拟网卡写入到远程节点websocketFal免费翻墙网
wret = vpn_ws_write_websocket(Fal免费翻墙网
b_peer, Fal免费翻墙网
data, Fal免费翻墙网
data_len);Fal免费翻墙网
}Fal免费翻墙网
else {Fal免费翻墙网
//这里只可能有一种情况即 !b_peer->raw && !peer->raw成立Fal免费翻墙网
//也就是从远程节点转发到另一个远程节点,所以保持整个websocket包进行转发Fal免费翻墙网
wret = vpn_ws_write(b_peer, data, data_len);Fal免费翻墙网
}

客户端实现

1, 创建tun/tap设备,Fal免费翻墙网

vpn_ws_fd tuntap_fd = Fal免费翻墙网
vpn_ws_tuntap(vpn_ws_conf.tuntap_name);

2, 创建连接至Nginx Web端Fal免费翻墙网

int main(){Fal免费翻墙网
vpn_ws_fd tuntap_fd = vpn_ws_tuntap(vpn_ws_conf.tuntap_name);Fal免费翻墙网
vpn_ws_nb(tuntap_fd);Fal免费翻墙网
peer = vpn_ws_calloc(sizeof(vpn_ws_peer));Fal免费翻墙网
memcpy(peer->mac, vpn_ws_conf.tuntap_mac, 6);Fal免费翻墙网
Fal免费翻墙网
if (vpn_ws_connect(peer, vpn_ws_conf.server_addr)) {Fal免费翻墙网
vpn_ws_client_destroy(peer);Fal免费翻墙网
goto reconnect;Fal免费翻墙网
}Fal免费翻墙网
Fal免费翻墙网
Fal免费翻墙网
}

发送HTTP请求进行websocket升级协商。如果使用HTTPS,那么在此前还有SSL的握手:Fal免费翻墙网

int vpn_ws_connect(vpn_ws_peer *peer, char *name) {Fal免费翻墙网
if (!strncmp(cpy, "wss://", 6)) {Fal免费翻墙网
ssl = 1;Fal免费翻墙网
port = 443;Fal免费翻墙网
}Fal免费翻墙网
Fal免费翻墙网
struct hostent *he = gethostbyname(domain);Fal免费翻墙网
Fal免费翻墙网
Fal免费翻墙网
if (connect(peer->fd, Fal免费翻墙网
(struct sockaddr *) &sin, Fal免费翻墙网
sizeof(struct sockaddr_in))) {Fal免费翻墙网
Fal免费翻墙网
vpn_ws_error("vpn_ws_connect()/connect()");Fal免费翻墙网
return -1;Fal免费翻墙网
}Fal免费翻墙网
Fal免费翻墙网
int ret = snprintf(buf, 8192, Fal免费翻墙网
"GET /%s HTTP/1.1\r\nHost: %s%s%s\r\n%sUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: %.*s\r\nX-vpn-ws-MAC: %02x:%02x:%02x:%02x:%02x:%02x%s\r\n\r\n,Fal免费翻墙网
Fal免费翻墙网
)Fal免费翻墙网
Fal免费翻墙网
if (ssl) {Fal免费翻墙网
vpn_ws_conf.ssl_ctx = vpn_ws_ssl_handshake(Fal免费翻墙网
peer, Fal免费翻墙网
domain, Fal免费翻墙网
vpn_ws_conf.ssl_key, Fal免费翻墙网
vpn_ws_conf.ssl_crt);Fal免费翻墙网
Fal免费翻墙网
if (!vpn_ws_conf.ssl_ctx) {Fal免费翻墙网
return -1;Fal免费翻墙网
}Fal免费翻墙网
if (vpn_ws_ssl_write(vpn_ws_conf.ssl_ctx,Fal免费翻墙网
(uint8_t *)buf, ret)) {Fal免费翻墙网
return -1;Fal免费翻墙网
}Fal免费翻墙网
}Fal免费翻墙网
Fal免费翻墙网

等待websocket升级协商回应:Fal免费翻墙网

int http_code = vpn_ws_wait_101(Fal免费翻墙网
peer->fd, Fal免费翻墙网
vpn_ws_conf.ssl_ctx);Fal免费翻墙网
Fal免费翻墙网
if (http_code != 101) {Fal免费翻墙网
vpn_ws_log("error, Fal免费翻墙网
websocket handshake returned code: %d\n", Fal免费翻墙网
http_code);Fal免费翻墙网
Fal免费翻墙网
return -1;Fal免费翻墙网
}

3, 读事件的响应。Fal免费翻墙网

每个客户端要监听的是tuntap和peer到服务端的连接socket这两个fd,接收前者的以太网格式数据包封装成websocket包转发到后者,接收后者的websocket数据包解码成以太网数据包后转发到前者。Fal免费翻墙网

这里的17秒超时设置是为了超时后会发送一个ping包,即每17秒一个ping包的保证。ping包为\x89\x00,这里websocket的数据帧格式,表示FIN=1,opcode=9(PING类型),HAS_MASK=0,Payload length=0:Fal免费翻墙网

for(;;) {Fal免费翻墙网
Fal免费翻墙网
FD_ZERO(&rset);Fal免费翻墙网
FD_SET(peer->fd, &rset);Fal免费翻墙网
FD_SET(tuntap_fd, &rset);Fal免费翻墙网
tv.tv_sec = 17;Fal免费翻墙网
tv.tv_usec = 0;Fal免费翻墙网
int ret = select(max_fd, &rset, NULL, NULL, &tv);Fal免费翻墙网
Fal免费翻墙网
if (ret == 0) {Fal免费翻墙网
// 超时Fal免费翻墙网
if (vpn_ws_client_write(peer, Fal免费翻墙网
(uint8_t *) "\x89\x00", 2)) {Fal免费翻墙网
vpn_ws_client_destroy(peer);Fal免费翻墙网
goto reconnect;Fal免费翻墙网
} Fal免费翻墙网
continue;Fal免费翻墙网
}Fal免费翻墙网
Fal免费翻墙网

处理远端来的websocket数据包,即写入本地的tuntap设备:Fal免费翻墙网

if (FD_ISSET(peer->fd, &rset)) {Fal免费翻墙网
if (vpn_ws_client_read(peer, 8192)) {Fal免费翻墙网
vpn_ws_client_destroy(peer);Fal免费翻墙网
goto reconnect;Fal免费翻墙网
}Fal免费翻墙网
int64_t rlen = vpn_ws_websocket_parse(Fal免费翻墙网
peer, Fal免费翻墙网
&ws_header);Fal免费翻墙网
Fal免费翻墙网
uint8_t *ws = peer->buf + ws_header;Fal免费翻墙网
uint64_t ws_len = rlen - ws_header;Fal免费翻墙网
if (peer->has_mask) {Fal免费翻墙网
//XOR解密…Fal免费翻墙网
}Fal免费翻墙网
Fal免费翻墙网
vpn_ws_full_write(tuntap_fd, ws, ws_len)Fal免费翻墙网
}

转发来自tuntap的以太网数据包以websocket格式封装后转发到服务器:Fal免费翻墙网

if (FD_ISSET(tuntap_fd, &rset)) {Fal免费翻墙网
vpn_ws_recv(tuntap_fd, mtu+8, 1500, rlen);Fal免费翻墙网
Fal免费翻墙网
vpn_ws_client_write(peer, mtu, rlen + 8)Fal免费翻墙网
}

代码不严谨/不妥的地方

C语言直是一门可怕的语言,拿C语言写出大工程更不容易。心疼C语言超弱的表达能力:字符串操作还要自己写,字符串拷贝还要自己写,还要小心什么时候应该释放掉。缺乏面向对象和结构体的权限限制。Fal免费翻墙网

本项目代码存在的一些问题:Fal免费翻墙网

  • 低效的重分配内存。没有内存池复用,只使用了C的realloc,会有频繁的分配内存和内存拷贝(这里又没有对write_buf的收缩,网络状态不好时只能一直涨大下去)
uint64_t available = peer->write_len - peer->write_pos;Fal免费翻墙网
if (available < amount) {Fal免费翻墙网
peer->write_len += amount;Fal免费翻墙网
void *tmp = realloc(peer->write_buf, Fal免费翻墙网
peer->write_len);
  • 可能会指针越界的字符串拷贝(几处地方,一下子找不到了)
  • websocket的代码没有封装,客户端和服务端代码都把websocket的组包和解包的逻辑(尤其是XOR解密MASK的数据)全放入发包的地方,可读性差
  • 没有客户端认证机制?看起来是是个客户端就能连上服务端
  • 缺少合理的封装。比如MAC地址及相关方法。比如vpn_ws_peer这个结构太多成员而且很多不相关,可以封装更多子结构。
  • 客户端与服务端的主循环代码太长,还使用了好多goto,每一个IO调用都要有不同返回值表示出了什么情况,也就是0,小于0,大于0的三种路经,整体逻辑复杂。

  1. Windows并未完全实现,不可用
Fal免费翻墙网

 Fal免费翻墙网

来自https://medium.com/@FWTO_O/vpn-ws%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-7f21809ceaa#.qhn4bq4c6Fal免费翻墙网

来顶一下
返回首页
返回首页
欢迎评论:免登录,输入验证码即可匿名评论 共有条评论
用户名: 密码:
验证码: 匿名发表

推荐资讯

Octohide VPN:快如闪电的免费VPN
Octohide VPN:快如闪
原子网络加速器 - 免费高速VPN 一键链接 方便快捷
原子网络加速器 - 免费
foxovpn绿狐VPN——即连即用、快速、安全
foxovpn绿狐VPN——即
Dubai VPN - Free, Fast & Secure VPN下载
Dubai VPN - Free, Fa
相关文章
栏目更新
栏目热门
墙外新闻
读者文摘

你可以访问真正的互联网了。You can access the real Internet.

管理员精中特别提醒:本网站域名、主机和管理员都在美国,且本站内容仅为非中国大陆网友服务。禁止中国大陆网友浏览本站!若中国大陆网友因错误操作打开本站网页,请立即关闭!中国大陆网友浏览本站存在法律风险,恳请立即关闭本站所有页面!对于您因浏览本站所遭遇的法律问题、安全问题和其他所有问题,本站均无法负责也概不负责。

特别警告:本站推荐各种免费科学上网软件、app和方法,不建议各位网友购买收费账号或服务。若您因付费购买而遭遇骗局,没有得到想要的服务,请把苦水往自己肚子里咽,本站无法承担也概不承担任何责任!

本站严正声明:各位翻墙的网友切勿将本站介绍的翻墙方法运用于违反当地法律法规的活动,本站对网友的遵纪守法行为表示支持,对网友的违法犯罪行为表示反对!

网站管理员定居美国,因此本站所推荐的翻墙软件及翻墙方法都未经测试,发布仅供网友测试和参考,但你懂的——翻墙软件或方法随时有可能失效,因此本站信息具有极强时效性,想要更多有效免费翻墙方法敬请阅读本站最新信息,建议收藏本站!本站为纯粹技术网站,支持科学与民主,支持宗教信仰自由,反对恐怖主义、邪教、伪科学与专制,不支持或反对任何极端主义的政治观点或宗教信仰。有注明出处的信息均为转载文章,转载信息仅供参考,并不表明本站支持其观点或行为。未注明出处的信息为本站原创,转载时也请注明来自本站。

鉴于各种免费翻墙软件甚至是收费翻墙软件可能存在的安全风险及个人隐私泄漏可能,本站提醒各位网友做好各方面的安全防护措施!本站无法对推荐的翻墙软件、应用或服务等进行全面而严格的安全测试,因此无法对其安全性做保证,无法对您因为安全问题或隐私泄漏等问题造成的任何损失承担任何责任!

S. Grand Ave.,Suite 3910,Los Angeles,CA 90071

知识共享许可协议
本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。