你也能撸一个 V2Ray
FQ技术则通过加密和伪装等方案突破网络审查,目前普遍采用的方式都是客户端-服务端的方式;在客户端也就是用户自己的机器上,在流量进入互联网之前,对流量进行加密和伪装,从而穿透Q的层层审查,然后在服务端还原为原始的流量访问目标网站;服务端必须是Q外的服务器,如下图所示:

由于用户的网络环境五花八门,而且国际出口拥堵的时候会对一般线路降权处理,因此不少FQ服务商会在国内增加一个BGP节点进行中转;如,长城宽带没有自己的国际出口,是向电信和联通租用的国际出口,会限制访问国外网站的速度和稳定性(我个人经验是有可能限制在2Mbps下),通过国内中转节点的话就能避免这个问题。
中转方案如下图所示(如果是通过国际专线,不经过GFW审查):

V2Ray灵活的配置使得其能够胜任上述的各种情况,比如多入口多出口路由就尤其适用于中转服务器,内建多种协议配合使用也能够满足各种用户需求和多样的网络环境。
V2Ray使用Go语言开发,因此可以方便的编译出适用与各种操作系统的内核,并且简单高效的运行在诸如MacOS、Windows、OpenWrt和各种Linux系统中;其自研的VMess协议也得到了大部分移动客户端的支持。
所以V2Ray迅速火了起来,写此文目的是简要分析一下V2Ray的源代码,学习Go语言和掌握相关的网络传输技术。
V2Ray代码结构
V2Ray的核心项目为v2ray-core,其项目结构如下:
v2ray-core
├── app // 应用模块
│ ├── dispatcher // 用于把入站代理所接收到的数据,传送给出站代理
│ ├── router // 路由
│ ├── dns // 内置的 DNS 缓存
│ ├── proxyman // 代理管理器
├── common // 公用代码
├── proxy // 各协议具体实现
│ ├── blackhole
│ ├── dokodemo-door
│ ├── freedom
│ ├── socks
│ ├── vmess
├── transport // 传输模块程序启动的时候解析配置文件,将配置文件的不同部分构建为不同组件自有的配置。
// v2ray.com/core/infra/conf/v2ray.gofunc (c *Config) Build() {
c.Api.Build()
c.Stats.Build()
c.LogConfig.Build()
c.RouterConfig.Build()
c.DNSConfig.Build()
c.Policy.Build()
c.Reverse.Build()
for rawInboundConfig in c.InboundConfigs {
rawInboundConfig.Build()
} for rawOutboundConfig in c.OutboundConfigs {
rawOutboundConfig.Build()
}
}
不同组件已经预先注册了创建方法,因此通过上述方法得到的配置即可用来创建组件;下面显示的是注册组件的代码,是通过在main.go中的import _ 执行各个包中所有init()方法来实现的:
// v2ray.com/core/app/proxyman/inbound/inbound.gofunc init(){
common.RegisterConfig(
(*core.InboundHandlerConfig)(nil),
func(ctx context.Context,
config interface{})(interface{}, error){
return NewHandler(ctx, config)
},
)
}
组件创建成功后即逐一启动,下图展示的是代理模块的处理逻辑,以本地开启Socks5代理并与远端的VMess服务建立通道为例:
- 蓝色部分为Inbound逻辑,其中tcpWorker.Start()即启动本地TCP监听,有连接请求时则调用Socks5协议Server.Process()进行处理,此处根据 Socks5协议 解析请求,提取出要访问的目标网站,并调用后续的Route/Outbound/Transport逻辑将请求加密和传输
- 绿色部分为Route路由逻辑,根据路由配置选择对应的Outbound
- 黄色部分为Outbound逻辑,调用VMess协议的Outbound部分的Handler.Process()来处理,此处根据VMess协议对请求进行编码,同时对远端的响应进行解码
- 红色部分为Transport传输逻辑,根据Outbound设置中的传输协议来传输数据,如TLS协议、WS协议等

