diff --git a/docs/collector.dhcp.md b/docs/collector.dhcp.md index 01d369a6..20eb1579 100644 --- a/docs/collector.dhcp.md +++ b/docs/collector.dhcp.md @@ -2,49 +2,177 @@ The dhcp collector exposes DHCP Server metrics -||| --|- -Metric name prefix | `dhcp` -Data source | Perflib -Classes | `DHCP Server` -Enabled by default? | No +| | | +|---------------------|---------------| +| Metric name prefix | `dhcp` | +| Data source | Perflib | +| Classes | `DHCP Server` | +| Enabled by default? | No | ## Flags -None +### `--collector.dhcp.enabled` + +Comma-separated list of collectors to use. Defaults to all, if not specified. ## Metrics -Name | Description | Type | Labels ------|-------------|------|------- -`packets_received_total` | Total number of packets received by the DHCP server | counter | None -`duplicates_dropped_total` | Total number of duplicate packets received by the DHCP server | counter | None -`packets_expired_total` | Total number of packets expired in the DHCP server message queue | counter | None -`active_queue_length` | Number of packets in the processing queue of the DHCP server | gauge | None -`conflict_check_queue_length` | Number of packets in the DHCP server queue waiting on conflict detection (ping) | gauge | None -`discovers_total` | Total DHCP Discovers received by the DHCP server | counter | None -`offers_total` | Total DHCP Offers sent by the DHCP server | counter | None -`requests_total` | Total DHCP Requests received by the DHCP server | counter | None -`informs_total` | Total DHCP Informs received by the DHCP server | counter | None -`acks_total` | Total DHCP Acks sent by the DHCP server | counter | None -`nacks_total` | Total DHCP Nacks sent by the DHCP server | counter | None -`declines_total` | Total DHCP Declines received by the DHCP server | counter | None -`releases_total` | Total DHCP Releases received by the DHCP server | counter | None -`offer_queue_length` | Number of packets in the offer queue of the DHCP server | gauge | None -`denied_due_to_match_total` | Total number of DHCP requests denied, based on matches from the Deny List | gauge | None -`denied_due_to_nonmatch_total` | Total number of DHCP requests denied, based on non-matches from the Allow List | gauge | None -`failover_bndupd_sent_total` | Number of DHCP failover Binding Update messages sent | counter | None -`failover_bndupd_received_total` | Number of DHCP failover Binding Update messages received | counter | None -`failover_bndack_sent_total` | Number of DHCP failover Binding Ack messages sent | counter | None -`failover_bndack_received_total` | Number of DHCP failover Binding Ack messages received | counter | None -`failover_bndupd_pending_in_outbound_queue` | Number of pending outbound DHCP failover Binding Update messages | counter | None -`failover_transitions_communicationinterrupted_state_total` | Total number of transitions into COMMUNICATION INTERRUPTED state | counter | None -`failover_transitions_partnerdown_state_total` | Total number of transitions into PARTNER DOWN state | counter | None -`failover_transitions_recover_total` | Total number of transitions into RECOVER state | counter | None -`failover_bndupd_dropped_total` | Total number of DHCP faileover Binding Updates dropped | counter | None +| Name | Description | Type | Labels | +|--------------------------------------------------------------------------|--------------------------------------------------------------------------------|---------|-----------------------------------------------------| +| `windows_dhcp_ack_total` | Total DHCP Acks sent by the DHCP server | counter | None | +| `windows_dhcp_denied_due_to_match_total` | Total number of DHCP requests denied, based on matches from the Deny List | gauge | None | +| `windows_dhcp_denied_due_to_nonmatch_total` | Total number of DHCP requests denied, based on non-matches from the Allow List | gauge | None | +| `windows_dhcp_declines_total` | Total DHCP Declines received by the DHCP server | counter | None | +| `windows_dhcp_discovers_total` | Total DHCP Discovers received by the DHCP server | counter | None | +| `windows_dhcp_failover_bndack_received_total` | Number of DHCP failover Binding Ack messages received | counter | None | +| `windows_dhcp_failover_bndack_sent_total` | Number of DHCP failover Binding Ack messages sent | counter | None | +| `windows_dhcp_failover_bndupd_dropped_total` | Total number of DHCP failover Binding Updates dropped | counter | None | +| `windows_dhcp_failover_bndupd_received_total` | Number of DHCP failover Binding Update messages received | counter | None | +| `windows_dhcp_failover_bndupd_sent_total` | Number of DHCP failover Binding Update messages sent | counter | None | +| `windows_dhcp_failover_bndupd_pending_in_outbound_queue` | Number of pending outbound DHCP failover Binding Update messages | counter | None | +| `windows_dhcp_failover_transitions_communicationinterrupted_state_total` | Total number of transitions into COMMUNICATION INTERRUPTED state | counter | None | +| `windows_dhcp_failover_transitions_partnerdown_state_total` | Total number of transitions into PARTNER DOWN state | counter | None | +| `windows_dhcp_failover_transitions_recover_total` | Total number of transitions into RECOVER state | counter | None | +| `windows_dhcp_informs_total` | Total DHCP Informs received by the DHCP server | counter | None | +| `windows_dhcp_nacks_total` | Total DHCP Nacks sent by the DHCP server | counter | None | +| `windows_dhcp_offers_total` | Total DHCP Offers sent by the DHCP server | counter | None | +| `windows_dhcp_packets_expired_total` | Total number of packets expired in the DHCP server message queue | counter | None | +| `windows_dhcp_packets_received_total` | Total number of packets received by the DHCP server | counter | None | +| `windows_dhcp_pending_offers_total` | Total number of pending offers in the DHCP server | counter | None | +| `windows_dhcp_releases_total` | Total DHCP Releases received by the DHCP server | counter | None | +| `windows_dhcp_requests_total` | Total DHCP Requests received by the DHCP server | counter | None | +| `windows_dhcp_scope_addresses_free_on_this_server` | DHCP Scope free addresses on this server | gauge | `scope` | +| `windows_dhcp_scope_addresses_free_on_partner_server` | DHCP Scope free addresses on partner server | gauge | `scope` | +| `windows_dhcp_scope_addresses_free` | DHCP Scope free addresses | gauge | `scope` | +| `windows_dhcp_scope_addresses_in_use_on_this_server` | DHCP Scope addresses in use on this server | gauge | `scope` | +| `windows_dhcp_scope_addresses_in_use_on_partner_server` | DHCP Scope addresses in use on partner server | gauge | `scope` | +| `windows_dhcp_scope_addresses_in_use` | DHCP Scope addresses in use | gauge | `scope` | +| `windows_dhcp_scope_info` | DHCP Scope information | gauge | `name`, `superscope_name`, `superscope_id`, `scope` | +| `windows_dhcp_scope_pending_offers` | DHCP Scope pending offers | gauge | `scope` | +| `windows_dhcp_scope_reserved_address` | DHCP Scope reserved addresses | gauge | `scope` | +| `windows_dhcp_scope_state` | DHCP Scope state | gauge | `scope`, `state` | + ### Example metric -_This collector does not yet have explained examples, we would appreciate your help adding them!_ +``` +# HELP windows_dhcp_acks_total Total DHCP Acks sent by the DHCP server (AcksTotal) +# TYPE windows_dhcp_acks_total counter +windows_dhcp_acks_total 0 +# HELP windows_dhcp_active_queue_length Number of packets in the processing queue of the DHCP server (ActiveQueueLength) +# TYPE windows_dhcp_active_queue_length gauge +windows_dhcp_active_queue_length 0 +# HELP windows_dhcp_conflict_check_queue_length Number of packets in the DHCP server queue waiting on conflict detection (ping). (ConflictCheckQueueLength) +# TYPE windows_dhcp_conflict_check_queue_length gauge +windows_dhcp_conflict_check_queue_length 0 +# HELP windows_dhcp_declines_total Total DHCP Declines received by the DHCP server (DeclinesTotal) +# TYPE windows_dhcp_declines_total counter +windows_dhcp_declines_total 0 +# HELP windows_dhcp_denied_due_to_match_total Total number of DHCP requests denied, based on matches from the Deny list (DeniedDueToMatch) +# TYPE windows_dhcp_denied_due_to_match_total counter +windows_dhcp_denied_due_to_match_total 0 +# HELP windows_dhcp_denied_due_to_nonmatch_total Total number of DHCP requests denied, based on non-matches from the Allow list (DeniedDueToNonMatch) +# TYPE windows_dhcp_denied_due_to_nonmatch_total counter +windows_dhcp_denied_due_to_nonmatch_total 0 +# HELP windows_dhcp_discovers_total Total DHCP Discovers received by the DHCP server (DiscoversTotal) +# TYPE windows_dhcp_discovers_total counter +windows_dhcp_discovers_total 0 +# HELP windows_dhcp_duplicates_dropped_total Total number of duplicate packets received by the DHCP server (DuplicatesDroppedTotal) +# TYPE windows_dhcp_duplicates_dropped_total counter +windows_dhcp_duplicates_dropped_total 0 +# HELP windows_dhcp_failover_bndack_received_total Number of DHCP fail over Binding Ack messages received (FailoverBndackReceivedTotal) +# TYPE windows_dhcp_failover_bndack_received_total counter +windows_dhcp_failover_bndack_received_total 0 +# HELP windows_dhcp_failover_bndack_sent_total Number of DHCP fail over Binding Ack messages sent (FailoverBndackSentTotal) +# TYPE windows_dhcp_failover_bndack_sent_total counter +windows_dhcp_failover_bndack_sent_total 0 +# HELP windows_dhcp_failover_bndupd_dropped_total Total number of DHCP fail over Binding Updates dropped (FailoverBndupdDropped) +# TYPE windows_dhcp_failover_bndupd_dropped_total counter +windows_dhcp_failover_bndupd_dropped_total 0 +# HELP windows_dhcp_failover_bndupd_pending_in_outbound_queue Number of pending outbound DHCP fail over Binding Update messages (FailoverBndupdPendingOutboundQueue) +# TYPE windows_dhcp_failover_bndupd_pending_in_outbound_queue gauge +windows_dhcp_failover_bndupd_pending_in_outbound_queue 0 +# HELP windows_dhcp_failover_bndupd_received_total Number of DHCP fail over Binding Update messages received (FailoverBndupdReceivedTotal) +# TYPE windows_dhcp_failover_bndupd_received_total counter +windows_dhcp_failover_bndupd_received_total 0 +# HELP windows_dhcp_failover_bndupd_sent_total Number of DHCP fail over Binding Update messages sent (FailoverBndupdSentTotal) +# TYPE windows_dhcp_failover_bndupd_sent_total counter +windows_dhcp_failover_bndupd_sent_total 0 +# HELP windows_dhcp_failover_transitions_communicationinterrupted_state_total Total number of transitions into COMMUNICATION INTERRUPTED state (FailoverTransitionsCommunicationinterruptedState) +# TYPE windows_dhcp_failover_transitions_communicationinterrupted_state_total counter +windows_dhcp_failover_transitions_communicationinterrupted_state_total 0 +# HELP windows_dhcp_failover_transitions_partnerdown_state_total Total number of transitions into PARTNER DOWN state (FailoverTransitionsPartnerdownState) +# TYPE windows_dhcp_failover_transitions_partnerdown_state_total counter +windows_dhcp_failover_transitions_partnerdown_state_total 0 +# HELP windows_dhcp_failover_transitions_recover_total Total number of transitions into RECOVER state (FailoverTransitionsRecoverState) +# TYPE windows_dhcp_failover_transitions_recover_total counter +windows_dhcp_failover_transitions_recover_total 0 +# HELP windows_dhcp_informs_total Total DHCP Informs received by the DHCP server (InformsTotal) +# TYPE windows_dhcp_informs_total counter +windows_dhcp_informs_total 0 +# HELP windows_dhcp_nacks_total Total DHCP Nacks sent by the DHCP server (NacksTotal) +# TYPE windows_dhcp_nacks_total counter +windows_dhcp_nacks_total 0 +# HELP windows_dhcp_offer_queue_length Number of packets in the offer queue of the DHCP server (OfferQueueLength) +# TYPE windows_dhcp_offer_queue_length gauge +windows_dhcp_offer_queue_length 0 +# HELP windows_dhcp_offers_total Total DHCP Offers sent by the DHCP server (OffersTotal) +# TYPE windows_dhcp_offers_total counter +windows_dhcp_offers_total 0 +# HELP windows_dhcp_packets_expired_total Total number of packets expired in the DHCP server message queue (PacketsExpiredTotal) +# TYPE windows_dhcp_packets_expired_total counter +windows_dhcp_packets_expired_total 0 +# HELP windows_dhcp_packets_received_total Total number of packets received by the DHCP server (PacketsReceivedTotal) +# TYPE windows_dhcp_packets_received_total counter +windows_dhcp_packets_received_total 0 +# HELP windows_dhcp_releases_total Total DHCP Releases received by the DHCP server (ReleasesTotal) +# TYPE windows_dhcp_releases_total counter +windows_dhcp_releases_total 0 +# HELP windows_dhcp_requests_total Total DHCP Requests received by the DHCP server (RequestsTotal) +# TYPE windows_dhcp_requests_total counter +windows_dhcp_requests_total 0 +# HELP windows_dhcp_scope_addresses_free_total DHCP Scope free addresses +# TYPE windows_dhcp_scope_addresses_free_total gauge +windows_dhcp_scope_addresses_free_total{scope="10.11.12.0/25"} 0 +windows_dhcp_scope_addresses_free_total{scope="172.16.0.0/24"} 0 +windows_dhcp_scope_addresses_free_total{scope="192.168.0.0/24"} 231 +# HELP windows_dhcp_scope_addresses_in_use_total DHCP Scope addresses in use +# TYPE windows_dhcp_scope_addresses_in_use_total gauge +windows_dhcp_scope_addresses_in_use_total{scope="10.11.12.0/25"} 0 +windows_dhcp_scope_addresses_in_use_total{scope="172.16.0.0/24"} 0 +windows_dhcp_scope_addresses_in_use_total{scope="192.168.0.0/24"} 0 +# HELP windows_dhcp_scope_info DHCP Scope information +# TYPE windows_dhcp_scope_info gauge +windows_dhcp_scope_info{name="SUBSUPERSCOPE",scope="172.16.0.0/24",superscope_id="2",superscope_name="SUPERSCOPE"} 1 +windows_dhcp_scope_info{name="TEST",scope="192.168.0.0/24",superscope_id="0",superscope_name=""} 1 +windows_dhcp_scope_info{name="TEST2",scope="10.11.12.0/25",superscope_id="2",superscope_name="SUPERSCOPE"} 1 +# HELP windows_dhcp_scope_pending_offers_total DHCP Scope pending offers +# TYPE windows_dhcp_scope_pending_offers_total gauge +windows_dhcp_scope_pending_offers_total{scope="10.11.12.0/25"} 0 +windows_dhcp_scope_pending_offers_total{scope="172.16.0.0/24"} 0 +windows_dhcp_scope_pending_offers_total{scope="192.168.0.0/24"} 0 +# HELP windows_dhcp_scope_reserved_address_total DHCP Scope reserved addresses +# TYPE windows_dhcp_scope_reserved_address_total gauge +windows_dhcp_scope_reserved_address_total{scope="10.11.12.0/25"} 0 +windows_dhcp_scope_reserved_address_total{scope="172.16.0.0/24"} 0 +windows_dhcp_scope_reserved_address_total{scope="192.168.0.0/24"} 2 +# HELP windows_dhcp_scope_state DHCP Scope state +# TYPE windows_dhcp_scope_state gauge +windows_dhcp_scope_state{scope="10.11.12.0/25",state="Disabled"} 1 +windows_dhcp_scope_state{scope="10.11.12.0/25",state="DisabledSwitched"} 0 +windows_dhcp_scope_state{scope="10.11.12.0/25",state="Enabled"} 0 +windows_dhcp_scope_state{scope="10.11.12.0/25",state="EnabledSwitched"} 0 +windows_dhcp_scope_state{scope="10.11.12.0/25",state="InvalidState"} 0 +windows_dhcp_scope_state{scope="172.16.0.0/24",state="Disabled"} 1 +windows_dhcp_scope_state{scope="172.16.0.0/24",state="DisabledSwitched"} 0 +windows_dhcp_scope_state{scope="172.16.0.0/24",state="Enabled"} 0 +windows_dhcp_scope_state{scope="172.16.0.0/24",state="EnabledSwitched"} 0 +windows_dhcp_scope_state{scope="172.16.0.0/24",state="InvalidState"} 0 +windows_dhcp_scope_state{scope="192.168.0.0/24",state="Disabled"} 0 +windows_dhcp_scope_state{scope="192.168.0.0/24",state="DisabledSwitched"} 0 +windows_dhcp_scope_state{scope="192.168.0.0/24",state="Enabled"} 1 +windows_dhcp_scope_state{scope="192.168.0.0/24",state="EnabledSwitched"} 0 +windows_dhcp_scope_state{scope="192.168.0.0/24",state="InvalidState"} 0 +``` ## Useful queries _This collector does not yet have any useful queries added, we would appreciate your help adding them!_ diff --git a/docs/collector.net.md b/docs/collector.net.md index 1006aaa7..112c1718 100644 --- a/docs/collector.net.md +++ b/docs/collector.net.md @@ -19,6 +19,10 @@ If given, an interface name needs to match the include regexp in order for the c If given, an interface name needs to *not* match the exclude regexp in order for the corresponding metrics to be reported +### `--collector.net.enabled` + +Comma-separated list of collectors to use. Defaults to all, if not specified. + ## Metrics Name | Description | Type | Labels diff --git a/docs/collector.netframework.md b/docs/collector.netframework.md index 4ef4a368..4b503dc6 100644 --- a/docs/collector.netframework.md +++ b/docs/collector.netframework.md @@ -10,7 +10,9 @@ The netframework collector exposes metrics about dotnet framework. ## Flags -None +### `--collector.netframework.enabled` + +Comma-separated list of collectors to use. Defaults to all, if not specified. ## Metrics diff --git a/internal/collector/dhcp/dhcp.go b/internal/collector/dhcp/dhcp.go index 71540bd6..49991e64 100644 --- a/internal/collector/dhcp/dhcp.go +++ b/internal/collector/dhcp/dhcp.go @@ -16,22 +16,39 @@ package dhcp import ( + "errors" "fmt" "log/slog" + "slices" + "strconv" + "strings" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/headers/dhcpsapi" "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/pdh" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" ) -const Name = "dhcp" +const ( + Name = "dhcp" -type Config struct{} + subCollectorServerMetrics = "server_metrics" + subCollectorScopeMetrics = "scope_metrics" +) + +type Config struct { + CollectorsEnabled []string `yaml:"collectors_enabled"` +} //nolint:gochecknoglobals -var ConfigDefaults = Config{} +var ConfigDefaults = Config{ + CollectorsEnabled: []string{ + subCollectorServerMetrics, + subCollectorScopeMetrics, + }, +} // A Collector is a Prometheus Collector perflib DHCP metrics. type Collector struct { @@ -65,6 +82,17 @@ type Collector struct { packetsReceivedTotal *prometheus.Desc releasesTotal *prometheus.Desc requestsTotal *prometheus.Desc + + scopeInfo *prometheus.Desc + scopeState *prometheus.Desc + scopeAddressesFreeTotal *prometheus.Desc + scopeAddressesFreeOnPartnerServerTotal *prometheus.Desc + scopeAddressesFreeOnThisServerTotal *prometheus.Desc + scopeAddressesInUseTotal *prometheus.Desc + scopeAddressesInUseOnPartnerServerTotal *prometheus.Desc + scopeAddressesInUseOnThisServerTotal *prometheus.Desc + scopePendingOffersTotal *prometheus.Desc + scopeReservedAddressTotal *prometheus.Desc } func New(config *Config) *Collector { @@ -72,6 +100,10 @@ func New(config *Config) *Collector { config = &ConfigDefaults } + if config.CollectorsEnabled == nil { + config.CollectorsEnabled = ConfigDefaults.CollectorsEnabled + } + c := &Collector{ config: *config, } @@ -79,8 +111,26 @@ func New(config *Config) *Collector { return c } -func NewWithFlags(_ *kingpin.Application) *Collector { - return &Collector{} +func NewWithFlags(app *kingpin.Application) *Collector { + c := &Collector{ + config: ConfigDefaults, + } + c.config.CollectorsEnabled = make([]string, 0) + + var collectorsEnabled string + + app.Flag( + "collector.dhcp.enabled", + "Comma-separated list of collectors to use. Defaults to all, if not specified.", + ).Default(strings.Join(ConfigDefaults.CollectorsEnabled, ",")).StringVar(&collectorsEnabled) + + app.Action(func(*kingpin.ParseContext) error { + c.config.CollectorsEnabled = strings.Split(collectorsEnabled, ",") + + return nil + }) + + return c } func (c *Collector) GetName() string { @@ -88,7 +138,9 @@ func (c *Collector) GetName() string { } func (c *Collector) Close() error { - c.perfDataCollector.Close() + if slices.Contains(c.config.CollectorsEnabled, subCollectorServerMetrics) { + c.perfDataCollector.Close() + } return nil } @@ -96,166 +148,258 @@ func (c *Collector) Close() error { func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { var err error - c.perfDataCollector, err = pdh.NewCollector[perfDataCounterValues](pdh.CounterTypeRaw, "DHCP Server", nil) - if err != nil { - return fmt.Errorf("failed to create DHCP Server collector: %w", err) + if slices.Contains(c.config.CollectorsEnabled, subCollectorServerMetrics) { + c.perfDataCollector, err = pdh.NewCollector[perfDataCounterValues](pdh.CounterTypeRaw, "DHCP Server", nil) + if err != nil { + return fmt.Errorf("failed to create DHCP Server collector: %w", err) + } + + c.packetsReceivedTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "packets_received_total"), + "Total number of packets received by the DHCP server (PacketsReceivedTotal)", + nil, + nil, + ) + c.duplicatesDroppedTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "duplicates_dropped_total"), + "Total number of duplicate packets received by the DHCP server (DuplicatesDroppedTotal)", + nil, + nil, + ) + c.packetsExpiredTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "packets_expired_total"), + "Total number of packets expired in the DHCP server message queue (PacketsExpiredTotal)", + nil, + nil, + ) + c.activeQueueLength = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "active_queue_length"), + "Number of packets in the processing queue of the DHCP server (ActiveQueueLength)", + nil, + nil, + ) + c.conflictCheckQueueLength = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "conflict_check_queue_length"), + "Number of packets in the DHCP server queue waiting on conflict detection (ping). (ConflictCheckQueueLength)", + nil, + nil, + ) + c.discoversTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "discovers_total"), + "Total DHCP Discovers received by the DHCP server (DiscoversTotal)", + nil, + nil, + ) + c.offersTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "offers_total"), + "Total DHCP Offers sent by the DHCP server (OffersTotal)", + nil, + nil, + ) + c.requestsTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "requests_total"), + "Total DHCP Requests received by the DHCP server (RequestsTotal)", + nil, + nil, + ) + c.informsTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "informs_total"), + "Total DHCP Informs received by the DHCP server (InformsTotal)", + nil, + nil, + ) + c.acksTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "acks_total"), + "Total DHCP Acks sent by the DHCP server (AcksTotal)", + nil, + nil, + ) + c.nACKsTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "nacks_total"), + "Total DHCP Nacks sent by the DHCP server (NacksTotal)", + nil, + nil, + ) + c.declinesTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "declines_total"), + "Total DHCP Declines received by the DHCP server (DeclinesTotal)", + nil, + nil, + ) + c.releasesTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "releases_total"), + "Total DHCP Releases received by the DHCP server (ReleasesTotal)", + nil, + nil, + ) + c.offerQueueLength = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "offer_queue_length"), + "Number of packets in the offer queue of the DHCP server (OfferQueueLength)", + nil, + nil, + ) + c.deniedDueToMatch = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "denied_due_to_match_total"), + "Total number of DHCP requests denied, based on matches from the Deny list (DeniedDueToMatch)", + nil, + nil, + ) + c.deniedDueToNonMatch = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "denied_due_to_nonmatch_total"), + "Total number of DHCP requests denied, based on non-matches from the Allow list (DeniedDueToNonMatch)", + nil, + nil, + ) + c.failoverBndUpdSentTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "failover_bndupd_sent_total"), + "Number of DHCP fail over Binding Update messages sent (FailoverBndupdSentTotal)", + nil, + nil, + ) + c.failoverBndUpdReceivedTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "failover_bndupd_received_total"), + "Number of DHCP fail over Binding Update messages received (FailoverBndupdReceivedTotal)", + nil, + nil, + ) + c.failoverBndAckSentTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "failover_bndack_sent_total"), + "Number of DHCP fail over Binding Ack messages sent (FailoverBndackSentTotal)", + nil, + nil, + ) + c.failoverBndAckReceivedTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "failover_bndack_received_total"), + "Number of DHCP fail over Binding Ack messages received (FailoverBndackReceivedTotal)", + nil, + nil, + ) + c.failoverBndUpdPendingOutboundQueue = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "failover_bndupd_pending_in_outbound_queue"), + "Number of pending outbound DHCP fail over Binding Update messages (FailoverBndupdPendingOutboundQueue)", + nil, + nil, + ) + c.failoverTransitionsCommunicationInterruptedState = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "failover_transitions_communicationinterrupted_state_total"), + "Total number of transitions into COMMUNICATION INTERRUPTED state (FailoverTransitionsCommunicationinterruptedState)", + nil, + nil, + ) + c.failoverTransitionsPartnerDownState = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "failover_transitions_partnerdown_state_total"), + "Total number of transitions into PARTNER DOWN state (FailoverTransitionsPartnerdownState)", + nil, + nil, + ) + c.failoverTransitionsRecoverState = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "failover_transitions_recover_total"), + "Total number of transitions into RECOVER state (FailoverTransitionsRecoverState)", + nil, + nil, + ) + c.failoverBndUpdDropped = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "failover_bndupd_dropped_total"), + "Total number of DHCP fail over Binding Updates dropped (FailoverBndupdDropped)", + nil, + nil, + ) } - c.packetsReceivedTotal = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "packets_received_total"), - "Total number of packets received by the DHCP server (PacketsReceivedTotal)", - nil, - nil, - ) - c.duplicatesDroppedTotal = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "duplicates_dropped_total"), - "Total number of duplicate packets received by the DHCP server (DuplicatesDroppedTotal)", - nil, - nil, - ) - c.packetsExpiredTotal = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "packets_expired_total"), - "Total number of packets expired in the DHCP server message queue (PacketsExpiredTotal)", - nil, - nil, - ) - c.activeQueueLength = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "active_queue_length"), - "Number of packets in the processing queue of the DHCP server (ActiveQueueLength)", - nil, - nil, - ) - c.conflictCheckQueueLength = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "conflict_check_queue_length"), - "Number of packets in the DHCP server queue waiting on conflict detection (ping). (ConflictCheckQueueLength)", - nil, - nil, - ) - c.discoversTotal = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "discovers_total"), - "Total DHCP Discovers received by the DHCP server (DiscoversTotal)", - nil, - nil, - ) - c.offersTotal = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "offers_total"), - "Total DHCP Offers sent by the DHCP server (OffersTotal)", - nil, - nil, - ) - c.requestsTotal = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "requests_total"), - "Total DHCP Requests received by the DHCP server (RequestsTotal)", - nil, - nil, - ) - c.informsTotal = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "informs_total"), - "Total DHCP Informs received by the DHCP server (InformsTotal)", - nil, - nil, - ) - c.acksTotal = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "acks_total"), - "Total DHCP Acks sent by the DHCP server (AcksTotal)", - nil, - nil, - ) - c.nACKsTotal = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "nacks_total"), - "Total DHCP Nacks sent by the DHCP server (NacksTotal)", - nil, - nil, - ) - c.declinesTotal = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "declines_total"), - "Total DHCP Declines received by the DHCP server (DeclinesTotal)", - nil, - nil, - ) - c.releasesTotal = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "releases_total"), - "Total DHCP Releases received by the DHCP server (ReleasesTotal)", - nil, - nil, - ) - c.offerQueueLength = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "offer_queue_length"), - "Number of packets in the offer queue of the DHCP server (OfferQueueLength)", - nil, - nil, - ) - c.deniedDueToMatch = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "denied_due_to_match_total"), - "Total number of DHCP requests denied, based on matches from the Deny list (DeniedDueToMatch)", - nil, - nil, - ) - c.deniedDueToNonMatch = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "denied_due_to_nonmatch_total"), - "Total number of DHCP requests denied, based on non-matches from the Allow list (DeniedDueToNonMatch)", - nil, - nil, - ) - c.failoverBndUpdSentTotal = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "failover_bndupd_sent_total"), - "Number of DHCP fail over Binding Update messages sent (FailoverBndupdSentTotal)", - nil, - nil, - ) - c.failoverBndUpdReceivedTotal = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "failover_bndupd_received_total"), - "Number of DHCP fail over Binding Update messages received (FailoverBndupdReceivedTotal)", - nil, - nil, - ) - c.failoverBndAckSentTotal = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "failover_bndack_sent_total"), - "Number of DHCP fail over Binding Ack messages sent (FailoverBndackSentTotal)", - nil, - nil, - ) - c.failoverBndAckReceivedTotal = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "failover_bndack_received_total"), - "Number of DHCP fail over Binding Ack messages received (FailoverBndackReceivedTotal)", - nil, - nil, - ) - c.failoverBndUpdPendingOutboundQueue = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "failover_bndupd_pending_in_outbound_queue"), - "Number of pending outbound DHCP fail over Binding Update messages (FailoverBndupdPendingOutboundQueue)", - nil, - nil, - ) - c.failoverTransitionsCommunicationInterruptedState = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "failover_transitions_communicationinterrupted_state_total"), - "Total number of transitions into COMMUNICATION INTERRUPTED state (FailoverTransitionsCommunicationinterruptedState)", - nil, - nil, - ) - c.failoverTransitionsPartnerDownState = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "failover_transitions_partnerdown_state_total"), - "Total number of transitions into PARTNER DOWN state (FailoverTransitionsPartnerdownState)", - nil, - nil, - ) - c.failoverTransitionsRecoverState = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "failover_transitions_recover_total"), - "Total number of transitions into RECOVER state (FailoverTransitionsRecoverState)", - nil, - nil, - ) - c.failoverBndUpdDropped = prometheus.NewDesc( - prometheus.BuildFQName(types.Namespace, Name, "failover_bndupd_dropped_total"), - "Total number of DHCP fail over Binding Updates dropped (FailoverBndupdDropped)", - nil, - nil, - ) + if slices.Contains(c.config.CollectorsEnabled, subCollectorScopeMetrics) { + c.scopeInfo = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "scope_info"), + "DHCP Scope information", + []string{"name", "superscope_name", "superscope_id", "scope"}, + nil, + ) + + c.scopeState = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "scope_state"), + "DHCP Scope state", + []string{"scope", "state"}, + nil, + ) + + c.scopeAddressesFreeTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "scope_addresses_free"), + "DHCP Scope free addresses", + []string{"scope"}, + nil, + ) + + c.scopeAddressesFreeOnPartnerServerTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "scope_addresses_free_on_partner_server"), + "DHCP Scope free addresses on partner server", + []string{"scope"}, + nil, + ) + + c.scopeAddressesFreeOnThisServerTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "scope_addresses_free_on_this_server"), + "DHCP Scope free addresses on this server", + []string{"scope"}, + nil, + ) + + c.scopeAddressesInUseTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "scope_addresses_in_use"), + "DHCP Scope addresses in use", + []string{"scope"}, + nil, + ) + + c.scopeAddressesInUseOnPartnerServerTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "scope_addresses_in_use_on_partner_server"), + "DHCP Scope addresses in use on partner server", + []string{"scope"}, + nil, + ) + + c.scopeAddressesInUseOnThisServerTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "scope_addresses_in_use_on_this_server"), + "DHCP Scope addresses in use on this server", + []string{"scope"}, + nil, + ) + + c.scopePendingOffersTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "scope_pending_offers"), + "DHCP Scope pending offers", + []string{"scope"}, + nil, + ) + + c.scopeReservedAddressTotal = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "scope_reserved_address"), + "DHCP Scope reserved addresses", + []string{"scope"}, + nil, + ) + } return nil } func (c *Collector) Collect(ch chan<- prometheus.Metric) error { + var errs []error + + if slices.Contains(c.config.CollectorsEnabled, subCollectorServerMetrics) { + if err := c.collectServerMetrics(ch); err != nil { + errs = append(errs, err) + } + } + + if slices.Contains(c.config.CollectorsEnabled, subCollectorScopeMetrics) { + if err := c.collectScopeMetrics(ch); err != nil { + errs = append(errs, err) + } + } + + return errors.Join(errs...) +} + +func (c *Collector) collectServerMetrics(ch chan<- prometheus.Metric) error { err := c.perfDataCollector.Collect(&c.perfDataObject) if err != nil { return fmt.Errorf("failed to collect DHCP Server metrics: %w", err) @@ -413,3 +557,113 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error { return nil } + +func (c *Collector) collectScopeMetrics(ch chan<- prometheus.Metric) error { + dhcpScopes, err := dhcpsapi.GetDHCPV4ScopeStatistics() + if err != nil { + return fmt.Errorf("failed to get DHCP scopes: %w", err) + } + + for _, scope := range dhcpScopes { + scopeID := scope.ScopeIPAddress.String() + + ch <- prometheus.MustNewConstMetric( + c.scopeInfo, + prometheus.GaugeValue, + 1, + scope.Name, + scope.SuperScopeName, + strconv.Itoa(int(scope.SuperScopeNumber)), + scopeID, + ) + + for state, name := range dhcpsapi.DHCP_SUBNET_STATE_NAMES { + metric := 0.0 + if state == scope.State { + metric = 1.0 + } + + ch <- prometheus.MustNewConstMetric( + c.scopeState, + prometheus.GaugeValue, + metric, + scopeID, + name, + ) + } + + if scope.AddressesFree != -1 { + ch <- prometheus.MustNewConstMetric( + c.scopeAddressesFreeTotal, + prometheus.GaugeValue, + scope.AddressesFree, + scopeID, + ) + } + + if scope.AddressesFreeOnPartnerServer != -1 { + ch <- prometheus.MustNewConstMetric( + c.scopeAddressesFreeOnPartnerServerTotal, + prometheus.GaugeValue, + scope.AddressesFreeOnPartnerServer, + scopeID, + ) + } + + if scope.AddressesFreeOnThisServer != -1 { + ch <- prometheus.MustNewConstMetric( + c.scopeAddressesFreeOnThisServerTotal, + prometheus.GaugeValue, + scope.AddressesFreeOnThisServer, + scopeID, + ) + } + + if scope.AddressesInUse != -1 { + ch <- prometheus.MustNewConstMetric( + c.scopeAddressesInUseTotal, + prometheus.GaugeValue, + scope.AddressesInUse, + scopeID, + ) + } + + if scope.AddressesInUseOnPartnerServer != -1 { + ch <- prometheus.MustNewConstMetric( + c.scopeAddressesInUseOnPartnerServerTotal, + prometheus.GaugeValue, + scope.AddressesInUseOnPartnerServer, + scopeID, + ) + } + + if scope.AddressesInUseOnThisServer != -1 { + ch <- prometheus.MustNewConstMetric( + c.scopeAddressesInUseOnThisServerTotal, + prometheus.GaugeValue, + scope.AddressesInUseOnThisServer, + scopeID, + ) + } + + if scope.PendingOffers != -1 { + ch <- prometheus.MustNewConstMetric( + c.scopePendingOffersTotal, + prometheus.GaugeValue, + scope.PendingOffers, + scopeID, + ) + } + + if scope.ReservedAddress != -1 { + ch <- prometheus.MustNewConstMetric( + c.scopeReservedAddressTotal, + prometheus.GaugeValue, + scope.ReservedAddress, + scopeID, + ) + } + } + + return nil +} diff --git a/internal/headers/dhcpsapi/dhcpsapi.go b/internal/headers/dhcpsapi/dhcpsapi.go new file mode 100644 index 00000000..645f152e --- /dev/null +++ b/internal/headers/dhcpsapi/dhcpsapi.go @@ -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)) +} diff --git a/internal/headers/dhcpsapi/dhcpsapi_test.go b/internal/headers/dhcpsapi/dhcpsapi_test.go new file mode 100644 index 00000000..e89454ad --- /dev/null +++ b/internal/headers/dhcpsapi/dhcpsapi_test.go @@ -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) +} diff --git a/internal/headers/dhcpsapi/types.go b/internal/headers/dhcpsapi/types.go new file mode 100644 index 00000000..45004b1a --- /dev/null +++ b/internal/headers/dhcpsapi/types.go @@ -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 +} diff --git a/internal/headers/win32api/types.go b/internal/headers/win32api/types.go new file mode 100644 index 00000000..9d5129db --- /dev/null +++ b/internal/headers/win32api/types.go @@ -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) +}