2023-01-27 21:37:20 +00:00
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
2020-09-10 22:21:32 +00:00
// Package netstat returns the local machine's network connection table.
package netstat
import (
"errors"
"fmt"
2020-11-15 05:24:09 +00:00
"math/bits"
all: convert more code to use net/netip directly
perl -i -npe 's,netaddr.IPPrefixFrom,netip.PrefixFrom,' $(git grep -l -F netaddr.)
perl -i -npe 's,netaddr.IPPortFrom,netip.AddrPortFrom,' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPrefix,netip.Prefix,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPort,netip.AddrPort,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IP\b,netip.Addr,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPv6Raw\b,netip.AddrFrom16,g' $(git grep -l -F netaddr. )
goimports -w .
Then delete some stuff from the net/netaddr shim package which is no
longer neeed.
Updates #5162
Change-Id: Ia7a86893fe21c7e3ee1ec823e8aba288d4566cd8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-26 04:14:09 +00:00
"net/netip"
2020-09-10 22:21:32 +00:00
"unsafe"
2023-02-02 15:23:13 +00:00
"golang.org/x/sys/cpu"
2020-09-10 22:21:32 +00:00
"golang.org/x/sys/windows"
2022-07-25 03:08:42 +00:00
"tailscale.com/net/netaddr"
2020-09-10 22:21:32 +00:00
)
2022-11-18 20:09:01 +00:00
// OSMetadata includes any additional OS-specific information that may be
// obtained during the retrieval of a given Entry.
type OSMetadata interface {
2023-01-27 18:02:16 +00:00
// GetModule returns the entry's module name.
//
// It returns ("", nil) if no entry is found. As of 2023-01-27, any returned
// error is silently discarded by its sole caller in portlist_windows.go and
// treated equivalently as returning ("", nil), but this may change in the
// future. An error should only be returned in casees that are worthy of
// being logged at least.
2022-11-18 20:09:01 +00:00
GetModule ( ) ( string , error )
}
2020-09-10 22:21:32 +00:00
// See https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getextendedtcptable
2022-11-18 20:09:01 +00:00
// TCP_TABLE_OWNER_MODULE_ALL means to include the PID and module. The table type
2020-09-10 22:21:32 +00:00
// we get back from Windows depends on AF_INET vs AF_INET6:
2022-11-18 20:09:01 +00:00
// MIB_TCPTABLE_OWNER_MODULE for v4 or MIB_TCP6TABLE_OWNER_MODULE for v6.
const tcpTableOwnerModuleAll = 8
// TCPIP_OWNER_MODULE_BASIC_INFO means to request "basic information" about the
// owner module.
const tcpipOwnerModuleBasicInfo = 0
2020-09-10 22:21:32 +00:00
var (
2022-11-18 20:09:01 +00:00
iphlpapi = windows . NewLazySystemDLL ( "iphlpapi.dll" )
getTCPTable = iphlpapi . NewProc ( "GetExtendedTcpTable" )
getOwnerModuleFromTcpEntry = iphlpapi . NewProc ( "GetOwnerModuleFromTcpEntry" )
getOwnerModuleFromTcp6Entry = iphlpapi . NewProc ( "GetOwnerModuleFromTcp6Entry" )
2020-09-10 22:21:32 +00:00
// TODO: GetExtendedUdpTable also? if/when needed.
)
2022-11-18 20:09:01 +00:00
// See https://web.archive.org/web/20221219211913/https://learn.microsoft.com/en-us/windows/win32/api/tcpmib/ns-tcpmib-mib_tcprow_owner_module
type _MIB_TCPROW_OWNER_MODULE struct {
state uint32
localAddr uint32
localPort uint32
remoteAddr 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
2020-09-10 22:21:32 +00:00
}
2022-11-18 20:09:01 +00:00
func ( m * _MIB_TCP6TABLE_OWNER_MODULE ) getRows ( ) [ ] _MIB_TCP6ROW_OWNER_MODULE {
return unsafe . Slice ( & m . table , m . numEntries )
}
// See https://web.archive.org/web/20221219213143/https://learn.microsoft.com/en-us/windows/win32/api/iprtrmib/ns-iprtrmib-tcpip_owner_module_basic_info
type _TCPIP_OWNER_MODULE_BASIC_INFO struct {
moduleName * uint16
modulePath * uint16
2020-09-10 22:21:32 +00:00
}
func get ( ) ( * Table , error ) {
t := new ( Table )
if err := t . addEntries ( windows . AF_INET ) ; err != nil {
return nil , fmt . Errorf ( "failed to get IPv4 entries: %w" , err )
}
if err := t . addEntries ( windows . AF_INET6 ) ; err != nil {
return nil , fmt . Errorf ( "failed to get IPv6 entries: %w" , err )
}
return t , nil
}
func ( t * Table ) addEntries ( fam int ) error {
var size uint32
var addr unsafe . Pointer
var buf [ ] byte
for {
err , _ , _ := getTCPTable . Call (
uintptr ( addr ) ,
uintptr ( unsafe . Pointer ( & size ) ) ,
1 , // sorted
uintptr ( fam ) ,
2022-11-18 20:09:01 +00:00
tcpTableOwnerModuleAll ,
2020-09-10 22:21:32 +00:00
0 , // reserved; "must be zero"
)
if err == 0 {
break
}
2022-11-18 20:09:01 +00:00
if err == uintptr ( windows . ERROR_INSUFFICIENT_BUFFER ) {
2020-09-10 22:21:32 +00:00
const maxSize = 10 << 20
if size > maxSize || size < 4 {
return fmt . Errorf ( "unreasonable kernel-reported size %d" , size )
}
buf = make ( [ ] byte , size )
addr = unsafe . Pointer ( & buf [ 0 ] )
continue
}
2022-11-18 20:09:01 +00:00
return windows . Errno ( err )
2020-09-10 22:21:32 +00:00
}
if len ( buf ) < int ( size ) {
return errors . New ( "unexpected size growth from system call" )
}
buf = buf [ : size ]
switch fam {
case windows . AF_INET :
2022-11-18 20:09:01 +00:00
info := ( * _MIB_TCPTABLE_OWNER_MODULE ) ( unsafe . Pointer ( & buf [ 0 ] ) )
rows := info . getRows ( )
for _ , row := range rows {
t . Entries = append ( t . Entries , row . asEntry ( ) )
}
2020-09-10 22:21:32 +00:00
case windows . AF_INET6 :
2022-11-18 20:09:01 +00:00
info := ( * _MIB_TCP6TABLE_OWNER_MODULE ) ( unsafe . Pointer ( & buf [ 0 ] ) )
rows := info . getRows ( )
for _ , row := range rows {
t . Entries = append ( t . Entries , row . asEntry ( ) )
2020-09-10 22:21:32 +00:00
}
}
2022-11-18 20:09:01 +00:00
2020-09-10 22:21:32 +00:00
return nil
}
var states = [ ] string {
"" ,
"CLOSED" ,
"LISTEN" ,
"SYN-SENT" ,
"SYN-RECEIVED" ,
"ESTABLISHED" ,
"FIN-WAIT-1" ,
"FIN-WAIT-2" ,
"CLOSE-WAIT" ,
"CLOSING" ,
"LAST-ACK" ,
"DELETE-TCB" ,
}
func state ( v uint32 ) string {
if v < uint32 ( len ( states ) ) {
return states [ v ]
}
return fmt . Sprintf ( "unknown-state-%d" , v )
}
all: convert more code to use net/netip directly
perl -i -npe 's,netaddr.IPPrefixFrom,netip.PrefixFrom,' $(git grep -l -F netaddr.)
perl -i -npe 's,netaddr.IPPortFrom,netip.AddrPortFrom,' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPrefix,netip.Prefix,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPort,netip.AddrPort,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IP\b,netip.Addr,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPv6Raw\b,netip.AddrFrom16,g' $(git grep -l -F netaddr. )
goimports -w .
Then delete some stuff from the net/netaddr shim package which is no
longer neeed.
Updates #5162
Change-Id: Ia7a86893fe21c7e3ee1ec823e8aba288d4566cd8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-26 04:14:09 +00:00
func ipport4 ( addr uint32 , port uint16 ) netip . AddrPort {
2023-02-02 15:23:13 +00:00
if ! cpu . IsBigEndian {
2020-11-15 05:24:09 +00:00
addr = bits . ReverseBytes32 ( addr )
}
all: convert more code to use net/netip directly
perl -i -npe 's,netaddr.IPPrefixFrom,netip.PrefixFrom,' $(git grep -l -F netaddr.)
perl -i -npe 's,netaddr.IPPortFrom,netip.AddrPortFrom,' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPrefix,netip.Prefix,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPort,netip.AddrPort,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IP\b,netip.Addr,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPv6Raw\b,netip.AddrFrom16,g' $(git grep -l -F netaddr. )
goimports -w .
Then delete some stuff from the net/netaddr shim package which is no
longer neeed.
Updates #5162
Change-Id: Ia7a86893fe21c7e3ee1ec823e8aba288d4566cd8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-26 04:14:09 +00:00
return netip . AddrPortFrom (
2021-05-15 01:07:28 +00:00
netaddr . IPv4 ( byte ( addr >> 24 ) , byte ( addr >> 16 ) , byte ( addr >> 8 ) , byte ( addr ) ) ,
port )
2020-09-10 22:21:32 +00:00
}
all: convert more code to use net/netip directly
perl -i -npe 's,netaddr.IPPrefixFrom,netip.PrefixFrom,' $(git grep -l -F netaddr.)
perl -i -npe 's,netaddr.IPPortFrom,netip.AddrPortFrom,' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPrefix,netip.Prefix,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPort,netip.AddrPort,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IP\b,netip.Addr,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPv6Raw\b,netip.AddrFrom16,g' $(git grep -l -F netaddr. )
goimports -w .
Then delete some stuff from the net/netaddr shim package which is no
longer neeed.
Updates #5162
Change-Id: Ia7a86893fe21c7e3ee1ec823e8aba288d4566cd8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-26 04:14:09 +00:00
func ipport6 ( addr [ 16 ] byte , scope uint32 , port uint16 ) netip . AddrPort {
2022-08-02 20:38:11 +00:00
ip := netip . AddrFrom16 ( addr ) . Unmap ( )
2020-09-10 22:21:32 +00:00
if scope != 0 {
// TODO: something better here?
ip = ip . WithZone ( fmt . Sprint ( scope ) )
}
all: convert more code to use net/netip directly
perl -i -npe 's,netaddr.IPPrefixFrom,netip.PrefixFrom,' $(git grep -l -F netaddr.)
perl -i -npe 's,netaddr.IPPortFrom,netip.AddrPortFrom,' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPrefix,netip.Prefix,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPort,netip.AddrPort,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IP\b,netip.Addr,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPv6Raw\b,netip.AddrFrom16,g' $(git grep -l -F netaddr. )
goimports -w .
Then delete some stuff from the net/netaddr shim package which is no
longer neeed.
Updates #5162
Change-Id: Ia7a86893fe21c7e3ee1ec823e8aba288d4566cd8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-26 04:14:09 +00:00
return netip . AddrPortFrom ( ip , port )
2020-09-10 22:21:32 +00:00
}
func port ( v * uint32 ) uint16 {
2023-02-02 15:23:13 +00:00
if ! cpu . IsBigEndian {
2020-11-15 05:24:09 +00:00
return uint16 ( bits . ReverseBytes32 ( * v ) >> 16 )
}
return uint16 ( * v >> 16 )
2020-09-10 22:21:32 +00:00
}
2022-11-18 20:09:01 +00:00
type moduleInfoConstraint interface {
_MIB_TCPROW_OWNER_MODULE | _MIB_TCP6ROW_OWNER_MODULE
}
2023-01-27 18:02:16 +00:00
// moduleInfo implements OSMetadata.GetModule. It calls
// getOwnerModuleFromTcpEntry or getOwnerModuleFromTcp6Entry.
//
// See
// https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getownermodulefromtcpentry
//
// It may return "", nil indicating a successful call but with empty data.
2022-11-18 20:09:01 +00:00
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
}
2023-01-27 18:02:16 +00:00
if err == windows . ERROR_NOT_FOUND {
return "" , nil
}
2022-11-18 20:09:01 +00:00
if err != windows . ERROR_INSUFFICIENT_BUFFER {
return "" , err
}
2023-01-27 18:02:16 +00:00
if desiredLen > 1 << 20 {
// Sanity check before allocating too much.
return "" , nil
}
2022-11-18 20:09:01 +00:00
buf = make ( [ ] byte , desiredLen )
addr = unsafe . Pointer ( & buf [ 0 ] )
}
2023-01-27 18:02:16 +00:00
if addr == nil {
// GetOwnerModuleFromTcp*Entry can apparently return ERROR_SUCCESS
// (NO_ERROR) on the first call without the usual first
// ERROR_INSUFFICIENT_BUFFER result. Windows said success, so interpret
// that was sucessfully not having data.
2023-01-27 16:31:00 +00:00
return "" , nil
}
2023-01-27 18:02:16 +00:00
basicInfo := ( * _TCPIP_OWNER_MODULE_BASIC_INFO ) ( addr )
2022-11-18 20:09:01 +00:00
return windows . UTF16PtrToString ( basicInfo . moduleName ) , nil
}
2023-01-27 18:02:16 +00:00
// GetModule implements OSMetadata.
2022-11-18 20:09:01 +00:00
func ( m * _MIB_TCPROW_OWNER_MODULE ) GetModule ( ) ( string , error ) {
return moduleInfo ( m , getOwnerModuleFromTcpEntry )
}
2023-01-27 18:02:16 +00:00
// GetModule implements OSMetadata.
2022-11-18 20:09:01 +00:00
func ( m * _MIB_TCP6ROW_OWNER_MODULE ) GetModule ( ) ( string , error ) {
return moduleInfo ( m , getOwnerModuleFromTcp6Entry )
}