mirror of
https://github.com/safing/portmaster
synced 2025-09-01 18:19:12 +00:00
Merge pull request #293 from safing/fix/patch-set-3
DNS and other fixes & improvements
This commit is contained in:
commit
06eee6805c
20 changed files with 309 additions and 233 deletions
124
firewall/dns.go
124
firewall/dns.go
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/safing/portbase/database"
|
||||
|
@ -16,9 +15,19 @@ import (
|
|||
"github.com/safing/portmaster/resolver"
|
||||
)
|
||||
|
||||
func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, resolverScope netutils.IPScope, sysResolver bool) ([]dns.RR, []string, int, string) {
|
||||
func filterDNSSection(
|
||||
ctx context.Context,
|
||||
entries []dns.RR,
|
||||
p *profile.LayeredProfile,
|
||||
resolverScope netutils.IPScope,
|
||||
sysResolver bool,
|
||||
) ([]dns.RR, []string, int, string) {
|
||||
|
||||
// Will be filled 1:1 most of the time.
|
||||
goodEntries := make([]dns.RR, 0, len(entries))
|
||||
filteredRecords := make([]string, 0, len(entries))
|
||||
|
||||
// Will stay empty most of the time.
|
||||
var filteredRecords []string
|
||||
|
||||
// keeps track of the number of valid and allowed
|
||||
// A and AAAA records.
|
||||
|
@ -44,13 +53,16 @@ func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, resolverScope
|
|||
switch {
|
||||
case ipScope.IsLocalhost():
|
||||
// No DNS should return localhost addresses
|
||||
filteredRecords = append(filteredRecords, rr.String())
|
||||
filteredRecords = append(filteredRecords, formatRR(rr))
|
||||
interveningOptionKey = profile.CfgOptionRemoveOutOfScopeDNSKey
|
||||
log.Tracer(ctx).Tracef("filter: RR violates resolver scope: %s", formatRR(rr))
|
||||
continue
|
||||
|
||||
case resolverScope.IsGlobal() && ipScope.IsLAN() && !sysResolver:
|
||||
// No global DNS should return LAN addresses
|
||||
filteredRecords = append(filteredRecords, rr.String())
|
||||
filteredRecords = append(filteredRecords, formatRR(rr))
|
||||
interveningOptionKey = profile.CfgOptionRemoveOutOfScopeDNSKey
|
||||
log.Tracer(ctx).Tracef("filter: RR violates resolver scope: %s", formatRR(rr))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
@ -59,16 +71,21 @@ func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, resolverScope
|
|||
// filter by flags
|
||||
switch {
|
||||
case p.BlockScopeInternet() && ipScope.IsGlobal():
|
||||
filteredRecords = append(filteredRecords, rr.String())
|
||||
filteredRecords = append(filteredRecords, formatRR(rr))
|
||||
interveningOptionKey = profile.CfgOptionBlockScopeInternetKey
|
||||
log.Tracer(ctx).Tracef("filter: RR is in blocked scope Internet: %s", formatRR(rr))
|
||||
continue
|
||||
|
||||
case p.BlockScopeLAN() && ipScope.IsLAN():
|
||||
filteredRecords = append(filteredRecords, rr.String())
|
||||
filteredRecords = append(filteredRecords, formatRR(rr))
|
||||
interveningOptionKey = profile.CfgOptionBlockScopeLANKey
|
||||
log.Tracer(ctx).Tracef("filter: RR is in blocked scope LAN: %s", formatRR(rr))
|
||||
continue
|
||||
|
||||
case p.BlockScopeLocal() && ipScope.IsLocalhost():
|
||||
filteredRecords = append(filteredRecords, rr.String())
|
||||
filteredRecords = append(filteredRecords, formatRR(rr))
|
||||
interveningOptionKey = profile.CfgOptionBlockScopeLocalKey
|
||||
log.Tracer(ctx).Tracef("filter: RR is in blocked scope Localhost: %s", formatRR(rr))
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -83,9 +100,13 @@ func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, resolverScope
|
|||
return goodEntries, filteredRecords, allowedAddressRecords, interveningOptionKey
|
||||
}
|
||||
|
||||
func filterDNSResponse(conn *network.Connection, rrCache *resolver.RRCache, sysResolver bool) *resolver.RRCache {
|
||||
p := conn.Process().Profile()
|
||||
|
||||
func filterDNSResponse(
|
||||
ctx context.Context,
|
||||
conn *network.Connection,
|
||||
p *profile.LayeredProfile,
|
||||
rrCache *resolver.RRCache,
|
||||
sysResolver bool,
|
||||
) *resolver.RRCache {
|
||||
// do not modify own queries
|
||||
if conn.Process().Pid == ownPID {
|
||||
return rrCache
|
||||
|
@ -96,20 +117,20 @@ func filterDNSResponse(conn *network.Connection, rrCache *resolver.RRCache, sysR
|
|||
return rrCache
|
||||
}
|
||||
|
||||
// duplicate entry
|
||||
rrCache = rrCache.ShallowCopy()
|
||||
rrCache.FilteredEntries = make([]string, 0)
|
||||
|
||||
var filteredRecords []string
|
||||
var validIPs int
|
||||
var interveningOptionKey string
|
||||
|
||||
rrCache.Answer, filteredRecords, validIPs, interveningOptionKey = filterDNSSection(rrCache.Answer, p, rrCache.Resolver.IPScope, sysResolver)
|
||||
rrCache.FilteredEntries = append(rrCache.FilteredEntries, filteredRecords...)
|
||||
rrCache.Answer, filteredRecords, validIPs, interveningOptionKey = filterDNSSection(ctx, rrCache.Answer, p, rrCache.Resolver.IPScope, sysResolver)
|
||||
if len(filteredRecords) > 0 {
|
||||
rrCache.FilteredEntries = append(rrCache.FilteredEntries, filteredRecords...)
|
||||
}
|
||||
|
||||
// we don't count the valid IPs in the extra section
|
||||
rrCache.Extra, filteredRecords, _, _ = filterDNSSection(rrCache.Extra, p, rrCache.Resolver.IPScope, sysResolver)
|
||||
rrCache.FilteredEntries = append(rrCache.FilteredEntries, filteredRecords...)
|
||||
// Don't count the valid IPs in the extra section.
|
||||
rrCache.Extra, filteredRecords, _, _ = filterDNSSection(ctx, rrCache.Extra, p, rrCache.Resolver.IPScope, sysResolver)
|
||||
if len(filteredRecords) > 0 {
|
||||
rrCache.FilteredEntries = append(rrCache.FilteredEntries, filteredRecords...)
|
||||
}
|
||||
|
||||
if len(rrCache.FilteredEntries) > 0 {
|
||||
rrCache.Filtered = true
|
||||
|
@ -127,34 +148,8 @@ func filterDNSResponse(conn *network.Connection, rrCache *resolver.RRCache, sysR
|
|||
conn.Block("DNS response only contained to-be-blocked IPs", interveningOptionKey)
|
||||
}
|
||||
|
||||
// If all entries are filtered, this could mean that these are broken/bogus resource records.
|
||||
if rrCache.Expired() {
|
||||
// If the entry is expired, force delete it.
|
||||
err := resolver.ResetCachedRecord(rrCache.Domain, rrCache.Question.String())
|
||||
if err != nil && err != database.ErrNotFound {
|
||||
log.Warningf(
|
||||
"filter: failed to delete fully filtered name cache for %s: %s",
|
||||
rrCache.ID(),
|
||||
err,
|
||||
)
|
||||
}
|
||||
} else if rrCache.Expires > time.Now().Add(10*time.Second).Unix() {
|
||||
// Set a low TTL of 10 seconds if TTL is higher than that.
|
||||
rrCache.Expires = time.Now().Add(10 * time.Second).Unix()
|
||||
err := rrCache.Save()
|
||||
if err != nil {
|
||||
log.Debugf(
|
||||
"filter: failed to set shorter TTL on fully filtered name cache for %s: %s",
|
||||
rrCache.ID(),
|
||||
err,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return rrCache
|
||||
}
|
||||
|
||||
log.Infof("filter: filtered DNS replies for %s: %s", conn, strings.Join(rrCache.FilteredEntries, ", "))
|
||||
}
|
||||
|
||||
return rrCache
|
||||
|
@ -168,9 +163,15 @@ func FilterResolvedDNS(
|
|||
q *resolver.Query,
|
||||
rrCache *resolver.RRCache,
|
||||
) *resolver.RRCache {
|
||||
// Check if we have a process and profile.
|
||||
layeredProfile := conn.Process().Profile()
|
||||
if layeredProfile == nil {
|
||||
log.Tracer(ctx).Warning("unknown process or profile")
|
||||
return nil
|
||||
}
|
||||
|
||||
// special grant for connectivity domains
|
||||
if checkConnectivityDomain(ctx, conn, nil) {
|
||||
if checkConnectivityDomain(ctx, conn, layeredProfile, nil) {
|
||||
// returns true if check triggered
|
||||
return rrCache
|
||||
}
|
||||
|
@ -178,33 +179,35 @@ func FilterResolvedDNS(
|
|||
// Only filter criticial things if request comes from the system resolver.
|
||||
sysResolver := conn.Process().IsSystemResolver()
|
||||
|
||||
updatedRR := filterDNSResponse(conn, rrCache, sysResolver)
|
||||
if updatedRR == nil {
|
||||
return nil
|
||||
// Filter dns records and return if the query is blocked.
|
||||
rrCache = filterDNSResponse(ctx, conn, layeredProfile, rrCache, sysResolver)
|
||||
if conn.Verdict == network.VerdictBlock {
|
||||
return rrCache
|
||||
}
|
||||
|
||||
if !sysResolver && mayBlockCNAMEs(ctx, conn) {
|
||||
return nil
|
||||
// Block by CNAMEs.
|
||||
if !sysResolver {
|
||||
mayBlockCNAMEs(ctx, conn, layeredProfile)
|
||||
}
|
||||
|
||||
return updatedRR
|
||||
return rrCache
|
||||
}
|
||||
|
||||
func mayBlockCNAMEs(ctx context.Context, conn *network.Connection) bool {
|
||||
func mayBlockCNAMEs(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile) bool {
|
||||
// if we have CNAMEs and the profile is configured to filter them
|
||||
// we need to re-check the lists and endpoints here
|
||||
if conn.Process().Profile().FilterCNAMEs() {
|
||||
if p.FilterCNAMEs() {
|
||||
conn.Entity.ResetLists()
|
||||
conn.Entity.EnableCNAMECheck(ctx, true)
|
||||
|
||||
result, reason := conn.Process().Profile().MatchEndpoint(ctx, conn.Entity)
|
||||
result, reason := p.MatchEndpoint(ctx, conn.Entity)
|
||||
if result == endpoints.Denied {
|
||||
conn.BlockWithContext(reason.String(), profile.CfgOptionFilterCNAMEKey, reason.Context())
|
||||
return true
|
||||
}
|
||||
|
||||
if result == endpoints.NoMatch {
|
||||
result, reason = conn.Process().Profile().MatchFilterLists(ctx, conn.Entity)
|
||||
result, reason = p.MatchFilterLists(ctx, conn.Entity)
|
||||
if result == endpoints.Denied {
|
||||
conn.BlockWithContext(reason.String(), profile.CfgOptionFilterCNAMEKey, reason.Context())
|
||||
return true
|
||||
|
@ -304,3 +307,8 @@ func UpdateIPsAndCNAMEs(q *resolver.Query, rrCache *resolver.RRCache, conn *netw
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// formatRR is a friendlier alternative to miekg/dns.RR.String().
|
||||
func formatRR(rr dns.RR) string {
|
||||
return strings.ReplaceAll(rr.String(), "\t", " ")
|
||||
}
|
||||
|
|
|
@ -144,15 +144,14 @@ func getConnection(pkt packet.Packet) (*network.Connection, error) {
|
|||
|
||||
// Transform and log result.
|
||||
conn := newConn.(*network.Connection)
|
||||
switch {
|
||||
case created && shared:
|
||||
log.Tracer(pkt.Ctx()).Tracef("filter: created new connection %s (shared)", conn.ID)
|
||||
case created:
|
||||
log.Tracer(pkt.Ctx()).Tracef("filter: created new connection %s", conn.ID)
|
||||
case shared:
|
||||
log.Tracer(pkt.Ctx()).Tracef("filter: assigned connection %s (shared)", conn.ID)
|
||||
default:
|
||||
log.Tracer(pkt.Ctx()).Tracef("filter: assigned connection %s", conn.ID)
|
||||
sharedIndicator := ""
|
||||
if shared {
|
||||
sharedIndicator = " (shared)"
|
||||
}
|
||||
if created {
|
||||
log.Tracer(pkt.Ctx()).Tracef("filter: created new connection %s%s", conn.ID, sharedIndicator)
|
||||
} else {
|
||||
log.Tracer(pkt.Ctx()).Tracef("filter: assigned connection %s%s", conn.ID, sharedIndicator)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
|
@ -307,7 +306,7 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) {
|
|||
log.Tracer(pkt.Ctx()).Trace("filter: handing over to connection-based handler")
|
||||
|
||||
// Check for pre-authenticated port.
|
||||
if localPortIsPreAuthenticated(conn.Entity.Protocol, conn.LocalPort) {
|
||||
if !conn.Inbound && localPortIsPreAuthenticated(conn.Entity.Protocol, conn.LocalPort) {
|
||||
// Approve connection.
|
||||
conn.Accept("connection by Portmaster", noReasonOptionKey)
|
||||
conn.Internal = true
|
||||
|
|
|
@ -181,7 +181,7 @@ func (q *Queue) packetHandler(ctx context.Context) func(nfqueue.Attribute) int {
|
|||
|
||||
if attrs.Payload == nil {
|
||||
// There is not payload.
|
||||
log.Warningf("nfqueue: packet #%s has no payload", pkt.pktID)
|
||||
log.Warningf("nfqueue: packet #%d has no payload", pkt.pktID)
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ import (
|
|||
|
||||
const noReasonOptionKey = ""
|
||||
|
||||
type deciderFn func(context.Context, *network.Connection, packet.Packet) bool
|
||||
type deciderFn func(context.Context, *network.Connection, *profile.LayeredProfile, packet.Packet) bool
|
||||
|
||||
var defaultDeciders = []deciderFn{
|
||||
checkPortmasterConnection,
|
||||
|
@ -45,6 +45,7 @@ var defaultDeciders = []deciderFn{
|
|||
checkConnectionType,
|
||||
checkConnectionScope,
|
||||
checkEndpointLists,
|
||||
checkResolverScope,
|
||||
checkConnectivityDomain,
|
||||
checkBypassPrevention,
|
||||
checkFilterLists,
|
||||
|
@ -82,6 +83,13 @@ func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packe
|
|||
if conn.Entity != nil {
|
||||
conn.Entity.ResetLists()
|
||||
}
|
||||
} else {
|
||||
// Check if the revision counter of the connection needs updating.
|
||||
revCnt := layeredProfile.RevisionCnt()
|
||||
if conn.ProfileRevisionCounter != revCnt {
|
||||
conn.ProfileRevisionCounter = revCnt
|
||||
conn.SaveWhenFinished()
|
||||
}
|
||||
}
|
||||
|
||||
// DNS request from the system resolver require a special decision process,
|
||||
|
@ -90,7 +98,7 @@ func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packe
|
|||
// connection is then blocked when the original requesting process is known.
|
||||
if conn.Type == network.DNSRequest && conn.Process().IsSystemResolver() {
|
||||
// Run all deciders and return if they came to a conclusion.
|
||||
done, _ := runDeciders(ctx, dnsFromSystemResolverDeciders, conn, pkt)
|
||||
done, _ := runDeciders(ctx, dnsFromSystemResolverDeciders, conn, layeredProfile, pkt)
|
||||
if !done {
|
||||
conn.Accept("allowing system resolver dns request", noReasonOptionKey)
|
||||
}
|
||||
|
@ -98,7 +106,7 @@ func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packe
|
|||
}
|
||||
|
||||
// Run all deciders and return if they came to a conclusion.
|
||||
done, defaultAction := runDeciders(ctx, defaultDeciders, conn, pkt)
|
||||
done, defaultAction := runDeciders(ctx, defaultDeciders, conn, layeredProfile, pkt)
|
||||
if done {
|
||||
return
|
||||
}
|
||||
|
@ -114,16 +122,14 @@ func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packe
|
|||
}
|
||||
}
|
||||
|
||||
func runDeciders(ctx context.Context, selectedDeciders []deciderFn, conn *network.Connection, pkt packet.Packet) (done bool, defaultAction uint8) {
|
||||
layeredProfile := conn.Process().Profile()
|
||||
|
||||
// Read-lock the all the profiles.
|
||||
func runDeciders(ctx context.Context, selectedDeciders []deciderFn, conn *network.Connection, layeredProfile *profile.LayeredProfile, pkt packet.Packet) (done bool, defaultAction uint8) {
|
||||
// Read-lock all the profiles.
|
||||
layeredProfile.LockForUsage()
|
||||
defer layeredProfile.UnlockForUsage()
|
||||
|
||||
// Go though all deciders, return if one sets an action.
|
||||
for _, decider := range selectedDeciders {
|
||||
if decider(ctx, conn, pkt) {
|
||||
if decider(ctx, conn, layeredProfile, pkt) {
|
||||
return true, profile.DefaultActionNotSet
|
||||
}
|
||||
}
|
||||
|
@ -134,7 +140,7 @@ func runDeciders(ctx context.Context, selectedDeciders []deciderFn, conn *networ
|
|||
|
||||
// checkPortmasterConnection allows all connection that originate from
|
||||
// portmaster itself.
|
||||
func checkPortmasterConnection(ctx context.Context, conn *network.Connection, pkt packet.Packet) bool {
|
||||
func checkPortmasterConnection(ctx context.Context, conn *network.Connection, _ *profile.LayeredProfile, pkt packet.Packet) bool {
|
||||
// Grant own outgoing connections.
|
||||
if conn.Process().Pid == ownPID &&
|
||||
(pkt == nil || pkt.IsOutbound()) {
|
||||
|
@ -148,7 +154,7 @@ func checkPortmasterConnection(ctx context.Context, conn *network.Connection, pk
|
|||
}
|
||||
|
||||
// checkSelfCommunication checks if the process is communicating with itself.
|
||||
func checkSelfCommunication(ctx context.Context, conn *network.Connection, pkt packet.Packet) bool {
|
||||
func checkSelfCommunication(ctx context.Context, conn *network.Connection, _ *profile.LayeredProfile, pkt packet.Packet) bool {
|
||||
// check if process is communicating with itself
|
||||
if pkt != nil {
|
||||
// TODO: evaluate the case where different IPs in the 127/8 net are used.
|
||||
|
@ -183,13 +189,10 @@ func checkSelfCommunication(ctx context.Context, conn *network.Connection, pkt p
|
|||
return false
|
||||
}
|
||||
|
||||
func checkEndpointLists(ctx context.Context, conn *network.Connection, _ packet.Packet) bool {
|
||||
func checkEndpointLists(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
|
||||
var result endpoints.EPResult
|
||||
var reason endpoints.Reason
|
||||
|
||||
// there must always be a profile.
|
||||
p := conn.Process().Profile()
|
||||
|
||||
// check endpoints list
|
||||
var optionKey string
|
||||
if conn.Inbound {
|
||||
|
@ -211,35 +214,41 @@ func checkEndpointLists(ctx context.Context, conn *network.Connection, _ packet.
|
|||
return false
|
||||
}
|
||||
|
||||
func checkConnectionType(ctx context.Context, conn *network.Connection, _ packet.Packet) bool {
|
||||
p := conn.Process().Profile()
|
||||
func checkConnectionType(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
|
||||
switch {
|
||||
case conn.Type != network.IPConnection:
|
||||
|
||||
// check conn type
|
||||
switch conn.Scope {
|
||||
case network.IncomingLAN, network.IncomingInternet, network.IncomingInvalid:
|
||||
if p.BlockInbound() {
|
||||
if conn.Scope == network.IncomingHost {
|
||||
conn.Block("inbound connections blocked", profile.CfgOptionBlockInboundKey)
|
||||
} else {
|
||||
conn.Drop("inbound connections blocked", profile.CfgOptionBlockInboundKey)
|
||||
}
|
||||
return true
|
||||
}
|
||||
case network.PeerInternet:
|
||||
// BlockP2P only applies to connections to the Internet
|
||||
if p.BlockP2P() {
|
||||
conn.Block("direct connections (P2P) blocked", profile.CfgOptionBlockP2PKey)
|
||||
return true
|
||||
}
|
||||
// Decider only applies to IP connections.
|
||||
return false
|
||||
|
||||
case conn.Inbound &&
|
||||
!conn.Entity.IPScope.IsLocalhost() &&
|
||||
p.BlockInbound():
|
||||
|
||||
// BlockInbound does not apply to the Localhost scope.
|
||||
conn.Drop("inbound connections blocked", profile.CfgOptionBlockInboundKey)
|
||||
return true
|
||||
|
||||
case conn.Entity.IPScope.IsGlobal() &&
|
||||
conn.Entity.Domain == "" &&
|
||||
p.BlockP2P():
|
||||
|
||||
// BlockP2P only applies to the Global scope.
|
||||
conn.Block("direct connections (P2P) blocked", profile.CfgOptionBlockP2PKey)
|
||||
return true
|
||||
|
||||
default:
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkConnectivityDomain(_ context.Context, conn *network.Connection, _ packet.Packet) bool {
|
||||
p := conn.Process().Profile()
|
||||
|
||||
func checkConnectivityDomain(_ context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
|
||||
switch {
|
||||
case conn.Entity.Domain == "":
|
||||
// Only applies if a domain is available.
|
||||
return false
|
||||
|
||||
case netenv.GetOnlineStatus() > netenv.StatusPortal:
|
||||
// Special grant only applies if network status is Portal (or even more limited).
|
||||
return false
|
||||
|
@ -263,9 +272,7 @@ func checkConnectivityDomain(_ context.Context, conn *network.Connection, _ pack
|
|||
}
|
||||
}
|
||||
|
||||
func checkConnectionScope(_ context.Context, conn *network.Connection, _ packet.Packet) bool {
|
||||
p := conn.Process().Profile()
|
||||
|
||||
func checkConnectionScope(_ context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
|
||||
// If we are handling a DNS request, check if we can immediately block it.
|
||||
if conn.Type == network.DNSRequest {
|
||||
// DNS is expected to resolve to LAN or Internet addresses.
|
||||
|
@ -300,8 +307,46 @@ func checkConnectionScope(_ context.Context, conn *network.Connection, _ packet.
|
|||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkBypassPrevention(_ context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
|
||||
if p.PreventBypassing() {
|
||||
// check for bypass protection
|
||||
result, reason, reasonCtx := PreventBypassing(conn)
|
||||
switch result {
|
||||
case endpoints.Denied:
|
||||
conn.BlockWithContext("bypass prevention: "+reason, profile.CfgOptionPreventBypassingKey, reasonCtx)
|
||||
return true
|
||||
case endpoints.Permitted:
|
||||
conn.AcceptWithContext("bypass prevention: "+reason, profile.CfgOptionPreventBypassingKey, reasonCtx)
|
||||
return true
|
||||
case endpoints.NoMatch:
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func checkFilterLists(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, pkt packet.Packet) bool {
|
||||
// apply privacy filter lists
|
||||
result, reason := p.MatchFilterLists(ctx, conn.Entity)
|
||||
switch result {
|
||||
case endpoints.Denied:
|
||||
conn.DenyWithContext(reason.String(), profile.CfgOptionFilterListsKey, reason.Context())
|
||||
return true
|
||||
case endpoints.NoMatch:
|
||||
// nothing to do
|
||||
default:
|
||||
log.Tracer(ctx).Debugf("filter: filter lists returned unsupported verdict: %s", result)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func checkResolverScope(_ context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
|
||||
// If the IP address was resolved, check the scope of the resolver.
|
||||
switch {
|
||||
case conn.Type != network.IPConnection:
|
||||
// Only applies to IP connections.
|
||||
case !p.RemoveOutOfScopeDNS():
|
||||
// Out of scope checking is not active.
|
||||
case conn.Resolver == nil:
|
||||
|
@ -321,43 +366,7 @@ func checkConnectionScope(_ context.Context, conn *network.Connection, _ packet.
|
|||
return false
|
||||
}
|
||||
|
||||
func checkBypassPrevention(_ context.Context, conn *network.Connection, _ packet.Packet) bool {
|
||||
if conn.Process().Profile().PreventBypassing() {
|
||||
// check for bypass protection
|
||||
result, reason, reasonCtx := PreventBypassing(conn)
|
||||
switch result {
|
||||
case endpoints.Denied:
|
||||
conn.BlockWithContext("bypass prevention: "+reason, profile.CfgOptionPreventBypassingKey, reasonCtx)
|
||||
return true
|
||||
case endpoints.Permitted:
|
||||
conn.AcceptWithContext("bypass prevention: "+reason, profile.CfgOptionPreventBypassingKey, reasonCtx)
|
||||
return true
|
||||
case endpoints.NoMatch:
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func checkFilterLists(ctx context.Context, conn *network.Connection, pkt packet.Packet) bool {
|
||||
// apply privacy filter lists
|
||||
p := conn.Process().Profile()
|
||||
|
||||
result, reason := p.MatchFilterLists(ctx, conn.Entity)
|
||||
switch result {
|
||||
case endpoints.Denied:
|
||||
conn.DenyWithContext(reason.String(), profile.CfgOptionFilterListsKey, reason.Context())
|
||||
return true
|
||||
case endpoints.NoMatch:
|
||||
// nothing to do
|
||||
default:
|
||||
log.Tracer(ctx).Debugf("filter: filter lists returned unsupported verdict: %s", result)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func checkDomainHeuristics(ctx context.Context, conn *network.Connection, _ packet.Packet) bool {
|
||||
p := conn.Process().Profile()
|
||||
|
||||
func checkDomainHeuristics(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
|
||||
if !p.DomainHeuristics() {
|
||||
return false
|
||||
}
|
||||
|
@ -415,7 +424,7 @@ func checkDomainHeuristics(ctx context.Context, conn *network.Connection, _ pack
|
|||
return false
|
||||
}
|
||||
|
||||
func dropInbound(_ context.Context, conn *network.Connection, _ packet.Packet) bool {
|
||||
func dropInbound(_ context.Context, conn *network.Connection, _ *profile.LayeredProfile, _ packet.Packet) bool {
|
||||
// implicit default=block for inbound
|
||||
if conn.Inbound {
|
||||
conn.Drop("incoming connection blocked by default", profile.CfgOptionServiceEndpointsKey)
|
||||
|
@ -424,9 +433,7 @@ func dropInbound(_ context.Context, conn *network.Connection, _ packet.Packet) b
|
|||
return false
|
||||
}
|
||||
|
||||
func checkAutoPermitRelated(_ context.Context, conn *network.Connection, _ packet.Packet) bool {
|
||||
p := conn.Process().Profile()
|
||||
|
||||
func checkAutoPermitRelated(_ context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
|
||||
// Auto permit is disabled for default action permit.
|
||||
if p.DefaultAction() == profile.DefaultActionPermit {
|
||||
return false
|
||||
|
|
|
@ -106,10 +106,10 @@ func createPrompt(ctx context.Context, conn *network.Connection, pkt packet.Pack
|
|||
switch {
|
||||
case conn.Inbound, conn.Entity.Domain == "": // connection to/from IP
|
||||
nID = fmt.Sprintf(
|
||||
"%s-%s-%s-%s",
|
||||
"%s-%s-%v-%s",
|
||||
promptIDPrefix,
|
||||
localProfile.ID,
|
||||
conn.Scope,
|
||||
conn.Inbound,
|
||||
pkt.Info().RemoteIP(),
|
||||
)
|
||||
default: // connection to domain
|
||||
|
@ -117,7 +117,7 @@ func createPrompt(ctx context.Context, conn *network.Connection, pkt packet.Pack
|
|||
"%s-%s-%s",
|
||||
promptIDPrefix,
|
||||
localProfile.ID,
|
||||
conn.Scope,
|
||||
conn.Entity.Domain,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -189,8 +189,8 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
|
|||
|
||||
// Resolve request.
|
||||
rrCache, err = resolver.Resolve(ctx, q)
|
||||
// Handle error.
|
||||
if err != nil {
|
||||
// React to special errors.
|
||||
switch {
|
||||
case errors.Is(err, resolver.ErrNotFound):
|
||||
tracer.Tracef("nameserver: %s", err)
|
||||
|
@ -214,31 +214,34 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
|
|||
return reply(nsutil.ServerFailure("internal error: " + err.Error()))
|
||||
}
|
||||
}
|
||||
if rrCache == nil {
|
||||
// Handle special cases.
|
||||
switch {
|
||||
case rrCache == nil:
|
||||
tracer.Warning("nameserver: received successful, but empty reply from resolver")
|
||||
return reply(nsutil.ServerFailure("internal error: empty reply"))
|
||||
case rrCache.RCode == dns.RcodeNameError:
|
||||
return reply(nsutil.NxDomain("no answer found (NXDomain)"))
|
||||
}
|
||||
|
||||
tracer.Trace("nameserver: deciding on resolved dns")
|
||||
rrCache = firewall.FilterResolvedDNS(ctx, conn, q, rrCache)
|
||||
if rrCache == nil {
|
||||
// Check again if there is a responder from the firewall.
|
||||
if responder, ok := conn.Reason.Context.(nsutil.Responder); ok {
|
||||
tracer.Infof("nameserver: handing over request for %s to filter responder: %s", q.ID(), conn.Reason.Msg)
|
||||
return reply(responder)
|
||||
}
|
||||
|
||||
// Request was blocked by the firewall.
|
||||
switch conn.Verdict {
|
||||
case network.VerdictBlock, network.VerdictDrop, network.VerdictFailed:
|
||||
tracer.Infof(
|
||||
"nameserver: returning %s response for %s to %s",
|
||||
conn.Verdict.Verb(),
|
||||
q.ID(),
|
||||
conn.Process(),
|
||||
)
|
||||
return reply(conn, conn)
|
||||
}
|
||||
// Check again if there is a responder from the firewall.
|
||||
if responder, ok := conn.Reason.Context.(nsutil.Responder); ok {
|
||||
tracer.Infof("nameserver: handing over request for %s to special filter responder: %s", q.ID(), conn.Reason.Msg)
|
||||
return reply(responder)
|
||||
}
|
||||
|
||||
// Check if there is a Verdict to act upon.
|
||||
switch conn.Verdict {
|
||||
case network.VerdictBlock, network.VerdictDrop, network.VerdictFailed:
|
||||
tracer.Infof(
|
||||
"nameserver: returning %s response for %s to %s",
|
||||
conn.Verdict.Verb(),
|
||||
q.ID(),
|
||||
conn.Process(),
|
||||
)
|
||||
return reply(conn, conn, rrCache)
|
||||
}
|
||||
|
||||
// Revert back to non-standard question format, if we had to convert.
|
||||
|
@ -247,10 +250,15 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
|
|||
}
|
||||
|
||||
// Reply with successful response.
|
||||
noAnswerIndicator := ""
|
||||
if len(rrCache.Answer) == 0 {
|
||||
noAnswerIndicator = "/no answer"
|
||||
}
|
||||
tracer.Infof(
|
||||
"nameserver: returning %s response (%s) for %s to %s",
|
||||
"nameserver: returning %s response (%s%s) for %s to %s",
|
||||
conn.Verdict.Verb(),
|
||||
dns.RcodeToString[rrCache.RCode],
|
||||
noAnswerIndicator,
|
||||
q.ID(),
|
||||
conn.Process(),
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/safing/portbase/log"
|
||||
|
@ -73,10 +74,8 @@ func ZeroIP(msgs ...string) ResponderFunc {
|
|||
}
|
||||
|
||||
switch {
|
||||
case hasErr && len(reply.Answer) == 0:
|
||||
case hasErr || len(reply.Answer) == 0:
|
||||
reply.SetRcode(request, dns.RcodeServerFailure)
|
||||
case len(reply.Answer) == 0:
|
||||
reply.SetRcode(request, dns.RcodeNameError)
|
||||
default:
|
||||
reply.SetRcode(request, dns.RcodeSuccess)
|
||||
}
|
||||
|
@ -115,10 +114,8 @@ func Localhost(msgs ...string) ResponderFunc {
|
|||
}
|
||||
|
||||
switch {
|
||||
case hasErr && len(reply.Answer) == 0:
|
||||
case hasErr || len(reply.Answer) == 0:
|
||||
reply.SetRcode(request, dns.RcodeServerFailure)
|
||||
case len(reply.Answer) == 0:
|
||||
reply.SetRcode(request, dns.RcodeNameError)
|
||||
default:
|
||||
reply.SetRcode(request, dns.RcodeSuccess)
|
||||
}
|
||||
|
@ -134,6 +131,15 @@ func NxDomain(msgs ...string) ResponderFunc {
|
|||
return func(ctx context.Context, request *dns.Msg) *dns.Msg {
|
||||
reply := new(dns.Msg).SetRcode(request, dns.RcodeNameError)
|
||||
AddMessagesToReply(ctx, reply, log.InfoLevel, msgs...)
|
||||
|
||||
// According to RFC4074 (https://tools.ietf.org/html/rfc4074), there are
|
||||
// nameservers that incorrectly respond with NXDomain instead of an empty
|
||||
// SUCCESS response when there are other RRs for the queried domain name.
|
||||
// This can lead to the software thinking that no RRs exist for that
|
||||
// domain. In order to mitigate this a bit, we slightly delay NXDomain
|
||||
// responses.
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
return reply
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,8 +51,9 @@ func GetAssignedGlobalAddresses() (ipv4 []net.IP, ipv6 []net.IP, err error) {
|
|||
}
|
||||
|
||||
var (
|
||||
myNetworks []*net.IPNet
|
||||
myNetworksLock sync.Mutex
|
||||
myNetworks []*net.IPNet
|
||||
myNetworksLock sync.Mutex
|
||||
myNetworksNetworkChangedFlag = GetNetworkChangedFlag()
|
||||
)
|
||||
|
||||
// IsMyIP returns whether the given unicast IP is currently configured on the local host.
|
||||
|
@ -69,8 +70,12 @@ func IsMyIP(ip net.IP) (yes bool, err error) {
|
|||
myNetworksLock.Lock()
|
||||
defer myNetworksLock.Unlock()
|
||||
|
||||
// Check for match.
|
||||
if mine, matched := checkIfMyIP(ip); matched {
|
||||
// Check if the network changed.
|
||||
if myNetworksNetworkChangedFlag.IsSet() {
|
||||
// Reset changed flag.
|
||||
myNetworksNetworkChangedFlag.Refresh()
|
||||
} else if mine, matched := checkIfMyIP(ip); matched {
|
||||
// If the network did not change, check for match immediately.
|
||||
return mine, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ func init() {
|
|||
flag.BoolVar(&privileged, "privileged", false, "run tests that require root/admin privileges")
|
||||
}
|
||||
|
||||
func TestGetApproximateInternetLocation(t *testing.T) {
|
||||
func TestGetInternetLocation(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
|
@ -21,9 +21,9 @@ func TestGetApproximateInternetLocation(t *testing.T) {
|
|||
t.Skip("skipping privileged test, active with -privileged argument")
|
||||
}
|
||||
|
||||
loc, err := GetInternetLocation()
|
||||
if err != nil {
|
||||
t.Fatalf("GetApproximateInternetLocation failed: %s", err)
|
||||
loc, ok := GetInternetLocation()
|
||||
if !ok {
|
||||
t.Fatal("GetApproximateInternetLocation failed")
|
||||
}
|
||||
t.Logf("GetApproximateInternetLocation: %+v", loc)
|
||||
}
|
||||
|
|
|
@ -265,6 +265,10 @@ func NewConnectionFromDNSRequest(ctx context.Context, fqdn string, cnames []stri
|
|||
dnsConn.Internal = true
|
||||
}
|
||||
|
||||
// DNS Requests are saved by the nameserver depending on the result of the
|
||||
// query. Blocked requests are saved immediately, accepted ones are only
|
||||
// saved if they are not "used" by a connection.
|
||||
|
||||
return dnsConn
|
||||
}
|
||||
|
||||
|
@ -295,6 +299,10 @@ func NewConnectionFromExternalDNSRequest(ctx context.Context, fqdn string, cname
|
|||
dnsConn.Internal = localProfile.Internal
|
||||
}
|
||||
|
||||
// DNS Requests are saved by the nameserver depending on the result of the
|
||||
// query. Blocked requests are saved immediately, accepted ones are only
|
||||
// saved if they are not "used" by a connection.
|
||||
|
||||
return dnsConn, nil
|
||||
}
|
||||
|
||||
|
@ -307,20 +315,19 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection {
|
|||
proc = process.GetUnidentifiedProcess(pkt.Ctx())
|
||||
}
|
||||
|
||||
// Create the (remote) entity.
|
||||
entity := &intel.Entity{
|
||||
Protocol: uint8(pkt.Info().Protocol),
|
||||
Port: pkt.Info().RemotePort(),
|
||||
}
|
||||
entity.SetIP(pkt.Info().RemoteIP())
|
||||
entity.SetDstPort(pkt.Info().DstPort)
|
||||
|
||||
var scope string
|
||||
var entity *intel.Entity
|
||||
var resolverInfo *resolver.ResolverInfo
|
||||
|
||||
if inbound {
|
||||
|
||||
// inbound connection
|
||||
entity = &intel.Entity{
|
||||
Protocol: uint8(pkt.Info().Protocol),
|
||||
Port: pkt.Info().SrcPort,
|
||||
}
|
||||
entity.SetIP(pkt.Info().Src)
|
||||
entity.SetDstPort(pkt.Info().DstPort)
|
||||
|
||||
switch entity.IPScope {
|
||||
case netutils.HostLocal:
|
||||
scope = IncomingHost
|
||||
|
@ -337,19 +344,11 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection {
|
|||
|
||||
} else {
|
||||
|
||||
// outbound connection
|
||||
entity = &intel.Entity{
|
||||
Protocol: uint8(pkt.Info().Protocol),
|
||||
Port: pkt.Info().DstPort,
|
||||
}
|
||||
entity.SetIP(pkt.Info().Dst)
|
||||
entity.SetDstPort(entity.Port)
|
||||
|
||||
// check if we can find a domain for that IP
|
||||
ipinfo, err := resolver.GetIPInfo(proc.Profile().LocalProfile().ID, pkt.Info().Dst.String())
|
||||
ipinfo, err := resolver.GetIPInfo(proc.Profile().LocalProfile().ID, pkt.Info().RemoteIP().String())
|
||||
if err != nil {
|
||||
// Try again with the global scope, in case DNS went through the system resolver.
|
||||
ipinfo, err = resolver.GetIPInfo(resolver.IPInfoProfileScopeGlobal, pkt.Info().Dst.String())
|
||||
ipinfo, err = resolver.GetIPInfo(resolver.IPInfoProfileScopeGlobal, pkt.Info().RemoteIP().String())
|
||||
}
|
||||
if err == nil {
|
||||
lastResolvedDomain := ipinfo.MostRecentDomain()
|
||||
|
@ -364,7 +363,7 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection {
|
|||
|
||||
// check if destination IP is the captive portal's IP
|
||||
portal := netenv.GetCaptivePortal()
|
||||
if pkt.Info().Dst.Equal(portal.IP) {
|
||||
if pkt.Info().RemoteIP().Equal(portal.IP) {
|
||||
scope = portal.Domain
|
||||
entity.Domain = portal.Domain
|
||||
}
|
||||
|
@ -416,6 +415,10 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection {
|
|||
newConn.Internal = localProfile.Internal
|
||||
}
|
||||
|
||||
// Save connection to internal state in order to mitigate creation of
|
||||
// duplicates. Do not propagate yet, as there is no verdict yet.
|
||||
conns.add(newConn)
|
||||
|
||||
return newConn
|
||||
}
|
||||
|
||||
|
@ -647,12 +650,12 @@ func (conn *Connection) SetInspectorData(new map[uint8]interface{}) {
|
|||
|
||||
// String returns a string representation of conn.
|
||||
func (conn *Connection) String() string {
|
||||
switch conn.Scope {
|
||||
case IncomingHost, IncomingLAN, IncomingInternet, IncomingInvalid:
|
||||
switch {
|
||||
case conn.Inbound:
|
||||
return fmt.Sprintf("%s <- %s", conn.process, conn.Entity.IP)
|
||||
case PeerHost, PeerLAN, PeerInternet, PeerInvalid:
|
||||
return fmt.Sprintf("%s -> %s", conn.process, conn.Entity.IP)
|
||||
default:
|
||||
case conn.Entity.Domain != "":
|
||||
return fmt.Sprintf("%s to %s (%s)", conn.process, conn.Entity.Domain, conn.Entity.IP)
|
||||
default:
|
||||
return fmt.Sprintf("%s -> %s", conn.process, conn.Entity.IP)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,11 +55,14 @@ func SaveOpenDNSRequest(conn *Connection) {
|
|||
openDNSRequestsLock.Lock()
|
||||
defer openDNSRequestsLock.Unlock()
|
||||
|
||||
key := getDNSRequestCacheKey(conn.process.Pid, conn.Scope)
|
||||
key := getDNSRequestCacheKey(conn.process.Pid, conn.Entity.Domain)
|
||||
if existingConn, ok := openDNSRequests[key]; ok {
|
||||
// End previous request and save it.
|
||||
existingConn.Lock()
|
||||
defer existingConn.Unlock()
|
||||
existingConn.Ended = conn.Started
|
||||
existingConn.Unlock()
|
||||
existingConn.Save()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,11 @@ nextPort:
|
|||
}
|
||||
}
|
||||
|
||||
// Log if it took more than 10 attempts.
|
||||
if i >= 10 {
|
||||
log.Warningf("network: took %d attempts to find a suitable unused port for pre-auth", i+1)
|
||||
}
|
||||
|
||||
// The checks have passed. We have found a good unused port.
|
||||
return port, true
|
||||
}
|
||||
|
|
|
@ -379,12 +379,11 @@ func TestEndpointMatching(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testEndpointMatch(t, ep, (&intel.Entity{
|
||||
IP: net.ParseIP("192.168.0.1"),
|
||||
}).Init(), Permitted)
|
||||
testEndpointMatch(t, ep, (&intel.Entity{
|
||||
IP: net.ParseIP("151.101.1.164"), // nytimes.com
|
||||
}).Init(), NoMatch)
|
||||
entity = &intel.Entity{}
|
||||
entity.SetIP(net.ParseIP("192.168.0.1"))
|
||||
testEndpointMatch(t, ep, entity, Permitted)
|
||||
entity.SetIP(net.ParseIP("151.101.1.164")) // nytimes.com
|
||||
testEndpointMatch(t, ep, entity, NoMatch)
|
||||
|
||||
// Lists
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package resolver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/safing/portbase/api"
|
||||
"github.com/safing/portbase/database/record"
|
||||
)
|
||||
|
||||
func registerAPI() error {
|
||||
|
@ -25,6 +28,24 @@ func registerAPI() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Path: `dns/cache`,
|
||||
Read: api.PermitUser,
|
||||
RecordFunc: func(r *api.Request) (record.Record, error) {
|
||||
return recordDatabase.Get(nameRecordsKeyPrefix + r.URL.Query().Get("q"))
|
||||
},
|
||||
Name: "Get DNS Record from Cache",
|
||||
Description: "Returns cached dns records from the internal cache.",
|
||||
Parameters: []api.Parameter{{
|
||||
Method: http.MethodGet,
|
||||
Field: "q",
|
||||
Value: "fqdn and query type",
|
||||
Description: "Specify the query like this: `example.com.A`.",
|
||||
}},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,9 @@ func TestNameRecordStorage(t *testing.T) {
|
|||
testNameRecord := &NameRecord{
|
||||
Domain: testDomain,
|
||||
Question: testQuestion,
|
||||
Resolver: &ResolverInfo{
|
||||
Type: "dns",
|
||||
},
|
||||
}
|
||||
|
||||
err := testNameRecord.Save()
|
||||
|
|
|
@ -341,6 +341,7 @@ resolveLoop:
|
|||
}
|
||||
|
||||
// resolve
|
||||
log.Tracer(ctx).Tracef("resolver: sending query for %s to %s", q.ID(), resolver.Info.ID())
|
||||
rrCache, err = resolver.Conn.Query(ctx, q)
|
||||
if err != nil {
|
||||
switch {
|
||||
|
@ -416,9 +417,11 @@ resolveLoop:
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Save the new entry if cache is enabled.
|
||||
// Adjust TTLs.
|
||||
rrCache.Clean(minTTL)
|
||||
|
||||
// Save the new entry if cache is enabled and the record may be cached.
|
||||
if !q.NoCaching && rrCache.Cacheable() {
|
||||
rrCache.Clean(minTTL)
|
||||
err = rrCache.Save()
|
||||
if err != nil {
|
||||
log.Tracer(ctx).Warningf("resolver: failed to cache RR for %s: %s", q.ID(), err)
|
||||
|
|
|
@ -33,5 +33,5 @@ func TestResolveIPAndValidate(t *testing.T) {
|
|||
testReverse(t, "2606:4700:4700::1111", "one.one.one.one.", "")
|
||||
|
||||
testReverse(t, "93.184.216.34", "example.com.", "record could not be found: 34.216.184.93.in-addr.arpa.PTR")
|
||||
testReverse(t, "185.199.109.153", "sites.github.io.", "record could not be found: 153.109.199.185.in-addr.arpa.PTR")
|
||||
testReverse(t, "185.199.109.153", "cdn-185-199-109-153.github.com.", "record could not be found: 153.109.199.185.in-addr.arpa.PTR")
|
||||
}
|
||||
|
|
|
@ -82,12 +82,6 @@ func (rrCache *RRCache) Clean(minExpires uint32) {
|
|||
lowestTTL = maxTTL
|
||||
}
|
||||
|
||||
// Adjust return code if there are no answers
|
||||
if rrCache.RCode == dns.RcodeSuccess &&
|
||||
len(rrCache.Answer) == 0 {
|
||||
rrCache.RCode = dns.RcodeNameError
|
||||
}
|
||||
|
||||
// shorten caching
|
||||
switch {
|
||||
case rrCache.RCode != dns.RcodeSuccess:
|
||||
|
@ -96,6 +90,9 @@ func (rrCache *RRCache) Clean(minExpires uint32) {
|
|||
case netenv.IsConnectivityDomain(rrCache.Domain):
|
||||
// Responses from these domains might change very quickly depending on the environment.
|
||||
lowestTTL = 3
|
||||
case len(rrCache.Answer) == 0:
|
||||
// Empty answer section: Domain exists, but not the queried RR.
|
||||
lowestTTL = 60
|
||||
case !netenv.Online():
|
||||
// Not being fully online could mean that we get funny responses.
|
||||
lowestTTL = 60
|
||||
|
@ -318,9 +315,12 @@ func (rrCache *RRCache) GetExtraRRs(ctx context.Context, query *dns.Msg) (extra
|
|||
// Add information about filtered entries.
|
||||
if rrCache.Filtered {
|
||||
if len(rrCache.FilteredEntries) > 1 {
|
||||
extra = addExtra(ctx, extra, fmt.Sprintf("%d records have been filtered", len(rrCache.FilteredEntries)))
|
||||
extra = addExtra(ctx, extra, fmt.Sprintf("%d RRs have been filtered:", len(rrCache.FilteredEntries)))
|
||||
} else {
|
||||
extra = addExtra(ctx, extra, fmt.Sprintf("%d record has been filtered", len(rrCache.FilteredEntries)))
|
||||
extra = addExtra(ctx, extra, fmt.Sprintf("%d RR has been filtered:", len(rrCache.FilteredEntries)))
|
||||
}
|
||||
for _, filteredRecord := range rrCache.FilteredEntries {
|
||||
extra = addExtra(ctx, extra, filteredRecord)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,9 @@ func TestCaching(t *testing.T) {
|
|||
testNameRecord := &NameRecord{
|
||||
Domain: testDomain,
|
||||
Question: testQuestion,
|
||||
Resolver: &ResolverInfo{
|
||||
Type: "dns",
|
||||
},
|
||||
}
|
||||
|
||||
err := testNameRecord.Save()
|
||||
|
|
5
test
5
test
|
@ -176,13 +176,16 @@ echo "running tests for ${platformInfo//$'\n'/ }:"
|
|||
|
||||
# run vet/test on packages
|
||||
for package in $packages; do
|
||||
package=${package#github.com/safing/portmaster}
|
||||
package=${package#/}
|
||||
package=$PWD/$package
|
||||
echo ""
|
||||
echo $package
|
||||
if [[ $testonly -eq 0 ]]; then
|
||||
checkformat $package
|
||||
run golint -set_exit_status -min_confidence 1.0 $package
|
||||
run go vet $package
|
||||
run golangci-lint run $GOPATH/src/$package
|
||||
run golangci-lint run $package
|
||||
fi
|
||||
run go test -cover $fullTestFlags $package
|
||||
done
|
||||
|
|
Loading…
Add table
Reference in a new issue