mirror of
https://github.com/safing/portmaster
synced 2025-09-02 18:49:14 +00:00
230 lines
6 KiB
Go
230 lines
6 KiB
Go
package firewall
|
|
|
|
import (
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/miekg/dns"
|
|
"github.com/safing/portbase/database"
|
|
"github.com/safing/portbase/log"
|
|
"github.com/safing/portmaster/network"
|
|
"github.com/safing/portmaster/network/netutils"
|
|
"github.com/safing/portmaster/profile"
|
|
"github.com/safing/portmaster/profile/endpoints"
|
|
"github.com/safing/portmaster/resolver"
|
|
)
|
|
|
|
func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, scope int8) ([]dns.RR, []string, int) {
|
|
goodEntries := make([]dns.RR, 0, len(entries))
|
|
filteredRecords := make([]string, 0, len(entries))
|
|
|
|
// keeps track of the number of valid and allowed
|
|
// A and AAAA records.
|
|
var allowedAddressRecords int
|
|
|
|
for _, rr := range entries {
|
|
// get IP and classification
|
|
var ip net.IP
|
|
switch v := rr.(type) {
|
|
case *dns.A:
|
|
ip = v.A
|
|
case *dns.AAAA:
|
|
ip = v.AAAA
|
|
default:
|
|
// add non A/AAAA entries
|
|
goodEntries = append(goodEntries, rr)
|
|
continue
|
|
}
|
|
classification := netutils.ClassifyIP(ip)
|
|
|
|
if p.RemoveOutOfScopeDNS() {
|
|
switch {
|
|
case classification == netutils.HostLocal:
|
|
// No DNS should return localhost addresses
|
|
filteredRecords = append(filteredRecords, rr.String())
|
|
continue
|
|
case scope == netutils.Global && (classification == netutils.SiteLocal || classification == netutils.LinkLocal):
|
|
// No global DNS should return LAN addresses
|
|
filteredRecords = append(filteredRecords, rr.String())
|
|
continue
|
|
}
|
|
}
|
|
|
|
if p.RemoveBlockedDNS() {
|
|
// filter by flags
|
|
switch {
|
|
case p.BlockScopeInternet() && classification == netutils.Global:
|
|
filteredRecords = append(filteredRecords, rr.String())
|
|
continue
|
|
case p.BlockScopeLAN() && (classification == netutils.SiteLocal || classification == netutils.LinkLocal):
|
|
filteredRecords = append(filteredRecords, rr.String())
|
|
continue
|
|
case p.BlockScopeLocal() && classification == netutils.HostLocal:
|
|
filteredRecords = append(filteredRecords, rr.String())
|
|
continue
|
|
}
|
|
|
|
// TODO: filter by endpoint list (IP only)
|
|
}
|
|
|
|
// if survived, add to good entries
|
|
allowedAddressRecords++
|
|
goodEntries = append(goodEntries, rr)
|
|
}
|
|
|
|
return goodEntries, filteredRecords, allowedAddressRecords
|
|
}
|
|
|
|
func filterDNSResponse(conn *network.Connection, rrCache *resolver.RRCache) *resolver.RRCache {
|
|
// do not modify own queries
|
|
if conn.Process().Pid == os.Getpid() {
|
|
return rrCache
|
|
}
|
|
|
|
// get profile
|
|
p := conn.Process().Profile()
|
|
if p == nil {
|
|
conn.Block("no profile")
|
|
return nil
|
|
}
|
|
|
|
// check if DNS response filtering is completely turned off
|
|
if !p.RemoveOutOfScopeDNS() && !p.RemoveBlockedDNS() {
|
|
return rrCache
|
|
}
|
|
|
|
// duplicate entry
|
|
rrCache = rrCache.ShallowCopy()
|
|
rrCache.FilteredEntries = make([]string, 0)
|
|
|
|
var filteredRecords []string
|
|
var validIPs int
|
|
|
|
rrCache.Answer, filteredRecords, validIPs = filterDNSSection(rrCache.Answer, p, rrCache.ServerScope)
|
|
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.FilteredEntries = append(rrCache.FilteredEntries, filteredRecords...)
|
|
|
|
if len(rrCache.FilteredEntries) > 0 {
|
|
rrCache.Filtered = true
|
|
if validIPs == 0 {
|
|
conn.Block("no addresses returned for this domain are permitted")
|
|
return nil
|
|
}
|
|
|
|
log.Infof("filter: filtered DNS replies for %s: %s", conn, strings.Join(rrCache.FilteredEntries, ", "))
|
|
}
|
|
|
|
return rrCache
|
|
}
|
|
|
|
// DecideOnResolvedDNS filters a dns response according to the application profile and settings.
|
|
func DecideOnResolvedDNS(conn *network.Connection, q *resolver.Query, rrCache *resolver.RRCache) *resolver.RRCache {
|
|
updatedRR := filterDNSResponse(conn, rrCache)
|
|
if updatedRR == nil {
|
|
return nil
|
|
}
|
|
|
|
updateIPsAndCNAMEs(q, rrCache, conn)
|
|
|
|
if mayBlockCNAMEs(conn) {
|
|
return nil
|
|
}
|
|
|
|
// TODO: Gate17 integration
|
|
// tunnelInfo, err := AssignTunnelIP(fqdn)
|
|
|
|
return updatedRR
|
|
}
|
|
|
|
func mayBlockCNAMEs(conn *network.Connection) bool {
|
|
// if we have CNAMEs and the profile is configured to filter them
|
|
// we need to re-check the lists and endpoints here
|
|
if conn.Process().Profile().FilterCNAMEs() {
|
|
conn.Entity.ResetLists()
|
|
conn.Entity.EnableCNAMECheck(true)
|
|
|
|
result, reason := conn.Process().Profile().MatchEndpoint(conn.Entity)
|
|
if result == endpoints.Denied {
|
|
conn.BlockWithContext(reason.String(), reason.Context())
|
|
return true
|
|
}
|
|
|
|
if result == endpoints.NoMatch {
|
|
result, reason = conn.Process().Profile().MatchFilterLists(conn.Entity)
|
|
if result == endpoints.Denied {
|
|
conn.BlockWithContext(reason.String(), reason.Context())
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func updateIPsAndCNAMEs(q *resolver.Query, rrCache *resolver.RRCache, conn *network.Connection) {
|
|
// save IP addresses to IPInfo
|
|
cnames := make(map[string]string)
|
|
ips := make(map[string]struct{})
|
|
|
|
for _, rr := range append(rrCache.Answer, rrCache.Extra...) {
|
|
switch v := rr.(type) {
|
|
case *dns.CNAME:
|
|
cnames[v.Hdr.Name] = v.Target
|
|
|
|
case *dns.A:
|
|
ips[v.A.String()] = struct{}{}
|
|
|
|
case *dns.AAAA:
|
|
ips[v.AAAA.String()] = struct{}{}
|
|
}
|
|
}
|
|
|
|
for ip := range ips {
|
|
record := resolver.ResolvedDomain{
|
|
Domain: q.FQDN,
|
|
}
|
|
|
|
// resolve all CNAMEs in the correct order.
|
|
var domain = q.FQDN
|
|
for {
|
|
nextDomain, isCNAME := cnames[domain]
|
|
if !isCNAME {
|
|
break
|
|
}
|
|
|
|
record.CNAMEs = append(record.CNAMEs, nextDomain)
|
|
domain = nextDomain
|
|
}
|
|
|
|
// update the entity to include the cnames
|
|
conn.Entity.CNAME = record.CNAMEs
|
|
|
|
// get the existing IP info or create a new one
|
|
var save bool
|
|
info, err := resolver.GetIPInfo(ip)
|
|
if err != nil {
|
|
if err != database.ErrNotFound {
|
|
log.Errorf("nameserver: failed to search for IP info record: %s", err)
|
|
}
|
|
|
|
info = &resolver.IPInfo{
|
|
IP: ip,
|
|
}
|
|
save = true
|
|
}
|
|
|
|
// and the new resolved domain record and save
|
|
if new := info.AddDomain(record); new {
|
|
save = true
|
|
}
|
|
if save {
|
|
if err := info.Save(); err != nil {
|
|
log.Errorf("nameserver: failed to save IP info record: %s", err)
|
|
}
|
|
}
|
|
}
|
|
}
|