mirror of
https://github.com/daeuniverse/dae.git
synced 2025-07-09 07:19:17 +07:00
feat: dns routing (#26)
This commit is contained in:
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
Reference in New Issue
Block a user