远程服务端接收到请求后以相同的逻辑执行,即以VMess协议的inbound部分处理接入请求,接着使用Freedom协议访问目标网站,再将目标网站的响应数据通过VMess协议加密后返回客户端。
VMess协议
VMess协议 是V2Ray的原创协议,主要解决了两个问题:
- 用户的鉴权
- 数据的加密
本质上是对流量增加了一层编解码的逻辑,在Q看来是未知协议的TCP连接,此乃最大特征,至少在敏感时期就可以轻易被Q阻断,因此一般建议配合TLS或者WS来使用。
此处简述 VMess协议 中的「标准格式」的数据传输方式,即指令部分的Opt为0x01时的处理方式。
- 客户端将原始的请求加密,得到一条加密的请求数据,包含了认证信息,指令和三个部分
- 服务端接收到加密的请求,还原为原始请求并访问目标网站,获取响应数据后对其加密发回客户端
- 客户端接收到加密的响应,解密并返回给浏览器

小结
以下是个人粗浅看法 ~
过多的配置项本来没有什么问题,毕竟项目的出发点是做一个大而全的流量代理平台,自然需要很多配置项才能够定制灵活的路由和承载各种各样的协议。
但是在代码中似乎过度的使用了 Protocol Buffers 来定义配置项目,第一次看代码的时候我很惊讶很多package都有一个config.proto文件及其生成的config.pb.go代码,要知道PB的本意是用来跨平台交换数据的,只用来定义对象就明显有种杀鸡用牛刀的感觉,代码更多更复杂,而且多了不必要的类型转换,比如:
/infra/conf/vmess.go::VMessAccount
/proxy/vmess/account.pb.go::Account
/proxy/vmess/account.go::MemoryAccount这几个struct包含的字段都是id/alterId/security/testEnabled,试想一下如果要增加一个配置项,光定义就要改三个地方,更不用说相关的类型转换和业务逻辑了。
这些config.proto互相嵌套引用,使得真正需要用来做API调用的对象反而变得复杂,比如要用python来远程调用统计接口时,发现层层引用之后,居然需要9个proto文件才能生成python的客户端代码,调用逻辑写出来也是极为啰嗦的。
第二点,把Context当成Java的ThreadLocal来使用,传递了很多参数,包括Inbound/Outbound/Connection Meta等等。是否使用上下文来传递参数一直是有争论的,我认同 draveness 的观点,即,这不是一个好的设计。
使用上下文传递参数实际上是实现了一个参数可以随意变化的接口,对于V2Ray这种可拔插的组件架构,会导致代码难以阅读和维护,因为无法确定哪个组件对上下文中的参数做了哪些处理,往往只有运行时才能确定;初次接触项目的时候,经常会发现有参数从上下文中取出来了,但是一时半会找不到是在哪里写进去的,很容易迷失;此外,随着越来越多人加入项目,由于通过上下文传递参数是「方便」的途径,渐渐的就会加入各种奇奇怪怪的东西了。
总而言之,V2Ray的代码算是比较难看懂和维护的那种,有浓浓的Java风格,总感觉是Java出身的程序员的作品,让我回想起被Java的类爆炸和过度设计模式支配的痛苦,但是这并不妨碍它成为一个成功的项目,上面讲的两点也仅仅是皮毛,并未涉及V2Ray的核心,即灵活的路由和多协议支持;在内存使用和性能上,也有颇见功力的优化,是一个值得学习的Go语言项目.
为了学习Go语言和掌握相关的网络传输技术,我撸了一个项目V2Simple,实现的是一个简单版本的V2Ray,可以无缝对接一些简单配置的V2Ray客户端或者服务端,核心代码不过100行,如果你有兴趣,不妨star一下。
from https://medium.com/@jarvisgally/v2ray-%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90-b4f8db55b0f6
No comments:
Post a Comment