mirror of
https://github.com/safing/portmaster
synced 2025-09-01 10:09:11 +00:00
Add special handling to dns queries from the system resolver
This commit is contained in:
parent
a38f546da8
commit
01e7160bfe
5 changed files with 129 additions and 75 deletions
|
@ -16,7 +16,7 @@ import (
|
|||
"github.com/safing/portmaster/resolver"
|
||||
)
|
||||
|
||||
func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, scope int8) ([]dns.RR, []string, int, string) {
|
||||
func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, resolverScope netutils.IPScope, sysResolver bool) ([]dns.RR, []string, int, string) {
|
||||
goodEntries := make([]dns.RR, 0, len(entries))
|
||||
filteredRecords := make([]string, 0, len(entries))
|
||||
|
||||
|
@ -38,16 +38,16 @@ func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, scope int8) (
|
|||
goodEntries = append(goodEntries, rr)
|
||||
continue
|
||||
}
|
||||
classification := netutils.ClassifyIP(ip)
|
||||
ipScope := netutils.GetIPScope(ip)
|
||||
|
||||
if p.RemoveOutOfScopeDNS() {
|
||||
switch {
|
||||
case classification == netutils.HostLocal:
|
||||
case ipScope.IsLocalhost():
|
||||
// No DNS should return localhost addresses
|
||||
filteredRecords = append(filteredRecords, rr.String())
|
||||
interveningOptionKey = profile.CfgOptionRemoveOutOfScopeDNSKey
|
||||
continue
|
||||
case scope == netutils.Global && (classification == netutils.SiteLocal || classification == netutils.LinkLocal):
|
||||
case resolverScope.IsGlobal() && ipScope.IsLAN() && !sysResolver:
|
||||
// No global DNS should return LAN addresses
|
||||
filteredRecords = append(filteredRecords, rr.String())
|
||||
interveningOptionKey = profile.CfgOptionRemoveOutOfScopeDNSKey
|
||||
|
@ -55,18 +55,18 @@ func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, scope int8) (
|
|||
}
|
||||
}
|
||||
|
||||
if p.RemoveBlockedDNS() {
|
||||
if p.RemoveBlockedDNS() && !sysResolver {
|
||||
// filter by flags
|
||||
switch {
|
||||
case p.BlockScopeInternet() && classification == netutils.Global:
|
||||
case p.BlockScopeInternet() && ipScope.IsGlobal():
|
||||
filteredRecords = append(filteredRecords, rr.String())
|
||||
interveningOptionKey = profile.CfgOptionBlockScopeInternetKey
|
||||
continue
|
||||
case p.BlockScopeLAN() && (classification == netutils.SiteLocal || classification == netutils.LinkLocal):
|
||||
case p.BlockScopeLAN() && ipScope.IsLAN():
|
||||
filteredRecords = append(filteredRecords, rr.String())
|
||||
interveningOptionKey = profile.CfgOptionBlockScopeLANKey
|
||||
continue
|
||||
case p.BlockScopeLocal() && classification == netutils.HostLocal:
|
||||
case p.BlockScopeLocal() && ipScope.IsLocalhost():
|
||||
filteredRecords = append(filteredRecords, rr.String())
|
||||
interveningOptionKey = profile.CfgOptionBlockScopeLocalKey
|
||||
continue
|
||||
|
@ -83,7 +83,7 @@ func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, scope int8) (
|
|||
return goodEntries, filteredRecords, allowedAddressRecords, interveningOptionKey
|
||||
}
|
||||
|
||||
func filterDNSResponse(conn *network.Connection, rrCache *resolver.RRCache) *resolver.RRCache {
|
||||
func filterDNSResponse(conn *network.Connection, rrCache *resolver.RRCache, sysResolver bool) *resolver.RRCache {
|
||||
p := conn.Process().Profile()
|
||||
|
||||
// do not modify own queries
|
||||
|
@ -104,11 +104,11 @@ func filterDNSResponse(conn *network.Connection, rrCache *resolver.RRCache) *res
|
|||
var validIPs int
|
||||
var interveningOptionKey string
|
||||
|
||||
rrCache.Answer, filteredRecords, validIPs, interveningOptionKey = filterDNSSection(rrCache.Answer, p, rrCache.ServerScope)
|
||||
rrCache.Answer, filteredRecords, validIPs, interveningOptionKey = filterDNSSection(rrCache.Answer, p, rrCache.Resolver.IPScope, sysResolver)
|
||||
rrCache.FilteredEntries = append(rrCache.FilteredEntries, filteredRecords...)
|
||||
|
||||
// we don't count the valid IPs in the extra section
|
||||
rrCache.Extra, filteredRecords, _, _ = filterDNSSection(rrCache.Extra, p, rrCache.ServerScope)
|
||||
rrCache.Extra, filteredRecords, _, _ = filterDNSSection(rrCache.Extra, p, rrCache.Resolver.IPScope, sysResolver)
|
||||
rrCache.FilteredEntries = append(rrCache.FilteredEntries, filteredRecords...)
|
||||
|
||||
if len(rrCache.FilteredEntries) > 0 {
|
||||
|
@ -160,8 +160,9 @@ func filterDNSResponse(conn *network.Connection, rrCache *resolver.RRCache) *res
|
|||
return rrCache
|
||||
}
|
||||
|
||||
// DecideOnResolvedDNS filters a dns response according to the application profile and settings.
|
||||
func DecideOnResolvedDNS(
|
||||
// FilterResolvedDNS filters a dns response according to the application
|
||||
// profile and settings.
|
||||
func FilterResolvedDNS(
|
||||
ctx context.Context,
|
||||
conn *network.Connection,
|
||||
q *resolver.Query,
|
||||
|
@ -174,14 +175,15 @@ func DecideOnResolvedDNS(
|
|||
return rrCache
|
||||
}
|
||||
|
||||
updatedRR := filterDNSResponse(conn, rrCache)
|
||||
// Only filter criticial things if request comes from the system resolver.
|
||||
sysResolver := conn.Process().IsSystemResolver()
|
||||
|
||||
updatedRR := filterDNSResponse(conn, rrCache, sysResolver)
|
||||
if updatedRR == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
updateIPsAndCNAMEs(q, rrCache, conn)
|
||||
|
||||
if mayBlockCNAMEs(ctx, conn) {
|
||||
if !sysResolver && mayBlockCNAMEs(ctx, conn) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -213,14 +215,23 @@ func mayBlockCNAMEs(ctx context.Context, conn *network.Connection) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// updateIPsAndCNAMEs saves all the IP->Name mappings to the cache database and
|
||||
// UpdateIPsAndCNAMEs saves all the IP->Name mappings to the cache database and
|
||||
// updates the CNAMEs in the Connection's Entity.
|
||||
func updateIPsAndCNAMEs(q *resolver.Query, rrCache *resolver.RRCache, conn *network.Connection) {
|
||||
func UpdateIPsAndCNAMEs(q *resolver.Query, rrCache *resolver.RRCache, conn *network.Connection) {
|
||||
// Sanity check input, as this is called from defer.
|
||||
if q == nil || rrCache == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Get profileID for scoping IPInfo.
|
||||
var profileID string
|
||||
proc := conn.Process()
|
||||
if proc != nil {
|
||||
profileID = proc.LocalProfileKey
|
||||
localProfile := conn.Process().Profile().LocalProfile()
|
||||
switch localProfile.ID {
|
||||
case profile.UnidentifiedProfileID,
|
||||
profile.SystemResolverProfileID:
|
||||
profileID = resolver.IPInfoProfileScopeGlobal
|
||||
default:
|
||||
profileID = localProfile.ID
|
||||
}
|
||||
|
||||
// Collect IPs and CNAMEs.
|
||||
|
@ -249,8 +260,9 @@ func updateIPsAndCNAMEs(q *resolver.Query, rrCache *resolver.RRCache, conn *netw
|
|||
|
||||
// Create new record for this IP.
|
||||
record := resolver.ResolvedDomain{
|
||||
Domain: q.FQDN,
|
||||
Expires: rrCache.Expires,
|
||||
Domain: q.FQDN,
|
||||
Expires: rrCache.Expires,
|
||||
Resolver: rrCache.Resolver,
|
||||
}
|
||||
|
||||
// Resolve all CNAMEs in the correct order and add the to the record.
|
||||
|
|
|
@ -39,7 +39,7 @@ const noReasonOptionKey = ""
|
|||
|
||||
type deciderFn func(context.Context, *network.Connection, packet.Packet) bool
|
||||
|
||||
var deciders = []deciderFn{
|
||||
var defaultDeciders = []deciderFn{
|
||||
checkPortmasterConnection,
|
||||
checkSelfCommunication,
|
||||
checkConnectionType,
|
||||
|
@ -53,6 +53,11 @@ var deciders = []deciderFn{
|
|||
checkAutoPermitRelated,
|
||||
}
|
||||
|
||||
var dnsFromSystemResolverDeciders = []deciderFn{
|
||||
checkConnectivityDomain,
|
||||
checkBypassPrevention,
|
||||
}
|
||||
|
||||
// DecideOnConnection makes a decision about a connection.
|
||||
// When called, the connection and profile is already locked.
|
||||
func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packet.Packet) {
|
||||
|
@ -79,8 +84,21 @@ func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packe
|
|||
}
|
||||
}
|
||||
|
||||
// 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, pkt)
|
||||
if !done {
|
||||
conn.Accept("permitting system resolver dns request", noReasonOptionKey)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Run all deciders and return if they came to a conclusion.
|
||||
done, defaultAction := runDeciders(ctx, conn, pkt)
|
||||
done, defaultAction := runDeciders(ctx, defaultDeciders, conn, pkt)
|
||||
if done {
|
||||
return
|
||||
}
|
||||
|
@ -96,7 +114,7 @@ func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packe
|
|||
}
|
||||
}
|
||||
|
||||
func runDeciders(ctx context.Context, conn *network.Connection, pkt packet.Packet) (done bool, defaultAction uint8) {
|
||||
func runDeciders(ctx context.Context, selectedDeciders []deciderFn, conn *network.Connection, pkt packet.Packet) (done bool, defaultAction uint8) {
|
||||
layeredProfile := conn.Process().Profile()
|
||||
|
||||
// Read-lock the all the profiles.
|
||||
|
@ -104,7 +122,7 @@ func runDeciders(ctx context.Context, conn *network.Connection, pkt packet.Packe
|
|||
defer layeredProfile.UnlockForUsage()
|
||||
|
||||
// Go though all deciders, return if one sets an action.
|
||||
for _, decider := range deciders {
|
||||
for _, decider := range selectedDeciders {
|
||||
if decider(ctx, conn, pkt) {
|
||||
return true, profile.DefaultActionNotSet
|
||||
}
|
||||
|
@ -248,39 +266,58 @@ func checkConnectivityDomain(_ context.Context, conn *network.Connection, _ pack
|
|||
func checkConnectionScope(_ context.Context, conn *network.Connection, _ packet.Packet) bool {
|
||||
p := conn.Process().Profile()
|
||||
|
||||
// check scopes
|
||||
if conn.Entity.IP != nil {
|
||||
classification := netutils.ClassifyIP(conn.Entity.IP)
|
||||
|
||||
switch classification {
|
||||
case netutils.Global, netutils.GlobalMulticast:
|
||||
if p.BlockScopeInternet() {
|
||||
conn.Deny("Internet access blocked", profile.CfgOptionBlockScopeInternetKey) // Block Outbound / Drop Inbound
|
||||
return true
|
||||
}
|
||||
case netutils.SiteLocal, netutils.LinkLocal, netutils.LocalMulticast:
|
||||
if p.BlockScopeLAN() {
|
||||
conn.Block("LAN access blocked", profile.CfgOptionBlockScopeLANKey) // Block Outbound / Drop Inbound
|
||||
return true
|
||||
}
|
||||
case netutils.HostLocal:
|
||||
if p.BlockScopeLocal() {
|
||||
conn.Block("Localhost access blocked", profile.CfgOptionBlockScopeLocalKey) // Block Outbound / Drop Inbound
|
||||
return true
|
||||
}
|
||||
default: // netutils.Invalid
|
||||
conn.Deny("invalid IP", noReasonOptionKey) // Block Outbound / Drop Inbound
|
||||
return true
|
||||
}
|
||||
} else if conn.Entity.Domain != "" {
|
||||
// This is a DNS Request.
|
||||
// If we are handling a DNS request, check if we can immediately block it.
|
||||
if conn.Type == network.DNSRequest {
|
||||
// DNS is expected to resolve to LAN or Internet addresses.
|
||||
// Localhost queries are immediately responded to by the nameserver.
|
||||
if p.BlockScopeInternet() && p.BlockScopeLAN() {
|
||||
conn.Block("Internet and LAN access blocked", profile.CfgOptionBlockScopeInternetKey)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if the network scope is permitted.
|
||||
switch conn.Entity.IPScope {
|
||||
case netutils.Global, netutils.GlobalMulticast:
|
||||
if p.BlockScopeInternet() {
|
||||
conn.Deny("Internet access blocked", profile.CfgOptionBlockScopeInternetKey) // Block Outbound / Drop Inbound
|
||||
return true
|
||||
}
|
||||
case netutils.SiteLocal, netutils.LinkLocal, netutils.LocalMulticast:
|
||||
if p.BlockScopeLAN() {
|
||||
conn.Block("LAN access blocked", profile.CfgOptionBlockScopeLANKey) // Block Outbound / Drop Inbound
|
||||
return true
|
||||
}
|
||||
case netutils.HostLocal:
|
||||
if p.BlockScopeLocal() {
|
||||
conn.Block("Localhost access blocked", profile.CfgOptionBlockScopeLocalKey) // Block Outbound / Drop Inbound
|
||||
return true
|
||||
}
|
||||
default: // netutils.Unknown and netutils.Invalid
|
||||
conn.Deny("invalid IP", noReasonOptionKey) // Block Outbound / Drop Inbound
|
||||
return true
|
||||
}
|
||||
|
||||
// If the IP address was resolved, check the scope of the resolver.
|
||||
switch {
|
||||
case p.RemoveOutOfScopeDNS():
|
||||
// Out of scope checking is not active.
|
||||
case conn.Resolver == nil:
|
||||
// IP address of connection was not resolved.
|
||||
case conn.Resolver.IPScope.IsGlobal() &&
|
||||
(conn.Entity.IPScope.IsLAN() || conn.Entity.IPScope.IsLocalhost()):
|
||||
// Block global resolvers from returning LAN/Localhost IPs.
|
||||
conn.Block("DNS server horizon violation: global DNS server returned local IP address", profile.CfgOptionRemoveOutOfScopeDNSKey)
|
||||
return true
|
||||
case conn.Resolver.IPScope.IsLAN() &&
|
||||
conn.Entity.IPScope.IsLocalhost():
|
||||
// Block LAN resolvers from returning Localhost IPs.
|
||||
conn.Block("DNS server horizon violation: LAN DNS server returned localhost IP address", profile.CfgOptionRemoveOutOfScopeDNSKey)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package nameserver
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -57,6 +58,7 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
|
|||
log.Warningf("nameserver: failed to get remote address of request for %s%s, ignoring", q.FQDN, q.QType)
|
||||
return nil
|
||||
}
|
||||
// log.Errorf("DEBUG: nameserver: handling new request for %s from %s:%d", q.ID(), remoteAddr.IP, remoteAddr.Port)
|
||||
|
||||
// Start context tracer for context-aware logging.
|
||||
ctx, tracer := log.AddTracer(ctx)
|
||||
|
@ -133,20 +135,24 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
|
|||
conn.Lock()
|
||||
defer conn.Unlock()
|
||||
|
||||
// Create reference for the rrCache.
|
||||
var rrCache *resolver.RRCache
|
||||
|
||||
// Once we decided on the connection we might need to save it to the database,
|
||||
// so we defer that check for now.
|
||||
defer func() {
|
||||
switch conn.Verdict {
|
||||
// We immediately save blocked, dropped or failed verdicts so
|
||||
// they pop up in the UI.
|
||||
case network.VerdictBlock, network.VerdictDrop, network.VerdictFailed:
|
||||
case network.VerdictBlock, network.VerdictDrop, network.VerdictFailed, network.VerdictRerouteToNameserver, network.VerdictRerouteToTunnel:
|
||||
conn.Save()
|
||||
|
||||
// For undecided or accepted connections we don't save them yet, because
|
||||
// that will happen later anyway.
|
||||
case network.VerdictUndecided, network.VerdictAccept,
|
||||
network.VerdictRerouteToNameserver, network.VerdictRerouteToTunnel:
|
||||
return
|
||||
case network.VerdictUndecided, network.VerdictAccept:
|
||||
// Save the request as open, as we don't know if there will be a connection or not.
|
||||
network.SaveOpenDNSRequest(conn)
|
||||
firewall.UpdateIPsAndCNAMEs(q, rrCache, conn)
|
||||
|
||||
default:
|
||||
tracer.Warningf("nameserver: unexpected verdict %s for connection %s, not saving", conn.Verdict, conn)
|
||||
|
@ -162,17 +168,19 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
|
|||
// IP address in which case we "accept" it, but let the firewall handle
|
||||
// the resolving as it wishes.
|
||||
if responder, ok := conn.Reason.Context.(nsutil.Responder); ok {
|
||||
// Save the request as open, as we don't know if there will be a connection or not.
|
||||
network.SaveOpenDNSRequest(conn)
|
||||
|
||||
tracer.Infof("nameserver: handing over request for %s to special filter responder: %s", q.ID(), conn.Reason.Msg)
|
||||
return reply(responder)
|
||||
}
|
||||
|
||||
// Check if there is Verdict to act upon.
|
||||
// Check if there is a Verdict to act upon.
|
||||
switch conn.Verdict {
|
||||
case network.VerdictBlock, network.VerdictDrop, network.VerdictFailed:
|
||||
tracer.Infof("nameserver: request for %s from %s %s", q.ID(), conn.Process(), conn.Verdict.Verb())
|
||||
tracer.Infof(
|
||||
"nameserver: returning %s response for %s to %s",
|
||||
conn.Verdict.Verb(),
|
||||
q.ID(),
|
||||
conn.Process(),
|
||||
)
|
||||
return reply(conn, conn)
|
||||
}
|
||||
|
||||
|
@ -180,7 +188,7 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
|
|||
q.SecurityLevel = conn.Process().Profile().SecurityLevel()
|
||||
|
||||
// Resolve request.
|
||||
rrCache, err := resolver.Resolve(ctx, q)
|
||||
rrCache, err = resolver.Resolve(ctx, q)
|
||||
if err != nil {
|
||||
// React to special errors.
|
||||
switch {
|
||||
|
@ -212,13 +220,10 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
|
|||
}
|
||||
|
||||
tracer.Trace("nameserver: deciding on resolved dns")
|
||||
rrCache = firewall.DecideOnResolvedDNS(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 {
|
||||
// Save the request as open, as we don't know if there will be a connection or not.
|
||||
network.SaveOpenDNSRequest(conn)
|
||||
|
||||
tracer.Infof("nameserver: handing over request for %s to filter responder: %s", q.ID(), conn.Reason.Msg)
|
||||
return reply(responder)
|
||||
}
|
||||
|
@ -236,9 +241,6 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
|
|||
}
|
||||
}
|
||||
|
||||
// Save dns request as open.
|
||||
defer network.SaveOpenDNSRequest(conn)
|
||||
|
||||
// Revert back to non-standard question format, if we had to convert.
|
||||
if nonStandardQuestionFormat {
|
||||
rrCache.ReplaceAnswerNames(originalQuestion.Name)
|
||||
|
|
|
@ -17,7 +17,7 @@ func registerConfiguration() error {
|
|||
err := config.Register(&config.Option{
|
||||
Name: "Process Detection",
|
||||
Key: CfgOptionEnableProcessDetectionKey,
|
||||
Description: "This option enables the attribution of network traffic to processes. This should always be enabled, and effectively disables app settings if disabled.",
|
||||
Description: "This option enables the attribution of network traffic to processes. Without it, app settings are effectively disabled.",
|
||||
OptType: config.OptTypeBool,
|
||||
ExpertiseLevel: config.ExpertiseLevelDeveloper,
|
||||
DefaultValue: true,
|
||||
|
|
|
@ -434,7 +434,7 @@ The lists are automatically updated every hour using incremental updates.
|
|||
err = config.Register(&config.Option{
|
||||
Name: "Enforce Global/Private Split-View",
|
||||
Key: CfgOptionRemoveOutOfScopeDNSKey,
|
||||
Description: "Reject private IP addresses (RFC1918 et al.) from public DNS responses.",
|
||||
Description: "Reject private IP addresses (RFC1918 et al.) from public DNS responses. If the system resolver is in use, the resulting connection will be blocked instead of the DNS request.",
|
||||
OptType: config.OptTypeInt,
|
||||
ExpertiseLevel: config.ExpertiseLevelDeveloper,
|
||||
DefaultValue: status.SecurityLevelsAll,
|
||||
|
@ -455,7 +455,7 @@ The lists are automatically updated every hour using incremental updates.
|
|||
err = config.Register(&config.Option{
|
||||
Name: "Reject Blocked IPs",
|
||||
Key: CfgOptionRemoveBlockedDNSKey,
|
||||
Description: "Reject blocked IP addresses directly from the DNS response instead of handing them over to the app and blocking a resulting connection.",
|
||||
Description: "Reject blocked IP addresses directly from the DNS response instead of handing them over to the app and blocking a resulting connection. This settings does not affect privacy and only takes effect when the system resolver is not in use.",
|
||||
OptType: config.OptTypeInt,
|
||||
ExpertiseLevel: config.ExpertiseLevelDeveloper,
|
||||
DefaultValue: status.SecurityLevelsAll,
|
||||
|
@ -491,6 +491,7 @@ The lists are automatically updated every hour using incremental updates.
|
|||
return err
|
||||
}
|
||||
cfgOptionDomainHeuristics = config.Concurrent.GetAsInt(CfgOptionDomainHeuristicsKey, int64(status.SecurityLevelsAll))
|
||||
cfgIntOptions[CfgOptionDomainHeuristicsKey] = cfgOptionDomainHeuristics
|
||||
|
||||
// Bypass prevention
|
||||
err = config.Register(&config.Option{
|
||||
|
@ -499,7 +500,9 @@ The lists are automatically updated every hour using incremental updates.
|
|||
Description: `Prevent apps from bypassing the privacy filter.
|
||||
Current Features:
|
||||
- Disable Firefox' internal DNS-over-HTTPs resolver
|
||||
- Block direct access to public DNS resolvers`,
|
||||
- Block direct access to public DNS resolvers
|
||||
|
||||
Please note that if you are using the system resolver, bypass attempts might be additionally blocked there too.`,
|
||||
OptType: config.OptTypeInt,
|
||||
ExpertiseLevel: config.ExpertiseLevelUser,
|
||||
ReleaseLevel: config.ReleaseLevelBeta,
|
||||
|
|
Loading…
Add table
Reference in a new issue