all: sync with master; upd chlog
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
345
internal/dnsforward/dns64.go
Normal file
345
internal/dnsforward/dns64.go
Normal 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
|
||||
}
|
||||
290
internal/dnsforward/dns64_test.go
Normal file
290
internal/dnsforward/dns64_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
|
||||
@@ -242,6 +242,8 @@ func generateServerConfig(
|
||||
ConfigModified: onConfigModified,
|
||||
HTTPRegister: httpReg,
|
||||
OnDNSRequest: onDNSRequest,
|
||||
UseDNS64: config.DNS.UseDNS64,
|
||||
DNS64Prefixes: config.DNS.DNS64Prefixes,
|
||||
}
|
||||
|
||||
if tlsConf.Enabled {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
Reference in New Issue
Block a user