mirror of
https://github.com/safing/portmaster
synced 2025-09-02 10:39:22 +00:00
Merge pull request #423 from safing/fix/patch-set-9
Fix DNS request context and improve location proximity alg
This commit is contained in:
commit
a9184576b3
8 changed files with 133 additions and 85 deletions
|
@ -264,8 +264,8 @@ func UpdateIPsAndCNAMEs(q *resolver.Query, rrCache *resolver.RRCache, conn *netw
|
||||||
// Create new record for this IP.
|
// Create new record for this IP.
|
||||||
record := resolver.ResolvedDomain{
|
record := resolver.ResolvedDomain{
|
||||||
Domain: q.FQDN,
|
Domain: q.FQDN,
|
||||||
RRCache: rrCache,
|
|
||||||
Resolver: rrCache.Resolver,
|
Resolver: rrCache.Resolver,
|
||||||
|
DNSRequestContext: rrCache.ToDNSRequestContext(),
|
||||||
Expires: rrCache.Expires,
|
Expires: rrCache.Expires,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,57 +30,55 @@ type Coordinates struct {
|
||||||
Longitude float64 `maxminddb:"longitude"`
|
Longitude float64 `maxminddb:"longitude"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// About GeoLite2 City accuracy_radius:
|
|
||||||
//
|
|
||||||
// range: 1-1000
|
|
||||||
// seen values (from memory): 1,5,10,20,50,100,200,500,1000
|
|
||||||
// default seems to be 100
|
|
||||||
//
|
|
||||||
// examples:
|
|
||||||
// 1.1.1/24 has 1000: Anycast
|
|
||||||
// 8.8.0/19 has 1000: Anycast
|
|
||||||
// 8.8.52/22 has 1: City of Westfield
|
|
||||||
//
|
|
||||||
// Conclusion:
|
|
||||||
// - Ignore location data completely if accuracy_radius > 500
|
|
||||||
|
|
||||||
// EstimateNetworkProximity aims to calculate the distance between two network locations. Returns a proximity value between 0 (far away) and 100 (nearby).
|
// EstimateNetworkProximity aims to calculate the distance between two network locations. Returns a proximity value between 0 (far away) and 100 (nearby).
|
||||||
func (l *Location) EstimateNetworkProximity(to *Location) (proximity int) {
|
func (l *Location) EstimateNetworkProximity(to *Location) (proximity int) {
|
||||||
// Distance Value:
|
/*
|
||||||
// 0: other side of the Internet
|
Distance Value
|
||||||
// 100: same network/datacenter
|
|
||||||
|
|
||||||
// Weighting:
|
- 0: Other side of the Internet.
|
||||||
// continent match: 25
|
- 100: Very near, up to same network / datacenter.
|
||||||
// country match: 20
|
|
||||||
// AS owner match: 25
|
|
||||||
// AS network match: 20
|
|
||||||
// coordinate distance: 0-10
|
|
||||||
|
|
||||||
// continent match: 25
|
Weighting Goal
|
||||||
if l.Continent.Code == to.Continent.Code {
|
|
||||||
|
- Exposure to different networks shall be limited as much as possible.
|
||||||
|
- A single network should not see a connection over a large distance.
|
||||||
|
- Latency should be low.
|
||||||
|
|
||||||
|
Weighting Intentions
|
||||||
|
|
||||||
|
- Being on the same continent is better than being in the same AS.
|
||||||
|
- Being in the same country is better than having low coordinate distance.
|
||||||
|
- Coordinate distance is only a tie breaker, as accuracy varies heavily.
|
||||||
|
- Same AS with lower coordinate distance beats being on the same continent.
|
||||||
|
|
||||||
|
Weighting Configuration
|
||||||
|
|
||||||
|
- Continent match: 30
|
||||||
|
- Country match: 25
|
||||||
|
- ASOrg match: 20
|
||||||
|
- ASN match: 15
|
||||||
|
- Coordinate distance: 0-10
|
||||||
|
*/
|
||||||
|
|
||||||
|
if l.Continent.Code != "" &&
|
||||||
|
l.Continent.Code == to.Continent.Code {
|
||||||
|
proximity += 30
|
||||||
|
if l.Country.ISOCode != "" &&
|
||||||
|
l.Country.ISOCode == to.Country.ISOCode {
|
||||||
proximity += 25
|
proximity += 25
|
||||||
// country match: 20
|
}
|
||||||
if l.Country.ISOCode == to.Country.ISOCode {
|
}
|
||||||
|
|
||||||
|
if l.AutonomousSystemOrganization != "" &&
|
||||||
|
l.AutonomousSystemOrganization == to.AutonomousSystemOrganization {
|
||||||
proximity += 20
|
proximity += 20
|
||||||
|
if l.AutonomousSystemNumber != 0 &&
|
||||||
|
l.AutonomousSystemNumber == to.AutonomousSystemNumber {
|
||||||
|
proximity += 15
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AS owner match: 25
|
// Check coordinates and adjust accuracy value.
|
||||||
if l.AutonomousSystemOrganization == to.AutonomousSystemOrganization {
|
|
||||||
proximity += 25
|
|
||||||
// AS network match: 20
|
|
||||||
if l.AutonomousSystemNumber == to.AutonomousSystemNumber {
|
|
||||||
proximity += 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// coordinate distance: 0-10
|
|
||||||
fromCoords := haversine.Coord{Lat: l.Coordinates.Latitude, Lon: l.Coordinates.Longitude}
|
|
||||||
toCoords := haversine.Coord{Lat: to.Coordinates.Latitude, Lon: to.Coordinates.Longitude}
|
|
||||||
_, km := haversine.Distance(fromCoords, toCoords)
|
|
||||||
|
|
||||||
// adjust accuracy value
|
|
||||||
accuracy := l.Coordinates.AccuracyRadius
|
accuracy := l.Coordinates.AccuracyRadius
|
||||||
switch {
|
switch {
|
||||||
case l.Coordinates.Latitude == 0 && l.Coordinates.Longitude == 0:
|
case l.Coordinates.Latitude == 0 && l.Coordinates.Longitude == 0:
|
||||||
|
@ -93,12 +91,37 @@ func (l *Location) EstimateNetworkProximity(to *Location) (proximity int) {
|
||||||
accuracy = to.Coordinates.AccuracyRadius
|
accuracy = to.Coordinates.AccuracyRadius
|
||||||
}
|
}
|
||||||
|
|
||||||
if km <= 10 && accuracy <= 100 {
|
/*
|
||||||
|
About the Accuracy Radius
|
||||||
|
|
||||||
|
- Range: 1-1000
|
||||||
|
- Seen values (estimation): 1,5,10,20,50,100,200,500,1000
|
||||||
|
- The default seems to be 100.
|
||||||
|
|
||||||
|
Cxamples
|
||||||
|
|
||||||
|
- 1.1.1/24 has 1000: Anycast
|
||||||
|
- 8.8.0/19 has 1000: Anycast
|
||||||
|
- 8.8.52/22 has 1: City of Westfield
|
||||||
|
|
||||||
|
Conclusion
|
||||||
|
|
||||||
|
- Ignore or penalize high accuracy radius.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Calculate coordinate distance in kilometers.
|
||||||
|
fromCoords := haversine.Coord{Lat: l.Coordinates.Latitude, Lon: l.Coordinates.Longitude}
|
||||||
|
toCoords := haversine.Coord{Lat: to.Coordinates.Latitude, Lon: to.Coordinates.Longitude}
|
||||||
|
_, km := haversine.Distance(fromCoords, toCoords)
|
||||||
|
|
||||||
|
if km <= 50 && accuracy <= 100 {
|
||||||
|
// Give a flat out ten for highly accurate coordinates within 50km.
|
||||||
proximity += 10
|
proximity += 10
|
||||||
} else {
|
} else {
|
||||||
|
// Else, take a percentage.
|
||||||
distanceInPercent := (earthCircumferenceInKm - km) * 100 / earthCircumferenceInKm
|
distanceInPercent := (earthCircumferenceInKm - km) * 100 / earthCircumferenceInKm
|
||||||
|
|
||||||
// apply penalty for locations with low accuracy (targeting accuracy radius >100)
|
// Apply penalty for locations with low accuracy (targeting accuracy radius >100).
|
||||||
accuracyModifier := 1 - float64(accuracy)/2000
|
accuracyModifier := 1 - float64(accuracy)/2000
|
||||||
proximity += int(distanceInPercent * 0.10 * accuracyModifier)
|
proximity += int(distanceInPercent * 0.10 * accuracyModifier)
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,6 +141,12 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
|
||||||
// Once we decided on the connection we might need to save it to the database,
|
// Once we decided on the connection we might need to save it to the database,
|
||||||
// so we defer that check for now.
|
// so we defer that check for now.
|
||||||
defer func() {
|
defer func() {
|
||||||
|
// Add metadata to connection.
|
||||||
|
if rrCache != nil {
|
||||||
|
conn.DNSContext = rrCache.ToDNSRequestContext()
|
||||||
|
conn.Resolver = rrCache.Resolver
|
||||||
|
}
|
||||||
|
|
||||||
switch conn.Verdict {
|
switch conn.Verdict {
|
||||||
// We immediately save blocked, dropped or failed verdicts so
|
// We immediately save blocked, dropped or failed verdicts so
|
||||||
// they pop up in the UI.
|
// they pop up in the UI.
|
||||||
|
@ -222,27 +228,19 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Handle special cases.
|
// Handle special cases.
|
||||||
if rrCache == nil {
|
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:
|
||||||
|
|
||||||
// Add dns context and resolver to connection.
|
|
||||||
conn.DNSContext = rrCache
|
|
||||||
conn.Resolver = rrCache.Resolver
|
|
||||||
|
|
||||||
// Return now if NXDomain.
|
// Return now if NXDomain.
|
||||||
if rrCache.RCode == dns.RcodeNameError {
|
|
||||||
return reply(nsutil.NxDomain("no answer found (NXDomain)"))
|
return reply(nsutil.NxDomain("no answer found (NXDomain)"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check with firewall again after resolving.
|
||||||
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)
|
||||||
|
|
||||||
// Add dns context and resolver to connection.
|
|
||||||
conn.DNSContext = rrCache
|
|
||||||
conn.Resolver = rrCache.Resolver
|
|
||||||
|
|
||||||
// Check again if there is a responder from the firewall.
|
// Check again if there is a responder from the firewall.
|
||||||
if responder, ok := conn.Reason.Context.(nsutil.Responder); ok {
|
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)
|
tracer.Infof("nameserver: handing over request for %s to special filter responder: %s", q.ID(), conn.Reason.Msg)
|
||||||
|
|
|
@ -42,6 +42,8 @@ var (
|
||||||
DNSFallbackTestDomain = "dns-check.safing.io."
|
DNSFallbackTestDomain = "dns-check.safing.io."
|
||||||
DNSFallbackTestExpectedIP = net.IPv4(0, 65, 67, 75) // Ascii: \0ACK
|
DNSFallbackTestExpectedIP = net.IPv4(0, 65, 67, 75) // Ascii: \0ACK
|
||||||
|
|
||||||
|
ConnectedToSPN = abool.New()
|
||||||
|
|
||||||
// SpecialCaptivePortalDomain is the domain name used to point to the detected captive portal IP
|
// SpecialCaptivePortalDomain is the domain name used to point to the detected captive portal IP
|
||||||
// or the captive portal test IP. The default value should be overridden by the resolver package,
|
// or the captive portal test IP. The default value should be overridden by the resolver package,
|
||||||
// which defines the custom internal domain name to use.
|
// which defines the custom internal domain name to use.
|
||||||
|
@ -350,6 +352,12 @@ func checkOnlineStatus(ctx context.Context) {
|
||||||
return StatusUnknown
|
return StatusUnknown
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
// 0) check if connected to SPN
|
||||||
|
|
||||||
|
if ConnectedToSPN.IsSet() {
|
||||||
|
updateOnlineStatus(StatusOnline, nil, "connected to SPN")
|
||||||
|
}
|
||||||
|
|
||||||
// 1) check for addresses
|
// 1) check for addresses
|
||||||
|
|
||||||
ipv4, ipv6, err := GetAssignedAddresses()
|
ipv4, ipv6, err := GetAssignedAddresses()
|
||||||
|
|
|
@ -145,7 +145,7 @@ type Connection struct { //nolint:maligned // TODO: fix alignment
|
||||||
ProcessContext ProcessContext
|
ProcessContext ProcessContext
|
||||||
// DNSContext holds additional information about the DNS request that was
|
// DNSContext holds additional information about the DNS request that was
|
||||||
// probably used to resolve the IP of this connection.
|
// probably used to resolve the IP of this connection.
|
||||||
DNSContext *resolver.RRCache
|
DNSContext *resolver.DNSRequestContext
|
||||||
// TunnelContext holds additional information about the tunnel that this
|
// TunnelContext holds additional information about the tunnel that this
|
||||||
// connection is using.
|
// connection is using.
|
||||||
TunnelContext interface{}
|
TunnelContext interface{}
|
||||||
|
@ -333,7 +333,7 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection {
|
||||||
|
|
||||||
var scope string
|
var scope string
|
||||||
var resolverInfo *resolver.ResolverInfo
|
var resolverInfo *resolver.ResolverInfo
|
||||||
var dnsContext *resolver.RRCache
|
var dnsContext *resolver.DNSRequestContext
|
||||||
|
|
||||||
if inbound {
|
if inbound {
|
||||||
|
|
||||||
|
@ -365,7 +365,7 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection {
|
||||||
scope = lastResolvedDomain.Domain
|
scope = lastResolvedDomain.Domain
|
||||||
entity.Domain = lastResolvedDomain.Domain
|
entity.Domain = lastResolvedDomain.Domain
|
||||||
entity.CNAME = lastResolvedDomain.CNAMEs
|
entity.CNAME = lastResolvedDomain.CNAMEs
|
||||||
dnsContext = lastResolvedDomain.RRCache
|
dnsContext = lastResolvedDomain.DNSRequestContext
|
||||||
resolverInfo = lastResolvedDomain.Resolver
|
resolverInfo = lastResolvedDomain.Resolver
|
||||||
removeOpenDNSRequest(proc.Pid, lastResolvedDomain.Domain)
|
removeOpenDNSRequest(proc.Pid, lastResolvedDomain.Domain)
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,8 +44,8 @@ type ResolvedDomain struct {
|
||||||
// information.
|
// information.
|
||||||
Resolver *ResolverInfo
|
Resolver *ResolverInfo
|
||||||
|
|
||||||
// RRCache holds the DNS response that was received for this domain.
|
// DNSRequestContext holds the DNS request context.
|
||||||
RRCache *RRCache
|
DNSRequestContext *DNSRequestContext
|
||||||
|
|
||||||
// Expires holds the timestamp when this entry expires.
|
// Expires holds the timestamp when this entry expires.
|
||||||
// This does not mean that the entry may not be used anymore afterwards,
|
// This does not mean that the entry may not be used anymore afterwards,
|
||||||
|
|
39
resolver/rr_context.go
Normal file
39
resolver/rr_context.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DNSRequestContext is a static structure to add information to DNS request connections.
|
||||||
|
type DNSRequestContext struct {
|
||||||
|
Domain string
|
||||||
|
Question string
|
||||||
|
RCode string
|
||||||
|
|
||||||
|
ServedFromCache bool
|
||||||
|
RequestingNew bool
|
||||||
|
IsBackup bool
|
||||||
|
Filtered bool
|
||||||
|
|
||||||
|
Modified time.Time
|
||||||
|
Expires time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToDNSRequestContext returns a new DNSRequestContext of the RRCache.
|
||||||
|
func (rrCache *RRCache) ToDNSRequestContext() *DNSRequestContext {
|
||||||
|
return &DNSRequestContext{
|
||||||
|
Domain: rrCache.Domain,
|
||||||
|
Question: rrCache.Question.String(),
|
||||||
|
RCode: dns.RcodeToString[rrCache.RCode],
|
||||||
|
|
||||||
|
ServedFromCache: rrCache.ServedFromCache,
|
||||||
|
RequestingNew: rrCache.RequestingNew,
|
||||||
|
IsBackup: rrCache.IsBackup,
|
||||||
|
Filtered: rrCache.Filtered,
|
||||||
|
|
||||||
|
Modified: time.Unix(rrCache.Modified, 0),
|
||||||
|
Expires: time.Unix(rrCache.Expires, 0),
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ package resolver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
@ -45,25 +44,6 @@ type RRCache struct {
|
||||||
Modified int64
|
Modified int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rrCache *RRCache) MarshalJSON() ([]byte, error) {
|
|
||||||
var record = struct {
|
|
||||||
RRCache
|
|
||||||
|
|
||||||
Question string
|
|
||||||
RCode string
|
|
||||||
Modified time.Time
|
|
||||||
Expires time.Time
|
|
||||||
}{
|
|
||||||
RRCache: *rrCache,
|
|
||||||
Question: rrCache.Question.String(),
|
|
||||||
RCode: dns.RcodeToString[rrCache.RCode],
|
|
||||||
Modified: time.Unix(rrCache.Modified, 0),
|
|
||||||
Expires: time.Unix(rrCache.Expires, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal(record)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID returns the ID of the RRCache consisting of the domain and question type.
|
// ID returns the ID of the RRCache consisting of the domain and question type.
|
||||||
func (rrCache *RRCache) ID() string {
|
func (rrCache *RRCache) ID() string {
|
||||||
return rrCache.Domain + rrCache.Question.String()
|
return rrCache.Domain + rrCache.Question.String()
|
||||||
|
|
Loading…
Add table
Reference in a new issue