feat: dns routing (#26)

This commit is contained in:
mzz
2023-02-25 02:38:21 +08:00
committed by GitHub
parent 33ad434f8a
commit 8bd6a77398
48 changed files with 2758 additions and 1449 deletions

View File

@ -7,7 +7,6 @@ package config
import (
"fmt"
"github.com/v2rayA/dae/common"
"github.com/v2rayA/dae/pkg/config_parser"
"reflect"
"time"
@ -18,40 +17,67 @@ type Global struct {
LogLevel string `mapstructure:"log_level" default:"info"`
// We use DirectTcpCheckUrl to check (tcp)*(ipv4/ipv6) connectivity for direct.
//DirectTcpCheckUrl string `mapstructure:"direct_tcp_check_url" default:"http://www.qualcomm.cn/generate_204"`
TcpCheckUrl string `mapstructure:"tcp_check_url" default:"http://keep-alv.google.com/generate_204"`
UdpCheckDns string `mapstructure:"udp_check_dns" default:"dns.google:53"`
CheckInterval time.Duration `mapstructure:"check_interval" default:"30s"`
CheckTolerance time.Duration `mapstructure:"check_tolerance" default:"0"`
DnsUpstream common.UrlOrEmpty `mapstructure:"dns_upstream" default:""`
LanInterface []string `mapstructure:"lan_interface"`
LanNatDirect bool `mapstructure:"lan_nat_direct" default:"true"`
WanInterface []string `mapstructure:"wan_interface"`
AllowInsecure bool `mapstructure:"allow_insecure" default:"false"`
DialMode string `mapstructure:"dial_mode" default:"domain"`
TcpCheckUrl string `mapstructure:"tcp_check_url" default:"http://keep-alv.google.com/generate_204"`
UdpCheckDns string `mapstructure:"udp_check_dns" default:"dns.google:53"`
CheckInterval time.Duration `mapstructure:"check_interval" default:"30s"`
CheckTolerance time.Duration `mapstructure:"check_tolerance" default:"0"`
DnsUpstream string `mapstructure:"dns_upstream" default:"<empty>"`
LanInterface []string `mapstructure:"lan_interface"`
LanNatDirect bool `mapstructure:"lan_nat_direct" default:"true"`
WanInterface []string `mapstructure:"wan_interface"`
AllowInsecure bool `mapstructure:"allow_insecure" default:"false"`
DialMode string `mapstructure:"dial_mode" default:"domain"`
}
type FunctionOrString interface{}
func FunctionOrStringToFunction(fs FunctionOrString) (f *config_parser.Function) {
switch fs := fs.(type) {
case string:
return &config_parser.Function{Name: fs}
case *config_parser.Function:
return fs
default:
panic(fmt.Sprintf("unknown type of 'fallback' in section routing: %T", fs))
}
}
type Group struct {
Name string
Param GroupParam
}
Name string `mapstructure:"_"`
type GroupParam struct {
Filter []*config_parser.Function `mapstructure:"filter"`
Policy interface{} `mapstructure:"policy" required:""`
}
type DnsRequestRouting struct {
Rules []*config_parser.RoutingRule `mapstructure:"_"`
Fallback FunctionOrString `mapstructure:"fallback" required:""`
}
type DnsResponseRouting struct {
Rules []*config_parser.RoutingRule `mapstructure:"_"`
Fallback FunctionOrString `mapstructure:"fallback" required:""`
}
type Dns struct {
Upstream []string `mapstructure:"upstream"`
Routing struct {
Request DnsRequestRouting `mapstructure:"request"`
Response DnsResponseRouting `mapstructure:"response"`
} `mapstructure:"routing"`
}
type Routing struct {
Rules []*config_parser.RoutingRule `mapstructure:"_"`
Fallback interface{} `mapstructure:"fallback"`
Final interface{} `mapstructure:"final"`
Fallback FunctionOrString `mapstructure:"fallback"`
Final FunctionOrString `mapstructure:"final"`
}
type Params struct {
Global Global `mapstructure:"global" parser:"ParamParser"`
Subscription []string `mapstructure:"subscription" parser:"StringListParser"`
Node []string `mapstructure:"node" parser:"StringListParser"`
Group []Group `mapstructure:"group" parser:"GroupListParser"`
Routing Routing `mapstructure:"routing" parser:"RoutingRuleAndParamParser"`
Global Global `mapstructure:"global" required:""`
Subscription []string `mapstructure:"subscription"`
Node []string `mapstructure:"node"`
Group []Group `mapstructure:"group" required:""`
Routing Routing `mapstructure:"routing" required:""`
Dns Dns `mapstructure:"dns"`
}
// New params from sections. This func assumes merging (section "include") and deduplication for section names has been executed.
@ -82,21 +108,15 @@ func New(sections []*config_parser.Section) (params *Params, err error) {
}
section, ok := nameToSection[sectionName]
if !ok {
return nil, fmt.Errorf("section %v is required but not provided", sectionName)
}
// Find corresponding parser func.
parserName, ok := structField.Tag.Lookup("parser")
if !ok {
return nil, fmt.Errorf("no parser is specified in field %v", structField.Name)
}
parser, ok := ParserMap[parserName]
if !ok {
return nil, fmt.Errorf("unknown parser %v in field %v", parserName, structField.Name)
if _, required := structField.Tag.Lookup("required"); required {
return nil, fmt.Errorf("section %v is required but not provided", sectionName)
} else {
continue
}
}
// Parse section and unmarshal to field.
if err := parser(field.Addr(), section.Val); err != nil {
if err := SectionParser(field.Addr(), section.Val); err != nil {
return nil, fmt.Errorf("failed to parse \"%v\": %w", sectionName, err)
}
section.Parsed = true

View File

@ -8,12 +8,15 @@ package config
import (
"fmt"
"github.com/sirupsen/logrus"
"github.com/v2rayA/dae/common/consts"
)
type patch func(params *Params) error
var patches = []patch{
patchRoutingFallback,
patchEmptyDns,
patchDeprecatedGlobalDnsUpstream,
}
func patchRoutingFallback(params *Params) error {
@ -28,3 +31,20 @@ func patchRoutingFallback(params *Params) error {
}
return nil
}
func patchEmptyDns(params *Params) error {
if params.Dns.Routing.Request.Fallback == nil {
params.Dns.Routing.Request.Fallback = consts.DnsRequestOutboundIndex_AsIs.String()
}
if params.Dns.Routing.Response.Fallback == nil {
params.Dns.Routing.Response.Fallback = consts.DnsResponseOutboundIndex_Accept.String()
}
return nil
}
func patchDeprecatedGlobalDnsUpstream(params *Params) error {
if params.Global.DnsUpstream != "<empty>" {
return fmt.Errorf("'global.dns_upstream' was deprecated, please refer to the latest examples and docs for help")
}
return nil
}

View File

@ -13,16 +13,6 @@ import (
"strings"
)
// Parser is section items parser
type Parser func(to reflect.Value, section *config_parser.Section) error
var ParserMap = map[string]Parser{
"StringListParser": StringListParser,
"ParamParser": ParamParser,
"GroupListParser": GroupListParser,
"RoutingRuleAndParamParser": RoutingRuleAndParamParser,
}
func StringListParser(to reflect.Value, section *config_parser.Section) error {
if to.Kind() != reflect.Pointer {
return fmt.Errorf("StringListParser can only unmarshal section to *[]string")
@ -44,7 +34,7 @@ func StringListParser(to reflect.Value, section *config_parser.Section) error {
return nil
}
func paramParser(to reflect.Value, section *config_parser.Section, ignoreType []reflect.Type) error {
func ParamParser(to reflect.Value, section *config_parser.Section, ignoreType []reflect.Type) error {
if to.Kind() != reflect.Pointer {
return fmt.Errorf("ParamParser can only unmarshal section to *struct")
}
@ -67,7 +57,7 @@ func paramParser(to reflect.Value, section *config_parser.Section, ignoreType []
// Set up key to field mapping.
key, ok := structField.Tag.Lookup("mapstructure")
if !ok {
return fmt.Errorf("field %v has no mapstructure tag", structField.Name)
return fmt.Errorf("field \"%v\" has no mapstructure tag", structField.Name)
}
if key == "_" {
// omit
@ -95,11 +85,11 @@ func paramParser(to reflect.Value, section *config_parser.Section, ignoreType []
switch itemVal := item.Value.(type) {
case *config_parser.Param:
if itemVal.Key == "" {
return fmt.Errorf("section %v does not support text without a key: %v", section.Name, itemVal.String(true))
return fmt.Errorf("unsupported text without a key: %v", itemVal.String(true))
}
field, ok := keyToField[itemVal.Key]
if !ok {
return fmt.Errorf("section %v does not support key: %v", section.Name, itemVal.Key)
return fmt.Errorf("unexpected key: %v", itemVal.Key)
}
if itemVal.AndFunctions != nil {
// AndFunctions.
@ -108,7 +98,7 @@ func paramParser(to reflect.Value, section *config_parser.Section, ignoreType []
field.Val.Type() == reflect.TypeOf(itemVal.AndFunctions) {
field.Val.Set(reflect.ValueOf(itemVal.AndFunctions))
} else {
return fmt.Errorf("failed to parse \"%v.%v\": value \"%v\" cannot be convert to %v", section.Name, itemVal.Key, itemVal.Val, field.Val.Type().String())
return fmt.Errorf("failed to parse \"%v\": value \"%v\" cannot be convert to %v", itemVal.Key, itemVal.Val, field.Val.Type().String())
}
} else {
// String value.
@ -122,21 +112,42 @@ func paramParser(to reflect.Value, section *config_parser.Section, ignoreType []
for _, value := range values {
vPointerNew := reflect.New(field.Val.Type().Elem())
if !common.FuzzyDecode(vPointerNew.Interface(), value) {
return fmt.Errorf("failed to parse \"%v.%v\": value \"%v\" cannot be convert to %v", section.Name, itemVal.Key, itemVal.Val, field.Val.Type().Elem().String())
return fmt.Errorf("failed to parse \"%v\": value \"%v\" cannot be convert to %v", itemVal.Key, itemVal.Val, field.Val.Type().Elem().String())
}
field.Val.Set(reflect.Append(field.Val, vPointerNew.Elem()))
}
default:
// Field is not interface{}, we can decode.
if !common.FuzzyDecode(field.Val.Addr().Interface(), itemVal.Val) {
return fmt.Errorf("failed to parse \"%v.%v\": value \"%v\" cannot be convert to %v", section.Name, itemVal.Key, itemVal.Val, field.Val.Type().String())
return fmt.Errorf("failed to parse \"%v\": value \"%v\" cannot be convert to %v", itemVal.Key, itemVal.Val, field.Val.Type().String())
}
}
}
field.Set = true
case *config_parser.Section:
// Named section config item.
field, ok := keyToField[itemVal.Name]
if !ok {
return fmt.Errorf("unexpected key: %v", itemVal.Name)
}
if err := SectionParser(field.Val.Addr(), itemVal); err != nil {
return fmt.Errorf("failed to parse %v: %w", itemVal.Name, err)
}
field.Set = true
case *config_parser.RoutingRule:
// Assign. "to" should have field "Rules".
structField, ok := to.Type().FieldByName("Rules")
if !ok || structField.Type != reflect.TypeOf([]*config_parser.RoutingRule{}) {
return fmt.Errorf("unexpected type: \"routing rule\": %v", itemVal.String(true))
}
if structField.Tag.Get("mapstructure") != "_" {
return fmt.Errorf("a []*RoutingRule field \"Rules\" with mapstructure:\"_\" is required in struct %v to parse section", to.Type().String())
}
field := to.FieldByName("Rules")
field.Set(reflect.Append(field, reflect.ValueOf(itemVal)))
default:
if _, ignore := ignoreTypeSet[reflect.TypeOf(itemVal)]; !ignore {
return fmt.Errorf("section %v does not support type %v: %v", section.Name, item.Type.String(), item.String())
return fmt.Errorf("unexpected type %v: %v", item.Type.String(), item.String())
}
}
}
@ -155,76 +166,66 @@ func paramParser(to reflect.Value, section *config_parser.Section, ignoreType []
return nil
}
func ParamParser(to reflect.Value, section *config_parser.Section) error {
return paramParser(to, section, nil)
}
func GroupListParser(to reflect.Value, section *config_parser.Section) error {
func SectionParser(to reflect.Value, section *config_parser.Section) error {
if to.Kind() != reflect.Pointer {
return fmt.Errorf("GroupListParser can only unmarshal section to *[]Group")
return fmt.Errorf("SectionParser can only unmarshal section to a pointer")
}
to = to.Elem()
if to.Type() != reflect.TypeOf([]Group{}) {
return fmt.Errorf("GroupListParser can only unmarshal section to *[]Group")
}
for _, item := range section.Items {
switch itemVal := item.Value.(type) {
case *config_parser.Section:
group := Group{
Name: itemVal.Name,
Param: GroupParam{},
switch to.Kind() {
case reflect.Slice:
elemType := to.Type().Elem()
switch elemType.Kind() {
case reflect.String:
return StringListParser(to.Addr(), section)
case reflect.Struct:
// "to" is a section list (sections in section).
/**
to {
field1 {
...
}
field2 {
...
}
}
should be parsed to:
to []struct {
Name string `mapstructure: "_"`
...
}
*/
// The struct should contain Name.
nameStructField, ok := elemType.FieldByName("Name")
if !ok || nameStructField.Type.Kind() != reflect.String || nameStructField.Tag.Get("mapstructure") != "_" {
return fmt.Errorf("a string field \"Name\" with mapstructure:\"_\" is required in struct %v to parse section", to.Type().Elem().String())
}
paramVal := reflect.ValueOf(&group.Param)
if err := paramParser(paramVal, itemVal, nil); err != nil {
return fmt.Errorf("failed to parse \"%v\": %w", itemVal.Name, err)
// Scan sections.
for _, item := range section.Items {
elem := reflect.New(elemType).Elem()
switch itemVal := item.Value.(type) {
case *config_parser.Section:
elem.FieldByName("Name").SetString(itemVal.Name)
if err := SectionParser(elem.Addr(), itemVal); err != nil {
return fmt.Errorf("error when parse \"%v\": %w", itemVal.Name, err)
}
to.Set(reflect.Append(to, elem))
default:
return fmt.Errorf("unmatched type: %v -> %v", item.Type.String(), elemType)
}
}
to.Set(reflect.Append(to, reflect.ValueOf(group)))
return nil
default:
return fmt.Errorf("section %v does not support type %v: %v", section.Name, item.Type.String(), item.String())
goto unsupported
}
case reflect.Struct:
// Section.
return ParamParser(to.Addr(), section, nil)
default:
goto unsupported
}
return nil
}
func RoutingRuleAndParamParser(to reflect.Value, section *config_parser.Section) error {
if to.Kind() != reflect.Pointer {
return fmt.Errorf("RoutingRuleAndParamParser can only unmarshal section to *struct")
}
to = to.Elem()
if to.Kind() != reflect.Struct {
return fmt.Errorf("RoutingRuleAndParamParser can only unmarshal section to *struct")
}
// Find the first []*RoutingRule field to unmarshal.
targetType := reflect.TypeOf([]*config_parser.RoutingRule{})
var ruleTo *reflect.Value
for i := 0; i < to.NumField(); i++ {
field := to.Field(i)
if field.Type() == targetType {
ruleTo = &field
break
}
}
if ruleTo == nil {
return fmt.Errorf(`no %v field found`, targetType.String())
}
// Parse and unmarshal list of RoutingRule to ruleTo.
for _, item := range section.Items {
switch itemVal := item.Value.(type) {
case *config_parser.RoutingRule:
ruleTo.Set(reflect.Append(*ruleTo, reflect.ValueOf(itemVal)))
case *config_parser.Param:
// pass
default:
return fmt.Errorf("section %v does not support type %v: %v", section.Name, item.Type.String(), item.String())
}
}
// Parse Param.
return paramParser(to.Addr(), section,
[]reflect.Type{reflect.TypeOf(&config_parser.RoutingRule{})},
)
panic("code should not reach here")
unsupported:
return fmt.Errorf("unsupported section type %v", to.Type())
}