Project

General

Profile

Feature #13017 » diag_packet_capture.patch

Phil Wardt, 04/01/2022 03:52 PM

View differences:

src/usr/local/www/diag_packet_capture.php
135 135
	}
136 136
}
137 137

  
138
function tcpdump_get_running_process($f){
139
	$processcheck = trim(shell_exec("/bin/ps axw -O pid= | /usr/bin/grep '/usr/sbin/[t]cpdump.*{$f}'"));
140
	return $processcheck;
141
}
142

  
138 143
if ($_POST['downloadbtn'] == gettext("Download Capture")) {
139 144
	$nocsrf = true;
140 145
}
......
146 151

  
147 152
$fp = "/root/";
148 153
$fn = "packetcapture.cap";
154
$fe = "packetcapture.error";
149 155
$fns = "packetcapture.start";
150 156
$snaplen = 0;//default packet length
151 157
$count = 100;//default number of packets to capture
......
155 161
$protos = array('icmp', 'icmp6', 'tcp', 'udp', 'arp', 'carp', 'esp', 'pfsync', 'ospf',
156 162
		        '!icmp', '!icmp6', '!tcp', '!udp', '!arp', '!carp', '!esp', '!pfsync', '!ospf');
157 163

  
158
$input_errors = array();
159

  
160 164
$interfaces = get_configured_interface_with_descr();
161 165
if (ipsec_enabled()) {
162 166
	$interfaces['enc0'] = "IPsec";
......
175 179

  
176 180
$interfaces = array_merge($interfaces, interface_ipsec_vti_list_all());
177 181

  
182
$input_errors = array();
183
$do_tcpdump = false;
178 184
if ($_POST) {
179 185
	$host = $_POST['host'];
180 186
	$selectedif = $_POST['interface'];
......
185 191
	$detail = $_POST['detail'];
186 192
	$fam = $_POST['fam'];
187 193
	$proto = $_POST['proto'];
194
	$dnsquery = isset($_POST['dnsquery']);
188 195

  
196
	if ($_POST['startbtn'] != "") {
197
		$action = gettext("Start");
198

  
199
		// check for input errors
189 200
		if (!array_key_exists($selectedif, $interfaces)) {
190 201
			$input_errors[] = gettext("Invalid interface.");
191 202
		}
......
245 256
		}
246 257
	}
247 258

  
248
	if ($count == "") {
249
		$count = 0;
259
		if ($count == "") {// default is 100
260
			$count = 100;
250 261
		} else {
251 262
			if (!is_numeric($count) || $count < 0) {
252 263
				$input_errors[] = gettext("Invalid value specified for packet count.");
253 264
			}
254 265
		}
255 266

  
267
		// process capture options only if no input errors
256 268
	if (!count($input_errors)) {
257
		$do_tcpdump = true;
258 269

  
259

  
260
		if ($_POST['promiscuous']) {
270
			if ($promiscuous) {
261 271
				//if promiscuous mode is checked
262 272
				$disablepromiscuous = "";
263 273
			} else {
......
265 275
			$disablepromiscuous = "-p";
266 276
		}
267 277

  
268
		if ($_POST['dnsquery']) {
278
			if ($dnsquery) {
269 279
				//if dns lookup is checked
270 280
				$disabledns = "";
271 281
			} else {
......
275 285

  
276 286
			if ($_POST['startbtn'] != "") {
277 287
				$action = gettext("Start");
288
				$do_tcpdump = true;
278 289

  
279
			//delete previous packet capture if it exists
280
			if (file_exists($fp.$fn)) {
281
				unlink ($fp.$fn);
290
				//Cature will be started, delete previous packet capture if it exists
291
				touch($fp.$fns);
292
				unlink_if_exists($fp.$fn);
282 293
			}
283

  
294
		}
284 295
		} elseif ($_POST['stopbtn'] != "") {
285 296
			$action = gettext("Stop");
286
			$processes_running = trim(shell_exec("/bin/ps axw -O pid= | /usr/bin/grep tcpdump | /usr/bin/grep {$fn} | /usr/bin/egrep -v '(pflog|grep)'"));
297
		/* check if nmap scan is already running */
298
		$processes_running = tcpdump_get_running_process($fn);
299
		$processisrunning = ($processes_running != "");
287 300

  
288 301
		//explode processes into an array, (delimiter is new line)
289 302
		$processes_running_array = explode("\n", $processes_running);
......
294 307
			$process_id = substr($process, 0, $process_id_pos);
295 308
			exec("kill $process_id");
296 309
		}
310
	} elseif ($_POST['viewbtn'] != "" || $_POST['refreshbtn'] != "") {
311
		$action = gettext("View");
297 312
	} elseif ($_POST['downloadbtn'] != "") {
298 313
		//download file
314
		$action = gettext("Download");
299 315
		send_user_download('file', $fp.$fn);
316
	} elseif ($_POST['clearbtn'] != "") {
317
		$action = gettext("Delete");
318
		//delete previous packet capture if it exists
319
		unlink_if_exists($fp.$fn);
320
		unlink_if_exists($fp.$fe);
300 321
		}
301
	}
302
} else {
303
	$do_tcpdump = false;
304
}
322
}
305 323

  
306 324
$excl = gettext("Exclude");
307 325

  
......
349 367
	'Promiscuous',
350 368
	'Enable promiscuous mode',
351 369
	$promiscuous
352
))->setHelp('%1$sNon-promiscuous mode captures only traffic that is directly relevant to the host (sent by it, sent or broadcast to it, or routed through it) and ' .
353
	'does not show packets that are ignored at network adapter level.%2$s%3$sPromiscuous mode%4$s ("sniffing") captures all data seen by the adapter, whether ' .
354
	'or not it is valid or related to the host, but in some cases may have undesirable side effects and not all adapters support this option. Click Info for details %5$s' .
370
))->setHelp('Put the interface into promiscuous mode.%1$s' .
371
	'%2$sNon-promiscuous mode captures only traffic that is directly relevant to the host (sent by it, sent or broadcast to it, or routed through it) and ' .
372
	'does not show packets that are ignored at network adapter level.%3$s%4$sPromiscuous mode%5$s ("sniffing") captures all data seen by the adapter, whether ' .
373
	'or not it is valid or related to the host, but in some cases may have undesirable side effects and not all adapters support this option.%3$s' .
355 374
	'Promiscuous mode requires more kernel processing of packets. This puts a slightly higher demand on system resources, especially ' .
356 375
	'on very busy networks or low power processors. The change in packet processing may allow a hostile host to detect that an adapter is in promiscuous mode ' .
357 376
	'or to \'fingerprint\' the kernel (see %6$s). Some network adapters may not support or work well in promiscuous mode (see %7$s).%8$s',
358 377

  
359
	'<p style="margin-bottom:2px;padding-bottom:0px">',
360
	'</p><p style="margin:0px;padding:0px">',
378
	'<span class="infoblock" style="font-size:90%"><br />',
379
	'<p style="margin:0px;padding:0px">',
380
	'<br />',
361 381
	'<a href="https://en.wikipedia.org/wiki/Promiscuous_mode">',
362 382
	'</a>',
363
	'<span class="infoblock" style="font-size:90%"><br />',
364 383
	'&nbsp;<a target="_blank" href="https://security.stackexchange.com/questions/3630/how-to-find-out-that-a-nic-is-in-promiscuous-mode-on-a-lan">[1]</a>' .
365 384
		'&nbsp;<a href="https://nmap.org/nsedoc/scripts/sniffer-detect.html">[2]</a>',
366 385
	'&nbsp;<a target="_blank" href="https://www.freebsd.org/cgi/man.cgi?query=tcpdump&apropos=0&sektion=0&manpath=FreeBSD+12.2-RELEASE+and+Ports&arch=default&format=html">[3]</a>',
367
	'</span></p>'
368
);
386
	'</p></span>');
