github.com/xmplusdev/xmcore@v1.8.11-0.20240412132628-5518b55526af/infra/conf/dns.go (about) 1 package conf 2 3 import ( 4 "encoding/json" 5 "sort" 6 "strings" 7 8 "github.com/xmplusdev/xmcore/app/dns" 9 "github.com/xmplusdev/xmcore/app/router" 10 "github.com/xmplusdev/xmcore/common/net" 11 ) 12 13 type NameServerConfig struct { 14 Address *Address 15 ClientIP *Address 16 Port uint16 17 SkipFallback bool 18 Domains []string 19 ExpectIPs StringList 20 QueryStrategy string 21 } 22 23 func (c *NameServerConfig) UnmarshalJSON(data []byte) error { 24 var address Address 25 if err := json.Unmarshal(data, &address); err == nil { 26 c.Address = &address 27 return nil 28 } 29 30 var advanced struct { 31 Address *Address `json:"address"` 32 ClientIP *Address `json:"clientIp"` 33 Port uint16 `json:"port"` 34 SkipFallback bool `json:"skipFallback"` 35 Domains []string `json:"domains"` 36 ExpectIPs StringList `json:"expectIps"` 37 QueryStrategy string `json:"queryStrategy"` 38 } 39 if err := json.Unmarshal(data, &advanced); err == nil { 40 c.Address = advanced.Address 41 c.ClientIP = advanced.ClientIP 42 c.Port = advanced.Port 43 c.SkipFallback = advanced.SkipFallback 44 c.Domains = advanced.Domains 45 c.ExpectIPs = advanced.ExpectIPs 46 c.QueryStrategy = advanced.QueryStrategy 47 return nil 48 } 49 50 return newError("failed to parse name server: ", string(data)) 51 } 52 53 func toDomainMatchingType(t router.Domain_Type) dns.DomainMatchingType { 54 switch t { 55 case router.Domain_Domain: 56 return dns.DomainMatchingType_Subdomain 57 case router.Domain_Full: 58 return dns.DomainMatchingType_Full 59 case router.Domain_Plain: 60 return dns.DomainMatchingType_Keyword 61 case router.Domain_Regex: 62 return dns.DomainMatchingType_Regex 63 default: 64 panic("unknown domain type") 65 } 66 } 67 68 func (c *NameServerConfig) Build() (*dns.NameServer, error) { 69 if c.Address == nil { 70 return nil, newError("NameServer address is not specified.") 71 } 72 73 var domains []*dns.NameServer_PriorityDomain 74 var originalRules []*dns.NameServer_OriginalRule 75 76 for _, rule := range c.Domains { 77 parsedDomain, err := parseDomainRule(rule) 78 if err != nil { 79 return nil, newError("invalid domain rule: ", rule).Base(err) 80 } 81 82 for _, pd := range parsedDomain { 83 domains = append(domains, &dns.NameServer_PriorityDomain{ 84 Type: toDomainMatchingType(pd.Type), 85 Domain: pd.Value, 86 }) 87 } 88 originalRules = append(originalRules, &dns.NameServer_OriginalRule{ 89 Rule: rule, 90 Size: uint32(len(parsedDomain)), 91 }) 92 } 93 94 geoipList, err := ToCidrList(c.ExpectIPs) 95 if err != nil { 96 return nil, newError("invalid IP rule: ", c.ExpectIPs).Base(err) 97 } 98 99 var myClientIP []byte 100 if c.ClientIP != nil { 101 if !c.ClientIP.Family().IsIP() { 102 return nil, newError("not an IP address:", c.ClientIP.String()) 103 } 104 myClientIP = []byte(c.ClientIP.IP()) 105 } 106 107 return &dns.NameServer{ 108 Address: &net.Endpoint{ 109 Network: net.Network_UDP, 110 Address: c.Address.Build(), 111 Port: uint32(c.Port), 112 }, 113 ClientIp: myClientIP, 114 SkipFallback: c.SkipFallback, 115 PrioritizedDomain: domains, 116 Geoip: geoipList, 117 OriginalRules: originalRules, 118 QueryStrategy: resolveQueryStrategy(c.QueryStrategy), 119 }, nil 120 } 121 122 var typeMap = map[router.Domain_Type]dns.DomainMatchingType{ 123 router.Domain_Full: dns.DomainMatchingType_Full, 124 router.Domain_Domain: dns.DomainMatchingType_Subdomain, 125 router.Domain_Plain: dns.DomainMatchingType_Keyword, 126 router.Domain_Regex: dns.DomainMatchingType_Regex, 127 } 128 129 // DNSConfig is a JSON serializable object for dns.Config. 130 type DNSConfig struct { 131 Servers []*NameServerConfig `json:"servers"` 132 Hosts *HostsWrapper `json:"hosts"` 133 ClientIP *Address `json:"clientIp"` 134 Tag string `json:"tag"` 135 QueryStrategy string `json:"queryStrategy"` 136 DisableCache bool `json:"disableCache"` 137 DisableFallback bool `json:"disableFallback"` 138 DisableFallbackIfMatch bool `json:"disableFallbackIfMatch"` 139 } 140 141 type HostAddress struct { 142 addr *Address 143 addrs []*Address 144 } 145 146 // UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON 147 func (h *HostAddress) UnmarshalJSON(data []byte) error { 148 addr := new(Address) 149 var addrs []*Address 150 switch { 151 case json.Unmarshal(data, &addr) == nil: 152 h.addr = addr 153 case json.Unmarshal(data, &addrs) == nil: 154 h.addrs = addrs 155 default: 156 return newError("invalid address") 157 } 158 return nil 159 } 160 161 type HostsWrapper struct { 162 Hosts map[string]*HostAddress 163 } 164 165 func getHostMapping(ha *HostAddress) *dns.Config_HostMapping { 166 if ha.addr != nil { 167 if ha.addr.Family().IsDomain() { 168 return &dns.Config_HostMapping{ 169 ProxiedDomain: ha.addr.Domain(), 170 } 171 } 172 return &dns.Config_HostMapping{ 173 Ip: [][]byte{ha.addr.IP()}, 174 } 175 } 176 177 ips := make([][]byte, 0, len(ha.addrs)) 178 for _, addr := range ha.addrs { 179 if addr.Family().IsDomain() { 180 return &dns.Config_HostMapping{ 181 ProxiedDomain: addr.Domain(), 182 } 183 } 184 ips = append(ips, []byte(addr.IP())) 185 } 186 return &dns.Config_HostMapping{ 187 Ip: ips, 188 } 189 } 190 191 // UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON 192 func (m *HostsWrapper) UnmarshalJSON(data []byte) error { 193 hosts := make(map[string]*HostAddress) 194 err := json.Unmarshal(data, &hosts) 195 if err == nil { 196 m.Hosts = hosts 197 return nil 198 } 199 return newError("invalid DNS hosts").Base(err) 200 } 201 202 // Build implements Buildable 203 func (m *HostsWrapper) Build() ([]*dns.Config_HostMapping, error) { 204 mappings := make([]*dns.Config_HostMapping, 0, 20) 205 206 domains := make([]string, 0, len(m.Hosts)) 207 for domain := range m.Hosts { 208 domains = append(domains, domain) 209 } 210 sort.Strings(domains) 211 212 for _, domain := range domains { 213 switch { 214 case strings.HasPrefix(domain, "domain:"): 215 domainName := domain[7:] 216 if len(domainName) == 0 { 217 return nil, newError("empty domain type of rule: ", domain) 218 } 219 mapping := getHostMapping(m.Hosts[domain]) 220 mapping.Type = dns.DomainMatchingType_Subdomain 221 mapping.Domain = domainName 222 mappings = append(mappings, mapping) 223 224 case strings.HasPrefix(domain, "geosite:"): 225 listName := domain[8:] 226 if len(listName) == 0 { 227 return nil, newError("empty geosite rule: ", domain) 228 } 229 geositeList, err := loadGeositeWithAttr("geosite.dat", listName) 230 if err != nil { 231 return nil, newError("failed to load geosite: ", listName).Base(err) 232 } 233 for _, d := range geositeList { 234 mapping := getHostMapping(m.Hosts[domain]) 235 mapping.Type = typeMap[d.Type] 236 mapping.Domain = d.Value 237 mappings = append(mappings, mapping) 238 } 239 240 case strings.HasPrefix(domain, "regexp:"): 241 regexpVal := domain[7:] 242 if len(regexpVal) == 0 { 243 return nil, newError("empty regexp type of rule: ", domain) 244 } 245 mapping := getHostMapping(m.Hosts[domain]) 246 mapping.Type = dns.DomainMatchingType_Regex 247 mapping.Domain = regexpVal 248 mappings = append(mappings, mapping) 249 250 case strings.HasPrefix(domain, "keyword:"): 251 keywordVal := domain[8:] 252 if len(keywordVal) == 0 { 253 return nil, newError("empty keyword type of rule: ", domain) 254 } 255 mapping := getHostMapping(m.Hosts[domain]) 256 mapping.Type = dns.DomainMatchingType_Keyword 257 mapping.Domain = keywordVal 258 mappings = append(mappings, mapping) 259 260 case strings.HasPrefix(domain, "full:"): 261 fullVal := domain[5:] 262 if len(fullVal) == 0 { 263 return nil, newError("empty full domain type of rule: ", domain) 264 } 265 mapping := getHostMapping(m.Hosts[domain]) 266 mapping.Type = dns.DomainMatchingType_Full 267 mapping.Domain = fullVal 268 mappings = append(mappings, mapping) 269 270 case strings.HasPrefix(domain, "dotless:"): 271 mapping := getHostMapping(m.Hosts[domain]) 272 mapping.Type = dns.DomainMatchingType_Regex 273 switch substr := domain[8:]; { 274 case substr == "": 275 mapping.Domain = "^[^.]*$" 276 case !strings.Contains(substr, "."): 277 mapping.Domain = "^[^.]*" + substr + "[^.]*$" 278 default: 279 return nil, newError("substr in dotless rule should not contain a dot: ", substr) 280 } 281 mappings = append(mappings, mapping) 282 283 case strings.HasPrefix(domain, "ext:"): 284 kv := strings.Split(domain[4:], ":") 285 if len(kv) != 2 { 286 return nil, newError("invalid external resource: ", domain) 287 } 288 filename := kv[0] 289 list := kv[1] 290 geositeList, err := loadGeositeWithAttr(filename, list) 291 if err != nil { 292 return nil, newError("failed to load domain list: ", list, " from ", filename).Base(err) 293 } 294 for _, d := range geositeList { 295 mapping := getHostMapping(m.Hosts[domain]) 296 mapping.Type = typeMap[d.Type] 297 mapping.Domain = d.Value 298 mappings = append(mappings, mapping) 299 } 300 301 default: 302 mapping := getHostMapping(m.Hosts[domain]) 303 mapping.Type = dns.DomainMatchingType_Full 304 mapping.Domain = domain 305 mappings = append(mappings, mapping) 306 } 307 } 308 return mappings, nil 309 } 310 311 // Build implements Buildable 312 func (c *DNSConfig) Build() (*dns.Config, error) { 313 config := &dns.Config{ 314 Tag: c.Tag, 315 DisableCache: c.DisableCache, 316 DisableFallback: c.DisableFallback, 317 DisableFallbackIfMatch: c.DisableFallbackIfMatch, 318 QueryStrategy: resolveQueryStrategy(c.QueryStrategy), 319 } 320 321 if c.ClientIP != nil { 322 if !c.ClientIP.Family().IsIP() { 323 return nil, newError("not an IP address:", c.ClientIP.String()) 324 } 325 config.ClientIp = []byte(c.ClientIP.IP()) 326 } 327 328 for _, server := range c.Servers { 329 ns, err := server.Build() 330 if err != nil { 331 return nil, newError("failed to build nameserver").Base(err) 332 } 333 config.NameServer = append(config.NameServer, ns) 334 } 335 336 if c.Hosts != nil { 337 staticHosts, err := c.Hosts.Build() 338 if err != nil { 339 return nil, newError("failed to build hosts").Base(err) 340 } 341 config.StaticHosts = append(config.StaticHosts, staticHosts...) 342 } 343 344 return config, nil 345 } 346 347 func resolveQueryStrategy(queryStrategy string) dns.QueryStrategy { 348 switch strings.ToLower(queryStrategy) { 349 case "useip", "use_ip", "use-ip": 350 return dns.QueryStrategy_USE_IP 351 case "useip4", "useipv4", "use_ip4", "use_ipv4", "use_ip_v4", "use-ip4", "use-ipv4", "use-ip-v4": 352 return dns.QueryStrategy_USE_IP4 353 case "useip6", "useipv6", "use_ip6", "use_ipv6", "use_ip_v6", "use-ip6", "use-ipv6", "use-ip-v6": 354 return dns.QueryStrategy_USE_IP6 355 default: 356 return dns.QueryStrategy_USE_IP 357 } 358 }