Merge pull request #379 from safing/fix/endpoint-list-matching
Improve endpoint/rule lists and filtering of DNS requests
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/safing/portmaster/nameserver/nsutil"
|
"github.com/safing/portmaster/nameserver/nsutil"
|
||||||
"github.com/safing/portmaster/network"
|
"github.com/safing/portmaster/network"
|
||||||
|
"github.com/safing/portmaster/network/packet"
|
||||||
"github.com/safing/portmaster/profile/endpoints"
|
"github.com/safing/portmaster/profile/endpoints"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,17 +17,23 @@ var (
|
|||||||
// PreventBypassing checks if the connection should be denied or permitted
|
// PreventBypassing checks if the connection should be denied or permitted
|
||||||
// based on some bypass protection checks.
|
// based on some bypass protection checks.
|
||||||
func PreventBypassing(ctx context.Context, conn *network.Connection) (endpoints.EPResult, string, nsutil.Responder) {
|
func PreventBypassing(ctx context.Context, conn *network.Connection) (endpoints.EPResult, string, nsutil.Responder) {
|
||||||
// Block firefox canary domain to disable DoH
|
// Block firefox canary domain to disable DoH.
|
||||||
if strings.ToLower(conn.Entity.Domain) == "use-application-dns.net." {
|
if strings.ToLower(conn.Entity.Domain) == "use-application-dns.net." {
|
||||||
return endpoints.Denied,
|
return endpoints.Denied,
|
||||||
"blocked canary domain to prevent enabling of DNS-over-HTTPs",
|
"blocked canary domain to prevent enabling of DNS-over-HTTPs",
|
||||||
nsutil.NxDomain()
|
nsutil.NxDomain()
|
||||||
}
|
}
|
||||||
|
|
||||||
if conn.Entity.MatchLists(resolverFilterLists) {
|
// Block direct connections to known DNS resolvers.
|
||||||
return endpoints.Denied,
|
switch packet.IPProtocol(conn.Entity.Protocol) {
|
||||||
"blocked rogue connection to DNS resolver",
|
case packet.ICMP, packet.ICMPv6:
|
||||||
nsutil.ZeroIP()
|
// Make an exception for ICMP, as these IPs are also often used for debugging.
|
||||||
|
default:
|
||||||
|
if conn.Entity.MatchLists(resolverFilterLists) {
|
||||||
|
return endpoints.Denied,
|
||||||
|
"blocked rogue connection to DNS resolver",
|
||||||
|
nsutil.BlockIP()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return endpoints.NoMatch, "", nil
|
return endpoints.NoMatch, "", nil
|
||||||
|
|||||||
@@ -54,13 +54,6 @@ var defaultDeciders = []deciderFn{
|
|||||||
checkAutoPermitRelated,
|
checkAutoPermitRelated,
|
||||||
}
|
}
|
||||||
|
|
||||||
var dnsFromSystemResolverDeciders = []deciderFn{
|
|
||||||
checkEndpointListsForSystemResolverDNSRequests,
|
|
||||||
checkConnectivityDomain,
|
|
||||||
checkBypassPrevention,
|
|
||||||
checkFilterLists,
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecideOnConnection makes a decision about a connection.
|
// DecideOnConnection makes a decision about a connection.
|
||||||
// When called, the connection and profile is already locked.
|
// When called, the connection and profile is already locked.
|
||||||
func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packet.Packet) {
|
func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packet.Packet) {
|
||||||
@@ -99,25 +92,19 @@ func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packe
|
|||||||
conn.Entity.EnableCNAMECheck(ctx, layeredProfile.FilterCNAMEs())
|
conn.Entity.EnableCNAMECheck(ctx, layeredProfile.FilterCNAMEs())
|
||||||
conn.Entity.LoadLists(ctx)
|
conn.Entity.LoadLists(ctx)
|
||||||
|
|
||||||
// DNS request from the system resolver require a special decision process,
|
|
||||||
// because the original requesting process is not known. Here, we only check
|
|
||||||
// global-only and the most important per-app aspects. The resulting
|
|
||||||
// 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, layeredProfile, pkt)
|
|
||||||
if !done {
|
|
||||||
conn.Accept("allowing system resolver dns request", noReasonOptionKey)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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, layeredProfile, pkt)
|
done, defaultAction := runDeciders(ctx, defaultDeciders, conn, layeredProfile, pkt)
|
||||||
if done {
|
if done {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DNS Request are always default allowed, as the endpoint lists could not
|
||||||
|
// be checked fully.
|
||||||
|
if conn.Type == network.DNSRequest {
|
||||||
|
conn.Accept("allowing dns request", noReasonOptionKey)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Deciders did not conclude, use default action.
|
// Deciders did not conclude, use default action.
|
||||||
switch defaultAction {
|
switch defaultAction {
|
||||||
case profile.DefaultActionPermit:
|
case profile.DefaultActionPermit:
|
||||||
@@ -197,6 +184,14 @@ func checkSelfCommunication(ctx context.Context, conn *network.Connection, _ *pr
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkEndpointLists(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
|
func checkEndpointLists(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
|
||||||
|
// DNS request from the system resolver require a special decision process,
|
||||||
|
// because the original requesting process is not known. Here, we only check
|
||||||
|
// global-only and the most important per-app aspects. The resulting
|
||||||
|
// connection is then blocked when the original requesting process is known.
|
||||||
|
if conn.Type == network.DNSRequest && conn.Process().IsSystemResolver() {
|
||||||
|
return checkEndpointListsForSystemResolverDNSRequests(ctx, conn, p)
|
||||||
|
}
|
||||||
|
|
||||||
var result endpoints.EPResult
|
var result endpoints.EPResult
|
||||||
var reason endpoints.Reason
|
var reason endpoints.Reason
|
||||||
|
|
||||||
@@ -210,7 +205,7 @@ func checkEndpointLists(ctx context.Context, conn *network.Connection, p *profil
|
|||||||
optionKey = profile.CfgOptionEndpointsKey
|
optionKey = profile.CfgOptionEndpointsKey
|
||||||
}
|
}
|
||||||
switch result {
|
switch result {
|
||||||
case endpoints.Denied:
|
case endpoints.Denied, endpoints.MatchError:
|
||||||
conn.DenyWithContext(reason.String(), optionKey, reason.Context())
|
conn.DenyWithContext(reason.String(), optionKey, reason.Context())
|
||||||
return true
|
return true
|
||||||
case endpoints.Permitted:
|
case endpoints.Permitted:
|
||||||
@@ -225,13 +220,13 @@ func checkEndpointLists(ctx context.Context, conn *network.Connection, p *profil
|
|||||||
// checkEndpointLists that is only meant for DNS queries by the system
|
// checkEndpointLists that is only meant for DNS queries by the system
|
||||||
// resolver. It only checks the endpoint filter list of the local profile and
|
// resolver. It only checks the endpoint filter list of the local profile and
|
||||||
// does not include the global profile.
|
// does not include the global profile.
|
||||||
func checkEndpointListsForSystemResolverDNSRequests(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
|
func checkEndpointListsForSystemResolverDNSRequests(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile) bool {
|
||||||
profileEndpoints := p.LocalProfile().GetEndpoints()
|
profileEndpoints := p.LocalProfile().GetEndpoints()
|
||||||
if profileEndpoints.IsSet() {
|
if profileEndpoints.IsSet() {
|
||||||
result, reason := profileEndpoints.Match(ctx, conn.Entity)
|
result, reason := profileEndpoints.Match(ctx, conn.Entity)
|
||||||
if endpoints.IsDecision(result) {
|
if endpoints.IsDecision(result) {
|
||||||
switch result {
|
switch result {
|
||||||
case endpoints.Denied:
|
case endpoints.Denied, endpoints.MatchError:
|
||||||
conn.DenyWithContext(reason.String(), profile.CfgOptionEndpointsKey, reason.Context())
|
conn.DenyWithContext(reason.String(), profile.CfgOptionEndpointsKey, reason.Context())
|
||||||
return true
|
return true
|
||||||
case endpoints.Permitted:
|
case endpoints.Permitted:
|
||||||
@@ -396,11 +391,13 @@ func checkResolverScope(_ context.Context, conn *network.Connection, p *profile.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkDomainHeuristics(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
|
func checkDomainHeuristics(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
|
||||||
if !p.DomainHeuristics() {
|
// Don't check domain heuristics if no domain is available.
|
||||||
|
if conn.Entity.Domain == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if conn.Entity.Domain == "" {
|
// Check if domain heuristics are enabled.
|
||||||
|
if !p.DomainHeuristics() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,10 +482,11 @@ func checkAutoPermitRelated(_ context.Context, conn *network.Connection, p *prof
|
|||||||
|
|
||||||
// checkRelation tries to find a relation between a process and a communication. This is for better out of the box experience and is _not_ meant to thwart intentional malware.
|
// checkRelation tries to find a relation between a process and a communication. This is for better out of the box experience and is _not_ meant to thwart intentional malware.
|
||||||
func checkRelation(conn *network.Connection) (related bool, reason string) {
|
func checkRelation(conn *network.Connection) (related bool, reason string) {
|
||||||
if conn.Entity.Domain != "" {
|
// Don't check relation if no domain is available.
|
||||||
|
if conn.Entity.Domain == "" {
|
||||||
return false, ""
|
return false, ""
|
||||||
}
|
}
|
||||||
// don't check for unknown processes
|
// Don't check for unknown processes.
|
||||||
if conn.Process().Pid < 0 {
|
if conn.Process().Pid < 0 {
|
||||||
return false, ""
|
return false, ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ type Entity struct {
|
|||||||
// ASOrg holds the owner's name of the autonomous system.
|
// ASOrg holds the owner's name of the autonomous system.
|
||||||
ASOrg string
|
ASOrg string
|
||||||
|
|
||||||
|
// LocationError holds an error message if fetching the location failed.
|
||||||
|
LocationError string
|
||||||
|
|
||||||
location *geoip.Location
|
location *geoip.Location
|
||||||
|
|
||||||
// BlockedByLists holds list source IDs that
|
// BlockedByLists holds list source IDs that
|
||||||
@@ -86,13 +89,16 @@ type Entity struct {
|
|||||||
// to a list of sources where the entity has been observed in.
|
// to a list of sources where the entity has been observed in.
|
||||||
ListOccurences map[string][]string
|
ListOccurences map[string][]string
|
||||||
|
|
||||||
|
// ListsError holds an error message if fetching the lists failed.
|
||||||
|
ListsError string
|
||||||
|
|
||||||
// we only load each data above at most once
|
// we only load each data above at most once
|
||||||
fetchLocationOnce sync.Once
|
fetchLocationOnce sync.Once
|
||||||
reverseResolveOnce sync.Once
|
reverseResolveOnce sync.Once
|
||||||
loadDomainListOnce sync.Once
|
loadDomainListOnce sync.Once
|
||||||
loadIPListOnce sync.Once
|
loadIPListOnce sync.Once
|
||||||
loadCoutryListOnce sync.Once
|
loadCountryListOnce sync.Once
|
||||||
loadAsnListOnce sync.Once
|
loadAsnListOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initializes the internal state and returns the entity.
|
// Init initializes the internal state and returns the entity.
|
||||||
@@ -142,7 +148,7 @@ func (e *Entity) ResetLists() {
|
|||||||
e.checkCNAMEs = false
|
e.checkCNAMEs = false
|
||||||
e.loadDomainListOnce = sync.Once{}
|
e.loadDomainListOnce = sync.Once{}
|
||||||
e.loadIPListOnce = sync.Once{}
|
e.loadIPListOnce = sync.Once{}
|
||||||
e.loadCoutryListOnce = sync.Once{}
|
e.loadCountryListOnce = sync.Once{}
|
||||||
e.loadAsnListOnce = sync.Once{}
|
e.loadAsnListOnce = sync.Once{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,6 +242,7 @@ func (e *Entity) getLocation(ctx context.Context) {
|
|||||||
loc, err := geoip.GetLocation(e.IP)
|
loc, err := geoip.GetLocation(e.IP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Tracer(ctx).Warningf("intel: failed to get location data for %s: %s", e.IP, err)
|
log.Tracer(ctx).Warningf("intel: failed to get location data for %s: %s", e.IP, err)
|
||||||
|
e.LocationError = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
e.location = loc
|
e.location = loc
|
||||||
@@ -259,7 +266,7 @@ func (e *Entity) GetLocation(ctx context.Context) (*geoip.Location, bool) {
|
|||||||
func (e *Entity) GetCountry(ctx context.Context) (string, bool) {
|
func (e *Entity) GetCountry(ctx context.Context) (string, bool) {
|
||||||
e.getLocation(ctx)
|
e.getLocation(ctx)
|
||||||
|
|
||||||
if e.Country == "" {
|
if e.LocationError != "" {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
return e.Country, true
|
return e.Country, true
|
||||||
@@ -269,13 +276,14 @@ func (e *Entity) GetCountry(ctx context.Context) (string, bool) {
|
|||||||
func (e *Entity) GetASN(ctx context.Context) (uint, bool) {
|
func (e *Entity) GetASN(ctx context.Context) (uint, bool) {
|
||||||
e.getLocation(ctx)
|
e.getLocation(ctx)
|
||||||
|
|
||||||
if e.ASN == 0 {
|
if e.LocationError != "" {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
return e.ASN, true
|
return e.ASN, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lists
|
// Lists
|
||||||
|
|
||||||
func (e *Entity) getLists(ctx context.Context) {
|
func (e *Entity) getLists(ctx context.Context) {
|
||||||
e.getDomainLists(ctx)
|
e.getDomainLists(ctx)
|
||||||
e.getASNLists(ctx)
|
e.getASNLists(ctx)
|
||||||
@@ -305,11 +313,11 @@ func (e *Entity) getDomainLists(ctx context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
log.Tracer(ctx).Tracef("intel: loading domain list for %s", domain)
|
||||||
e.loadDomainListOnce.Do(func() {
|
e.loadDomainListOnce.Do(func() {
|
||||||
var domainsToInspect = []string{domain}
|
var domainsToInspect = []string{domain}
|
||||||
|
|
||||||
if e.checkCNAMEs {
|
if e.checkCNAMEs && len(e.CNAME) > 0 {
|
||||||
log.Tracer(ctx).Tracef("intel: CNAME filtering enabled, checking %v too", e.CNAME)
|
log.Tracer(ctx).Tracef("intel: CNAME filtering enabled, checking %v too", e.CNAME)
|
||||||
domainsToInspect = append(domainsToInspect, e.CNAME...)
|
domainsToInspect = append(domainsToInspect, e.CNAME...)
|
||||||
}
|
}
|
||||||
@@ -327,11 +335,10 @@ func (e *Entity) getDomainLists(ctx context.Context) {
|
|||||||
domains = makeDistinct(domains)
|
domains = makeDistinct(domains)
|
||||||
|
|
||||||
for _, d := range domains {
|
for _, d := range domains {
|
||||||
log.Tracer(ctx).Tracef("intel: loading domain list for %s", d)
|
list, err := filterlists.LookupDomain(d)
|
||||||
var list []string
|
|
||||||
list, err = filterlists.LookupDomain(d)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Tracer(ctx).Errorf("intel: failed to get domain blocklists for %s: %s", d, err)
|
log.Tracer(ctx).Errorf("intel: failed to get domain blocklists for %s: %s", d, err)
|
||||||
|
e.ListsError = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,10 +346,6 @@ func (e *Entity) getDomainLists(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
e.domainListLoaded = true
|
e.domainListLoaded = true
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
e.loadDomainListOnce = sync.Once{}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitDomain(domain string) []string {
|
func splitDomain(domain string) []string {
|
||||||
@@ -377,7 +380,7 @@ func (e *Entity) getASNLists(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
asn, ok := e.GetASN(ctx)
|
asn, ok := e.GetASN(ctx)
|
||||||
if !ok {
|
if !ok || asn == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,7 +390,7 @@ func (e *Entity) getASNLists(ctx context.Context) {
|
|||||||
list, err := filterlists.LookupASNString(asnStr)
|
list, err := filterlists.LookupASNString(asnStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Tracer(ctx).Errorf("intel: failed to get ASN blocklist for %d: %s", asn, err)
|
log.Tracer(ctx).Errorf("intel: failed to get ASN blocklist for %d: %s", asn, err)
|
||||||
e.loadAsnListOnce = sync.Once{}
|
e.ListsError = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,16 +405,16 @@ func (e *Entity) getCountryLists(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
country, ok := e.GetCountry(ctx)
|
country, ok := e.GetCountry(ctx)
|
||||||
if !ok {
|
if !ok || country == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Tracer(ctx).Tracef("intel: loading country list for %s", country)
|
log.Tracer(ctx).Tracef("intel: loading country list for %s", country)
|
||||||
e.loadCoutryListOnce.Do(func() {
|
e.loadCountryListOnce.Do(func() {
|
||||||
list, err := filterlists.LookupCountry(country)
|
list, err := filterlists.LookupCountry(country)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Tracer(ctx).Errorf("intel: failed to load country blocklist for %s: %s", country, err)
|
log.Tracer(ctx).Errorf("intel: failed to load country blocklist for %s: %s", country, err)
|
||||||
e.loadCoutryListOnce = sync.Once{}
|
e.ListsError = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,11 +429,7 @@ func (e *Entity) getIPLists(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ip, ok := e.GetIP()
|
ip, ok := e.GetIP()
|
||||||
if !ok {
|
if !ok || ip == nil {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ip == nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,12 +441,12 @@ func (e *Entity) getIPLists(ctx context.Context) {
|
|||||||
log.Tracer(ctx).Tracef("intel: loading IP list for %s", ip)
|
log.Tracer(ctx).Tracef("intel: loading IP list for %s", ip)
|
||||||
e.loadIPListOnce.Do(func() {
|
e.loadIPListOnce.Do(func() {
|
||||||
list, err := filterlists.LookupIP(ip)
|
list, err := filterlists.LookupIP(ip)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Tracer(ctx).Errorf("intel: failed to get IP blocklist for %s: %s", ip.String(), err)
|
log.Tracer(ctx).Errorf("intel: failed to get IP blocklist for %s: %s", ip.String(), err)
|
||||||
e.loadIPListOnce = sync.Once{}
|
e.ListsError = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
e.ipListLoaded = true
|
e.ipListLoaded = true
|
||||||
e.mergeList(ip.String(), list)
|
e.mergeList(ip.String(), list)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
|
|||||||
return reply(nsutil.NxDomain("nxdomain: " + err.Error()))
|
return reply(nsutil.NxDomain("nxdomain: " + err.Error()))
|
||||||
case errors.Is(err, resolver.ErrBlocked):
|
case errors.Is(err, resolver.ErrBlocked):
|
||||||
tracer.Tracef("nameserver: %s", err)
|
tracer.Tracef("nameserver: %s", err)
|
||||||
return reply(nsutil.ZeroIP("blocked: " + err.Error()))
|
return reply(nsutil.BlockIP("blocked: " + err.Error()))
|
||||||
case errors.Is(err, resolver.ErrLocalhost):
|
case errors.Is(err, resolver.ErrLocalhost):
|
||||||
tracer.Tracef("nameserver: returning localhost records")
|
tracer.Tracef("nameserver: returning localhost records")
|
||||||
return reply(nsutil.Localhost())
|
return reply(nsutil.Localhost())
|
||||||
|
|||||||
@@ -45,50 +45,42 @@ func (rf ResponderFunc) ReplyWithDNS(ctx context.Context, request *dns.Msg) *dns
|
|||||||
return rf(ctx, request)
|
return rf(ctx, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BlockIP is a ResponderFunc than replies with either 0.0.0.17 or ::17 for
|
||||||
|
// each A or AAAA question respectively. If there is no A or AAAA question, it
|
||||||
|
// defaults to replying with NXDomain.
|
||||||
|
func BlockIP(msgs ...string) ResponderFunc {
|
||||||
|
return createResponderFunc(
|
||||||
|
"blocking",
|
||||||
|
"0.0.0.17",
|
||||||
|
"::17",
|
||||||
|
msgs...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// ZeroIP is a ResponderFunc than replies with either 0.0.0.0 or :: for each A
|
// ZeroIP is a ResponderFunc than replies with either 0.0.0.0 or :: for each A
|
||||||
// or AAAA question respectively. If there is no A or AAAA question, it
|
// or AAAA question respectively. If there is no A or AAAA question, it
|
||||||
// defaults to replying with NXDomain.
|
// defaults to replying with NXDomain.
|
||||||
func ZeroIP(msgs ...string) ResponderFunc {
|
func ZeroIP(msgs ...string) ResponderFunc {
|
||||||
return func(ctx context.Context, request *dns.Msg) *dns.Msg {
|
return createResponderFunc(
|
||||||
reply := new(dns.Msg)
|
"zero ip",
|
||||||
hasErr := false
|
"0.0.0.0",
|
||||||
|
"::",
|
||||||
for _, question := range request.Question {
|
msgs...,
|
||||||
var rr dns.RR
|
)
|
||||||
var err error
|
|
||||||
|
|
||||||
switch question.Qtype {
|
|
||||||
case dns.TypeA:
|
|
||||||
rr, err = dns.NewRR(question.Name + " 1 IN A 0.0.0.17")
|
|
||||||
case dns.TypeAAAA:
|
|
||||||
rr, err = dns.NewRR(question.Name + " 1 IN AAAA ::17")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case err != nil:
|
|
||||||
log.Tracer(ctx).Errorf("nameserver: failed to create zero-ip response for %s: %s", question.Name, err)
|
|
||||||
hasErr = true
|
|
||||||
case rr != nil:
|
|
||||||
reply.Answer = append(reply.Answer, rr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case hasErr || len(reply.Answer) == 0:
|
|
||||||
reply.SetRcode(request, dns.RcodeServerFailure)
|
|
||||||
default:
|
|
||||||
reply.SetRcode(request, dns.RcodeSuccess)
|
|
||||||
}
|
|
||||||
|
|
||||||
AddMessagesToReply(ctx, reply, log.InfoLevel, msgs...)
|
|
||||||
|
|
||||||
return reply
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Localhost is a ResponderFunc than replies with localhost IP addresses.
|
// Localhost is a ResponderFunc than replies with localhost IP addresses.
|
||||||
// If there is no A or AAAA question, it defaults to replying with NXDomain.
|
// If there is no A or AAAA question, it defaults to replying with NXDomain.
|
||||||
func Localhost(msgs ...string) ResponderFunc {
|
func Localhost(msgs ...string) ResponderFunc {
|
||||||
|
return createResponderFunc(
|
||||||
|
"localhost",
|
||||||
|
"127.0.0.1",
|
||||||
|
"::1",
|
||||||
|
msgs...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createResponderFunc(responderName, aAnswer, aaaaAnswer string, 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)
|
reply := new(dns.Msg)
|
||||||
hasErr := false
|
hasErr := false
|
||||||
@@ -99,14 +91,14 @@ func Localhost(msgs ...string) ResponderFunc {
|
|||||||
|
|
||||||
switch question.Qtype {
|
switch question.Qtype {
|
||||||
case dns.TypeA:
|
case dns.TypeA:
|
||||||
rr, err = dns.NewRR("localhost. 1 IN A 127.0.0.1")
|
rr, err = dns.NewRR(question.Name + " 1 IN A " + aAnswer)
|
||||||
case dns.TypeAAAA:
|
case dns.TypeAAAA:
|
||||||
rr, err = dns.NewRR("localhost. 1 IN AAAA ::1")
|
rr, err = dns.NewRR(question.Name + " 1 IN AAAA " + aaaaAnswer)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case err != nil:
|
case err != nil:
|
||||||
log.Tracer(ctx).Errorf("nameserver: failed to create localhost response for %s: %s", question.Name, err)
|
log.Tracer(ctx).Errorf("nameserver: failed to create %s response for %s: %s", responderName, question.Name, err)
|
||||||
hasErr = true
|
hasErr = true
|
||||||
case rr != nil:
|
case rr != nil:
|
||||||
reply.Answer = append(reply.Answer, rr)
|
reply.Answer = append(reply.Answer, rr)
|
||||||
@@ -114,8 +106,10 @@ 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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,11 +103,11 @@ func (conn *Connection) ReplyWithDNS(ctx context.Context, request *dns.Msg) *dns
|
|||||||
// Select request responder.
|
// Select request responder.
|
||||||
switch conn.Verdict {
|
switch conn.Verdict {
|
||||||
case VerdictBlock:
|
case VerdictBlock:
|
||||||
return nsutil.ZeroIP().ReplyWithDNS(ctx, request)
|
return nsutil.BlockIP().ReplyWithDNS(ctx, request)
|
||||||
case VerdictDrop:
|
case VerdictDrop:
|
||||||
return nil // Do not respond to request.
|
return nil // Do not respond to request.
|
||||||
case VerdictFailed:
|
case VerdictFailed:
|
||||||
return nsutil.ZeroIP().ReplyWithDNS(ctx, request)
|
return nsutil.BlockIP().ReplyWithDNS(ctx, request)
|
||||||
default:
|
default:
|
||||||
reply := nsutil.ServerFailure().ReplyWithDNS(ctx, request)
|
reply := nsutil.ServerFailure().ReplyWithDNS(ctx, request)
|
||||||
nsutil.AddMessagesToReply(ctx, reply, log.ErrorLevel, "INTERNAL ERROR: incorrect use of Connection DNS Responder")
|
nsutil.AddMessagesToReply(ctx, reply, log.ErrorLevel, "INTERNAL ERROR: incorrect use of Connection DNS Responder")
|
||||||
|
|||||||
@@ -182,6 +182,8 @@ func registerConfiguration() error {
|
|||||||
Additionally, you may supply a protocol and port just behind that using numbers ("6/80") or names ("TCP/HTTP").
|
Additionally, you may supply a protocol and port just behind that using numbers ("6/80") or names ("TCP/HTTP").
|
||||||
In this case the rule is only matched if the protocol and port also match.
|
In this case the rule is only matched if the protocol and port also match.
|
||||||
Example: "192.168.0.1 TCP/HTTP"
|
Example: "192.168.0.1 TCP/HTTP"
|
||||||
|
|
||||||
|
Important: DNS Requests are only matched against domain and filter list rules, all others require an IP address and are checked only with the following IP connection.
|
||||||
`, `"`, "`")
|
`, `"`, "`")
|
||||||
|
|
||||||
// Endpoint Filter List
|
// Endpoint Filter List
|
||||||
|
|||||||
@@ -22,9 +22,18 @@ type EndpointASN struct {
|
|||||||
|
|
||||||
// Matches checks whether the given entity matches this endpoint definition.
|
// Matches checks whether the given entity matches this endpoint definition.
|
||||||
func (ep *EndpointASN) Matches(ctx context.Context, entity *intel.Entity) (EPResult, Reason) {
|
func (ep *EndpointASN) Matches(ctx context.Context, entity *intel.Entity) (EPResult, Reason) {
|
||||||
|
if entity.IP == nil {
|
||||||
|
return NoMatch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !entity.IPScope.IsGlobal() {
|
||||||
|
return NoMatch, nil
|
||||||
|
}
|
||||||
|
|
||||||
asn, ok := entity.GetASN(ctx)
|
asn, ok := entity.GetASN(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return Undeterminable, nil
|
asnStr := strconv.Itoa(int(ep.ASN))
|
||||||
|
return MatchError, ep.makeReason(ep, asnStr, "ASN data not available to match")
|
||||||
}
|
}
|
||||||
|
|
||||||
if asn == ep.ASN {
|
if asn == ep.ASN {
|
||||||
|
|||||||
@@ -21,9 +21,17 @@ type EndpointCountry struct {
|
|||||||
|
|
||||||
// Matches checks whether the given entity matches this endpoint definition.
|
// Matches checks whether the given entity matches this endpoint definition.
|
||||||
func (ep *EndpointCountry) Matches(ctx context.Context, entity *intel.Entity) (EPResult, Reason) {
|
func (ep *EndpointCountry) Matches(ctx context.Context, entity *intel.Entity) (EPResult, Reason) {
|
||||||
|
if entity.IP == nil {
|
||||||
|
return NoMatch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !entity.IPScope.IsGlobal() {
|
||||||
|
return NoMatch, nil
|
||||||
|
}
|
||||||
|
|
||||||
country, ok := entity.GetCountry(ctx)
|
country, ok := entity.GetCountry(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return Undeterminable, nil
|
return MatchError, ep.makeReason(ep, country, "country data not available to match")
|
||||||
}
|
}
|
||||||
|
|
||||||
if country == ep.Country {
|
if country == ep.Country {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ type EndpointIP struct {
|
|||||||
// Matches checks whether the given entity matches this endpoint definition.
|
// Matches checks whether the given entity matches this endpoint definition.
|
||||||
func (ep *EndpointIP) Matches(_ context.Context, entity *intel.Entity) (EPResult, Reason) {
|
func (ep *EndpointIP) Matches(_ context.Context, entity *intel.Entity) (EPResult, Reason) {
|
||||||
if entity.IP == nil {
|
if entity.IP == nil {
|
||||||
return Undeterminable, nil
|
return NoMatch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if ep.IP.Equal(entity.IP) {
|
if ep.IP.Equal(entity.IP) {
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ type EndpointIPRange struct {
|
|||||||
// Matches checks whether the given entity matches this endpoint definition.
|
// Matches checks whether the given entity matches this endpoint definition.
|
||||||
func (ep *EndpointIPRange) Matches(_ context.Context, entity *intel.Entity) (EPResult, Reason) {
|
func (ep *EndpointIPRange) Matches(_ context.Context, entity *intel.Entity) (EPResult, Reason) {
|
||||||
if entity.IP == nil {
|
if entity.IP == nil {
|
||||||
return Undeterminable, nil
|
return NoMatch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if ep.Net.Contains(entity.IP) {
|
if ep.Net.Contains(entity.IP) {
|
||||||
return ep.match(ep, entity, ep.Net.String(), "IP is in")
|
return ep.match(ep, entity, ep.Net.String(), "IP is in")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ type EndpointScope struct {
|
|||||||
// Matches checks whether the given entity matches this endpoint definition.
|
// Matches checks whether the given entity matches this endpoint definition.
|
||||||
func (ep *EndpointScope) Matches(_ context.Context, entity *intel.Entity) (EPResult, Reason) {
|
func (ep *EndpointScope) Matches(_ context.Context, entity *intel.Entity) (EPResult, Reason) {
|
||||||
if entity.IP == nil {
|
if entity.IP == nil {
|
||||||
return Undeterminable, nil
|
return NoMatch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var scope uint8
|
var scope uint8
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ type EndpointBase struct { //nolint:maligned // TODO
|
|||||||
|
|
||||||
func (ep *EndpointBase) match(s fmt.Stringer, entity *intel.Entity, value, desc string, keyval ...interface{}) (EPResult, Reason) {
|
func (ep *EndpointBase) match(s fmt.Stringer, entity *intel.Entity, value, desc string, keyval ...interface{}) (EPResult, Reason) {
|
||||||
result := ep.matchesPPP(entity)
|
result := ep.matchesPPP(entity)
|
||||||
if result == Undeterminable || result == NoMatch {
|
if result == NoMatch {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,10 +57,6 @@ func (ep *EndpointBase) makeReason(s fmt.Stringer, value, desc string, keyval ..
|
|||||||
func (ep *EndpointBase) matchesPPP(entity *intel.Entity) (result EPResult) {
|
func (ep *EndpointBase) matchesPPP(entity *intel.Entity) (result EPResult) {
|
||||||
// only check if protocol is defined
|
// only check if protocol is defined
|
||||||
if ep.Protocol > 0 {
|
if ep.Protocol > 0 {
|
||||||
// if protocol is unknown, return Undeterminable
|
|
||||||
if entity.Protocol == 0 {
|
|
||||||
return Undeterminable
|
|
||||||
}
|
|
||||||
// if protocol does not match, return NoMatch
|
// if protocol does not match, return NoMatch
|
||||||
if entity.Protocol != ep.Protocol {
|
if entity.Protocol != ep.Protocol {
|
||||||
return NoMatch
|
return NoMatch
|
||||||
@@ -69,10 +65,6 @@ func (ep *EndpointBase) matchesPPP(entity *intel.Entity) (result EPResult) {
|
|||||||
|
|
||||||
// only check if port is defined
|
// only check if port is defined
|
||||||
if ep.StartPort > 0 {
|
if ep.StartPort > 0 {
|
||||||
// if port is unknown, return Undeterminable
|
|
||||||
if entity.DstPort() == 0 {
|
|
||||||
return Undeterminable
|
|
||||||
}
|
|
||||||
// if port does not match, return NoMatch
|
// if port does not match, return NoMatch
|
||||||
if entity.DstPort() < ep.StartPort || entity.DstPort() > ep.EndPort {
|
if entity.DstPort() < ep.StartPort || entity.DstPort() > ep.EndPort {
|
||||||
return NoMatch
|
return NoMatch
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ type EPResult uint8
|
|||||||
// Endpoint matching return values
|
// Endpoint matching return values
|
||||||
const (
|
const (
|
||||||
NoMatch EPResult = iota
|
NoMatch EPResult = iota
|
||||||
Undeterminable
|
MatchError
|
||||||
Denied
|
Denied
|
||||||
Permitted
|
Permitted
|
||||||
)
|
)
|
||||||
@@ -25,7 +25,7 @@ const (
|
|||||||
// IsDecision returns true if result represents a decision
|
// IsDecision returns true if result represents a decision
|
||||||
// and false if result is NoMatch or Undeterminable.
|
// and false if result is NoMatch or Undeterminable.
|
||||||
func IsDecision(result EPResult) bool {
|
func IsDecision(result EPResult) bool {
|
||||||
return result == Denied || result == Permitted || result == Undeterminable
|
return result == Denied || result == Permitted || result == MatchError
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseEndpoints parses a list of endpoints and returns a list of Endpoints for matching.
|
// ParseEndpoints parses a list of endpoints and returns a list of Endpoints for matching.
|
||||||
@@ -88,8 +88,8 @@ func (epr EPResult) String() string {
|
|||||||
switch epr {
|
switch epr {
|
||||||
case NoMatch:
|
case NoMatch:
|
||||||
return "No Match"
|
return "No Match"
|
||||||
case Undeterminable:
|
case MatchError:
|
||||||
return "Undeterminable"
|
return "Match Error"
|
||||||
case Denied:
|
case Denied:
|
||||||
return "Denied"
|
return "Denied"
|
||||||
case Permitted:
|
case Permitted:
|
||||||
|
|||||||
Reference in New Issue
Block a user