Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tcp: use GetExtendedTcpTable to display states Closed, Listening, SynSent, SynRcvd, CloseWait, TimeWait ... #1638

Merged
merged 25 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3d4e17f
installer: Add UI + Replace `ADD_FIREWALL_EXCEPTION` with `ADDLOCAL=F…
jkroepke Sep 6, 2024
ed8906f
chore(deps): bump github.com/prometheus/common from 0.55.0 to 0.57.0 …
dependabot[bot] Sep 7, 2024
397b018
container: do not fail hard, if single containers can't be scraped (#…
jkroepke Sep 7, 2024
6f91f83
chore: release 0.29.0.rc0 (#1600)
jkroepke Sep 10, 2024
bb69838
chore: remove test push pipelines (#1621)
jkroepke Sep 11, 2024
4e9f658
installer: fix arm64 msi installer (#1623)
jkroepke Sep 12, 2024
7811d90
service: fix label name in `windows_service_state` (#1625)
jkroepke Sep 12, 2024
107b0f5
perfdata: fix incorrect collector log lines (#1626)
jkroepke Sep 13, 2024
2aa02d8
cpu: Fetch performance counter via PDH.dll via feature toggle. (off b…
jkroepke Sep 13, 2024
17e20bd
chore: remove replace (#1628)
jkroepke Sep 14, 2024
de01ef7
chore: Update github.com/prometheus/client_golang (#1631)
jkroepke Sep 19, 2024
ee32a62
netframework: merge multiple collector into one (Click here for more …
jkroepke Sep 20, 2024
87bde5d
docs: remove old service.services-where CLI frag from docs (#1634)
jkroepke Sep 20, 2024
38c6b63
use iphlpapi.dll to GetExtendedTcpTable
jkroepke Sep 22, 2024
0cd3497
replace syscall with golang.org/x/sys/windows
astigmata Sep 24, 2024
6b5be9f
replace all syscall
astigmata Sep 24, 2024
767c997
use package iphlpapi
astigmata Sep 24, 2024
d8f0fe5
use MIB_TCP6ROW_OWNER_PID
astigmata Sep 25, 2024
73451d2
replace TCPState with connectionsStateCount
astigmata Sep 25, 2024
b77e989
filetime: add collector (#1639)
jkroepke Sep 24, 2024
e94ec65
try to fix lint 2
astigmata Sep 26, 2024
645b8aa
cleanup code
jkroepke Sep 26, 2024
39edc9c
Merge branch 'master' into tcp_probes
jkroepke Sep 26, 2024
a7b3dae
cleanup code
jkroepke Sep 26, 2024
3ac4f4e
Merge branch 'master' into tcp_probes
jkroepke Sep 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/collector.tcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Name | Description | Type | Labels
`windows_tcp_segments_received_total` | Total segments received, including those received in error. This count includes segments received on currently established connections | counter | af
`windows_tcp_segments_retransmitted_total` | Total segments retransmitted. That is, segments transmitted that contain one or more previously transmitted bytes | counter | af
`windows_tcp_segments_sent_total` | Total segments sent, including those on current connections, but excluding those containing *only* retransmitted bytes | counter | af
`windows_tcp_connections_state_count` | Number of TCP connections by state among: CLOSED, LISTENING, SYN_SENT, SYN_RECEIVED, ESTABLISHED, FIN_WAIT1, FIN_WAIT2, CLOSE_WAIT, CLOSING, LAST_ACK, TIME_WAIT, DELETE_TCB | gauge | af

### Example metric
_This collector does not yet have explained examples, we would appreciate your help adding them!_
Expand Down
1 change: 1 addition & 0 deletions pkg/collector/tcp/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tcp
// Win32_PerfRawData_Tcpip_TCPv4 docs
// - https://msdn.microsoft.com/en-us/library/aa394341(v=vs.85).aspx
// The TCPv6 performance object uses the same fields.
// https://learn.microsoft.com/en-us/dotnet/api/system.net.networkinformation.tcpstate?view=net-8.0.
const (
ConnectionFailures = "Connection Failures"
ConnectionsActive = "Connections Active"
Expand Down
62 changes: 54 additions & 8 deletions pkg/collector/tcp/tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import (
"log/slog"

"github.com/alecthomas/kingpin/v2"
"github.com/prometheus-community/windows_exporter/pkg/headers/iphlpapi"
"github.com/prometheus-community/windows_exporter/pkg/perfdata"
"github.com/prometheus-community/windows_exporter/pkg/types"
"github.com/prometheus/client_golang/prometheus"
"github.com/yusufpapurcu/wmi"
"golang.org/x/sys/windows"
)

const Name = "tcp"
Expand All @@ -35,6 +37,7 @@ type Collector struct {
segmentsReceivedTotal *prometheus.Desc
segmentsRetransmittedTotal *prometheus.Desc
segmentsSentTotal *prometheus.Desc
connectionsStateCount *prometheus.Desc
}

func New(config *Config) *Collector {
Expand Down Expand Up @@ -144,6 +147,11 @@ func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error {
[]string{"af"},
nil,
)
c.connectionsStateCount = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "connections_state_count"),
"Number of TCP connections by state and address family",
[]string{"af", "state"}, nil,
)

return nil
}
Expand All @@ -160,10 +168,36 @@ func (c *Collector) Collect(_ *types.ScrapeContext, logger *slog.Logger, ch chan
return err
}

if err := c.collectConnectionsState(ch); err != nil {
logger.Error("failed collecting tcp connection state metrics",
slog.Any("err", err),
)

return err
}

return nil
}

func (c *Collector) collect(ch chan<- prometheus.Metric) error {
data, err := c.perfDataCollector4.Collect()
if err != nil {
return fmt.Errorf("failed to collect TCPv4 metrics: %w", err)
}

c.writeTCPCounters(ch, data[perfdata.EmptyInstance], []string{"ipv4"})

data, err = c.perfDataCollector6.Collect()
if err != nil {
return fmt.Errorf("failed to collect TCPv6 metrics: %w", err)
}

c.writeTCPCounters(ch, data[perfdata.EmptyInstance], []string{"ipv6"})

return nil
}

func writeTCPCounters(metrics map[string]perfdata.CounterValues, labels []string, c *Collector, ch chan<- prometheus.Metric) {
func (c *Collector) writeTCPCounters(ch chan<- prometheus.Metric, metrics map[string]perfdata.CounterValues, labels []string) {
ch <- prometheus.MustNewConstMetric(
c.connectionFailures,
prometheus.CounterValue,
Expand Down Expand Up @@ -220,20 +254,32 @@ func writeTCPCounters(metrics map[string]perfdata.CounterValues, labels []string
)
}

func (c *Collector) collect(ch chan<- prometheus.Metric) error {
data, err := c.perfDataCollector4.Collect()
func (c *Collector) collectConnectionsState(ch chan<- prometheus.Metric) error {
stateCounts, err := iphlpapi.GetTCPConnectionStates(windows.AF_INET)
if err != nil {
return fmt.Errorf("failed to collect TCPv4 metrics: %w", err)
return fmt.Errorf("failed to collect TCP connection states for %s: %w", "ipv4", err)
}

writeTCPCounters(data[perfdata.EmptyInstance], []string{"ipv4"}, c, ch)
c.sendTCPStateMetrics(ch, stateCounts, "ipv4")

data, err = c.perfDataCollector6.Collect()
stateCounts, err = iphlpapi.GetTCPConnectionStates(windows.AF_INET6)
if err != nil {
return fmt.Errorf("failed to collect TCPv6 metrics: %w", err)
return fmt.Errorf("failed to collect TCP6 connection states for %s: %w", "ipv6", err)
}

writeTCPCounters(data[perfdata.EmptyInstance], []string{"ipv6"}, c, ch)
c.sendTCPStateMetrics(ch, stateCounts, "ipv6")

return nil
}

func (c *Collector) sendTCPStateMetrics(ch chan<- prometheus.Metric, stateCounts map[iphlpapi.MIB_TCP_STATE]uint32, af string) {
for state, count := range stateCounts {
ch <- prometheus.MustNewConstMetric(
c.connectionsStateCount,
prometheus.GaugeValue,
float64(count),
af,
state.String(),
)
}
}
6 changes: 6 additions & 0 deletions pkg/headers/iphlpapi/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package iphlpapi

const (
TCPTableClass uint32 = 5
TCP6TableClass uint32 = 5
)
77 changes: 77 additions & 0 deletions pkg/headers/iphlpapi/iphlpapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package iphlpapi

import (
"fmt"
"unsafe"

"golang.org/x/sys/windows"
)

var (
modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
procGetExtendedTcpTable = modiphlpapi.NewProc("GetExtendedTcpTable")
)

func GetTCPConnectionStates(family uint32) (map[MIB_TCP_STATE]uint32, error) {
var size uint32

stateCounts := make(map[MIB_TCP_STATE]uint32)
rowSize := uint32(unsafe.Sizeof(MIB_TCPROW_OWNER_PID{}))
tableClass := TCPTableClass

if family == windows.AF_INET6 {
rowSize = uint32(unsafe.Sizeof(MIB_TCP6ROW_OWNER_PID{}))
tableClass = TCP6TableClass
}

ret := getExtendedTcpTable(0, &size, true, family, tableClass, 0)
if ret != 0 && ret != uintptr(windows.ERROR_INSUFFICIENT_BUFFER) {
return nil, fmt.Errorf("getExtendedTcpTable (size query) failed with code %d", ret)
}

buf := make([]byte, size)

ret = getExtendedTcpTable(uintptr(unsafe.Pointer(&buf[0])), &size, true, family, tableClass, 0)
if ret != 0 {
return nil, fmt.Errorf("getExtendedTcpTable (data query) failed with code %d", ret)
}

numEntries := *(*uint32)(unsafe.Pointer(&buf[0]))

for i := range numEntries {
var state MIB_TCP_STATE

if family == windows.AF_INET6 {
row := (*MIB_TCP6ROW_OWNER_PID)(unsafe.Pointer(&buf[4+i*rowSize]))
state = row.dwState
} else {
row := (*MIB_TCPROW_OWNER_PID)(unsafe.Pointer(&buf[4+i*rowSize]))
state = row.dwState
}

stateCounts[state]++
}

return stateCounts, nil
}

func getExtendedTcpTable(pTCPTable uintptr, pdwSize *uint32, bOrder bool, ulAf uint32, tableClass uint32, reserved uint32) uintptr {
ret, _, _ := procGetExtendedTcpTable.Call(
pTCPTable,
uintptr(unsafe.Pointer(pdwSize)),
uintptr(boolToInt(bOrder)),
uintptr(ulAf),
uintptr(tableClass),
uintptr(reserved),
)

return ret
}

func boolToInt(b bool) int {
if b {
return 1
}

return 0
}
76 changes: 76 additions & 0 deletions pkg/headers/iphlpapi/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package iphlpapi

import "fmt"

// MIB_TCPROW_OWNER_PID structure for IPv4.
// https://learn.microsoft.com/en-us/windows/win32/api/tcpmib/ns-tcpmib-mib_tcprow_owner_pid
type MIB_TCPROW_OWNER_PID struct {
dwState MIB_TCP_STATE
dwLocalAddr uint32
dwLocalPort uint32
dwRemoteAddr uint32
dwRemotePort uint32
dwOwningPid uint32
}

// MIB_TCP6ROW_OWNER_PID structure for IPv6.
// https://learn.microsoft.com/en-us/windows/win32/api/tcpmib/ns-tcpmib-mib_tcp6row_owner_pid
type MIB_TCP6ROW_OWNER_PID struct {
ucLocalAddr [16]byte
dwLocalScopeId uint32
dwLocalPort uint32
ucRemoteAddr [16]byte
dwRemoteScopeId uint32
dwRemotePort uint32
dwState MIB_TCP_STATE
dwOwningPid uint32
}

type MIB_TCP_STATE uint32

const (
_ MIB_TCP_STATE = iota
TCPStateClosed
TCPStateListening
TCPStateSynSent
TCPStateSynRcvd
TCPStateEstablished
TCPStateFinWait1
TCPStateFinWait2
TCPStateCloseWait
TCPStateClosing
TCPStateLastAck
TCPStateTimeWait
TCPStateDeleteTcb
)

func (state MIB_TCP_STATE) String() string {
switch state {
case TCPStateClosed:
return "CLOSED"
case TCPStateListening:
return "LISTENING"
case TCPStateSynSent:
return "SYN_SENT"
case TCPStateSynRcvd:
return "SYN_RECEIVED"
case TCPStateEstablished:
return "ESTABLISHED"
case TCPStateFinWait1:
return "FIN_WAIT1"
case TCPStateFinWait2:
return "FIN_WAIT2"
case TCPStateCloseWait:
return "CLOSE_WAIT"
case TCPStateClosing:
return "CLOSING"
case TCPStateLastAck:
return "LAST_ACK"
case TCPStateTimeWait:
return "TIME_WAIT"
case TCPStateDeleteTcb:
return "DELETE_TCB"
default:
return fmt.Sprintf("UNKNOWN_%d", state)
}
}