wip: migrate to mono-repo. SPN has already been moved to spn/

This commit is contained in:
Patrick Pacher
2024-03-15 11:55:13 +01:00
parent b30fd00ccf
commit 8579430db9
577 changed files with 35981 additions and 818 deletions

View File

@@ -0,0 +1,71 @@
//go:build windows
package iphelper
import (
"sync"
"github.com/safing/portmaster/service/network/socket"
)
var (
ipHelper *IPHelper
// lock locks access to the whole DLL.
// TODO: It's unproven if we can access the iphlpapi.dll concurrently, especially as we might be encountering various versions of the DLL. In the future, we could possibly investigate and improve performance here.
lock sync.RWMutex
)
// GetTCP4Table returns the system table for IPv4 TCP activity.
func GetTCP4Table() (connections []*socket.ConnectionInfo, listeners []*socket.BindInfo, err error) {
lock.Lock()
defer lock.Unlock()
err = checkIPHelper()
if err != nil {
return nil, nil, err
}
return ipHelper.getTable(IPv4, TCP)
}
// GetTCP6Table returns the system table for IPv6 TCP activity.
func GetTCP6Table() (connections []*socket.ConnectionInfo, listeners []*socket.BindInfo, err error) {
lock.Lock()
defer lock.Unlock()
err = checkIPHelper()
if err != nil {
return nil, nil, err
}
return ipHelper.getTable(IPv6, TCP)
}
// GetUDP4Table returns the system table for IPv4 UDP activity.
func GetUDP4Table() (binds []*socket.BindInfo, err error) {
lock.Lock()
defer lock.Unlock()
err = checkIPHelper()
if err != nil {
return nil, err
}
_, binds, err = ipHelper.getTable(IPv4, UDP)
return
}
// GetUDP6Table returns the system table for IPv6 UDP activity.
func GetUDP6Table() (binds []*socket.BindInfo, err error) {
lock.Lock()
defer lock.Unlock()
err = checkIPHelper()
if err != nil {
return nil, err
}
_, binds, err = ipHelper.getTable(IPv6, UDP)
return
}

View File

@@ -0,0 +1,63 @@
//go:build windows
package iphelper
import (
"errors"
"fmt"
"github.com/tevino/abool"
"golang.org/x/sys/windows"
)
var (
errInvalid = errors.New("IPHelper not initialized or broken")
)
// IPHelper represents a subset of the Windows iphlpapi.dll.
type IPHelper struct {
dll *windows.LazyDLL
getExtendedTCPTable *windows.LazyProc
getExtendedUDPTable *windows.LazyProc
valid *abool.AtomicBool
}
func checkIPHelper() (err error) {
if ipHelper == nil {
ipHelper, err = New()
return err
}
return nil
}
// New returns a new IPHelper API (with an instance of iphlpapi.dll loaded).
func New() (*IPHelper, error) {
new := &IPHelper{}
new.valid = abool.NewBool(false)
var err error
// load dll
new.dll = windows.NewLazySystemDLL("iphlpapi.dll")
err = new.dll.Load()
if err != nil {
return nil, err
}
// load functions
new.getExtendedTCPTable = new.dll.NewProc("GetExtendedTcpTable")
err = new.getExtendedTCPTable.Find()
if err != nil {
return nil, fmt.Errorf("could find proc GetExtendedTcpTable: %s", err)
}
new.getExtendedUDPTable = new.dll.NewProc("GetExtendedUdpTable")
err = new.getExtendedUDPTable.Find()
if err != nil {
return nil, fmt.Errorf("could find proc GetExtendedUdpTable: %s", err)
}
new.valid.Set()
return new, nil
}

View File

