修订,重构代码, 修复dns的bug; 添加Dns的DoT功能.

修复dns配置中"特殊服务器" 无法被正确配置、使用的bug

将 proxy.Standard结构 移动到 项目根目录的 StandardConf.
将 proxy.AppConf, LoadTomlConfStr, LoadTomlConfFile 函数 移动到根目录

因为 StandardConf和 AppConf里包含很多App级别的配置, 不宜放到proxy子包中

将 proxy.RuleConf 移动到 netLayer
将 proxy.LoadRulesForRoutePolicy 移动到 netLayer
将 proxy.LoadDnsMachine 移动到 netLayer

在dnsquery失败后,会判断错误, 若发现是Read错误,则会试图重新拨号
This commit is contained in:
hahahrfool
2022-04-07 13:45:24 +08:00
parent 8bfe56bb24
commit 2d384314f4
16 changed files with 762 additions and 508 deletions

30
cli.go
View File

@@ -9,6 +9,7 @@ import (
"strings" "strings"
"github.com/asaskevich/govalidator" "github.com/asaskevich/govalidator"
"github.com/hahahrfool/v2ray_simple/netLayer"
"github.com/hahahrfool/v2ray_simple/proxy" "github.com/hahahrfool/v2ray_simple/proxy"
"github.com/hahahrfool/v2ray_simple/proxy/vless" "github.com/hahahrfool/v2ray_simple/proxy/vless"
"github.com/hahahrfool/v2ray_simple/quic" "github.com/hahahrfool/v2ray_simple/quic"
@@ -60,7 +61,7 @@ func init() {
return return
} }
fmt.Printf("你选择了 %q\n", result) fmt.Printf("你选择了 %s\n", result)
switch i { switch i {
case 0: case 0:
@@ -136,7 +137,7 @@ func runCli() {
return return
} }
fmt.Printf("你选择了 %q\n", result) fmt.Printf("你选择了 %s\n", result)
if f := cliCmdList[i].F; f != nil { if f := cliCmdList[i].F; f != nil {
f() f()
@@ -156,8 +157,8 @@ func generateConfigFileInteractively() {
"将此次生成的配置投入运行(热加载)", "将此次生成的配置投入运行(热加载)",
} }
confClient := proxy.Standard{} confClient := StandardConf{}
confServer := proxy.Standard{} confServer := StandardConf{}
var clientStr, serverStr string var clientStr, serverStr string
@@ -174,16 +175,16 @@ func generateConfigFileInteractively() {
return return
} }
fmt.Printf("你选择了 %q\n", result) fmt.Printf("你选择了 %s\n", result)
generateConfStr := func() { generateConfStr := func() {
confClient.Route = []*proxy.RuleConf{{ confClient.Route = []*netLayer.RuleConf{{
DialTag: "direct", DialTag: "direct",
Domains: []string{"geosite:cn"}, Domains: []string{"geosite:cn"},
}} }}
confClient.App = &proxy.AppConf{MyCountryISO_3166: "CN"} confClient.App = &AppConf{MyCountryISO_3166: "CN"}
clientStr, err = utils.GetPurgedTomlStr(confClient) clientStr, err = utils.GetPurgedTomlStr(confClient)
if err != nil { if err != nil {
@@ -210,8 +211,8 @@ func generateConfigFileInteractively() {
fmt.Printf("\n") fmt.Printf("\n")
case 2: //clear case 2: //clear
confClient = proxy.Standard{} confClient = StandardConf{}
confServer = proxy.Standard{} confServer = StandardConf{}
clientStr = "" clientStr = ""
serverStr = "" serverStr = ""
case 3: //output case 3: //output
@@ -310,7 +311,7 @@ func generateConfigFileInteractively() {
return return
} }
fmt.Printf("你选择了 %q\n", result) fmt.Printf("你选择了 %s\n", result)
if i2 < 2 { if i2 < 2 {
confClient.Listen = append(confClient.Listen, &proxy.ListenConf{}) confClient.Listen = append(confClient.Listen, &proxy.ListenConf{})
@@ -374,7 +375,7 @@ func generateConfigFileInteractively() {
return return
} }
fmt.Printf("你选择了 %q\n", result) fmt.Printf("你选择了 %s\n", result)
confClient.Dial = append(confClient.Dial, &proxy.DialConf{}) confClient.Dial = append(confClient.Dial, &proxy.DialConf{})
clientDial := confClient.Dial[0] clientDial := confClient.Dial[0]
@@ -543,6 +544,9 @@ func generateConfigFileInteractively() {
serverListen.TLSKey = "cert.key" serverListen.TLSKey = "cert.key"
serverListen.Insecure = true serverListen.Insecure = true
clientDial.Insecure = true clientDial.Insecure = true
fmt.Printf("你选择了默认自签名证书, 这是不安全的, 我们不推荐. 所以自动生成证书这一步需要你一会再到交互模式里选择相应选项进行生成。 \n")
} else { } else {
fmt.Printf("请输入 cert路径\n") fmt.Printf("请输入 cert路径\n")
@@ -609,7 +613,7 @@ func interactively_hotRemoveServerOrClient() {
var will_delete_index int var will_delete_index int
fmt.Printf("你选择了 %q\n", result) fmt.Printf("你选择了 %s\n", result)
switch i { switch i {
case 0: case 0:
will_delete_listen = true will_delete_listen = true
@@ -707,7 +711,7 @@ func interactively_hotLoadConfigFile() {
fmt.Printf("你输入了 %s\n", fpath) fmt.Printf("你输入了 %s\n", fpath)
standardConf, err = proxy.LoadTomlConfFile(fpath) standardConf, err = LoadTomlConfFile(fpath)
if err != nil { if err != nil {
log.Printf("can not load standard config file: %s\n", err) log.Printf("can not load standard config file: %s\n", err)

View File

@@ -19,7 +19,6 @@ import (
var ( var (
cmdPrintSupportedProtocols bool cmdPrintSupportedProtocols bool
//cmdGenerateUUID bool
interactive_mode bool interactive_mode bool
nodownload bool nodownload bool
@@ -28,7 +27,6 @@ var (
func init() { func init() {
flag.BoolVar(&cmdPrintSupportedProtocols, "sp", false, "print supported protocols") flag.BoolVar(&cmdPrintSupportedProtocols, "sp", false, "print supported protocols")
//flag.BoolVar(&cmdGenerateUUID, "gu", false, "generate a random valid uuid string")
flag.BoolVar(&interactive_mode, "i", false, "enable interactive commandline mode") flag.BoolVar(&interactive_mode, "i", false, "enable interactive commandline mode")
flag.BoolVar(&nodownload, "nd", false, "don't automatically download any extra data files") flag.BoolVar(&nodownload, "nd", false, "don't automatically download any extra data files")
flag.BoolVar(&cmdPrintVer, "v", false, "print the version string then exit") flag.BoolVar(&cmdPrintVer, "v", false, "print the version string then exit")
@@ -85,11 +83,6 @@ func runPreCommands() {
} }
//if cmdGenerateUUID {
// generateAndPrintUUID()
//}
} }
func generateAndPrintUUID() { func generateAndPrintUUID() {
@@ -179,7 +172,7 @@ func tryDownloadGeositeSourceFromConfiguredProxy() {
protocol = "http" protocol = "http"
` `
clientConf, err := proxy.LoadTomlConfStr(tempClientConfStr) clientConf, err := LoadTomlConfStr(tempClientConfStr)
if err != nil { if err != nil {
fmt.Println("can not create LoadTomlConfStr: ", err) fmt.Println("can not create LoadTomlConfStr: ", err)

View File

@@ -3,10 +3,13 @@ package main
import ( import (
"errors" "errors"
"flag" "flag"
"io/ioutil"
"log" "log"
"net/url" "net/url"
"os"
"path/filepath" "path/filepath"
"github.com/BurntSushi/toml"
"github.com/hahahrfool/v2ray_simple/httpLayer" "github.com/hahahrfool/v2ray_simple/httpLayer"
"github.com/hahahrfool/v2ray_simple/netLayer" "github.com/hahahrfool/v2ray_simple/netLayer"
"github.com/hahahrfool/v2ray_simple/proxy" "github.com/hahahrfool/v2ray_simple/proxy"
@@ -21,6 +24,48 @@ func init() {
flag.IntVar(&jsonMode, "jm", 0, "json mode, 0:verysimple mode; 1: v2ray mode(not implemented yet)") flag.IntVar(&jsonMode, "jm", 0, "json mode, 0:verysimple mode; 1: v2ray mode(not implemented yet)")
} }
type AppConf struct {
LogLevel *int `toml:"loglevel"` //需要为指针, 否则无法判断0到底是未给出的默认值还是 显式声明的0
DefaultUUID string `toml:"default_uuid"`
MyCountryISO_3166 string `toml:"mycountry" json:"mycountry"` //加了mycountry后就会自动按照geoip分流,也会对顶级域名进行国别分流
NoReadV bool `toml:"noreadv"`
AdminPass string `toml:"admin_pass"`
}
//标准配置。默认使用toml格式
// tomlhttps://toml.io/cn/
// english: https://toml.io/en/
type StandardConf struct {
App *AppConf `toml:"app"`
DnsConf *netLayer.DnsConf `toml:"dns"`
Listen []*proxy.ListenConf `toml:"listen"`
Dial []*proxy.DialConf `toml:"dial"`
Route []*netLayer.RuleConf `toml:"route"`
Fallbacks []*httpLayer.FallbackConf `toml:"fallback"`
}
func LoadTomlConfStr(str string) (c StandardConf, err error) {
_, err = toml.Decode(str, &c)
return
}
func LoadTomlConfFile(fileNamePath string) (StandardConf, error) {
if cf, err := os.Open(fileNamePath); err == nil {
defer cf.Close()
bs, _ := ioutil.ReadAll(cf)
return LoadTomlConfStr(string(bs))
} else {
return StandardConf{}, utils.ErrInErr{ErrDesc: "can't open config file", ErrDetail: err}
}
}
//mainfallback, dnsMachine, routePolicy //mainfallback, dnsMachine, routePolicy
func loadCommonComponentsFromStandardConf() { func loadCommonComponentsFromStandardConf() {
@@ -29,7 +74,7 @@ func loadCommonComponentsFromStandardConf() {
} }
if dnsConf := standardConf.DnsConf; dnsConf != nil { if dnsConf := standardConf.DnsConf; dnsConf != nil {
dnsMachine = proxy.LoadDnsMachine(dnsConf) dnsMachine = netLayer.LoadDnsMachine(dnsConf)
} }
hasAppLevelMyCountry := (standardConf.App != nil && standardConf.App.MyCountryISO_3166 != "") hasAppLevelMyCountry := (standardConf.App != nil && standardConf.App.MyCountryISO_3166 != "")
@@ -44,7 +89,7 @@ func loadCommonComponentsFromStandardConf() {
} }
proxy.LoadRulesForRoutePolicy(standardConf.Route, routePolicy) netLayer.LoadRulesForRoutePolicy(standardConf.Route, routePolicy)
} }
} }
@@ -57,7 +102,7 @@ func loadConfig() (err error) {
ext := filepath.Ext(fpath) ext := filepath.Ext(fpath)
if ext == ".toml" { if ext == ".toml" {
standardConf, err = proxy.LoadTomlConfFile(fpath) standardConf, err = LoadTomlConfFile(fpath)
if err != nil { if err != nil {
log.Printf("can not load standard config file: %s\n", err) log.Printf("can not load standard config file: %s\n", err)

View File

@@ -22,7 +22,7 @@ servers = [
"udp://114.114.114.114:53", # 如果把该url指向我们dokodemo监听的端口就可以达到通过节点请求dns的目的. "udp://114.114.114.114:53", # 如果把该url指向我们dokodemo监听的端口就可以达到通过节点请求dns的目的.
#"udp://127.0.0.1:63782", # 如这一行 就是通过我们节点请求dns #"udp://127.0.0.1:63782", # 如这一行 就是通过我们节点请求dns
{ addr = "udp://1.1.1.1:53", domains = [ "google.com" ] } # 还可以为特定域名指定特定服务器 { addr = "udp://8.8.8.8:53", domain = [ "google.com" ] } # 还可以为特定域名指定特定服务器
] ]
[dns.hosts] # 自己定义的dns解析 [dns.hosts] # 自己定义的dns解析

View File

@@ -45,7 +45,7 @@ var (
confMode int = -1 //0: simple json, 1: standard toml, 2: v2ray compatible json confMode int = -1 //0: simple json, 1: standard toml, 2: v2ray compatible json
simpleConf proxy.Simple simpleConf proxy.Simple
standardConf proxy.Standard standardConf StandardConf
directClient, _, _ = proxy.ClientFromURL("direct://") directClient, _, _ = proxy.ClientFromURL("direct://")
defaultOutClient proxy.Client defaultOutClient proxy.Client
default_uuid string default_uuid string

View File

@@ -1,6 +1,7 @@
package netLayer package netLayer
import ( import (
"crypto/tls"
"errors" "errors"
"math/rand" "math/rand"
"net" "net"
@@ -230,6 +231,7 @@ func (a *Addr) GetNetIPAddr() (na netip.Addr) {
return return
} }
//a.Network == "udp", "udp4", "udp6"
func (a *Addr) IsUDP() bool { func (a *Addr) IsUDP() bool {
return IsStrUDP_network(a.Network) return IsStrUDP_network(a.Network)
} }
@@ -253,11 +255,19 @@ func (a *Addr) HostStr() string {
func (addr *Addr) Dial() (net.Conn, error) { func (addr *Addr) Dial() (net.Conn, error) {
//log.Println("Dial called", addr, addr.Network) //log.Println("Dial called", addr, addr.Network)
var istls bool
var resultConn net.Conn
var err error
switch addr.Network { switch addr.Network {
case "": case "":
addr.Network = "tcp" addr.Network = "tcp"
goto tcp goto tcp
case "tcp", "tcp4", "tcp6":
goto tcp
case "tls": //此形式目前被用于dns配置中 的 dns over tls 的 url中
istls = true
goto tcp
case "udp", "udp4", "udp6": case "udp", "udp4", "udp6":
ua := addr.ToUDPAddr() ua := addr.ToUDPAddr()
@@ -267,36 +277,56 @@ func (addr *Addr) Dial() (net.Conn, error) {
return DialUDP(ua) return DialUDP(ua)
default: default:
if strings.HasPrefix(addr.Network, "tcp") {
goto tcp
}
goto defaultPart goto defaultPart
} }
tcp: tcp:
if addr.IP != nil { if addr.IP != nil {
if addr.IP.To4() == nil { if addr.IP.To4() == nil {
if !machineCanConnectToIpv6 { if !machineCanConnectToIpv6 {
return nil, ErrMachineCantConnectToIpv6 return nil, ErrMachineCantConnectToIpv6
} else { } else {
return net.DialTCP("tcp6", nil, &net.TCPAddr{ resultConn, err = net.DialTCP("tcp6", nil, &net.TCPAddr{
IP: addr.IP, IP: addr.IP,
Port: addr.Port, Port: addr.Port,
}) })
goto dialedPart
} }
} else { } else {
return net.DialTCP("tcp4", nil, &net.TCPAddr{ resultConn, err = net.DialTCP("tcp4", nil, &net.TCPAddr{
IP: addr.IP, IP: addr.IP,
Port: addr.Port, Port: addr.Port,
}) })
goto dialedPart
} }
} }
defaultPart: defaultPart:
return net.Dial(addr.Network, addr.String()) resultConn, err = net.Dial(addr.Network, addr.String())
dialedPart:
if istls && err == nil {
conf := &tls.Config{}
if addr.Name != "" {
conf.ServerName = addr.Name
} else {
conf.InsecureSkipVerify = true
}
tlsconn := tls.Client(resultConn, conf)
err = tlsconn.Handshake()
return tlsconn, err
}
return resultConn, err
} }
// 如果a的ip不为空则会返回 AtypIP4 或 AtypIP6否则会返回 AtypDomain // 如果a的ip不为空则会返回 AtypIP4 或 AtypIP6否则会返回 AtypDomain

View File

@@ -1,8 +1,10 @@
package netLayer package netLayer
import ( import (
"errors"
"net" "net"
"net/netip" "net/netip"
"os"
"strings" "strings"
"sync" "sync"
@@ -13,12 +15,46 @@ import (
var globalDnsQueryMutex sync.Mutex var globalDnsQueryMutex sync.Mutex
var ErrRecursion = errors.New("multiple recursion not allowed")
// 判断 DNSQuery 返回的错误 是否是 Read底层连接 的错误
func Is_DNSQuery_returnType_ReadErr(err error) bool {
if err == nil {
return false
}
switch err {
case os.ErrNotExist, dns.ErrRcode, ErrRecursion:
return false
default:
return true
}
}
//筛除掉 Is_DNSQuery_returnType_ReadErr 时err 为 net.Error.Timeout() 的情况
func Is_DNSQuery_returnType_ReadFatalErr(err error) bool {
if !Is_DNSQuery_returnType_ReadErr(err) {
return false
}
if ne, ok := err.(net.Error); ok {
if ne.Timeout() {
return false
}
return true
}
return false
}
//domain必须是 dns.Fqdn 函数 包过的, 本函数不检查是否包过。如果不包过就传入,会报错。 //domain必须是 dns.Fqdn 函数 包过的, 本函数不检查是否包过。如果不包过就传入,会报错。
// dns_type 为 miekg/dns 包中定义的类型, 如 TypeA, TypeAAAA, TypeCNAME. // dns_type 为 miekg/dns 包中定义的类型, 如 TypeA, TypeAAAA, TypeCNAME.
// conn是一个建立好的 dns.Conn, 必须非空, 本函数不检查. // conn是一个建立好的 dns.Conn, 必须非空, 本函数不检查.
// theMux是与 conn相匹配的mutex, 这是为了防止同时有多个请求导致无法对口内部若判断为nil,会主动使用一个全局mux. // theMux是与 conn相匹配的mutex, 这是为了防止同时有多个请求导致无法对口内部若判断为nil,会主动使用一个全局mux.
// recursionCount 使用者统一填0 即可,用于内部 遇到cname时进一步查询时防止无限递归. // recursionCount 使用者统一填0 即可,用于内部 遇到cname时进一步查询时防止无限递归.
func DNSQuery(domain string, dns_type uint16, conn *dns.Conn, theMux *sync.Mutex, recursionCount int) net.IP { //
// 如果从conn中Read后成功返回, 则可能返回如下几种错误 os.ErrNotExist (表示查无此记录), dns.ErrRcode (表示dns返回的 Rcode 不是 dns.RcodeSuccess), ErrRecursion,
// 如果不是这三个error, 那就是 从 该 conn 读取数据时出错了.
func DNSQuery(domain string, dns_type uint16, conn *dns.Conn, theMux *sync.Mutex, recursionCount int) (net.IP, error) {
m := new(dns.Msg) m := new(dns.Msg)
m.SetQuestion((domain), dns_type) //为了更快,不使用 dns.Fqdn, 请调用之前先确保ok m.SetQuestion((domain), dns_type) //为了更快,不使用 dns.Fqdn, 请调用之前先确保ok
c := new(dns.Client) c := new(dns.Client)
@@ -37,7 +73,7 @@ func DNSQuery(domain string, dns_type uint16, conn *dns.Conn, theMux *sync.Mutex
if ce := utils.CanLogErr("dns query read err"); ce != nil { if ce := utils.CanLogErr("dns query read err"); ce != nil {
ce.Write(zap.Error(err)) ce.Write(zap.Error(err))
} }
return nil return nil, err
} }
if r.Rcode != dns.RcodeSuccess { if r.Rcode != dns.RcodeSuccess {
@@ -45,20 +81,20 @@ func DNSQuery(domain string, dns_type uint16, conn *dns.Conn, theMux *sync.Mutex
//dns查不到的情况是很有可能的所以还是放在debug日志里 //dns查不到的情况是很有可能的所以还是放在debug日志里
ce.Write(zap.Error(err), zap.Int("rcode", r.Rcode), zap.String("value", r.String())) ce.Write(zap.Error(err), zap.Int("rcode", r.Rcode), zap.String("value", r.String()))
} }
return nil return nil, dns.ErrRcode
} }
switch dns_type { switch dns_type {
case dns.TypeA: case dns.TypeA:
for _, a := range r.Answer { for _, a := range r.Answer {
if aa, ok := a.(*dns.A); ok { if aa, ok := a.(*dns.A); ok {
return aa.A return aa.A, nil
} }
} }
case dns.TypeAAAA: case dns.TypeAAAA:
for _, a := range r.Answer { for _, a := range r.Answer {
if aa, ok := a.(*dns.AAAA); ok { if aa, ok := a.(*dns.AAAA); ok {
return aa.AAAA return aa.AAAA, nil
} }
} }
} }
@@ -76,20 +112,24 @@ func DNSQuery(domain string, dns_type uint16, conn *dns.Conn, theMux *sync.Mutex
if ce := utils.CanLogDebug("dns query got cname but recursionCount>2"); ce != nil { if ce := utils.CanLogDebug("dns query got cname but recursionCount>2"); ce != nil {
ce.Write(zap.String("query", domain), zap.String("cname", aa.Target)) ce.Write(zap.String("query", domain), zap.String("cname", aa.Target))
} }
return nil return nil, ErrRecursion
} }
return DNSQuery(dns.Fqdn(aa.Target), dns_type, conn, theMux, recursionCount+1) return DNSQuery(dns.Fqdn(aa.Target), dns_type, conn, theMux, recursionCount+1)
} }
} }
return nil return nil, os.ErrNotExist
} }
// 给 miekg/dns.Conn 加一个互斥锁, 可保证同一时间仅有一个请求发生 // 给 miekg/dns.Conn 加一个互斥锁, 可保证同一时间仅有一个请求发生
// 这样就不会造成并发时的混乱 // 这样就不会造成并发时的混乱
type DnsConn struct { type DnsConn struct {
*dns.Conn *dns.Conn
Name string //我们这里惯例直接使用配置文件中配置的url字符串作为Name
raddr *Addr //这个用于在Conn出故障后, 重新拨号时所使用
mutex sync.Mutex mutex sync.Mutex
garbageMark bool
} }
//dns machine维持与多个dns服务器的连接(最好是udp这种无状态的)并可以发起dns请求。 //dns machine维持与多个dns服务器的连接(最好是udp这种无状态的)并可以发起dns请求。
@@ -98,9 +138,9 @@ type DnsConn struct {
// SpecialServerPollicy 用于为特殊的 域名指定特殊的 dns服务器这样遇到这种域名时会通过该特定服务器查询 // SpecialServerPollicy 用于为特殊的 域名指定特殊的 dns服务器这样遇到这种域名时会通过该特定服务器查询
type DNSMachine struct { type DNSMachine struct {
TypeStrategy int64 // 0, 4, 6, 40, 60 TypeStrategy int64 // 0, 4, 6, 40, 60
DefaultConn DnsConn defaultConn DnsConn
conns map[string]*DnsConn conns map[string]*DnsConn
cache map[string]net.IP cache map[string]net.IP //cache的key统一为 未经 Fqdn包装过的域名. 即尾部没有点号
SpecialIPPollicy map[string][]netip.Addr SpecialIPPollicy map[string][]netip.Addr
@@ -110,63 +150,89 @@ type DNSMachine struct {
} }
//并不初始化所有内部成员, 只是创建空结构并拨号若为nil则号也不拨 // Dial通过 c 内部设置好的地址进行拨号,并将 c.Conn.Conn 设为 新建立好的连接
func NewDnsMachine(defaultDnsServerAddr *Addr) *DNSMachine { func (c *DnsConn) Dial() error {
var dm DNSMachine nc, err := DialDnsAddr(c.raddr)
if defaultDnsServerAddr != nil { if err != nil {
return err
}
c.Conn.Conn = nc
return nil
}
var conn net.Conn //建立一个与dns服务器连接, 可为纯udp的dns, 或者 DoT的. 如果是DoT的, 则要求 addr.Network == "tls",
var err error // 如果是纯udp的要求 addr.IsUDP() == true
func DialDnsAddr(addr *Addr) (conn net.Conn, err error) {
//实测 miekg/dns 必须用 net.PacketConn, 不过本作udp最新代码已经支持了. //实测 miekg/dns 必须用 net.PacketConn, 不过本作udp最新代码已经支持了.
// 不过dns还是没必要额外包装一次, 直接用原始的udp即可. // 不过dns还是没必要额外包装一次, 直接用原始的udp即可.
//在 miekg/dns 遇到非 net.PacketConn 的连接时,会采用不同的办法,先从数据读取一个长度信息,然后再读其它信息,可能它没有料到 net.Conn 被包装的情况 //在 miekg/dns 遇到非 net.PacketConn 的连接时,会采用不同的办法,先从数据读取一个长度信息,然后再读其它信息,可能它没有料到 net.Conn 被包装的情况
if defaultDnsServerAddr.IsUDP() { /*
conn, err = net.DialUDP("udp", nil, defaultDnsServerAddr.ToUDPAddr()) dns over tls rfchttps://datatracker.ietf.org/doc/html/rfc7858
} else { 853端口
conn, err = defaultDnsServerAddr.Dial()
} 根据
https://datatracker.ietf.org/doc/html/rfc7858#section-3.3
每个信息之前都要传2字节的信息长度
所以显然 miekg/dns 认为传入的conn不是 net.UDPConn 就是 tls.Conn
另外miekg/dns 不支持 doh, 证据在 https://github.com/miekg/dns/pull/800
就是因为 doh完全和 dot不同使用了不同的数据结构.
*/
if addr.IsUDP() {
conn, err = net.DialUDP("udp", nil, addr.ToUDPAddr())
} else {
conn, err = addr.Dial()
}
//todo: 以后支持DoH的话要分离出https这个Network然后单独使用独特方法进行dial
return
}
func (dm *DNSMachine) SetDefaultConn(c net.Conn, addr *Addr) {
dm.defaultConn.Conn = new(dns.Conn)
dm.defaultConn.Conn.Conn = c
dm.defaultConn.raddr = addr
}
// 添加一个 特定的DNS服务器 , name为该dns服务器的名称. 若第一次调用, 则会设为 dm.DefaultConn
func (dm *DNSMachine) AddNewServer(name string, addr *Addr) error {
if dm.defaultConn.Conn == nil { //若未配置过 DefaultConn
dm.defaultConn = DnsConn{Conn: new(dns.Conn), raddr: addr, Name: name}
err := dm.defaultConn.Dial()
if err != nil { if err != nil {
if ce := utils.CanLogErr("NewDnsMachine"); ce != nil { return err
ce.Write(zap.Error(err)) }
} } else {
dcc := &DnsConn{Conn: new(dns.Conn), raddr: addr, Name: name}
err := dcc.Dial()
if err != nil {
return err
} }
dc := new(dns.Conn) if dm.conns == nil {
dc.Conn = conn dm.conns = make(map[string]*DnsConn)
dm.DefaultConn.Conn = dc }
dm.conns[name] = dcc
} }
return &dm return nil
}
func (dm *DNSMachine) SetDefaultConn(c net.Conn) {
dm.DefaultConn.Conn = new(dns.Conn)
dm.DefaultConn.Conn.Conn = c
}
// 添加一个 特定名称的 域名服务器的 连接。
//name为该dns服务器的名称
func (dm *DNSMachine) AddConnForServer(name string, c net.Conn) {
dc := new(dns.Conn)
dc.Conn = c
if dm.conns == nil {
dm.conns = map[string]*DnsConn{}
}
dcc := &DnsConn{Conn: dc}
dm.conns[name] = dcc
} }
func (dm *DNSMachine) Query(domain string) (ip net.IP) { func (dm *DNSMachine) Query(domain string) (ip net.IP) {
switch dm.TypeStrategy { switch dm.TypeStrategy {
default: default:
fallthrough fallthrough
case 0: case 0, 4:
fallthrough
case 4:
ip = dm.QueryType(domain, dns.TypeA) ip = dm.QueryType(domain, dns.TypeA)
if ip == nil { if ip == nil {
ip = dm.QueryType(domain, dns.TypeAAAA) ip = dm.QueryType(domain, dns.TypeAAAA)
@@ -187,14 +253,44 @@ func (dm *DNSMachine) Query(domain string) (ip net.IP) {
//传入的domain必须是不带尾缀点号的domain, 即没有包过 Fqdn //传入的domain必须是不带尾缀点号的domain, 即没有包过 Fqdn
func (dm *DNSMachine) QueryType(domain string, dns_type uint16) (ip net.IP) { func (dm *DNSMachine) QueryType(domain string, dns_type uint16) (ip net.IP) {
var generalCacheHit bool // 若读到了 cache 或 SpecialIPPollicy 的项, 则 generalCacheHit 为 true var generalCacheHit bool // 若读到了 cache 或 SpecialIPPollicy 的项, 则 generalCacheHit 为 true
var theDNSServerConn *DnsConn
defer func() { defer func() {
if theDNSServerConn != nil && theDNSServerConn.garbageMark {
dm.mutex.Lock()
delete(dm.conns, theDNSServerConn.Name)
if theDNSServerConn == &dm.defaultConn {
//如果DefaultConn都废了那就糟糕
//我们选一个备用的conn升格为defaultConn
dm.defaultConn.Conn = nil
if len(dm.conns) > 0 {
for name, c := range dm.conns {
dm.defaultConn.Conn = c.Conn
dm.defaultConn.garbageMark = false
delete(dm.conns, name)
break
}
}
}
dm.mutex.Unlock()
}
if generalCacheHit { if generalCacheHit {
if ce := utils.CanLogDebug("[DNSMachine] hit cache"); ce != nil {
ce.Write(zap.String("domain", domain), zap.String("ip", ip.String()))
}
return return
} }
if len(ip) > 0 { if len(ip) > 0 {
if ce := utils.CanLogDebug("will add to dns cache"); ce != nil { domain = strings.TrimSuffix(domain, ".")
ce.Write(zap.String("domain", domain)) if ce := utils.CanLogDebug("[DNSMachine] will add to cache"); ce != nil {
ce.Write(zap.String("domain", domain), zap.String("ip", ip.String()))
} }
dm.mutex.Lock() dm.mutex.Lock()
@@ -202,15 +298,19 @@ func (dm *DNSMachine) QueryType(domain string, dns_type uint16) (ip net.IP) {
dm.cache = make(map[string]net.IP) dm.cache = make(map[string]net.IP)
} }
domain = strings.TrimSuffix(domain, ".")
dm.cache[domain] = ip dm.cache[domain] = ip
dm.mutex.Unlock() dm.mutex.Unlock()
} }
}() }()
//golang的defer原理: 多个defer 调用顺序是LIFO后入先出defer后的操作可以理解为压入栈中
//所以实际是会先 dm.mutex.RUnlock(), 再调用 上面的 RLock, RUnlock, 所以不存在死锁导致无法return的问题
dm.mutex.RLock() dm.mutex.RLock()
defer dm.mutex.RUnlock() defer dm.mutex.RUnlock()
// 查找步骤:
//先从 cache找有就直接返回 //先从 cache找有就直接返回
//然后, //然后,
//先查 specialIPPollicy类似cache有就直接返回 //先查 specialIPPollicy类似cache有就直接返回
@@ -221,9 +321,7 @@ func (dm *DNSMachine) QueryType(domain string, dns_type uint16) (ip net.IP) {
if dm.cache != nil { if dm.cache != nil {
if ip = dm.cache[domain]; ip != nil { if ip = dm.cache[domain]; ip != nil {
generalCacheHit = true generalCacheHit = true
if ce := utils.CanLogDebug("hit dns cache"); ce != nil {
ce.Write(zap.String("domain", domain))
}
return return
} }
} }
@@ -254,17 +352,49 @@ func (dm *DNSMachine) QueryType(domain string, dns_type uint16) (ip net.IP) {
} }
} }
theDNSServerConn := &dm.DefaultConn theDNSServerConn = &dm.defaultConn
if dm.conns != nil && dm.SpecialServerPollicy != nil { if len(dm.conns) > 0 && len(dm.SpecialServerPollicy) > 0 {
if sn := dm.SpecialServerPollicy[domain]; sn != "" { if dnsServerName := dm.SpecialServerPollicy[domain]; dnsServerName != "" {
if serConn := dm.conns[domain]; serConn != nil { if serConn := dm.conns[dnsServerName]; serConn != nil {
theDNSServerConn = serConn theDNSServerConn = serConn
} }
} }
} }
if theDNSServerConn.Conn == nil { //如果配置文件只配置了自定义映射, 而没配置dns服务器的话, 那么我们就无法进行实际的dns查询
if ce := utils.CanLogDebug("[DNSMachine] no server configured, return nil."); ce != nil {
ce.Write()
}
return
}
domain = dns.Fqdn(domain) domain = dns.Fqdn(domain)
return DNSQuery(domain, dns_type, theDNSServerConn.Conn, &theDNSServerConn.mutex, 0)
if ce := utils.CanLogDebug("[DNSMachine] start querying"); ce != nil {
ce.Write(zap.String("domain", domain), zap.String("through", theDNSServerConn.Name))
}
ip, err := DNSQuery(domain, dns_type, theDNSServerConn.Conn, &theDNSServerConn.mutex, 0)
if Is_DNSQuery_returnType_ReadFatalErr(err) {
//如果是读取的、非timeout的错误那么我们直接认为底层连接出故障了, 我们需要重新dial
//因为 miekg/dns 包会设置4秒的timeout所以确实要筛除timeout的情况
theDNSServerConn.Conn.Close()
err = theDNSServerConn.Dial()
if err != nil {
//再dial还是错误那么就废了
if ce := utils.CanLogErr("[DNSMachine] Re-Dial Dns Server Failed"); ce != nil {
ce.Write(zap.Error(err))
}
theDNSServerConn.garbageMark = true
}
//我们只是重新Dial并不再次查询否则就又递归了
}
return ip
} }

143
netLayer/dns_conf.go Normal file
View File

@@ -0,0 +1,143 @@
package netLayer
import (
"log"
"net"
"net/netip"
"github.com/hahahrfool/v2ray_simple/utils"
"go.uber.org/zap"
)
type DnsConf struct {
Strategy int64 `toml:"strategy"` //0表示默认(和4含义相同), 4表示先查ip4后查ip6, 6表示先查6后查4; 40表示只查ipv4, 60 表示只查ipv6
Hosts map[string]any `toml:"hosts"` //用于强制指定哪些域名会被解析为哪些具体的ip可以为一个ip字符串或者一个 []string 数组, 数组内可以是A,AAAA或CNAME
Servers []any `toml:"servers"` //可以为一个地址url字符串或者为 SpecialDnsServerConf; 如果第一个元素是url字符串形式则此第一个元素将会被用作默认dns服务器
}
type SpecialDnsServerConf struct {
AddrUrlStr string `toml:"addr"` //必须为 udp://1.1.1.1:53 这种格式
Domains []string `toml:"domain"` //指定哪些域名需要通过 该dns服务器进行查询
}
func loadSpecialDnsServerConf_fromTomlUnmarshaledMap(m map[string]any) *SpecialDnsServerConf {
addr := m["addr"]
if addr == nil {
return nil
}
addrStr, ok := addr.(string)
if !ok {
return nil
}
domains := m["domain"]
if domains == nil {
return nil
}
domainsAnySlice, ok := domains.([]any)
if !ok {
return nil
}
domainsSlice := []string{}
for _, anyD := range domainsAnySlice {
dstr, ok := anyD.(string)
if !ok {
return nil
}
domainsSlice = append(domainsSlice, dstr)
}
return &SpecialDnsServerConf{
Domains: domainsSlice,
AddrUrlStr: addrStr,
}
}
func LoadDnsMachine(conf *DnsConf) *DNSMachine {
var dm = &DNSMachine{TypeStrategy: conf.Strategy}
var ok = false
if len(conf.Servers) > 0 {
//log.Println("conf.Servers", conf.Servers)
ok = true
servers := conf.Servers
dm.SpecialServerPollicy = make(map[string]string)
for _, ser := range servers {
switch server := ser.(type) {
case string:
ad, e := NewAddrByURL(server)
if e != nil {
continue
}
dm.AddNewServer(server, &ad)
case map[string]any:
realServer := loadSpecialDnsServerConf_fromTomlUnmarshaledMap(server)
if realServer == nil {
continue
}
if len(realServer.Domains) <= 0 { //既然是特殊dns服务器, 那么就必须指定哪些域名要使用该dns服务器进行查询
continue
}
addr, e := NewAddrByURL(realServer.AddrUrlStr)
if e != nil {
continue
}
if err := dm.AddNewServer(realServer.AddrUrlStr, &addr); err != nil {
continue
}
for _, thisdomain := range realServer.Domains {
dm.SpecialServerPollicy[thisdomain] = realServer.AddrUrlStr
}
}
}
}
if conf.Hosts != nil {
ok = true
dm.SpecialIPPollicy = make(map[string][]netip.Addr)
for thishost, things := range conf.Hosts {
switch value := things.(type) {
case string:
ip := net.ParseIP(value)
ad, _ := netip.AddrFromSlice(ip)
dm.SpecialIPPollicy[thishost] = []netip.Addr{ad}
case []string:
for _, str := range value {
ad, err := NewAddrFromAny(str)
if err != nil {
if utils.ZapLogger != nil {
utils.ZapLogger.Fatal("LoadDnsMachine loading host err", zap.Error(err))
} else {
log.Fatalf("LoadDnsMachine loading host err %s\n", err)
}
}
dm.SpecialIPPollicy[thishost] = append(dm.SpecialIPPollicy[thishost], ad.GetHashable().Addr())
}
}
}
}
if !ok {
return nil
}
return dm
}

View File

@@ -3,38 +3,36 @@ package netLayer_test
import ( import (
"testing" "testing"
"github.com/hahahrfool/v2ray_simple/proxy" "github.com/BurntSushi/toml"
"github.com/hahahrfool/v2ray_simple/netLayer"
"github.com/hahahrfool/v2ray_simple/utils" "github.com/hahahrfool/v2ray_simple/utils"
) )
func TestDNS(t *testing.T) { type testConfStruct struct {
DnsConf *netLayer.DnsConf `toml:"dns"`
}
func testDns_withConf(t *testing.T, config string) {
utils.LogLevel = utils.Log_debug utils.LogLevel = utils.Log_debug
utils.InitLog() utils.InitLog()
config := ` config += `
[dns.hosts]
[dns]
servers = [
"udp://114.114.114.114:53"
]
[dns.hosts]
"www.myfake.com" = "11.22.33.44" "www.myfake.com" = "11.22.33.44"
`
var c testConfStruct
_, e := toml.Decode(config, &c)
`
c, e := proxy.LoadTomlConfStr(config)
if e != nil { if e != nil {
t.Log(e) t.Log(e)
t.FailNow() t.FailNow()
} }
t.Log(c.DnsConf) t.Log(c.DnsConf)
dm := proxy.LoadDnsMachine(c.DnsConf) dm := netLayer.LoadDnsMachine(c.DnsConf)
t.Log(&dm) t.Log(&dm)
t.Log(dm.DefaultConn.RemoteAddr().Network(), dm.DefaultConn.RemoteAddr())
//dm.TypeStrategy = 60 //dm.TypeStrategy = 60
@@ -44,5 +42,54 @@ servers = [
t.Log("record for imgstat.baidu.com is ", dm.Query("imgstat.baidu.com")) t.Log("record for imgstat.baidu.com is ", dm.Query("imgstat.baidu.com"))
t.Log("record for imgstat.n.shifen.com is ", dm.Query("imgstat.n.shifen.com")) t.Log("record for imgstat.n.shifen.com is ", dm.Query("imgstat.n.shifen.com"))
}
func TestDNS(t *testing.T) {
const config = `
[dns]
servers = [
"udp://114.114.114.114:53"
]
`
testDns_withConf(t, config)
}
func TestDNS_DoT(t *testing.T) {
const config = `
[dns]
servers = [
"tls://223.5.5.5:853"
]
`
testDns_withConf(t, config)
}
func TestDNS_SpecialServer(t *testing.T) {
const config = `
[dns]
servers = [
{ addr = "udp://8.8.8.8:53", domain = [ "google.com" ] }
]
`
utils.LogLevel = utils.Log_debug
utils.InitLog()
var c testConfStruct
_, e := toml.Decode(config, &c)
if e != nil {
t.Log(e)
t.FailNow()
}
t.Log(c.DnsConf)
dm := netLayer.LoadDnsMachine(c.DnsConf)
t.Log(&dm)
//dm.TypeStrategy = 60
t.Log("record for google.com is ", dm.Query("google.com"))
} }

View File

@@ -110,6 +110,7 @@ func GetRawConn(reader io.Reader) syscall.RawConn {
return nil return nil
} }
//"udp", "udp4", "udp6"
func IsStrUDP_network(s string) bool { func IsStrUDP_network(s string) bool {
switch s { switch s {
case "udp", "udp4", "udp6": case "udp", "udp4", "udp6":

109
netLayer/route_conf.go Normal file
View File

@@ -0,0 +1,109 @@
package netLayer
import (
"net"
"net/netip"
"regexp"
"strings"
"github.com/hahahrfool/v2ray_simple/utils"
"github.com/yl2chen/cidranger"
"go.uber.org/zap"
)
type RuleConf struct {
DialTag any `toml:"dialTag"`
InTags []string `toml:"inTag"`
Countries []string `toml:"country"` // 如果类似 !CN, 则意味着专门匹配不为CN 的国家(目前还未实现)
IPs []string `toml:"ip"`
Domains []string `toml:"domain"`
Network []string `toml:"network"`
}
func LoadRulesForRoutePolicy(rules []*RuleConf, policy *RoutePolicy) {
for _, rc := range rules {
newrs := LoadRuleForRouteSet(rc)
policy.List = append(policy.List, newrs)
}
}
func LoadRuleForRouteSet(rule *RuleConf) (rs *RouteSet) {
if len(GeositeListMap) == 0 {
err := LoadGeositeFiles()
if err != nil {
if ce := utils.CanLogWarn("LoadGeositeFiles err"); ce != nil {
ce.Write(zap.Error(err))
}
}
}
rs = NewFullRouteSet()
switch value := rule.DialTag.(type) {
case string:
rs.OutTag = value
case []string:
rs.OutTags = value
}
for _, c := range rule.Countries {
rs.Countries[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 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 _, t := range rule.InTags {
rs.InTags[t] = true
}
//ip 过滤 需要 分辨 cidr 和普通ip
for _, ipStr := range rule.IPs {
if strings.Contains(ipStr, "/") {
if _, net, err := net.ParseCIDR(ipStr); err == nil {
rs.NetRanger.Insert(cidranger.NewBasicRangerEntry(*net))
}
continue
}
na, e := netip.ParseAddr(ipStr)
if e == nil {
rs.IPs[na] = true
}
}
for _, ns := range rule.Network {
tp := StrToTransportProtocol(ns)
rs.AllowedTransportLayerProtocols |= tp
}
return rs
}

88
proxy/config.go Normal file
View File

@@ -0,0 +1,88 @@
package proxy
import (
"strconv"
)
// CommonConf 是标准配置中 Listen和Dial 都有的部分
//如果新协议有其他新项,可以放入 Extra.
type CommonConf struct {
Tag string `toml:"tag"` //可选
Protocol string `toml:"protocol"` //约定如果一个Protocol尾缀去掉了's'后仍然是一个有效协议,则该协议使用了 tls。这种方法继承自 v2simple适合极简模式
Uuid string `toml:"uuid"` //一个用户的唯一标识建议使用uuid但也不一定
Host string `toml:"host"` //ip 或域名. 若unix domain socket 则为文件路径
IP string `toml:"ip"` //给出Host后该项可以省略; 既有Host又有ip的情况比较适合cdn
Port int `toml:"port"` //若Network不为 unix , 则port项必填
Version int `toml:"version"` //可选
TLS bool `toml:"tls"` //可选. 如果不使用 's' 后缀法则还可以配置这一项来更清晰第标明使用tls
Insecure bool `toml:"insecure"` //tls 是否安全
Alpn []string `toml:"alpn"`
Network string `toml:"network"` //默认使用tcp, network可选值为 tcp, udp, unix;
AdvancedLayer string `toml:"advancedLayer"` //可不填或者为ws或者为grpc
Path string `toml:"path"` //ws 的path 或 grpc的 serviceName。为了简便我们在同一位置给出.
Extra map[string]interface{} `toml:"extra"` //用于包含任意其它数据.虽然本包自己定义的协议肯定都是已知的,但是如果其他人使用了本包的话,那就有可能添加一些 新协议 特定的数据.
}
func (cc *CommonConf) GetAddrStr() string {
switch cc.Network {
case "unix":
return cc.Host
default:
if cc.Host != "" {
return cc.Host + ":" + strconv.Itoa(cc.Port)
} else {
return cc.IP + ":" + strconv.Itoa(cc.Port)
}
}
}
//和 GetAddr的区别是它优先使用ip其次再使用host
func (cc *CommonConf) GetAddrStrForListenOrDial() string {
switch cc.Network {
case "unix":
return cc.Host
default:
if cc.IP != "" {
return cc.IP + ":" + strconv.Itoa(cc.Port)
} else {
return cc.Host + ":" + strconv.Itoa(cc.Port)
}
}
}
// 监听所使用的设置, 使用者可被称为 listener 或者 inServer
// CommonConf.Host , CommonConf.IP, CommonConf.Port 为监听地址与端口
type ListenConf struct {
CommonConf
Fallback any `toml:"fallback"` //可选默认回落的地址一般可以是ip:port,数字port 或者 unix socket的文件名
TLSCert string `toml:"cert"`
TLSKey string `toml:"key"`
//noroute 意味着 传入的数据 不会被分流,一定会被转发到默认的 dial
// 这一项是针对 分流功能的. 如果不设noroute, 则所有listen 得到的流量都会被 试图 进行分流
NoRoute bool `toml:"noroute"`
TargetAddr string `toml:"target"` //若使用dokodemo协议则这一项会给出. 格式为url, 如 tcp://127.0.0.1:443 , 必须带scheme以及端口。只能为tcp或udp
}
// 拨号所使用的设置, 使用者可被称为 dialer 或者 outClient
// CommonConf.Host , CommonConf.IP, CommonConf.Port 为拨号地址与端口
type DialConf struct {
CommonConf
Utls bool `toml:"utls"`
}

View File

@@ -6,6 +6,7 @@ import (
"os" "os"
"github.com/hahahrfool/v2ray_simple/httpLayer" "github.com/hahahrfool/v2ray_simple/httpLayer"
"github.com/hahahrfool/v2ray_simple/netLayer"
"github.com/hahahrfool/v2ray_simple/utils" "github.com/hahahrfool/v2ray_simple/utils"
) )
@@ -13,7 +14,7 @@ import (
type Simple struct { type Simple struct {
Server_ThatListenPort_Url string `json:"listen"` Server_ThatListenPort_Url string `json:"listen"`
Client_ThatDialRemote_Url string `json:"dial"` Client_ThatDialRemote_Url string `json:"dial"`
Route []*RuleConf `json:"route" toml:"route"` Route []*netLayer.RuleConf `json:"route" toml:"route"`
Fallbacks []*httpLayer.FallbackConf `json:"fallbacks"` Fallbacks []*httpLayer.FallbackConf `json:"fallbacks"`
MyCountryISO_3166 string `toml:"mycountry" json:"mycountry"` MyCountryISO_3166 string `toml:"mycountry" json:"mycountry"`
} }

View File

@@ -1,337 +0,0 @@
package proxy
import (
"io/ioutil"
"log"
"net"
"net/netip"
"os"
"regexp"
"strconv"
"strings"
"github.com/BurntSushi/toml"
"github.com/hahahrfool/v2ray_simple/httpLayer"
"github.com/hahahrfool/v2ray_simple/netLayer"
"github.com/hahahrfool/v2ray_simple/utils"
"github.com/yl2chen/cidranger"
"go.uber.org/zap"
)
// CommonConf 是标准配置中 Listen和Dial 都有的部分
//如果新协议有其他新项,可以放入 Extra.
type CommonConf struct {
Tag string `toml:"tag"` //可选
Protocol string `toml:"protocol"` //约定如果一个Protocol尾缀去掉了's'后仍然是一个有效协议,则该协议使用了 tls。这种方法继承自 v2simple适合极简模式
Uuid string `toml:"uuid"` //一个用户的唯一标识建议使用uuid但也不一定
Host string `toml:"host"` //ip 或域名. 若unix domain socket 则为文件路径
IP string `toml:"ip"` //给出Host后该项可以省略; 既有Host又有ip的情况比较适合cdn
Port int `toml:"port"` //若Network不为 unix , 则port项必填
Version int `toml:"version"` //可选
TLS bool `toml:"tls"` //可选. 如果不使用 's' 后缀法则还可以配置这一项来更清晰第标明使用tls
Insecure bool `toml:"insecure"` //tls 是否安全
Alpn []string `toml:"alpn"`
Network string `toml:"network"` //默认使用tcp, network可选值为 tcp, udp, unix;
AdvancedLayer string `toml:"advancedLayer"` //可不填或者为ws或者为grpc
Path string `toml:"path"` //ws 的path 或 grpc的 serviceName。为了简便我们在同一位置给出.
Extra map[string]interface{} `toml:"extra"` //用于包含任意其它数据.虽然本包自己定义的协议肯定都是已知的,但是如果其他人使用了本包的话,那就有可能添加一些 新协议 特定的数据.
}
func (cc *CommonConf) GetAddrStr() string {
switch cc.Network {
case "unix":
return cc.Host
default:
if cc.Host != "" {
return cc.Host + ":" + strconv.Itoa(cc.Port)
} else {
return cc.IP + ":" + strconv.Itoa(cc.Port)
}
}
}
//和 GetAddr的区别是它优先使用ip其次再使用host
func (cc *CommonConf) GetAddrStrForListenOrDial() string {
switch cc.Network {
case "unix":
return cc.Host
default:
if cc.IP != "" {
return cc.IP + ":" + strconv.Itoa(cc.Port)
} else {
return cc.Host + ":" + strconv.Itoa(cc.Port)
}
}
}
// 监听所使用的设置, 使用者可被称为 listener 或者 inServer
// CommonConf.Host , CommonConf.IP, CommonConf.Port 为监听地址与端口
type ListenConf struct {
CommonConf
Fallback any `toml:"fallback"` //可选默认回落的地址一般可以是ip:port,数字port 或者 unix socket的文件名
TLSCert string `toml:"cert"`
TLSKey string `toml:"key"`
//noroute 意味着 传入的数据 不会被分流,一定会被转发到默认的 dial
// 这一项是针对 mycountry 分流功能的. 如果不设noroute, 且给定了 app.mycountry, 则所有listener 得到的流量都会被 试图 进行国别分流
NoRoute bool `toml:"noroute"`
TargetAddr string `toml:"target"` //若使用dokodemo协议则这一项会给出. 格式 tcp://127.0.0.1:443 , 必须带scheme以及端口。只能为tcp或udp
}
// 拨号所使用的设置, 使用者可被称为 dialer 或者 outClient
// CommonConf.Host , CommonConf.IP, CommonConf.Port 为拨号地址与端口
type DialConf struct {
CommonConf
Utls bool `toml:"utls"`
}
type AppConf struct {
LogLevel *int `toml:"loglevel"` //需要为指针, 否则无法判断0到底是未给出的默认值还是 显式声明的0
DefaultUUID string `toml:"default_uuid"`
MyCountryISO_3166 string `toml:"mycountry" json:"mycountry"` //加了mycountry后就会自动按照geoip分流,也会对顶级域名进行国别分流
NoReadV bool `toml:"noreadv"`
AdminPass string `toml:"admin_pass"`
}
type DnsConf struct {
Strategy int64 `toml:"strategy"` //0表示默认(和4含义相同), 4表示先查ip4后查ip6, 6表示先查6后查4; 40表示只查ipv4, 60 表示只查ipv6
Hosts map[string]any `toml:"hosts"` //用于强制指定哪些域名会被解析为哪些具体的ip可以为一个ip字符串或者一个 []string 数组, 数组内可以是A,AAAA或CNAME
Servers []any `toml:"servers"` //可以为一个地址url字符串或者为 SpecialDnsServerConf; 如果第一个元素是字符串形式则此第一个元素将会被用作默认dns服务器
}
type SpecialDnsServerConf struct {
Addr string `toml:"addr"` //必须为 udp://1.1.1.1:53 这种格式
Domains []string `toml:"domains"` //指定哪些域名需要通过 该dns服务器进行查询
}
type RuleConf struct {
DialTag any `toml:"dialTag"`
InTags []string `toml:"inTag"`
Countries []string `toml:"country"` // 如果类似 !CN, 则意味着专门匹配不为CN 的国家(目前还未实现)
IPs []string `toml:"ip"`
Domains []string `toml:"domain"`
Network []string `toml:"network"`
}
//标准配置。默认使用toml格式
// tomlhttps://toml.io/cn/
// english: https://toml.io/en/
type Standard struct {
App *AppConf `toml:"app"`
DnsConf *DnsConf `toml:"dns"`
Listen []*ListenConf `toml:"listen"`
Dial []*DialConf `toml:"dial"`
Route []*RuleConf `toml:"route"`
Fallbacks []*httpLayer.FallbackConf `toml:"fallback"`
}
func LoadTomlConfStr(str string) (c Standard, err error) {
_, err = toml.Decode(str, &c)
return
}
func LoadTomlConfFile(fileNamePath string) (Standard, error) {
if cf, err := os.Open(fileNamePath); err == nil {
defer cf.Close()
bs, _ := ioutil.ReadAll(cf)
return LoadTomlConfStr(string(bs))
} else {
return Standard{}, utils.ErrInErr{ErrDesc: "can't open config file", ErrDetail: err}
}
}
func LoadRulesForRoutePolicy(rules []*RuleConf, policy *netLayer.RoutePolicy) {
for _, rc := range rules {
newrs := LoadRuleForRouteSet(rc)
policy.List = append(policy.List, newrs)
}
}
func LoadRuleForRouteSet(rule *RuleConf) (rs *netLayer.RouteSet) {
if len(netLayer.GeositeListMap) == 0 {
err := netLayer.LoadGeositeFiles()
if err != nil {
if ce := utils.CanLogWarn("netLayer.LoadGeositeFiles err"); ce != nil {
ce.Write(zap.Error(err))
}
}
}
rs = netLayer.NewFullRouteSet()
switch value := rule.DialTag.(type) {
case string:
rs.OutTag = value
case []string:
rs.OutTags = value
}
for _, c := range rule.Countries {
rs.Countries[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 _, t := range rule.InTags {
rs.InTags[t] = true
}
//ip 过滤 需要 分辨 cidr 和普通ip
for _, ipStr := range rule.IPs {
if strings.Contains(ipStr, "/") {
if _, net, err := net.ParseCIDR(ipStr); err == nil {
rs.NetRanger.Insert(cidranger.NewBasicRangerEntry(*net))
}
continue
}
na, e := netip.ParseAddr(ipStr)
if e == nil {
rs.IPs[na] = true
}
}
for _, ns := range rule.Network {
tp := netLayer.StrToTransportProtocol(ns)
rs.AllowedTransportLayerProtocols |= tp
}
return rs
}
func LoadDnsMachine(conf *DnsConf) *netLayer.DNSMachine {
var dm = &netLayer.DNSMachine{TypeStrategy: conf.Strategy}
var ok = false
if len(conf.Servers) > 0 {
ok = true
ss := conf.Servers
first := ss[0]
firstDealed := false
switch value := first.(type) {
case string:
ad, e := netLayer.NewAddrByURL(value)
if e != nil {
if utils.ZapLogger != nil {
utils.ZapLogger.Fatal("LoadDnsMachine loading server err", zap.Error(e))
} else {
log.Fatalf("LoadDnsMachine loading server err %s\n", e)
}
}
dm = netLayer.NewDnsMachine(&ad)
dm.TypeStrategy = conf.Strategy
firstDealed = true
}
if firstDealed {
ss = ss[1:]
}
dm.SpecialServerPollicy = make(map[string]string)
for _, s := range ss {
switch value := s.(type) {
case SpecialDnsServerConf:
for _, d := range value.Domains {
dm.SpecialServerPollicy[d] = value.Addr
}
}
}
}
if conf.Hosts != nil {
ok = true
dm.SpecialIPPollicy = make(map[string][]netip.Addr)
for thishost, things := range conf.Hosts {
switch value := things.(type) {
case string:
ip := net.ParseIP(value)
ad, _ := netip.AddrFromSlice(ip)
dm.SpecialIPPollicy[thishost] = []netip.Addr{ad}
case []string:
for _, str := range value {
ad, err := netLayer.NewAddrFromAny(str)
if err != nil {
if utils.ZapLogger != nil {
utils.ZapLogger.Fatal("LoadDnsMachine loading host err", zap.Error(err))
} else {
log.Fatalf("LoadDnsMachine loading host err %s\n", err)
}
}
dm.SpecialIPPollicy[thishost] = append(dm.SpecialIPPollicy[thishost], ad.GetHashable().Addr())
}
}
}
}
if !ok {
return nil
}
return dm
}

View File

@@ -4,7 +4,6 @@ import (
"net/url" "net/url"
"testing" "testing"
"github.com/BurntSushi/toml"
"github.com/hahahrfool/v2ray_simple/proxy" "github.com/hahahrfool/v2ray_simple/proxy"
) )
@@ -48,61 +47,3 @@ func TestClientSimpleConfig(t *testing.T) {
t.Log(i, v) t.Log(i, v)
} }
} }
func TestTomlConfig(t *testing.T) {
var conf proxy.Standard
_, err := toml.Decode(testTomlConfStr, &conf)
if err != nil {
t.Log(err)
t.FailNow()
}
t.Log(conf)
t.Log("dial0", conf.Dial[0])
t.Log("listen0", conf.Listen[0])
t.Log("extra", conf.Listen[0].Extra)
t.Log(conf.Route[0])
t.Log(conf.Route[1])
t.Log(conf.Fallbacks)
}
const testTomlConfStr = `# this is a verysimple standard config
[app]
mycountry = "CN"
[[dial]]
tag = "my_vlesss1"
protocol = "vlesss"
uuid = "a684455c-b14f-11ea-bf0d-42010aaa0003"
host = "127.0.0.1"
port = 4433
version = 0
insecure = true
utls = true
[[listen]]
protocol = "socks5"
host = "127.0.0.1"
port = 1080
tag = "my_socks51"
extra = { ws_earlydata = 4096 }
[[route]]
dialTag = "my_ws1"
country = ["CN"]
ip = ["0.0.0.0/8","10.0.0.0/8","fe80::/10","10.0.0.1"]
domain = ["www.google.com","www.twitter.com"]
network = ["tcp","udp"]
[[route]]
dialTag = "my_vless1"
[[fallback]]
path = "/asf"
dest = 6060
`

View File

@@ -4,6 +4,7 @@ import (
"log" "log"
"testing" "testing"
"github.com/BurntSushi/toml"
"github.com/hahahrfool/v2ray_simple/proxy" "github.com/hahahrfool/v2ray_simple/proxy"
"github.com/hahahrfool/v2ray_simple/utils" "github.com/hahahrfool/v2ray_simple/utils"
"github.com/miekg/dns" "github.com/miekg/dns"
@@ -81,12 +82,12 @@ cert = "cert.pem"
key = "cert.key" key = "cert.key"
` `
clientConf, err := proxy.LoadTomlConfStr(testClientConfStr) clientConf, err := LoadTomlConfStr(testClientConfStr)
if err != nil { if err != nil {
t.Log(err) t.Log(err)
t.FailNow() t.FailNow()
} }
serverConf, err := proxy.LoadTomlConfStr(testServerConfStr) serverConf, err := LoadTomlConfStr(testServerConfStr)
if err != nil { if err != nil {
t.Log(err) t.Log(err)
t.FailNow() t.FailNow()
@@ -142,3 +143,61 @@ key = "cert.key"
} }
} }
} }
func TestLoadTomlConf(t *testing.T) {
var conf StandardConf
_, err := toml.Decode(testTomlConfStr, &conf)
if err != nil {
t.Log(err)
t.FailNow()
}
t.Log(conf)
t.Log("dial0", conf.Dial[0])
t.Log("listen0", conf.Listen[0])
t.Log("extra", conf.Listen[0].Extra)
t.Log(conf.Route[0])
t.Log(conf.Route[1])
t.Log(conf.Fallbacks)
}
const testTomlConfStr = `# this is a verysimple standard config
[app]
mycountry = "CN"
[[dial]]
tag = "my_vlesss1"
protocol = "vlesss"
uuid = "a684455c-b14f-11ea-bf0d-42010aaa0003"
host = "127.0.0.1"
port = 4433
version = 0
insecure = true
utls = true
[[listen]]
protocol = "socks5"
host = "127.0.0.1"
port = 1080
tag = "my_socks51"
extra = { ws_earlydata = 4096 }
[[route]]
dialTag = "my_ws1"
country = ["CN"]
ip = ["0.0.0.0/8","10.0.0.0/8","fe80::/10","10.0.0.1"]
domain = ["www.google.com","www.twitter.com"]
network = ["tcp","udp"]
[[route]]
dialTag = "my_vless1"
[[fallback]]
path = "/asf"
dest = 6060
`