Merge pull request #293 from safing/fix/patch-set-3

DNS and other fixes & improvements
This commit is contained in:
Daniel 2021-04-19 15:16:18 +02:00 committed by GitHub
commit 06eee6805c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 309 additions and 233 deletions

View file

@ -4,7 +4,6 @@ import (
"context" "context"
"net" "net"
"strings" "strings"
"time"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/safing/portbase/database" "github.com/safing/portbase/database"
@ -16,9 +15,19 @@ import (
"github.com/safing/portmaster/resolver" "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)) 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 // keeps track of the number of valid and allowed
// A and AAAA records. // A and AAAA records.
@ -44,13 +53,16 @@ func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, resolverScope
switch { switch {
case ipScope.IsLocalhost(): case ipScope.IsLocalhost():
// No DNS should return localhost addresses // No DNS should return localhost addresses
filteredRecords = append(filteredRecords, rr.String()) filteredRecords = append(filteredRecords, formatRR(rr))
interveningOptionKey = profile.CfgOptionRemoveOutOfScopeDNSKey interveningOptionKey = profile.CfgOptionRemoveOutOfScopeDNSKey
log.Tracer(ctx).Tracef("filter: RR violates resolver scope: %s", formatRR(rr))
continue continue
case resolverScope.IsGlobal() && ipScope.IsLAN() && !sysResolver: case resolverScope.IsGlobal() && ipScope.IsLAN() && !sysResolver:
// No global DNS should return LAN addresses // No global DNS should return LAN addresses
filteredRecords = append(filteredRecords, rr.String()) filteredRecords = append(filteredRecords, formatRR(rr))
interveningOptionKey = profile.CfgOptionRemoveOutOfScopeDNSKey interveningOptionKey = profile.CfgOptionRemoveOutOfScopeDNSKey
log.Tracer(ctx).Tracef("filter: RR violates resolver scope: %s", formatRR(rr))
continue continue
} }
} }
@ -59,16 +71,21 @@ func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, resolverScope
// filter by flags // filter by flags
switch { switch {
case p.BlockScopeInternet() && ipScope.IsGlobal(): case p.BlockScopeInternet() && ipScope.IsGlobal():
filteredRecords = append(filteredRecords, rr.String()) filteredRecords = append(filteredRecords, formatRR(rr))
interveningOptionKey = profile.CfgOptionBlockScopeInternetKey interveningOptionKey = profile.CfgOptionBlockScopeInternetKey
log.Tracer(ctx).Tracef("filter: RR is in blocked scope Internet: %s", formatRR(rr))
continue continue
case p.BlockScopeLAN() && ipScope.IsLAN(): case p.BlockScopeLAN() && ipScope.IsLAN():
filteredRecords = append(filteredRecords, rr.String()) filteredRecords = append(filteredRecords, formatRR(rr))
interveningOptionKey = profile.CfgOptionBlockScopeLANKey interveningOptionKey = profile.CfgOptionBlockScopeLANKey
log.Tracer(ctx).Tracef("filter: RR is in blocked scope LAN: %s", formatRR(rr))
continue continue
case p.BlockScopeLocal() && ipScope.IsLocalhost(): case p.BlockScopeLocal() && ipScope.IsLocalhost():
filteredRecords = append(filteredRecords, rr.String()) filteredRecords = append(filteredRecords, formatRR(rr))
interveningOptionKey = profile.CfgOptionBlockScopeLocalKey interveningOptionKey = profile.CfgOptionBlockScopeLocalKey
log.Tracer(ctx).Tracef("filter: RR is in blocked scope Localhost: %s", formatRR(rr))
continue continue
} }
@ -83,9 +100,13 @@ func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, resolverScope
return goodEntries, filteredRecords, allowedAddressRecords, interveningOptionKey return goodEntries, filteredRecords, allowedAddressRecords, interveningOptionKey
} }
func filterDNSResponse(conn *network.Connection, rrCache *resolver.RRCache, sysResolver bool) *resolver.RRCache { func filterDNSResponse(
p := conn.Process().Profile() ctx context.Context,
conn *network.Connection,
p *profile.LayeredProfile,
rrCache *resolver.RRCache,
sysResolver bool,
) *resolver.RRCache {
// do not modify own queries // do not modify own queries
if conn.Process().Pid == ownPID { if conn.Process().Pid == ownPID {
return rrCache return rrCache
@ -96,20 +117,20 @@ func filterDNSResponse(conn *network.Connection, rrCache *resolver.RRCache, sysR
return rrCache return rrCache
} }
// duplicate entry
rrCache = rrCache.ShallowCopy()
rrCache.FilteredEntries = make([]string, 0)
var filteredRecords []string var filteredRecords []string
var validIPs int var validIPs int
var interveningOptionKey string var interveningOptionKey string
rrCache.Answer, filteredRecords, validIPs, interveningOptionKey = filterDNSSection(rrCache.Answer, p, rrCache.Resolver.IPScope, sysResolver) rrCache.Answer, filteredRecords, validIPs, interveningOptionKey = filterDNSSection(ctx, rrCache.Answer, p, rrCache.Resolver.IPScope, sysResolver)
rrCache.FilteredEntries = append(rrCache.FilteredEntries, filteredRecords...) if len(filteredRecords) > 0 {
rrCache.FilteredEntries = append(rrCache.FilteredEntries, filteredRecords...)
}
// we don't count the valid IPs in the extra section // Don't count the valid IPs in the extra section.
rrCache.Extra, filteredRecords, _, _ = filterDNSSection(rrCache.Extra, p, rrCache.Resolver.IPScope, sysResolver) rrCache.Extra, filteredRecords, _, _ = filterDNSSection(ctx, rrCache.Extra, p, rrCache.Resolver.IPScope, sysResolver)
rrCache.FilteredEntries = append(rrCache.FilteredEntries, filteredRecords...) if len(filteredRecords) > 0 {
rrCache.FilteredEntries = append(rrCache.FilteredEntries, filteredRecords...)
}
if len(rrCache.FilteredEntries) > 0 { if len(rrCache.FilteredEntries) > 0 {
rrCache.Filtered = true 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) 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. return rrCache
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
} }
log.Infof("filter: filtered DNS replies for %s: %s", conn, strings.Join(rrCache.FilteredEntries, ", "))
} }
return rrCache return rrCache
@ -168,9 +163,15 @@ func FilterResolvedDNS(
q *resolver.Query, q *resolver.Query,
rrCache *resolver.RRCache, rrCache *resolver.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 // special grant for connectivity domains
if checkConnectivityDomain(ctx, conn, nil) { if checkConnectivityDomain(ctx, conn, layeredProfile, nil) {
// returns true if check triggered // returns true if check triggered
return rrCache return rrCache
} }
@ -178,33 +179,35 @@ func FilterResolvedDNS(
// Only filter criticial things if request comes from the system resolver. // Only filter criticial things if request comes from the system resolver.
sysResolver := conn.Process().IsSystemResolver() sysResolver := conn.Process().IsSystemResolver()
updatedRR := filterDNSResponse(conn, rrCache, sysResolver) // Filter dns records and return if the query is blocked.
if updatedRR == nil { rrCache = filterDNSResponse(ctx, conn, layeredProfile, rrCache, sysResolver)
return nil if conn.Verdict == network.VerdictBlock {
return rrCache
} }
if !sysResolver && mayBlockCNAMEs(ctx, conn) { // Block by CNAMEs.
return nil 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 // if we have CNAMEs and the profile is configured to filter them
// we need to re-check the lists and endpoints here // we need to re-check the lists and endpoints here
if conn.Process().Profile().FilterCNAMEs() { if p.FilterCNAMEs() {
conn.Entity.ResetLists() conn.Entity.ResetLists()
conn.Entity.EnableCNAMECheck(ctx, true) 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 { if result == endpoints.Denied {
conn.BlockWithContext(reason.String(), profile.CfgOptionFilterCNAMEKey, reason.Context()) conn.BlockWithContext(reason.String(), profile.CfgOptionFilterCNAMEKey, reason.Context())
return true return true
} }
if result == endpoints.NoMatch { if result == endpoints.NoMatch {
result, reason = conn.Process().Profile().MatchFilterLists(ctx, conn.Entity) result, reason = p.MatchFilterLists(ctx, conn.Entity)
if result == endpoints.Denied { if result == endpoints.Denied {
conn.BlockWithContext(reason.String(), profile.CfgOptionFilterCNAMEKey, reason.Context()) conn.BlockWithContext(reason.String(), profile.CfgOptionFilterCNAMEKey, reason.Context())
return true 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", " ")
}

View file

@ -144,15 +144,14 @@ func getConnection(pkt packet.Packet) (*network.Connection, error) {
// Transform and log result. // Transform and log result.
conn := newConn.(*network.Connection) conn := newConn.(*network.Connection)
switch { sharedIndicator := ""
case created && shared: if shared {
log.Tracer(pkt.Ctx()).Tracef("filter: created new connection %s (shared)", conn.ID) sharedIndicator = " (shared)"
case created: }
log.Tracer(pkt.Ctx()).Tracef("filter: created new connection %s", conn.ID) if created {
case shared: log.Tracer(pkt.Ctx()).Tracef("filter: created new connection %s%s", conn.ID, sharedIndicator)
log.Tracer(pkt.Ctx()).Tracef("filter: assigned connection %s (shared)", conn.ID) } else {
default: log.Tracer(pkt.Ctx()).Tracef("filter: assigned connection %s%s", conn.ID, sharedIndicator)
log.Tracer(pkt.Ctx()).Tracef("filter: assigned connection %s", conn.ID)
} }
return conn, nil 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") log.Tracer(pkt.Ctx()).Trace("filter: handing over to connection-based handler")
// Check for pre-authenticated port. // Check for pre-authenticated port.
if localPortIsPreAuthenticated(conn.Entity.Protocol, conn.LocalPort) { if !conn.Inbound && localPortIsPreAuthenticated(conn.Entity.Protocol, conn.LocalPort) {
// Approve connection. // Approve connection.
conn.Accept("connection by Portmaster", noReasonOptionKey) conn.Accept("connection by Portmaster", noReasonOptionKey)
conn.Internal = true conn.Internal = true

View file

@ -181,7 +181,7 @@ func (q *Queue) packetHandler(ctx context.Context) func(nfqueue.Attribute) int {
if attrs.Payload == nil { if attrs.Payload == nil {
// There is not payload. // 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 return 0
} }

View file

@ -37,7 +37,7 @@ import (
const noReasonOptionKey = "" 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{ var defaultDeciders = []deciderFn{
checkPortmasterConnection, checkPortmasterConnection,
@ -45,6 +45,7 @@ var defaultDeciders = []deciderFn{
checkConnectionType, checkConnectionType,
checkConnectionScope, checkConnectionScope,
checkEndpointLists, checkEndpointLists,
checkResolverScope,
checkConnectivityDomain, checkConnectivityDomain,
checkBypassPrevention, checkBypassPrevention,
checkFilterLists, checkFilterLists,
@ -82,6 +83,13 @@ func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packe
if conn.Entity != nil { if conn.Entity != nil {
conn.Entity.ResetLists() 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, // 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. // connection is then blocked when the original requesting process is known.
if conn.Type == network.DNSRequest && conn.Process().IsSystemResolver() { if conn.Type == network.DNSRequest && conn.Process().IsSystemResolver() {
// Run all deciders and return if they came to a conclusion. // 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 { if !done {
conn.Accept("allowing system resolver dns request", noReasonOptionKey) 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. // 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 { if done {
return 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) { func runDeciders(ctx context.Context, selectedDeciders []deciderFn, conn *network.Connection, layeredProfile *profile.LayeredProfile, pkt packet.Packet) (done bool, defaultAction uint8) {
layeredProfile := conn.Process().Profile() // Read-lock all the profiles.
// Read-lock the all the profiles.
layeredProfile.LockForUsage() layeredProfile.LockForUsage()
defer layeredProfile.UnlockForUsage() defer layeredProfile.UnlockForUsage()
// Go though all deciders, return if one sets an action. // Go though all deciders, return if one sets an action.
for _, decider := range selectedDeciders { for _, decider := range selectedDeciders {
if decider(ctx, conn, pkt) { if decider(ctx, conn, layeredProfile, pkt) {
return true, profile.DefaultActionNotSet return true, profile.DefaultActionNotSet
} }
} }
@ -134,7 +140,7 @@ func runDeciders(ctx context.Context, selectedDeciders []deciderFn, conn *networ
// checkPortmasterConnection allows all connection that originate from // checkPortmasterConnection allows all connection that originate from
// portmaster itself. // 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. // Grant own outgoing connections.
if conn.Process().Pid == ownPID && if conn.Process().Pid == ownPID &&
(pkt == nil || pkt.IsOutbound()) { (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. // 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 // check if process is communicating with itself
if pkt != nil { if pkt != nil {
// TODO: evaluate the case where different IPs in the 127/8 net are used. // 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 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 result endpoints.EPResult
var reason endpoints.Reason var reason endpoints.Reason
// there must always be a profile.
p := conn.Process().Profile()
// check endpoints list // check endpoints list
var optionKey string var optionKey string
if conn.Inbound { if conn.Inbound {
@ -211,35 +214,41 @@ func checkEndpointLists(ctx context.Context, conn *network.Connection, _ packet.
return false return false
} }
func checkConnectionType(ctx context.Context, conn *network.Connection, _ packet.Packet) bool { func checkConnectionType(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
p := conn.Process().Profile() switch {
case conn.Type != network.IPConnection:
// check conn type // Decider only applies to IP connections.
switch conn.Scope { return false
case network.IncomingLAN, network.IncomingInternet, network.IncomingInvalid:
if p.BlockInbound() { case conn.Inbound &&
if conn.Scope == network.IncomingHost { !conn.Entity.IPScope.IsLocalhost() &&
conn.Block("inbound connections blocked", profile.CfgOptionBlockInboundKey) p.BlockInbound():
} else {
conn.Drop("inbound connections blocked", profile.CfgOptionBlockInboundKey) // BlockInbound does not apply to the Localhost scope.
} conn.Drop("inbound connections blocked", profile.CfgOptionBlockInboundKey)
return true return true
}
case network.PeerInternet: case conn.Entity.IPScope.IsGlobal() &&
// BlockP2P only applies to connections to the Internet conn.Entity.Domain == "" &&
if p.BlockP2P() { p.BlockP2P():
conn.Block("direct connections (P2P) blocked", profile.CfgOptionBlockP2PKey)
return true // 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 { func checkConnectivityDomain(_ context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
p := conn.Process().Profile()
switch { switch {
case conn.Entity.Domain == "":
// Only applies if a domain is available.
return false
case netenv.GetOnlineStatus() > netenv.StatusPortal: case netenv.GetOnlineStatus() > netenv.StatusPortal:
// Special grant only applies if network status is Portal (or even more limited). // Special grant only applies if network status is Portal (or even more limited).
return false return false
@ -263,9 +272,7 @@ func checkConnectivityDomain(_ context.Context, conn *network.Connection, _ pack
} }
} }
func checkConnectionScope(_ context.Context, conn *network.Connection, _ packet.Packet) bool { func checkConnectionScope(_ context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
p := conn.Process().Profile()
// If we are handling a DNS request, check if we can immediately block it. // If we are handling a DNS request, check if we can immediately block it.
if conn.Type == network.DNSRequest { if conn.Type == network.DNSRequest {
// DNS is expected to resolve to LAN or Internet addresses. // 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 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. // If the IP address was resolved, check the scope of the resolver.
switch { switch {
case conn.Type != network.IPConnection:
// Only applies to IP connections.
case !p.RemoveOutOfScopeDNS(): case !p.RemoveOutOfScopeDNS():
// Out of scope checking is not active. // Out of scope checking is not active.
case conn.Resolver == nil: case conn.Resolver == nil:
@ -321,43 +366,7 @@ func checkConnectionScope(_ context.Context, conn *network.Connection, _ packet.
return false return false
} }
func checkBypassPrevention(_ context.Context, conn *network.Connection, _ packet.Packet) bool { func checkDomainHeuristics(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ 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()
if !p.DomainHeuristics() { if !p.DomainHeuristics() {
return false return false
} }
@ -415,7 +424,7 @@ func checkDomainHeuristics(ctx context.Context, conn *network.Connection, _ pack
return false 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 // implicit default=block for inbound
if conn.Inbound { if conn.Inbound {
conn.Drop("incoming connection blocked by default", profile.CfgOptionServiceEndpointsKey) 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 return false
} }
func checkAutoPermitRelated(_ context.Context, conn *network.Connection, _ packet.Packet) bool { func checkAutoPermitRelated(_ context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
p := conn.Process().Profile()
// Auto permit is disabled for default action permit. // Auto permit is disabled for default action permit.
if p.DefaultAction() == profile.DefaultActionPermit { if p.DefaultAction() == profile.DefaultActionPermit {
return false return false

View file

@ -106,10 +106,10 @@ func createPrompt(ctx context.Context, conn *network.Connection, pkt packet.Pack
switch { switch {
case conn.Inbound, conn.Entity.Domain == "": // connection to/from IP case conn.Inbound, conn.Entity.Domain == "": // connection to/from IP
nID = fmt.Sprintf( nID = fmt.Sprintf(
"%s-%s-%s-%s", "%s-%s-%v-%s",
promptIDPrefix, promptIDPrefix,
localProfile.ID, localProfile.ID,
conn.Scope, conn.Inbound,
pkt.Info().RemoteIP(), pkt.Info().RemoteIP(),
) )
default: // connection to domain default: // connection to domain
@ -117,7 +117,7 @@ func createPrompt(ctx context.Context, conn *network.Connection, pkt packet.Pack
"%s-%s-%s", "%s-%s-%s",
promptIDPrefix, promptIDPrefix,
localProfile.ID, localProfile.ID,
conn.Scope, conn.Entity.Domain,
) )
} }

View file

@ -189,8 +189,8 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
// Resolve request. // Resolve request.
rrCache, err = resolver.Resolve(ctx, q) rrCache, err = resolver.Resolve(ctx, q)
// Handle error.
if err != nil { if err != nil {
// React to special errors.
switch { switch {
case errors.Is(err, resolver.ErrNotFound): case errors.Is(err, resolver.ErrNotFound):
tracer.Tracef("nameserver: %s", err) 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())) 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") tracer.Warning("nameserver: received successful, but empty reply from resolver")
return reply(nsutil.ServerFailure("internal error: empty reply")) 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") tracer.Trace("nameserver: deciding on resolved dns")
rrCache = firewall.FilterResolvedDNS(ctx, conn, q, rrCache) 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. // Check again if there is a responder from the firewall.
switch conn.Verdict { if responder, ok := conn.Reason.Context.(nsutil.Responder); ok {
case network.VerdictBlock, network.VerdictDrop, network.VerdictFailed: tracer.Infof("nameserver: handing over request for %s to special filter responder: %s", q.ID(), conn.Reason.Msg)
tracer.Infof( return reply(responder)
"nameserver: returning %s response for %s to %s", }
conn.Verdict.Verb(),
q.ID(), // Check if there is a Verdict to act upon.
conn.Process(), switch conn.Verdict {
) case network.VerdictBlock, network.VerdictDrop, network.VerdictFailed:
return reply(conn, conn) 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. // 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. // Reply with successful response.
noAnswerIndicator := ""
if len(rrCache.Answer) == 0 {
noAnswerIndicator = "/no answer"
}
tracer.Infof( tracer.Infof(
"nameserver: returning %s response (%s) for %s to %s", "nameserver: returning %s response (%s%s) for %s to %s",
conn.Verdict.Verb(), conn.Verdict.Verb(),
dns.RcodeToString[rrCache.RCode], dns.RcodeToString[rrCache.RCode],
noAnswerIndicator,
q.ID(), q.ID(),
conn.Process(), conn.Process(),
) )

View file

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/safing/portbase/log" "github.com/safing/portbase/log"
@ -73,10 +74,8 @@ func ZeroIP(msgs ...string) ResponderFunc {
} }
switch { switch {
case hasErr && len(reply.Answer) == 0: case hasErr || len(reply.Answer) == 0:
reply.SetRcode(request, dns.RcodeServerFailure) reply.SetRcode(request, dns.RcodeServerFailure)
case len(reply.Answer) == 0:
reply.SetRcode(request, dns.RcodeNameError)
default: default:
reply.SetRcode(request, dns.RcodeSuccess) reply.SetRcode(request, dns.RcodeSuccess)
} }
@ -115,10 +114,8 @@ func Localhost(msgs ...string) ResponderFunc {
} }
switch { switch {
case hasErr && len(reply.Answer) == 0: case hasErr || len(reply.Answer) == 0:
reply.SetRcode(request, dns.RcodeServerFailure) reply.SetRcode(request, dns.RcodeServerFailure)
case len(reply.Answer) == 0:
reply.SetRcode(request, dns.RcodeNameError)
default: default:
reply.SetRcode(request, dns.RcodeSuccess) reply.SetRcode(request, dns.RcodeSuccess)
} }
@ -134,6 +131,15 @@ func NxDomain(msgs ...string) ResponderFunc {
return func(ctx context.Context, request *dns.Msg) *dns.Msg { return func(ctx context.Context, request *dns.Msg) *dns.Msg {
reply := new(dns.Msg).SetRcode(request, dns.RcodeNameError) reply := new(dns.Msg).SetRcode(request, dns.RcodeNameError)
AddMessagesToReply(ctx, reply, log.InfoLevel, msgs...) 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 return reply
} }
} }

View file

@ -51,8 +51,9 @@ func GetAssignedGlobalAddresses() (ipv4 []net.IP, ipv6 []net.IP, err error) {
} }
var ( var (
myNetworks []*net.IPNet myNetworks []*net.IPNet
myNetworksLock sync.Mutex myNetworksLock sync.Mutex
myNetworksNetworkChangedFlag = GetNetworkChangedFlag()
) )
// IsMyIP returns whether the given unicast IP is currently configured on the local host. // 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() myNetworksLock.Lock()
defer myNetworksLock.Unlock() defer myNetworksLock.Unlock()
// Check for match. // Check if the network changed.
if mine, matched := checkIfMyIP(ip); matched { 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 return mine, nil
} }

View file

@ -13,7 +13,7 @@ func init() {
flag.BoolVar(&privileged, "privileged", false, "run tests that require root/admin privileges") 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() { if testing.Short() {
t.Skip() t.Skip()
} }
@ -21,9 +21,9 @@ func TestGetApproximateInternetLocation(t *testing.T) {
t.Skip("skipping privileged test, active with -privileged argument") t.Skip("skipping privileged test, active with -privileged argument")
} }
loc, err := GetInternetLocation() loc, ok := GetInternetLocation()
if err != nil { if !ok {
t.Fatalf("GetApproximateInternetLocation failed: %s", err) t.Fatal("GetApproximateInternetLocation failed")
} }
t.Logf("GetApproximateInternetLocation: %+v", loc) t.Logf("GetApproximateInternetLocation: %+v", loc)
} }

View file

@ -265,6 +265,10 @@ func NewConnectionFromDNSRequest(ctx context.Context, fqdn string, cnames []stri
dnsConn.Internal = true 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 return dnsConn
} }
@ -295,6 +299,10 @@ func NewConnectionFromExternalDNSRequest(ctx context.Context, fqdn string, cname
dnsConn.Internal = localProfile.Internal 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 return dnsConn, nil
} }
@ -307,20 +315,19 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection {
proc = process.GetUnidentifiedProcess(pkt.Ctx()) 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 scope string
var entity *intel.Entity
var resolverInfo *resolver.ResolverInfo var resolverInfo *resolver.ResolverInfo
if inbound { 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 { switch entity.IPScope {
case netutils.HostLocal: case netutils.HostLocal:
scope = IncomingHost scope = IncomingHost
@ -337,19 +344,11 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection {
} else { } 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 // 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 { if err != nil {
// Try again with the global scope, in case DNS went through the system resolver. // 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 { if err == nil {
lastResolvedDomain := ipinfo.MostRecentDomain() lastResolvedDomain := ipinfo.MostRecentDomain()
@ -364,7 +363,7 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection {
// check if destination IP is the captive portal's IP // check if destination IP is the captive portal's IP
portal := netenv.GetCaptivePortal() portal := netenv.GetCaptivePortal()
if pkt.Info().Dst.Equal(portal.IP) { if pkt.Info().RemoteIP().Equal(portal.IP) {
scope = portal.Domain scope = portal.Domain
entity.Domain = portal.Domain entity.Domain = portal.Domain
} }
@ -416,6 +415,10 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection {
newConn.Internal = localProfile.Internal 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 return newConn
} }
@ -647,12 +650,12 @@ func (conn *Connection) SetInspectorData(new map[uint8]interface{}) {
// String returns a string representation of conn. // String returns a string representation of conn.
func (conn *Connection) String() string { func (conn *Connection) String() string {
switch conn.Scope { switch {
case IncomingHost, IncomingLAN, IncomingInternet, IncomingInvalid: case conn.Inbound:
return fmt.Sprintf("%s <- %s", conn.process, conn.Entity.IP) return fmt.Sprintf("%s <- %s", conn.process, conn.Entity.IP)
case PeerHost, PeerLAN, PeerInternet, PeerInvalid: case conn.Entity.Domain != "":
return fmt.Sprintf("%s -> %s", conn.process, conn.Entity.IP)
default:
return fmt.Sprintf("%s to %s (%s)", conn.process, conn.Entity.Domain, conn.Entity.IP) 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)
} }
} }

View file

@ -55,11 +55,14 @@ func SaveOpenDNSRequest(conn *Connection) {
openDNSRequestsLock.Lock() openDNSRequestsLock.Lock()
defer openDNSRequestsLock.Unlock() defer openDNSRequestsLock.Unlock()
key := getDNSRequestCacheKey(conn.process.Pid, conn.Scope) key := getDNSRequestCacheKey(conn.process.Pid, conn.Entity.Domain)
if existingConn, ok := openDNSRequests[key]; ok { if existingConn, ok := openDNSRequests[key]; ok {
// End previous request and save it.
existingConn.Lock() existingConn.Lock()
defer existingConn.Unlock()
existingConn.Ended = conn.Started existingConn.Ended = conn.Started
existingConn.Unlock()
existingConn.Save()
return return
} }

View file

@ -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. // The checks have passed. We have found a good unused port.
return port, true return port, true
} }

View file

@ -379,12 +379,11 @@ func TestEndpointMatching(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
testEndpointMatch(t, ep, (&intel.Entity{ entity = &intel.Entity{}
IP: net.ParseIP("192.168.0.1"), entity.SetIP(net.ParseIP("192.168.0.1"))
}).Init(), Permitted) testEndpointMatch(t, ep, entity, Permitted)
testEndpointMatch(t, ep, (&intel.Entity{ entity.SetIP(net.ParseIP("151.101.1.164")) // nytimes.com
IP: net.ParseIP("151.101.1.164"), // nytimes.com testEndpointMatch(t, ep, entity, NoMatch)
}).Init(), NoMatch)
// Lists // Lists

View file

@ -1,7 +1,10 @@
package resolver package resolver
import ( import (
"net/http"
"github.com/safing/portbase/api" "github.com/safing/portbase/api"
"github.com/safing/portbase/database/record"
) )
func registerAPI() error { func registerAPI() error {
@ -25,6 +28,24 @@ func registerAPI() error {
return err 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 return nil
} }

View file

@ -9,6 +9,9 @@ func TestNameRecordStorage(t *testing.T) {
testNameRecord := &NameRecord{ testNameRecord := &NameRecord{
Domain: testDomain, Domain: testDomain,
Question: testQuestion, Question: testQuestion,
Resolver: &ResolverInfo{
Type: "dns",
},
} }
err := testNameRecord.Save() err := testNameRecord.Save()

View file

@ -341,6 +341,7 @@ resolveLoop:
} }
// resolve // resolve
log.Tracer(ctx).Tracef("resolver: sending query for %s to %s", q.ID(), resolver.Info.ID())
rrCache, err = resolver.Conn.Query(ctx, q) rrCache, err = resolver.Conn.Query(ctx, q)
if err != nil { if err != nil {
switch { switch {
@ -416,9 +417,11 @@ resolveLoop:
return nil, err 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() { if !q.NoCaching && rrCache.Cacheable() {
rrCache.Clean(minTTL)
err = rrCache.Save() err = rrCache.Save()
if err != nil { if err != nil {
log.Tracer(ctx).Warningf("resolver: failed to cache RR for %s: %s", q.ID(), err) log.Tracer(ctx).Warningf("resolver: failed to cache RR for %s: %s", q.ID(), err)

View file

@ -33,5 +33,5 @@ func TestResolveIPAndValidate(t *testing.T) {
testReverse(t, "2606:4700:4700::1111", "one.one.one.one.", "") 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, "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")
} }

View file

@ -82,12 +82,6 @@ func (rrCache *RRCache) Clean(minExpires uint32) {
lowestTTL = maxTTL 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 // shorten caching
switch { switch {
case rrCache.RCode != dns.RcodeSuccess: case rrCache.RCode != dns.RcodeSuccess:
@ -96,6 +90,9 @@ func (rrCache *RRCache) Clean(minExpires uint32) {
case netenv.IsConnectivityDomain(rrCache.Domain): case netenv.IsConnectivityDomain(rrCache.Domain):
// Responses from these domains might change very quickly depending on the environment. // Responses from these domains might change very quickly depending on the environment.
lowestTTL = 3 lowestTTL = 3
case len(rrCache.Answer) == 0:
// Empty answer section: Domain exists, but not the queried RR.
lowestTTL = 60
case !netenv.Online(): case !netenv.Online():
// Not being fully online could mean that we get funny responses. // Not being fully online could mean that we get funny responses.
lowestTTL = 60 lowestTTL = 60
@ -318,9 +315,12 @@ func (rrCache *RRCache) GetExtraRRs(ctx context.Context, query *dns.Msg) (extra
// Add information about filtered entries. // Add information about filtered entries.
if rrCache.Filtered { if rrCache.Filtered {
if len(rrCache.FilteredEntries) > 1 { 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 { } 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)
} }
} }

View file

@ -13,6 +13,9 @@ func TestCaching(t *testing.T) {
testNameRecord := &NameRecord{ testNameRecord := &NameRecord{
Domain: testDomain, Domain: testDomain,
Question: testQuestion, Question: testQuestion,
Resolver: &ResolverInfo{
Type: "dns",
},
} }
err := testNameRecord.Save() err := testNameRecord.Save()

5
test
View file

@ -176,13 +176,16 @@ echo "running tests for ${platformInfo//$'\n'/ }:"
# run vet/test on packages # run vet/test on packages
for package in $packages; do for package in $packages; do
package=${package#github.com/safing/portmaster}
package=${package#/}
package=$PWD/$package
echo "" echo ""
echo $package echo $package
if [[ $testonly -eq 0 ]]; then if [[ $testonly -eq 0 ]]; then
checkformat $package checkformat $package
run golint -set_exit_status -min_confidence 1.0 $package run golint -set_exit_status -min_confidence 1.0 $package
run go vet $package run go vet $package
run golangci-lint run $GOPATH/src/$package run golangci-lint run $package
fi fi
run go test -cover $fullTestFlags $package run go test -cover $fullTestFlags $package
done done