| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- package netstat
- import (
- "errors"
- "fmt"
- "math/bits"
- "net/netip"
- "unsafe"
- "golang.org/x/sys/cpu"
- "golang.org/x/sys/windows"
- "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 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.
- GetModule() (string, error)
- }
- // See https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getextendedtcptable
- // 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:
- // 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
- var (
- iphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
- getTCPTable = iphlpapi.NewProc("GetExtendedTcpTable")
- getOwnerModuleFromTcpEntry = iphlpapi.NewProc("GetOwnerModuleFromTcpEntry")
- getOwnerModuleFromTcp6Entry = iphlpapi.NewProc("GetOwnerModuleFromTcp6Entry")
- // TODO: GetExtendedUdpTable also? if/when needed.
- )
- // 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
- }
- 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
- }
- 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),
- tcpTableOwnerModuleAll,
- 0, // reserved; "must be zero"
- )
- if err == 0 {
- break
- }
- if err == uintptr(windows.ERROR_INSUFFICIENT_BUFFER) {
- 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
- }
- return windows.Errno(err)
- }
- if len(buf) < int(size) {
- return errors.New("unexpected size growth from system call")
- }
- buf = buf[:size]
- switch fam {
- case windows.AF_INET:
- 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:
- info := (*_MIB_TCP6TABLE_OWNER_MODULE)(unsafe.Pointer(&buf[0]))
- rows := info.getRows()
- for _, row := range rows {
- t.Entries = append(t.Entries, row.asEntry())
- }
- }
- 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)
- }
- func ipport4(addr uint32, port uint16) netip.AddrPort {
- if !cpu.IsBigEndian {
- addr = bits.ReverseBytes32(addr)
- }
- return netip.AddrPortFrom(
- netaddr.IPv4(byte(addr>>24), byte(addr>>16), byte(addr>>8), byte(addr)),
- port)
- }
- func ipport6(addr [16]byte, scope uint32, port uint16) netip.AddrPort {
- ip := netip.AddrFrom16(addr).Unmap()
- if scope != 0 {
- // TODO: something better here?
- ip = ip.WithZone(fmt.Sprint(scope))
- }
- return netip.AddrPortFrom(ip, port)
- }
- func port(v *uint32) uint16 {
- if !cpu.IsBigEndian {
- return uint16(bits.ReverseBytes32(*v) >> 16)
- }
- return uint16(*v >> 16)
- }
- type moduleInfoConstraint interface {
- _MIB_TCPROW_OWNER_MODULE | _MIB_TCP6ROW_OWNER_MODULE
- }
- // 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.
- 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_NOT_FOUND {
- return "", nil
- }
- if err != windows.ERROR_INSUFFICIENT_BUFFER {
- return "", err
- }
- if desiredLen > 1<<20 {
- // Sanity check before allocating too much.
- return "", nil
- }
- buf = make([]byte, desiredLen)
- addr = unsafe.Pointer(&buf[0])
- }
- 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.
- return "", nil
- }
- basicInfo := (*_TCPIP_OWNER_MODULE_BASIC_INFO)(addr)
- return windows.UTF16PtrToString(basicInfo.moduleName), nil
- }
- // GetModule implements OSMetadata.
- func (m *_MIB_TCPROW_OWNER_MODULE) GetModule() (string, error) {
- return moduleInfo(m, getOwnerModuleFromTcpEntry)
- }
- // GetModule implements OSMetadata.
- func (m *_MIB_TCP6ROW_OWNER_MODULE) GetModule() (string, error) {
- return moduleInfo(m, getOwnerModuleFromTcp6Entry)
- }
|