feat:添加回落和分流功能.

创建新子包netLayer, 将 proxy.Addr改为 netLayer.Addr
修订文档

RoutePolicy等分流机制也放到 netLayer

引入github.com/oschwald/maxminddb-golang 依赖,支持使用 GeoLite2-Country.mmdb 来进行ip分流

另外注意它默认的版本对于 golang.org/x/sys 包的依赖太老了,会导致go1.18中编译不通过,我在
go.mod 文件中新增了下面代码,就能通过编译了

```
require (
	golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 // indirect
)
```

verysimple的可执行文件的相同目录下,必须有该mmdb文件才能够开启ip分流功能

新配置方式:配置文件新加一行 "route":{ "mycountry":"CN" }

mycountry指定的国家的ip会被直连发送,其他地址ip会被发送到代理.

新配置方式,回落,直接在 local 项的 url 的 query部分添加 fallback=:80, 或者 fallback=127.0.0.1:80
即可 回落到指定端口.

将tls_test重新挪动到tlsLayer包中

在main.go中添加了 logLevel变量,并且把关于配置文件的部分挪动到 config.go

出了上面的分流和回落以外,还新增支持了 #xxx 的尾缀,用于配置该url的tag. tag在未来会被用于精准分流

Makefile中新增了 PACK 参数用于编译出 打包版的发行包;可选 tag=embed_geoip 参数用于将mmdb.tgz文件内置到可执行程序里

同时,我开始直接使用go1.18编译本项目,期待性能提升,因为这是新发布的版本,看了介绍据说对 mac m1有20%的提升.
This commit is contained in:
hahahrfool
2022-03-16 19:28:26 +08:00
parent 405207bc56
commit e664b9740e
27 changed files with 664 additions and 170 deletions

14
.gitignore vendored
View File

@@ -1,8 +1,10 @@
*.DS_Store
v2ray_simple
v2ray_simple_linux*
v2ray_simple_win*
verysimple_*
*.exe
client.json
server.json
v2ray_simple
v2ray_simple_*
verysimple
verysimple_*
*.exe
*.DS_Store
*.mmdb
*.tgz

View File

@@ -8,33 +8,58 @@ macosAmd :=_macos_
macosArm :=_macos_m1_
windows :=_win10_
# for building with filename "verysimple" and pack into verysimple_xxx.tgz:
# make PACK=1
#我们只支持64位
linuxAmdFn:=${prefix}${linuxAmd}${BUILD_VERSION}
linuxArmFn:=${prefix}${linuxArm}${BUILD_VERSION}
macFn :=${prefix}${macosAmd}${BUILD_VERSION}
macM1Fn :=${prefix}${macosArm}${BUILD_VERSION}
winFn :=${prefix}${windows}${BUILD_VERSION}
cmd:=go build -trimpath -ldflags "-X 'main.Version=${BUILD_VERSION}' -s -w -buildid=" -o
# for embedding geoip file:
# make tags="embed_geoip" macm1
all: win10 linux_amd64 linux_arm64 macos macos_arm64
cmd:=go build -tags $(tags) -trimpath -ldflags "-X 'main.Version=${BUILD_VERSION}' -s -w -buildid=" -o
ifdef PACK
define compile
GOOS=$(2) GOARCH=$(3) $(cmd) $(1)
mv $(1) verysimple$(4)
tar -czf $(1).tgz verysimple$(4)
rm verysimple$(4)
endef
else
define compile
GOOS=$(2) GOARCH=$(3) $(cmd) $(1)$(4)
endef
endif
all: win10 linux_amd64 linux_arm64 macos macm1
#注意调用参数时,逗号前后不能留空格
linux_amd64:
GOARCH=amd64 GOOS=linux $(cmd) $(linuxAmdFn)
$(call compile, $(linuxAmdFn),linux,amd64)
linux_arm64:
GOARCH=arm64 GOOS=linux $(cmd) $(linuxArmFn)
$(call compile, $(linuxArmFn),linux,arm64)
macos:
GOARCH=amd64 GOOS=darwin $(cmd) $(macFn)
$(call compile, $(macFn),darwin,amd64)
#我也提供mac 的apple silicon版本.
macos_arm64:
GOARCH=arm64 GOOS=darwin $(cmd) $(macM1Fn)
#我也提供macos 的apple silicon版本.
macm1:
$(call compile, $(macM1Fn),darwin,arm64)
win10:
GOARCH=amd64 GOOS=windows $(cmd) ${winFn}.exe
$(call compile, $(winFn),windows,amd64,.exe)
clean:
@@ -43,3 +68,10 @@ clean:
rm -f ${winFn}.exe
rm -f $(macFn)
rm -f $(macM1Fn)
rm -f $(linuxAmdFn).tgz
rm -f $(linuxArmFn).tgz
rm -f ${winFn}.tgz
rm -f $(macFn).tgz
rm -f $(macM1Fn).tgz

View File

