master
go 107 lines 3.19 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package panos
4
5 import (
6 "context"
7 "errors"
8 "fmt"
9 "strings"
10 )
11
12 type ipsecResult struct {
13 NTun string `xml:"ntun"`
14 Entries *ipsecEntries `xml:"entries"`
15 }
16
17 type ipsecEntries struct {
18 Entries []ipsecTunnel `xml:"entry"`
19 }
20
21 type ipsecTunnel struct {
22 Name string `xml:"name"`
23 Gateway string `xml:"gateway"`
24 Remote string `xml:"remote"`
25 Protocol string `xml:"proto"`
26 Encryption string `xml:"enc"`
27 Remain string `xml:"remain"`
28 TID string `xml:"tid"`
29 ISPI string `xml:"i_spi"`
30 OSPI string `xml:"o_spi"`
31 }
32
33 func (c *Collector) collectIPSecMetrics(ctx context.Context) (bool, error) {
34 body, err := c.apiClient.op(ctx, ipsecSACommand)
35 if err != nil {
36 return false, fmt.Errorf("ipsec metricset: %s API call: %w", panosCommandName(ipsecSACommand), err)
37 }
38
39 payload, err := parseIPSecTunnels(body)
40 if err != nil {
41 return false, fmt.Errorf("ipsec metricset: %s response: %w", panosCommandName(ipsecSACommand), err)
42 }
43 if !payload.found {
44 return false, fmt.Errorf("ipsec metricset: %s response: %w", panosCommandName(ipsecSACommand), missingPANOSResultError{expected: "<ntun> or <entries>"})
45 }
46
47 c.metrics.ipsec.tunnelsActive.Observe(float64(payload.activeCount))
48
49 var errs []error
50 if payload.entriesFound && payload.activeCount != int64(len(payload.tunnels)) {
51 errs = append(errs, fmt.Errorf("IPsec active tunnel count mismatch: ntun=%d entries=%d; per-tunnel lifetime metrics may be incomplete", payload.activeCount, len(payload.tunnels)))
52 }
53 for _, tunnel := range payload.tunnels {
54 key := ipsecTunnelKey(tunnel)
55 value, err := parseRequiredPANOSIntField("IPsec tunnel "+firstNonEmpty(tunnel.Name, key)+" remain", tunnel.Remain)
56 if err != nil {
57 errs = append(errs, err)
58 continue
59 }
60 c.metrics.ipsec.saLifetime.WithLabelValues(ipsecTunnelLabelValues(tunnel)...).Observe(float64(value))
61 }
62 return true, errors.Join(errs...)
63 }
64
65 type ipsecTunnelPayload struct {
66 tunnels []ipsecTunnel
67 activeCount int64
68 found bool
69 entriesFound bool
70 }
71
72 func parseIPSecTunnels(body []byte) (ipsecTunnelPayload, error) {
73 var result ipsecResult
74 if err := decodePANOSResult(body, "PAN-OS IPsec response", &result); err != nil {
75 return ipsecTunnelPayload{}, err
76 }
77 if result.Entries == nil && strings.TrimSpace(result.NTun) == "" {
78 return ipsecTunnelPayload{}, nil
79 }
80
81 payload := ipsecTunnelPayload{
82 found: true,
83 entriesFound: result.Entries != nil,
84 }
85 var activeCount int64
86 if strings.TrimSpace(result.NTun) != "" {
87 count, err := parseRequiredPANOSIntField("IPsec active tunnel count", result.NTun)
88 if err != nil {
89 return ipsecTunnelPayload{found: true, entriesFound: result.Entries != nil}, err
90 }
91 activeCount = count
92 }
93 if result.Entries == nil {
94 payload.activeCount = activeCount
95 return payload, nil
96 }
97 if strings.TrimSpace(result.NTun) == "" {
98 activeCount = int64(len(result.Entries.Entries))
99 }
100 payload.tunnels = result.Entries.Entries
101 payload.activeCount = activeCount
102 return payload, nil
103 }
104
105 func ipsecTunnelKey(tunnel ipsecTunnel) string {
106 return cleanID(firstNonEmpty(tunnel.Name, "unknown") + "_" + tunnel.Gateway + "_" + tunnel.Remote + "_" + firstNonEmpty(tunnel.TID, tunnel.ISPI, tunnel.OSPI))
107 }