-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathprocess_local.go
More file actions
156 lines (137 loc) · 5.02 KB
/
process_local.go
File metadata and controls
156 lines (137 loc) · 5.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/*
File: process_local.go
Version: 1.6.0
Updated: 11-May-2026 14:31 CEST
Description:
Intercepts queries for locally known hosts or DHCP leases and "spoofs"
responses to serve them directly without upstream forwarding.
Extracted from process.go to improve modularity.
Changes:
1.6.0 - [REFACTOR] Inherited `PreserveEDNS0` utility to systematically guarantee
RFC 6891 bounds natively on structural LAN returns without redundant arrays.
1.5.0 - [REFACTOR] Adopted the centralized `RecordBlockEvent` telemetry function
to securely align internal domain blocks (NULL-IP tracking) across
the analytical outputs naturally.
1.4.0 - [SECURITY/FIX] Propagated `originalQName` and `spoofedAlias` parameters natively
to ensure internal LAN query resolutions correctly honor reporting
transparency when chained beneath a custom `rrs:` Spoofed Record engine.
1.3.0 - [PERF] Wired synthetic response instantiation natively into the zero-allocation
`msgPool`. Eradicates severe Garbage Collection (GC) thrashing if the
router is flooded with excessive internal LAN queries (e.g., IoT scanning).
1.2.0 - [FIX] Addressed pointer aliasing boundaries. Explicitly allocates and copies
IP byte arrays to completely prevent memory-sharing regressions and heap
corruption when iterating across identity maps.
*/
package main
import (
"fmt"
"log"
"net"
"github.com/miekg/dns"
)
// handleLocalIdentity searches local hostfiles and DHCP leases for the requested
// domain name and securely builds a synthetic DNS response if a match is found.
// Returns true if the query was successfully handled locally.
func handleLocalIdentity(w dns.ResponseWriter, r *dns.Msg, q dns.Question, qNameTrimmed, clientID, clientIP, protocol, routeName, routeOriginType string, bypassLocal bool, cacheKey DNSCacheKey, parentalForcedTTL uint32, originalQName, spoofedAlias string) bool {
if bypassLocal {
return false
}
switch q.Qtype {
case dns.TypeA, dns.TypeAAAA:
if addrs, match := LookupIPsByNameLower(qNameTrimmed); len(addrs) > 0 {
resp := msgPool.Get().(*dns.Msg)
*resp = dns.Msg{} // Zero fields safely
resp.SetReply(r)
resp.Authoritative = true
resp.Answer = make([]dns.RR, 0, len(addrs))
for _, addr := range addrs {
switch {
case addr.Is4() && q.Qtype == dns.TypeA:
a := addr.As4()
ip := make(net.IP, 4)
copy(ip, a[:])
resp.Answer = append(resp.Answer, &dns.A{
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeA,
Class: dns.ClassINET, Ttl: syntheticTTL},
A: ip,
})
case addr.Is6() && !addr.Is4In6() && q.Qtype == dns.TypeAAAA:
a := addr.As16()
ip := make(net.IP, 16)
copy(ip, a[:])
resp.Answer = append(resp.Answer, &dns.AAAA{
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeAAAA,
Class: dns.ClassINET, Ttl: syntheticTTL},
AAAA: ip,
})
}
}
if len(resp.Answer) > 0 {
if cacheLocalIdentity {
CacheSetSynth(cacheKey, resp)
}
if parentalForcedTTL > 0 {
CapResponseTTL(resp, parentalForcedTTL)
}
PreserveEDNS0(r, resp)
isNullIP := responseContainsNullIP(resp)
if isNullIP {
IncrPolicyBlock()
RecordBlockEvent(clientIP, qNameTrimmed, "Local Hosts (NULL-IP)")
}
IncrReturnCode(resp.Rcode, isNullIP)
w.WriteMsg(resp)
msgPool.Put(resp)
if logQueries {
matchInfo := ""
if match != qNameTrimmed {
matchInfo = " (matched " + match + ")"
}
statusLog := "LOCAL"
if isNullIP { statusLog = "LOCAL BLOCK" }
if spoofedAlias != "" {
statusLog = fmt.Sprintf("SPOOFED ALIAS (%s) | %s", spoofedAlias, statusLog)
}
if isNullIP {
log.Printf("[DNS] [%s] %s -> %s %s | ROUTE: %s (%s) | %s%s | NOERROR (NULL-IP)",
protocol, clientID, originalQName, dns.TypeToString[q.Qtype],
routeName, routeOriginType, statusLog, matchInfo)
} else {
log.Printf("[DNS] [%s] %s -> %s %s | ROUTE: %s (%s) | %s%s | NOERROR",
protocol, clientID, originalQName, dns.TypeToString[q.Qtype],
routeName, routeOriginType, statusLog, matchInfo)
}
}
return true
}
msgPool.Put(resp) // In case Answer array was empty
}
case dns.TypePTR:
if names := LookupNamesByARPA(qNameTrimmed); len(names) > 0 {
resp := msgPool.Get().(*dns.Msg)
*resp = dns.Msg{} // Zero fields safely
resp.SetReply(r)
resp.Authoritative = true
resp.Answer = make([]dns.RR, 0, len(names))
for _, name := range names {
resp.Answer = append(resp.Answer, &dns.PTR{
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypePTR,
Class: dns.ClassINET, Ttl: syntheticTTL},
Ptr: dns.Fqdn(name),
})
}
PreserveEDNS0(r, resp)
if cacheLocalIdentity {
CacheSetSynth(cacheKey, resp)
}
w.WriteMsg(resp)
msgPool.Put(resp)
if logQueries {
log.Printf("[DNS] [%s] %s -> %s PTR | ROUTE: %s (%s) | LOCAL | NOERROR",
protocol, clientID, originalQName, routeName, routeOriginType)
}
return true
}
}
return false
}