| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119 |
- // Package dnsconf validates the JSON-subscription DNS setting against xray's own
- // schema, so a block the client could not load never reaches an emitted document.
- package dnsconf
- import (
- "encoding/json"
- "errors"
- "fmt"
- "strings"
- "github.com/xtls/xray-core/infra/conf"
- )
- // Parse resolves the setting into the dns subtree to emit: a full dns object, or
- // a bare array of servers wrapped into one. A blank value means "no override".
- func Parse(raw string) (map[string]any, error) {
- trimmed := strings.TrimSpace(raw)
- if trimmed == "" {
- return nil, nil
- }
- var decoded any
- if err := json.Unmarshal([]byte(trimmed), &decoded); err != nil {
- return nil, fmt.Errorf("invalid DNS JSON: %w", err)
- }
- block := make(map[string]any)
- servers, isList := decoded.([]any)
- if isList {
- block["servers"] = servers
- } else {
- object, isObject := decoded.(map[string]any)
- if !isObject {
- return nil, errors.New("DNS config must be a JSON object or an array of servers")
- }
- block = object
- }
- if err := validate(block); err != nil {
- return nil, err
- }
- return block, nil
- }
- // validate decodes the block into xray's own schema. Build() is deliberately not
- // run: it resolves geosite tokens from geodata files the panel may not have.
- func validate(block map[string]any) (err error) {
- // Third-party parser fed by a panel setting: a panic must degrade to
- // "unusable value", never take the panel or sub server down.
- defer func() {
- if panicValue := recover(); panicValue != nil {
- err = fmt.Errorf("invalid DNS config: %v", panicValue)
- }
- }()
- payload, err := json.Marshal(block)
- if err != nil {
- return fmt.Errorf("invalid DNS config: %w", err)
- }
- var parsed conf.DNSConfig
- if err := json.Unmarshal(payload, &parsed); err != nil {
- return fmt.Errorf("invalid DNS config: %w", err)
- }
- servers, _ := block["servers"].([]any)
- if len(servers) == 0 {
- // xray quietly installs the system resolver when no server is
- // configured, which would leak lookups outside the tunnel.
- return errors.New(`"servers" must list at least one DNS server`)
- }
- for index, entry := range servers {
- if err := validateServer(index, entry); err != nil {
- return err
- }
- }
- if err := validateClientIP("dns", parsed.ClientIP); err != nil {
- return err
- }
- for index, server := range parsed.Servers {
- if err := validateClientIP(fmt.Sprintf("DNS server #%d", index+1), server.ClientIP); err != nil {
- return err
- }
- }
- return nil
- }
- // validateServer enforces the address rule Build() would: xray refuses a name
- // server without one, but only reports it when the client starts.
- func validateServer(index int, entry any) error {
- label := fmt.Sprintf("DNS server #%d", index+1)
- switch server := entry.(type) {
- case string:
- if strings.TrimSpace(server) == "" {
- return fmt.Errorf("%s is empty", label)
- }
- case map[string]any:
- address, ok := server["address"]
- if !ok {
- return fmt.Errorf(`%s needs a non-empty "address"`, label)
- }
- text, ok := address.(string)
- if ok && strings.TrimSpace(text) == "" {
- return fmt.Errorf(`%s needs a non-empty "address"`, label)
- }
- if _, isObject := address.(map[string]any); !ok && !isObject {
- return fmt.Errorf(`%s needs a non-empty "address"`, label)
- }
- default:
- return fmt.Errorf("%s must be a string or an object", label)
- }
- return nil
- }
- func validateClientIP(label string, clientIP *conf.Address) error {
- if clientIP != nil && !clientIP.Family().IsIP() {
- return fmt.Errorf("%s clientIp must be an IP address", label)
- }
- return nil
- }
|