@@ -10,12 +10,15 @@
本项目独立发展希望大家不要关注给其它项目贡献的问题多关注给咱们verysimple项目贡献不要本末倒置。
v2fly社区 的代码风气不好,竟然对 ’要写注释‘ 这个问题大加反驳,而且讨论时经常没有证据就指责他人,还讽刺人(从普通路人到真实开发者都是这个风气)内部开发者还有一个私聊群从未公开也就是说Project V群一个v2fly群还有“私密开发者”群三群独立对“群管理员”控制很差v2ray“实控人”自己也承认自己并不怎么看“社区群”的聊天面向社区的群中的群管理同意的PR请求到了真正的开发者嘴上就又变得不同意而且理由自己相互矛盾。整个社区充满“能不做就不做代码不需要注释开发者内部自己能看懂就行”的气氛对创新以及代码质量的改善充满了抵触情绪一副大爷的嘴脸。对于一些 “可能” “正在做的东西”没有证据就说“正在做了”毫无意义给予虚假的期望。这种社区没必要提PR的自己没事找事徒增烦恼。就算“实控人”的话语态度很和气那也是老态龙钟表面迎合很难适应新变化自己对自己的“管理员”们 表示下无奈而已,就像去餐馆吃饭,价格或者菜有问题,找他们经理一样,找来,明面说要教育自己员工,实际上回去还不是什么也不做, 笑面虎。
v2fly社区 的代码风气不好,竟然对 ’要写注释‘ 这个问题大加反驳,而且讨论时经常没有证据就指责他人,还讽刺人(从普通路人到真实开发者都是这个风气)内部开发者还有一个私聊群从未公开也就是说Project V群一个v2fly群还有“私密开发者”群三群独立对“群管理员”控制很差v2ray“实控人”自己也承认自己并不怎么看“社区群”的聊天面向社区的群中的群管理同意的PR请求到了真正的开发者嘴上就又变得不同意而且理由自己相互矛盾。整个社区充满“能不做就不做代码不需要注释开发者内部自己能看懂就行”的气氛对创新以及代码质量的改善充满了抵触情绪一副大爷的嘴脸。对于一些 “可能” “正在做的东西”没有证据就说“正在做了”毫无意义给予虚假的期望。这种社区没必要提PR的自己没事找事徒增烦恼。就算“实控人”的话语态度很和气那也是老态龙钟表面迎合很难适应新变化自己对自己的“管理员”们 表示下无奈而已,就像去餐馆吃饭,价格或者菜有问题,找他们经理一样,找来,明面说要教育自己员工,实际上回去还不是什么也不做, 笑面虎.我成天热脸贴冷屁股,心态再好的人也崩了啊. 感觉他们就像被感染了一种僵尸病毒一样,腐朽,沿着自己固定的思路慢慢变老
而xray更是漏洞百出、后继无人
在中国的人,从小到大都在学习政治,各个管理员都是政治怪物。我已经向前看了,我在乎他们吗?他们那么垃圾,我巴不得跟他们划清界限。现在他们是把我逼上梁山了。
他们都已经是历史产物,我们要学会扬弃。只要我们做的足够好,就会有人来贡献的,不怕社区小,不怕没支持。我们向前看。
开源社区,就是自己动手,丰衣足食,风气不好、漏洞百出的东西我们 要扬弃,才能不断进化。否则的话,就会被时代所抛弃。
历史的车轮滚滚向前。

View File

