From 960101d3a9380437b3d5c5771dc679cb338ec098 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 12 Oct 2021 16:18:36 +0200 Subject: [PATCH] Add custom routing for DNS server connections --- firewall/interception.go | 2 + firewall/master.go | 4 ++ firewall/tunnel.go | 130 +++++++-------------------------------- network/connection.go | 6 +- resolver/resolvers.go | 15 +++++ 5 files changed, 46 insertions(+), 111 deletions(-) diff --git a/firewall/interception.go b/firewall/interception.go index 3b2b3421..a67849c9 100644 --- a/firewall/interception.go +++ b/firewall/interception.go @@ -328,6 +328,8 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) { conn.Accept("connection by Portmaster", noReasonOptionKey) conn.Internal = true + // Set tunnel options. + setCustomTunnelOptionsForPortmaster(conn) case pkt.IsOutbound() && pkt.Info().DstPort == 53 && diff --git a/firewall/master.go b/firewall/master.go index fcf946a5..e5fc0440 100644 --- a/firewall/master.go +++ b/firewall/master.go @@ -141,6 +141,10 @@ func checkPortmasterConnection(ctx context.Context, conn *network.Connection, _ log.Tracer(ctx).Infof("filter: granting own connection %s", conn) conn.Accept("connection by Portmaster", noReasonOptionKey) conn.Internal = true + + // Set tunnel options. + setCustomTunnelOptionsForPortmaster(conn) + return true } diff --git a/firewall/tunnel.go b/firewall/tunnel.go index 22f880f2..27435466 100644 --- a/firewall/tunnel.go +++ b/firewall/tunnel.go @@ -1,111 +1,23 @@ package firewall -// var ( -// TunnelNet4 *net.IPNet -// TunnelNet6 *net.IPNet -// TunnelEntry4 = net.IPv4(127, 0, 0, 17) -// TunnelEntry6 = net.ParseIP("fd17::17") -// -// ipToDomainMap = make(map[string]*TunnelInfo) -// ipToDomainMapLock sync.RWMutex -// ) -// -// func init() { -// var err error -// _, TunnelNet4, err = net.ParseCIDR("127.17.0.0/16") -// if err != nil { -// log.Fatalf("portmaster: could not parse 127.17.0.0/16: %s", err) -// } -// _, TunnelNet6, err = net.ParseCIDR("fd17::/64") -// if err != nil { -// log.Fatalf("portmaster: could not parse fd17::/64: %s", err) -// } -// -// go tunnelInfoCleaner() -// } -// -// type TunnelInfo struct { -// IP net.IP -// Domain string -// RRCache *intel.RRCache -// Expires int64 -// } -// -// func (ti *TunnelInfo) ExportTunnelIP() *intel.RRCache { -// return &intel.RRCache{ -// Answer: []dns.RR{ -// &dns.A{ -// Hdr: dns.RR_Header{ -// Name: ti.Domain, -// Rrtype: 1, -// Class: 1, -// Ttl: 17, -// Rdlength: 8, -// }, -// A: ti.IP, -// }, -// }, -// } -// } -// -// func AssignTunnelIP(domain string) (*TunnelInfo, error) { -// ipToDomainMapLock.Lock() -// defer ipToDomainMapLock.Unlock() -// -// for i := 0; i < 100; i++ { -// // get random IP -// r, err := random.Bytes(2) -// if err != nil { -// return nil, err -// } -// randomIP := net.IPv4(127, 17, r[0], r[1]) -// -// // clean after every 20 tries -// if i > 0 && i%20 == 0 { -// cleanExpiredTunnelInfos() -// } -// -// // if it does not exist yet, set and return -// _, ok := ipToDomainMap[randomIP.String()] -// if !ok { -// tunnelInfo := &TunnelInfo{ -// IP: randomIP, -// Domain: domain, -// Expires: time.Now().Add(5 * time.Minute).Unix(), -// } -// ipToDomainMap[randomIP.String()] = tunnelInfo -// return tunnelInfo, nil -// } -// } -// -// return nil, errors.New("could not find available tunnel IP, please retry later") -// } -// -// func GetTunnelInfo(tunnelIP net.IP) (tunnelInfo *TunnelInfo) { -// ipToDomainMapLock.RLock() -// defer ipToDomainMapLock.RUnlock() -// var ok bool -// tunnelInfo, ok = ipToDomainMap[tunnelIP.String()] -// if ok && tunnelInfo.Expires >= time.Now().Unix() { -// return tunnelInfo -// } -// return nil -// } -// -// func tunnelInfoCleaner() { -// for { -// time.Sleep(5 * time.Minute) -// ipToDomainMapLock.Lock() -// cleanExpiredTunnelInfos() -// ipToDomainMapLock.Unlock() -// } -// } -// -// func cleanExpiredTunnelInfos() { -// now := time.Now().Unix() -// for domain, tunnelInfo := range ipToDomainMap { -// if tunnelInfo.Expires < now { -// delete(ipToDomainMap, domain) -// } -// } -// } +import ( + "github.com/safing/portmaster/network" + "github.com/safing/portmaster/resolver" + "github.com/safing/spn/navigator" +) + +func setCustomTunnelOptionsForPortmaster(conn *network.Connection) { + switch { + case !tunnelEnabled(): + // Ignore when tunneling is not enabled. + return + case !conn.Entity.IPScope.IsGlobal(): + // Ignore if destination is not in global address space. + return + case resolver.IsResolverAddress(conn.Entity.IP, conn.Entity.Port): + // Set custom tunnel options for DNS servers. + conn.TunnelOpts = &navigator.Options{ + RoutingProfile: navigator.RoutingProfileHomeName, + } + } +} diff --git a/network/connection.go b/network/connection.go index af5ba4bb..84a63db1 100644 --- a/network/connection.go +++ b/network/connection.go @@ -7,15 +7,15 @@ import ( "sync" "time" - "github.com/safing/portmaster/netenv" - "github.com/safing/portbase/database/record" "github.com/safing/portbase/log" "github.com/safing/portmaster/intel" + "github.com/safing/portmaster/netenv" "github.com/safing/portmaster/network/netutils" "github.com/safing/portmaster/network/packet" "github.com/safing/portmaster/process" "github.com/safing/portmaster/resolver" + "github.com/safing/spn/navigator" ) // FirewallHandler defines the function signature for a firewall @@ -137,6 +137,8 @@ type Connection struct { //nolint:maligned // TODO: fix alignment Tunneled bool // Encrypted is currently unused and MUST be ignored. Encrypted bool + // TunnelOpts holds options for tunneling the connection. + TunnelOpts *navigator.Options // ProcessContext holds additional information about the process // that iniated the connection. It is set once when the connection // object is created and is considered immutable afterwards. diff --git a/resolver/resolvers.go b/resolver/resolvers.go index c235b85f..4cbaf46d 100644 --- a/resolver/resolvers.go +++ b/resolver/resolvers.go @@ -395,3 +395,18 @@ func checkSearchScope(searchDomain string) (ok bool) { return true } + +// IsResolverAddress returns whether the given ip and port match a configured resolver. +func IsResolverAddress(ip net.IP, port uint16) bool { + resolversLock.RLock() + defer resolversLock.RUnlock() + + // Check if the given IP and port matches a resolver. + for _, r := range globalResolvers { + if port == r.Info.Port && r.Info.IP.Equal(ip) { + return true + } + } + + return false +}