github.com/netdata/go.d.plugin@v0.58.1/modules/dnsmasq_dhcp/parse_configuration.go (about) 1 // SPDX-License-Identifier: GPL-3.0-or-later 2 3 package dnsmasq_dhcp 4 5 import ( 6 "bufio" 7 "fmt" 8 "net" 9 "os" 10 "path/filepath" 11 "regexp" 12 "sort" 13 "strings" 14 15 "github.com/netdata/go.d.plugin/pkg/iprange" 16 ) 17 18 func (d *DnsmasqDHCP) parseDnsmasqDHCPConfiguration() ([]iprange.Range, []net.IP) { 19 configs := findConfigurationFiles(d.ConfPath, d.ConfDir) 20 21 dhcpRanges := d.getDHCPRanges(configs) 22 dhcpHosts := d.getDHCPHosts(configs) 23 24 return dhcpRanges, dhcpHosts 25 } 26 27 func (d *DnsmasqDHCP) getDHCPRanges(configs []*configFile) []iprange.Range { 28 var dhcpRanges []iprange.Range 29 var parsed string 30 seen := make(map[string]bool) 31 32 for _, conf := range configs { 33 d.Debugf("looking in '%s'", conf.path) 34 35 for _, value := range conf.get("dhcp-range") { 36 d.Debugf("found dhcp-range '%s'", value) 37 if parsed = parseDHCPRangeValue(value); parsed == "" || seen[parsed] { 38 continue 39 } 40 seen[parsed] = true 41 42 r, err := iprange.ParseRange(parsed) 43 if r == nil || err != nil { 44 d.Warningf("error on parsing dhcp-range '%s', skipping it", parsed) 45 continue 46 } 47 48 d.Debugf("adding dhcp-range '%s'", parsed) 49 dhcpRanges = append(dhcpRanges, r) 50 } 51 } 52 53 // order: ipv4, ipv6 54 sort.Slice(dhcpRanges, func(i, j int) bool { return dhcpRanges[i].Family() < dhcpRanges[j].Family() }) 55 56 return dhcpRanges 57 } 58 59 func (d *DnsmasqDHCP) getDHCPHosts(configs []*configFile) []net.IP { 60 var dhcpHosts []net.IP 61 seen := make(map[string]bool) 62 var parsed string 63 64 for _, conf := range configs { 65 d.Debugf("looking in '%s'", conf.path) 66 67 for _, value := range conf.get("dhcp-host") { 68 d.Debugf("found dhcp-host '%s'", value) 69 if parsed = parseDHCPHostValue(value); parsed == "" || seen[parsed] { 70 continue 71 } 72 seen[parsed] = true 73 74 v := net.ParseIP(parsed) 75 if v == nil { 76 d.Warningf("error on parsing dhcp-host '%s', skipping it", parsed) 77 continue 78 } 79 80 d.Debugf("adding dhcp-host '%s'", parsed) 81 dhcpHosts = append(dhcpHosts, v) 82 } 83 } 84 return dhcpHosts 85 } 86 87 /* 88 Examples: 89 - 192.168.0.50,192.168.0.150,12h 90 - 192.168.0.50,192.168.0.150,255.255.255.0,12h 91 - set:red,1.1.1.50,1.1.2.150, 255.255.252.0 92 - 192.168.0.0,static 93 - 1234::2,1234::500, 64, 12h 94 - 1234::2,1234::500 95 - 1234::2,1234::500, slaac 96 - 1234::,ra-only 97 - 1234::,ra-names 98 - 1234::,ra-stateless 99 */ 100 var reDHCPRange = regexp.MustCompile(`([0-9a-f.:]+),([0-9a-f.:]+)`) 101 102 func parseDHCPRangeValue(s string) (r string) { 103 if strings.Contains(s, "ra-stateless") { 104 return 105 } 106 107 match := reDHCPRange.FindStringSubmatch(s) 108 if match == nil { 109 return 110 } 111 112 start, end := net.ParseIP(match[1]), net.ParseIP(match[2]) 113 if start == nil || end == nil { 114 return 115 } 116 117 return fmt.Sprintf("%s-%s", start, end) 118 } 119 120 /* 121 Examples: 122 - 11:22:33:44:55:66,192.168.0.60 123 - 11:22:33:44:55:66,fred,192.168.0.60,45m 124 - 11:22:33:44:55:66,12:34:56:78:90:12,192.168.0.60 125 - bert,192.168.0.70,infinite 126 - id:01:02:02:04,192.168.0.60 127 - id:ff:00:00:00:00:00:02:00:00:02:c9:00:f4:52:14:03:00:28:05:81,192.168.0.61 128 - id:marjorie,192.168.0.60 129 - id:00:01:00:01:16:d2:83:fc:92:d4:19:e2:d8:b2, fred, [1234::5] 130 */ 131 var ( 132 reDHCPHostV4 = regexp.MustCompile(`(?:[0-9]{1,3}\.){3}[0-9]{1,3}`) 133 reDHCPHostV6 = regexp.MustCompile(`\[([0-9a-f.:]+)]`) 134 ) 135 136 func parseDHCPHostValue(s string) (r string) { 137 if strings.Contains(s, "[") { 138 return strings.Trim(reDHCPHostV6.FindString(s), "[]") 139 } 140 return reDHCPHostV4.FindString(s) 141 } 142 143 type ( 144 extension string 145 146 extensions []extension 147 148 configDir struct { 149 path string 150 include extensions 151 exclude extensions 152 } 153 ) 154 155 func (e extension) match(filename string) bool { 156 return strings.HasSuffix(filename, string(e)) 157 } 158 159 func (es extensions) match(filename string) bool { 160 for _, e := range es { 161 if e.match(filename) { 162 return true 163 } 164 } 165 return false 166 } 167 168 func parseConfDir(confDirStr string) configDir { 169 // # Include all the files in a directory except those ending in .bak 170 //#conf-dir=/etc/dnsmasq.d,.bak 171 //# Include all files in a directory which end in .conf 172 //#conf-dir=/etc/dnsmasq.d/,*.conf 173 174 parts := strings.Split(confDirStr, ",") 175 cd := configDir{path: parts[0]} 176 177 for _, arg := range parts[1:] { 178 arg = strings.TrimSpace(arg) 179 if strings.HasPrefix(arg, "*") { 180 cd.include = append(cd.include, extension(arg[1:])) 181 } else { 182 cd.exclude = append(cd.exclude, extension(arg)) 183 } 184 } 185 return cd 186 } 187 188 func (cd configDir) isValidFilename(filename string) bool { 189 switch { 190 default: 191 return true 192 case strings.HasPrefix(filename, "."): 193 case strings.HasPrefix(filename, "~"): 194 case strings.HasPrefix(filename, "#") && strings.HasSuffix(filename, "#"): 195 } 196 return false 197 } 198 199 func (cd configDir) match(filename string) bool { 200 switch { 201 default: 202 return true 203 case !cd.isValidFilename(filename): 204 case len(cd.include) > 0 && !cd.include.match(filename): 205 case cd.exclude.match(filename): 206 } 207 return false 208 } 209 210 func (cd configDir) findConfigs() ([]string, error) { 211 fis, err := os.ReadDir(cd.path) 212 if err != nil { 213 return nil, err 214 } 215 216 var files []string 217 for _, fi := range fis { 218 info, err := fi.Info() 219 if err != nil { 220 return nil, err 221 } 222 if !info.Mode().IsRegular() || !cd.match(fi.Name()) { 223 continue 224 } 225 files = append(files, filepath.Join(cd.path, fi.Name())) 226 } 227 return files, nil 228 } 229 230 func openFile(filepath string) (f *os.File, err error) { 231 defer func() { 232 if err != nil && f != nil { 233 _ = f.Close() 234 } 235 }() 236 237 f, err = os.Open(filepath) 238 if err != nil { 239 return nil, err 240 } 241 242 fi, err := f.Stat() 243 if err != nil { 244 return nil, err 245 } 246 247 if !fi.Mode().IsRegular() { 248 return nil, fmt.Errorf("'%s' is not a regular file", filepath) 249 } 250 return f, nil 251 } 252 253 type ( 254 configOption struct { 255 key, value string 256 } 257 258 configFile struct { 259 path string 260 options []configOption 261 } 262 ) 263 264 func (cf *configFile) get(name string) []string { 265 var options []string 266 for _, o := range cf.options { 267 if o.key != name { 268 continue 269 } 270 options = append(options, o.value) 271 } 272 return options 273 } 274 275 func parseConfFile(filename string) (*configFile, error) { 276 f, err := openFile(filename) 277 if err != nil { 278 return nil, err 279 } 280 defer func() { _ = f.Close() }() 281 282 cf := configFile{path: filename} 283 s := bufio.NewScanner(f) 284 for s.Scan() { 285 line := strings.TrimSpace(s.Text()) 286 if strings.HasPrefix(line, "#") { 287 continue 288 } 289 290 if !strings.Contains(line, "=") { 291 continue 292 } 293 294 line = strings.ReplaceAll(line, " ", "") 295 parts := strings.Split(line, "=") 296 if len(parts) != 2 { 297 continue 298 } 299 300 cf.options = append(cf.options, configOption{key: parts[0], value: parts[1]}) 301 } 302 return &cf, nil 303 } 304 305 type ConfigFinder struct { 306 entryConfig string 307 entryDir string 308 visitedConfigs map[string]bool 309 visitedDirs map[string]bool 310 } 311 312 func (f *ConfigFinder) find() []*configFile { 313 f.visitedConfigs = make(map[string]bool) 314 f.visitedDirs = make(map[string]bool) 315 316 configs := f.recursiveFind(f.entryConfig) 317 318 for _, file := range f.entryDirConfigs() { 319 configs = append(configs, f.recursiveFind(file)...) 320 } 321 return configs 322 } 323 324 func (f *ConfigFinder) entryDirConfigs() []string { 325 if f.entryDir == "" { 326 return nil 327 } 328 files, err := parseConfDir(f.entryDir).findConfigs() 329 if err != nil { 330 return nil 331 } 332 return files 333 } 334 335 func (f *ConfigFinder) recursiveFind(filename string) (configs []*configFile) { 336 if f.visitedConfigs[filename] { 337 return nil 338 } 339 340 config, err := parseConfFile(filename) 341 if err != nil { 342 return nil 343 } 344 345 files, dirs := config.get("conf-file"), config.get("conf-dir") 346 347 f.visitedConfigs[filename] = true 348 configs = append(configs, config) 349 350 for _, file := range files { 351 configs = append(configs, f.recursiveFind(file)...) 352 } 353 354 for _, dir := range dirs { 355 if dir == "" { 356 continue 357 } 358 359 d := parseConfDir(dir) 360 361 if f.visitedDirs[d.path] { 362 continue 363 } 364 f.visitedDirs[d.path] = true 365 366 files, err = d.findConfigs() 367 if err != nil { 368 continue 369 } 370 371 for _, file := range files { 372 configs = append(configs, f.recursiveFind(file)...) 373 } 374 } 375 return configs 376 } 377 378 func findConfigurationFiles(entryConfig string, entryDir string) []*configFile { 379 cf := ConfigFinder{ 380 entryConfig: entryConfig, 381 entryDir: entryDir, 382 } 383 return cf.find() 384 }