Validate DoH/DoH3 endpoint properly

When resolver type is doh/doh3, the endpoint must be a valid http url.

Updates #149
This commit is contained in:
Cuong Manh Le
2023-08-09 10:01:00 +07:00
committed by Cuong Manh Le
parent f7d3db06c6
commit 7ce62ccaec
3 changed files with 38 additions and 1 deletions
+2
View File
@@ -1383,6 +1383,8 @@ func fieldErrorMsg(fe validator.FieldError) string {
return fmt.Sprintf("invalid IP format: %s", fe.Value()) return fmt.Sprintf("invalid IP format: %s", fe.Value())
case "file": case "file":
return fmt.Sprintf("filed does not exist: %s", fe.Value()) return fmt.Sprintf("filed does not exist: %s", fe.Value())
case "http_url":
return fmt.Sprintf("invalid http/https url: %s", fe.Value())
} }
return "" return ""
} }
+28 -1
View File
@@ -193,7 +193,7 @@ type NetworkConfig struct {
type UpstreamConfig struct { type UpstreamConfig struct {
Name string `mapstructure:"name" toml:"name,omitempty"` Name string `mapstructure:"name" toml:"name,omitempty"`
Type string `mapstructure:"type" toml:"type,omitempty" validate:"oneof=doh doh3 dot doq os legacy"` Type string `mapstructure:"type" toml:"type,omitempty" validate:"oneof=doh doh3 dot doq os legacy"`
Endpoint string `mapstructure:"endpoint" toml:"endpoint,omitempty" validate:"required_unless=Type os"` Endpoint string `mapstructure:"endpoint" toml:"endpoint,omitempty"`
BootstrapIP string `mapstructure:"bootstrap_ip" toml:"bootstrap_ip,omitempty"` BootstrapIP string `mapstructure:"bootstrap_ip" toml:"bootstrap_ip,omitempty"`
Domain string `mapstructure:"-" toml:"-"` Domain string `mapstructure:"-" toml:"-"`
IPStack string `mapstructure:"ip_stack" toml:"ip_stack,omitempty" validate:"ipstack"` IPStack string `mapstructure:"ip_stack" toml:"ip_stack,omitempty" validate:"ipstack"`
@@ -589,6 +589,7 @@ func ValidateConfig(validate *validator.Validate, cfg *Config) error {
_ = validate.RegisterValidation("dnsrcode", validateDnsRcode) _ = validate.RegisterValidation("dnsrcode", validateDnsRcode)
_ = validate.RegisterValidation("ipstack", validateIpStack) _ = validate.RegisterValidation("ipstack", validateIpStack)
_ = validate.RegisterValidation("iporempty", validateIpOrEmpty) _ = validate.RegisterValidation("iporempty", validateIpOrEmpty)
validate.RegisterStructValidation(upstreamConfigStructLevelValidation, UpstreamConfig{})
return validate.Struct(cfg) return validate.Struct(cfg)
} }
@@ -613,6 +614,32 @@ func validateIpOrEmpty(fl validator.FieldLevel) bool {
return net.ParseIP(val) != nil return net.ParseIP(val) != nil
} }
func upstreamConfigStructLevelValidation(sl validator.StructLevel) {
uc := sl.Current().Addr().Interface().(*UpstreamConfig)
if uc.Type == ResolverTypeOS {
return
}
// Endpoint is required for non os resolver.
if uc.Endpoint == "" {
sl.ReportError(uc.Endpoint, "endpoint", "Endpoint", "required_unless", "")
return
}
// DoH/DoH3 requires endpoint is an HTTP url.
if uc.Type == ResolverTypeDOH || uc.Type == ResolverTypeDOH3 {
u, err := url.Parse(uc.Endpoint)
if err != nil || u.Host == "" {
sl.ReportError(uc.Endpoint, "endpoint", "Endpoint", "http_url", "")
return
}
if u.Scheme != "http" && u.Scheme != "https" {
sl.ReportError(uc.Endpoint, "endpoint", "Endpoint", "http_url", "")
return
}
}
}
func defaultPortFor(typ string) string { func defaultPortFor(typ string) string {
switch typ { switch typ {
case ResolverTypeDOH, ResolverTypeDOH3: case ResolverTypeDOH, ResolverTypeDOH3:
+8
View File
@@ -95,6 +95,7 @@ func TestConfigValidation(t *testing.T) {
{"non-existed lease file", configWithNonExistedLeaseFile(t), true}, {"non-existed lease file", configWithNonExistedLeaseFile(t), true},
{"lease file format required if lease file exist", configWithExistedLeaseFile(t), true}, {"lease file format required if lease file exist", configWithExistedLeaseFile(t), true},
{"invalid lease file format", configWithInvalidLeaseFileFormat(t), true}, {"invalid lease file format", configWithInvalidLeaseFileFormat(t), true},
{"invalid doh/doh3 endpoint", configWithInvalidDoHEndpoint(t), true},
} }
for _, tc := range tests { for _, tc := range tests {
@@ -225,3 +226,10 @@ func configWithInvalidLeaseFileFormat(t *testing.T) *ctrld.Config {
cfg.Service.DHCPLeaseFileFormat = "invalid" cfg.Service.DHCPLeaseFileFormat = "invalid"
return cfg return cfg
} }
func configWithInvalidDoHEndpoint(t *testing.T) *ctrld.Config {
cfg := defaultConfig(t)
cfg.Upstream["0"].Endpoint = "1.1.1.1"
cfg.Upstream["0"].Type = ctrld.ResolverTypeDOH
return cfg
}