Files
elmprodvpn/selective-vpn-api/app/resolver/static_labels.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
}