140 lines
3.2 KiB
Go
140 lines
3.2 KiB
Go
package resolver
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func ParseStaticEntries(lines []string, logf func(string, ...any)) (entries [][3]string, skipped int) {
|
|
for _, ln := range lines {
|
|
s := strings.TrimSpace(ln)
|
|
if s == "" || strings.HasPrefix(s, "#") {
|
|
continue
|
|
}
|
|
comment := ""
|
|
if idx := strings.Index(s, "#"); idx >= 0 {
|
|
comment = strings.TrimSpace(s[idx+1:])
|
|
s = strings.TrimSpace(s[:idx])
|
|
}
|
|
if s == "" || IsPrivateIPv4(s) {
|
|
continue
|
|
}
|
|
|
|
rawBase := strings.SplitN(s, "/", 2)[0]
|
|
if strings.Contains(s, "/") {
|
|
if _, err := netip.ParsePrefix(s); err != nil {
|
|
skipped++
|
|
if logf != nil {
|
|
logf("static skip invalid prefix %q: %v", s, err)
|
|
}
|
|
continue
|
|
}
|
|
} else {
|
|
if _, err := netip.ParseAddr(rawBase); err != nil {
|
|
skipped++
|
|
if logf != nil {
|
|
logf("static skip invalid ip %q: %v", s, err)
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
|
|
entries = append(entries, [3]string{s, rawBase, comment})
|
|
}
|
|
return entries, skipped
|
|
}
|
|
|
|
func ResolveStaticLabels(entries [][3]string, dnsForPtr string, ptrCache map[string]any, ttl int, logf func(string, ...any)) (map[string][]string, int, int) {
|
|
now := int(time.Now().Unix())
|
|
result := map[string][]string{}
|
|
ptrLookups := 0
|
|
ptrErrors := 0
|
|
|
|
for _, e := range entries {
|
|
ipEntry, baseIP, comment := e[0], e[1], e[2]
|
|
var labels []string
|
|
if comment != "" {
|
|
labels = append(labels, "*"+comment)
|
|
}
|
|
if comment == "" {
|
|
if cached, ok := ptrCache[baseIP].(map[string]any); ok {
|
|
names, _ := cached["names"].([]any)
|
|
last, _ := cached["last_resolved"].(float64)
|
|
if len(names) > 0 && last > 0 && now-int(last) <= ttl {
|
|
for _, n := range names {
|
|
if s, ok := n.(string); ok && s != "" {
|
|
labels = append(labels, "*"+s)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if len(labels) == 0 {
|
|
ptrLookups++
|
|
names, err := DigPTR(baseIP, dnsForPtr, 3*time.Second, logf)
|
|
if err != nil {
|
|
ptrErrors++
|
|
}
|
|
if len(names) > 0 {
|
|
ptrCache[baseIP] = map[string]any{"names": names, "last_resolved": now}
|
|
for _, n := range names {
|
|
labels = append(labels, "*"+n)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if len(labels) == 0 {
|
|
labels = []string{"*[STATIC-IP]"}
|
|
}
|
|
result[ipEntry] = labels
|
|
if logf != nil {
|
|
logf("static %s -> %v", ipEntry, labels)
|
|
}
|
|
}
|
|
|
|
return result, ptrLookups, ptrErrors
|
|
}
|
|
|
|
func DigPTR(ip, upstream string, timeout time.Duration, logf func(string, ...any)) ([]string, error) {
|
|
server, port := SplitDNS(upstream)
|
|
if server == "" {
|
|
return nil, fmt.Errorf("upstream empty")
|
|
}
|
|
if port == "" {
|
|
port = "53"
|
|
}
|
|
addr := net.JoinHostPort(server, port)
|
|
r := &net.Resolver{
|
|
PreferGo: true,
|
|
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
|
d := net.Dialer{}
|
|
return d.DialContext(ctx, "udp", addr)
|
|
},
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
names, err := r.LookupAddr(ctx, ip)
|
|
cancel()
|
|
if err != nil {
|
|
if logf != nil {
|
|
logf("ptr error %s via %s: %v", ip, addr, err)
|
|
}
|
|
return nil, err
|
|
}
|
|
seen := map[string]struct{}{}
|
|
var out []string
|
|
for _, n := range names {
|
|
n = strings.TrimSuffix(strings.ToLower(strings.TrimSpace(n)), ".")
|
|
if n == "" {
|
|
continue
|
|
}
|
|
if _, ok := seen[n]; !ok {
|
|
seen[n] = struct{}{}
|
|
out = append(out, n)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|