@@ -0,0 +1,398 @@
//go:build windows
package iphelper
import (
"encoding/binary"
"errors"
"fmt"
"net"
"sync"
"unsafe"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/service/network/socket"
"golang.org/x/sys/windows"
)
// Windows API constants
const (
iphelperTCPTableOwnerPIDAll uintptr = 5
iphelperUDPTableOwnerPID uintptr = 1
iphelperTCPStateListen uint32 = 2
winErrInsufficientBuffer = uintptr(windows.ERROR_INSUFFICIENT_BUFFER)
winErrInvalidParameter = uintptr(windows.ERROR_INVALID_PARAMETER)
)
type iphelperTCPTable struct {
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366921(v=vs.85).aspx
numEntries uint32
table [maxStateTableEntries]iphelperTCPRow
}
type iphelperTCPRow struct {
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366913(v=vs.85).aspx
state uint32
localAddr uint32
localPort uint32
remoteAddr uint32
remotePort uint32
owningPid uint32
}
type iphelperTCP6Table struct {
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366905(v=vs.85).aspx
numEntries uint32
table [maxStateTableEntries]iphelperTCP6Row
}
type iphelperTCP6Row struct {
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366896(v=vs.85).aspx
localAddr [16]byte
_ uint32 // localScopeID
localPort uint32
remoteAddr [16]byte
_ uint32 // remoteScopeID
remotePort uint32
state uint32
owningPid uint32
}
type iphelperUDPTable struct {
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366932(v=vs.85).aspx
numEntries uint32
table [maxStateTableEntries]iphelperUDPRow
}
type iphelperUDPRow struct {
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366928(v=vs.85).aspx
localAddr uint32
localPort uint32
owningPid uint32
}
type iphelperUDP6Table struct {
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366925(v=vs.85).aspx
numEntries uint32
table [maxStateTableEntries]iphelperUDP6Row
}
type iphelperUDP6Row struct {
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366923(v=vs.85).aspx
localAddr [16]byte
_ uint32 // localScopeID
localPort uint32
owningPid uint32
}
// IP and Protocol constants
const (
IPv4 uint8 = 4
IPv6 uint8 = 6
TCP uint8 = 6
UDP uint8 = 17
)
type learningBufSize struct {
sync.Mutex
size int
usesLeft int
useFor int
start int
max int
}
func newLearningBufSize(start, max, ttl int) *learningBufSize {
return &learningBufSize{
size: start,
usesLeft: ttl,
useFor: ttl,
start: start,
max: max,
}
}
const (
startBufSize = 1024
// bufSizeUsageTTL defines how often a buffer size is used before it is
// shrunk again.
bufSizeUsageTTL = 100
// maxBufSize is the maximum size we will allocate for responses. This was
// previously set at 65k, which was too little for some production cases.
maxBufSize = 1048576 // 2^20B, 1MB
// maxStateTableEntries is the maximum supported amount of entries of the
// state tables.
// This is never allocated, but just casted to from an unsafe pointer.
maxStateTableEntries = 65535
)
var (
tcp4BufSize = newLearningBufSize(startBufSize, maxBufSize, bufSizeUsageTTL)
udp4BufSize = newLearningBufSize(startBufSize, maxBufSize, bufSizeUsageTTL)
tcp6BufSize = newLearningBufSize(startBufSize, maxBufSize, bufSizeUsageTTL)
udp6BufSize = newLearningBufSize(startBufSize, maxBufSize, bufSizeUsageTTL)
)
func (lbf *learningBufSize) getBufSize() int {
lbf.Lock()
defer lbf.Unlock()
// using bufSize
lbf.usesLeft--
// check if we want to reset
if lbf.usesLeft <= 0 {
// decrease
lbf.size /= 2
// not too little
if lbf.size < lbf.start {
lbf.size = lbf.start
}
// reset TTL counter
lbf.usesLeft = lbf.useFor
}
return lbf.size
}
func (lbf *learningBufSize) increaseBufSize(minSize int) int {
lbf.Lock()
defer lbf.Unlock()
// increase
lbf.size *= 2
// increase until we reach the minimum size
for lbf.size < minSize {
lbf.size *= 2
}
// not too much
if lbf.size > lbf.max {
lbf.size = lbf.max
}
// reset TTL counter
lbf.usesLeft = lbf.useFor
// return new bufSize
return lbf.size
}
// getTable returns the current connection state table of Windows of the given protocol and IP version.
func (ipHelper *IPHelper) getTable(ipVersion, protocol uint8) (connections []*socket.ConnectionInfo, binds []*socket.BindInfo, err error) { //nolint:gocognit,gocycle // TODO
// docs: https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getextendedtcptable
if !ipHelper.valid.IsSet() {
return nil, nil, errInvalid
}
var afClass int
var lbf *learningBufSize
switch ipVersion {
case IPv4:
afClass = windows.AF_INET
if protocol == TCP {
lbf = tcp4BufSize
} else {
lbf = udp4BufSize
}
case IPv6:
afClass = windows.AF_INET6
if protocol == TCP {
lbf = tcp6BufSize
} else {
lbf = udp6BufSize
}
default:
return nil, nil, errors.New("invalid protocol")
}
// try max 5 times
maxTries := 5
usedBufSize := lbf.getBufSize()
var buf []byte
for i := 1; i <= maxTries; i++ {
bufSizeParam := usedBufSize
buf = make([]byte, bufSizeParam)
var r1 uintptr
switch protocol {
case TCP:
r1, _, err = ipHelper.getExtendedTCPTable.Call(
uintptr(unsafe.Pointer(&buf[0])), // _Out_ PVOID pTcpTable
uintptr(unsafe.Pointer(&bufSizeParam)), // _Inout_ PDWORD pdwSize
0, // _In_ BOOL bOrder
uintptr(afClass), // _In_ ULONG ulAf
iphelperTCPTableOwnerPIDAll, // _In_ TCP_TABLE_CLASS TableClass
0, // _In_ ULONG Reserved
)
case UDP:
r1, _, err = ipHelper.getExtendedUDPTable.Call(
uintptr(unsafe.Pointer(&buf[0])), // _Out_ PVOID pUdpTable,
uintptr(unsafe.Pointer(&bufSizeParam)), // _Inout_ PDWORD pdwSize,
0, // _In_ BOOL bOrder,
uintptr(afClass), // _In_ ULONG ulAf,
iphelperUDPTableOwnerPID, // _In_ UDP_TABLE_CLASS TableClass,
0, // _In_ ULONG Reserved
)
}
switch r1 {
case winErrInsufficientBuffer:
if i >= maxTries {
return nil, nil, fmt.Errorf(
"insufficient buffer error (tried %d times): provided %d bytes; required %d bytes - [NT 0x%X] %s",
i, usedBufSize, bufSizeParam, r1, err,
)
}
// bufSizeParam was modified by ipHelper.getExtended*Table to hold the
// required buffer size.
usedBufSize = lbf.increaseBufSize(bufSizeParam)
case winErrInvalidParameter:
return nil, nil, fmt.Errorf("invalid parameter: [NT 0x%X] %s", r1, err)
case windows.NO_ERROR:
// success
break
default:
return nil, nil, fmt.Errorf("unexpected error: [NT 0x%X] %s", r1, err)
}
}
// parse output
switch {
case protocol == TCP && ipVersion == IPv4:
tcpTable := (*iphelperTCPTable)(unsafe.Pointer(&buf[0]))
// Check if we got more entries than supported.
tableEntries := tcpTable.numEntries
if tableEntries > maxStateTableEntries {
tableEntries = maxStateTableEntries
log.Warningf("network/iphelper: received TCPv4 table with more entries than supported: %d/%d", tcpTable.numEntries, maxStateTableEntries)
}
// Cap table to actual entries.
table := tcpTable.table[:tableEntries]
for _, row := range table {
if row.state == iphelperTCPStateListen {
binds = append(binds, &socket.BindInfo{
Local: socket.Address{
IP: convertIPv4(row.localAddr),
Port: convertPort(row.localPort),
},
PID: int(row.owningPid),
})
} else {
connections = append(connections, &socket.ConnectionInfo{
Local: socket.Address{
IP: convertIPv4(row.localAddr),
Port: convertPort(row.localPort),
},
Remote: socket.Address{
IP: convertIPv4(row.remoteAddr),
Port: convertPort(row.remotePort),
},
PID: int(row.owningPid),
})
}
}
case protocol == TCP && ipVersion == IPv6:
tcpTable := (*iphelperTCP6Table)(unsafe.Pointer(&buf[0]))
// Check if we got more entries than supported.
tableEntries := tcpTable.numEntries
if tableEntries > maxStateTableEntries {
tableEntries = maxStateTableEntries
log.Warningf("network/iphelper: received TCPv6 table with more entries than supported: %d/%d", tcpTable.numEntries, maxStateTableEntries)
}
// Cap table to actual entries.
table := tcpTable.table[:tableEntries]
for _, row := range table {
if row.state == iphelperTCPStateListen {
binds = append(binds, &socket.BindInfo{
Local: socket.Address{
IP: net.IP(row.localAddr[:]),
Port: convertPort(row.localPort),
},
PID: int(row.owningPid),
})
} else {
connections = append(connections, &socket.ConnectionInfo{
Local: socket.Address{
IP: net.IP(row.localAddr[:]),
Port: convertPort(row.localPort),
},
Remote: socket.Address{
IP: net.IP(row.remoteAddr[:]),
Port: convertPort(row.remotePort),
},
PID: int(row.owningPid),
})
}
}
case protocol == UDP && ipVersion == IPv4:
udpTable := (*iphelperUDPTable)(unsafe.Pointer(&buf[0]))
// Check if we got more entries than supported.
tableEntries := udpTable.numEntries
if tableEntries > maxStateTableEntries {
tableEntries = maxStateTableEntries
log.Warningf("network/iphelper: received UDPv4 table with more entries than supported: %d/%d", udpTable.numEntries, maxStateTableEntries)
}
// Cap table to actual entries.
table := udpTable.table[:tableEntries]
for _, row := range table {
binds = append(binds, &socket.BindInfo{
Local: socket.Address{
IP: convertIPv4(row.localAddr),
Port: convertPort(row.localPort),
},
PID: int(row.owningPid),
})
}
case protocol == UDP && ipVersion == IPv6:
udpTable := (*iphelperUDP6Table)(unsafe.Pointer(&buf[0]))
// Check if we got more entries than supported.
tableEntries := udpTable.numEntries
if tableEntries > maxStateTableEntries {
tableEntries = maxStateTableEntries
log.Warningf("network/iphelper: received UDPv6 table with more entries than supported: %d/%d", udpTable.numEntries, maxStateTableEntries)
}
// Cap table to actual entries.
table := udpTable.table[:tableEntries]
for _, row := range table {
binds = append(binds, &socket.BindInfo{
Local: socket.Address{
IP: net.IP(row.localAddr[:]),
Port: convertPort(row.localPort),
},
PID: int(row.owningPid),
})
}
}
return connections, binds, nil
}
// convertIPv4 as needed for iphlpapi.dll
func convertIPv4(input uint32) net.IP {
addressBuf := make([]byte, 4)
binary.LittleEndian.PutUint32(addressBuf, input)
return net.IP(addressBuf)
}
// convertPort converts ports received from iphlpapi.dll
func convertPort(input uint32) uint16 {
return uint16(input>>8 | input<<8)
}

