Improve captive portal handling

This commit is contained in:
Daniel
2020-07-15 08:53:55 +02:00
parent ec637bdce8
commit e369a9484a
13 changed files with 323 additions and 31 deletions

View File

@@ -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
View 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
}

View File

@@ -18,10 +18,12 @@ const (
ServerTypeTCP = "tcp"
ServerTypeDoT = "dot"
ServerTypeDoH = "doh"
ServerTypeEnv = "env"
ServerSourceConfigured = "config"
ServerSourceAssigned = "dhcp"
ServerSourceMDNS = "mdns"
ServerSourceEnv = "env"
)
var (

View File

@@ -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 {

View File

@@ -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
}