369 387

  
370 388
$section->addInput(new Form_Select(
371 389
	'fam',
......
382 400
	'*Protocol',
383 401
	$proto,
384 402
	$protocollist
385
))->setHelp('Select the protocol to capture, or "Any". ');
403
))->setHelp('Select the protocol to capture, or "Any".');
386 404

  
387 405
$section->addInput(new Form_Input(
388 406
	'host',
389 407
	'Host Address',
390 408
	'text',
391 409
	$host
392
))->setHelp('This value is either the Source or Destination IP address, subnet in CIDR notation, or MAC address.%1$s' .
410
))->setHelp('Source or Destination IP, subnet or MAC address.%1$s' .
411
			'%2$sThis value is either the Source or Destination IP address, subnet in CIDR notation, or MAC address.%3$s' .
393 412
			'Matching can be negated by preceding the value with "!". Multiple IP addresses or CIDR subnets may be specified. Comma (",") separated values perform a boolean "AND". ' .
394
			'Separating with a pipe ("|") performs a boolean "OR".%1$s' .
395
			'MAC addresses must be entered in colon-separated format, such as xx:xx:xx:xx:xx:xx or a partial address consisting of one (xx), two (xx:xx), or four (xx:xx:xx:xx) segments.%1$s' .
396
			'If this field is left blank, all packets on the specified interface will be captured.',
397
			'<br />');