@@ -54,9 +54,9 @@ tls lazy encrypt 特性 运行时可以用 -lazy 参数打开(服务端客户
关于 splice还可以参考我的文章 https://github.com/hahahrfool/xray_splice-
该特性不完全稳定,可能会导致一些网页访问有时出现异常
该特性不完全稳定,可能会导致一些网页访问有时出现异常,有时出现bad mac alert;刷新页面可以解决
不是速度慢,是因为 目前的tls过滤方式有点问题, 对close_alert等情况没处理好。而且使用不同的浏览器现象也会不同似乎对safari支持好一些 chrome就差很多
不是速度慢,是因为 目前的tls过滤方式有点问题, 对close_alert等情况没处理好。而且使用不同的浏览器现象也会不同似乎对safari支持好一些 chrome就差一些
在我的最新代码里采用了独特的技术已经规避了大部分不稳定性。总之比较适合看视频毕竟双向splice不是白给的
@@ -107,8 +107,25 @@ cp client.example.json client.json
cp server.example.json server.json
```
详细优化的编译参数请参考Makefile文件
如果你是直接下载的可执行文件,则不需要 go build了直接复制example.json文件即可
### 关于内嵌geoip 文件
默认的Makefile或者直接 go build 是不开启内嵌功能的需要加载外部mmdb文件就是说你要自己去下载mmdb文件
可以从 https://github.com/P3TERX/GeoLite.mmdb 项目https://github.com/Loyalsoldier/geoip 项目, 或者类似项目 进行下载
加载的外部文件 必须使用原始 mmdb格式。
若要内嵌编译,要用 `tar -czf GeoLite2-Country.mmdb.tgz GeoLite2-Country.mmdb` 来打包一下将生成的tgz文件放到 netLayer文件夹中然后再编译 ,用 `go build -tags embed_geoip` 编译
内嵌编译 所使用的 文件名 必须是 GeoLite2-Country.mmdb.tgz
因为为了减小文件体积所以才内嵌的gzip格式而不是直接内嵌原始数据
## 使用方式
```sh
@@ -137,7 +154,9 @@ v2ray_simple -c server.json
文档、注释尽量详细且尽量完全使用中文尽量符合golang的各种推荐标准。
根据golang的标准注释就是文档本身godoc的原理所以一定要多写注释。
根据golang的标准注释就是文档本身godoc的原理所以一定要多写注释。不要以为解释重复了就不要写因为要生成godoc文档在 pkg.go.dev 上 给用户看的时候它们首先看到的是注释内容,而不是代码内容
本项目所生成的文档在 https://pkg.go.dev/github.com/hahahrfool/v2ray_simple
再次重复,文档越多越好,尽量降低开发者入门的门槛。
@@ -206,7 +225,7 @@ openssl req -new -x509 -days 7305 -key cert.key -out cert.pem
测试环境ubuntu虚拟机, 使用开源测试工具
https://github.com/librespeed/speedtest-go
编译后运行会监听8989
编译后运行会监听8989。注意要先按speedtest-go的要求把web/asset文件夹 和一个toml配置文件 放到 可执行文件的文件夹中,我们直接在项目文件夹里编译的,所以直接移动到项目文件夹根部即可
然后内网搭建nginx 前置,加自签名证书,配置添加反代:
`proxy_pass http://127.0.0.1:8989;`
@@ -256,7 +275,9 @@ https://github.com/librespeed/speedtest-go
https://t.me/shadowrocket_unofficial
# 免责声明
# 免责声明与鸣谢
## 免责
MIT协议我不负任何责任。本项目只是个代理项目适合内网测试使用以及适合阅读代码了解原理。

View File

@@ -1,4 +1,5 @@
{
"local": "socks5://127.0.0.1:10800",
"remote": "vlesss://a684455c-b14f-11ea-bf0d-42010aaa0003@127.0.0.1:4433?insecure=true&version=0"
"local": "socks5://127.0.0.1:10800#myvlesss1",
"remote": "vlesss://a684455c-b14f-11ea-bf0d-42010aaa0003@127.0.0.1:4433?insecure=true&version=0",
"route":{ "mycountry":"CN" ,"comment":"route这一项是可选的,如果没给出的话,就不分流;写了mycountry后, 向该国家的ip发送的请求就会直连, 然后其他的过代理;本comment项你可以自行删掉, 注意要删的话, 连着前面的逗号一起删"}
}

View File

@@ -1,4 +1,5 @@
{
"local": "socks5://0.0.0.0:10800",
"remote": "vlesss://a684455c-b14f-11ea-bf0d-42010aaa0003@127.0.0.1:4433?insecure=true&version=0"
"remote": "vlesss://a684455c-b14f-11ea-bf0d-42010aaa0003@127.0.0.1:4433?insecure=true&version=0",
"xroute":{ "mycountry":"CN" }
}

44
config.go Normal file
View File

@@ -0,0 +1,44 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"github.com/hahahrfool/v2ray_simple/common"
)
type Config struct {
Server_ThatListenPort_Url string `json:"local"`
Client_ThatDialRemote_Url string `json:"remote"`
Route *RouteStruct `json:"route"`
}
type RouteStruct struct {
MyCountryISO_3166 string `json:"mycountry"` //加了mycountry后就会自动按照geoip分流
}
func loadConfig(fileName string) (*Config, error) {
path := common.GetFilePath(fileName)
if len(path) > 0 {
if cf, err := os.Open(path); err == nil {
defer cf.Close()
bs, _ := ioutil.ReadAll(cf)
config := &Config{}
if err = json.Unmarshal(bs, config); err != nil {
return nil, fmt.Errorf("can not parse config file %v, %v", fileName, err)
}
return config, nil
}
}
return nil, fmt.Errorf("can not load config file %v", fileName)
}
func loadConfigFromStr(str string) (*Config, error) {
config := &Config{}
if err := json.Unmarshal([]byte(str), config); err != nil {
return nil, common.NewErr("can not parse config ", err)
}
return config, nil
}

37
config_test.go Normal file
View File

@@ -0,0 +1,37 @@
package main
import (
"net/url"
"testing"
)
func TestNormalClientConfig(t *testing.T) {
confstr1 := `{
"local": "socks5://0.0.0.0:10800#taglocal",
"remote": "vlesss://a684455c-b14f-11ea-bf0d-42010aaa0003@127.0.0.1:4433?insecure=true&version=0#tag1",
"route":{ "mycountry":"CN" }
}`
mc, err := loadConfigFromStr(confstr1)
if err != nil {
t.Log("loadConfigFromStr err", err)
t.FailNow()
}
t.Log(mc.Client_ThatDialRemote_Url)
u, e := url.Parse(mc.Client_ThatDialRemote_Url)
if e != nil {
t.FailNow()
}
t.Log(u.Fragment)
u, e = url.Parse(mc.Server_ThatListenPort_Url)
if e != nil {
t.FailNow()
}
t.Log(u.Fragment)
t.Log(mc.Server_ThatListenPort_Url)
t.Log(mc.Route.MyCountryISO_3166)
if mc.Route.MyCountryISO_3166 != "CN" {
t.FailNow()
}
}

12
go.mod
View File

@@ -1,5 +1,13 @@
module github.com/hahahrfool/v2ray_simple
go 1.14
go 1.17
require github.com/yl2chen/cidranger v1.0.2
require (
github.com/oschwald/maxminddb-golang v1.8.0
github.com/yl2chen/cidranger v1.0.2
)
require (
github.com/stretchr/testify v1.7.0 // indirect
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 // indirect
)

21
go.sum
View File

@@ -1,26 +1,19 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1 h1:52QO5WkIUcHGIR7EnGagH88x1bUzqGXTC5/1bDTUQ7U=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yl2chen/cidranger v1.0.0 h1:9tdo0orHQJvXsX6mf+1Goou/R4kq21AfpbYeTcpXs2Q=
github.com/yl2chen/cidranger v1.0.0/go.mod h1:L7Msw4X7EQK7zMVjOtv7o8xMyjv1rJcNlYlMgGwP7ko=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=

View File

