1
|
<?php
|
2
|
/*
|
3
|
* parser_dhcpv6_leases.inc
|
4
|
*
|
5
|
* Copyright (c) 2017-2018 Anders Lind (anders.lind@gmail.com)
|
6
|
* All rights reserved.
|
7
|
*
|
8
|
* part of pfSense (https://www.pfsense.org)
|
9
|
* Copyright (c) 2004-2016 Rubicon Communications, LLC (Netgate)
|
10
|
* Copyright (c) 2011 Seth Mos
|
11
|
* All rights reserved.
|
12
|
*
|
13
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
14
|
* you may not use this file except in compliance with the License.
|
15
|
* You may obtain a copy of the License at
|
16
|
*
|
17
|
* http://www.apache.org/licenses/LICENSE-2.0
|
18
|
*
|
19
|
* Unless required by applicable law or agreed to in writing, software
|
20
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
21
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
22
|
* See the License for the specific language governing permissions and
|
23
|
* limitations under the License.
|
24
|
*/
|
25
|
|
26
|
require_once 'tools_for_debugging_n_testing.inc';
|
27
|
require_once 'parser_ipv6.inc';
|
28
|
|
29
|
/*
|
30
|
* Moved over from status_dhcpv6_leases.php
|
31
|
*/
|
32
|
function parse_duid($duid_string) {
|
33
|
$parsed_duid = array();
|
34
|
for ($i = 0; $i < strlen($duid_string); $i++) {
|
35
|
$s = substr($duid_string, $i, 1);
|
36
|
if ($s == '\\') {
|
37
|
$n = substr($duid_string, $i+1, 1);
|
38
|
if (($n == '\\') || ($n == '"')) {
|
39
|
$parsed_duid[] = sprintf("%02x", ord($n));
|
40
|
$i += 1;
|
41
|
} else {
|
42
|
$n = substr($duid_string, $i+1, 3);
|
43
|
if (preg_match('/[0-3][0-7]{2}/', $n)) {
|
44
|
$parsed_duid[] = sprintf("%02x", octdec($n));
|
45
|
$i += 3;
|
46
|
}
|
47
|
}
|
48
|
} else {
|
49
|
$parsed_duid[] = sprintf("%02x", ord($s));
|
50
|
}
|
51
|
}
|
52
|
$iaid = array_slice($parsed_duid, 0, 4);
|
53
|
$duid = array_slice($parsed_duid, 4);
|
54
|
return array($iaid, $duid);
|
55
|
}
|
56
|
|
57
|
/*
|
58
|
* Moved over from status_dhcpv6_leases.php
|
59
|
*/
|
60
|
function get_ndpdata() {
|
61
|
exec("/usr/sbin/ndp -an", $rawdata);
|
62
|
$ndpdata = array();
|
63
|
foreach ($rawdata as $line) {
|
64
|
$elements = preg_split('/\s+/ ', $line);
|
65
|
if ($elements[1] != "(incomplete)") {
|
66
|
$ndpent = array();
|
67
|
$ip = trim(str_replace(array('(', ')'), '', $elements[0]));
|
68
|
$ndpent['mac'] = trim($elements[1]);
|
69
|
$ndpent['interface'] = trim($elements[2]);
|
70
|
$ndpdata[$ip] = $ndpent;
|
71
|
}
|
72
|
}
|
73
|
|
74
|
return $ndpdata;
|
75
|
}
|
76
|
|
77
|
const IA_NA = 1;
|
78
|
const IA_PD = 2;
|
79
|
const IA_TA = 3;
|
80
|
|
81
|
/*
|
82
|
* returns false if it was not possible to parse the leases.
|
83
|
*/
|
84
|
function gui_parse_leases(&$pools, &$leases, &$prefixes, &$mappings, $raw_leases, $ndpdata, $lang_pack) {
|
85
|
$amount_of_matches = preg_match_all('/'.base_regex.'/i', (string) $raw_leases, $lease_array, PREG_SET_ORDER);
|
86
|
|
87
|
if ($amount_of_matches == false) {
|
88
|
return false;
|
89
|
}
|
90
|
|
91
|
$amount_of_leases = 0;
|
92
|
foreach ($lease_array as $val) {
|
93
|
general_debug('---LEASE '.++$amount_of_leases.' START---');
|
94
|
|
95
|
$iaid_duid = parse_duid($val['IAID_DUID']);
|
96
|
|
97
|
general_debug($val, 'IAID_DUID');
|
98
|
|
99
|
$entry = [ 'iaid' => hexdec(implode("", array_reverse($iaid_duid[0]))),
|
100
|
'duid' => implode(":", $iaid_duid[1]) ];
|
101
|
|
102
|
general_debug($entry, 'iaid');
|
103
|
general_debug($entry, 'duid');
|
104
|
|
105
|
if (preg_match('/^ia-na$/i', $val['IA_ASSOC'])) {
|
106
|
gui_parse_single_lease(IA_NA, $val['NEXT_LEVEL'], $lang_pack, $ndpdata, $mappings, $entry);
|
107
|
$leases[] = $entry;
|
108
|
} elseif (preg_match('/^ia-pd$/i', $val['IA_ASSOC'])) {
|
109
|
gui_parse_single_lease(IA_PD, $val['NEXT_LEVEL'], $lang_pack, $ndpdata, $mappings, $entry);
|
110
|
$prefixes[] = $entry;
|
111
|
} elseif (preg_match('/^ia-ta$/i', $val['IA_ASSOC'])) {
|
112
|
// Not used in the pfSense UI at the moment.
|
113
|
general_debug('Notice: ia-ta is unhandled for now.');
|
114
|
}
|
115
|
|
116
|
general_debug("---LEASE $amount_of_leases END---\n");
|
117
|
}
|
118
|
|
119
|
// Handle failover - NOTE Experimental;
|
120
|
// has not been tested on a live configured system.
|
121
|
$amount_of_matches = preg_match_all('/'.failover_base_regex.'/i', $raw_leases, $lease_array, PREG_SET_ORDER);
|
122
|
$amount_of_leases = 0;
|
123
|
foreach ($lease_array as $val) {
|
124
|
general_debug('---FAILOVER '.++$amount_of_leases.' START---');
|
125
|
$entry = ['name' => $val['FPN']];
|
126
|
general_debug($val, 'FPN');
|
127
|
gui_parse_single_failover($val['NEXT_LEVEL'], $entry);
|
128
|
$pools[] = $entry;
|
129
|
general_debug("---FAILOVER $amount_of_leases END---\n");
|
130
|
}
|
131
|
|
132
|
general_debug('Leases', $leases);
|
133
|
general_debug('Prefixes', $prefixes);
|
134
|
general_debug('Mappings', $mappings);
|
135
|
general_debug('Pools', $pools);
|
136
|
}
|
137
|
|
138
|
function gui_parse_single_lease($type, $content, $lang_pack, $ndpdata, &$mappings, &$entry) {
|
139
|
switch ($type) {
|
140
|
case IA_NA:
|
141
|
preg_match_all('/'.na_regex.'/i', $content, $matches, PREG_SET_ORDER);
|
142
|
break;
|
143
|
case IA_PD:
|
144
|
preg_match_all('/'.pd_regex.'/i', $content, $matches, PREG_SET_ORDER);
|
145
|
break;
|
146
|
// case IA_TA: // Not implemented in pfSense.
|
147
|
default:
|
148
|
general_debug('Error: Unhandled lease type.');
|
149
|
return -1;
|
150
|
}
|
151
|
|
152
|
$amount_of_matches = 0;
|
153
|
|
154
|
foreach ($matches as $val) {
|
155
|
general_debug("\n --Match number " . ++$amount_of_matches . ' start.');
|
156
|
general_debug("Matched: " . $val[0]);
|
157
|
|
158
|
check_value_set_entry($val, 'CLTT', $entry, 'start', true, function () use (&$val) {
|
159
|
general_debug($val, 'WEEKDAY', 'DATE', 'YEAR', 'MONTH', 'DAY', 'TIME');
|
160
|
return $val['DATE'] . ' ' . $val['TIME'];});
|
161
|
|
162
|
if (setnlen($val, 'NEXT_LEVEL')) {
|
163
|
general_debug($val, 'NEXT_LEVEL');
|
164
|
|
165
|
gui_handle_ia($val['NEXT_LEVEL'], $entry, $lang_pack);
|
166
|
}
|
167
|
|
168
|
// For now it does not matter that this goes last.
|
169
|
if ($type === IA_NA) {
|
170
|
gui_parse_single_na_lease($val, $lang_pack, $ndpdata, $mappings, $entry);
|
171
|
} elseif ($type === IA_PD) {
|
172
|
gui_parse_single_pd_lease($val, $lang_pack, $entry);
|
173
|
}
|
174
|
|
175
|
general_debug(' --Match number ' . $amount_of_matches . ' end.');
|
176
|
}
|
177
|
}
|
178
|
|
179
|
function gui_parse_single_na_lease($val, $lang_pack, $ndpdata, &$mappings, &$entry) {
|
180
|
if (setnlen($val, 'ADDRESS')) {
|
181
|
general_debug($val, 'ADDRESS');
|
182
|
|
183
|
$space_result = parse_all_ipv6_to_array($val['ADDRESS']);
|
184
|
|
185
|
if (count($space_result) == 1) {
|
186
|
if (setnlen($space_result[0], 'MATCH')) {
|
187
|
general_debug("We have a valid ipv6 address");
|
188
|
if (check_n_set($entry, 'ip', $space_result[0]['MATCH'], true)) {
|
189
|
// In general we trust that $lang_pack exists with values so no checking (either
|
190
|
// it works for a certain language/translation or it does not (for all entries.)
|
191
|
check_n_set($entry, 'type', $lang_pack['dynamic'], true);
|
192
|
|
193
|
in_array($entry['ip'], array_keys($ndpdata))
|
194
|
? check_n_set($entry, 'online', $lang_pack['online'], true)
|
195
|
: check_n_set($entry, 'online', $lang_pack['offline'], true);
|
196
|
|
197
|
// mappings - using DUID and IAID
|
198
|
if (setnlen($entry, 'duid') && setnlen($entry, 'iaid')) {
|
199
|
// We assume $mappings array exist - else nothing works.
|
200
|
general_debug('Setting the mappings array for DUID ' . $entry['duid'] .
|
201
|
' with IAID ' . $entry['iaid'] . ' as index with value ' .
|
202
|
$entry['ip']);
|
203
|
check_n_set($mappings[$entry['duid']], $entry['iaid'], $entry['ip'], true);
|
204
|
} else {
|
205
|
general_debug('WARNING: DUID or IAID is missing in entry. Likely either has zero length content.');
|
206
|
}
|
207
|
} // else { } - we use overwrite set to true and entry must have been set.
|
208
|
} else {
|
209
|
general_debug('Unknown problem with IPv6 address. See output just above.');
|
210
|
}
|
211
|
} else {
|
212
|
general_debug('WARNING: Count of IPv6 addresses is: '.count($space_result));
|
213
|
}
|
214
|
}
|
215
|
}
|
216
|
|
217
|
function gui_parse_single_pd_lease($val, $lang_pack, &$entry) {
|
218
|
if (setnlen($val, 'SPACE')) {
|
219
|
general_debug($val, 'SPACE');
|
220
|
|
221
|
$space_result = parse_all_ipv6_to_array($val['SPACE']);
|
222
|
|
223
|
if (count($space_result) == 1) {
|
224
|
if (setnlen($space_result[0], 'MATCH')) {
|
225
|
general_debug("We have a valid ipv6 address");
|
226
|
|
227
|
if (setnlen($space_result[0], 'CMR6') &&
|
228
|
preg_match('/::$/', $space_result[0]['CMR6'])) {
|
229
|
general_debug("that is properly terminated with ::");
|
230
|
} else {
|
231
|
general_debug(", but not a proper subnet that ends with ::");
|
232
|
}
|
233
|
|
234
|
check_value_set_entry($val, 'PREFIX', $entry, 'prefix', true,
|
235
|
function () use (&$val, &$lang_pack, &$entry, &$space_result) {
|
236
|
check_n_set($entry, 'type', $lang_pack['dynamic'], true);
|
237
|
return $space_result[0]['MATCH'].'/'.$val['PREFIX'];});
|
238
|
} else {
|
239
|
general_debug('Unknown problem with IPv6 address. See output just above.');
|
240
|
}
|
241
|
} else {
|
242
|
general_debug('WARNING: Count of IPv6 addresses is: '.count($space_result));
|
243
|
}
|
244
|
}
|
245
|
}
|
246
|
|
247
|
function gui_handle_ia($content, &$entry, $lang_pack) {
|
248
|
preg_match_all('/'.ia_regex.'/i', $content, $ia_matches, PREG_SET_ORDER);
|
249
|
|
250
|
/* The reason why we use foreach and not simply just try lookup content is due to the
|
251
|
* fact that we match with alternation different sets of values from different
|
252
|
* regex groups.
|
253
|
* In theory one set/group could be repeated inside $ia_matches which means we want to
|
254
|
* know if that is the case. check_n_set makes sure to check if the value exists already
|
255
|
* and returns debug info about that.
|
256
|
*/
|
257
|
foreach ($ia_matches as $ia_val) {
|
258
|
if (setnlen($ia_val, 'BINDINGSTATE')) {
|
259
|
switch ($ia_val['BINDINGSTATE']) {
|
260
|
case "active":
|
261
|
check_n_set($entry, 'act', $lang_pack['active'], true);
|
262
|
break;
|
263
|
case "expired":
|
264
|
check_n_set($entry, 'act', $lang_pack['expired'], true);
|
265
|
break;
|
266
|
case "free":
|
267
|
check_n_set($entry, 'act', $lang_pack['expired'], true);
|
268
|
check_n_set($entry, 'online', $lang_pack['offline'], true);
|
269
|
break;
|
270
|
case "backup":
|
271
|
check_n_set($entry, 'act', $lang_pack['reserved'], true);
|
272
|
check_n_set($entry, 'online', $lang_pack['offline'], true);
|
273
|
break;
|
274
|
case "released":
|
275
|
check_n_set($entry, 'act', $lang_pack['released'], true);
|
276
|
check_n_set($entry, 'online', $lang_pack['offline'], true);
|
277
|
break;
|
278
|
default:
|
279
|
general_debug('Notice: Binding state "' . $ia_val['BINDINGSTATE'] .
|
280
|
'" is not handled.');
|
281
|
break;
|
282
|
}
|
283
|
|
284
|
general_debug($ia_val, 'BINDINGSTATE');
|
285
|
}
|
286
|
|
287
|
// does not seem to be used by lease gui so we do not set anything; we just debug.
|
288
|
check_value_set_entry($ia_val, 'PREFLIFE');
|
289
|
|
290
|
// does not seem to be used by lease gui so we do not set anything; we just debug.
|
291
|
check_value_set_entry($ia_val, 'MAXLIFE');
|
292
|
|
293
|
check_value_set_entry($ia_val, 'ENDS', $entry, 'end', true, function () use (&$ia_val) {
|
294
|
general_debug($ia_val, 'WEEKDAY', 'DATE', 'YEAR', 'MONTH', 'DAY', 'TIME');
|
295
|
return $ia_val['DATE'] . ' ' . $ia_val['TIME'];});
|
296
|
}
|
297
|
}
|
298
|
|
299
|
function gui_parse_single_failover($content, &$entry) {
|
300
|
preg_match_all('/'.failover_level1_regex.'/i', $content, $matches, PREG_SET_ORDER);
|
301
|
|
302
|
/* The reason why we use foreach and not simply just try lookup content is due to the
|
303
|
* fact that we match with alternation different sets of values from different
|
304
|
* regex groups.
|
305
|
* In theory one set/group could be repeated inside $matches which means we want to
|
306
|
* know if that is the case. check_value_set_entry makes sure to check if the value
|
307
|
* exists already and returns debug info about that.
|
308
|
*/
|
309
|
foreach ($matches as $val) {
|
310
|
check_value_set_entry($val, 'MYSTATE', $entry, 'mystate', true);
|
311
|
check_value_set_entry($val, 'MYDATE', $entry, 'mydate', true,
|
312
|
function () use (&$val) {return $val['DATE'] . ' ' . $val['TIME'];});
|
313
|
check_value_set_entry($val, 'PEERSTATE', $entry, 'peerstate', true);
|
314
|
check_value_set_entry($val, 'PEERDATE', $entry, 'peerdate', true,
|
315
|
function () use (&$val) {return $val['PDATE'] . ' ' . $val['PTIME'];});
|
316
|
/* Does not seem to be used by lease gui so we do not set anything; we just debug.
|
317
|
* mclt (Maximum Client Lead Time):
|
318
|
* https://kb.isc.org/article/AA-00502/0/A-Basic-Guide-to-Configuring-DHCP-Failover.html
|
319
|
* https://kb.isc.org/article/AA-00268/0/DHCP-Failover-and-MCLT-configuration-implications.html
|
320
|
*/
|
321
|
check_value_set_entry($val, 'MCLT');
|
322
|
}
|
323
|
}
|
324
|
|
325
|
/* Main leases - Level 0
|
326
|
* regex matches the outer/top level of a lease
|
327
|
* We allow free spacing and comments with (?x)
|
328
|
* We capture:
|
329
|
* . the identity association of a temporary address and non-temporary address
|
330
|
* and delegated prefix (IA-TA, IA-NA and IA-PD)
|
331
|
* . the partly octal encoded iaid + duid (Identity association identifier + DHCP Unique Identifier)
|
332
|
* . the content at the next level,
|
333
|
* but our matching also reuses our
|
334
|
* current match (RECURSE) to make sure
|
335
|
* there is a balance to the curly braces {}
|
336
|
* semicolons where there are no curly braces
|
337
|
* and sub-content to curly braces - but neither the
|
338
|
* next level and sub levels are directly
|
339
|
* individually matched/captured (that is for later
|
340
|
* regex matches to achieve!)
|
341
|
*
|
342
|
* \x5C = \
|
343
|
* Octal numbers are the characters that are found in the IAID and DUID with a
|
344
|
* backslash \ in front of them.
|
345
|
* Hex values in ranges are those characters that are printed as characters (not
|
346
|
* octal values):
|
347
|
* [\x20\x21\x23-\x5B\x5D-\x7E]
|
348
|
* 1. See ascii table that includes hex values:
|
349
|
* https://en.wikipedia.org/wiki/ASCII#Printable_characters
|
350
|
* https://en.wikipedia.org/wiki/ASCII#Character_set
|
351
|
* 2. Check: https://source.isc.org/cgi-bin/gitweb.cgi?p=dhcp.git;a=blob;f=common/print.c
|
352
|
* 3. isprint is more limited than isascii: http://www.cplusplus.com/reference/cctype/isprint/
|
353
|
*/
|
354
|
|
355
|
/* Notice (?x) must be a the beginning of the line - cannot be tabbed in,
|
356
|
* because free spacing mode must start at the very beginning (^) of the
|
357
|
* content.
|
358
|
*/
|
359
|
const base_regex = <<<'BASE'
|
360
|
(?x)
|
361
|
(?'IA_ASSOC'ia-(?>ta|na|pd))
|
362
|
\s+"
|
363
|
(?'IAID_DUID'
|
364
|
(?>
|
365
|
[\x20\x21\x23-\x5B\x5D-\x7E]
|
366
|
|
|
367
|
\x5C
|
368
|
(?>
|
369
|
"
|
370
|
|
|
371
|
\x5C
|
372
|
|
|
373
|
(?>00[0-7]|0[1-3][0-7]|177|[2-3][0-7]{2})
|
374
|
)
|
375
|
)*
|
376
|
)
|
377
|
"\s+
|
378
|
\{
|
379
|
(?'NEXT_LEVEL'
|
380
|
(?>
|
381
|
\s*
|
382
|
(?>
|
383
|
[^\{\}\n]+;
|
384
|
|
|
385
|
[^\{\}\n]+
|
386
|
\s*
|
387
|
\{
|
388
|
(?&NEXT_LEVEL)
|
389
|
\}
|
390
|
\s*
|
391
|
)*
|
392
|
)*
|
393
|
)
|
394
|
\}
|
395
|
BASE;
|
396
|
/* Inside the: (?'NEXT_LEVEL' ... )
|
397
|
* various white space characters where substituted with \s* to match all white
|
398
|
* space, but at different places before the substitutions some of the white
|
399
|
* space characters were as mentioned below and their purpose:
|
400
|
* * (star) needed for when no space or newline e.g. nested {} is used in e.g.:
|
401
|
* something {dww w{} w {d;}}
|
402
|
* \n (new line) needed for quicker matching (here)
|
403
|
* \ (space) needed so we eat all space before a faulty double {}:
|
404
|
* something {} {}
|
405
|
* , is repeated - which we consider illegal.
|
406
|
* \ \n has been substituted with \s to cover more white space characters!
|
407
|
*/
|
408
|
|
409
|
/* https://source.isc.org/cgi-bin/gitweb.cgi?p=dhcp.git;a=blob;f=includes/dhcpd.h
|
410
|
* struct lease etc. and various write_lease methods
|
411
|
*
|
412
|
* https://source.isc.org/cgi-bin/gitweb.cgi?p=dhcp.git;a=blob;f=server/db.c
|
413
|
* method for write lease file content is found here e.g. write_lease()
|
414
|
*
|
415
|
* Notice lease time entries are written with C gmtime() as UTC:
|
416
|
* https://source.isc.org/cgi-bin/gitweb.cgi?p=dhcp.git;a=blob;f=common/print.c
|
417
|
* const char * print_time(TIME t) last else clause:
|
418
|
* if (strftime(buf, sizeof(buf), "%w %Y/%m/%d %H:%M:%S;",
|
419
|
* gmtime(&t)) == 0)
|
420
|
*/
|
421
|
const date_time = <<<'DATETIME'
|
422
|
(?'WEEKDAY'[0-6])\s+
|
423
|
(?'DATE'
|
424
|
(?'YEAR'\d+)\/(?'MONTH'(?>0[1-9]|1[0-2]))\/
|
425
|
(?'DAY'(?>0[1-9]|[1-2][0-9]|30|31))
|
426
|
)\s+
|
427
|
(?'TIME'(?>[0-1][0-9]|2[0-3]):(?>[0-5][0-9]):(?>[0-5][0-9]|60))
|
428
|
DATETIME;
|
429
|
|
430
|
// Level 1
|
431
|
const common_napd_top =
|
432
|
'(?x)
|
433
|
cltt\s+(?\'CLTT\''.date_time.');|';
|
434
|
|
435
|
# Perform real check of the IPv6 address/space somewhere else.
|
436
|
const pd_specific = <<<'PD'
|
437
|
iaprefix\s+(?'SPACE'[\.\da-fA-F:]*)\/(?'PREFIX'\d{1,3})\s+\{
|
438
|
PD;
|
439
|
const na_specific = <<<'NA'
|
440
|
iaaddr\s+(?'ADDRESS'[\.\da-fA-F:]*)\s+\{
|
441
|
NA;
|
442
|
|
443
|
/* Level 1
|
444
|
* This part handles matching properly placed curly braces and semicolons.
|
445
|
* If in doubt just do experiments! To achieve matching the curly braces we need
|
446
|
* to check at all levels however we do just capture it all since matching the
|
447
|
* individual components needs to be handled separatly at each level by other
|
448
|
* regexes.
|
449
|
* Same idea as with base_regex above, but not expanded to save space.
|
450
|
*/
|
451
|
const common_napd_bottom = <<<'COMMON'
|
452
|
(?'NEXT_LEVEL'(?>\s*
|
453
|
(?>[^\{\}\n]+;|
|
454
|
[^\{\}\n]+\s*\{
|
455
|
(?&NEXT_LEVEL)
|
456
|
\}\s*
|
457
|
)*)*)
|
458
|
\}
|
459
|
COMMON;
|
460
|
|
461
|
/* Level 2 stuff
|
462
|
* binding state names defined in array binding_state_names
|
463
|
* https://source.isc.org/cgi-bin/gitweb.cgi?p=dhcp.git;a=blob;f=server/stables.c
|
464
|
* "free", "active", "expired", "released", "abandoned", "reset", "backup"
|
465
|
* write_lease and write_ia use binding_state_names in:
|
466
|
* https://source.isc.org/cgi-bin/gitweb.cgi?p=dhcp.git;a=blob;f=server/db.c
|
467
|
* defined in https://source.isc.org/cgi-bin/gitweb.cgi?p=dhcp.git;a=blob;f=server/stables.c
|
468
|
*/
|
469
|
const ia_regex =
|
470
|
'(?x)
|
471
|
(binding\s+state\s+(?\'BINDINGSTATE\'free|active|expired|released|abandoned|reset|backup)|
|
472
|
preferred-life\s+(?\'PREFLIFE\'\d+)|
|
473
|
max-life\s+(?\'MAXLIFE\'\d+)|
|
474
|
ends\s+(?\'ENDS\''.date_time.'));\s*';
|
475
|
|
476
|
const pd_regex = common_napd_top."\n".pd_specific."\n".common_napd_bottom;
|
477
|
const na_regex = common_napd_top."\n".na_specific."\n".common_napd_bottom;
|
478
|
|
479
|
/* Print these out if you would like to test and debug with (online) tools such
|
480
|
as https://regex101.com - you could also test and debug some parts mentioned
|
481
|
above. Just use the raw lease file as input and see how they match. */
|
482
|
|
483
|
// NOTE THIS IS NOT TESTED ON A REAL FAILOVER CONFIGURED SYSTEM
|
484
|
/*
|
485
|
* Handling of failover base - level 0
|
486
|
* Documentation is misleading so the source of ISC DHCP server is the best
|
487
|
* resource combined with this article:
|
488
|
* https://deepthought.isc.org/article/AA-00252/31/Putting-the-working-server-in-a-failover-pair-into-partner-down-state.html
|
489
|
* , that seems to be right! Whereas the documentation tells something different
|
490
|
* in: https://www.isc.org/wp-content/uploads/2017/08/dhcp43leases.html
|
491
|
* , it writes: peer state state at date;
|
492
|
* For more about how we implement see under level 1 below.
|
493
|
*
|
494
|
* FPN = failover pair name
|
495
|
* Relevant source places:
|
496
|
* https://source.isc.org/cgi-bin/gitweb.cgi?p=dhcp.git;a=blob;f=server/db.c
|
497
|
* method: write_failover_state
|
498
|
*
|
499
|
* Same idea as with base_regex above, but not expanded to save space.
|
500
|
*/
|
501
|
const failover_base_regex = <<<'FAILOVERBASE'
|
502
|
(?x)
|
503
|
failover\s+peer\s+\"(?'FPN'.*)\"\s+state\s+\{
|
504
|
(?'NEXT_LEVEL'(?>\s*
|
505
|
(?>[^\{\}\n]+;|
|
506
|
[^\{\}\n]+\s*\{
|
507
|
(?&NEXT_LEVEL)
|
508
|
\}\s*
|
509
|
)*)*)
|
510
|
\}
|
511
|
FAILOVERBASE;
|
512
|
|
513
|
/*
|
514
|
* Handling of failover statements - level 1
|
515
|
*
|
516
|
* Definition of state names:
|
517
|
* https://source.isc.org/cgi-bin/gitweb.cgi?p=dhcp.git;a=blob;f=server/failover.c;h=25e1b72b5ed1705981fa7867d7c6172daa27f5a0;hb=HEAD
|
518
|
* const char *dhcp_failover_state_name_print (enum failover_state state)
|
519
|
* https://source.isc.org/cgi-bin/gitweb.cgi?p=dhcp.git;a=blob;f=includes/failover.h
|
520
|
* enum failover_state, that holds the enumerated state names and the typedef
|
521
|
* of dhcp_failover_state_t with references to dhcp_failover_config_t me,
|
522
|
* partner and the enumerated failover_state for saved_state that is used by
|
523
|
* int write_failover_state (dhcp_failover_state_t *state) in:
|
524
|
* https://source.isc.org/cgi-bin/gitweb.cgi?p=dhcp.git;a=blob;f=server/db.c
|
525
|
* to write the actual failover info in our lease file!
|
526
|
*
|
527
|
*/
|
528
|
const p_date_time = <<< 'PDATETIME'
|
529
|
(?'PWEEKDAY'[0-6])\s+
|
530
|
(?'PDATE'
|
531
|
(?'PYEAR'\d+)\/(?'PMONTH'(?>0[1-9]|1[0-2]))\/
|
532
|
(?'PDAY'(?>0[1-9]|[1-2][0-9]|30|31))
|
533
|
)\s+
|
534
|
(?'PTIME'(?>[0-1][0-9]|2[0-3]):(?>[0-5][0-9]):(?>[0-5][0-9]|60))
|
535
|
PDATETIME;
|
536
|
|
537
|
/* Layout of these matters. 'recover-...' must come before 'recover', because
|
538
|
* we make use of atomic grouping in failover_level1 to save amount of steps to
|
539
|
* match!
|
540
|
*/
|
541
|
const failover_states = 'unknown-state|partner-down|normal|conflict-done'.
|
542
|
'|communications-interrupted|resolution-interrupted|potential-conflict'.
|
543
|
'|recover-done|recover-wait|recover|shutdown|paused|startup';
|
544
|
|
545
|
const failover_level1_regex =
|
546
|
'(?x)
|
547
|
(
|
548
|
my\s+state\s+(?\'MYSTATE\'(?>'.failover_states.'))\s+
|
549
|
at\s+(?\'MYDATE\''.date_time.')
|
550
|
|
|
551
|
partner\s+state\s+(?\'PEERSTATE\'(?&MYSTATE))\s+
|
552
|
at\s+(?\'PEERDATE\''.p_date_time.')
|
553
|
|
|
554
|
mclt\s+(?\'MCLT\'\d+)
|
555
|
);\s*';
|