mirror of
https://github.com/fosrl/olm.git
synced 2026-09-01 19:51:32 +02:00
Add match domains config
This commit is contained in:
@@ -29,6 +29,7 @@ type ConnectionRequest struct {
|
||||
PingInterval string `json:"pingInterval,omitempty"`
|
||||
PingTimeout string `json:"pingTimeout,omitempty"`
|
||||
OrgID string `json:"orgId,omitempty"`
|
||||
MatchDomains []string `json:"matchDomains,omitempty"`
|
||||
}
|
||||
|
||||
// SwitchOrgRequest defines the structure for switching organizations
|
||||
|
||||
28
config.go
28
config.go
@@ -27,6 +27,13 @@ type OlmConfig struct {
|
||||
UpstreamDNS []string `json:"upstreamDNS"`
|
||||
InterfaceName string `json:"interface"`
|
||||
|
||||
// MatchDomains lists FQDN wildcard patterns (using * and ? wildcards, e.g.
|
||||
// "*.proxy.internal") that olm should check against local records / resolve
|
||||
// via UpstreamDNS. Queries for domains that don't match any pattern are sent
|
||||
// directly to the host's own system DNS servers instead. Empty means match
|
||||
// every domain (i.e. the feature is disabled).
|
||||
MatchDomains []string `json:"matchDomains"`
|
||||
|
||||
// Logging
|
||||
LogLevel string `json:"logLevel"`
|
||||
|
||||
@@ -99,6 +106,7 @@ func DefaultConfig() *OlmConfig {
|
||||
config.sources["mtu"] = string(SourceDefault)
|
||||
config.sources["dns"] = string(SourceDefault)
|
||||
config.sources["upstreamDNS"] = string(SourceDefault)
|
||||
config.sources["matchDomains"] = string(SourceDefault)
|
||||
config.sources["logLevel"] = string(SourceDefault)
|
||||
config.sources["interface"] = string(SourceDefault)
|
||||
config.sources["enableApi"] = string(SourceDefault)
|
||||
@@ -229,6 +237,10 @@ func loadConfigFromEnv(config *OlmConfig) {
|
||||
config.UpstreamDNS = []string{val}
|
||||
config.sources["upstreamDNS"] = string(SourceEnv)
|
||||
}
|
||||
if val := os.Getenv("MATCH_DOMAINS"); val != "" {
|
||||
config.MatchDomains = splitComma(val)
|
||||
config.sources["matchDomains"] = string(SourceEnv)
|
||||
}
|
||||
if val := os.Getenv("LOG_LEVEL"); val != "" {
|
||||
config.LogLevel = val
|
||||
config.sources["logLevel"] = string(SourceEnv)
|
||||
@@ -293,6 +305,7 @@ func loadConfigFromCLI(config *OlmConfig, args []string) (bool, bool, error) {
|
||||
"mtu": config.MTU,
|
||||
"dns": config.DNS,
|
||||
"upstreamDNS": fmt.Sprintf("%v", config.UpstreamDNS),
|
||||
"matchDomains": fmt.Sprintf("%v", config.MatchDomains),
|
||||
"logLevel": config.LogLevel,
|
||||
"interface": config.InterfaceName,
|
||||
"httpAddr": config.HTTPAddr,
|
||||
@@ -317,6 +330,8 @@ func loadConfigFromCLI(config *OlmConfig, args []string) (bool, bool, error) {
|
||||
serviceFlags.StringVar(&config.DNS, "dns", config.DNS, "DNS server to use")
|
||||
var upstreamDNSFlag string
|
||||
serviceFlags.StringVar(&upstreamDNSFlag, "upstream-dns", "", "Upstream DNS server(s) (comma-separated, default: 8.8.8.8:53)")
|
||||
var matchDomainsFlag string
|
||||
serviceFlags.StringVar(&matchDomainsFlag, "match-domains", "", "FQDN wildcard patterns (comma-separated, e.g. '*.proxy.internal,*.host-0?.autoco.internal') to check against local records/upstream DNS; queries for non-matching domains are sent directly to the system's DNS servers (default: match all domains)")
|
||||
serviceFlags.StringVar(&config.LogLevel, "log-level", config.LogLevel, "Log level (DEBUG, INFO, WARN, ERROR, FATAL)")
|
||||
serviceFlags.StringVar(&config.InterfaceName, "interface", config.InterfaceName, "Name of the WireGuard interface")
|
||||
serviceFlags.StringVar(&config.HTTPAddr, "http-addr", config.HTTPAddr, "HTTP server address (e.g., ':9452')")
|
||||
@@ -348,6 +363,11 @@ func loadConfigFromCLI(config *OlmConfig, args []string) (bool, bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Parse match domains flag if provided
|
||||
if matchDomainsFlag != "" {
|
||||
config.MatchDomains = splitComma(matchDomainsFlag)
|
||||
}
|
||||
|
||||
// Track which values were changed by CLI args
|
||||
if config.Endpoint != origValues["endpoint"].(string) {
|
||||
config.sources["endpoint"] = string(SourceCLI)
|
||||
@@ -373,6 +393,9 @@ func loadConfigFromCLI(config *OlmConfig, args []string) (bool, bool, error) {
|
||||
if fmt.Sprintf("%v", config.UpstreamDNS) != origValues["upstreamDNS"].(string) {
|
||||
config.sources["upstreamDNS"] = string(SourceCLI)
|
||||
}
|
||||
if fmt.Sprintf("%v", config.MatchDomains) != origValues["matchDomains"].(string) {
|
||||
config.sources["matchDomains"] = string(SourceCLI)
|
||||
}
|
||||
if config.LogLevel != origValues["logLevel"].(string) {
|
||||
config.sources["logLevel"] = string(SourceCLI)
|
||||
}
|
||||
@@ -481,6 +504,10 @@ func mergeConfigs(dest, src *OlmConfig) {
|
||||
dest.UpstreamDNS = src.UpstreamDNS
|
||||
dest.sources["upstreamDNS"] = string(SourceFile)
|
||||
}
|
||||
if len(src.MatchDomains) > 0 {
|
||||
dest.MatchDomains = src.MatchDomains
|
||||
dest.sources["matchDomains"] = string(SourceFile)
|
||||
}
|
||||
if src.LogLevel != "" && src.LogLevel != "INFO" {
|
||||
dest.LogLevel = src.LogLevel
|
||||
dest.sources["logLevel"] = string(SourceFile)
|
||||
@@ -598,6 +625,7 @@ func (c *OlmConfig) ShowConfig() {
|
||||
fmt.Printf(" mtu = %d [%s]\n", c.MTU, getSource("mtu"))
|
||||
fmt.Printf(" dns = %s [%s]\n", c.DNS, getSource("dns"))
|
||||
fmt.Printf(" upstream-dns = %v [%s]\n", c.UpstreamDNS, getSource("upstreamDNS"))
|
||||
fmt.Printf(" match-domains = %v [%s]\n", c.MatchDomains, getSource("matchDomains"))
|
||||
fmt.Printf(" interface = %s [%s]\n", c.InterfaceName, getSource("interface"))
|
||||
|
||||
// Logging
|
||||
|
||||
119
dns/dns_proxy.go
119
dns/dns_proxy.go
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -38,6 +39,20 @@ type DNSProxy struct {
|
||||
middleDevice *device.MiddleDevice // Reference to MiddleDevice for packet filtering and TUN writes
|
||||
recordStore *DNSRecordStore // Local DNS records
|
||||
|
||||
// matchDomains lists the FQDN wildcard patterns (using * and ? wildcards, see
|
||||
// matchWildcard) that this proxy is responsible for. Queries whose name matches
|
||||
// one of these patterns are checked against local records and, failing that,
|
||||
// forwarded to upstreamDNS. Queries that match none of the patterns are sent
|
||||
// directly to localDNS instead, bypassing local records and upstreamDNS
|
||||
// entirely. An empty matchDomains means "match everything" (i.e. behave as if
|
||||
// this feature were not configured).
|
||||
matchDomains []string
|
||||
// localDNS holds the host's own system DNS servers (as reported by
|
||||
// SystemDNSMonitor / PublicDNS), used to resolve queries that don't match
|
||||
// matchDomains rather than sending them upstream or through the tunnel.
|
||||
localDNS []string
|
||||
matchMu sync.RWMutex
|
||||
|
||||
// Tunnel DNS fields - for sending queries over WireGuard
|
||||
tunnelIP netip.Addr // WireGuard interface IP (source for tunneled queries)
|
||||
tunnelStack *stack.Stack // Separate netstack for outbound tunnel queries
|
||||
@@ -55,8 +70,14 @@ type DNSProxy struct {
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewDNSProxy creates a new DNS proxy
|
||||
func NewDNSProxy(middleDevice *device.MiddleDevice, mtu int, utilitySubnet string, upstreamDns []string, tunnelDns bool, tunnelIP string) (*DNSProxy, error) {
|
||||
// NewDNSProxy creates a new DNS proxy.
|
||||
//
|
||||
// matchDomains, if non-empty, restricts local-record lookup and upstream
|
||||
// forwarding to queries whose name matches one of the given wildcard patterns
|
||||
// (see matchWildcard). Queries that match none of the patterns are instead
|
||||
// forwarded directly to localDNS (the host's own system DNS servers). Pass an
|
||||
// empty matchDomains to match every query, preserving prior behavior.
|
||||
func NewDNSProxy(middleDevice *device.MiddleDevice, mtu int, utilitySubnet string, upstreamDns []string, tunnelDns bool, tunnelIP string, matchDomains []string, localDNS []string) (*DNSProxy, error) {
|
||||
proxyIP, err := PickIPFromSubnet(utilitySubnet)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to pick DNS proxy IP from subnet: %v", err)
|
||||
@@ -76,6 +97,8 @@ func NewDNSProxy(middleDevice *device.MiddleDevice, mtu int, utilitySubnet strin
|
||||
tunnelDNS: tunnelDns,
|
||||
recordStore: NewDNSRecordStore(),
|
||||
tunnelActivePorts: make(map[uint16]bool),
|
||||
matchDomains: matchDomains,
|
||||
localDNS: localDNS,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
@@ -383,6 +406,27 @@ func (p *DNSProxy) handleDNSQuery(udpConn *gonet.UDPConn, queryData []byte, clie
|
||||
question := msg.Question[0]
|
||||
logger.Debug("DNS query for %s (type %s)", question.Name, dns.TypeToString[question.Qtype])
|
||||
|
||||
// If matchDomains is configured and this query's name doesn't match any of
|
||||
// the configured patterns, skip local records and upstream entirely and
|
||||
// send it straight to the host's own system DNS servers.
|
||||
if !p.matchesConfiguredDomains(question.Name) {
|
||||
logger.Debug("Query for %s does not match configured domains, forwarding to local DNS %v", question.Name, p.getLocalDNS())
|
||||
response := p.forwardToLocalDNS(msg)
|
||||
if response == nil {
|
||||
logger.Error("Failed to get DNS response for %s from local DNS", question.Name)
|
||||
return
|
||||
}
|
||||
responseData, err := response.Pack()
|
||||
if err != nil {
|
||||
logger.Error("Failed to pack DNS response: %v", err)
|
||||
return
|
||||
}
|
||||
if _, err := udpConn.WriteTo(responseData, clientAddr); err != nil {
|
||||
logger.Error("Failed to send DNS response: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Check if we have local records for this query
|
||||
var response *dns.Msg
|
||||
if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA || question.Qtype == dns.TypePTR {
|
||||
@@ -505,6 +549,77 @@ func (p *DNSProxy) checkLocalRecords(query *dns.Msg, question dns.Question) *dns
|
||||
return response
|
||||
}
|
||||
|
||||
// matchesConfiguredDomains reports whether name matches one of the configured
|
||||
// matchDomains wildcard patterns. If matchDomains is empty, every name is
|
||||
// considered a match (i.e. the feature is disabled).
|
||||
func (p *DNSProxy) matchesConfiguredDomains(name string) bool {
|
||||
p.matchMu.RLock()
|
||||
patterns := p.matchDomains
|
||||
p.matchMu.RUnlock()
|
||||
|
||||
if len(patterns) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
name = strings.ToLower(dns.Fqdn(name))
|
||||
for _, pattern := range patterns {
|
||||
pattern = strings.ToLower(dns.Fqdn(pattern))
|
||||
if matchWildcard(pattern, name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// getLocalDNS returns the currently configured local (system) DNS servers.
|
||||
func (p *DNSProxy) getLocalDNS() []string {
|
||||
p.matchMu.RLock()
|
||||
defer p.matchMu.RUnlock()
|
||||
return p.localDNS
|
||||
}
|
||||
|
||||
// forwardToLocalDNS forwards a DNS query directly to the host's own system DNS
|
||||
// servers (localDNS), always using host networking regardless of tunnelDNS -
|
||||
// these queries are for domains the caller has explicitly excluded from
|
||||
// Pangolin resolution, so they should never traverse the tunnel.
|
||||
func (p *DNSProxy) forwardToLocalDNS(query *dns.Msg) *dns.Msg {
|
||||
servers := p.getLocalDNS()
|
||||
if len(servers) == 0 {
|
||||
logger.Warn("No local DNS servers configured, dropping query for %s", query.Question[0].Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for _, server := range servers {
|
||||
response, err := p.queryUpstreamDirect(server, query, 2*time.Second)
|
||||
if err == nil {
|
||||
return response
|
||||
}
|
||||
lastErr = err
|
||||
}
|
||||
logger.Error("All local DNS servers failed: %v", lastErr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetMatchDomains replaces the list of wildcard domain patterns (see
|
||||
// matchWildcard) that this proxy checks against local records / upstream DNS.
|
||||
// Queries not matching any pattern are sent to localDNS instead. Pass an
|
||||
// empty slice to match every query (i.e. disable filtering).
|
||||
func (p *DNSProxy) SetMatchDomains(patterns []string) {
|
||||
p.matchMu.Lock()
|
||||
defer p.matchMu.Unlock()
|
||||
p.matchDomains = patterns
|
||||
}
|
||||
|
||||
// SetLocalDNS replaces the list of local (host system) DNS servers used to
|
||||
// resolve queries that don't match matchDomains. Servers must be in
|
||||
// "host:port" format (e.g. "192.168.1.1:53").
|
||||
func (p *DNSProxy) SetLocalDNS(servers []string) {
|
||||
p.matchMu.Lock()
|
||||
defer p.matchMu.Unlock()
|
||||
p.localDNS = servers
|
||||
}
|
||||
|
||||
// forwardToUpstream forwards a DNS query to upstream DNS servers
|
||||
func (p *DNSProxy) forwardToUpstream(query *dns.Msg) *dns.Msg {
|
||||
// Try primary DNS server
|
||||
|
||||
1
main.go
1
main.go
@@ -263,6 +263,7 @@ func runOlmMainWithArgs(ctx context.Context, cancel context.CancelFunc, signalCt
|
||||
MTU: config.MTU,
|
||||
DNS: config.DNS,
|
||||
UpstreamDNS: config.UpstreamDNS,
|
||||
MatchDomains: config.MatchDomains,
|
||||
InterfaceName: config.InterfaceName,
|
||||
Holepunch: !config.DisableHolepunch,
|
||||
TlsClientCert: config.TlsClientCert,
|
||||
|
||||
@@ -145,7 +145,7 @@ func (o *Olm) handleConnect(msg websocket.WSMessage) {
|
||||
}
|
||||
|
||||
// Create and start DNS proxy
|
||||
o.dnsProxy, err = dns.NewDNSProxy(o.middleDev, o.tunnelConfig.MTU, wgData.UtilitySubnet, o.tunnelConfig.UpstreamDNS, o.tunnelConfig.TunnelDNS, interfaceIP)
|
||||
o.dnsProxy, err = dns.NewDNSProxy(o.middleDev, o.tunnelConfig.MTU, wgData.UtilitySubnet, o.tunnelConfig.UpstreamDNS, o.tunnelConfig.TunnelDNS, interfaceIP, o.tunnelConfig.MatchDomains, o.tunnelConfig.PublicDNS)
|
||||
if err != nil {
|
||||
logger.Error("Failed to create DNS proxy: %v", err)
|
||||
}
|
||||
|
||||
@@ -231,6 +231,7 @@ func (o *Olm) registerAPICallbacks() {
|
||||
Holepunch: req.Holepunch,
|
||||
TlsClientCert: req.TlsClientCert,
|
||||
OrgID: req.OrgID,
|
||||
MatchDomains: req.MatchDomains,
|
||||
}
|
||||
|
||||
var err error
|
||||
@@ -427,6 +428,11 @@ func (o *Olm) StartTunnel(config TunnelConfig) {
|
||||
if pm := o.getPeerManager(); pm != nil {
|
||||
pm.SetPublicDNS(servers)
|
||||
}
|
||||
// Keep the DNS proxy's local-DNS fallback (used for MatchDomains
|
||||
// misses) in sync with the host's real system DNS servers.
|
||||
if o.dnsProxy != nil {
|
||||
o.dnsProxy.SetLocalDNS(servers)
|
||||
}
|
||||
|
||||
// UpstreamDNS is updated only when the caller did not supply an
|
||||
// explicit value; dynamic updates keep the proxy forwarding to the
|
||||
|
||||
@@ -79,6 +79,13 @@ type TunnelConfig struct {
|
||||
PublicDNS []string
|
||||
InterfaceName string
|
||||
|
||||
// MatchDomains lists FQDN wildcard patterns (using * and ? wildcards) that
|
||||
// olm should check against local records / resolve via UpstreamDNS. Queries
|
||||
// that don't match any pattern are sent directly to the host's own system
|
||||
// DNS servers (PublicDNS) instead of being handled by the DNS proxy at all.
|
||||
// An empty MatchDomains matches every query, preserving prior behavior.
|
||||
MatchDomains []string
|
||||
|
||||
// Advanced
|
||||
Holepunch bool
|
||||
TlsClientCert string
|
||||
|
||||
Reference in New Issue
Block a user