Files
AdGuardHome/internal/dnsforward/ipset.go
Stanislav Chzhen 86de4e75f0 Pull request 2442: AGDNS-3061-config-modifier
Merge in DNS/adguard-home from AGDNS-3061-config-modifier to master

Squashed commit of the following:

commit a0068547bd0209d12e8dbf98ddd5e4ed2545cdd0
Merge: 97b798af6 451255675
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Aug 5 17:39:31 2025 +0300

    Merge branch 'master' into AGDNS-3061-config-modifier

commit 97b798af6a50ee27ee5ed2bcf1c4c3670f5afc5d
Merge: 96d21efc9 b8043e4f0
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jul 30 20:27:41 2025 +0300

    Merge branch 'master' into AGDNS-3061-config-modifier

commit 96d21efc984073adef5026de8a03b5bf94542648
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Jul 30 20:18:43 2025 +0300

    all: imp code

commit 67c5608b4be3bd712a0ab5980f25ecea1ed21d65
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Jul 29 20:31:19 2025 +0300

    all: imp code

commit 52f45a9f70f57d8e7f7fc0e9e8291ff0dde74343
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Jul 28 20:32:13 2025 +0300

    all: use config modifier

commit d389ffd286460d8ff1964bd2ee8dabdafb832b9b
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jul 25 14:20:31 2025 +0300

    bamboo-specs: fix ci

commit 3f303ac9131a07e4af5783696b237436fd93d65c
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Jul 25 14:18:42 2025 +0300

    home: config modifier
2025-08-05 18:16:39 +03:00

150 lines
3.6 KiB
Go

package dnsforward
import (
"context"
"fmt"
"log/slog"
"net"
"os"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/ipset"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/miekg/dns"
)
// ipsetHandler is the ipset context. ipsetMgr can be nil.
type ipsetHandler struct {
ipsetMgr ipset.Manager
logger *slog.Logger
}
// newIpsetHandler returns a new initialized [ipsetHandler]. It is not safe for
// concurrent use.
func newIpsetHandler(
ctx context.Context,
logger *slog.Logger,
ipsetList []string,
) (h *ipsetHandler, err error) {
h = &ipsetHandler{
logger: logger,
}
conf := &ipset.Config{
Logger: logger,
Lines: ipsetList,
}
h.ipsetMgr, err = ipset.NewManager(ctx, conf)
if errors.Is(err, os.ErrInvalid) ||
errors.Is(err, os.ErrPermission) ||
errors.Is(err, errors.ErrUnsupported) {
// ipset cannot currently be initialized if the server was installed
// from Snap or when the user or the binary doesn't have the required
// permissions, or when the kernel doesn't support netfilter.
//
// Log and go on.
//
// TODO(a.garipov): The Snap problem can probably be solved if we add
// the netlink-connector interface plug.
logger.WarnContext(ctx, "cannot initialize", slogutil.KeyError, err)
return h, nil
} else if err != nil {
return nil, fmt.Errorf("initializing ipset: %w", err)
}
return h, nil
}
// close closes the Linux Netfilter connections. close can be called on a nil
// handler.
func (h *ipsetHandler) close() (err error) {
if h != nil && h.ipsetMgr != nil {
return h.ipsetMgr.Close()
}
return nil
}
// dctxIsFilled returns true if dctx has enough information to process.
func dctxIsFilled(dctx *dnsContext) (ok bool) {
return dctx != nil &&
dctx.responseFromUpstream &&
dctx.proxyCtx != nil &&
dctx.proxyCtx.Res != nil &&
dctx.proxyCtx.Req != nil &&
len(dctx.proxyCtx.Req.Question) > 0
}
// skipIpsetProcessing returns true when the ipset processing can be skipped for
// this request.
func (h *ipsetHandler) skipIpsetProcessing(dctx *dnsContext) (ok bool) {
if h == nil || h.ipsetMgr == nil || !dctxIsFilled(dctx) {
return true
}
qtype := dctx.proxyCtx.Req.Question[0].Qtype
return qtype != dns.TypeA && qtype != dns.TypeAAAA && qtype != dns.TypeANY
}
// ipFromRR returns an IP address from a DNS resource record.
func ipFromRR(rr dns.RR) (ip net.IP) {
switch a := rr.(type) {
case *dns.A:
return a.A
case *dns.AAAA:
return a.AAAA
default:
return nil
}
}
// ipsFromAnswer returns IPv4 and IPv6 addresses from a DNS answer.
func ipsFromAnswer(ans []dns.RR) (ip4s, ip6s []net.IP) {
for _, rr := range ans {
ip := ipFromRR(rr)
if ip == nil {
continue
}
if ip.To4() == nil {
ip6s = append(ip6s, ip)
continue
}
ip4s = append(ip4s, ip)
}
return ip4s, ip6s
}
// process adds the resolved IP addresses to the domain's ipsets, if any.
func (h *ipsetHandler) process(ctx context.Context, dctx *dnsContext) (rc resultCode) {
h.logger.DebugContext(ctx, "started processing")
defer h.logger.DebugContext(ctx, "finished processing")
if h.skipIpsetProcessing(dctx) {
return resultCodeSuccess
}
req := dctx.proxyCtx.Req
host := req.Question[0].Name
host = strings.TrimSuffix(host, ".")
host = strings.ToLower(host)
ip4s, ip6s := ipsFromAnswer(dctx.proxyCtx.Res.Answer)
n, err := h.ipsetMgr.Add(ctx, host, ip4s, ip6s)
if err != nil {
// Consider ipset errors non-critical to the request.
h.logger.ErrorContext(ctx, "adding host ips", slogutil.KeyError, err)
return resultCodeSuccess
}
h.logger.DebugContext(ctx, "added new ipset entries", "num", n)
return resultCodeSuccess
}