341 lines
13 KiB
Go
341 lines
13 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
defaultMainListen = "0.0.0.0:80"
|
|
defaultExtraListen = "0.0.0.0:8080"
|
|
defaultDNSTTListen = "0.0.0.0:5300"
|
|
defaultUDPGWListen = "0.0.0.0:7400"
|
|
)
|
|
|
|
func normalizeRuntimePorts(cfg *Config) []string {
|
|
var warnings []string
|
|
warn := func(format string, args ...interface{}) {
|
|
msg := fmt.Sprintf(format, args...)
|
|
warnings = append(warnings, msg)
|
|
log.Printf("config safety: %s", msg)
|
|
}
|
|
|
|
cfg.Listen = strings.TrimSpace(cfg.Listen)
|
|
if cfg.Listen == "" {
|
|
cfg.Listen = defaultMainListen
|
|
}
|
|
if err := tcpAddrAvailableForPool(cfg.Listen, publicPool); err != nil {
|
|
old := cfg.Listen
|
|
cfg.Listen = defaultMainListen
|
|
warn("main listener %s is unavailable (%v); using default %s", old, err, cfg.Listen)
|
|
if err2 := tcpAddrAvailableForPool(cfg.Listen, publicPool); err2 != nil {
|
|
warn("default main listener %s is also unavailable: %v", cfg.Listen, err2)
|
|
}
|
|
}
|
|
|
|
seen := map[string]bool{cfg.Listen: true}
|
|
extra := make([]string, 0, len(cfg.ExtraListen))
|
|
for _, addr := range cfg.ExtraListen {
|
|
addr = strings.TrimSpace(addr)
|
|
if addr == "" || seen[addr] {
|
|
continue
|
|
}
|
|
if err := tcpAddrAvailableForPool(addr, publicPool); err != nil {
|
|
warn("extra listener %s is unavailable (%v)", addr, err)
|
|
fallback := defaultExtraListen
|
|
if !seen[fallback] {
|
|
if err2 := tcpAddrAvailableForPool(fallback, publicPool); err2 == nil {
|
|
extra = append(extra, fallback)
|
|
seen[fallback] = true
|
|
warn("extra listener fell back to default %s", fallback)
|
|
} else {
|
|
warn("default extra listener %s is also unavailable: %v", fallback, err2)
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
extra = append(extra, addr)
|
|
seen[addr] = true
|
|
}
|
|
cfg.ExtraListen = extra
|
|
|
|
// DragonCore no longer uses an internal local SSH listener.
|
|
cfg.LocalSSHListen = ""
|
|
|
|
cfg.ProxyAutoRestartInterval = strings.TrimSpace(cfg.ProxyAutoRestartInterval)
|
|
if cfg.ProxyAutoRestartInterval != "" && cfg.ProxyAutoRestartInterval != "0" && cfg.ProxyAutoRestartInterval != "0s" && !strings.EqualFold(cfg.ProxyAutoRestartInterval, "off") && !strings.EqualFold(cfg.ProxyAutoRestartInterval, "disabled") {
|
|
if d, err := time.ParseDuration(cfg.ProxyAutoRestartInterval); err != nil {
|
|
warn("proxy auto restart interval %q is invalid; disabling auto restart", cfg.ProxyAutoRestartInterval)
|
|
cfg.ProxyAutoRestartInterval = ""
|
|
} else if d < time.Minute {
|
|
warn("proxy auto restart interval %q is below 1m; disabling auto restart", cfg.ProxyAutoRestartInterval)
|
|
cfg.ProxyAutoRestartInterval = ""
|
|
}
|
|
}
|
|
cfg.ProxyAutoRestartGrace = strings.TrimSpace(cfg.ProxyAutoRestartGrace)
|
|
if cfg.ProxyAutoRestartGrace != "" {
|
|
if d, err := time.ParseDuration(cfg.ProxyAutoRestartGrace); err != nil || d < 0 {
|
|
warn("proxy auto restart grace %q is invalid; using default 2s", cfg.ProxyAutoRestartGrace)
|
|
cfg.ProxyAutoRestartGrace = ""
|
|
} else if d > time.Minute {
|
|
warn("proxy auto restart grace %q is above 1m; clamping to 1m", cfg.ProxyAutoRestartGrace)
|
|
cfg.ProxyAutoRestartGrace = "1m"
|
|
}
|
|
}
|
|
|
|
if cfg.DNSTT != nil {
|
|
cfg.DNSTT.FakeDNSDomain = strings.TrimSpace(cfg.DNSTT.FakeDNSDomain)
|
|
if cfg.DNSTT.FakeDNSEnabled {
|
|
if cfg.DNSTT.FakeDNSDomain == "" {
|
|
cfg.DNSTT.FakeDNSDomain = "t.local.lan"
|
|
}
|
|
// Automatically add the local/fake test zone to the accepted DNSTT
|
|
// domains so the tunnel handler can decode traffic for it.
|
|
cfg.DNSTT.Domains = append(cfg.DNSTT.Domains, cfg.DNSTT.FakeDNSDomain)
|
|
}
|
|
cfg.DNSTT.Domains = normalizeDNSTTDomainList(cfg.DNSTT.Domain, cfg.DNSTT.Domains)
|
|
if len(cfg.DNSTT.Domains) > 0 {
|
|
cfg.DNSTT.Domain = cfg.DNSTT.Domains[0]
|
|
} else {
|
|
cfg.DNSTT.Domain = strings.TrimSpace(cfg.DNSTT.Domain)
|
|
}
|
|
if cfg.DNSTT.FakeDNSEnabled {
|
|
localDomains := normalizeDNSTTDomainList(cfg.DNSTT.FakeDNSDomain, nil)
|
|
if len(localDomains) > 0 {
|
|
cfg.DNSTT.FakeDNSDomain = localDomains[0]
|
|
}
|
|
}
|
|
|
|
var migratedLegacyDNSTTWildcard bool
|
|
cfg.DNSTT.UDPListen, migratedLegacyDNSTTWildcard = normalizeDNSTTListenDefault(cfg.DNSTT.UDPListen)
|
|
if migratedLegacyDNSTTWildcard {
|
|
warn("DNSTT legacy default [::]:5300 is IPv6-only; using IPv4 default %s", cfg.DNSTT.UDPListen)
|
|
}
|
|
if err := udpAddrAvailableForDNSTT(cfg.DNSTT.UDPListen); err != nil {
|
|
old := cfg.DNSTT.UDPListen
|
|
cfg.DNSTT.UDPListen = defaultDNSTTListen
|
|
warn("DNSTT UDP listener %s is unavailable (%v); using default %s", old, err, cfg.DNSTT.UDPListen)
|
|
if err2 := udpAddrAvailableForDNSTT(cfg.DNSTT.UDPListen); err2 != nil {
|
|
warn("default DNSTT UDP listener %s is also unavailable: %v", cfg.DNSTT.UDPListen, err2)
|
|
}
|
|
}
|
|
|
|
if cfg.DNSTT.FakeDNSEnabled {
|
|
cfg.DNSTT.FakeDNSListen = strings.TrimSpace(cfg.DNSTT.FakeDNSListen)
|
|
if cfg.DNSTT.FakeDNSListen == "" {
|
|
cfg.DNSTT.FakeDNSListen = "[::]:53"
|
|
}
|
|
if !sameUDPListenAddress(cfg.DNSTT.FakeDNSListen, cfg.DNSTT.UDPListen) {
|
|
if err := udpAddrAvailableForDNSTT(cfg.DNSTT.FakeDNSListen); err != nil {
|
|
warn("built-in DNSTT local DNS listener %s is unavailable: %v", cfg.DNSTT.FakeDNSListen, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
cfg.DNSTT.AutoRestartInterval = strings.TrimSpace(cfg.DNSTT.AutoRestartInterval)
|
|
if cfg.DNSTT.AutoRestartInterval != "" && cfg.DNSTT.AutoRestartInterval != "0" && cfg.DNSTT.AutoRestartInterval != "0s" && !strings.EqualFold(cfg.DNSTT.AutoRestartInterval, "off") && !strings.EqualFold(cfg.DNSTT.AutoRestartInterval, "disabled") {
|
|
if d, err := time.ParseDuration(cfg.DNSTT.AutoRestartInterval); err != nil {
|
|
warn("DNSTT auto restart interval %q is invalid; disabling auto restart", cfg.DNSTT.AutoRestartInterval)
|
|
cfg.DNSTT.AutoRestartInterval = ""
|
|
} else if d < time.Minute {
|
|
warn("DNSTT auto restart interval %q is below 1m; disabling auto restart", cfg.DNSTT.AutoRestartInterval)
|
|
cfg.DNSTT.AutoRestartInterval = ""
|
|
}
|
|
}
|
|
cfg.DNSTT.AutoRestartGrace = strings.TrimSpace(cfg.DNSTT.AutoRestartGrace)
|
|
if cfg.DNSTT.AutoRestartGrace != "" {
|
|
if d, err := time.ParseDuration(cfg.DNSTT.AutoRestartGrace); err != nil || d < 0 {
|
|
warn("DNSTT auto restart grace %q is invalid; using default 2s", cfg.DNSTT.AutoRestartGrace)
|
|
cfg.DNSTT.AutoRestartGrace = ""
|
|
} else if d > time.Minute {
|
|
warn("DNSTT auto restart grace %q is above 1m; clamping to 1m", cfg.DNSTT.AutoRestartGrace)
|
|
cfg.DNSTT.AutoRestartGrace = "1m"
|
|
}
|
|
}
|
|
|
|
if cfg.DNSTT.MaxSessions < -1 {
|
|
warn("DNSTT max_sessions %d is invalid; using unlimited (-1)", cfg.DNSTT.MaxSessions)
|
|
cfg.DNSTT.MaxSessions = -1
|
|
}
|
|
if cfg.DNSTT.MaxStreams < -1 {
|
|
warn("DNSTT max_streams %d is invalid; using unlimited (-1)", cfg.DNSTT.MaxStreams)
|
|
cfg.DNSTT.MaxStreams = -1
|
|
}
|
|
if cfg.DNSTT.PendingResponses > 0 {
|
|
if cfg.DNSTT.PendingResponses < minDNSTTPendingResponses {
|
|
warn("DNSTT pending_responses %d is too low; clamping to %d", cfg.DNSTT.PendingResponses, minDNSTTPendingResponses)
|
|
cfg.DNSTT.PendingResponses = minDNSTTPendingResponses
|
|
} else if cfg.DNSTT.PendingResponses > maxDNSTTPendingResponses {
|
|
warn("DNSTT pending_responses %d is too high; clamping to %d", cfg.DNSTT.PendingResponses, maxDNSTTPendingResponses)
|
|
cfg.DNSTT.PendingResponses = maxDNSTTPendingResponses
|
|
}
|
|
}
|
|
if cfg.DNSTT.StreamBuffer > 0 {
|
|
if cfg.DNSTT.StreamBuffer < minDNSTTStreamBuffer {
|
|
warn("DNSTT stream_buffer %d is too low; clamping to %d", cfg.DNSTT.StreamBuffer, minDNSTTStreamBuffer)
|
|
cfg.DNSTT.StreamBuffer = minDNSTTStreamBuffer
|
|
} else if cfg.DNSTT.StreamBuffer > maxDNSTTStreamBuffer {
|
|
warn("DNSTT stream_buffer %d is too high; clamping to %d", cfg.DNSTT.StreamBuffer, maxDNSTTStreamBuffer)
|
|
cfg.DNSTT.StreamBuffer = maxDNSTTStreamBuffer
|
|
}
|
|
}
|
|
if cfg.DNSTT.UDPReadBuffer < 0 {
|
|
warn("DNSTT udp_read_buffer %d is invalid; using default", cfg.DNSTT.UDPReadBuffer)
|
|
cfg.DNSTT.UDPReadBuffer = 0
|
|
}
|
|
if cfg.DNSTT.UDPWriteBuffer < 0 {
|
|
warn("DNSTT udp_write_buffer %d is invalid; using default", cfg.DNSTT.UDPWriteBuffer)
|
|
cfg.DNSTT.UDPWriteBuffer = 0
|
|
}
|
|
if cfg.DNSTT.FakeDNSWorkers < 0 {
|
|
warn("DNSTT fake_dns_workers %d is invalid; using automatic default", cfg.DNSTT.FakeDNSWorkers)
|
|
cfg.DNSTT.FakeDNSWorkers = 0
|
|
} else if cfg.DNSTT.FakeDNSWorkers > maxDNSTTFakeDNSWorkers {
|
|
warn("DNSTT fake_dns_workers %d is too high; clamping to %d", cfg.DNSTT.FakeDNSWorkers, maxDNSTTFakeDNSWorkers)
|
|
cfg.DNSTT.FakeDNSWorkers = maxDNSTTFakeDNSWorkers
|
|
}
|
|
if cfg.DNSTT.DNSResponseWorkers < 0 {
|
|
warn("DNSTT dns_response_workers %d is invalid; using default", cfg.DNSTT.DNSResponseWorkers)
|
|
cfg.DNSTT.DNSResponseWorkers = 0
|
|
} else if cfg.DNSTT.DNSResponseWorkers > maxDNSTTResponseWorkers {
|
|
warn("DNSTT dns_response_workers %d is too high; clamping to %d", cfg.DNSTT.DNSResponseWorkers, maxDNSTTResponseWorkers)
|
|
cfg.DNSTT.DNSResponseWorkers = maxDNSTTResponseWorkers
|
|
}
|
|
}
|
|
|
|
if cfg.UDPGW != nil {
|
|
cfg.UDPGW.Listen = strings.TrimSpace(cfg.UDPGW.Listen)
|
|
if cfg.UDPGW.Listen == "" {
|
|
cfg.UDPGW.Listen = defaultUDPGWListen
|
|
}
|
|
if err := tcpAddrAvailableForUDPGW(cfg.UDPGW.Listen); err != nil {
|
|
old := cfg.UDPGW.Listen
|
|
cfg.UDPGW.Listen = defaultUDPGWListen
|
|
warn("UDPGW listener %s is unavailable (%v); using default %s", old, err, cfg.UDPGW.Listen)
|
|
if err2 := tcpAddrAvailableForUDPGW(cfg.UDPGW.Listen); err2 != nil {
|
|
warn("default UDPGW listener %s is also unavailable: %v", cfg.UDPGW.Listen, err2)
|
|
}
|
|
}
|
|
|
|
cfg.UDPGW.AutoRestartInterval = strings.TrimSpace(cfg.UDPGW.AutoRestartInterval)
|
|
if cfg.UDPGW.AutoRestartInterval != "" && cfg.UDPGW.AutoRestartInterval != "0" && cfg.UDPGW.AutoRestartInterval != "0s" && !strings.EqualFold(cfg.UDPGW.AutoRestartInterval, "off") && !strings.EqualFold(cfg.UDPGW.AutoRestartInterval, "disabled") {
|
|
if d, err := time.ParseDuration(cfg.UDPGW.AutoRestartInterval); err != nil {
|
|
warn("UDPGW auto restart interval %q is invalid; disabling auto restart", cfg.UDPGW.AutoRestartInterval)
|
|
cfg.UDPGW.AutoRestartInterval = ""
|
|
} else if d < time.Minute {
|
|
warn("UDPGW auto restart interval %q is below 1m; disabling auto restart", cfg.UDPGW.AutoRestartInterval)
|
|
cfg.UDPGW.AutoRestartInterval = ""
|
|
}
|
|
}
|
|
cfg.UDPGW.AutoRestartGrace = strings.TrimSpace(cfg.UDPGW.AutoRestartGrace)
|
|
if cfg.UDPGW.AutoRestartGrace != "" {
|
|
if d, err := time.ParseDuration(cfg.UDPGW.AutoRestartGrace); err != nil || d < 0 {
|
|
warn("UDPGW auto restart grace %q is invalid; using default 2s", cfg.UDPGW.AutoRestartGrace)
|
|
cfg.UDPGW.AutoRestartGrace = ""
|
|
} else if d > time.Minute {
|
|
warn("UDPGW auto restart grace %q is above 1m; clamping to 1m", cfg.UDPGW.AutoRestartGrace)
|
|
cfg.UDPGW.AutoRestartGrace = "1m"
|
|
}
|
|
}
|
|
}
|
|
|
|
return warnings
|
|
}
|
|
|
|
func tcpAddrAvailableForPool(addr string, pool *listenerPool) error {
|
|
if addr == "" {
|
|
return nil
|
|
}
|
|
if pool != nil && pool.Has(addr) {
|
|
return nil
|
|
}
|
|
ln, err := net.Listen("tcp", addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ln.Close()
|
|
}
|
|
|
|
func tcpAddrAvailableForUDPGW(addr string) error {
|
|
if addr == "" {
|
|
return nil
|
|
}
|
|
globalCfgMu.RLock()
|
|
current := globalCfg != nil && globalCfg.UDPGW != nil && globalCfg.UDPGW.Listen == addr && udpgwRunning()
|
|
globalCfgMu.RUnlock()
|
|
if current {
|
|
return nil
|
|
}
|
|
ln, err := net.Listen("tcp", addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ln.Close()
|
|
}
|
|
|
|
func normalizeDNSTTDomainList(primary string, domains []string) []string {
|
|
seen := make(map[string]bool, len(domains)+1)
|
|
out := make([]string, 0, len(domains)+1)
|
|
add := func(v string) {
|
|
v = strings.TrimSpace(v)
|
|
v = strings.TrimSuffix(v, ".")
|
|
v = strings.ToLower(v)
|
|
if v == "" || seen[v] {
|
|
return
|
|
}
|
|
seen[v] = true
|
|
out = append(out, v)
|
|
}
|
|
add(primary)
|
|
for _, d := range domains {
|
|
add(d)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// normalizeDNSTTListenDefault keeps explicit IPv4 and concrete IPv6 listeners,
|
|
// but migrates the old wildcard IPv6 default. listenDNSTTPacket deliberately
|
|
// opens IPv6 addresses with udp6, so [::]:5300 never receives IPv4 queries.
|
|
// Existing installations commonly inherited that value from the old default;
|
|
// moving only that wildcard/default-port combination makes them work after an
|
|
// update without changing intentionally selected IPv6 interface addresses.
|
|
func normalizeDNSTTListenDefault(addr string) (string, bool) {
|
|
addr = strings.TrimSpace(addr)
|
|
if addr == "" {
|
|
return defaultDNSTTListen, false
|
|
}
|
|
|
|
host, port, err := net.SplitHostPort(addr)
|
|
if err != nil || port != "5300" {
|
|
return addr, false
|
|
}
|
|
ip := net.ParseIP(strings.Trim(host, "[]"))
|
|
if ip != nil && ip.To4() == nil && ip.IsUnspecified() {
|
|
return defaultDNSTTListen, true
|
|
}
|
|
return addr, false
|
|
}
|
|
|
|
func udpAddrAvailableForDNSTT(addr string) error {
|
|
if addr == "" {
|
|
return nil
|
|
}
|
|
globalCfgMu.RLock()
|
|
current := false
|
|
if globalCfg != nil && globalCfg.DNSTT != nil && dnsttRunning() {
|
|
current = sameUDPListenAddress(globalCfg.DNSTT.UDPListen, addr) || sameUDPListenAddress(globalCfg.DNSTT.FakeDNSListen, addr)
|
|
}
|
|
globalCfgMu.RUnlock()
|
|
if current {
|
|
return nil
|
|
}
|
|
pc, err := listenDNSTTPacket(addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return pc.Close()
|
|
}
|