View File

@@ -0,0 +1,54 @@
//go:build windows
package iphelper
import (
"fmt"
"testing"
)
func TestSockets(t *testing.T) {
connections, listeners, err := GetTCP4Table()
if err != nil {
t.Fatal(err)
}
fmt.Println("\nTCP 4 connections:")
for _, connection := range connections {
fmt.Printf("%+v\n", connection)
}
fmt.Println("\nTCP 4 listeners:")
for _, listener := range listeners {
fmt.Printf("%+v\n", listener)
}
connections, listeners, err = GetTCP6Table()
if err != nil {
t.Fatal(err)
}
fmt.Println("\nTCP 6 connections:")
for _, connection := range connections {
fmt.Printf("%+v\n", connection)
}
fmt.Println("\nTCP 6 listeners:")
for _, listener := range listeners {
fmt.Printf("%+v\n", listener)
}
binds, err := GetUDP4Table()
if err != nil {
t.Fatal(err)
}
fmt.Println("\nUDP 4 binds:")
for _, bind := range binds {
fmt.Printf("%+v\n", bind)
}
binds, err = GetUDP6Table()
if err != nil {
t.Fatal(err)
}
fmt.Println("\nUDP 6 binds:")
for _, bind := range binds {
fmt.Printf("%+v\n", bind)
}
}