@@ -1,3 +1,6 @@
/*
Package httpLayer 提供http层的一些方法和定义
*/
package httpLayer
const (

View File

@@ -25,7 +25,7 @@ func HasFallbackType(ftype, b byte) bool {
type Fallback interface {
GetFallback(ftype byte, param string) *netLayer.Addr
SupportType() byte //参考Fallback_开头的常量。如果支持多个则返回它们 按位与 的结果
FirstBuffer() *bytes.Buffer
FirstBuffer() *bytes.Buffer //因为能确认fallback一定是读取过数据的所以需要给出之前所读的数据fallback时要用到要重新传输给目标服务器
}
type SingleFallback struct {
@@ -45,7 +45,7 @@ func (ef *SingleFallback) FirstBuffer() *bytes.Buffer {
return ef.First
}
//实现 Fallback
//实现 Fallback,支持 path,alpn, sni 分流
type ClassicFallback struct {
First *bytes.Buffer
Default *netLayer.Addr

76
main.go
View File

@@ -1,11 +1,9 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"os"
@@ -25,22 +23,36 @@ import (
"github.com/hahahrfool/v2ray_simple/proxy"
)
const (
log_debug = iota
log_info
log_warning
log_error
)
var (
desc = "v2ray_simple, a very simple implementation of V2Ray, 并且在某些地方试图走在v2ray前面"
configFileName string
uniqueTestDomain string //有时需要测试到单一网站的流量,此时为了避免其它干扰,需要在这里声明 一下 该域名,然后程序里会进行过滤
logLevel int //值越小越唠叨, 废话越多值越大打印的越少见log_开头的常量
//另外本作暂时不考虑引入外界log包。依赖越少越好。
conf *Config
//directClient proxy.Client
directClient proxy.Client
tls_lazy_encrypt bool
tls_lazy_secure bool
routePolicy *netLayer.RoutePolicy
)
func init() {
//directClient, _ = proxy.ClientFromURL("direct://")
directClient, _ = proxy.ClientFromURL("direct://")
flag.IntVar(&logLevel, "ll", log_warning, "log level,0=debug, 1=info, 2=warning, 3=error")
flag.BoolVar(&tls_lazy_encrypt, "lazy", false, "tls lazy encrypt (splice)")
flag.BoolVar(&tls_lazy_secure, "ls", false, "tls lazy secure, use special techs to ensure the tls lazy encrypt data can't be detected. Only valid at client end.")
@@ -59,28 +71,6 @@ func printDesc() {
fmt.Printf("=============== 所有协议均可套tls ================\n")
}
type Config struct {
Server_ThatListenPort_Url string `json:"local"`
//RouteMethod string `json:"route"`
Client_ThatDialRemote_Url string `json:"remote"`
}
func loadConfig(fileName string) (*Config, error) {
path := common.GetFilePath(fileName)
if len(path) > 0 {
if cf, err := os.Open(path); err == nil {
defer cf.Close()
bytes, _ := ioutil.ReadAll(cf)
config := &Config{}
if err = json.Unmarshal(bytes, config); err != nil {
return nil, fmt.Errorf("can not parse config file %v, %v", fileName, err)
}
return config, nil
}
}
return nil, fmt.Errorf("can not load config file %v", fileName)
}
func main() {
printDesc()
@@ -102,6 +92,18 @@ func main() {
}
defer localServer.Stop()
if !localServer.CantRoute() && conf.Route != nil {
netLayer.LoadMaxmindGeoipFile("")
//目前只支持通过 mycountry进行 geoip分流 这一种情况
routePolicy = netLayer.NewRoutePolicy()
if conf.Route.MyCountryISO_3166 != "" {
routePolicy.AddRouteSet(netLayer.NewRouteSetForMyCountry(conf.Route.MyCountryISO_3166))
}
}
remoteClient, err := proxy.ClientFromURL(conf.Client_ThatDialRemote_Url)
if err != nil {
log.Println("can not create remote client: ", err)
@@ -217,9 +219,27 @@ func handleNewIncomeConnection(localServer proxy.Server, remoteClient proxy.Clie
afterLocalServerHandshake:
var client proxy.Client
var client proxy.Client = remoteClient
client = remoteClient //如果加了白名单等过滤方式则client可能会等于direct等再说
//如果可以route
if !localServer.CantRoute() && routePolicy != nil {
if logLevel <= log_info {
log.Println("enabling routing feature")
}
//目前只支持一个 localServer/remoteClient, 所以目前根据tag分流是没有意义的以后再说
// 现在就用addr分流就行
outtag := routePolicy.GetOutTag(&netLayer.TargetDescription{
Addr: targetAddr,
})
if outtag == "direct" {
client = directClient
if logLevel <= log_info {
log.Println("routed to direct", targetAddr.UrlString())
}
}
}
// 我们在客户端 lazy_encrypt 探测时读取socks5 传来的信息因为这个和要发送到tls的信息是一模一样的所以就不需要等包上vless、tls后再判断了, 直接解包 socks5进行判断
//

75
netLayer/geoip.go Normal file
View File

@@ -0,0 +1,75 @@
package netLayer
import (
_ "embed"
"flag"
"log"
"net"
"os"
"github.com/oschwald/maxminddb-golang"
)
var (
the_geoipdb *maxminddb.Reader
embedGeoip bool
GeoipFileName string //若运行程序指定了 geoip 参数则该值为给定值否则默认会被init为 GeoLite2-Country.mmdb
)
func init() {
flag.StringVar(&GeoipFileName, "geoip", "GeoLite2-Country.mmdb", "geoip maxmind file name")
}
func HasEmbedGeoip() bool {
return embedGeoip
}
func loadMaxmindGeoipBytes(bs []byte) {
db, err := maxminddb.FromBytes(bs)
if err != nil {
log.Fatalln("loadMaxmindGeoipBytes", err)
}
the_geoipdb = db
}
//将一个外部的文件加载为我们默认的 geoip文件;若fn=="",则会自动使用 GeoipFileName 的值
func LoadMaxmindGeoipFile(fn string) {
if fn == "" {
fn = GeoipFileName
}
if fn == "" { //因为 GeoipFileName 是共有变量,所以可能会被设成"", 不排除脑残
return
}
bs, e := os.ReadFile(fn)
if e != nil {
log.Fatalln("loadMaxmindGeoipBytes", e)
}
loadMaxmindGeoipBytes(bs)
}
//使用默认的 geoip文件会调用 GetIP_ISO_byReader
func GetIP_ISO(ip net.IP) string {
if the_geoipdb == nil {
return ""
}
return GetIP_ISO_byReader(the_geoipdb, ip)
}
//返回 iso 3166 字符串, 见 https://dev.maxmind.com/geoip/legacy/codes?lang=en ,大写,两字节
func GetIP_ISO_byReader(db *maxminddb.Reader, ip net.IP) string {
var record struct {
Country struct {
ISOCode string `maxminddb:"iso_code"`
} `maxminddb:"country"`
}
err := db.Lookup(ip, &record)
if err != nil {
log.Fatal(err) //不应该发生
}
return record.Country.ISOCode
}

71
netLayer/geoip_embed.go Normal file
View File

@@ -0,0 +1,71 @@
//go:build embed_geoip
// +build embed_geoip
package netLayer
import (
"archive/tar"
"bytes"
"compress/gzip"
_ "embed"
"fmt"
"io"
"log"
"os"
"github.com/oschwald/maxminddb-golang"
)
//使用 go build -tags embed_geoip 来将文件 编译进 可执行文件
//文件来自 https://dev.maxmind.com/geoip/geolite2-free-geolocation-data?lang=en
// 需要自行下载或者到其他提供该文件的github项目下载然后放入我们的 netLayer 文件夹中
//go:embed GeoLite2-Country.mmdb.tgz
var geoLite2Country_embed_tgz []byte
//注意如果使用了embed版然后又提供命令参数加载外部文件就会内嵌版被覆盖
//将 tgz文件解压成maxmind的mmdb文件
func init() {
embedGeoip = true
outBuf := &bytes.Buffer{}
gzf, err := gzip.NewReader(bytes.NewBuffer(geoLite2Country_embed_tgz))
if err != nil {
fmt.Println("load geoLite2Country_embed_tgz err,", err)
os.Exit(1)
}
tarReader := tar.NewReader(gzf)
header, err := tarReader.Next()
if err != nil {
if err == io.EOF {
fmt.Println("load geoLite2Country_embed_tgz err,", io.EOF)
os.Exit(1)
}
fmt.Println("load geoLite2Country_embed_tgz err,", err)
os.Exit(1)
}
switch header.Typeflag {
case tar.TypeReg:
io.Copy(outBuf, tarReader)
default:
log.Fatal("load geoLite2Country_embed_tgz, not a simple file??", header.Name)
}
//这个函数应该直接接管了 我们的bytes所以不能用 common.PutBuf 放回
db, err := maxminddb.FromBytes(outBuf.Bytes())
if err != nil {
log.Fatal("maxminddb.FromBytes", err)
}
the_geoipdb = db
}

View File

@@ -1,19 +0,0 @@
package netLayer
import (
"net"
"github.com/yl2chen/cidranger"
)
type TargetDescription struct {
}
//任意一个参数匹配后,都将发往相同的方向,具体房向并不是 SameGroup 所关心的
// SameGroup只负责把一些属性相同的 “网络层特征” 放到一起
type SameGroup struct {
NetRanger cidranger.Ranger
IPMap map[string]net.IP
DomainMap map[string]string
TagMap map[string]bool
}

138
netLayer/route.go Normal file
View File

@@ -0,0 +1,138 @@
package netLayer
import (
"net"
"strings"
"github.com/yl2chen/cidranger"
)
// TargetDescription 可以完整地描述一个网络层/传输层上的一个特定目标,
// 一般来说一个具体的监听配置就会分配一个tag
type TargetDescription struct {
Addr *Addr
Tag string
}
// Set 是 “集合” 的意思, 是一组相同类型的数据放到一起。
// 这里的相同点,就是它们同属于 将发往一个方向, 即同属一个路由策略
// 任意一个参数匹配后都将发往相同的方向由该方向OutTag 指定
// RouteSet 只负责把一些属性相同的 “网络层/传输层 特征” 放到一起
type RouteSet struct {
//网络层
NetRanger cidranger.Ranger //一个范围
IPs map[string]net.IP //一个确定值
Domains, InTags, Countries map[string]bool // Countries 使用 ISO 3166 字符串 作为key
//传输层
NotAllowTCP bool
NotAllowUDP bool
OutTag string
}
func NewRouteSetForMyCountry(iso string) *RouteSet {
if len(iso) != 2 {
return nil
}
rs := &RouteSet{
Countries: make(map[string]bool),
OutTag: "direct",
}
rs.Countries[iso] = true
return rs
}
func NewFullRouteSet() *RouteSet {
return &RouteSet{
NetRanger: cidranger.NewPCTrieRanger(),
IPs: make(map[string]net.IP),
Domains: make(map[string]bool),
InTags: make(map[string]bool),
Countries: make(map[string]bool),
}
}
func (sg *RouteSet) IsIn(td *TargetDescription) bool {
if td.Tag != "" {
if _, found := sg.InTags[td.Tag]; found {
return true
}
}
return sg.IsAddrIn(td.Addr)
}
func (sg *RouteSet) IsAddrIn(a *Addr) bool {
//我们先过滤传输层再过滤网络层因为传输层没那么多判断只有tcp/udp这个判断而已
if a.IsUDP && sg.NotAllowTCP || !a.IsUDP && sg.NotAllowUDP {
return false
} else if sg.NotAllowTCP != sg.NotAllowUDP && sg.NetRanger == nil && sg.IPs == nil && sg.Domains == nil && sg.InTags == nil && sg.Countries == nil {
//如果仅限制了一个传输层协议,且本集合里没有任何其它内容,那就直接通过
return true
}
//开始网络层判断
if a.IP != nil {
if sg.NetRanger != nil {
if has, _ := sg.NetRanger.Contains(a.IP); has {
return true
}
}
if sg.Countries != nil {
if isoStr := GetIP_ISO(a.IP); isoStr != "" {
if _, found := sg.Countries[isoStr]; found {
return true
}
}
}
if sg.IPs != nil {
if _, found := sg.IPs[a.IP.To16().String()]; found {
return true
}
}
}
if a.Name != "" && sg.Domains != nil {
tokens := strings.Split(a.Name, ".")
if len(tokens) > 1 {
suffix := tokens[len(tokens)-1]
for i := len(tokens) - 2; i >= 0; i-- {
suffix = tokens[i] + "." + suffix
if _, found := sg.Domains[suffix]; found {
return true
}
}
}
}
return false
}
//一个完整的 所有RouteSet的列表进行路由时直接遍历即可
// 所谓的路由实际上就是分流。
type RoutePolicy struct {
List []*RouteSet
}
func NewRoutePolicy() *RoutePolicy {
return &RoutePolicy{
List: make([]*RouteSet, 0, 2),
}
}
func (rp *RoutePolicy) AddRouteSet(rs *RouteSet) {
rp.List = append(rp.List, rs)
}
// 返回一个 proxy.Client 的 tag
// 默认情况下始终具有direct这个tag以及 proxy这个tag无需用户额外在配置文件中指定
// 默认如果不匹配任何值的话,就会流向 "proxy" tag也就是客户设置的 remoteClient的值
func (rp *RoutePolicy) GetOutTag(td *TargetDescription) string {
for _, s := range rp.List {
if s.IsIn(td) {
return s.OutTag
}
}
return "proxy"
}

View File

@@ -1,3 +1,4 @@
// Package direct provies direct proxy support for proxy.Client
package direct
import (

View File

@@ -1,49 +1,49 @@
/*
package proxy 定义了代理转发所需的必备组件
Package proxy 定义了代理转发所需的必备组件
# 层级讨论
目前认为一个传输过程由四个部分组成基础连接udp/tcpTLS可选中间层ws、grpc、http等可选具体协议socks5vlesstrojan等
Layer Definition 层级讨论
其中,wsgrpc被认为是 高级应用层http伪装属于低级应用层。
目前认为一个传输过程由四个部分组成基础连接udp/tcpTLS可选中间层wsgrpc、http等可选具体协议socks5vlesstrojan等.
TLSTransport Layer Security 顾名思义TLS作用于传输层第四层但是我们tcp也是第四层所以在本项目中认为不需要“会话层”单独加一个专用于tls的层比较稳妥
其中ws和grpc被认为是 高级应用层http伪装属于低级应用层.
正常OSI是7层我们在这里规定一个 第八层和第九层,第八层就是 vless协议所在位置第九层就是我们实际传输的承载数据
TLS - Transport Layer Security 顾名思义TLS作用于传输层第四层但是我们tcp也是第四层所以在本项目中认为不需要“会话层”单独加一个专用于tls的层比较稳妥.
正常OSI是7层我们在这里规定一个 第八层和第九层,第八层就是 vless协议所在位置第九层就是我们实际传输的承载数据.
## 新的VSI 模型
New Model - VSI 新的VSI 模型
那么我们提出一个 verysimple Interconnection Model 简称vsi模型。1到4层与OSI相同物理、链路、网络、传输)
那么我们提出一个 verysimple Interconnection Model 简称vsi模型。1到4层与OSI相同物理、链路、网络、传输).
把第五层替换成“加密层”把TLS放进去把第六层改为低级应用层http属于这一层
把第五层替换成“加密层”把TLS放进去把第六层改为低级应用层http属于这一层.
第七层 改为高级应用层ws/grpc 属于这一层, 简称高级层;第八层定为 代理层vless/trojan 在这层
第七层 改为高级应用层ws/grpc 属于这一层, 简称高级层;第八层定为 代理层vless/trojan 在这层.
第九层为 承载数据层,承载的为 另一大串 第四层的数据
第九层为 承载数据层,承载的为 另一大串 第四层的数据.
那么我们verysimple实际上就是 基于 “层” 的架构,或称 可分层结构.
那么我们verysimple实际上就是 基于 “层” 的架构,或称 可分层结构。
```
9 tcp data
--------------------
--------------------
8 vless/trojan/socks5
--------------------
--------------------
7 ws/grpc
--------------------
--------------------
6 http
--------------------
--------------------
5 tls
--------------------
--------------------
4 tcp
--------------------
```
--------------------
基本上5-8层都是可控的
对应的理想配置文件应该如下
基本上5-8层都是可控的.
```json
{
对应的理想配置文件应该如下.
{
"layer5_settings": { //或者叫 tls_settings
"tls":{"insecure": true},
"utls":{}
@@ -58,35 +58,42 @@ TLSTransport Layer Security 顾名思义TLS作用于传输层第四层
"vless":{}
"trojan":{}
},
}
```
}
我们项目的文件夹proxy文件夹代表第8层tlsLayer文件夹代表第5层; 未来还计划在根目录下添加http文件夹第六层
ws和grpc文件夹第七层
ws和grpc文件夹第七层.
同级的ws和grpc是独占的可以都放到一个layer里然后比如第八层配置了一个vless一个trojan那么排列组合就是4种vless+ws, vless+ grpc, trojan+ws, trojan+grpc
同级的ws和grpc是独占的可以都放到一个layer里然后比如第八层配置了一个vless一个trojan那么排列组合就是4种vless+ws, vless+ grpc, trojan+ws, trojan+grpc.
这就大大减轻了各种”一键脚本“的 使用需求,咱们只要选择自己喜欢的各个层,程序自动就为我们生成所有配置;
这就大大减轻了各种”一键脚本“的 使用需求,咱们只要选择自己喜欢的各个层,程序自动就为我们生成所有配置.
运行时如果所有配置都要有那么就需要多种端口共用端口的话可以用nginx
运行时如果所有配置都要有那么就需要多种端口共用端口的话可以用nginx.
也可以程序指定一种 特定的情况比如开始运行程序时冒出交互界面自己按项选择好后就自动运行然后自动生成客户端分享url
也可以程序指定一种 特定的情况比如开始运行程序时冒出交互界面自己按项选择好后就自动运行然后自动生成客户端分享url.
可以在脑海里想象 “穿鞋带” 的画面,有很多洞可以经过,都穿好了,鞋带就系好了。或者手机手势解锁的情况
可以在脑海里想象 “穿鞋带” 的画面,有很多洞可以经过,都穿好了,鞋带就系好了。或者手机手势解锁的情况.
这种好处是每次运行都可以采用不同的配置不同的uuid手机一扫码就能连上
这种好处是每次运行都可以采用不同的配置不同的uuid手机一扫码就能连上.
# proxy 文件夹内容
Contents of proxy package - proxy包内容
接口 ProxyCommon 和 结构 ProxyCommonStruct 给 这个架构定义了标准
接口 ProxyCommon 和 结构 ProxyCommonStruct 给 这个架构定义了标准.
而 Client 和 Server 接口 是 具体利用该架构的 客户端 和 服务端都位于VSI中的第八层
而 Client 和 Server 接口 是 具体利用该架构的 客户端 和 服务端都位于VSI中的第八层.
使用 RegisterClient 和 RegisterServer 来注册新的实现
使用 RegisterClient 和 RegisterServer 来注册新的实现.
还定义了关于udp 转发到机制,该部分直接参考 各个UDP开头的 部分即可
还定义了关于udp 转发到机制,该部分直接参考 各个UDP开头的 部分即可.
Server and Client
我们服务端和 客户端的程序都是有至少一个入口和一个出口的。入口我们叫做localServer出口我们叫做remoteClient.
在localServer中我们负责监听未知连接在remoteClient中我们负责拨号特定目标服务器.
这里的local和remote指的是 数据传输的目的地。
*/
package proxy
@@ -102,10 +109,12 @@ import (
"github.com/hahahrfool/v2ray_simple/tlsLayer"
)
// 给一个节点 提供 VSI中 第 5-7层 的支持
// 给一个节点 提供 VSI中 第 5-7层 的支持, server和 client通用. 个别方法只能用于某一端
type ProxyCommon interface {
AddrStr() string //地址在server就是监听地址在client就是拨号地址
SetAddrStr(string)
CantRoute() bool //for localServer,
GetTag() string
SetUseTLS()
IsUseTLS() bool
@@ -117,8 +126,12 @@ type ProxyCommon interface {
GetTLS_Server() *tlsLayer.Server
GetTLS_Client() *tlsLayer.Client
setCantRoute(bool)
setTag(string)
}
//给 ProxyCommon 的tls做一些配置上的准备从url读取配置
func PrepareTLS_forProxyCommon(u *url.URL, isclient bool, com ProxyCommon) error {
insecureStr := u.Query().Get("insecure")
insecure := false
@@ -149,9 +162,28 @@ func PrepareTLS_forProxyCommon(u *url.URL, isclient bool, com ProxyCommon) error
type ProxyCommonStruct struct {
Addr string
TLS bool
Tag string //可用于路由, 见 netLayer.route.go
tls_s *tlsLayer.Server
tls_c *tlsLayer.Client
cantRoute bool //for localServer, 若为true则 localServer 读得的数据 不会经过分流一定会通过用户指定的remoteclient发出
}
func (pcs *ProxyCommonStruct) CantRoute() bool {
return pcs.cantRoute
}
func (pcs *ProxyCommonStruct) GetTag() string {
return pcs.Tag
}
func (pcs *ProxyCommonStruct) setTag(tag string) {
pcs.Tag = tag
}
func (pcs *ProxyCommonStruct) setCantRoute(cr bool) {
pcs.cantRoute = cr
}
func (pcs *ProxyCommonStruct) HasAdvancedApplicationLayer() bool {
@@ -228,7 +260,7 @@ func ClientFromURL(s string) (Client, error) {
return creatorFunc(u)
} else {
//尝试判断是否套tls
//尝试判断是否套tls, 比如vlesss实际上是vless+tlshttps实际上是http+tls
realScheme := strings.TrimSuffix(schemeName, "s")
creatorFunc, ok = clientMap[realScheme]
@@ -291,6 +323,8 @@ func PrintAllClientNames() {
// ServerFromURL calls the registered creator to create proxy servers
// dialer is the default upstream dialer so cannot be nil, we can use Default when calling this function
// 所有的server都可有 "norule"参数标明无需路由或者此server不可使用路由在监听多个ip时是有用的;
// 路由配置可以在json的其他配置里面设置如 mycountry项
func ServerFromURL(s string) (Server, error) {
u, err := url.Parse(s)
if err != nil {
@@ -300,22 +334,40 @@ func ServerFromURL(s string) (Server, error) {
schemeName := strings.ToLower(u.Scheme)
creatorFunc, ok := serverMap[schemeName]
if ok {
return creatorFunc(u)
ser, err := creatorFunc(u)
if err != nil {
return nil, err
}
configCommonForServer(ser, u)
return ser, nil
} else {
realScheme := strings.TrimSuffix(schemeName, "s")
creatorFunc, ok = serverMap[realScheme]
if ok {
server, err := creatorFunc(u)
if err != nil {
return server, err
return nil, err
}
configCommonForServer(server, u)
server.SetUseTLS()
PrepareTLS_forProxyCommon(u, false, server)
return server, err
return server, nil
}
}
return nil, common.NewDataErr("unknown server scheme '", nil, u.Scheme)
}
func configCommonForServer(ser ProxyCommon, u *url.URL) {
nr := false
q := u.Query()
if q.Get("norule") != "" {
nr = true
}
ser.setCantRoute(nr)
ser.setTag(u.Fragment)
}

View File

@@ -1,3 +1,4 @@
// Package socks5 provies socks5 proxy support for proxy.Client and proxy.Server
package socks5
//总体而言vless和vmess协议借鉴了socks5所以有类似的地方。trojan协议也是一样。

View File

@@ -1,3 +1,4 @@
// Package vless provies vless proxy support for proxy.Client and proxy.Server
package vless
import (

View File

@@ -1,4 +1,4 @@
{
"local": "vlesss://a684455c-b14f-11ea-bf0d-42010aaa0003@0.0.0.0:4433?cert=cert.pem&key=cert.key&version=0&fallback=:80",
"local": "vlesss://a684455c-b14f-11ea-bf0d-42010aaa0003@0.0.0.0:4433?cert=cert.pem&key=cert.key&version=0&fallback=:80#myvlesss1",
"remote": "direct://"
}

View File

@@ -14,9 +14,7 @@ type faketlsconn struct {
}
// 本包会用到这个Conn比如server和client的 Handshake
//唯一特性就是它可以返回tls连接的底层tcp连接见 GetRaw
// 开启了回落功能的话,这里还会用到 http.sniff
// 唯一特性就是它可以返回tls连接的底层tcp连接见 GetRaw
type Conn struct {
*tls.Conn
}
@@ -47,3 +45,10 @@ func (c *Conn) GetTeeConn() *TeeConn {
return rc.conn.(*TeeConn)
}
//return c.Conn.ConnectionState().NegotiatedProtocol
func (c *Conn) GetAlpn() string {
return c.Conn.ConnectionState().NegotiatedProtocol
}

View File

@@ -1,3 +1,6 @@
/*
Package tlsLayer 提供tls流量的基本探测功能
*/
package tlsLayer
var etStrMap map[int]string

View File

@@ -34,7 +34,6 @@ func (s *Server) Handshake(underlay net.Conn) (tlsConn *Conn, err error) {
rawTlsConn := tls.Server(underlay, s.tlsConfig)
err = rawTlsConn.Handshake()
if err != nil {
//return tlsConn,
err = common.NewErr("tlsLayer: tls握手失败", err)
return

View File

@@ -1,4 +1,4 @@
package main
package tlsLayer_test
import (
"bytes"
@@ -10,26 +10,23 @@ import (
"github.com/hahahrfool/v2ray_simple/common"
"github.com/hahahrfool/v2ray_simple/netLayer"
"github.com/hahahrfool/v2ray_simple/proxy"
_ "github.com/hahahrfool/v2ray_simple/proxy/vless"
)
func init() {
}
func TestVlesss(t *testing.T) {
testTls("vlesss", "9507", t)
}
func testTls(protocol string, port string, t *testing.T) {
if !common.FileExist("cert.pem") {
ioutil.WriteFile("cert.pem", []byte(sampleCertStr), 0777)
if !common.FileExist("../cert.pem") {
ioutil.WriteFile("../cert.pem", []byte(sampleCertStr), 0777)
}
if !common.FileExist("cert.key") {
ioutil.WriteFile("cert.key", []byte(sampleKeyStr), 0777)
if !common.FileExist("../cert.key") {
ioutil.WriteFile("../cert.key", []byte(sampleKeyStr), 0777)
}
url := protocol + "://a684455c-b14f-11ea-bf0d-42010aaa0003@localhost:" + port + "?alterID=4&cert=cert.pem&key=cert.key&insecure=1"
url := protocol + "://a684455c-b14f-11ea-bf0d-42010aaa0003@localhost:" + port + "?alterID=4&cert=../cert.pem&key=../cert.key&insecure=1"
server, err := proxy.ServerFromURL(url)
if err != nil {
t.Log("fail1", err)

View File

@@ -3,11 +3,16 @@ package main
import (
"fmt"
"runtime"
"github.com/hahahrfool/v2ray_simple/netLayer"
)
var Version string //版本号由Makefile指定
func printVersion() {
fmt.Printf("===============================\nverysimple %v (%v), %v %v %v\n", Version, desc, runtime.Version(), runtime.GOOS, runtime.GOARCH)
if netLayer.HasEmbedGeoip() {
fmt.Println("Contains embeded Geoip file")
}
}