413
			'Separating with a pipe ("|") performs a boolean "OR".%3$s' .
414
			'MAC addresses must be entered in colon-separated format, such as xx:xx:xx:xx:xx:xx or a partial address consisting of one (xx), two (xx:xx), or four (xx:xx:xx:xx) segments.%3$s' .
415
			'If this field is left blank, all packets on the specified interface will be captured.%4$s',
398 416

  
417
			'<span class="infoblock" style="font-size:90%"><br />',
418
			'<p style="margin:0px;padding:0px">',
419
			'<br />',
420
			'</p></span>');
421

  
399 422
$section->addInput(new Form_Input(
400 423
	'port',
401 424
	'Port',
402 425
	'text',
403 426
	$port
404
))->setHelp('The port can be either the source or destination port. The packet capture will look for this port in either field. ' .
405
			'Matching can be negated by preceding the value with "!". Multiple ports may be specified. Comma (",") separated values perform a boolean "AND". Separating with a pipe ("|") performs a boolean "OR".' .
406
			'Leave blank if not filtering by port.');
427
))->setHelp('Port to match on either the source or destination.%1$s' .
428
			'%2$sThe port can be either the source or destination port. The packet capture will look for this port in either field.%3$s' .
429
			'Matching can be negated by preceding the value with "!". Multiple ports may be specified. Comma (",") separated values perform a boolean "AND". ' .
430
			'Separating with a pipe ("|") performs a boolean "OR". Leave blank if not filtering by port.%4$s',
407 431

  
432
			'<span class="infoblock" style="font-size:90%"><br />',
433
			'<p style="margin:0px;padding:0px">',
434
			'<br />',
435
			'</p></span>');
436

  
408 437
$section->addInput(new Form_Input(
409 438
	'snaplen',
410 439
	'Packet Length',
411 440
	'text',
412 441
	$snaplen
413
))->setHelp('The Packet length is the number of bytes of each packet that will be captured. Default value is 0, ' .
414
			'which will capture the entire frame regardless of its size.');
442
))->setHelp('Number of bytes to capture for each packet.%1$s' .
443
			'%2$sDefault value is 0, which will set capture size to 262144 bytes.%3$s' .
444
			'Note that taking larger snapshots both  increases the amount of time it takes to process packets and, effectively, ' .
445
			'decreases the amount of packet buffering. This may cause packets to be lost.%3$s' .
446
			'You should limit snaplen to the smallest number that will capture the protocol information you are interested in.%4$s',
415 447

  
448
			'<span class="infoblock" style="font-size:90%"><br />',
449
			'<p style="margin:0px;padding:0px">',
450
			'<br />',
451
			'</p></span>');
452

  
416 453
$section->addInput(new Form_Input(
417 454
	'count',
418 455
	'Count',
419 456
	'text',
420 457
	$count
421
))->setHelp('This is the number of packets the packet capture will grab. Default value is 100.%s' .
422
			'Enter 0 (zero) for no count limit.',
423
			'<br />');
