master
go 168 lines 3.4 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 //go:build linux
4
5 package dnsmasq_dhcp
6
7 import (
8 "bufio"
9 "io"
10 "math"
11 "math/big"
12 "net/netip"
13 "os"
14 "strings"
15 "time"
16
17 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/iprange"
18 )
19
20 func (c *Collector) collect() (map[string]int64, error) {
21 now := time.Now()
22 var updated bool
23
24 if now.Sub(c.parseConfigTime) > c.parseConfigEvery {
25 c.parseConfigTime = now
26
27 dhcpRanges, dhcpHosts := c.parseDnsmasqDHCPConfiguration()
28 c.dhcpRanges, c.dhcpHosts = dhcpRanges, dhcpHosts
29 updated = c.updateCharts()
30
31 c.collectV4V6Stats()
32 }
33
34 f, err := os.Open(c.LeasesPath)
35 if err != nil {
36 return nil, err
37 }
38 defer func() { _ = f.Close() }()
39
40 if !updated {
41 fi, err := f.Stat()
42 if err != nil {
43 return nil, err
44 }
45
46 if c.leasesModTime.Equal(fi.ModTime()) {
47 c.Debug("lease database file modification time has not changed, old data is returned")
48 return c.mx, nil
49 }
50
51 c.Debug("leases db file modification time has changed, reading it")
52 c.leasesModTime = fi.ModTime()
53 }
54
55 leases := findLeases(f)
56 c.collectRangesStats(leases)
57
58 return c.mx, nil
59 }
60
61 func (c *Collector) collectV4V6Stats() {
62 c.mx["ipv4_dhcp_ranges"], c.mx["ipv6_dhcp_ranges"] = 0, 0
63 for _, r := range c.dhcpRanges {
64 if r.Family() == iprange.V6Family {
65 c.mx["ipv6_dhcp_ranges"]++
66 } else {
67 c.mx["ipv4_dhcp_ranges"]++
68 }
69 }
70
71 c.mx["ipv4_dhcp_hosts"], c.mx["ipv6_dhcp_hosts"] = 0, 0
72 for _, ip := range c.dhcpHosts {
73 if ip.Is6() {
74 c.mx["ipv6_dhcp_hosts"]++
75 } else {
76 c.mx["ipv4_dhcp_hosts"]++
77 }
78 }
79 }
80
81 func (c *Collector) collectRangesStats(leases []netip.Addr) {
82 for _, r := range c.dhcpRanges {
83 c.mx["dhcp_range_"+r.String()+"_allocated_leases"] = 0
84 c.mx["dhcp_range_"+r.String()+"_utilization"] = 0
85 }
86
87 for _, addr := range leases {
88 for _, r := range c.dhcpRanges {
89 if r.Contains(addr) {
90 c.mx["dhcp_range_"+r.String()+"_allocated_leases"]++
91 break
92 }
93 }
94 }
95
96 for _, addr := range c.dhcpHosts {
97 for _, r := range c.dhcpRanges {
98 if r.Contains(addr) {
99 c.mx["dhcp_range_"+r.String()+"_allocated_leases"]++
100 break
101 }
102 }
103 }
104
105 for _, r := range c.dhcpRanges {
106 name := "dhcp_range_" + r.String() + "_allocated_leases"
107 numOfIps, ok := c.mx[name]
108 if !ok {
109 c.mx[name] = 0
110 }
111 c.mx["dhcp_range_"+r.String()+"_utilization"] = int64(math.Round(calcPercent(numOfIps, r.Size())))
112 }
113 }
114
115 func (c *Collector) updateCharts() bool {
116 var updated bool
117 seen := make(map[string]bool)
118 for _, r := range c.dhcpRanges {
119 seen[r.String()] = true
120 if !c.cacheDHCPRanges[r.String()] {
121 c.cacheDHCPRanges[r.String()] = true
122 c.addDHCPRangeCharts(r.String())
123 updated = true
124 }
125 }
126
127 for v := range c.cacheDHCPRanges {
128 if !seen[v] {
129 delete(c.cacheDHCPRanges, v)
130 c.removeDHCPRangeCharts(v)
131 updated = true
132 }
133 }
134 return updated
135 }
136
137 func findLeases(r io.Reader) []netip.Addr {
138 /*
139 1560300536 08:00:27:61:3c:ee 2.2.2.3 debian8 *
140 duid 00:01:00:01:24:90:cf:5b:08:00:27:61:2e:2c
141 1560300414 660684014 1234::20b * 00:01:00:01:24:90:cf:a3:08:00:27:61:3c:ee
142 */
143 var addrs []netip.Addr
144 s := bufio.NewScanner(r)
145
146 for s.Scan() {
147 parts := strings.Fields(s.Text())
148 if len(parts) != 5 {
149 continue
150 }
151
152 addr, err := netip.ParseAddr(parts[2])
153 if err != nil {
154 continue
155 }
156 addrs = append(addrs, addr)
157 }
158
159 return addrs
160 }
161
162 func calcPercent(ips int64, hosts *big.Int) float64 {
163 h := hosts.Int64()
164 if ips == 0 || h == 0 || !hosts.IsInt64() {
165 return 0
166 }
167 return float64(ips) * 100 / float64(h)
168 }