Improve captive portal handling
This commit is contained in:
@@ -19,7 +19,7 @@ var (
|
||||
// basic errors
|
||||
|
||||
// ErrNotFound is a basic error that will match all "not found" errors
|
||||
ErrNotFound = errors.New("record does not exist")
|
||||
ErrNotFound = errors.New("record could not be found")
|
||||
// ErrBlocked is basic error that will match all "blocked" errors
|
||||
ErrBlocked = errors.New("query was blocked")
|
||||
// ErrLocalhost is returned to *.localhost queries
|
||||
@@ -30,6 +30,8 @@ var (
|
||||
ErrOffline = errors.New("device is offine")
|
||||
// ErrFailure is returned when the type of failure is unclear
|
||||
ErrFailure = errors.New("query failed")
|
||||
// ErrContinue is returned when the resolver has no answer, and the next resolver should be asked
|
||||
ErrContinue = errors.New("resolver has no answer")
|
||||
|
||||
// detailed errors
|
||||
|
||||
@@ -228,7 +230,7 @@ func resolveAndCache(ctx context.Context, q *Query) (rrCache *RRCache, err error
|
||||
|
||||
// check if we are online
|
||||
if netenv.GetOnlineStatus() == netenv.StatusOffline {
|
||||
if !netenv.IsOnlineStatusTestDomain(q.FQDN) {
|
||||
if !netenv.IsConnectivityDomain(q.FQDN) {
|
||||
log.Tracer(ctx).Debugf("resolver: not resolving %s, device is offline", q.FQDN)
|
||||
// we are offline and this is not an online check query
|
||||
return nil, ErrOffline
|
||||
@@ -260,7 +262,7 @@ resolveLoop:
|
||||
// some resolvers might also block
|
||||
return nil, err
|
||||
case netenv.GetOnlineStatus() == netenv.StatusOffline &&
|
||||
!netenv.IsOnlineStatusTestDomain(q.FQDN):
|
||||
!netenv.IsConnectivityDomain(q.FQDN):
|
||||
log.Tracer(ctx).Debugf("resolver: not resolving %s, device is offline", q.FQDN)
|
||||
// we are offline and this is not an online check query
|
||||
return nil, ErrOffline
|
||||
|
||||
95
resolver/resolver-env.go
Normal file
95
resolver/resolver-env.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portmaster/netenv"
|
||||
"github.com/safing/portmaster/network/netutils"
|
||||
)
|
||||
|
||||
var (
|
||||
envResolver = &Resolver{
|
||||
Server: ServerSourceEnv,
|
||||
ServerType: ServerTypeEnv,
|
||||
ServerIPScope: netutils.SiteLocal,
|
||||
Source: ServerSourceEnv,
|
||||
Conn: &envResolverConn{},
|
||||
}
|
||||
)
|
||||
|
||||
type envResolverConn struct{}
|
||||
|
||||
func (er *envResolverConn) Query(ctx context.Context, q *Query) (*RRCache, error) {
|
||||
// prepping
|
||||
portal := netenv.GetCaptivePortal()
|
||||
|
||||
// check for matching name
|
||||
switch q.FQDN {
|
||||
case netenv.SpecialCaptivePortalDomain:
|
||||
if portal.IP != nil {
|
||||
rr, err := portal.IPasRR()
|
||||
if err != nil {
|
||||
log.Warningf("nameserver: failed to create captive portal response to %s: %s", q.FQDN, err)
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return er.makeRRCache(q, []dns.RR{rr}), nil
|
||||
}
|
||||
return nil, ErrNotFound
|
||||
|
||||
case "router.local.":
|
||||
routers := netenv.Gateways()
|
||||
if len(routers) == 0 {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
records, err := ipsToRRs(q.FQDN, routers)
|
||||
if err != nil {
|
||||
log.Warningf("nameserver: failed to create gateway response to %s: %s", q.FQDN, err)
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return er.makeRRCache(q, records), nil
|
||||
}
|
||||
|
||||
// no match
|
||||
return nil, ErrContinue // continue with next resolver
|
||||
}
|
||||
|
||||
func (er *envResolverConn) makeRRCache(q *Query, answers []dns.RR) *RRCache {
|
||||
q.NoCaching = true // disable caching, as the env always has the data available and more up to date.
|
||||
return &RRCache{
|
||||
Domain: q.FQDN,
|
||||
Question: q.QType,
|
||||
Answer: answers,
|
||||
Server: envResolver.Server,
|
||||
ServerScope: envResolver.ServerIPScope,
|
||||
}
|
||||
}
|
||||
|
||||
func (er *envResolverConn) ReportFailure() {}
|
||||
|
||||
func (er *envResolverConn) IsFailing() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func ipsToRRs(domain string, ips []net.IP) ([]dns.RR, error) {
|
||||
var records []dns.RR
|
||||
var rr dns.RR
|
||||
var err error
|
||||
|
||||
for _, ip := range ips {
|
||||
if ip.To4() != nil {
|
||||
rr, err = dns.NewRR(domain + " 17 IN A " + ip.String())
|
||||
} else {
|
||||
rr, err = dns.NewRR(domain + " 17 IN AAAA " + ip.String())
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create record for %s: %w", ip, err)
|
||||
}
|
||||
records = append(records, rr)
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
@@ -18,10 +18,12 @@ const (
|
||||
ServerTypeTCP = "tcp"
|
||||
ServerTypeDoT = "dot"
|
||||
ServerTypeDoH = "doh"
|
||||
ServerTypeEnv = "env"
|
||||
|
||||
ServerSourceConfigured = "config"
|
||||
ServerSourceAssigned = "dhcp"
|
||||
ServerSourceMDNS = "mdns"
|
||||
ServerSourceEnv = "env"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -24,6 +24,7 @@ type Scope struct {
|
||||
var (
|
||||
globalResolvers []*Resolver // all (global) resolvers
|
||||
localResolvers []*Resolver // all resolvers that are in site-local or link-local IP ranges
|
||||
systemResolvers []*Resolver // all resolvers that were assigned by the system
|
||||
localScopes []*Scope // list of scopes with a list of local resolvers that can resolve the scope
|
||||
activeResolvers map[string]*Resolver // lookup map of all resolvers
|
||||
resolversLock sync.RWMutex
|
||||
@@ -231,7 +232,7 @@ func loadResolvers() {
|
||||
globalResolvers = newResolvers
|
||||
|
||||
// assing resolvers to scopes
|
||||
setLocalAndScopeResolvers(globalResolvers)
|
||||
setScopedResolvers(globalResolvers)
|
||||
|
||||
// set active resolvers (for cache validation)
|
||||
// reset
|
||||
@@ -241,6 +242,7 @@ func loadResolvers() {
|
||||
activeResolvers[resolver.Server] = resolver
|
||||
}
|
||||
activeResolvers[mDNSResolver.Server] = mDNSResolver
|
||||
activeResolvers[envResolver.Server] = envResolver
|
||||
|
||||
// log global resolvers
|
||||
if len(globalResolvers) > 0 {
|
||||
@@ -282,9 +284,10 @@ func loadResolvers() {
|
||||
}
|
||||
}
|
||||
|
||||
func setLocalAndScopeResolvers(resolvers []*Resolver) {
|
||||
func setScopedResolvers(resolvers []*Resolver) {
|
||||
// make list with local resolvers
|
||||
localResolvers = make([]*Resolver, 0)
|
||||
systemResolvers = make([]*Resolver, 0)
|
||||
localScopes = make([]*Scope, 0)
|
||||
|
||||
for _, resolver := range resolvers {
|
||||
@@ -292,6 +295,10 @@ func setLocalAndScopeResolvers(resolvers []*Resolver) {
|
||||
localResolvers = append(localResolvers, resolver)
|
||||
}
|
||||
|
||||
if resolver.Source == "dhcp" {
|
||||
systemResolvers = append(systemResolvers, resolver)
|
||||
}
|
||||
|
||||
if resolver.Search != nil {
|
||||
// add resolver to custom searches
|
||||
for _, search := range resolver.Search {
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/safing/portmaster/netenv"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/safing/portbase/log"
|
||||
)
|
||||
@@ -124,6 +126,13 @@ func GetResolversInScope(ctx context.Context, q *Query) (selected []*Resolver) {
|
||||
// global -> local scopes, global
|
||||
// special -> local scopes, local
|
||||
|
||||
// special connectivity domains
|
||||
if netenv.IsConnectivityDomain(q.FQDN) && len(systemResolvers) > 0 {
|
||||
selected = append(selected, envResolver)
|
||||
selected = append(selected, systemResolvers...) // dhcp assigned resolvers
|
||||
return selected
|
||||
}
|
||||
|
||||
// check local scopes
|
||||
for _, scope := range localScopes {
|
||||
if strings.HasSuffix(q.dotPrefixedFQDN, scope.Domain) {
|
||||
@@ -169,6 +178,8 @@ func GetResolversInScope(ctx context.Context, q *Query) (selected []*Resolver) {
|
||||
|
||||
// check for .local mdns
|
||||
if strings.HasSuffix(q.dotPrefixedFQDN, local) {
|
||||
// add env resolver
|
||||
selected = append(selected, envResolver)
|
||||
// add mdns
|
||||
if err := mDNSResolver.checkCompliance(ctx, q); err == nil {
|
||||
selected = append(selected, mDNSResolver)
|
||||
@@ -255,6 +266,8 @@ func (resolver *Resolver) checkCompliance(_ context.Context, q *Query) error {
|
||||
// compliant
|
||||
case ServerTypeDoH:
|
||||
// compliant
|
||||
case ServerTypeEnv:
|
||||
// compliant (data is sources from local network only and is highly limited)
|
||||
default:
|
||||
return errInsecureProtocol
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user