458
))->setHelp('Number of packets to capture before exiting (0 for no limit.)');
424 459

  
425 460
$section->addInput(new Form_Select(
426 461
	'detail',
......
432 467
		  'full' => gettext('Full'),
433 468
		  'none' => gettext('None'),
434 469
	)
435
))->setHelp('This is the level of detail that will be displayed after hitting "Stop" when the packets have been captured.%s' .
436
			'This option does not affect the level of detail when downloading the packet capture. ',
470
))->setHelp('Level of detail when viewing captured results.%s' .
471
			'It does not affect the level of detail when downloading the packet capture.',
437 472
			'<br />');
438 473

  
439 474
$section->addInput(new Form_Checkbox(
......
441 476
	'Reverse DNS Lookup',
442 477
	'Do reverse DNS lookup',
443 478
	$_POST['dnsquery']
444
))->setHelp('The packet capture will perform a reverse DNS lookup associated with all IP addresses.%s' .
445
			'This option can cause delays for large packet captures.',
446
			'<br />');
479
))->setHelp('Perform a reverse DNS lookup for all captured IP addresses.%s' .
480
			'%2$sThe packet capture will perform a reverse DNS lookup associated with all IP addresses. It will also convert port numbers, protocols, etc. to names.%3$s' .
481
			'This option can cause delays for large packet captures.%4$s',
447 482

  
483
			'<span class="infoblock" style="font-size:90%"><br />',
484
			'<p style="margin:0px;padding:0px">',
485
			'<br />',
486
			'</p></span>');
