@ -10,7 +10,6 @@ import (
"fmt"
"fmt"
"math/bits"
"math/bits"
"net/netip"
"net/netip"
"syscall"
"unsafe"
"unsafe"
"github.com/josharian/native"
"github.com/josharian/native"
@ -18,37 +17,99 @@ import (
"tailscale.com/net/netaddr"
"tailscale.com/net/netaddr"
)
)
// OSMetadata includes any additional OS-specific information that may be
// obtained during the retrieval of a given Entry.
type OSMetadata interface {
GetModule ( ) ( string , error )
}
// See https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getextendedtcptable
// See https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getextendedtcptable
// TCP_TABLE_OWNER_PID_ALL means to include the PID info. The table type
// TCP_TABLE_OWNER_ MODULE_ALL means to include the PID and module . The table type
// we get back from Windows depends on AF_INET vs AF_INET6:
// we get back from Windows depends on AF_INET vs AF_INET6:
// MIB_TCPTABLE_OWNER_PID for v4 or MIB_TCP6TABLE_OWNER_PID for v6.
// MIB_TCPTABLE_OWNER_MODULE for v4 or MIB_TCP6TABLE_OWNER_MODULE for v6.
const tcpTableOwnerPidAll = 5
const tcpTableOwnerModuleAll = 8
// TCPIP_OWNER_MODULE_BASIC_INFO means to request "basic information" about the
// owner module.
const tcpipOwnerModuleBasicInfo = 0
var (
var (
iphlpapi = syscall . NewLazyDLL ( "iphlpapi.dll" )
iphlpapi = windows . NewLazySystemDLL ( "iphlpapi.dll" )
getTCPTable = iphlpapi . NewProc ( "GetExtendedTcpTable" )
getTCPTable = iphlpapi . NewProc ( "GetExtendedTcpTable" )
getOwnerModuleFromTcpEntry = iphlpapi . NewProc ( "GetOwnerModuleFromTcpEntry" )
getOwnerModuleFromTcp6Entry = iphlpapi . NewProc ( "GetOwnerModuleFromTcp6Entry" )
// TODO: GetExtendedUdpTable also? if/when needed.
// TODO: GetExtendedUdpTable also? if/when needed.
)
)
type _MIB_TCPROW_OWNER_PID struct {
// See https://web.archive.org/web/20221219211913/https://learn.microsoft.com/en-us/windows/win32/api/tcpmib/ns-tcpmib-mib_tcprow_owner_module
state uint32
type _MIB_TCPROW_OWNER_MODULE struct {
localAddr uint32
state uint32
localPort uint32
localAddr uint32
remoteAddr uint32
localPort uint32
remotePort uint32
remoteAddr uint32
pid uint32
remotePort uint32
pid uint32
createTimestamp int64
owningModuleInfo [ 16 ] uint64
}
func ( row * _MIB_TCPROW_OWNER_MODULE ) asEntry ( ) Entry {
return Entry {
Local : ipport4 ( row . localAddr , port ( & row . localPort ) ) ,
Remote : ipport4 ( row . remoteAddr , port ( & row . remotePort ) ) ,
Pid : int ( row . pid ) ,
State : state ( row . state ) ,
OSMetadata : row ,
}
}
type _MIB_TCPTABLE_OWNER_MODULE struct {
numEntries uint32
table _MIB_TCPROW_OWNER_MODULE
}
func ( m * _MIB_TCPTABLE_OWNER_MODULE ) getRows ( ) [ ] _MIB_TCPROW_OWNER_MODULE {
return unsafe . Slice ( & m . table , m . numEntries )
}
// See https://web.archive.org/web/20221219212442/https://learn.microsoft.com/en-us/windows/win32/api/tcpmib/ns-tcpmib-mib_tcp6row_owner_module
type _MIB_TCP6ROW_OWNER_MODULE struct {
localAddr [ 16 ] byte
localScope uint32
localPort uint32
remoteAddr [ 16 ] byte
remoteScope uint32
remotePort uint32
state uint32
pid uint32
createTimestamp int64
owningModuleInfo [ 16 ] uint64
}
func ( row * _MIB_TCP6ROW_OWNER_MODULE ) asEntry ( ) Entry {
return Entry {
Local : ipport6 ( row . localAddr , row . localScope , port ( & row . localPort ) ) ,
Remote : ipport6 ( row . remoteAddr , row . remoteScope , port ( & row . remotePort ) ) ,
Pid : int ( row . pid ) ,
State : state ( row . state ) ,
OSMetadata : row ,
}
}
type _MIB_TCP6TABLE_OWNER_MODULE struct {
numEntries uint32
table _MIB_TCP6ROW_OWNER_MODULE
}
}
type _MIB_TCP6ROW_OWNER_PID struct {
func ( m * _MIB_TCP6TABLE_OWNER_MODULE ) getRows ( ) [ ] _MIB_TCP6ROW_OWNER_MODULE {
localAddr [ 16 ] byte
return unsafe . Slice ( & m . table , m . numEntries )
localScope uint32
}
localPort uint32
remoteAddr [ 16 ] byte
// See https://web.archive.org/web/20221219213143/https://learn.microsoft.com/en-us/windows/win32/api/iprtrmib/ns-iprtrmib-tcpip_owner_module_basic_info
remoteScope uint32
type _TCPIP_OWNER_MODULE_BASIC_INFO struct {
remotePort uint32
moduleName * uint16
state uint32
modulePath * uint16
pid uint32
}
}
func get ( ) ( * Table , error ) {
func get ( ) ( * Table , error ) {
@ -72,13 +133,13 @@ func (t *Table) addEntries(fam int) error {
uintptr ( unsafe . Pointer ( & size ) ) ,
uintptr ( unsafe . Pointer ( & size ) ) ,
1 , // sorted
1 , // sorted
uintptr ( fam ) ,
uintptr ( fam ) ,
tcpTableOwner Pid All,
tcpTableOwner Module All,
0 , // reserved; "must be zero"
0 , // reserved; "must be zero"
)
)
if err == 0 {
if err == 0 {
break
break
}
}
if err == uintptr ( syscall . ERROR_INSUFFICIENT_BUFFER ) {
if err == uintptr ( windows . ERROR_INSUFFICIENT_BUFFER ) {
const maxSize = 10 << 20
const maxSize = 10 << 20
if size > maxSize || size < 4 {
if size > maxSize || size < 4 {
return fmt . Errorf ( "unreasonable kernel-reported size %d" , size )
return fmt . Errorf ( "unreasonable kernel-reported size %d" , size )
@ -87,48 +148,28 @@ func (t *Table) addEntries(fam int) error {
addr = unsafe . Pointer ( & buf [ 0 ] )
addr = unsafe . Pointer ( & buf [ 0 ] )
continue
continue
}
}
return syscall . Errno ( err )
return windows . Errno ( err )
}
}
if len ( buf ) < int ( size ) {
if len ( buf ) < int ( size ) {
return errors . New ( "unexpected size growth from system call" )
return errors . New ( "unexpected size growth from system call" )
}
}
buf = buf [ : size ]
buf = buf [ : size ]
numEntries := native . Endian . Uint32 ( buf [ : 4 ] )
buf = buf [ 4 : ]
var recSize int
switch fam {
switch fam {
case windows . AF_INET :
case windows . AF_INET :
recSize = 6 * 4
info := ( * _MIB_TCPTABLE_OWNER_MODULE ) ( unsafe . Pointer ( & buf [ 0 ] ) )
rows := info . getRows ( )
for _ , row := range rows {
t . Entries = append ( t . Entries , row . asEntry ( ) )
}
case windows . AF_INET6 :
case windows . AF_INET6 :
recSize = 6 * 4 + 16 * 2
info := ( * _MIB_TCP6TABLE_OWNER_MODULE ) ( unsafe . Pointer ( & buf [ 0 ] ) )
}
rows := info . getRows ( )
dataLen := numEntries * uint32 ( recSize )
for _ , row := range rows {
if uint32 ( len ( buf ) ) > dataLen {
t . Entries = append ( t . Entries , row . asEntry ( ) )
buf = buf [ : dataLen ]
}
for len ( buf ) >= recSize {
switch fam {
case windows . AF_INET :
row := ( * _MIB_TCPROW_OWNER_PID ) ( unsafe . Pointer ( & buf [ 0 ] ) )
t . Entries = append ( t . Entries , Entry {
Local : ipport4 ( row . localAddr , port ( & row . localPort ) ) ,
Remote : ipport4 ( row . remoteAddr , port ( & row . remotePort ) ) ,
Pid : int ( row . pid ) ,
State : state ( row . state ) ,
} )
case windows . AF_INET6 :
row := ( * _MIB_TCP6ROW_OWNER_PID ) ( unsafe . Pointer ( & buf [ 0 ] ) )
t . Entries = append ( t . Entries , Entry {
Local : ipport6 ( row . localAddr , row . localScope , port ( & row . localPort ) ) ,
Remote : ipport6 ( row . remoteAddr , row . remoteScope , port ( & row . remotePort ) ) ,
Pid : int ( row . pid ) ,
State : state ( row . state ) ,
} )
}
}
buf = buf [ recSize : ]
}
}
return nil
return nil
}
}
@ -178,3 +219,43 @@ func port(v *uint32) uint16 {
}
}
return uint16 ( * v >> 16 )
return uint16 ( * v >> 16 )
}
}
type moduleInfoConstraint interface {
_MIB_TCPROW_OWNER_MODULE | _MIB_TCP6ROW_OWNER_MODULE
}
func moduleInfo [ entryType moduleInfoConstraint ] ( entry * entryType , proc * windows . LazyProc ) ( string , error ) {
var buf [ ] byte
var desiredLen uint32
var addr unsafe . Pointer
for {
e , _ , _ := proc . Call (
uintptr ( unsafe . Pointer ( entry ) ) ,
uintptr ( tcpipOwnerModuleBasicInfo ) ,
uintptr ( addr ) ,
uintptr ( unsafe . Pointer ( & desiredLen ) ) ,
)
err := windows . Errno ( e )
if err == windows . ERROR_SUCCESS {
break
}
if err != windows . ERROR_INSUFFICIENT_BUFFER {
return "" , err
}
buf = make ( [ ] byte , desiredLen )
addr = unsafe . Pointer ( & buf [ 0 ] )
}
basicInfo := ( * _TCPIP_OWNER_MODULE_BASIC_INFO ) ( addr )
return windows . UTF16PtrToString ( basicInfo . moduleName ) , nil
}
func ( m * _MIB_TCPROW_OWNER_MODULE ) GetModule ( ) ( string , error ) {
return moduleInfo ( m , getOwnerModuleFromTcpEntry )
}
func ( m * _MIB_TCP6ROW_OWNER_MODULE ) GetModule ( ) ( string , error ) {
return moduleInfo ( m , getOwnerModuleFromTcp6Entry )
}