diff --git a/configs.go b/configs.go index 35803c4..0e30c4d 100644 --- a/configs.go +++ b/configs.go @@ -21,6 +21,33 @@ func init() { flag.IntVar(&jsonMode, "jm", 0, "json mode, 0:verysimple mode; 1: v2ray mode(not implemented yet)") } +//mainfallback, dnsConf, routePolicy +func loadCommonComponentsFromStandardConf() { + + if len(standardConf.Fallbacks) != 0 { + mainFallback = httpLayer.NewClassicFallbackFromConfList(standardConf.Fallbacks) + } + + if dnsConf := standardConf.DnsConf; dnsConf != nil { + dnsMachine = proxy.LoadDnsMachine(dnsConf) + } + + hasAppLevelMyCountry := (standardConf.App != nil && standardConf.App.MyCountryISO_3166 != "") + + if standardConf.Route != nil || hasAppLevelMyCountry { + + netLayer.LoadMaxmindGeoipFile("") + + routePolicy = netLayer.NewRoutePolicy() + if hasAppLevelMyCountry { + routePolicy.AddRouteSet(netLayer.NewRouteSetForMyCountry(standardConf.App.MyCountryISO_3166)) + + } + + proxy.LoadRulesForRoutePolicy(standardConf.Route, routePolicy) + } +} + // set conf variable, or exit the program; 还会设置mainFallback // 先检查configFileName是否存在,存在就尝试加载文件,否则尝试 -L参数 func loadConfig() (err error) { @@ -37,26 +64,21 @@ func loadConfig() (err error) { return } - if len(standardConf.Fallbacks) != 0 { - mainFallback = httpLayer.NewClassicFallbackFromConfList(standardConf.Fallbacks) - - } confMode = 1 + + //loglevel 和 noreadv这种会被 命令行覆盖的配置,需要直接在 loadConfig函数中先处理一遍 if appConf := standardConf.App; appConf != nil { - if appConf.LogLevel != nil { + default_uuid = appConf.DefaultUUID + + if appConf.LogLevel != nil && !utils.IsFlagPassed("ll") { utils.LogLevel = *appConf.LogLevel } - default_uuid = appConf.DefaultUUID - if appConf.NoReadV { + if appConf.NoReadV && !utils.IsFlagPassed("readv") { netLayer.UseReadv = false } } - if dnsConf := standardConf.DnsConf; dnsConf != nil { - dnsMachine = proxy.LoadDnsMachine(dnsConf) - - } return } else { //默认认为所有其他后缀的都是json格式,因为有时我会用 server.json.vless 这种写法 diff --git a/examples/multi.client.toml b/examples/multi.client.toml index 31fd4b5..7464a2d 100644 --- a/examples/multi.client.toml +++ b/examples/multi.client.toml @@ -85,7 +85,7 @@ path = "ohmygod_verysimple_is_very_simple" [[route]] dialTag = "my_vless1" -# 这个route中,我们只给了tag, 没给其它限定条件,这个是无效的,永远匹配不到。 +# 上面这个route中,我们只给了tag, 没给其它限定条件,这个是无效的,永远匹配不到。 [[route]] @@ -98,9 +98,9 @@ country = ["US"] # 比如这个就是 将CN国家的ip 导向自己的grpc节点 -[[route]] -dialTag = "my_grpc" -country = ["CN"] +#[[route]] +#dialTag = "my_grpc" +#country = ["CN"] # 比如这个就是 将CN国家的ip进行直连 @@ -108,20 +108,31 @@ country = ["CN"] #dialTag = "direct" #country = ["CN"] +# 本示例为了测试节点可用性, 默认将直连的路由注释掉了, 如果你在CN国家并想直连CN的ip, 请取消注释上面三行. 并移除其它路由CN的route项 + + # 下面这种dialTag传入列表的用法非常简洁, 可以达到负载均衡的效果, # 每次路由US国家的流量都会随机从列表中选一项 #[[route]] #dialTag = ["my_vps1","myvps2"] #country = ["US"] -# 本示例为了测试节点可用性, 默认将直连的路由注释掉了, 如果你在CN国家并想直连CN的ip, 请取消注释上面三行. 并移除其它路由CN的route项 + # 如果所有route均不匹配,则数据会流向 "proxy" 这个tag 的 dial,如果 没有任何dial具有 "proxy" 这个标签名,则流向第一个dial # 如果匹配了app.mycountry, 则数据会直接被直连. # 其它分流匹配示例: # ip = ["0.0.0.0/8","10.0.0.0/8","fe80::/10","10.0.0.1"] -# domain = ["www.google.com","www.twitter.com"] + +# 域名匹配完全兼容 v2ray,请参考 https://www.v2fly.org/config/routing.html#ruleobject +# domain = ["domain:www.google.com","full:www.twitter.com", "geosite:cn"] + +# 比如这个就是 将CN国家的域名 导向自己的grpc节点 +[[route]] +dialTag = "my_grpc" +domain = ["geosite:cn"] + # network = ["tcp","udp"] # inTag = ["tag1","tag2"] # country = ["CN"] diff --git a/main.go b/main.go index fb0eb7e..54c2474 100644 --- a/main.go +++ b/main.go @@ -86,16 +86,6 @@ func init() { } -func isFlagPassed(name string) bool { - found := false - flag.Visit(func(f *flag.Flag) { - if f.Name == name { - found = true - } - }) - return found -} - func main() { flag.Parse() @@ -123,12 +113,6 @@ func main() { defer p.Stop() } - ll_beforeLoadConfigFile := utils.LogLevel - usereadv_beforeLoadConfigFile := netLayer.UseReadv - - cmdLL_given := isFlagPassed("ll") - cmdUseReadv_given := isFlagPassed("readv") - if err := loadConfig(); err != nil && !isFlexible() { log.Printf("no config exist, and no api server or interactive cli enabled, exiting...") os.Exit(-1) @@ -136,20 +120,6 @@ func main() { netLayer.Prepare() - //有点尴尬, 读取配置文件必须要用命令行参数,而配置文件里的部分配置又会覆盖部分命令行参数 - - if cmdLL_given && utils.LogLevel != ll_beforeLoadConfigFile { - //配置文件配置了日志等级, 但是因为 命令行给出的值优先, 所以要设回 - - utils.LogLevel = ll_beforeLoadConfigFile - } - - if cmdUseReadv_given && netLayer.UseReadv != usereadv_beforeLoadConfigFile { - //配置文件配置了readv, 但是因为 命令行给出的值优先, 所以要设回 - - netLayer.UseReadv = usereadv_beforeLoadConfigFile - } - //Printf不会发生 escapes to heap 现象,所以我们统一用 Printf fmt.Printf("Log Level:%d\n", utils.LogLevel) fmt.Printf("UseReadv:%t\n", netLayer.UseReadv) @@ -183,13 +153,14 @@ func main() { } } case standardMode: + + loadCommonComponentsFromStandardConf() + //虽然标准模式支持多个Server,目前先只考虑一个 //多个Server存在的话,则必须要用 tag指定路由; 然后,我们需在预先阶段就判断好tag指定的路由 if len(standardConf.Listen) < 1 { - if ce := utils.CanLogWarn("no listen in config settings"); ce != nil { - ce.Write() - } + utils.Warn("no listen in config settings") break } @@ -211,21 +182,6 @@ func main() { } } - hasMyCountry := (standardConf.App != nil && standardConf.App.MyCountryISO_3166 != "") - - if standardConf.Route != nil || hasMyCountry { - - netLayer.LoadMaxmindGeoipFile("") - - routePolicy = netLayer.NewRoutePolicy() - if hasMyCountry { - routePolicy.AddRouteSet(netLayer.NewRouteSetForMyCountry(standardConf.App.MyCountryISO_3166)) - - } - - proxy.LoadRulesForRoutePolicy(standardConf.Route, routePolicy) - } - } var defaultOutClient proxy.Client @@ -241,9 +197,7 @@ func main() { case standardMode: if len(standardConf.Dial) < 1 { - if ce := utils.CanLogWarn("no dial in config settings"); ce != nil { - ce.Write() - } + utils.Warn("no dial in config settings") break } @@ -331,15 +285,13 @@ func listenSer(inServer proxy.Server, defaultOutClientForThis proxy.Client) { handleFunc := inServer.HandleInitialLayersFunc() if handleFunc == nil { - utils.ZapLogger.Fatal("inServer.IsHandleInitialLayers but inServer.HandleInitialLayersFunc() returns nil") + utils.Fatal("inServer.IsHandleInitialLayers but inServer.HandleInitialLayersFunc() returns nil") } //baseConn可以为nil,quic就是如此 newConnChan, baseConn := handleFunc() if newConnChan == nil { - if ce := utils.CanLogErr("StarthandleInitialLayers can't extablish baseConn"); ce != nil { - ce.Write() - } + utils.Error("StarthandleInitialLayers can't extablish baseConn") return } @@ -347,10 +299,7 @@ func listenSer(inServer proxy.Server, defaultOutClientForThis proxy.Client) { for { newConn, ok := <-newConnChan if !ok { - if ce := utils.CanLogErr("read from SuperProxy not ok"); ce != nil { - ce.Write() - - } + utils.Error("read from SuperProxy not ok") quic.CloseSession(baseConn) @@ -1150,7 +1099,7 @@ func dialClient(iics incomingInserverConnState, targetAddr netLayer.Addr, client } else { //虽然拨号失败,但是不能认为我们一定有错误, 因为很可能申请的ip本身就是不可达的, 所以不是error等级而是warn等级 - if ce := utils.CanLogWarn("failed in dial"); ce != nil { + if ce := utils.CanLogWarn("failed dialing"); ce != nil { ce.Write( zap.String("target", realTargetAddr.String()), zap.Error(err), diff --git a/netLayer/addr.go b/netLayer/addr.go index 8aa0af7..99595ff 100644 --- a/netLayer/addr.go +++ b/netLayer/addr.go @@ -383,6 +383,9 @@ func UDPAddr_v4_to_Bytes(addr *net.UDPAddr) [6]byte { } func UDPAddr2AddrPort(ua *net.UDPAddr) netip.AddrPort { + if ua == nil { + return netip.AddrPort{} + } a, _ := netip.AddrFromSlice(ua.IP) return netip.AddrPortFrom(a, uint16(ua.Port)) } diff --git a/netLayer/geosite.go b/netLayer/geosite.go index a6774df..6470a41 100644 --- a/netLayer/geosite.go +++ b/netLayer/geosite.go @@ -12,6 +12,8 @@ import ( "net/http" "os" "path/filepath" + "regexp" + "strings" "github.com/hahahrfool/v2ray_simple/utils" ) @@ -83,7 +85,9 @@ var GeositeListMap = make(map[string]*GeositeList) //geosite:cn 这种是geosite列表匹配 func IsDomainInsideGeosite(geositeName string, domain string) bool { + geositeName = strings.ToUpper(geositeName) glist := GeositeListMap[geositeName] + //log.Println("IsDomainInsideGeosite called", geositeName, len(glist)) if glist == nil { return false } @@ -91,11 +95,15 @@ func IsDomainInsideGeosite(geositeName string, domain string) bool { if _, found := glist.FullDomains[domain]; found { return true } - if found := HasFullOrSubDomain(domain, MapGeositeDomainHaser(glist.Domains)); found { + if HasFullOrSubDomain(domain, MapGeositeDomainHaser(glist.Domains)) { return true } - //todo: regex part + for _, reg := range glist.RegexDomains { + if reg.MatchString(domain) { + return true + } + } return false } @@ -122,7 +130,7 @@ type GeositeList struct { FullDomains map[string]GeositeDomain Domains map[string]GeositeDomain - RegexDomains []GeositeDomain + RegexDomains []*regexp.Regexp } type MapGeositeDomainHaser map[string]GeositeDomain @@ -132,10 +140,15 @@ func (mdh MapGeositeDomainHaser) HasDomain(d string) bool { return found } -//从 geosite/data 文件夹中读取所有文件并加载到 GeositeListMap 中 +//从 geosite/data 文件夹中读取所有文件并加载到 GeositeListMap 中. +// +//该 geosite/data 就是 github.com/v2fly/domain-list-community 项目的 data文件夹. func LoadGeositeFiles() (err error) { dir := "geosite/data" dir = utils.GetFilePath(dir) + if !utils.DirExist(dir) { + return os.ErrNotExist + } ref := make(map[string]*GeositeRawList) err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { @@ -169,7 +182,7 @@ func LoadGeositeFiles() (err error) { return nil } -// 该函数适用于系统中没有git的情况, 如果有git我们直接 git clone就行了 +// 该函数适用于系统中没有git的情况, 如果有git我们直接 git clone就行了,而且还能不断pull进行滚动更新 func DownloadCommunity_DomainListFiles() { resp, err := http.Get("https://api.github.com/repos/v2fly/domain-list-community/releases/latest") if err != nil { diff --git a/netLayer/geosite_parse.go b/netLayer/geosite_parse.go index 43d9ac1..63683e8 100644 --- a/netLayer/geosite_parse.go +++ b/netLayer/geosite_parse.go @@ -5,11 +5,12 @@ import ( "errors" "os" "path/filepath" + "regexp" "strconv" "strings" ) -//来自 v2fly, 改动改了一下命名。 +//来自 v2fly, 有一定改动. // GeositeRawList 用于序列化 type GeositeRawList struct { @@ -34,7 +35,7 @@ func LoadGeositeFile(path string) (*GeositeRawList, error) { if len(line) == 0 { continue } - entry, err := parseGeoSiteEntry(line) + entry, err := parseGeositeEntry(line) if err != nil { return nil, err } @@ -44,7 +45,7 @@ func LoadGeositeFile(path string) (*GeositeRawList, error) { return list, nil } -func parseGeoSiteEntry(line string) (GeositeDomain, error) { +func parseGeositeEntry(line string) (GeositeDomain, error) { line = strings.TrimSpace(line) parts := strings.Split(line, " ") @@ -219,13 +220,17 @@ func (grl *GeositeRawList) ToGeositeList() (gl *GeositeList) { gl.Name = grl.Name gl.Domains = make(map[string]GeositeDomain) gl.FullDomains = make(map[string]GeositeDomain) - gl.RegexDomains = make([]GeositeDomain, 0) + gl.RegexDomains = make([]*regexp.Regexp, 0) for _, v := range grl.Domains { switch v.Type { case "domain": gl.Domains[v.Value] = v case "regexp": - gl.RegexDomains = append(gl.RegexDomains, v) + reg, err := regexp.Compile(v.Value) + if err == nil { + gl.RegexDomains = append(gl.RegexDomains, reg) + + } case "full": gl.FullDomains[v.Value] = v } diff --git a/netLayer/route.go b/netLayer/route.go index 4e347dd..75d32b5 100644 --- a/netLayer/route.go +++ b/netLayer/route.go @@ -3,6 +3,7 @@ package netLayer import ( "math/rand" "net/netip" + "regexp" "strings" "github.com/yl2chen/cidranger" @@ -53,9 +54,17 @@ type TargetDescription struct { // RouteSet 只负责把一些属性相同的 “网络层/传输层 特征” 放到一起 type RouteSet struct { //网络层 - NetRanger cidranger.Ranger //一个范围 - IPs map[netip.Addr]bool //一个确定值 - Domains, InTags, Countries map[string]bool // Countries 使用 ISO 3166 字符串 作为key + NetRanger cidranger.Ranger //一个范围 + IPs map[netip.Addr]bool //一个确定值 + + //Match 匹配任意字符串 + //Domains匹配子域名,当此域名是目标域名或其子域名时,该规则生效 + //Full只匹配完整域名 + Domains, Full, InTags, Countries map[string]bool // Countries 使用 ISO 3166 字符串 作为key + + //Regex是正则匹配域名 + Regex []*regexp.Regexp + Match, Geosites []string //传输层 AllowedTransportLayerProtocols uint16 @@ -76,7 +85,7 @@ func NewRouteSetForMyCountry(iso string) *RouteSet { AllowedTransportLayerProtocols: TCP | UDP, //默认即支持tcp和udp } - rs.Countries[iso] = true + rs.Countries[strings.ToUpper(iso)] = true rs.Domains[strings.ToLower(iso)] = true //iso字符串的小写正好可以作为顶级域名 return rs } @@ -85,7 +94,10 @@ func NewFullRouteSet() *RouteSet { return &RouteSet{ NetRanger: cidranger.NewPCTrieRanger(), IPs: make(map[netip.Addr]bool), + Match: make([]string, 0), Domains: make(map[string]bool), + Full: make(map[string]bool), + Geosites: make([]string, 0), InTags: make(map[string]bool), Countries: make(map[string]bool), AllowedTransportLayerProtocols: TCP | UDP, //默认即支持tcp和udp @@ -160,12 +172,45 @@ func (sg *RouteSet) IsAddrIn(a Addr) bool { } if a.Name != "" { - if sg.Domains != nil { + + if len(sg.Full) > 0 { + if _, found := sg.Full[a.Name]; found { + return true + } + } + + if len(sg.Domains) > 0 { return HasFullOrSubDomain(a.Name, MapDomainHaser(sg.Domains)) } + if len(sg.Match) > 0 { + for _, m := range sg.Match { + if strings.Contains(a.Name, m) { + return true + } + } + } + + if len(sg.Regex) > 0 { + for _, reg := range sg.Regex { + if reg.MatchString(a.Name) { + return true + } + } + } + + if len(sg.Geosites) > 0 && len(GeositeListMap) > 0 { + + for _, g := range sg.Geosites { + if IsDomainInsideGeosite(g, a.Name) { + return true + } + } + + } + } return false } diff --git a/proxy/config_standard.go b/proxy/config_standard.go index d0c0b25..52154ed 100644 --- a/proxy/config_standard.go +++ b/proxy/config_standard.go @@ -6,6 +6,7 @@ import ( "net" "net/netip" "os" + "regexp" "strconv" "strings" @@ -172,6 +173,14 @@ func LoadRulesForRoutePolicy(rules []*RuleConf, policy *netLayer.RoutePolicy) { } func LoadRuleForRouteSet(rule *RuleConf) (rs *netLayer.RouteSet) { + if len(netLayer.GeositeListMap) == 0 { + err := netLayer.LoadGeositeFiles() + if err != nil { + if ce := utils.CanLogWarn("geosite folder not exist"); ce != nil { + ce.Write(zap.Error(err)) + } + } + } rs = netLayer.NewFullRouteSet() switch value := rule.DialTag.(type) { @@ -185,12 +194,37 @@ func LoadRuleForRouteSet(rule *RuleConf) (rs *netLayer.RouteSet) { rs.Countries[c] = true } - for _, c := range rule.Domains { - rs.Domains[c] = true + for _, d := range rule.Domains { + colonIdx := strings.Index(d, ":") + if colonIdx < 0 { + rs.Match = append(rs.Match, d) + + } else { + switch d[:colonIdx] { + case "geosite": + if netLayer.GeositeListMap != nil { + rs.Geosites = append(rs.Geosites, d[colonIdx+1:]) + + } + case "full": + rs.Full[d[colonIdx+1:]] = true + case "domain": + rs.Domains[d[colonIdx+1:]] = true + case "regexp": + reg, err := regexp.Compile(d[colonIdx+1:]) + if err == nil { + rs.Regex = append(rs.Regex, reg) + } + } + + } + + continue + } - for _, c := range rule.InTags { - rs.InTags[c] = true + for _, t := range rule.InTags { + rs.InTags[t] = true } //ip 过滤 需要 分辨 cidr 和普通ip diff --git a/utils/log.go b/utils/log.go index de3dc5c..5c4c1f7 100644 --- a/utils/log.go +++ b/utils/log.go @@ -1,4 +1,3 @@ -// Package utils provides utilities that is used in all sub-packages in verysimple package utils import ( @@ -102,3 +101,19 @@ func CanLogFatal(msg string) *zapcore.CheckedEntry { return ZapLogger.Check(zap.FatalLevel, msg) } + +func Debug(msg string) { + ZapLogger.Debug(msg) +} +func Info(msg string) { + ZapLogger.Info(msg) +} +func Warn(msg string) { + ZapLogger.Warn(msg) +} +func Error(msg string) { + ZapLogger.Error(msg) +} +func Fatal(msg string) { + ZapLogger.Fatal(msg) +} diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 0000000..064a2e3 --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,14 @@ +// Package utils provides utilities that is used in all codes in verysimple +package utils + +import "flag" + +func IsFlagPassed(name string) bool { + found := false + flag.Visit(func(f *flag.Flag) { + if f.Name == name { + found = true + } + }) + return found +}