修订代码,示例;添加geosite分流,域名现已支持full,sub,regex,geosite,match

This commit is contained in:
hahahrfool
2022-04-05 08:59:26 +08:00
parent 59b9c0cb47
commit 3a997a32bc
10 changed files with 208 additions and 97 deletions

View File

@@ -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 这种写法

View File

@@ -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"]

69
main.go
View File

@@ -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可以为nilquic就是如此
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),

View File

@@ -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))
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -3,6 +3,7 @@ package netLayer
import (
"math/rand"
"net/netip"
"regexp"
"strings"
"github.com/yl2chen/cidranger"
@@ -55,7 +56,15 @@ type RouteSet struct {
//网络层
NetRanger cidranger.Ranger //一个范围
IPs map[netip.Addr]bool //一个确定值
Domains, InTags, Countries map[string]bool // Countries 使用 ISO 3166 字符串 作为key
//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
}

View File

@@ -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)
}
}
for _, c := range rule.InTags {
rs.InTags[c] = true
}
continue
}
for _, t := range rule.InTags {
rs.InTags[t] = true
}
//ip 过滤 需要 分辨 cidr 和普通ip

View File

@@ -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)
}

14
utils/utils.go Normal file
View File

@@ -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
}