mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-09-04 13:36:35 +02:00
69 lines
2.2 KiB
Go
69 lines
2.2 KiB
Go
package cli
|
|
|
|
// Interception probe registry.
|
|
//
|
|
// A probe sends a DNS query for a unique synthetic domain through the OS resolver and
|
|
// waits for ctrld's own handler to receive it. That is the only way to tell "the rules are
|
|
// present" from "the rules are actually redirecting packets", and both the macOS pf path
|
|
// and the Windows NRPT path use it.
|
|
//
|
|
// Each attempt registers its own domain, so overlapping probes cannot cancel each other,
|
|
// and deregistration only removes the entry it owns.
|
|
|
|
// registerInterceptProbe registers domain and returns the channel it will be signalled on
|
|
// plus the function that removes the registration.
|
|
//
|
|
//lint:ignore U1000 used on darwin (pf probes) and windows (NRPT probes)
|
|
func (p *prog) registerInterceptProbe(domain string) (<-chan struct{}, func()) {
|
|
ch := make(chan struct{}, 1)
|
|
|
|
p.interceptProbeMu.Lock()
|
|
current, _ := p.interceptProbes.Load().(map[string]chan struct{})
|
|
next := make(map[string]chan struct{}, len(current)+1)
|
|
for k, v := range current {
|
|
next[k] = v
|
|
}
|
|
next[domain] = ch
|
|
p.interceptProbes.Store(next)
|
|
p.interceptProbeMu.Unlock()
|
|
|
|
return ch, func() {
|
|
p.interceptProbeMu.Lock()
|
|
defer p.interceptProbeMu.Unlock()
|
|
current, _ := p.interceptProbes.Load().(map[string]chan struct{})
|
|
// Only drop the entry while it is still this attempt's channel. A later probe
|
|
// that reused the domain owns the slot now, and clearing it would make that one
|
|
// wait out its timeout for a query it already received.
|
|
if existing, ok := current[domain]; !ok || existing != ch {
|
|
return
|
|
}
|
|
next := make(map[string]chan struct{}, len(current))
|
|
for k, v := range current {
|
|
if k != domain {
|
|
next[k] = v
|
|
}
|
|
}
|
|
p.interceptProbes.Store(next)
|
|
}
|
|
}
|
|
|
|
// signalInterceptProbe reports whether domain is a pending probe, signalling its waiter
|
|
// when it is. Called from the DNS handler for every query, so the common case is a nil or
|
|
// empty map and no allocation.
|
|
func (p *prog) signalInterceptProbe(domain string) bool {
|
|
probes, _ := p.interceptProbes.Load().(map[string]chan struct{})
|
|
if len(probes) == 0 {
|
|
return false
|
|
}
|
|
ch, ok := probes[domain]
|
|
if !ok {
|
|
return false
|
|
}
|
|
select {
|
|
case ch <- struct{}{}:
|
|
default:
|
|
// Buffered channel already holds a signal: the waiter has what it needs.
|
|
}
|
|
return true
|
|
}
|