Giter VIP home page Giter VIP logo

v2simple's Introduction

V2Simple

该项目实现了一个简单版本的V2Ray。如果你对V2Ray等代理软件的运行机制很好奇,又没有时间研究其庞杂的实现,不妨关注此项目,核心代码不过100行,其余代码则是几个主要协议的具体实现。

目前实现了 VMess协议 的客户端和服务端以及一个简洁的路由,只需简单配置即可无缝对接如下配置的V2Ray客户端或者服务端:

以下V2Ray的功能暂不支持

  • VMess协议的AEAD认证以及动态端口
  • 多入口多出口路由
  • Mux/mKCP/WebSocket/H2等协议
  • 流量统计以及用户管理
  • DNS服务

接下来继续完成的功能点:

  • 支持 UDP over TCP
  • 支持 VLess

编译和使用

编译

要求 Go >= 1.14:

git clone https://github.com/jarvisgally/v2simple
cd v2simple && go build -o bin

客户端模式

客户端模式在本地启动一个socks5代理,对流量进行加密和伪装,随后转发到Q外的服务器上:

# 先将项目中的client.example.json复制到bin目录中,修改里面远程服务器的域名,并重命名为client.json
bin/v2simple -f client.json

服务端模式

服务器模式监听一个可以远程连接的接口,将流量还原为原始流量随后直接访问目标网站:

# 先将项目中的server.example.json复制到bin目录中,修改里面域名证书和私钥地址,并重命名为server.json
bin/v2simple -f server.json

配置文件

客户端模式client.example.json

{
  "local": "socks5://127.0.0.1:1081",
  "route": "whitelist",
  "remote": "vmesss://a684455c-b14f-11ea-bf0d-42010aaa0003@<fix-me>:443?alterID=4"
}

服务端模式server.example.json

{
  "local": "vmesss://[email protected]:443?alterID=4&cert=<fix-me>&key=<fix-me>&fallback=:80",
  "remote": "direct://"
}

其中local项标识本地服务的协议,remote项标识远端服务的协议,目前支持的协议:

  • socks5:仅服务端,用于监听本地的socks5代理请求
  • direct:直连
  • vmess:支持客户端和服务端
vmess://uuid[:alterId]@host:port?[&security=none|aes-128-gcm|chacha20-poly1305]
 - security:在服务端模式下是无需设置的,服务端会根据客户端的请求自动匹配
  • vmesss:使用tls的vmess,同样支持客户端和服务端,服务端还需要额外指定域名的证书和私钥地址
vmesss://uuid[:alterId]@host:port?[&security=none|aes-128-gcm|chacha20-poly1305][&fallback=:80]
 - security:在服务端模式下是无需设置的,服务端会根据客户端的请求自动匹配
 - fallback: 即使用http直接访问443端口会转跳到此地址

注:纯vmess的协议是是基于TCP的,不推荐裸奔;在例子中我们均使用了vmesss,即伪装成常规的https流量;这需要预先注册一个域名并申请TLS证书,具体做法可以参考这里

route项表示路由模式,目前支持如下模式:

  • whitelist:白名单模式,如果匹配,则直接访问,否则通过代理访问
  • blacklist:黑名单模式,如果匹配,则通过代理访问,否则直接访问
  • 空白:不做匹配,全部流量通过代理访问

目前项目中仅包含whitelist,每一行是一个域名、IP或者CIDR,来自V2Ray的geosite:cngeoip:cngeoip:private


核心代码

V2Ray的调用栈大致如下,层层叠叠的调用导致代码比较复杂,此处仅供参考:

+ InboundHandler.Start() - 入口启动
+   Worker.Start() - 启动TCP和UDP监听,并持续等待连接
+     Server.Process()
+       Server.processTCP() & Server.handleUDPPayload() - 处理连接
+       Server.transport() - 调用后续的路由和传输逻辑
+         DefaultDispatcher.Dispatch() - 路由逻辑
+           Router.PickRoute() - 选择Outbound
+             OutBoundHandler.Process() - 出口处理
+               Dailer.Dailer() - 连接远端并转发流量

更详细的说明请参考 V2Ray源码分析

作为对比,V2Simple的核心逻辑都在main.go中,通过三个go关键字,比较简单的就实现了支持多个连接的流量转发逻辑:

+ 启动TCP监听 - net.Listen
+   持续等待连接 - go for listener.accept
+     处理连接 - go server.handshake -> route -> client.handshake
+       转发 - go io.copy

具体代码如下:

