mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-02-08 05:56:37 +00:00
dhcp: add dhcp scope stats (#1840)
Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
214
internal/headers/dhcpsapi/dhcpsapi.go
Normal file
214
internal/headers/dhcpsapi/dhcpsapi.go
Normal file
@@ -0,0 +1,214 @@
|
||||
package dhcpsapi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var (
|
||||
modDhcpServer = windows.NewLazySystemDLL("dhcpsapi.dll")
|
||||
procDhcpGetSubnetInfo = modDhcpServer.NewProc("DhcpGetSubnetInfo")
|
||||
procDhcpGetSuperScopeInfoV4 = modDhcpServer.NewProc("DhcpGetSuperScopeInfoV4")
|
||||
procDhcpRpcFreeMemory = modDhcpServer.NewProc("DhcpRpcFreeMemory")
|
||||
procDhcpV4EnumSubnetReservations = modDhcpServer.NewProc("DhcpV4EnumSubnetReservations")
|
||||
procDhcpV4FailoverGetScopeStatistics = modDhcpServer.NewProc("DhcpV4FailoverGetScopeStatistics")
|
||||
procDhcpGetMibInfoV5 = modDhcpServer.NewProc("DhcpGetMibInfoV5")
|
||||
)
|
||||
|
||||
func GetDHCPV4ScopeStatistics() ([]DHCPV4Scope, error) {
|
||||
var mibInfo *DHCP_MIB_INFO_V5
|
||||
|
||||
if err := dhcpGetMibInfoV5(&mibInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer dhcpRpcFreeMemory(unsafe.Pointer(mibInfo))
|
||||
|
||||
subnetScopeInfos := make(map[DHCP_IP_ADDRESS]DHCP_SUBNET_MIB_INFO_V5, mibInfo.Scopes)
|
||||
subnetMIBScopeInfos := unsafe.Slice(mibInfo.ScopeInfo, mibInfo.Scopes)
|
||||
|
||||
for _, subnetMIBScopeInfo := range subnetMIBScopeInfos {
|
||||
subnetScopeInfos[subnetMIBScopeInfo.Subnet] = subnetMIBScopeInfo
|
||||
}
|
||||
|
||||
var superScopeTable *DHCP_SUPER_SCOPE_TABLE
|
||||
|
||||
if err := dhcpGetSuperScopeInfoV4(&superScopeTable); err != nil {
|
||||
return nil, err
|
||||
} else if superScopeTable == nil {
|
||||
return nil, errors.New("dhcpGetSuperScopeInfoV4 returned nil")
|
||||
}
|
||||
|
||||
defer dhcpRpcFreeMemory(unsafe.Pointer(superScopeTable))
|
||||
|
||||
scopes := make([]DHCPV4Scope, 0, superScopeTable.Count)
|
||||
subnets := unsafe.Slice(superScopeTable.Entries, superScopeTable.Count)
|
||||
|
||||
var errs []error
|
||||
|
||||
for _, subnet := range subnets {
|
||||
if err := (func() error {
|
||||
var subnetInfo *DHCP_SUBNET_INFO
|
||||
err := dhcpGetSubnetInfo(subnet.SubnetAddress, &subnetInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get subnet info: %w", err)
|
||||
}
|
||||
|
||||
defer dhcpRpcFreeMemory(unsafe.Pointer(subnetInfo))
|
||||
|
||||
scope := DHCPV4Scope{
|
||||
Name: subnetInfo.SubnetName.String(),
|
||||
SuperScopeName: subnet.SuperScopeName.String(),
|
||||
ScopeIPAddress: net.IPNet{IP: subnetInfo.SubnetAddress.IPv4(), Mask: subnetInfo.SubnetMask.IPv4Mask()},
|
||||
SuperScopeNumber: subnet.SuperScopeNumber,
|
||||
State: subnetInfo.SubnetState,
|
||||
|
||||
AddressesFree: -1,
|
||||
AddressesFreeOnPartnerServer: -1,
|
||||
AddressesFreeOnThisServer: -1,
|
||||
AddressesInUse: -1,
|
||||
AddressesInUseOnPartnerServer: -1,
|
||||
AddressesInUseOnThisServer: -1,
|
||||
PendingOffers: -1,
|
||||
ReservedAddress: -1,
|
||||
}
|
||||
|
||||
if subnetScopeInfo, ok := subnetScopeInfos[subnetInfo.SubnetAddress]; ok {
|
||||
scope.AddressesInUse = float64(subnetScopeInfo.NumAddressesInUse)
|
||||
scope.AddressesFree = float64(subnetScopeInfo.NumAddressesFree)
|
||||
scope.PendingOffers = float64(subnetScopeInfo.NumPendingOffers)
|
||||
}
|
||||
|
||||
subnetReservationCount, err := dhcpV4EnumSubnetReservations(subnet.SubnetAddress)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get subnet reservation count: %w", err)
|
||||
} else {
|
||||
scope.ReservedAddress = float64(subnetReservationCount)
|
||||
}
|
||||
|
||||
var subnetStatistics *DHCP_FAILOVER_STATISTICS
|
||||
err = dhcpV4FailoverGetScopeStatistics(subnet.SubnetAddress, &subnetStatistics)
|
||||
|
||||
defer dhcpRpcFreeMemory(unsafe.Pointer(subnetStatistics))
|
||||
|
||||
if err == nil {
|
||||
scope.AddressesFree = float64(subnetStatistics.AddrFree)
|
||||
scope.AddressesInUse = float64(subnetStatistics.AddrInUse)
|
||||
scope.AddressesFreeOnPartnerServer = float64(subnetStatistics.PartnerAddrFree)
|
||||
scope.AddressesInUseOnPartnerServer = float64(subnetStatistics.PartnerAddrInUse)
|
||||
scope.AddressesFreeOnThisServer = float64(subnetStatistics.ThisAddrFree)
|
||||
scope.AddressesInUseOnThisServer = float64(subnetStatistics.ThisAddrInUse)
|
||||
} else if !errors.Is(err, ERROR_DHCP_FO_SCOPE_NOT_IN_RELATIONSHIP) {
|
||||
return fmt.Errorf("failed to get subnet statistics: %w", err)
|
||||
}
|
||||
|
||||
scopes = append(scopes, scope)
|
||||
|
||||
return nil
|
||||
})(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
return scopes, errors.Join(errs...)
|
||||
}
|
||||
|
||||
// dhcpGetSubnetInfo https://learn.microsoft.com/en-us/windows/win32/api/dhcpsapi/nf-dhcpsapi-dhcpgetsubnetinfo
|
||||
func dhcpGetSubnetInfo(subnetAddress DHCP_IP_ADDRESS, subnetInfo **DHCP_SUBNET_INFO) error {
|
||||
ret, _, _ := procDhcpGetSubnetInfo.Call(
|
||||
0,
|
||||
uintptr(subnetAddress),
|
||||
uintptr(unsafe.Pointer(subnetInfo)),
|
||||
)
|
||||
|
||||
if ret != 0 {
|
||||
return fmt.Errorf("dhcpGetSubnetInfo failed with code %w", windows.Errno(ret))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// dhcpV4FailoverGetScopeStatistics https://learn.microsoft.com/en-us/windows/win32/api/dhcpsapi/nf-dhcpsapi-dhcpv4failovergetscopestatistics
|
||||
func dhcpV4FailoverGetScopeStatistics(scopeId DHCP_IP_ADDRESS, stats **DHCP_FAILOVER_STATISTICS) error {
|
||||
ret, _, _ := procDhcpV4FailoverGetScopeStatistics.Call(
|
||||
0,
|
||||
uintptr(scopeId),
|
||||
uintptr(unsafe.Pointer(stats)),
|
||||
)
|
||||
|
||||
if ret != 0 {
|
||||
return fmt.Errorf("dhcpV4FailoverGetScopeStatistics failed with code %w", windows.Errno(ret))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// dhcpGetSuperScopeInfoV4 https://learn.microsoft.com/en-us/windows/win32/api/dhcpsapi/nf-dhcpsapi-dhcpgetsuperscopeinfov4
|
||||
func dhcpGetSuperScopeInfoV4(superScopeTable **DHCP_SUPER_SCOPE_TABLE) error {
|
||||
ret, _, _ := procDhcpGetSuperScopeInfoV4.Call(
|
||||
0,
|
||||
uintptr(unsafe.Pointer(superScopeTable)),
|
||||
)
|
||||
|
||||
if ret != 0 {
|
||||
return fmt.Errorf("dhcpGetSuperScopeInfoV4 failed with code %w", windows.Errno(ret))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// dhcpGetMibInfoV5 https://learn.microsoft.com/en-us/windows/win32/api/dhcpsapi/nf-dhcpsapi-dhcpgetmibinfov5
|
||||
func dhcpGetMibInfoV5(mibInfo **DHCP_MIB_INFO_V5) error {
|
||||
ret, _, _ := procDhcpGetMibInfoV5.Call(
|
||||
0,
|
||||
uintptr(unsafe.Pointer(mibInfo)),
|
||||
)
|
||||
|
||||
if ret != 0 {
|
||||
return fmt.Errorf("dhcpGetMibInfoV5 failed with code %w", windows.Errno(ret))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// dhcpV4EnumSubnetReservations https://learn.microsoft.com/en-us/windows/win32/api/dhcpsapi/nf-dhcpsapi-dhcpv4enumsubnetreservations
|
||||
func dhcpV4EnumSubnetReservations(subnetAddress DHCP_IP_ADDRESS) (uint32, error) {
|
||||
var (
|
||||
elementsRead uint32
|
||||
elementsTotal uint32
|
||||
elementsInfo uintptr
|
||||
resumeHandle *windows.Handle
|
||||
)
|
||||
|
||||
ret, _, _ := procDhcpV4EnumSubnetReservations.Call(
|
||||
0,
|
||||
uintptr(subnetAddress),
|
||||
uintptr(unsafe.Pointer(&resumeHandle)),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&elementsInfo)),
|
||||
uintptr(unsafe.Pointer(&elementsRead)),
|
||||
uintptr(unsafe.Pointer(&elementsTotal)),
|
||||
)
|
||||
|
||||
dhcpRpcFreeMemory(unsafe.Pointer(elementsInfo))
|
||||
|
||||
if !errors.Is(windows.Errno(ret), windows.ERROR_MORE_DATA) && !errors.Is(windows.Errno(ret), windows.ERROR_NO_MORE_ITEMS) {
|
||||
return 0, fmt.Errorf("dhcpV4EnumSubnetReservations failed with code %w", windows.Errno(ret))
|
||||
}
|
||||
|
||||
return elementsRead + elementsTotal, nil
|
||||
}
|
||||
|
||||
func dhcpRpcFreeMemory(pointer unsafe.Pointer) {
|
||||
if uintptr(pointer) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
//nolint:dogsled
|
||||
_, _, _ = procDhcpRpcFreeMemory.Call(uintptr(pointer))
|
||||
}
|
||||
24
internal/headers/dhcpsapi/dhcpsapi_test.go
Normal file
24
internal/headers/dhcpsapi/dhcpsapi_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package dhcpsapi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func TestGetDHCPV4ScopeStatistics(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if procDhcpGetSuperScopeInfoV4.Find() != nil {
|
||||
t.Skip("DhcpGetSuperScopeInfoV4 is not available")
|
||||
}
|
||||
|
||||
_, err := GetDHCPV4ScopeStatistics()
|
||||
if errors.Is(err, windows.Errno(1753)) {
|
||||
t.Skip(err.Error())
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
}
|
||||
137
internal/headers/dhcpsapi/types.go
Normal file
137
internal/headers/dhcpsapi/types.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package dhcpsapi
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/headers/win32api"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var ERROR_DHCP_FO_SCOPE_NOT_IN_RELATIONSHIP = windows.Errno(20116)
|
||||
|
||||
type DHCPV4Scope struct {
|
||||
Name string
|
||||
State DHCP_SUBNET_STATE
|
||||
SuperScopeName string
|
||||
SuperScopeNumber uint32
|
||||
ScopeIPAddress net.IPNet
|
||||
|
||||
AddressesFree float64
|
||||
AddressesFreeOnPartnerServer float64
|
||||
AddressesFreeOnThisServer float64
|
||||
AddressesInUse float64
|
||||
AddressesInUseOnPartnerServer float64
|
||||
AddressesInUseOnThisServer float64
|
||||
PendingOffers float64
|
||||
ReservedAddress float64
|
||||
}
|
||||
|
||||
type (
|
||||
DHCP_IP_ADDRESS win32api.DWORD
|
||||
DHCP_IP_MASK win32api.DWORD
|
||||
)
|
||||
|
||||
func (ip DHCP_IP_ADDRESS) IPv4() net.IP {
|
||||
ipBytes := make([]byte, 4)
|
||||
|
||||
binary.BigEndian.PutUint32(ipBytes, uint32(ip))
|
||||
|
||||
return ipBytes
|
||||
}
|
||||
|
||||
func (ip DHCP_IP_MASK) IPv4Mask() net.IPMask {
|
||||
ipBytes := make([]byte, 4)
|
||||
|
||||
binary.BigEndian.PutUint32(ipBytes, uint32(ip))
|
||||
|
||||
return ipBytes
|
||||
}
|
||||
|
||||
type DHCP_SUPER_SCOPE_TABLE struct {
|
||||
Count win32api.DWORD
|
||||
Entries *DHCP_SUPER_SCOPE_TABLE_ENTRY
|
||||
}
|
||||
|
||||
type DHCP_SUPER_SCOPE_TABLE_ENTRY struct {
|
||||
SubnetAddress DHCP_IP_ADDRESS
|
||||
SuperScopeNumber win32api.DWORD
|
||||
NextInSuperScope win32api.DWORD
|
||||
SuperScopeName win32api.LPWSTR
|
||||
}
|
||||
|
||||
// DHCP_SUBNET_INFO https://learn.microsoft.com/de-de/windows/win32/api/dhcpsapi/ns-dhcpsapi-dhcp_subnet_info
|
||||
type DHCP_SUBNET_INFO struct {
|
||||
SubnetAddress DHCP_IP_ADDRESS
|
||||
SubnetMask DHCP_IP_MASK
|
||||
SubnetName win32api.LPWSTR
|
||||
SubnetComment win32api.LPWSTR
|
||||
PrimaryHost DHCP_HOST_INFO
|
||||
SubnetState DHCP_SUBNET_STATE
|
||||
}
|
||||
|
||||
type DHCP_HOST_INFO struct {
|
||||
IpAddress DHCP_IP_ADDRESS
|
||||
NetBiosName win32api.LPWSTR
|
||||
HostName win32api.LPWSTR
|
||||
}
|
||||
|
||||
// DHCP_SUBNET_STATE https://learn.microsoft.com/de-de/windows/win32/api/dhcpsapi/ne-dhcpsapi-dhcp_subnet_state
|
||||
type DHCP_SUBNET_STATE uint32
|
||||
|
||||
const (
|
||||
DhcpSubnetEnabled DHCP_SUBNET_STATE = 0
|
||||
DhcpSubnetDisabled DHCP_SUBNET_STATE = 1
|
||||
DhcpSubnetEnabledSwitched DHCP_SUBNET_STATE = 2
|
||||
DhcpSubnetDisabledSwitched DHCP_SUBNET_STATE = 3
|
||||
DhcpSubnetInvalidState DHCP_SUBNET_STATE = 4
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var DHCP_SUBNET_STATE_NAMES = map[DHCP_SUBNET_STATE]string{
|
||||
DhcpSubnetEnabled: "Enabled",
|
||||
DhcpSubnetDisabled: "Disabled",
|
||||
DhcpSubnetEnabledSwitched: "EnabledSwitched",
|
||||
DhcpSubnetDisabledSwitched: "DisabledSwitched",
|
||||
DhcpSubnetInvalidState: "InvalidState",
|
||||
}
|
||||
|
||||
type DHCP_FAILOVER_STATISTICS struct {
|
||||
NumAddr win32api.DWORD
|
||||
AddrFree win32api.DWORD
|
||||
AddrInUse win32api.DWORD
|
||||
PartnerAddrFree win32api.DWORD
|
||||
ThisAddrFree win32api.DWORD
|
||||
PartnerAddrInUse win32api.DWORD
|
||||
ThisAddrInUse win32api.DWORD
|
||||
}
|
||||
|
||||
type DHCP_MIB_INFO_V5 struct {
|
||||
Discovers win32api.DWORD
|
||||
Offers win32api.DWORD
|
||||
Requests win32api.DWORD
|
||||
Acks win32api.DWORD
|
||||
Naks win32api.DWORD
|
||||
Declines win32api.DWORD
|
||||
Releases win32api.DWORD
|
||||
ServerStartTime win32api.DATE_TIME
|
||||
QtnNumLeases win32api.DWORD
|
||||
QtnPctQtnLeases win32api.DWORD
|
||||
QtnProbationLeases win32api.DWORD
|
||||
QtnNonQtnLeases win32api.DWORD
|
||||
QtnExemptLeases win32api.DWORD
|
||||
QtnCapableClients win32api.DWORD
|
||||
QtnIASErrors win32api.DWORD
|
||||
DelayedOffers win32api.DWORD
|
||||
ScopesWithDelayedOffers win32api.DWORD
|
||||
Scopes win32api.DWORD
|
||||
ScopeInfo *DHCP_SUBNET_MIB_INFO_V5
|
||||
}
|
||||
|
||||
type DHCP_SUBNET_MIB_INFO_V5 struct {
|
||||
Subnet DHCP_IP_ADDRESS
|
||||
NumAddressesInUse win32api.DWORD
|
||||
NumAddressesFree win32api.DWORD
|
||||
NumPendingOffers win32api.DWORD
|
||||
}
|
||||
15
internal/headers/win32api/types.go
Normal file
15
internal/headers/win32api/types.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package win32api
|
||||
|
||||
import "golang.org/x/sys/windows"
|
||||
|
||||
type (
|
||||
DATE_TIME = windows.Filetime
|
||||
DWORD = uint32
|
||||
LPWSTR struct {
|
||||
*uint16
|
||||
}
|
||||
)
|
||||
|
||||
func (s LPWSTR) String() string {
|
||||
return windows.UTF16PtrToString(s.uint16)
|
||||
}
|
||||
Reference in New Issue
Block a user