diff --git a/README.md b/README.md index 533eafa..4debcfc 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ verysimple, 实际上 谐音来自 V2ray Simple (显然只适用于汉语母 verysimple项目大大简化了 转发机制,能提高运行速度。本项目 转发流量时,关键代码直接放在main.go里!非常直白易懂 -只有项目名称是v2ray_simple,其它所有场合全 使用 verysimple 这个名称,可简称 "vs"。 +只有项目名称是v2ray_simple,其它所有场合 全使用 verysimple 这个名称,可简称 "vs"。 规定,编译出的文件名必须以 verysimple 开头. @@ -130,22 +130,57 @@ cp server.example.json server.json ```sh #客户端 -v2ray_simple -c client.json +verysimple -c client.json #服务端 -v2ray_simple -c server.json +verysimple -c server.json ``` -如果你不是放在path里的,则要 `./v2ray_simple`, 前面要加一个点和一个斜杠。windows没这个要求。 +如果你不是放在path里的,则要 `./verysimple`, 前面要加一个点和一个斜杠。windows没这个要求。 + +注意,如果你是自己直接 go build 编译的,则可执行文件与项目名称一致,为 v2ray_simple; + +如果用的下载的官方编译版本,则可执行文件叫做 verysimple. 可以通过文件名称判断是自己编译的还是下载的。 + +官方发布版统一叫做verysimple是为了与 v2ray区别开。 关于 vlesss 的配置,查看 server.example.json和 client.example.json就知道了,很简单的。 目前配置文件最短情况一共就4行,其中两行还是花括号,这要是还要我解释我就踢你的屁股。 +如果学会了配置后,如果你使用v1.0.5以及更新版本,还可以用如下命令来运行,无需配置文件 + +```sh +#客户端 +verysimple -L=socks5://127.0.0.1:10800 -D=vlesss://你的uuid@你的服务器ip:443?insecure=true + +#服务端 +verysimple -L=vlesss://你的uuid@你的服务器ip:443?cert=cert.pem&key=cert.key&version=0&fallback=:80 +``` + ## 验证方式 对于功能的golang test,请使用 `go test ./...` 命令。如果要详细的打印出test的过程,可以添加 -v 参数 +## 关于证书 + +不要在实际场合使用我提供的证书!自己生成!而且最好是用 自己真实拥有的域名,使用acme.sh等脚本申请免费证书,特别是建站等情况。 + +而且用了真证书后,别忘了把配置文件中的 `insecure=true` 给删掉. + +使用自签名证书是会被中间人攻击的,再次特地提醒。如果被中间人攻击,就能直接获取你的uuid,然后你的服务器 攻击者就也能用了。 + +要想申请真实证书,仅有ip是不够的,要拥有一个域名。本项目提供的自签名证书仅供快速测试使用,切勿用于实际场合。 + +### 生成自签名证书 + +注意运行第二行命令时会要求你输入一些信息。确保至少有一行不是空白即可,比如打个1 +```sh +openssl ecparam -genkey -name prime256v1 -out cert.key +openssl req -new -x509 -days 7305 -key cert.key -out cert.pem +``` + +此命令会生成ecc证书,这个证书比rsa证书 速度更快, 有利于网速加速(加速tls握手)。 ## 开发标准以及理念 @@ -206,24 +241,12 @@ verysimple 继承 v2simple的一个优点,就是服务端的配置也可以用 不过,显然url无法配置大量复杂的内容,而且有些玩家也喜欢一份配置可以搞定多种内核,所以未来 verysimple 会推出兼容 v2ray的json配置 的模块。**只是兼容配置格式,不是兼容所有协议!目前只有vless v0 协议是兼容的,可以直接使用现有其它客户端** +目前的json格式被称作“极简模式”,只需给定url就可以完整确认一个协议的配置. + 其它开发计划请参考 https://github.com/hahahrfool/v2ray_simple/discussions/3 -## 生成自签名证书 -注意运行第二行命令时会要求你输入一些信息。确保至少有一行不是空白即可,比如打个1 -```sh -openssl ecparam -genkey -name prime256v1 -out cert.key -openssl req -new -x509 -days 7305 -key cert.key -out cert.pem -``` - -我给出的命令会生成ecc证书,这个证书速度更快, 有利于网速加速(加速tls握手)。 - -不要在实际场合使用我提供的证书!自己生成!而且最好是用 自己真实拥有的域名,使用acme.sh等脚本申请免费证书,特别是建站等情况。 - -使用自签名证书是会被中间人攻击的,再次特地提醒。如果被中间人攻击,就能直接获取你的uuid,然后你的服务器 攻击者就也能用了。 - -仅有ip是不够的,要结合域名。本项目提供的自签名证书仅供快速测试使用,切勿用于实际场合。 ## 测速 测试环境:ubuntu虚拟机, 使用开源测试工具 diff --git a/client.example.json b/client.example.json index 9f06668..819d882 100644 --- a/client.example.json +++ b/client.example.json @@ -1,5 +1,5 @@ { "listen": "socks5://127.0.0.1:10800#myvlesss1", - "dial": "vlesss://a684455c-b14f-11ea-bf0d-42010aaa0003@127.0.0.1:4433?insecure=true&version=0", + "dial": "vlesss://a684455c-b14f-11ea-bf0d-42010aaa0003@127.0.0.1:4433?insecure=true&version=0&utls=true", "route":{ "mycountry":"CN" ,"comment":"route这一项是可选的,如果没给出的话,就不分流;写了mycountry后, 向该国家的ip发送的请求就会直连, 然后其他的过代理;本comment项你可以自行删掉, 注意要删的话, 连着前面的逗号一起删"} } diff --git a/go.mod b/go.mod index 40d5c56..ba0ef6b 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,10 @@ require ( github.com/yl2chen/cidranger v1.0.2 ) +require golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect + require ( + github.com/refraction-networking/utls v1.0.0 github.com/stretchr/testify v1.7.0 // indirect golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 // indirect ) diff --git a/go.sum b/go.sum index dc25f75..25750a0 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/refraction-networking/utls v1.0.0 h1:6XQHSjDmeBCF9sPq8p2zMVGq7Ud3rTD2q88Fw8Tz1tA= +github.com/refraction-networking/utls v1.0.0/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -11,6 +13,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU= github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= +golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38= +golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 h1:A9i04dxx7Cribqbs8jf3FQLogkL/CV2YN7hj9KWJCkc= golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/main.go b/main.go index f8e1a4b..d6e50ea 100644 --- a/main.go +++ b/main.go @@ -161,7 +161,7 @@ func handleNewIncomeConnection(inServer proxy.Server, outClient proxy.Client, th //此时,baseLocalConn里面 正常情况下, 服务端看到的是 客户端的golang的tls 拨号发出的 tls数据 // 客户端看到的是 socks5的数据, 我们首先就是要看看socks5里的数据是不是tls,而socks5自然 IsUseTLS 是false - // 如果是服务端的话,那就是 localServer.IsUseTLS == true, 此时,我们正常握手,然后我们需要判断的是它承载的数据 + // 如果是服务端的话,那就是 inServer.IsUseTLS == true, 此时,我们正常握手,然后我们需要判断的是它承载的数据 // 每次tls试图从 原始连接 读取内容时,都会附带把原始数据写入到 这个 Recorder中 var serverEndLocalServerTlsRawReadRecorder *tlsLayer.Recorder @@ -181,7 +181,7 @@ func handleNewIncomeConnection(inServer proxy.Server, outClient proxy.Client, th if err != nil { if utils.CanLogErr() { - log.Println("failed in handshake localServer tls", inServer.AddrStr(), err) + log.Println("failed in handshake inServer tls", inServer.AddrStr(), err) } thisLocalConnectionInstance.Close() @@ -237,7 +237,7 @@ afterLocalServerHandshake: log.Println("trying routing feature") } - //目前只支持一个 localServer/remoteClient, 所以目前根据tag分流是没有意义的,以后再说 + //目前只支持一个 inServer/outClient, 所以目前根据tag分流是没有意义的,以后再说 // 现在就用addr分流就行 outtag := routePolicy.GetOutTag(&netLayer.TargetDescription{ Addr: targetAddr, @@ -254,7 +254,7 @@ afterLocalServerHandshake: // 我们在客户端 lazy_encrypt 探测时,读取socks5 传来的信息,因为这个和要发送到tls的信息是一模一样的,所以就不需要等包上vless、tls后再判断了, 直接解包 socks5进行判断 // // 而在服务端探测时,因为 客户端传来的连接 包了 tls,所以要在tls解包后, vless 解包后,再进行判断; - // 所以总之都是要在 localServer判断 wlc; 总之,含义就是,去检索“用户承载数据”的来源 + // 所以总之都是要在 inServer 判断 wlc; 总之,含义就是,去检索“用户承载数据”的来源 if tls_lazy_encrypt { @@ -276,7 +276,7 @@ afterLocalServerHandshake: // 而且也不在这里处理监听事件,client自己会在额外的 goroutine里处理 // server也一样,会在特定的场合给 CRUMFURS 传值,这个机制是与main函数无关的 - // 而且 thisLocalConnectionInstance 会被 localServer 保存起来,用于后面的 unknownRemoteAddrMsgWriter + // 而且 thisLocalConnectionInstance 会被 inServer 保存起来,用于后面的 unknownRemoteAddrMsgWriter return @@ -297,7 +297,7 @@ afterLocalServerHandshake: // 此时socks5包已经帮我们dial好了一个udp连接,即wlc,但是还未读取到客户端想要访问的东西 udpConn := wlc.(*socks5.UDPConn) - // 将 remoteClient 视为 UDP_Putter ,就可以转发udp信息了 + // 将 outClient 视为 UDP_Putter ,就可以转发udp信息了 // direct 也实现了 UDP_Putter (通过 UDP_Pipe和 RelayUDP_to_Direct函数), 所以目前 socks5直接转发udp到direct 的功能 已经实现。 // 我在 vless v1 的client 的 UserConn 中实现了 UDP_Putter, vless 的 client的 新连接的Handshake过程会在 UDP_Putter.WriteUDPRequest 被调用 时发生 @@ -428,7 +428,7 @@ afterLocalServerHandshake: tlsConn, err := client.GetTLS_Client().Handshake(clientConn) if err != nil { - log.Println("failed in handshake remoteClient tls", targetAddr.String(), ", Reason: ", err) + log.Println("failed in handshake outClient tls", targetAddr.String(), ", Reason: ", err) return } @@ -601,7 +601,7 @@ func tryRawCopy(useSecureMethod bool, proxy_client proxy.UserClient, proxy_serve tlsConn, err := proxy_client.GetTLS_Client().Handshake(teeConn) if err != nil { if utils.CanLogErr() { - log.Println("failed in handshake remoteClient tls", ", Reason: ", err) + log.Println("failed in handshake outClient tls", ", Reason: ", err) } return diff --git a/proxy/proxy.go b/proxy/proxy.go index 52c6a52..c19e994 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -91,6 +91,8 @@ Server and Client 我们服务端和 客户端的程序,都是有至少一个入口和一个出口的。入口我们叫做 inServer ,出口我们叫做 outClient. +这两个词的含义和 v2ray的 inbound 和 outbound 是等价的. + 在 inServer 中,我们负责监听未知连接;在 outClient 中,我们负责拨号特定目标服务器. */ @@ -109,10 +111,11 @@ import ( ) // 给一个节点 提供 VSI中 第 5-7层 的支持, server和 client通用. 个别方法只能用于某一端 +// 一个 ProxyCommon 会内嵌proxy以及上面各层的所有信息 type ProxyCommon interface { AddrStr() string //地址,在server就是监听地址,在client就是拨号地址 SetAddrStr(string) - CantRoute() bool //for localServer, + CantRoute() bool //for inServer, 分流属于netLayer的指责 GetTag() string SetUseTLS() @@ -120,6 +123,7 @@ type ProxyCommon interface { HasAdvancedApplicationLayer() bool //如果使用了ws或者grpc,这个要返回true + //tls属于tlsLayer的指责 SetTLS_Server(*tlsLayer.Server) SetTLS_Client(*tlsLayer.Client) @@ -139,7 +143,9 @@ func PrepareTLS_forProxyCommon(u *url.URL, isclient bool, com ProxyCommon) error } if isclient { - com.SetTLS_Client(tlsLayer.NewTlsClient(u.Host, insecure)) + utlsStr := u.Query().Get("utls") + useUtls := utlsStr != "" && utlsStr != "false" && utlsStr != "0" + com.SetTLS_Client(tlsLayer.NewTlsClient(u.Host, insecure, useUtls)) } else { certFile := u.Query().Get("cert") @@ -166,7 +172,7 @@ type ProxyCommonStruct struct { tls_s *tlsLayer.Server tls_c *tlsLayer.Client - cantRoute bool //for localServer, 若为true,则 localServer 读得的数据 不会经过分流,一定会通过用户指定的remoteclient发出 + cantRoute bool //for inServer, 若为true,则 inServer 读得的数据 不会经过分流,一定会通过用户指定的remoteclient发出 } func (pcs *ProxyCommonStruct) CantRoute() bool { diff --git a/proxy/vless/client.go b/proxy/vless/client.go index 1971425..2d3cb21 100644 --- a/proxy/vless/client.go +++ b/proxy/vless/client.go @@ -208,7 +208,7 @@ func (c *Client) getBufWithCmd(cmd byte) *bytes.Buffer { return buf } -// 把在 CRUMFURS 信道中 获取到的 未知流量 转发到 UDPResponseWriter (本v2simple中就是 转发到localServer中, 而且 只有 socks5 这一种localServer实现了该方法, 见 main.go) +// 把在 CRUMFURS 信道中 获取到的 未知流量 转发到 UDPResponseWriter (本作中就是 转发到 inServer 中, 而且 只有 socks5 这一种 inServer 实现了该方法, 见 main.go) func (c *Client) handle_CRUMFURS(UMFURS_conn net.Conn) { if c.udpResponseChan == nil { diff --git a/tlsLayer/client.go b/tlsLayer/client.go index 552a63b..4cd30f2 100644 --- a/tlsLayer/client.go +++ b/tlsLayer/client.go @@ -2,36 +2,68 @@ package tlsLayer import ( "crypto/tls" + "log" "net" + "unsafe" + + "github.com/hahahrfool/v2ray_simple/utils" + utls "github.com/refraction-networking/utls" ) type Client struct { tlsConfig *tls.Config + useTls bool } -func NewTlsClient(host string, insecure bool) *Client { +func NewTlsClient(host string, insecure bool, useTls bool) *Client { c := &Client{ tlsConfig: &tls.Config{ InsecureSkipVerify: insecure, ServerName: host, }, + useTls: useTls, + } + + if useTls && utils.CanLogInfo() { + log.Println("using utls and Chrome fingerprint") } return c } func (c *Client) Handshake(underlay net.Conn) (tlsConn *Conn, err error) { - rawTlsConn := tls.Client(underlay, c.tlsConfig) - err = rawTlsConn.Handshake() - if err != nil { - return + if c.useTls { + utlsConn := utls.UClient(underlay, &utls.Config{ + InsecureSkipVerify: c.tlsConfig.InsecureSkipVerify, + ServerName: c.tlsConfig.ServerName, + }, utls.HelloChrome_Auto) + + err = utlsConn.Handshake() + if err != nil { + return + } + tlsConn = &Conn{ + Conn: utlsConn, + ptr: unsafe.Pointer(utlsConn.Conn), + tlsPackageType: utlsPackage, + } + + } else { + officialConn := tls.Client(underlay, c.tlsConfig) + err = officialConn.Handshake() + + if err != nil { + return + } + + tlsConn = &Conn{ + Conn: officialConn, + ptr: unsafe.Pointer(officialConn), + tlsPackageType: official, + } + } - - tlsConn = &Conn{ - Conn: rawTlsConn, - } - return } diff --git a/tlsLayer/conn.go b/tlsLayer/conn.go index 9ae5e2c..d1e8fc5 100644 --- a/tlsLayer/conn.go +++ b/tlsLayer/conn.go @@ -4,23 +4,34 @@ import ( "crypto/tls" "net" "unsafe" + + utls "github.com/refraction-networking/utls" +) + +const ( + official = iota + utlsPackage ) //参考 crypt/tls 的 conn.go, 注意,如果上游代码的底层结构发生了改变,则这里也要跟着修改,保持头部结构一致 type faketlsconn struct { - // constant conn net.Conn isClient bool } // 本包会用到这个Conn,比如server和client的 Handshake, // 唯一特性就是它可以返回tls连接的底层tcp连接,见 GetRaw + type Conn struct { - *tls.Conn + //*tls.Conn + net.Conn + ptr unsafe.Pointer + tlsPackageType byte // 0 means crypto/tls, 1 means utls } func (c *Conn) GetRaw(tls_lazy_encrypt bool) *net.TCPConn { - rc := (*faketlsconn)(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)))) + + rc := (*faketlsconn)(c.ptr) if rc != nil { if rc.conn != nil { //log.Println("成功获取到 *net.TCPConn!", rc.conn.(*net.TCPConn)) //经测试,是毫无问题的,完全能提取出来并正常使用 @@ -40,7 +51,7 @@ func (c *Conn) GetRaw(tls_lazy_encrypt bool) *net.TCPConn { // 直接获取TeeConn,仅用于已经确定肯定能获取到的情况 func (c *Conn) GetTeeConn() *TeeConn { - rc := (*faketlsconn)(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)))) + rc := (*faketlsconn)(c.ptr) return rc.conn.(*TeeConn) @@ -49,6 +60,13 @@ func (c *Conn) GetTeeConn() *TeeConn { //return c.Conn.ConnectionState().NegotiatedProtocol func (c *Conn) GetAlpn() string { - return c.Conn.ConnectionState().NegotiatedProtocol + if c.tlsPackageType == utlsPackage { + cc := (*utls.Conn)(c.ptr) + return cc.ConnectionState().NegotiatedProtocol + + } else { + cc := (*tls.Conn)(c.ptr) + return cc.ConnectionState().NegotiatedProtocol + } }