487

  
448 488
$form->add($section);
449 489

  
450 490
/* check to see if packet capture tcpdump is already running */
451
$processcheck = (trim(shell_exec("/bin/ps axw -O pid= | /usr/bin/grep tcpdump | /usr/bin/grep {$fn} | /usr/bin/egrep -v '(pflog|grep)'")));
491
$processes_running = tcpdump_get_running_process($fn);
492
$processisrunning = ($processes_running != "");
452 493

  
453
$processisrunning = ($processcheck != "");
454

  
455
if (($action == gettext("Stop") or $action == "") and $processisrunning != true) {
494
if ($processisrunning or $do_tcpdump) {
456 495
	$form->addGlobal(new Form_Button(
457
		'startbtn',
458
		'Start',
459
		null,
460
		'fa-play-circle'
461
	))->addClass('btn-success');
462
} else {
463
	$form->addGlobal(new Form_Button(
464 496
		'stopbtn',
465 497
		'Stop',
466 498
		null,
467 499
		'fa-stop-circle'
468 500
	))->addClass('btn-warning');
469
	if ($action == gettext("Start")) {
470
		touch("/root/packetcapture.start");
471
	}
501

  
502
	$form->addGlobal(new Form_Button(
503
		'refreshbtn',
504
		'Refresh Results',
505
		null,
506
		'fa-retweet'
507
	))->addClass('btn-primary');
508

  
472 509
	if (file_exists($fp.$fns)) {
473 510
		$section->addInput(new Form_StaticText(
474
			'Last capture start',
511
			'Capture started on',
475 512
			date("F jS, Y g:i:s a.", filemtime($fp.$fns))
476 513
		));
477 514
	}
478
}
515
} else {
516
	$form->addGlobal(new Form_Button(
517
		'startbtn',
518
		'Start',
519
		null,
520
		'fa-play-circle'
521
	))->addClass('btn-success');
479 522

  
480
if (file_exists($fp.$fn) and $processisrunning != true) {
523
	if (file_exists($fp.$fn) or file_exists($fp.$fe)) {
481 524
		$form->addGlobal(new Form_Button(
482 525
			'viewbtn',
483 526
			'View Capture',
......
492 535
		'fa-download'
493 536
	))->addClass('btn-primary');
494 537

  
495
	if (file_exists($fp.$fns)) {
496
		$section->addInput(new Form_StaticText(
497
			'Last capture start',
498
			date("F jS, Y g:i:s a.", filemtime($fp.$fns))
499
		));
538
		$form->addGlobal(new Form_Button(
539
			'clearbtn',
540
			'Clear Capture',
541
			null,
542
			'fa-trash'
543
		))->addClass('btn-danger');
500 544
	}
545
}
546

  
547
if (file_exists($fp.$fn)) {
501 548
	$section->addInput(new Form_StaticText(
502
		'Last capture stop',
549
		'Last Capture Results',
503 550
		date("F jS, Y g:i:s a.", filemtime($fp.$fn))
504 551
	));
505 552
}
506 553

  
507 554
print($form);
508 555

  
509
if ($do_tcpdump) :
556
if ($do_tcpdump) {
510 557
	$matches = array();
511 558

  
512 559
	if (in_array($fam, $fams)) {
......
546 593

  
547 594
	$selectedif = convert_friendly_interface_to_real_interface_name($selectedif);
548 595

  
549
	if ($action == gettext("Start")) {
550 596
		$matchstr = implode(" and ", $matches);
551
		$cmd = "/usr/sbin/tcpdump -i {$selectedif} {$disablepromiscuous} {$searchcount} -s {$snaplen} -w {$fp}{$fn} " . escapeshellarg($matchstr);
597
	if (strlen($matchstr) !== 0) $matchstr = escapeshellarg($matchstr);
598

  
599
	$cmd = "/usr/sbin/tcpdump -i {$selectedif} {$disablepromiscuous} {$searchcount} -s {$snaplen} -U -w {$fp}{$fn} {$matchstr} >/dev/null 2>{$fp}{$fe} &";
552 600
		print_info_box(gettext('Packet capture is running'), 'info');
553 601
		?>
554 602
		<div class="infoblock">
555 603
		<? print_info_box(gettext('Command line') . ': ' . htmlspecialchars($cmd), 'info', false); ?>
556 604
		</div>
557 605
		<?php
558
		mwexec_bg ($cmd);
606
	exec($cmd);
607
} elseif ($action == gettext("View") || $action == gettext("Stop")) {
608
		if (file_exists($fp.$fe) && filesize($fp.$fe) > 0) {
609
?>
610

  
611
<div class="panel panel-default">
612
	<div class="panel-heading"><h2 class="panel-title"><?=gettext('Capture Summary')?></h2></div>
613
	<div class="panel-body">
614
		<div class="form-group">
615
<?php
616

  
617
			print('<textarea class="form-control" rows="10" style="font-size: 13px; font-family: consolas,monaco,roboto mono,liberation mono,courier;">');
618
			if (filesize($fp.$fe) > $max_display_size) {
619
				print(gettext("Packet Capture summary file is too large to display in the GUI.") .
620
					"\n" .
621
					gettext("Download the file, or view it in the console or ssh shell.") .
622
					"\n" .
623
					gettext("Summary file: {$fp}{$fe}"));
559 624
			} else {
625
				print(file_get_contents($fp.$fe));
626
			}
627
			print('</textarea>');
628

  
560 629
?>
630
		</div>
631
	</div>
632
</div>
633
<?php
634
		}
635
?>
561 636

  
562 637
<div class="panel panel-default">
563 638
	<div class="panel-heading"><h2 class="panel-title"><?=gettext('Packets Captured')?></h2></div>
......
590 665
		if (file_exists($fp.$fn) && (filesize($fp.$fn) > $max_display_size)) {
591 666
			print(gettext("Packet capture file is too large to display in the GUI.") .
592 667
			    "\n" .
593
			    gettext("Download the file, or view it in the console or ssh shell."));
594
		} elseif (!file_exists($fp.$fn)) {
595
			print(gettext("No capture file to display."));
596
		} elseif ($detail == 'none') {
668
				gettext("Download the file, or view it in the console or ssh shell.") .
669
				"\n" .
670
				gettext("Capture file: {$fp}{$fn}"));
671
		} elseif (!file_exists($fp.$fn) || (filesize($fp.$fn) === 0)) {
672
			print(gettext("No capture results to display."));
673
		} elseif ($detail === 'none') {
597 674
			print(gettext("Select a detail level to view the contents of the packet capture."));
598 675
		} else {
599 676
			system("/usr/sbin/tcpdump {$disabledns} {$detail_args} {$iscarp} -r {$fp}{$fn}");
......
605 682
	</div>
606 683
</div>
607 684
<?php
608
	}
609
endif;
685
}
610 686

  
611 687
include("foot.inc");
(1-1/7)