mirror of
https://github.com/safing/portmaster
synced 2025-09-01 10:09:11 +00:00
Make pre-authenticated ports simpler and stricter
This commit is contained in:
parent
2564c7b668
commit
c8bb071e29
5 changed files with 166 additions and 147 deletions
|
@ -1,46 +0,0 @@
|
|||
package firewall
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/safing/portmaster/netenv"
|
||||
"github.com/safing/portmaster/resolver"
|
||||
)
|
||||
|
||||
func init() {
|
||||
resolver.SetLocalAddrFactory(PermittedAddr)
|
||||
netenv.SetLocalAddrFactory(PermittedAddr)
|
||||
}
|
||||
|
||||
// PermittedAddr returns an already permitted local address for the given network for reliable connectivity.
|
||||
// Returns nil in case of error.
|
||||
func PermittedAddr(network string) net.Addr {
|
||||
switch network {
|
||||
case "udp":
|
||||
return PermittedUDPAddr()
|
||||
case "tcp":
|
||||
return PermittedTCPAddr()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PermittedUDPAddr returns an already permitted local udp address for reliable connectivity.
|
||||
// Returns nil in case of error.
|
||||
func PermittedUDPAddr() *net.UDPAddr {
|
||||
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", GetPermittedPort()))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// PermittedTCPAddr returns an already permitted local tcp address for reliable connectivity.
|
||||
// Returns nil in case of error.
|
||||
func PermittedTCPAddr() *net.TCPAddr {
|
||||
addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf(":%d", GetPermittedPort()))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return addr
|
||||
}
|
|
@ -63,7 +63,6 @@ func interceptionStart() error {
|
|||
|
||||
interceptionModule.StartWorker("stat logger", statLogger)
|
||||
interceptionModule.StartWorker("packet handler", packetHandler)
|
||||
interceptionModule.StartWorker("ports state cleaner", portsInUseCleaner)
|
||||
|
||||
return interception.Start()
|
||||
}
|
||||
|
@ -265,13 +264,12 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
|
|||
func initialHandler(conn *network.Connection, pkt packet.Packet) {
|
||||
log.Tracer(pkt.Ctx()).Trace("filter: handing over to connection-based handler")
|
||||
|
||||
// check for internal firewall bypass
|
||||
ps := getPortStatusAndMarkUsed(pkt.Info().LocalPort())
|
||||
if ps.isMe {
|
||||
// approve
|
||||
// Check for pre-authenticated port.
|
||||
if localPortIsPreAuthenticated(conn.Entity.Protocol, conn.LocalPort) {
|
||||
// Approve connection.
|
||||
conn.Accept("connection by Portmaster", noReasonOptionKey)
|
||||
conn.Internal = true
|
||||
// finish
|
||||
// Finalize connection.
|
||||
conn.StopFirewallHandler()
|
||||
issueVerdict(conn, pkt, 0, true)
|
||||
return
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/rng"
|
||||
)
|
||||
|
||||
type portStatus struct {
|
||||
lastSeen time.Time
|
||||
isMe bool
|
||||
}
|
||||
|
||||
var (
|
||||
portsInUse = make(map[uint16]*portStatus)
|
||||
portsInUseLock sync.Mutex
|
||||
|
||||
cleanerTickDuration = 10 * time.Second
|
||||
cleanTimeout = 10 * time.Minute
|
||||
)
|
||||
|
||||
func getPortStatusAndMarkUsed(port uint16) *portStatus {
|
||||
portsInUseLock.Lock()
|
||||
defer portsInUseLock.Unlock()
|
||||
|
||||
ps, ok := portsInUse[port]
|
||||
if ok {
|
||||
ps.lastSeen = time.Now()
|
||||
return ps
|
||||
}
|
||||
|
||||
new := &portStatus{
|
||||
lastSeen: time.Now(),
|
||||
isMe: false,
|
||||
}
|
||||
portsInUse[port] = new
|
||||
return new
|
||||
}
|
||||
|
||||
// GetPermittedPort returns a local port number that is already permitted for communication.
|
||||
// This bypasses the process attribution step to guarantee connectivity.
|
||||
// Communication on the returned port is attributed to the Portmaster.
|
||||
func GetPermittedPort() uint16 {
|
||||
portsInUseLock.Lock()
|
||||
defer portsInUseLock.Unlock()
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
// generate port between 10000 and 65535
|
||||
rN, err := rng.Number(55535)
|
||||
if err != nil {
|
||||
log.Warningf("filter: failed to generate random port: %s", err)
|
||||
return 0
|
||||
}
|
||||
port := uint16(rN + 10000)
|
||||
|
||||
// check if free, return if it is
|
||||
_, ok := portsInUse[port]
|
||||
if !ok {
|
||||
portsInUse[port] = &portStatus{
|
||||
lastSeen: time.Now(),
|
||||
isMe: true,
|
||||
}
|
||||
return port
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func portsInUseCleaner(ctx context.Context) error {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-time.After(cleanerTickDuration):
|
||||
cleanPortsInUse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cleanPortsInUse() {
|
||||
portsInUseLock.Lock()
|
||||
defer portsInUseLock.Unlock()
|
||||
|
||||
threshold := time.Now().Add(-cleanTimeout)
|
||||
|
||||
for port, status := range portsInUse {
|
||||
if status.lastSeen.Before(threshold) {
|
||||
delete(portsInUse, port)
|
||||
}
|
||||
}
|
||||
}
|
113
firewall/preauth.go
Normal file
113
firewall/preauth.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
package firewall
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/safing/portmaster/network"
|
||||
"github.com/safing/portmaster/network/packet"
|
||||
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/safing/portmaster/netenv"
|
||||
"github.com/safing/portmaster/resolver"
|
||||
)
|
||||
|
||||
var (
|
||||
preAuthenticatedPorts = make(map[string]struct{})
|
||||
preAuthenticatedPortsLock sync.Mutex
|
||||
)
|
||||
|
||||
func init() {
|
||||
resolver.SetLocalAddrFactory(PermittedAddr)
|
||||
netenv.SetLocalAddrFactory(PermittedAddr)
|
||||
}
|
||||
|
||||
// PermittedAddr returns an already permitted local address for the given network for reliable connectivity.
|
||||
// Returns nil in case of error.
|
||||
func PermittedAddr(network string) net.Addr {
|
||||
switch network {
|
||||
case "udp":
|
||||
return PermittedUDPAddr()
|
||||
case "tcp":
|
||||
return PermittedTCPAddr()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PermittedUDPAddr returns an already permitted local udp address for reliable connectivity.
|
||||
// Returns nil in case of error.
|
||||
func PermittedUDPAddr() *net.UDPAddr {
|
||||
preAuthdPort := GetPermittedPort(packet.UDP)
|
||||
if preAuthdPort == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", preAuthdPort))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
// PermittedTCPAddr returns an already permitted local tcp address for reliable connectivity.
|
||||
// Returns nil in case of error.
|
||||
func PermittedTCPAddr() *net.TCPAddr {
|
||||
preAuthdPort := GetPermittedPort(packet.TCP)
|
||||
if preAuthdPort == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf(":%d", preAuthdPort))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
// GetPermittedPort returns a local port number that is already permitted for communication.
|
||||
// This bypasses the process attribution step to guarantee connectivity.
|
||||
// Communication on the returned port is attributed to the Portmaster.
|
||||
// Every pre-authenticated port is only valid once.
|
||||
// If no unused local port number can be found, it will return 0, which is
|
||||
// expected to trigger automatic port selection by the underlying OS.
|
||||
func GetPermittedPort(protocol packet.IPProtocol) uint16 {
|
||||
port, ok := network.GetUnusedLocalPort(uint8(protocol))
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
preAuthenticatedPortsLock.Lock()
|
||||
defer preAuthenticatedPortsLock.Unlock()
|
||||
|
||||
// Save generated port.
|
||||
key := generateLocalPreAuthKey(uint8(protocol), port)
|
||||
preAuthenticatedPorts[key] = struct{}{}
|
||||
|
||||
return port
|
||||
}
|
||||
|
||||
// localPortIsPreAuthenticated checks if the given protocol and port are
|
||||
// pre-authenticated and should be attributed to the Portmaster itself.
|
||||
func localPortIsPreAuthenticated(protocol uint8, port uint16) bool {
|
||||
preAuthenticatedPortsLock.Lock()
|
||||
defer preAuthenticatedPortsLock.Unlock()
|
||||
|
||||
// Check if the given protocol and port are pre-authenticated.
|
||||
key := generateLocalPreAuthKey(protocol, port)
|
||||
_, ok := preAuthenticatedPorts[key]
|
||||
if ok {
|
||||
// Immediately remove pre authenticated port.
|
||||
delete(preAuthenticatedPorts, key)
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// generateLocalPreAuthKey creates a map key for the pre-authenticated ports.
|
||||
func generateLocalPreAuthKey(protocol uint8, port uint16) string {
|
||||
return strconv.Itoa(int(protocol)) + ":" + strconv.Itoa(int(port))
|
||||
}
|
49
network/ports.go
Normal file
49
network/ports.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package network
|
||||
|
||||
import (
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/rng"
|
||||
)
|
||||
|
||||
// GetUnusedLocalPort returns a local port of the specified protocol that is
|
||||
// currently unused and is unlikely to be used within the next seconds.
|
||||
func GetUnusedLocalPort(protocol uint8) (port uint16, ok bool) {
|
||||
allConns := conns.clone()
|
||||
|
||||
tries := 1000
|
||||
hundredth := tries / 100
|
||||
|
||||
// Try up to 1000 times to find an unused port.
|
||||
nextPort:
|
||||
for i := 0; i < tries; i++ {
|
||||
// Generate random port between 10000 and 65535
|
||||
rN, err := rng.Number(55535)
|
||||
if err != nil {
|
||||
log.Warningf("network: failed to generate random port: %s", err)
|
||||
return 0, false
|
||||
}
|
||||
port := uint16(rN + 10000)
|
||||
|
||||
// Shrink range when we chew through the tries.
|
||||
portRangeStart := port - uint16(100-(i/hundredth))
|
||||
|
||||
// Check if the generated port is unused.
|
||||
nextConnection:
|
||||
for _, conn := range allConns {
|
||||
// Skip connection if the protocol does not match the protocol of interest.
|
||||
if conn.Entity.Protocol != protocol {
|
||||
continue nextConnection
|
||||
}
|
||||
// Skip port if the local port is in dangerous proximity.
|
||||
// Consecutive port numbers are very common.
|
||||
if conn.LocalPort <= port && conn.LocalPort >= portRangeStart {
|
||||
continue nextPort
|
||||
}
|
||||
}
|
||||
|
||||
// The checks have passed. We have found a good unused port.
|
||||
return port, true
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
Loading…
Add table
Reference in a new issue