// 开启本地的TCP监听
listener, err := net.Listen("tcp", localServer.Addr())
if err != nil {
    log.Printf("can not listen on %v: %v", localServer.Addr(), err)
    os.Exit(-1)
}
log.Printf("%v listening TCP on %v", localServer.Name(), localServer.Addr())
go func() {
    for {
        lc, err := listener.Accept()
        if err != nil {
            errStr := err.Error()
            if strings.Contains(errStr, "closed") {
                break
            }
            log.Printf("failed to accepted connection: %v", err)
            if strings.Contains(errStr, "too many") {
                time.Sleep(time.Millisecond * 500)
            }
            continue
        }
        go func() {
            defer lc.Close()
            var client proxy.Client

            // 不同的服务端协议各自实现自己的响应逻辑, 其中返回的地址则用于匹配路由
            // 常常需要额外编解码或者流量统计的功能,故需要给lc包一层以实现这些逻辑,即wlc
            wlc, targetAddr, err := localServer.Handshake(lc)
            if err != nil {
                log.Printf("failed in handshake from %v: %v", localServer.Addr(), err)
                return
            }

            // 匹配路由
            if conf.Route == whitelist { // 白名单模式,如果匹配,则直接访问,否则使用代理访问
                if matcher.Check(targetAddr.Host()) {
                    client = directClient
                } else {
                    client = remoteClient
                }
            } else if conf.Route == blacklist { // 黑名单模式,如果匹配,则使用代理访问,否则直接访问
                if matcher.Check(targetAddr.Host()) {
                    client = remoteClient
                } else {
                    client = directClient
                }
            } else { // 全部流量使用代理访问
                client = remoteClient
            }
            log.Printf("%v to %v", client.Name(), targetAddr)

            // 连接远端地址
            dialAddr := remoteClient.Addr()
            if _, ok := client.(*direct.Direct); ok { // 直接访问则直接连接目标地址
                dialAddr = targetAddr.String()
            }
            rc, err := net.Dial("tcp", dialAddr)
            if err != nil {
                log.Printf("failed to dail to %v: %v", dialAddr, err)
                return
            }
            defer rc.Close()

            // 不同的客户端协议各自实现自己的请求逻辑
            wrc, err := client.Handshake(rc, targetAddr.String())
            if err != nil {
                log.Printf("failed in handshake to %v: %v", dialAddr, err)
                return
            }

            // 流量转发
            go io.Copy(wrc, wlc)
            io.Copy(wlc, wrc)
        }()
    }
}()

VMess协议

VMess协议 是V2Ray的原创协议,主要解决了两个问题:

  • 用户的鉴权
  • 数据的加密

本质上是对流量增加了一层编解码的逻辑,在Q看来是未知协议的TCP连接,此乃最大特征,至少在敏感时期就可以轻易被Q阻断,因此一般建议配合TLS或者WS来使用。

此处简述 VMess协议 中的「标准格式」的数据传输方式,即指令部分的Opt为0x01时的处理方式:

  1. 客户端将原始的请求加密为 认证信息 + 指令部分 + 数据部分
    1. 认证信息为16字节,使用用户id和时间戳生成,服务端通过此认证信息匹配用户,并且判断是否存在来自Q的 重放攻击
    2. 指令部分比较复杂,主要包含:
      1. 响应认证 - 服务端使用此认证作为头部将加密后的响应数据返回
      2. 原始请求的协议和目标地址 - 服务端以此访问目标地址
      3. 数据部分的对称加密算法和相关密码 - 服务端以此解密请求数据,并以同样的参数加密响应数据
      4. 将整个指令部分加密
    3. 数据部分包含:
      1. 实际的请求数据被分割为若干个小块,每个小块由2个字节的长度和数据包组成
      2. 每个数据包都会使用指令部分的相关参数进行加密
  2. 服务端接受到请求
    1. 校验请求的认证信息
      1. 检测用户是否存在
      2. 处理重放攻击等
    2. 如果认证通过,解密指令部分可获得:
      1. 响应认证
      2. 原始请求的协议和目标地址
      3. 数据部分的解密方法
    3. 解密数据部分,并以此访问目标网站
    4. 对目标网站的响应数据进行加密,生成加密后的响应数据包,主要包括:
      1. 响应认证
      2. 使用与请求同样的方法加密后的响应数据
  3. 客户端接收到加密后响应数据
    1. 校验响应认证
    2. 使用与请求一样的数据解密响应数据,并返回给浏览器

上述1.i的认证信息生成方式如下,为16字节的hash值:

H = MD5
K = 用户ID
M = UTC时间,精确到秒,取值为当前时间的前后30秒随机值
Hash = HMAC(H, K, M)

服务端会运行一个协程,不断的对已经注册的用户id生成前后120秒内的所有hash值,以此校验认证信息是合法的,这也是V2Ray要求客户端和服务器的时间不能有太大误差的原因。

上述1.ii的指令部分格式如下:

1 字节 16 字节 16 字节 1 字节 1 字节 4 位 4 位 1 字节 1 字节 2 字节 1 字节 N 字节 P 字节 4 字节
版本号 Ver 数据加密 IV 数据加密 Key 响应认证 V 选项 Opt 余量 P 加密方式 Sec 保留 指令 Cmd 端口 Port 地址类型 T 地址 A 随机值 校验 F

上述1.iii的数据部分被分割成小块,每一个块的数据格式如下,其中数据包由指定的加密算法加密:

2 字节 L 字节
长度 L 数据包

参考

该项目参考了如下资源:

  • 你也能写个 Shadowsocks ,当前项目参考了里面关于SOCKS5协议的实现;如果标题党一下,本文或许也可改为「你也能撸一个 V2Ray」吧
  • Clash ,VMess的客户端部分
  • V2Ray

v2simple's People

Contributors

jarvisgally avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

v2simple's Issues

大佬还在吗?我研究这个demo一个月了

我把本地和vps的服务跑起来以后网络连接还是不正常,只有极个别网站能勉强访问,我用的是无加密方式的,也就是用chunk.go里的读写数据的方法,我想知道这个demo的功能是否是完整的,我计划正常跑起这个项目以后再去研究v2ray-core的

标准的tls握手失败

大概是下面的代码
proxy -> tls -> client.go
func (c *Client) Handshake(underlay net.Conn, target string) (io.ReadWriter, error) { cc := stdtls.Client(underlay, c.tlsConfig) // 这一行返回EOF err := cc.Handshake() if err != nil { return nil, err } return c.inner.Handshake(cc, target) }
vmss服务器(v2ray)是一个可以正常提供服务。

然后报错是EOF

v2ray 错误日志如下

image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.