all: sync with master; upd chlog

This commit is contained in:
Ainar Garipov
2023-02-01 15:41:34 +03:00
parent c69639c013
commit 80eb339896
344 changed files with 2793 additions and 23680 deletions

View File

@@ -4,6 +4,8 @@ import (
"bytes"
"encoding/json"
"fmt"
"github.com/AdguardTeam/golibs/mathutil"
)
// NullBool is a nullable boolean. Use these in JSON requests and responses
@@ -33,11 +35,7 @@ func (nb NullBool) String() (s string) {
// BoolToNullBool converts a bool into a NullBool.
func BoolToNullBool(cond bool) (nb NullBool) {
if cond {
return NBTrue
}
return NBFalse
return NBFalse - mathutil.BoolToNumber[NullBool](cond)
}
// type check

View File

@@ -4,6 +4,9 @@ package aghio
import (
"fmt"
"io"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/mathutil"
)
// LimitReachedError records the limit and the operation that caused it.
@@ -11,22 +14,22 @@ type LimitReachedError struct {
Limit int64
}
// Error implements the error interface for LimitReachedError.
// Error implements the [error] interface for *LimitReachedError.
//
// TODO(a.garipov): Think about error string format.
func (lre *LimitReachedError) Error() string {
return fmt.Sprintf("attempted to read more than %d bytes", lre.Limit)
}
// limitedReader is a wrapper for io.Reader with limited reader and dealing with
// errors package.
// limitedReader is a wrapper for [io.Reader] limiting the input and dealing
// with errors package.
type limitedReader struct {
r io.Reader
limit int64
n int64
}
// Read implements Reader interface.
// Read implements the [io.Reader] interface.
func (lr *limitedReader) Read(p []byte) (n int, err error) {
if lr.n == 0 {
return 0, &LimitReachedError{
@@ -34,9 +37,7 @@ func (lr *limitedReader) Read(p []byte) (n int, err error) {
}
}
if int64(len(p)) > lr.n {
p = p[:lr.n]
}
p = p[:mathutil.Min(lr.n, int64(len(p)))]
n, err = lr.r.Read(p)
lr.n -= int64(n)
@@ -48,7 +49,7 @@ func (lr *limitedReader) Read(p []byte) (n int, err error) {
// n bytes read.
func LimitReader(r io.Reader, n int64) (limited io.Reader, err error) {
if n < 0 {
return nil, fmt.Errorf("aghio: invalid n in LimitReader: %d", n)
return nil, errors.Error("limit must be non-negative")
}
return &limitedReader{

View File

@@ -24,7 +24,7 @@ func TestLimitReader(t *testing.T) {
name: "zero",
n: 0,
}, {
wantErrMsg: "aghio: invalid n in LimitReader: -1",
wantErrMsg: "limit must be non-negative",
name: "negative",
n: -1,
}}

View File

@@ -56,7 +56,7 @@ func (rm *requestMatcher) MatchRequest(
) (res *urlfilter.DNSResult, ok bool) {
switch req.DNSType {
case dns.TypeA, dns.TypeAAAA, dns.TypePTR:
log.Debug("%s: handling the request", hostsContainerPref)
log.Debug("%s: handling the request for %s", hostsContainerPref, req.Hostname)
default:
return nil, false
}
@@ -481,9 +481,6 @@ func (hc *HostsContainer) refresh() (err error) {
}
// hc.last is nil on the first refresh, so let that one through.
//
// TODO(a.garipov): Once https://github.com/golang/go/issues/56621 is
// resolved, remove the first condition.
if hc.last != nil && maps.EqualFunc(hp.table, hc.last, (*HostsRecord).equal) {
log.Debug("%s: no changes detected", hostsContainerPref)

View File

@@ -17,6 +17,8 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/mathutil"
"golang.org/x/exp/slices"
)
// UnsupportedError is returned by functions and methods when a particular
@@ -60,9 +62,8 @@ const MaxCmdOutputSize = 64 * 1024
func RunCommand(command string, arguments ...string) (code int, output []byte, err error) {
cmd := exec.Command(command, arguments...)
out, err := cmd.Output()
if len(out) > MaxCmdOutputSize {
out = out[:MaxCmdOutputSize]
}
out = out[:mathutil.Min(len(out), MaxCmdOutputSize)]
if err != nil {
if eerr := new(exec.ExitError); errors.As(err, &eerr) {
@@ -136,14 +137,12 @@ func parsePSOutput(r io.Reader, cmdName string, ignore []int) (largest, instNum
}
cur, aerr := strconv.Atoi(fields[0])
if aerr != nil || cur < 0 || intIn(cur, ignore) {
if aerr != nil || cur < 0 || slices.Contains(ignore, cur) {
continue
}
instNum++
if cur > largest {
largest = cur
}
largest = mathutil.Max(largest, cur)
}
if err = s.Err(); err != nil {
return 0, 0, fmt.Errorf("scanning stdout: %w", err)
@@ -152,17 +151,6 @@ func parsePSOutput(r io.Reader, cmdName string, ignore []int) (largest, instNum
return largest, instNum, nil
}
// intIn returns true if nums contains n.
func intIn(n int, nums []int) (ok bool) {
for _, nn := range nums {
if n == nn {
return true
}
}
return false
}
// IsOpenWrt returns true if host OS is OpenWrt.
func IsOpenWrt() (ok bool) {
return isOpenWrt()

View File

@@ -224,6 +224,9 @@ type ServerConfig struct {
// resolving PTR queries for local addresses.
LocalPTRResolvers []string
// DNS64Prefixes is a slice of NAT64 prefixes to be used for DNS64.
DNS64Prefixes []string
// ResolveClients signals if the RDNS should resolve clients' addresses.
ResolveClients bool
@@ -231,6 +234,9 @@ type ServerConfig struct {
// locally-served networks should be resolved via private PTR resolvers.
UsePrivateRDNS bool
// UseDNS64 defines if DNS64 is enabled for incoming requests.
UseDNS64 bool
// ServeHTTP3 defines if HTTP/3 is be allowed for incoming requests.
ServeHTTP3 bool

View File

@@ -28,9 +28,10 @@ type dnsContext struct {
// response is modified by filters.
origResp *dns.Msg
// unreversedReqIP stores an IP address obtained from PTR request if it
// parsed successfully and belongs to one of locally-served IP ranges as per
// RFC 6303.
// unreversedReqIP stores an IP address obtained from a PTR request if it
// was parsed successfully and belongs to one of the locally served IP
// ranges. It is also filled with unmapped version of the address if it's
// within DNS64 prefixes.
unreversedReqIP net.IP
// err is the error returned from a processing function.
@@ -57,7 +58,7 @@ type dnsContext struct {
// responseAD shows if the response had the AD bit set.
responseAD bool
// isLocalClient shows if client's IP address is from locally-served
// isLocalClient shows if client's IP address is from locally served
// network.
isLocalClient bool
}
@@ -133,8 +134,8 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, pctx *proxy.DNSContext) error
return nil
}
// processRecursion checks the incoming request and halts it's handling if s
// have tried to resolve it recently.
// processRecursion checks the incoming request and halts its handling by
// answering NXDOMAIN if s has tried to resolve it recently.
func (s *Server) processRecursion(dctx *dnsContext) (rc resultCode) {
pctx := dctx.proxyCtx
@@ -349,8 +350,8 @@ func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
return resp
}
// processDetermineLocal determines if the client's IP address is from
// locally-served network and saves the result into the context.
// processDetermineLocal determines if the client's IP address is from locally
// served network and saves the result into the context.
func (s *Server) processDetermineLocal(dctx *dnsContext) (rc resultCode) {
rc = resultCodeSuccess
@@ -377,7 +378,8 @@ func (s *Server) dhcpHostToIP(host string) (ip netip.Addr, ok bool) {
}
// processDHCPHosts respond to A requests if the target hostname is known to
// the server.
// the server. It responds with a mapped IP address if the DNS64 is enabled and
// the request is for AAAA.
//
// TODO(a.garipov): Adapt to AAAA as well.
func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
@@ -409,20 +411,34 @@ func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
log.Debug("dnsforward: dhcp record for %q is %s", reqHost, ip)
resp := s.makeResponse(req)
if q.Qtype == dns.TypeA {
switch q.Qtype {
case dns.TypeA:
a := &dns.A{
Hdr: s.hdr(req, dns.TypeA),
A: ip.AsSlice(),
}
resp.Answer = append(resp.Answer, a)
case dns.TypeAAAA:
if len(s.dns64Prefs) > 0 {
// Respond with DNS64-mapped address for IPv4 host if DNS64 is
// enabled.
aaaa := &dns.AAAA{
Hdr: s.hdr(req, dns.TypeAAAA),
AAAA: s.mapDNS64(ip),
}
resp.Answer = append(resp.Answer, aaaa)
}
default:
// Go on.
}
dctx.proxyCtx.Res = resp
return resultCodeSuccess
}
// processRestrictLocal responds with NXDOMAIN to PTR requests for IP addresses
// in locally-served network from external clients.
// in locally served network from external clients.
func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
pctx := dctx.proxyCtx
req := pctx.Req
@@ -452,15 +468,24 @@ func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
return resultCodeSuccess
}
// Restrict an access to local addresses for external clients. We also
// assume that all the DHCP leases we give are locally-served or at least
// don't need to be accessible externally.
if !s.privateNets.Contains(ip) {
log.Debug("dnsforward: addr %s is not from locally-served network", ip)
if s.shouldStripDNS64(ip) {
// Strip the prefix from the address to get the original IPv4.
ip = ip[nat64PrefixLen:]
// Treat a DNS64-prefixed address as a locally served one since those
// queries should never be sent to the global DNS.
dctx.unreversedReqIP = ip
}
// Restrict an access to local addresses for external clients. We also
// assume that all the DHCP leases we give are locally served or at least
// shouldn't be accessible externally.
if !s.privateNets.Contains(ip) {
return resultCodeSuccess
}
log.Debug("dnsforward: addr %s is from locally served network", ip)
if !dctx.isLocalClient {
log.Debug("dnsforward: %q requests an internal ip", pctx.Addr)
pctx.Res = s.genNXDomain(req)
@@ -473,7 +498,7 @@ func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
dctx.unreversedReqIP = ip
// There is no need to filter request from external addresses since this
// code is only executed when the request is for locally-served ARPA
// code is only executed when the request is for locally served ARPA
// hostname so disable redundant filters.
dctx.setts.ParentalEnabled = false
dctx.setts.SafeBrowsingEnabled = false
@@ -508,7 +533,7 @@ func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) {
return resultCodeSuccess
}
// TODO(a.garipov): Remove once we switch to netip.Addr more fully.
// TODO(a.garipov): Remove once we switch to [netip.Addr] more fully.
ipAddr, err := netutil.IPToAddrNoMapped(ip)
if err != nil {
log.Debug("dnsforward: bad reverse ip %v from dhcp: %s", ip, err)
@@ -556,10 +581,6 @@ func (s *Server) processLocalPTR(dctx *dnsContext) (rc resultCode) {
s.serverLock.RLock()
defer s.serverLock.RUnlock()
if !s.privateNets.Contains(ip) {
return resultCodeSuccess
}
if s.conf.UsePrivateRDNS {
s.recDetector.add(*pctx.Req)
if err := s.localResolvers.Resolve(pctx); err != nil {
@@ -636,9 +657,8 @@ func (s *Server) processUpstream(dctx *dnsContext) (rc resultCode) {
origReqAD := false
if s.conf.EnableDNSSEC {
if req.AuthenticatedData {
origReqAD = true
} else {
origReqAD = req.AuthenticatedData
if !req.AuthenticatedData {
req.AuthenticatedData = true
}
}
@@ -655,6 +675,10 @@ func (s *Server) processUpstream(dctx *dnsContext) (rc resultCode) {
return resultCodeError
}
if s.performDNS64(prx, dctx) == resultCodeError {
return resultCodeError
}
dctx.responseFromUpstream = true
dctx.responseAD = pctx.Res.AuthenticatedData

View File

@@ -0,0 +1,345 @@
package dnsforward
import (
"fmt"
"net"
"net/netip"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/mathutil"
"github.com/AdguardTeam/golibs/netutil"
"github.com/miekg/dns"
)
const (
// maxNAT64PrefixBitLen is the maximum length of a NAT64 prefix in bits.
// See https://datatracker.ietf.org/doc/html/rfc6147#section-5.2.
maxNAT64PrefixBitLen = 96
// nat64PrefixLen is the length of a NAT64 prefix in bytes.
nat64PrefixLen = net.IPv6len - net.IPv4len
// maxDNS64SynTTL is the maximum TTL for synthesized DNS64 responses with no
// SOA records in seconds.
//
// If the SOA RR was not delivered with the negative response to the AAAA
// query, then the DNS64 SHOULD use the TTL of the original A RR or 600
// seconds, whichever is shorter.
//
// See https://datatracker.ietf.org/doc/html/rfc6147#section-5.1.7.
maxDNS64SynTTL uint32 = 600
)
// setupDNS64 initializes DNS64 settings, the NAT64 prefixes in particular. If
// the DNS64 feature is enabled and no prefixes are configured, the default
// Well-Known Prefix is used, just like Section 5.2 of RFC 6147 prescribes. Any
// configured set of prefixes discards the default Well-Known prefix unless it
// is specified explicitly. Each prefix also validated to be a valid IPv6
// CIDR with a maximum length of 96 bits. The first specified prefix is then
// used to synthesize AAAA records.
func (s *Server) setupDNS64() (err error) {
if !s.conf.UseDNS64 {
return nil
}
l := len(s.conf.DNS64Prefixes)
if l == 0 {
s.dns64Prefs = []netip.Prefix{dns64WellKnownPref}
return nil
}
prefs := make([]netip.Prefix, 0, l)
for i, pref := range s.conf.DNS64Prefixes {
var p netip.Prefix
p, err = netip.ParsePrefix(pref)
if err != nil {
return fmt.Errorf("prefix at index %d: %w", i, err)
}
addr := p.Addr()
if !addr.Is6() {
return fmt.Errorf("prefix at index %d: %q is not an IPv6 prefix", i, pref)
}
if p.Bits() > maxNAT64PrefixBitLen {
return fmt.Errorf("prefix at index %d: %q is too long for DNS64", i, pref)
}
prefs = append(prefs, p.Masked())
}
s.dns64Prefs = prefs
return nil
}
// checkDNS64 checks if DNS64 should be performed. It returns a DNS64 request
// to resolve or nil if DNS64 is not desired. It also filters resp to not
// contain any NAT64 excluded addresses in the answer section, if needed. Both
// req and resp must not be nil.
//
// See https://datatracker.ietf.org/doc/html/rfc6147.
func (s *Server) checkDNS64(req, resp *dns.Msg) (dns64Req *dns.Msg) {
if len(s.dns64Prefs) == 0 {
return nil
}
q := req.Question[0]
if q.Qtype != dns.TypeAAAA || q.Qclass != dns.ClassINET {
// DNS64 operation for classes other than IN is undefined, and a DNS64
// MUST behave as though no DNS64 function is configured.
return nil
}
rcode := resp.Rcode
if rcode == dns.RcodeNameError {
// A result with RCODE=3 (Name Error) is handled according to normal DNS
// operation (which is normally to return the error to the client).
return nil
}
if rcode == dns.RcodeSuccess {
// If resolver receives an answer with at least one AAAA record
// containing an address outside any of the excluded range(s), then it
// by default SHOULD build an answer section for a response including
// only the AAAA record(s) that do not contain any of the addresses
// inside the excluded ranges.
var hasAnswers bool
if resp.Answer, hasAnswers = s.filterNAT64Answers(resp.Answer); hasAnswers {
return nil
}
// Any other RCODE is treated as though the RCODE were 0 and the answer
// section were empty.
}
return &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: dns.Id(),
RecursionDesired: req.RecursionDesired,
AuthenticatedData: req.AuthenticatedData,
CheckingDisabled: req.CheckingDisabled,
},
Question: []dns.Question{{
Name: req.Question[0].Name,
Qtype: dns.TypeA,
Qclass: dns.ClassINET,
}},
}
}
// filterNAT64Answers filters out AAAA records that are within one of NAT64
// exclusion prefixes. hasAnswers is true if the filtered slice contains at
// least a single AAAA answer not within the prefixes or a CNAME.
func (s *Server) filterNAT64Answers(rrs []dns.RR) (filtered []dns.RR, hasAnswers bool) {
filtered = make([]dns.RR, 0, len(rrs))
for _, ans := range rrs {
switch ans := ans.(type) {
case *dns.AAAA:
addr, err := netutil.IPToAddrNoMapped(ans.AAAA)
if err != nil {
log.Error("dnsforward: bad AAAA record: %s", err)
continue
}
if s.withinDNS64(addr) {
// Filter the record.
continue
}
filtered, hasAnswers = append(filtered, ans), true
case *dns.CNAME, *dns.DNAME:
// If the response contains a CNAME or a DNAME, then the CNAME or
// DNAME chain is followed until the first terminating A or AAAA
// record is reached.
//
// Just treat CNAME and DNAME responses as passable answers since
// AdGuard Home doesn't follow any of these chains except the
// dnsrewrite-defined ones.
filtered, hasAnswers = append(filtered, ans), true
default:
filtered = append(filtered, ans)
}
}
return filtered, hasAnswers
}
// synthDNS64 synthesizes a DNS64 response using the original response as a
// basis and modifying it with data from resp. It returns true if the response
// was actually modified.
func (s *Server) synthDNS64(origReq, origResp, resp *dns.Msg) (ok bool) {
if len(resp.Answer) == 0 {
// If there is an empty answer, then the DNS64 responds to the original
// querying client with the answer the DNS64 received to the original
// (initiator's) query.
return false
}
// The Time to Live (TTL) field is set to the minimum of the TTL of the
// original A RR and the SOA RR for the queried domain. If the original
// response contains no SOA records, the minimum of the TTL of the original
// A RR and [maxDNS64SynTTL] should be used. See [maxDNS64SynTTL].
soaTTL := maxDNS64SynTTL
for _, rr := range origResp.Ns {
if hdr := rr.Header(); hdr.Rrtype == dns.TypeSOA && hdr.Name == origReq.Question[0].Name {
soaTTL = hdr.Ttl
break
}
}
newAns := make([]dns.RR, 0, len(resp.Answer))
for _, ans := range resp.Answer {
rr := s.synthRR(ans, soaTTL)
if rr == nil {
// The error should have already been logged.
return false
}
newAns = append(newAns, rr)
}
origResp.Answer = newAns
origResp.Ns = resp.Ns
origResp.Extra = resp.Extra
return true
}
// dns64WellKnownPref is the default prefix to use in an algorithmic mapping for
// DNS64. See https://datatracker.ietf.org/doc/html/rfc6052#section-2.1.
var dns64WellKnownPref = netip.MustParsePrefix("64:ff9b::/96")
// withinDNS64 checks if ip is within one of the configured DNS64 prefixes.
//
// TODO(e.burkov): We actually using bytes of only the first prefix from the
// set to construct the answer, so consider using some implementation of a
// prefix set for the rest.
func (s *Server) withinDNS64(ip netip.Addr) (ok bool) {
for _, n := range s.dns64Prefs {
if n.Contains(ip) {
return true
}
}
return false
}
// shouldStripDNS64 returns true if DNS64 is enabled and ip has either one of
// custom DNS64 prefixes or the Well-Known one. This is intended to be used
// with PTR requests.
//
// The requirement is to match any Pref64::/n used at the site, and not merely
// the locally configured Pref64::/n. This is because end clients could ask for
// a PTR record matching an address received through a different (site-provided)
// DNS64.
//
// See https://datatracker.ietf.org/doc/html/rfc6147#section-5.3.1.
func (s *Server) shouldStripDNS64(ip net.IP) (ok bool) {
if len(s.dns64Prefs) == 0 {
return false
}
addr, err := netutil.IPToAddr(ip, netutil.AddrFamilyIPv6)
if err != nil {
return false
}
switch {
case s.withinDNS64(addr):
log.Debug("dnsforward: %s is within DNS64 custom prefix set", ip)
case dns64WellKnownPref.Contains(addr):
log.Debug("dnsforward: %s is within DNS64 well-known prefix", ip)
default:
return false
}
return true
}
// mapDNS64 maps ip to IPv6 address using configured DNS64 prefix. ip must be a
// valid IPv4. It panics, if there are no configured DNS64 prefixes, because
// synthesis should not be performed unless DNS64 function enabled.
func (s *Server) mapDNS64(ip netip.Addr) (mapped net.IP) {
// Don't mask the address here since it should have already been masked on
// initialization stage.
pref := s.dns64Prefs[0].Addr().As16()
ipData := ip.As4()
mapped = make(net.IP, net.IPv6len)
copy(mapped[:nat64PrefixLen], pref[:])
copy(mapped[nat64PrefixLen:], ipData[:])
return mapped
}
// performDNS64 processes the current state of dctx assuming that it has already
// been tried to resolve, checks if it contains any acceptable response, and if
// it doesn't, performs DNS64 request and the following synthesis. It returns
// the [resultCodeError] if there was an error set to dctx.
func (s *Server) performDNS64(prx *proxy.Proxy, dctx *dnsContext) (rc resultCode) {
pctx := dctx.proxyCtx
req := pctx.Req
dns64Req := s.checkDNS64(req, pctx.Res)
if dns64Req == nil {
return resultCodeSuccess
}
log.Debug("dnsforward: received an empty AAAA response, checking DNS64")
origReq := pctx.Req
origResp := pctx.Res
origUps := pctx.Upstream
pctx.Req = dns64Req
defer func() { pctx.Req = origReq }()
if dctx.err = prx.Resolve(pctx); dctx.err != nil {
return resultCodeError
}
dns64Resp := pctx.Res
pctx.Res = origResp
if dns64Resp != nil && s.synthDNS64(origReq, pctx.Res, dns64Resp) {
log.Debug("dnsforward: synthesized AAAA response for %q", origReq.Question[0].Name)
} else {
pctx.Upstream = origUps
}
return resultCodeSuccess
}
// synthRR synthesizes a DNS64 resource record in compliance with RFC 6147. If
// rr is not an A record, it's returned as is. A records are modified to become
// a DNS64-synthesized AAAA records, and the TTL is set according to the
// original TTL of a record and soaTTL. It returns nil on invalid A records.
func (s *Server) synthRR(rr dns.RR, soaTTL uint32) (result dns.RR) {
aResp, ok := rr.(*dns.A)
if !ok {
return rr
}
addr, err := netutil.IPToAddr(aResp.A, netutil.AddrFamilyIPv4)
if err != nil {
log.Error("dnsforward: bad A record: %s", err)
return nil
}
aaaa := &dns.AAAA{
Hdr: dns.RR_Header{
Name: aResp.Hdr.Name,
Rrtype: dns.TypeAAAA,
Class: aResp.Hdr.Class,
Ttl: mathutil.Min(aResp.Hdr.Ttl, soaTTL),
},
AAAA: s.mapDNS64(addr),
}
return aaaa
}

View File

@@ -0,0 +1,290 @@
package dnsforward
import (
"net"
"testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/miekg/dns"
"github.com/stretchr/testify/require"
)
// newRR is a helper that creates a new dns.RR with the given name, qtype, ttl
// and value. It fails the test if the qtype is not supported or the type of
// value doesn't match the qtype.
func newRR(t *testing.T, name string, qtype uint16, ttl uint32, val any) (rr dns.RR) {
t.Helper()
switch qtype {
case dns.TypeA:
rr = &dns.A{A: testutil.RequireTypeAssert[net.IP](t, val)}
case dns.TypeAAAA:
rr = &dns.AAAA{AAAA: testutil.RequireTypeAssert[net.IP](t, val)}
case dns.TypeCNAME:
rr = &dns.CNAME{Target: testutil.RequireTypeAssert[string](t, val)}
case dns.TypeSOA:
rr = &dns.SOA{
Ns: "ns." + name,
Mbox: "hostmaster." + name,
Serial: 1,
Refresh: 1,
Retry: 1,
Expire: 1,
Minttl: 1,
}
case dns.TypePTR:
rr = &dns.PTR{Ptr: testutil.RequireTypeAssert[string](t, val)}
default:
t.Fatalf("unsupported qtype: %d", qtype)
}
*rr.Header() = dns.RR_Header{
Name: name,
Rrtype: qtype,
Class: dns.ClassINET,
Ttl: ttl,
}
return rr
}
func TestServer_HandleDNSRequest_dns64(t *testing.T) {
const (
ipv4Domain = "ipv4.only."
ipv6Domain = "ipv6.only."
soaDomain = "ipv4.soa."
mappedDomain = "filterable.ipv6."
anotherDomain = "another.domain."
pointedDomain = "local1234.ipv4."
globDomain = "real1234.ipv4."
)
someIPv4 := net.IP{1, 2, 3, 4}
someIPv6 := net.IP{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
mappedIPv6 := net.ParseIP("64:ff9b::102:304")
ptr64Domain, err := netutil.IPToReversedAddr(mappedIPv6)
require.NoError(t, err)
ptr64Domain = dns.Fqdn(ptr64Domain)
ptrGlobDomain, err := netutil.IPToReversedAddr(someIPv4)
require.NoError(t, err)
ptrGlobDomain = dns.Fqdn(ptrGlobDomain)
const (
sectionAnswer = iota
sectionAuthority
sectionAdditional
sectionsNum
)
// answerMap is a convenience alias for describing the upstream response for
// a given question type.
type answerMap = map[uint16][sectionsNum][]dns.RR
pt := testutil.PanicT{}
newUps := func(answers answerMap) (u upstream.Upstream) {
return aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
q := req.Question[0]
require.Contains(pt, answers, q.Qtype)
answer := answers[q.Qtype]
resp = (&dns.Msg{}).SetReply(req)
resp.Answer = answer[sectionAnswer]
resp.Ns = answer[sectionAuthority]
resp.Extra = answer[sectionAdditional]
return resp, nil
})
}
testCases := []struct {
name string
qname string
upsAns answerMap
wantAns []dns.RR
qtype uint16
}{{
name: "simple_a",
qname: ipv4Domain,
upsAns: answerMap{
dns.TypeA: {
sectionAnswer: {newRR(t, ipv4Domain, dns.TypeA, 3600, someIPv4)},
},
dns.TypeAAAA: {},
},
wantAns: []dns.RR{&dns.A{
Hdr: dns.RR_Header{
Name: ipv4Domain,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 3600,
Rdlength: 4,
},
A: someIPv4,
}},
qtype: dns.TypeA,
}, {
name: "simple_aaaa",
qname: ipv6Domain,
upsAns: answerMap{
dns.TypeA: {},
dns.TypeAAAA: {
sectionAnswer: {newRR(t, ipv6Domain, dns.TypeAAAA, 3600, someIPv6)},
},
},
wantAns: []dns.RR{&dns.AAAA{
Hdr: dns.RR_Header{
Name: ipv6Domain,
Rrtype: dns.TypeAAAA,
Class: dns.ClassINET,
Ttl: 3600,
Rdlength: 16,
},
AAAA: someIPv6,
}},
qtype: dns.TypeAAAA,
}, {
name: "actual_dns64",
qname: ipv4Domain,
upsAns: answerMap{
dns.TypeA: {
sectionAnswer: {newRR(t, ipv4Domain, dns.TypeA, 3600, someIPv4)},
},
dns.TypeAAAA: {},
},
wantAns: []dns.RR{&dns.AAAA{
Hdr: dns.RR_Header{
Name: ipv4Domain,
Rrtype: dns.TypeAAAA,
Class: dns.ClassINET,
Ttl: maxDNS64SynTTL,
Rdlength: 16,
},
AAAA: mappedIPv6,
}},
qtype: dns.TypeAAAA,
}, {
name: "actual_dns64_soattl",
qname: soaDomain,
upsAns: answerMap{
dns.TypeA: {
sectionAnswer: {newRR(t, soaDomain, dns.TypeA, 3600, someIPv4)},
},
dns.TypeAAAA: {
sectionAuthority: {newRR(t, soaDomain, dns.TypeSOA, maxDNS64SynTTL+50, nil)},
},
},
wantAns: []dns.RR{&dns.AAAA{
Hdr: dns.RR_Header{
Name: soaDomain,
Rrtype: dns.TypeAAAA,
Class: dns.ClassINET,
Ttl: maxDNS64SynTTL + 50,
Rdlength: 16,
},
AAAA: mappedIPv6,
}},
qtype: dns.TypeAAAA,
}, {
name: "filtered",
qname: mappedDomain,
upsAns: answerMap{
dns.TypeA: {},
dns.TypeAAAA: {
sectionAnswer: {
newRR(t, mappedDomain, dns.TypeAAAA, 3600, net.ParseIP("64:ff9b::506:708")),
newRR(t, mappedDomain, dns.TypeCNAME, 3600, anotherDomain),
},
},
},
wantAns: []dns.RR{&dns.CNAME{
Hdr: dns.RR_Header{
Name: mappedDomain,
Rrtype: dns.TypeCNAME,
Class: dns.ClassINET,
Ttl: 3600,
Rdlength: 16,
},
Target: anotherDomain,
}},
qtype: dns.TypeAAAA,
}, {
name: "ptr",
qname: ptr64Domain,
upsAns: nil,
wantAns: []dns.RR{&dns.PTR{
Hdr: dns.RR_Header{
Name: ptr64Domain,
Rrtype: dns.TypePTR,
Class: dns.ClassINET,
Ttl: 3600,
Rdlength: 16,
},
Ptr: pointedDomain,
}},
qtype: dns.TypePTR,
}, {
name: "ptr_glob",
qname: ptrGlobDomain,
upsAns: answerMap{
dns.TypePTR: {
sectionAnswer: {newRR(t, ptrGlobDomain, dns.TypePTR, 3600, globDomain)},
},
},
wantAns: []dns.RR{&dns.PTR{
Hdr: dns.RR_Header{
Name: ptrGlobDomain,
Rrtype: dns.TypePTR,
Class: dns.ClassINET,
Ttl: 3600,
Rdlength: 15,
},
Ptr: globDomain,
}},
qtype: dns.TypePTR,
}}
localRR := newRR(t, ptr64Domain, dns.TypePTR, 3600, pointedDomain)
localUps := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
require.Equal(pt, req.Question[0].Name, ptr64Domain)
resp = (&dns.Msg{}).SetReply(req)
resp.Answer = []dns.RR{localRR}
return resp, nil
})
s := createTestServer(t, &filtering.Config{}, ServerConfig{
UDPListenAddrs: []*net.UDPAddr{{}},
TCPListenAddrs: []*net.TCPAddr{{}},
UseDNS64: true,
}, localUps)
client := &dns.Client{
Net: "tcp",
Timeout: 1 * time.Second,
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newUps(tc.upsAns)}
startDeferStop(t, s)
req := (&dns.Msg{}).SetQuestion(tc.qname, tc.qtype)
resp, _, excErr := client.Exchange(req, s.dnsProxy.Addr(proxy.ProtoTCP).String())
require.NoError(t, excErr)
require.Equal(t, tc.wantAns, resp.Answer)
})
}
}

View File

@@ -82,6 +82,9 @@ type Server struct {
sysResolvers aghnet.SystemResolvers
recDetector *recursionDetector
// dns64Prefix is the set of NAT64 prefixes used for DNS64 handling.
dns64Prefs []netip.Prefix
// anonymizer masks the client's IP addresses if needed.
anonymizer *aghnet.IPMut
@@ -488,9 +491,11 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
return fmt.Errorf("preparing access: %w", err)
}
if !webRegistered && s.conf.HTTPRegister != nil {
webRegistered = true
s.registerHandlers()
s.registerHandlers()
err = s.setupDNS64()
if err != nil {
return fmt.Errorf("preparing DNS64: %w", err)
}
s.dnsProxy = &proxy.Proxy{Config: proxyConfig}

View File

@@ -712,6 +712,10 @@ func (s *Server) handleDoH(w http.ResponseWriter, r *http.Request) {
}
func (s *Server) registerHandlers() {
if webRegistered || s.conf.HTTPRegister == nil {
return
}
s.conf.HTTPRegister(http.MethodGet, "/control/dns_info", s.handleGetConfig)
s.conf.HTTPRegister(http.MethodPost, "/control/dns_config", s.handleSetConfig)
s.conf.HTTPRegister(http.MethodPost, "/control/test_upstream_dns", s.handleTestUpstreamDNS)
@@ -730,4 +734,6 @@ func (s *Server) registerHandlers() {
// See also https://github.com/AdguardTeam/AdGuardHome/issues/2628.
s.conf.HTTPRegister("", "/dns-query", s.handleDoH)
s.conf.HTTPRegister("", "/dns-query/", s.handleDoH)
webRegistered = true
}

View File

@@ -22,6 +22,7 @@ import (
"github.com/AdguardTeam/golibs/cache"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/mathutil"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/AdguardTeam/urlfilter"
"github.com/AdguardTeam/urlfilter/filterlist"
@@ -287,12 +288,7 @@ func (r Reason) In(reasons ...Reason) (ok bool) { return slices.Contains(reasons
// SetEnabled sets the status of the *DNSFilter.
func (d *DNSFilter) SetEnabled(enabled bool) {
var i int32
if enabled {
i = 1
}
atomic.StoreUint32(&d.enabled, uint32(i))
atomic.StoreUint32(&d.enabled, mathutil.BoolToNumber[uint32](enabled))
}
// GetConfig - get configuration

View File

@@ -9,6 +9,7 @@ import (
"strings"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/mathutil"
"github.com/miekg/dns"
"golang.org/x/exp/slices"
)
@@ -201,7 +202,7 @@ func findRewrites(
if isWildcard(r.Domain) {
// Don't use rewrites[:0], because we need to return at least one
// item here.
rewrites = rewrites[:max(1, i)]
rewrites = rewrites[:mathutil.Max(1, i)]
break
}
@@ -209,11 +210,3 @@ func findRewrites(
return rewrites, matched
}
func max(a, b int) int {
if a > b {
return a
}
return b
}

View File

@@ -10,6 +10,7 @@ import (
"net/http"
"sort"
"strings"
"sync"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
@@ -109,8 +110,8 @@ func (c *sbCtx) getCached() int {
now := time.Now().Unix()
hashesToRequest := map[[32]byte]string{}
for k, v := range c.hashToHost {
key := k[0:2]
val := c.cache.Get(key)
// nolint:looppointer // The subsilce is used for a safe cache lookup.
val := c.cache.Get(k[0:2])
if val == nil || now >= int64(binary.BigEndian.Uint32(val)) {
hashesToRequest[k] = v
continue
@@ -185,8 +186,7 @@ func (c *sbCtx) getQuestion() string {
b := &strings.Builder{}
for hash := range c.hashToHost {
// TODO(e.burkov, a.garipov): Find out and document why exactly
// this slice.
// nolint:looppointer // The subsilce is used for safe hex encoding.
stringutil.WriteToBuilder(b, hex.EncodeToString(hash[0:2]), ".")
}
@@ -248,8 +248,8 @@ func (c *sbCtx) storeCache(hashes [][]byte) {
var curData []byte
var prevPrefix []byte
for i, hash := range hashes {
prefix := hash[0:2]
if !bytes.Equal(prefix, prevPrefix) {
// nolint:looppointer // The subsilce is used for a safe comparison.
if !bytes.Equal(hash[0:2], prevPrefix) {
if i != 0 {
c.setCache(prevPrefix, curData)
curData = nil
@@ -264,6 +264,7 @@ func (c *sbCtx) storeCache(hashes [][]byte) {
}
for hash := range c.hashToHost {
// nolint:looppointer // The subsilce is used for a safe cache lookup.
prefix := hash[0:2]
val := c.cache.Get(prefix)
if val == nil {
@@ -369,13 +370,35 @@ func (d *DNSFilter) checkParental(
return check(sctx, res, d.parentalUpstream)
}
// setProtectedBool sets the value of a boolean pointer under a lock. l must
// protect the value under ptr.
//
// TODO(e.burkov): Make it generic?
func setProtectedBool(mu *sync.RWMutex, ptr *bool, val bool) {
mu.Lock()
defer mu.Unlock()
*ptr = val
}
// protectedBool gets the value of a boolean pointer under a read lock. l must
// protect the value under ptr.
//
// TODO(e.burkov): Make it generic?
func protectedBool(mu *sync.RWMutex, ptr *bool) (val bool) {
mu.RLock()
defer mu.RUnlock()
return *ptr
}
func (d *DNSFilter) handleSafeBrowsingEnable(w http.ResponseWriter, r *http.Request) {
d.Config.SafeBrowsingEnabled = true
setProtectedBool(&d.confLock, &d.Config.SafeBrowsingEnabled, true)
d.Config.ConfigModified()
}
func (d *DNSFilter) handleSafeBrowsingDisable(w http.ResponseWriter, r *http.Request) {
d.Config.SafeBrowsingEnabled = false
setProtectedBool(&d.confLock, &d.Config.SafeBrowsingEnabled, false)
d.Config.ConfigModified()
}
@@ -383,19 +406,19 @@ func (d *DNSFilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Requ
resp := &struct {
Enabled bool `json:"enabled"`
}{
Enabled: d.Config.SafeBrowsingEnabled,
Enabled: protectedBool(&d.confLock, &d.Config.SafeBrowsingEnabled),
}
_ = aghhttp.WriteJSONResponse(w, r, resp)
}
func (d *DNSFilter) handleParentalEnable(w http.ResponseWriter, r *http.Request) {
d.Config.ParentalEnabled = true
setProtectedBool(&d.confLock, &d.Config.ParentalEnabled, true)
d.Config.ConfigModified()
}
func (d *DNSFilter) handleParentalDisable(w http.ResponseWriter, r *http.Request) {
d.Config.ParentalEnabled = false
setProtectedBool(&d.confLock, &d.Config.ParentalEnabled, false)
d.Config.ConfigModified()
}
@@ -403,7 +426,7 @@ func (d *DNSFilter) handleParentalStatus(w http.ResponseWriter, r *http.Request)
resp := &struct {
Enabled bool `json:"enabled"`
}{
Enabled: d.Config.ParentalEnabled,
Enabled: protectedBool(&d.confLock, &d.Config.ParentalEnabled),
}
_ = aghhttp.WriteJSONResponse(w, r, resp)

View File

@@ -135,12 +135,12 @@ func (d *DNSFilter) checkSafeSearch(
}
func (d *DNSFilter) handleSafeSearchEnable(w http.ResponseWriter, r *http.Request) {
d.Config.SafeSearchEnabled = true
setProtectedBool(&d.confLock, &d.Config.SafeSearchEnabled, true)
d.Config.ConfigModified()
}
func (d *DNSFilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) {
d.Config.SafeSearchEnabled = false
setProtectedBool(&d.confLock, &d.Config.SafeSearchEnabled, false)
d.Config.ConfigModified()
}
@@ -148,7 +148,7 @@ func (d *DNSFilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Reques
resp := &struct {
Enabled bool `json:"enabled"`
}{
Enabled: d.Config.SafeSearchEnabled,
Enabled: protectedBool(&d.confLock, &d.Config.SafeSearchEnabled),
}
_ = aghhttp.WriteJSONResponse(w, r, resp)

File diff suppressed because it is too large Load Diff

View File

@@ -66,11 +66,12 @@ func (ab *authRateLimiter) check(usrID string) (left time.Duration) {
defer ab.failedAuthsLock.Unlock()
ab.cleanupLocked(now)
return ab.checkLocked(usrID, now)
}
// incLocked increments the number of unsuccessful attempts for attempter with
// ip and updates it's blocking moment if needed. For internal use only.
// usrID and updates it's blocking moment if needed. For internal use only.
func (ab *authRateLimiter) incLocked(usrID string, now time.Time) {
until := now.Add(failedAuthTTL)
var attNum uint = 1

View File

@@ -90,9 +90,6 @@ type configuration struct {
BindHost netip.Addr `yaml:"bind_host"`
// BindPort is the port for the web interface server to listen on.
BindPort int `yaml:"bind_port"`
// BetaBindPort is the port for the new client's web interface server to
// listen on.
BetaBindPort int `yaml:"beta_bind_port"`
// Users are the clients capable for accessing the web interface.
Users []webUser `yaml:"users"`
@@ -187,6 +184,12 @@ type dnsConfig struct {
// for PTR queries for locally-served networks.
LocalPTRResolvers []string `yaml:"local_ptr_upstreams"`
// UseDNS64 defines if DNS64 should be used for incoming requests.
UseDNS64 bool `yaml:"use_dns64"`
// DNS64Prefixes is the list of NAT64 prefixes to be used for DNS64.
DNS64Prefixes []string `yaml:"dns64_prefixes"`
// ServeHTTP3 defines if HTTP/3 is be allowed for incoming requests.
//
// TODO(a.garipov): Add to the UI when HTTP/3 support is no longer
@@ -230,7 +233,6 @@ type tlsConfigSettings struct {
// TODO(a.garipov, e.burkov): This global is awful and must be removed.
var config = &configuration{
BindPort: 3000,
BetaBindPort: 0,
BindHost: netip.IPv4Unspecified(),
AuthAttempts: 5,
AuthBlockMin: 15,
@@ -372,7 +374,7 @@ func parseConfig() (err error) {
}
tcpPorts := aghalg.UniqChecker[tcpPort]{}
addPorts(tcpPorts, tcpPort(config.BindPort), tcpPort(config.BetaBindPort))
addPorts(tcpPorts, tcpPort(config.BindPort))
udpPorts := aghalg.UniqChecker[udpPort]{}
addPorts(udpPorts, udpPort(config.DNS.Port))

View File

@@ -11,7 +11,6 @@ import (
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
"unicode/utf8"
@@ -99,7 +98,7 @@ func (req *checkConfReq) validateWeb(tcpPorts aghalg.UniqChecker[tcpPort]) (err
portInt := req.Web.Port
port := tcpPort(portInt)
addPorts(tcpPorts, tcpPort(config.BetaBindPort), port)
addPorts(tcpPorts, port)
if err = tcpPorts.Validate(); err != nil {
// Reset the value for the port to 1 to make sure that validateDNS
// doesn't throw the same error, unless the same TCP port is set there
@@ -321,7 +320,6 @@ type applyConfigReq struct {
func copyInstallSettings(dst, src *configuration) {
dst.BindHost = src.BindHost
dst.BindPort = src.BindPort
dst.BetaBindPort = src.BetaBindPort
dst.DNS.BindHosts = src.DNS.BindHosts
dst.DNS.Port = src.DNS.Port
}
@@ -472,7 +470,6 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
defer cancel()
shutdownSrv(ctx, web.httpServer)
shutdownSrv(ctx, web.httpServerBeta)
}(shutdownTimeout)
}
@@ -511,191 +508,3 @@ func (web *Web) registerInstallHandlers() {
Context.mux.HandleFunc("/control/install/check_config", preInstall(ensurePOST(web.handleInstallCheckConfig)))
Context.mux.HandleFunc("/control/install/configure", preInstall(ensurePOST(web.handleInstallConfigure)))
}
// checkConfigReqEntBeta is a struct representing new client's config check
// request entry. It supports multiple IP values unlike the checkConfigReqEnt.
//
// TODO(e.burkov): This should removed with the API v1 when the appropriate
// functionality will appear in default checkConfigReqEnt.
type checkConfigReqEntBeta struct {
IP []netip.Addr `json:"ip"`
Port int `json:"port"`
Autofix bool `json:"autofix"`
}
// checkConfigReqBeta is a struct representing new client's config check request
// body. It uses checkConfigReqEntBeta instead of checkConfigReqEnt.
//
// TODO(e.burkov): This should removed with the API v1 when the appropriate
// functionality will appear in default checkConfigReq.
type checkConfigReqBeta struct {
Web checkConfigReqEntBeta `json:"web"`
DNS checkConfigReqEntBeta `json:"dns"`
SetStaticIP bool `json:"set_static_ip"`
}
// handleInstallCheckConfigBeta is a substitution of /install/check_config
// handler for new client.
//
// TODO(e.burkov): This should removed with the API v1 when the appropriate
// functionality will appear in default handleInstallCheckConfig.
func (web *Web) handleInstallCheckConfigBeta(w http.ResponseWriter, r *http.Request) {
reqData := checkConfigReqBeta{}
err := json.NewDecoder(r.Body).Decode(&reqData)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to parse 'check_config' JSON data: %s", err)
return
}
if len(reqData.DNS.IP) == 0 || len(reqData.Web.IP) == 0 {
aghhttp.Error(r, w, http.StatusBadRequest, http.StatusText(http.StatusBadRequest))
return
}
nonBetaReqData := checkConfReq{
Web: checkConfReqEnt{
IP: reqData.Web.IP[0],
Port: reqData.Web.Port,
Autofix: reqData.Web.Autofix,
},
DNS: checkConfReqEnt{
IP: reqData.DNS.IP[0],
Port: reqData.DNS.Port,
Autofix: reqData.DNS.Autofix,
},
SetStaticIP: reqData.SetStaticIP,
}
nonBetaReqBody := &strings.Builder{}
err = json.NewEncoder(nonBetaReqBody).Encode(nonBetaReqData)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "encoding check_config: %s", err)
return
}
body := nonBetaReqBody.String()
r.Body = io.NopCloser(strings.NewReader(body))
r.ContentLength = int64(len(body))
web.handleInstallCheckConfig(w, r)
}
// applyConfigReqEntBeta is a struct representing new client's config setting
// request entry. It supports multiple IP values unlike the applyConfigReqEnt.
//
// TODO(e.burkov): This should removed with the API v1 when the appropriate
// functionality will appear in default applyConfigReqEnt.
type applyConfigReqEntBeta struct {
IP []netip.Addr `json:"ip"`
Port int `json:"port"`
}
// applyConfigReqBeta is a struct representing new client's config setting
// request body. It uses applyConfigReqEntBeta instead of applyConfigReqEnt.
//
// TODO(e.burkov): This should removed with the API v1 when the appropriate
// functionality will appear in default applyConfigReq.
type applyConfigReqBeta struct {
Username string `json:"username"`
Password string `json:"password"`
Web applyConfigReqEntBeta `json:"web"`
DNS applyConfigReqEntBeta `json:"dns"`
}
// handleInstallConfigureBeta is a substitution of /install/configure handler
// for new client.
//
// TODO(e.burkov): This should removed with the API v1 when the appropriate
// functionality will appear in default handleInstallConfigure.
func (web *Web) handleInstallConfigureBeta(w http.ResponseWriter, r *http.Request) {
reqData := applyConfigReqBeta{}
err := json.NewDecoder(r.Body).Decode(&reqData)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "Failed to parse 'check_config' JSON data: %s", err)
return
}
if len(reqData.DNS.IP) == 0 || len(reqData.Web.IP) == 0 {
aghhttp.Error(r, w, http.StatusBadRequest, http.StatusText(http.StatusBadRequest))
return
}
nonBetaReqData := applyConfigReq{
Web: applyConfigReqEnt{
IP: reqData.Web.IP[0],
Port: reqData.Web.Port,
},
DNS: applyConfigReqEnt{
IP: reqData.DNS.IP[0],
Port: reqData.DNS.Port,
},
Username: reqData.Username,
Password: reqData.Password,
}
nonBetaReqBody := &strings.Builder{}
err = json.NewEncoder(nonBetaReqBody).Encode(nonBetaReqData)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "encoding configure: %s", err)
return
}
body := nonBetaReqBody.String()
r.Body = io.NopCloser(strings.NewReader(body))
r.ContentLength = int64(len(body))
web.handleInstallConfigure(w, r)
}
// getAddrsResponseBeta is a struct representing new client's getting addresses
// request body. It uses array of structs instead of map.
//
// TODO(e.burkov): This should removed with the API v1 when the appropriate
// functionality will appear in default firstRunData.
type getAddrsResponseBeta struct {
Interfaces []*aghnet.NetInterface `json:"interfaces"`
WebPort int `json:"web_port"`
DNSPort int `json:"dns_port"`
}
// handleInstallConfigureBeta is a substitution of /install/get_addresses
// handler for new client.
//
// TODO(e.burkov): This should removed with the API v1 when the appropriate
// functionality will appear in default handleInstallGetAddresses.
func (web *Web) handleInstallGetAddressesBeta(w http.ResponseWriter, r *http.Request) {
data := getAddrsResponseBeta{
WebPort: defaultPortHTTP,
DNSPort: defaultPortDNS,
}
ifaces, err := aghnet.GetValidNetInterfacesForWeb()
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "Couldn't get interfaces: %s", err)
return
}
data.Interfaces = ifaces
_ = aghhttp.WriteJSONResponse(w, r, data)
}
// registerBetaInstallHandlers registers the install handlers for new client
// with the structures it supports.
//
// TODO(e.burkov): This should removed with the API v1 when the appropriate
// functionality will appear in default handlers.
func (web *Web) registerBetaInstallHandlers() {
Context.mux.HandleFunc("/control/install/get_addresses_beta", preInstall(ensureGET(web.handleInstallGetAddressesBeta)))
Context.mux.HandleFunc("/control/install/check_config_beta", preInstall(ensurePOST(web.handleInstallCheckConfigBeta)))
Context.mux.HandleFunc("/control/install/configure_beta", preInstall(ensurePOST(web.handleInstallConfigureBeta)))
}

View File

@@ -242,6 +242,8 @@ func generateServerConfig(
ConfigModified: onConfigModified,
HTTPRegister: httpReg,
OnDNSRequest: onDNSRequest,
UseDNS64: config.DNS.UseDNS64,
DNS64Prefixes: config.DNS.DNS64Prefixes,
}
if tlsConf.Enabled {

View File

@@ -148,13 +148,6 @@ func Main(clientBuildFS fs.FS) {
func setupContext(opts options) {
setupContextFlags(opts)
switch version.Channel() {
case version.ChannelEdge, version.ChannelDevelopment:
config.BetaBindPort = 3001
default:
// Go on.
}
Context.tlsRoots = aghtls.SystemRootCAs()
Context.transport = &http.Transport{
DialContext: customDialContext,
@@ -339,7 +332,7 @@ func setupConfig(opts options) (err error) {
if opts.bindPort != 0 {
tcpPorts := aghalg.UniqChecker[tcpPort]{}
addPorts(tcpPorts, tcpPort(opts.bindPort), tcpPort(config.BetaBindPort))
addPorts(tcpPorts, tcpPort(opts.bindPort))
udpPorts := aghalg.UniqChecker[udpPort]{}
addPorts(udpPorts, udpPort(config.DNS.Port))
@@ -376,36 +369,28 @@ func setupConfig(opts options) (err error) {
}
func initWeb(opts options, clientBuildFS fs.FS) (web *Web, err error) {
var clientFS, clientBetaFS fs.FS
var clientFS fs.FS
if opts.localFrontend {
log.Info("warning: using local frontend files")
clientFS = os.DirFS("build/static")
clientBetaFS = os.DirFS("build2/static")
} else {
clientFS, err = fs.Sub(clientBuildFS, "build/static")
if err != nil {
return nil, fmt.Errorf("getting embedded client subdir: %w", err)
}
clientBetaFS, err = fs.Sub(clientBuildFS, "build2/static")
if err != nil {
return nil, fmt.Errorf("getting embedded beta client subdir: %w", err)
}
}
webConf := webConfig{
firstRun: Context.firstRun,
BindHost: config.BindHost,
BindPort: config.BindPort,
BetaBindPort: config.BetaBindPort,
firstRun: Context.firstRun,
BindHost: config.BindHost,
BindPort: config.BindPort,
ReadTimeout: readTimeout,
ReadHeaderTimeout: readHdrTimeout,
WriteTimeout: writeTimeout,
clientFS: clientFS,
clientBetaFS: clientBetaFS,
clientFS: clientFS,
serveHTTP3: config.DNS.ServeHTTP3,
}
@@ -804,23 +789,12 @@ func loadCmdLineOpts() (opts options) {
}
// printWebAddrs prints addresses built from proto, addr, and an appropriate
// port. At least one address is printed with the value of port. If the value
// of betaPort is 0, the second address is not printed. Output example:
// port. At least one address is printed with the value of port. Output
// example:
//
// Go to http://127.0.0.1:80
// Go to http://127.0.0.1:3000 (BETA)
func printWebAddrs(proto, addr string, port, betaPort int) {
const (
hostMsg = "Go to %s://%s"
hostBetaMsg = hostMsg + " (BETA)"
)
log.Printf(hostMsg, proto, netutil.JoinHostPort(addr, port))
if betaPort == 0 {
return
}
log.Printf(hostBetaMsg, proto, netutil.JoinHostPort(addr, config.BetaBindPort))
// go to http://127.0.0.1:80
func printWebAddrs(proto, addr string, port int) {
log.Printf("go to %s://%s", proto, netutil.JoinHostPort(addr, port))
}
// printHTTPAddresses prints the IP addresses which user can use to access the
@@ -838,14 +812,14 @@ func printHTTPAddresses(proto string) {
// TODO(e.burkov): Inspect and perhaps merge with the previous condition.
if proto == aghhttp.SchemeHTTPS && tlsConf.ServerName != "" {
printWebAddrs(proto, tlsConf.ServerName, tlsConf.PortHTTPS, 0)
printWebAddrs(proto, tlsConf.ServerName, tlsConf.PortHTTPS)
return
}
bindhost := config.BindHost
if !bindhost.IsUnspecified() {
printWebAddrs(proto, bindhost.String(), port, config.BetaBindPort)
printWebAddrs(proto, bindhost.String(), port)
return
}
@@ -856,14 +830,14 @@ func printHTTPAddresses(proto string) {
// That's weird, but we'll ignore it.
//
// TODO(e.burkov): Find out when it happens.
printWebAddrs(proto, bindhost.String(), port, config.BetaBindPort)
printWebAddrs(proto, bindhost.String(), port)
return
}
for _, iface := range ifaces {
for _, addr := range iface.Addresses {
printWebAddrs(proto, addr.String(), config.BindPort, config.BetaBindPort)
printWebAddrs(proto, addr.String(), config.BindPort)
}
}
}

View File

@@ -75,18 +75,3 @@ func limitRequestBody(h http.Handler) (limited http.Handler) {
h.ServeHTTP(w, rr)
})
}
// wrapIndexBeta returns handler that deals with new client.
func (web *Web) wrapIndexBeta(http.Handler) (wrapped http.Handler) {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h, pattern := Context.mux.Handler(r)
switch pattern {
case "/":
web.handlerBeta.ServeHTTP(w, r)
case "/install.html":
web.installerBeta.ServeHTTP(w, r)
default:
h.ServeHTTP(w, r)
}
})
}

View File

@@ -301,7 +301,6 @@ func (m *tlsManager) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
if setts.Enabled {
err = validatePorts(
tcpPort(config.BindPort),
tcpPort(config.BetaBindPort),
tcpPort(setts.PortHTTPS),
tcpPort(setts.PortDNSOverTLS),
tcpPort(setts.PortDNSCrypt),
@@ -389,7 +388,6 @@ func (m *tlsManager) handleTLSConfigure(w http.ResponseWriter, r *http.Request)
if req.Enabled {
err = validatePorts(
tcpPort(config.BindPort),
tcpPort(config.BetaBindPort),
tcpPort(req.PortHTTPS),
tcpPort(req.PortDNSOverTLS),
tcpPort(req.PortDNSCrypt),
@@ -464,14 +462,13 @@ func (m *tlsManager) handleTLSConfigure(w http.ResponseWriter, r *http.Request)
// validatePorts validates the uniqueness of TCP and UDP ports for AdGuard Home
// DNS protocols.
func validatePorts(
bindPort, betaBindPort, dohPort, dotPort, dnscryptTCPPort tcpPort,
bindPort, dohPort, dotPort, dnscryptTCPPort tcpPort,
dnsPort, doqPort udpPort,
) (err error) {
tcpPorts := aghalg.UniqChecker[tcpPort]{}
addPorts(
tcpPorts,
tcpPort(bindPort),
tcpPort(betaBindPort),
tcpPort(dohPort),
tcpPort(dotPort),
tcpPort(dnscryptTCPPort),

View File

@@ -33,13 +33,11 @@ const (
)
type webConfig struct {
clientFS fs.FS
clientBetaFS fs.FS
clientFS fs.FS
BindHost netip.Addr
BindPort int
BetaBindPort int
PortHTTPS int
BindHost netip.Addr
BindPort int
PortHTTPS int
// ReadTimeout is an option to pass to http.Server for setting an
// appropriate field.
@@ -81,15 +79,6 @@ type Web struct {
// TODO(a.garipov): Refactor all these servers.
httpServer *http.Server
// httpServerBeta is a server for new client.
httpServerBeta *http.Server
// handlerBeta is the handler for new client.
handlerBeta http.Handler
// installerBeta is the pre-install handler for new client.
installerBeta http.Handler
// httpsServer is the server that handles HTTPS traffic. If it is not nil,
// [Web.http3Server] must also not be nil.
httpsServer httpsServer
@@ -106,20 +95,15 @@ func newWeb(conf *webConfig) (w *Web) {
}
clientFS := http.FileServer(http.FS(conf.clientFS))
betaClientFS := http.FileServer(http.FS(conf.clientBetaFS))
// if not configured, redirect / to /install.html, otherwise redirect /install.html to /
Context.mux.Handle("/", withMiddlewares(clientFS, gziphandler.GzipHandler, optionalAuthHandler, postInstallHandler))
w.handlerBeta = withMiddlewares(betaClientFS, gziphandler.GzipHandler, optionalAuthHandler, postInstallHandler)
// add handlers for /install paths, we only need them when we're not configured yet
if conf.firstRun {
log.Info("This is the first launch of AdGuard Home, redirecting everything to /install.html ")
Context.mux.Handle("/install.html", preInstallHandler(clientFS))
w.installerBeta = preInstallHandler(betaClientFS)
w.registerInstallHandlers()
// This must be removed in API v1.
w.registerBetaInstallHandlers()
} else {
registerControlHandlers()
}
@@ -208,8 +192,6 @@ func (web *Web) Start() {
errs <- web.httpServer.ListenAndServe()
}()
web.startBetaServer(hostStr)
err := <-errs
if !errors.Is(err, http.ErrServerClosed) {
cleanupAlways()
@@ -221,36 +203,6 @@ func (web *Web) Start() {
}
}
// startBetaServer starts the beta HTTP server if necessary.
func (web *Web) startBetaServer(hostStr string) {
if web.conf.BetaBindPort == 0 {
return
}
// Use an h2c handler to support unencrypted HTTP/2, e.g. for proxies.
hdlr := h2c.NewHandler(
withMiddlewares(Context.mux, limitRequestBody, web.wrapIndexBeta),
&http2.Server{},
)
web.httpServerBeta = &http.Server{
ErrorLog: log.StdLog("web: plain: beta", log.DEBUG),
Addr: netutil.JoinHostPort(hostStr, web.conf.BetaBindPort),
Handler: hdlr,
ReadTimeout: web.conf.ReadTimeout,
ReadHeaderTimeout: web.conf.ReadHeaderTimeout,
WriteTimeout: web.conf.WriteTimeout,
}
go func() {
defer log.OnPanic("web: plain: beta")
betaErr := web.httpServerBeta.ListenAndServe()
if betaErr != nil && !errors.Is(betaErr, http.ErrServerClosed) {
log.Error("starting beta http server: %s", betaErr)
}
}()
}
// Close gracefully shuts down the HTTP servers.
func (web *Web) Close(ctx context.Context) {
log.Info("stopping http server...")
@@ -266,7 +218,6 @@ func (web *Web) Close(ctx context.Context) {
shutdownSrv(ctx, web.httpsServer.server)
shutdownSrv3(web.httpsServer.server3)
shutdownSrv(ctx, web.httpServer)
shutdownSrv(ctx, web.httpServerBeta)
log.Info("stopped http server")
}

View File

@@ -5,15 +5,15 @@ go 1.18
require (
github.com/fzipp/gocyclo v0.6.0
github.com/golangci/misspell v0.4.0
github.com/gordonklaus/ineffassign v0.0.0-20220928193011-d2c82e48359b
github.com/kisielk/errcheck v1.6.2
github.com/kyoh86/looppointer v0.1.9
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28
github.com/kisielk/errcheck v1.6.3
github.com/kyoh86/looppointer v0.2.1
github.com/securego/gosec/v2 v2.14.0
golang.org/x/tools v0.2.0
golang.org/x/vuln v0.0.0-20221103225512-4f561ca73b59
golang.org/x/tools v0.5.1-0.20230117180257-8aba49bb5ea2
golang.org/x/vuln v0.0.0-20230130175424-dd534eeddf33
honnef.co/go/tools v0.3.3
mvdan.cc/gofumpt v0.4.0
mvdan.cc/unparam v0.0.0-20220926085101-66de63301820
mvdan.cc/unparam v0.0.0-20230125043941-70a0ce6e7b95
)
require (
@@ -24,10 +24,10 @@ require (
github.com/kyoh86/nolint v0.0.1 // indirect
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/exp v0.0.0-20221106115401-f9659909a136 // indirect
golang.org/x/exp/typeparams v0.0.0-20221106115401-f9659909a136 // indirect
golang.org/x/mod v0.6.0 // indirect
golang.org/x/exp v0.0.0-20230131160201-f062dba9d201 // indirect
golang.org/x/exp/typeparams v0.0.0-20230131160201-f062dba9d201 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/sys v0.4.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

View File

@@ -17,14 +17,14 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI=
github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg=
github.com/gordonklaus/ineffassign v0.0.0-20220928193011-d2c82e48359b h1:TYNAU9lu7ggdAereRq0dzCIDzHu9mNyGLj/hd5PXq8I=
github.com/gordonklaus/ineffassign v0.0.0-20220928193011-d2c82e48359b/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
github.com/kisielk/errcheck v1.6.2 h1:uGQ9xI8/pgc9iOoCe7kWQgRE6SBTrCGmTSf0LrEtY7c=
github.com/kisielk/errcheck v1.6.2/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw=
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 h1:9alfqbrhuD+9fLZ4iaAVwhlp5PEhmnBt7yvK2Oy5C1U=
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
github.com/kisielk/errcheck v1.6.3 h1:dEKh+GLHcWm2oN34nMvDzn1sqI0i0WxPvrgiJA5JuM8=
github.com/kisielk/errcheck v1.6.3/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kyoh86/looppointer v0.1.9 h1:siTt2dqv+pW3y5gvykZXhlVcTnUVMDf11bGlB9GL5PI=
github.com/kyoh86/looppointer v0.1.9/go.mod h1:q358WcM8cMWU+5vzqukvaZtnJi1kw/MpRHQm3xvTrjw=
github.com/kyoh86/looppointer v0.2.1 h1:Jx9fnkBj/JrIryBLMTYNTj9rvc2SrPS98Dg0w7fxdJg=
github.com/kyoh86/looppointer v0.2.1/go.mod h1:q358WcM8cMWU+5vzqukvaZtnJi1kw/MpRHQm3xvTrjw=
github.com/kyoh86/nolint v0.0.1 h1:GjNxDEkVn2wAxKHtP7iNTrRxytRZ1wXxLV5j4XzGfRU=
github.com/kyoh86/nolint v0.0.1/go.mod h1:1ZiZZ7qqrZ9dZegU96phwVcdQOMKIqRzFJL3ewq9gtI=
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA=
@@ -53,22 +53,22 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20221106115401-f9659909a136 h1:Fq7F/w7MAa1KJ5bt2aJ62ihqp9HDcRuyILskkpIAurw=
golang.org/x/exp v0.0.0-20221106115401-f9659909a136/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp/typeparams v0.0.0-20221106115401-f9659909a136 h1:962j4VxUJV3GKI6NxKDI9NjATh+tAixlH+9k9MvHSlU=
golang.org/x/exp/typeparams v0.0.0-20221106115401-f9659909a136/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp v0.0.0-20230131160201-f062dba9d201 h1:BEABXpNXLEz0WxtA+6CQIz2xkg80e+1zrhWyMcq8VzE=
golang.org/x/exp v0.0.0-20230131160201-f062dba9d201/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp/typeparams v0.0.0-20230131160201-f062dba9d201 h1:O1QcdQUR9htWjzzsXVFPX+RJ3n1P/u/5bsQR8dbs5BY=
golang.org/x/exp/typeparams v0.0.0-20230131160201-f062dba9d201/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -83,8 +83,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -96,10 +96,10 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/vuln v0.0.0-20221103225512-4f561ca73b59 h1:eOOJSuIRc2QwKAgX5qOIhUZJAd2LLKSBfk839dv+Clo=
golang.org/x/vuln v0.0.0-20221103225512-4f561ca73b59/go.mod h1:F12iebNzxRMpJsm4W7ape+r/KdnXiSy3VC94WsyCG68=
golang.org/x/tools v0.5.1-0.20230117180257-8aba49bb5ea2 h1:v0FhRDmSCNH/0EurAT6T8KRY4aNuUhz6/WwBMxG+gvQ=
golang.org/x/tools v0.5.1-0.20230117180257-8aba49bb5ea2/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
golang.org/x/vuln v0.0.0-20230130175424-dd534eeddf33 h1:je2aB5nnlseeGvJy5clg6EyC3jjbbCNsRDroC3qQJsA=
golang.org/x/vuln v0.0.0-20230130175424-dd534eeddf33/go.mod h1:cBP4HMKv0X+x96j8IJWCKk0eqpakBmmHjKGSSC0NaYE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -114,5 +114,5 @@ honnef.co/go/tools v0.3.3 h1:oDx7VAwstgpYpb3wv0oxiZlxY+foCpRAwY7Vk6XpAgA=
honnef.co/go/tools v0.3.3/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw=
mvdan.cc/gofumpt v0.4.0 h1:JVf4NN1mIpHogBj7ABpgOyZc65/UUOkKQFkoURsz4MM=
mvdan.cc/gofumpt v0.4.0/go.mod h1:PljLOHDeZqgS8opHRKLzp2It2VBuSdteAgqUfzMTxlQ=
mvdan.cc/unparam v0.0.0-20220926085101-66de63301820 h1:fggBTMFbBz7CMny3mWZphe0B/6D8ILBunvvB1cNNHi8=
mvdan.cc/unparam v0.0.0-20220926085101-66de63301820/go.mod h1:7fKhD/gH+APJ9Y27S2PYO7+oVWtb3XPrw9W5ayxVq2A=
mvdan.cc/unparam v0.0.0-20230125043941-70a0ce6e7b95 h1:n/xhncJPSt0YzfOhnyn41XxUdrWQNgmLBG72FE27Fqw=
mvdan.cc/unparam v0.0.0-20230125043941-70a0ce6e7b95/go.mod h1:2vU506e8nGWodqcci641NLi4im2twWSq4Lod756epHQ=