Project

General

Profile

Download (13.3 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * diag_arp.php
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-2020 Rubicon Communications, LLC (Netgate)
7
 * All rights reserved.
8
 *
9
 * originally based on m0n0wall (http://m0n0.ch/wall)
10
 * Copyright (c) 2003-2004 Manuel Kasper <mk@neon1.net>.
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
##|+PRIV
27
##|*IDENT=page-diagnostics-arptable
28
##|*NAME=Diagnostics: ARP Table
29
##|*DESCR=Allow access to the 'Diagnostics: ARP Table' page.
30
##|*MATCH=diag_arp.php*
31
##|-PRIV
32

    
33
@ini_set('zlib.output_compression', 0);
34
@ini_set('implicit_flush', 1);
35

    
36
require_once("guiconfig.inc");
37

    
38
// delete arp entry
39
if (isset($_POST['deleteentry'])) {
40
	$ip = $_POST['deleteentry'];
41
	if (is_ipaddrv4($ip)) {
42
		$ret = mwexec("arp -d " . $_POST['deleteentry'], true);
43
	} else {
44
		$ret = 1;
45
	}
46
	if ($ret) {
47
		$savemsg = sprintf(gettext("%s is not a valid IPv4 address or could not be deleted."), $ip);
48
		$savemsgtype = 'alert-warning';
49
	} else {
50
		$savemsg = sprintf(gettext("The ARP cache entry for %s has been deleted."), $ip);
51
		$savemsgtype = 'success';
52
	}
53
}
54

    
55
function leasecmp($a, $b) {
56
	return strcmp($a[$_REQUEST['order']], $b[$_REQUEST['order']]);
57
}
58

    
59
function adjust_gmt($dt) {
60
	$ts = strtotime($dt . " GMT");
61
	return strftime("%Y/%m/%d %H:%M:%S", $ts);
62
}
63

    
64
function remove_duplicate($array, $field) {
65
	foreach ($array as $sub) {
66
		$cmp[] = $sub[$field];
67
	}
68
	$unique = array_unique($cmp);
69
	foreach ($unique as $k => $rien) {
70
		$new[] = $array[$k];
71
	}
72
	return $new;
73
}
74

    
75
// Define path to AWK
76
$awk = "/usr/bin/awk";
77

    
78
// Read in leases file
79
$leasesfile = "{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases";
80

    
81
/* this pattern sticks comments into a single array item */
82
$cleanpattern = "'{ gsub(\"#.*\", \"\");} { gsub(\";\", \"\"); print;}'";
83

    
84
/* We then split the leases file by } */
85
$splitpattern = "'BEGIN { RS=\"}\";} {for (i=1; i<=NF; i++) printf \"%s \", \$i; printf \"}\\n\";}'";
86

    
87
/* stuff the leases file in a proper format into an array by line */
88
exec("cat {$leasesfile} | {$awk} {$cleanpattern} | {$awk} {$splitpattern}", $leases_content);
89
$leases_count = count($leases_content);
90

    
91
$pools = array();
92
$leases = array();
93
$i = 0;
94
$l = 0;
95
$p = 0;
96
// Put everything together again
97
while ($i < $leases_count) {
98
	/* split the line by space */
99
	$data = explode(" ", $leases_content[$i]);
100
	/* walk the fields */
101
	$f = 0;
102
	$fcount = count($data);
103
	/* with less then 20 fields there is nothing useful */
104
	if ($fcount < 20) {
105
		$i++;
106
		continue;
107
	}
108
	while ($f < $fcount) {
109
		switch ($data[$f]) {
110
			case "failover":
111
				$pools[$p]['name'] = $data[$f+2];
112
				$pools[$p]['mystate'] = $data[$f+7];
113
				$pools[$p]['peerstate'] = $data[$f+14];
114
				$pools[$p]['mydate'] = $data[$f+10];
115
				$pools[$p]['mydate'] .= " " . $data[$f+11];
116
				$pools[$p]['peerdate'] = $data[$f+17];
117
				$pools[$p]['peerdate'] .= " " . $data[$f+18];
118
				$p++;
119
				$i++;
120
				continue 3;
121
			case "lease":
122
				$leases[$l]['ip'] = $data[$f+1];
123
				$leases[$l]['type'] = "dynamic";
124
				$f = $f+2;
125
				break;
126
			case "starts":
127
				$leases[$l]['start'] = $data[$f+2];
128
				$leases[$l]['start'] .= " " . $data[$f+3];
129
				$f = $f+3;
130
				break;
131
			case "ends":
132
				$leases[$l]['end'] = $data[$f+2];
133
				$leases[$l]['end'] .= " " . $data[$f+3];
134
				$f = $f+3;
135
				break;
136
			case "tstp":
137
				$f = $f+3;
138
				break;
139
			case "tsfp":
140
				$f = $f+3;
141
				break;
142
			case "atsfp":
143
				$f = $f+3;
144
				break;
145
			case "cltt":
146
				$f = $f+3;
147
				break;
148
			case "binding":
149
				switch ($data[$f+2]) {
150
					case "active":
151
						$leases[$l]['act'] = "active";
152
						break;
153
					case "free":
154
						$leases[$l]['act'] = "expired";
155
						$leases[$l]['online'] = "offline";
156
						break;
157
					case "backup":
158
						$leases[$l]['act'] = "reserved";
159
						$leases[$l]['online'] = "offline";
160
						break;
161
				}
162
				$f = $f+1;
163
				break;
164
			case "next":
165
				/* skip the next binding statement */
166
				$f = $f+3;
167
				break;
168
			case "rewind":
169
				/* skip the rewind binding statement */
170
				$f = $f+3;
171
				break;
172
			case "hardware":
173
				$leases[$l]['mac'] = $data[$f+2];
174
				/* check if it's online and the lease is active */
175
				if ($leases[$l]['act'] == "active") {
176
					$online = exec("/usr/sbin/arp -an |/usr/bin/awk '/{$leases[$l]['ip']}/ {print}'|wc -l");
177
					if ($online == 1) {
178
						$leases[$l]['online'] = 'online';
179
					} else {
180
						$leases[$l]['online'] = 'offline';
181
					}
182
				}
183
				$f = $f+2;
184
				break;
185
			case "client-hostname":
186
				if ($data[$f+1] <> "") {
187
					$leases[$l]['hostname'] = preg_replace('/"/', '', $data[$f+1]);
188
				} else {
189
					$hostname = gethostbyaddr($leases[$l]['ip']);
190
					if ($hostname <> "") {
191
						$leases[$l]['hostname'] = $hostname;
192
					}
193
				}
194
				$f = $f+1;
195
				break;
196
			case "uid":
197
				$f = $f+1;
198
				break;
199
		}
200
		$f++;
201
	}
202
	$l++;
203
	$i++;
204
}
205

    
206
/* remove duplicate items by mac address */
207
if (count($leases) > 0) {
208
	$leases = remove_duplicate($leases, "ip");
209
}
210

    
211
if (count($pools) > 0) {
212
	$pools = remove_duplicate($pools, "name");
213
	asort($pools);
214
}
215

    
216
// Put this in an easy to use form
217
$dhcpmac = array();
218
$dhcpip = array();
219

    
220
foreach ($leases as $value) {
221
	$dhcpmac[$value['mac']] = $value['hostname'];
222
	$dhcpip[$value['ip']] = $value['hostname'];
223
}
224

    
225
exec("/usr/sbin/arp -an", $rawdata);
226

    
227
$i = 0;
228

    
229
/* if list */
230
$ifdescrs = get_configured_interface_with_descr();
231

    
232
foreach ($ifdescrs as $key => $interface) {
233
	$thisif = convert_friendly_interface_to_real_interface_name($key);
234
	if (!empty($thisif)) {
235
		$hwif[$thisif] = $interface;
236
	}
237
}
238

    
239
$data = array();
240
foreach ($rawdata as $line) {
241
	$elements = explode(' ', $line, 7);
242
	$arpent = array();
243
	$arpent['ip'] = trim(str_replace(array('(', ')'), '', $elements[1]));
244
	$arpent['mac'] = trim($elements[3]);
245
	$arpent['interface'] = trim($elements[5]);
246
	$arpent['status'] = trim(substr($elements[6], 0, strrpos($elements[6], ' ')));
247
	$arpent['linktype'] = trim(str_replace(array('[', ']'), '', strrchr($elements[6], ' ')));
248
	$data[] = $arpent;
249
}
250

    
251
function _getHostName($mac, $ip) {
252
	global $dhcpmac, $dhcpip;
253

    
254
	if ($dhcpmac[$mac]) {
255
		return $dhcpmac[$mac];
256
	} else if ($dhcpip[$ip]) {
257
		return $dhcpip[$ip];
258
	} else {
259
		exec("host -W 1 " . escapeshellarg($ip), $output);
260
		if (preg_match('/.*pointer ([A-Za-z_0-9.-]+)\..*/', $output[0], $matches)) {
261
			if ($matches[1] <> $ip) {
262
				return $matches[1];
263
			}
264
		}
265
	}
266
	return "";
267
}
268

    
269
$pgtitle = array(gettext("Diagnostics"), gettext("ARP Table"));
270
include("head.inc");
271

    
272
// Handle save msg if defined
273
if ($savemsg) {
274
	print_info_box(htmlentities($savemsg), $savemsgtype);
275
}
276
?>
277

    
278
<!-- On modern hardware the table will load so fast you may never see this! -->
279
<div id="loading">
280
	<?= gettext(" Loading, please wait...")?>
281
</div>
282

    
283
<?php
284

    
285
// Flush buffers out to client so that they see Loading, please wait....
286
for ($i = 0; $i < ob_get_level(); $i++) {
287
	ob_end_flush();
288
}
289

    
290
ob_implicit_flush(1);
291

    
292
// Resolve hostnames and replace Z_ with "".  The intention
293
// is to sort the list by hostnames, alpha and then the non
294
// resolvable addresses will appear last in the list.
295
$dnsavailable=1;
296
$dns = trim(_getHostName("", "8.8.8.8"));
297
if ($dns == "") {
298
	$dns = trim(_getHostName("", "8.8.4.4"));
299
	if ($dns == "") {
300
		$dnsavailable = 0;
301
	}
302
}
303

    
304
foreach ($data as &$entry) {
305
	if ($dnsavailable) {
306
		$dns = trim(_getHostName($entry['mac'], $entry['ip']));
307
	} else {
308
		$dns="";
309
	}
310
	if (trim($dns)) {
311
		$entry['dnsresolve'] = "$dns";
312
	} else {
313
		$entry['dnsresolve'] = "Z_ ";
314
	}
315
}
316
unset($entry);
317

    
318
// Sort the data alpha first
319
$data = msort($data, "dnsresolve");
320

    
321
// Load MAC-Manufacturer table
322
$mac_man = load_mac_manufacturer_table();
323
?>
324
<div class="panel panel-default" id="search-panel">
325
	<div class="panel-heading">
326
		<h2 class="panel-title">
327
			<?=gettext('Search')?>
328
			<span class="widget-heading-icon pull-right">
329
				<a data-toggle="collapse" href="#search-panel_panel-body">
330
					<i class="fa fa-plus-circle"></i>
331
				</a>
332
			</span>
333
		</h2>
334
	</div>
335
	<div id="search-panel_panel-body" class="panel-body collapse in">
336
		<div class="form-group">
337
			<label class="col-sm-2 control-label">
338
				<?=gettext("Search term")?>
339
			</label>
340
			<div class="col-sm-5"><input class="form-control" name="searchstr" id="searchstr" type="text"/></div>
341
			<div class="col-sm-2">
342
				<select id="where" class="form-control">
343
					<option value="0"><?=gettext("Interface")?></option>
344
					<option value="1"><?=gettext("IP Address")?></option>
345
					<option value="2"><?=gettext("MAC Address")?></option>
346
					<option value="3"><?=gettext("Hostname")?></option>
347
					<option value="4"><?=gettext("Status")?></option>
348
					<option value="5"><?=gettext("Link Type")?></option>
349
					<option value="6" selected><?=gettext("All")?></option>
350
				</select>
351
			</div>
352
			<div class="col-sm-3">
353
				<a id="btnsearch" title="<?=gettext("Search")?>" class="btn btn-primary btn-sm"><i class="fa fa-search icon-embed-btn"></i><?=gettext("Search")?></a>
354
				<a id="btnclear" title="<?=gettext("Clear")?>" class="btn btn-info btn-sm"><i class="fa fa-undo icon-embed-btn"></i><?=gettext("Clear")?></a>
355
			</div>
356
			<div class="col-sm-10 col-sm-offset-2">
357
				<span class="help-block"><?=gettext('Enter a search string or *nix regular expression to filter entries.')?></span>
358
			</div>
359
		</div>
360
	</div>
361
</div>
362

    
363
<div class="panel panel-default">
364
	<div class="panel-heading"><h2 class="panel-title"><?=gettext('ARP Table')?></h2></div>
365
	<div class="panel-body">
366

    
367
<div class="table-responsive">
368
	<table class="sortable-theme-bootstrap table table-striped table-hover" data-sortable>
369
		<thead>
370
			<tr>
371
				<th><?= gettext("Interface")?></th>
372
				<th><?= gettext("IP address")?></th>
373
				<th><?= gettext("MAC address")?></th>
374
				<th><?= gettext("Hostname")?></th>
375
				<th><?= gettext("Status")?></th>
376
				<th><?= gettext("Link Type")?></th>
377
				<th data-sortable="false"><?=gettext("Actions")?></th>
378
			</tr>
379
		</thead>
380
		<tbody>
381

    
382
<?php
383
		foreach ($data as $entry): ?>
384
			<tr>
385
				<td><?=$hwif[$entry['interface']]?></td>
386
				<td><?=$entry['ip']?></td>
387
				<td>
388
					<?=trim($entry['mac'])?>
389
				<?php
390
					$mac = trim($entry['mac']);
391
					$mac_hi = strtoupper($mac[0] . $mac[1] . $mac[3] . $mac[4] . $mac[6] . $mac[7]);
392

    
393
					if (isset($mac_man[$mac_hi])) {
394
						print '<small>('. $mac_man[$mac_hi] .')</small>';
395
					}
396
	?>
397
				</td>
398
				<td><?=trim(str_replace("Z_ ", "", $entry['dnsresolve']))?></td>
399
				<td><?=ucfirst($entry['status'])?></td>
400
				<td><?=$entry['linktype']?></td>
401
				<td>
402
					<a class="fa fa-trash" title="<?=gettext('Delete arp cache entry')?>"	href="diag_arp.php?deleteentry=<?=$entry['ip']?>" usepost></a>
403
				</td>
404
			</tr>
405
		<?php endforeach?>
406
		</tbody>
407
	</table>
408
</div>
409

    
410
	</div>
411
</div>
412

    
413
<script type="text/javascript">
414
//<![CDATA[
415
// Clear the "loading" div once the page has loaded"
416
events.push(function() {
417
	$('#loading').empty();
418

    
419
	// Make these controls plain buttons
420
	$("#btnsearch").prop('type', 'button');
421
	$("#btnclear").prop('type', 'button');
422

    
423
	// Search for a term in the entry name and/or dn
424
	$("#btnsearch").click(function() {
425
		var searchstr = $('#searchstr').val().toLowerCase();
426
		var table = $("table tbody");
427
		var where = $('#where').val();
428

    
429
		table.find('tr').each(function (i) {
430
			var $tds = $(this).find('td'),
431
				iface    = $tds.eq(0).text().trim().toLowerCase(),
432
				ipaddr   = $tds.eq(1).text().trim().toLowerCase();
433
				macaddr  = $tds.eq(2).text().trim().toLowerCase();
434
				hostname = $tds.eq(3).text().trim().toLowerCase();
435
				stat     = $tds.eq(4).text().trim().toLowerCase();
436
				linktype = $tds.eq(5).text().trim().toLowerCase();
437

    
438
			regexp = new RegExp(searchstr);
439
			if (searchstr.length > 0) {
440
				if (!(regexp.test(iface)    && ((where == 0) || (where == 6))) &&
441
				    !(regexp.test(ipaddr)   && ((where == 1) || (where == 6))) &&
442
				    !(regexp.test(macaddr)  && ((where == 2) || (where == 6))) &&
443
				    !(regexp.test(hostname) && ((where == 3) || (where == 6))) &&
444
				    !(regexp.test(stat)     && ((where == 4) || (where == 6))) &&
445
				    !(regexp.test(linktype) && ((where == 5) || (where == 6)))
446
				    ) {
447
					$(this).hide();
448
				} else {
449
					$(this).show();
450
				}
451
			} else {
452
				$(this).show();	// A blank search string shows all
453
			}
454
		});
455
	});
456

    
457
	// Clear the search term and unhide all rows (that were hidden during a previous search)
458
	$("#btnclear").click(function() {
459
		var table = $("table tbody");
460

    
461
		$('#searchstr').val("");
462

    
463
		table.find('tr').each(function (i) {
464
			$(this).show();
465
		});
466
	});
467

    
468
	// Hitting the enter key will do the same as clicking the search button
469
	$("#searchstr").on("keyup", function (event) {
470
		if (event.keyCode == 13) {
471
			$("#btnsearch").get(0).click();
472
		}
473
	});
474

    
475
});
476
//]]>
477
</script>
478

    
479
<div class="infoblock blockopen">
480
<?php
481
print_info_box(sprintf(gettext('Local IPv6 peers use %1$sNDP%2$s instead of ARP.'), '<a href="diag_ndp.php">', '</a>') . '<br />' .
482
   '<br />' . gettext('Permanent ARP entries are shown for local interfaces or static ARP entries.') .
483
   '<br />' . gettext('Normal dynamic ARP entries show a countdown timer until they will expire and then be re-checked.') .
484
   '<br />' . gettext('Incomplete ARP entries indicate that the target host has not yet replied to an ARP request.'), 'info', false);
485
?>
486
</div>
487

    
488
<?php
489
include("foot.inc");
490
?>
(8-8/235)