Project

General

Profile

Feature #13411 ยป 838_2.6.0.patch

Marcos M, 08/16/2022 03:49 PM

View differences:

src/usr/local/www/diag_packet_capture.php
28 28
##|*MATCH=diag_packet_capture.php*
29 29
##|-PRIV
30 30

  
31
$allowautocomplete = true;
31
require_once('util.inc');
32
require_once('interfaces_fast.inc');
33
require_once('guiconfig.inc');
34
require_once('pfsense-utils.inc');
35

  
36
/* Packet capture filter section */
37
define('PCAP_FSECTION_UNTAGGED',0);
38
define('PCAP_FSECTION_SINGLETAGGED',1);
39
define('PCAP_FSECTION_DOUBLETAGGED',2);
40

  
41
/* Packet capture attribute match options for filter sections and attributes; also used in js code */
42
define('PCAP_AMATCH_NONE',10);
43
define('PCAP_AMATCH_NONEOF',11);
44
define('PCAP_AMATCH_AND_ANYOF',12);
45
define('PCAP_AMATCH_OR_ANYOF',13);
46
// simple filter match options
47
define('PCAP_FMATCH_ANY',18);
48
define('PCAP_FMATCH_UNTAGGED',19);
49
define('PCAP_FMATCH_TAGGED',20);
50
define('PCAP_FMATCH_CUSTOM',21);
51

  
52
/* Packet capture filter attribute types */
53
define('PCAP_ATYPE_MATCH',50);
54
define('PCAP_ATYPE_VLAN',51);
55
define('PCAP_ATYPE_ETHERTYPE',52);
56
define('PCAP_ATYPE_PROTOCOL',53);
57
define('PCAP_ATYPE_IPADDRESS',54);
58
define('PCAP_ATYPE_MACADDRESS',55);
59
define('PCAP_ATYPE_PORT',56);
60
define('PCAP_ATYPE_SIMPLE',57);
61

  
62
/**
63
 * Represents a tcpdump filter attribute type; used in the generation of a pcap expression string.
64
 */
65
class FilterAttribute {
66
	public int $type = 0;
67
	private int $section = 0;
68
	private int $operator = 0;
69
	private string $values = '';
70
	private bool $exclude = false;
71
	private bool $required = false;
72

  
73
	/**
74
	 * @param int $section Which filter section this attribute belongs to; untagged, single-tagged, or double-tagged
75
	 * @param int $operator The match operator to use when combining multiple values from the given attribute input
76
	 * @param int $type The input attribute type
77
	 * 
78
	 * @throws Exception If it's an invalid type and operator combination
79
	 * @return FilterAttribute Filter attribute object
80
	 */
81
	public function __construct(int $filter_section, int $attribute_operator, int $attribute_type) {
82
		/* Validate and set attribute type */
83
		if (in_array($attribute_type, array(PCAP_ATYPE_MATCH, PCAP_ATYPE_VLAN, PCAP_ATYPE_ETHERTYPE, PCAP_ATYPE_PROTOCOL, PCAP_ATYPE_IPADDRESS, PCAP_ATYPE_MACADDRESS, PCAP_ATYPE_PORT, PCAP_ATYPE_SIMPLE))) {
84
			$this->type = $attribute_type;
85
		} else {
86
			throw new Exception("Invalid attribute type {$filter_section}:{$attribute_operator}:{$attribute_type}");
87
		}
32 88

  
33
function fixup_logic($value) {
34
	return str_replace(array(" ", ",", "+", "|", "!"), array("", "and ", "and ", "or ", "not "), $value);
35
}
89
		/* Validate and set attribute's filter section */
90
		if (in_array($filter_section, array(PCAP_FSECTION_UNTAGGED, PCAP_FSECTION_SINGLETAGGED, PCAP_FSECTION_DOUBLETAGGED))) {
91
			$this->section = $filter_section;
92
		} else {
93
			throw new Exception("Invalid filter section {$filter_section}:{$attribute_operator}:{$attribute_type}");
94
		}
36 95

  
37
function strip_logic($value) {
38
	return str_replace(array(" ", ",", "+", "|", "!"), array("", "", "", "", ""), $value);
39
}
96
		/* Validate and set the match operator */
97
		$attribute_operator = strtolower($attribute_operator);
98
		if ($attribute_type == PCAP_ATYPE_SIMPLE) {
99
			if ($filter_section == PCAP_FSECTION_UNTAGGED && !in_array($attribute_operator, array(PCAP_FMATCH_UNTAGGED, PCAP_FMATCH_TAGGED, PCAP_FMATCH_ANY, PCAP_FMATCH_CUSTOM))) {
100
				throw new Exception("Invalid match operator for simple expression {$filter_section}:{$attribute_operator}:{$attribute_type}");
101
			}
102
		} elseif ($attribute_type == PCAP_ATYPE_MATCH) {
103
			if (in_array($attribute_operator, array(PCAP_AMATCH_OR_ANYOF, PCAP_AMATCH_NONE, PCAP_AMATCH_AND_ANYOF, PCAP_AMATCH_NONEOF))) {
104
				if ($attribute_operator == PCAP_AMATCH_NONEOF) {
105
					$this->exclude = true;
106
				} elseif ($filter_section == PCAP_FSECTION_UNTAGGED && !in_array($attribute_operator, (array(PCAP_AMATCH_NONE, PCAP_AMATCH_OR_ANYOF)))) {
107
					throw new Exception("Invalid untagged match operator and offset combination {$filter_section}:{$attribute_operator}:{$attribute_type}");
108
				}
109
			} else {
110
				throw new Exception("Invalid match operator for filter {$filter_section}:{$attribute_operator}:{$attribute_type}");
111
			}
112
		} else {
113
			switch ($attribute_operator) {
114
				case PCAP_AMATCH_NONEOF:
115
					// Exclude this attribute's input from capture
116
					$this->required = true;
117
					$this->exclude = true;
118
					break;
119
				case PCAP_AMATCH_AND_ANYOF:
120
					// Explicitly include this attribute's input in capture
121
					$this->required = true;
122
					break;
123
				case PCAP_AMATCH_OR_ANYOF:
124
					// Optionally include this attribute's input in capture
125
					$this->required = false;
126
					break;
127
				case PCAP_AMATCH_NONE:
128
					// Exclude everything for this section
129
					$this->exclude = false;
130
					break;
131
				default:
132
					throw new Exception("Invalid match operator {$filter_section}:{$attribute_operator}:{$attribute_type}");
133
					break;
134
			}
135
		}
136
		$this->operator = $attribute_operator;
137
	}
40 138

  
41
function get_boolean($value, $entry) {
42
	$value = str_replace(array("!", $entry), array("", ""), $value);
43
	$andor = "";
44
	switch (trim($value)) {
45
		case "|":
46
			$andor = "or ";
47
			break;
48
		case ",":
49
		case "+":
50
			$andor = "and ";
51
			break;
139
	public function setInput(string $values) {
140
		$this->values = $values;
52 141
	}
53 142

  
54
	return $andor;
55
}
143
	public function getMatchOffset() {
144
		return $this->section;
145
	}
56 146

  
57
function has_not($value) {
58
	return strpos($value, '!') !== false;
59
}
147
	public function getMatch() {
148
		return $this->operator;
149
	}
60 150

  
61
function fixup_not($value) {
62
	return str_replace("!", "not ", $value);
63
}
151
	public function getInput() {
152
		return $this->values;
153
	}
64 154

  
65
function strip_not($value) {
66
	return ltrim(trim($value), '!');
67
}
155
	public function getIsExcluded() {
156
		return $this->exclude;
157
	}
68 158

  
69
function fixup_hostport($value, $position, $type) {
70
	$item = strip_logic($value);
71
	$not = has_not($value) ? "not " : "";
72
	$andor = ($position > 0) ? get_boolean($value, $item) : "";
73
	if ($type == 'port') {
74
		return "{$andor}port {$not}" . $item;
75
	} elseif (is_ipaddr($item)) {
76
		return "{$andor}host {$not}" . $item;
77
	} elseif (is_subnet($item)) {
78
		return "{$andor}net {$not}" . $item;
79
	} elseif (is_macaddr($item, false)) {
80
		return "{$andor}ether host {$not}" . $item;
81
	} elseif (is_macaddr($item, true)) {
82
		/* Try to match a partial MAC address. tcpdump only allows
83
		 * matching 1, 2, or 4 byte chunks so enforce that limit
84
		 */
85
		$searchmac = "0x";
86
		$partcount = 0;
87
		/* is_macaddr will fail a partial match that has empty sections
88
		 * but sections may only have one digit (leading 0) so add a
89
		 * left 0 pad.
90
		 */
91
		foreach (explode(':', $item) as $mp) {
92
			$searchmac .= str_pad($mp, 2, "0", STR_PAD_LEFT);
93
			$partcount++;
94
		}
95
		if (!in_array($partcount, array(1, 2, 4))) {
96
			return "";
97
		}
98
		$eq = has_not($value) ? "!=" : "==";
99
		// ether[0:2] == 0x0090 or ether[6:2] == 0x0090
100
		return "{$andor} ( ether[0:{$partcount}] {$eq} {$searchmac} or ether[6:{$partcount}] {$eq} {$searchmac} )";
101
	} else {
102
		return "";
159
	public function getIsRequired() {
160
		return $this->required;
103 161
	}
104 162
}
105 163

  
106
function precheck_hostport($item_array) {
107
	$item_string = str_replace(array(" ", "|", ","), array("", "#|", "#+"), $item_array);
164
/**
165
 * Constructs tcpdump filter string based on a specific filter attribute type.
166
 * 
167
 * @param FilterAttribute $fa Filter attribute object
168
 * 
169
 * @throws Exception If there's invalid input
170
 * @return string Attribute filter string
171
 */
172
function get_filter_attribute_string(FilterAttribute $fa) {
173
	if ($fa->getMatch() == PCAP_AMATCH_NONE) {
174
		throw new Exception("Cannot generate a filter attribute string when the match operator is set to none.");
175
	}
176

  
177
	/* Return an empty string if the input is empty; '0' is a valid attribute input string */
178
	if (empty($fa->getInput()) && $fa->getInput() != '0') {
179
		return '';
180
	}
108 181

  
109
	if (strpos($item_string, '#') === false) {
110
		$items = array($item_array);
182
	/* Set the attribute value's match operator */
183
	$fa_string = '';
184
	if ($fa->getIsExcluded()) {
185
		$prefix = 'not ';
186
		$infix = ' and not ';
111 187
	} else {
112
		$items = explode('#', $item_string);
188
		$prefix = '';
189
		$infix = ' or ';
113 190
	}
114
	return $items;
115
}
116 191

  
117
function hostport_array_fixer($items,$type) {
118
	$itemmatch = "";
119
	$itemcount = 0;
192
	/* Loop through this attribute's space-separated input string */
193
	$items = array();
194
	$value = '';
195
	$input_error = 'Invalid input.';
196
	switch ($fa->type) {
197
		case PCAP_ATYPE_VLAN:
198
			// Construct string parts for the VLAN tag attribute
199
			if ($fa->getMatchOffset() == PCAP_FSECTION_SINGLETAGGED) {
200
				$vlan_offset = 'ether[14:2]';
201
			} else {
202
				$vlan_offset = 'ether[18:2]';
203
			}
204
			foreach (preg_split('/\s+/', $fa->getInput(), -1, PREG_SPLIT_NO_EMPTY) as $input) {
205
				// Validate input values
206
				$vlan_tag = intval($input, 10);
207
				if ($vlan_tag >= 0 && $vlan_tag <= 4095) {
208
					$value = sprintf('%1$s==%2$s', $vlan_offset, $vlan_tag);
209
				} else {
210
					$input_error = sprintf('Invalid VLAN tag %s', $input);
211
					unset($items);
212
					break;
213
				}
214
				if (!empty($value)) {
215
					$items[] = $value;
216
				}
217
			}
218
			break;
219
		case PCAP_ATYPE_ETHERTYPE:
220
			// Construct string parts for the ethertype attribute
221
			foreach (preg_split('/\s+/', $fa->getInput(), -1, PREG_SPLIT_NO_EMPTY) as $input) {
222
				// Validate input values
223
				switch ($input) {
224
					case 'ipv4':
225
						$value = 'ip';
226
						break;
227
					case 'ipv6';
228
						$value = 'ip6';
229
						break;
230
					case 'arp';
231
						$value = 'arp';
232
						break;
233
					default:
234
						if (preg_match('/^[0-9a-f]{4}$/i', $input)) {
235
							$value = sprintf('0x%s', strtolower($input));
236
						} elseif (preg_match('/^0[x][0-9a-f]{4}$/i', $input)) {
237
							$value = strtolower($input);
238
						} else {
239
							$input_error = sprintf('Invalid ethertype %s', $input);
240
							unset($items);
241
							break 2;
242
						}
243

  
244
						if ($value != '0x8100' || $value != '0x88a8') {
245
							$value = sprintf('ether proto %s', $value);
246
						} else {
247
							$input_error = sprintf('Matching for VLAN must be done with filter match selection %s', $input);
248
							unset($items);
249
							break 2;
250
						}
251
						break;
252
				}
253
				if (!empty($value)) {
254
					$items[] = $value;
255
				}
256
			}
257
			break;
258
		case PCAP_ATYPE_PROTOCOL:
259
			// Construct string parts for the protocol attribute
260
			foreach (preg_split('/\s+/', $fa->getInput(), -1, PREG_SPLIT_NO_EMPTY) as $input) {
261
				// Validate input values
262
				switch ($input) {
263
					case 'icmp':
264
						$value = 'icmp';
265
						break;
266
					case 'icmp6';
267
						$value = 'icmp6';
268
						break;
269
					case 'tcp';
270
						$value = 'tcp';
271
						break;
272
					case 'udp';
273
						$value = 'udp';
274
						break;
275
					case 'ipsec';
276
						$value = '(esp or (udp port 4500 and udp[8:4]!=0))';
277
						break;
278
					case 'carp';
279
						$value = 'proto 112';
280
						break;
281
					case 'pfsync';
282
						$value = 'proto pfsync';
283
						break;
284
					case 'ospf';
285
						$value = 'proto ospf';
286
						break;
287
					default:
288
						if (preg_match('/\d{1,3}/', $input) && ($input >= 0 && $input <= 255)) {
289
							$value = sprintf('proto %s', $input);
290
						} else {
291
							$input_error = sprintf('Invalid protocol %s', $input);
292
							unset($items);
293
							break 2;
294
						}
295
						break;
296
				}
297
				if (!empty($value)) {
298
					$items[] = $value;
299
				}
300
			}
301
			break;
302
		case PCAP_ATYPE_IPADDRESS:
303
			// Construct string parts for the host IP address/subnet attribute
304
			foreach (preg_split('/\s+/', $fa->getInput(), -1, PREG_SPLIT_NO_EMPTY) as $input) {
305
				if (is_ipaddr($input)) {
306
					// Validate IPv4/IPv6 address input
307
					$value = sprintf('host %s', $input);
308
				} elseif (is_subnet($input)) {
309
					// Validate IPv4/IPv6 subnet input
310
					$subnet_parts = explode('/', $input);
311
					$subnet_cidr = intval($subnet_parts[array_key_last($subnet_parts)], 10);
312
					if ($subnet_cidr >= 0 && $subnet_cidr <= 128) {
313
						$subnet_address = gen_subnet($subnet_parts[array_key_first($subnet_parts)], $subnet_cidr);
314
						$value = sprintf('net %1$s/%2$s', $subnet_address, $subnet_cidr);
315
					} else {
316
						$input_error = sprintf('Invalid subnet %s', $input);
317
						unset($items);
318
						break;
319
					}
320
				} else {
321
					$input_error = sprintf('Invalid IP address or subnet %s', $input);
322
					unset($items);
323
					break;
324
				}
325
				if (!empty($value)) {
326
					$items[] = $value;
327
				}
328
			}
329
			break;
330
		case PCAP_ATYPE_MACADDRESS:
331
			// Construct string parts for the MAC address attribute
332
			foreach (preg_split('/\s+/', $fa->getInput(), -1, PREG_SPLIT_NO_EMPTY) as $input) {
333
				$mac_parts = array();
334
				$mac_parts_count = 0;
335
				// Pad MAC parts with 0 if needed to construct a valid partial match string
336
				foreach (explode(':', $input) as $macpart) {
337
					$mac_parts[] = str_pad($macpart, 2, '0', STR_PAD_LEFT);
338
				}
120 339

  
121
	foreach ($items as $i) {
122
		if ($type == 'port') {
123
			$i = fixup_hostport($i, $itemcount++,'port');
124
		} else {
125
			$i = fixup_hostport($i, $itemcount++,'host');
126
		}
340
				$mac_parts_count = count($mac_parts);
341
				if (in_array($mac_parts_count, array(1, 2, 4))) {
342
					// Validate partial MAC address. tcpdump will only accept 1, 2, or 4 byte segments
343
					$mac_string = implode($mac_parts);
344
					if (is_macaddr($input, true)) {
345
						$value = sprintf('ether[0:%1$s]==0x%2$s%3$sether[6:%1$s]==0x%2$s', $mac_parts_count, $mac_string, $infix);
346
					} else {
347
						$input_error = sprintf('Invalid partial MAC address %s', $input);
348
						unset($items);
349
						break;
350
					}
351
				} elseif ($mac_parts_count == 6) {
352
					// Validate full MAC address
353
					$mac_string = implode(':', $mac_parts);
354
					if (is_macaddr($input, false)) {
355
						$value = sprintf('ether host %s', $mac_string);
356
					} else {
357
						$input_error = sprintf('Invalid MAC address %s', $input);
358
						unset($items);
359
						break;
360
					}
361
				} else {
362
					$input_error = sprintf('Invalid MAC address length; can only match 1, 2, 4, or 6 segments %s', $input);
363
					unset($items);
364
					break;
365
				}
366
				if (!empty($value)) {
367
					$items[] = $value;
368
				}
369
			}    
370
			break;
371
		case PCAP_ATYPE_PORT:
372
			// Construct string parts for the port attribute
373
			foreach (preg_split('/\s+/', $fa->getInput(), -1, PREG_SPLIT_NO_EMPTY) as $input) {
374
				if (is_port($input)) {
375
					// Validate input values
376
					$value = sprintf('port %s', $input);
377
				} else {
378
					$input_error = sprintf('Invalid port %s', $input);
379
					unset($items);
380
					break;
381
				}
382
				// Save the attribute filter string
383
				if (!empty($value)) {
384
					$items[] = $value;
385
				}
386
			}    
387
			break;
388
		default:
389
			break;
390
	}
127 391

  
128
		if (!empty($i)) {
129
			$itemmatch .= " " . $i;
392
	if (empty($items)) {
393
		throw new Exception("{$input_error}");
394
	} else {
395
		/* Construct the filter string for this attribute */
396
		foreach ($items as $key => $value) {
397
			if ($key == array_key_first($items)) {
398
				$fa_string = sprintf('%1$s%2$s', $prefix, $value);
399
			} else {
400
				$fa_string .= sprintf('%1$s%2$s', $infix, $value);
401
			}
130 402
		}
131
	}
132 403

  
133
	if (!empty($itemmatch)) {
134
		return "({$itemmatch})";
404
		/* Return the attribute filter string */
405
		return $fa_string;
135 406
	}
136 407
}
137 408

  
138
if ($_POST['downloadbtn'] == gettext("Download Capture")) {
139
	$nocsrf = true;
140
}
141

  
142
$pgtitle = array(gettext("Diagnostics"), gettext("Packet Capture"));
143
require_once("guiconfig.inc");
144
require_once("pfsense-utils.inc");
145
require_once("ipsec.inc");
146

  
147
$fp = "/root/";
148
$fn = "packetcapture.cap";
149
$fns = "packetcapture.start";
150
$snaplen = 0;//default packet length
151
$count = 100;//default number of packets to capture
152
$max_display_size = 50*1024*1024; // 50MB limit on GUI capture display. See https://redmine.pfsense.org/issues/9239
153

  
154
$fams = array('ip', 'ip6');
155
$protos = array('icmp', 'icmp6', 'tcp', 'udp', 'arp', 'carp', 'esp', 'pfsync', 'ospf',
156
		        '!icmp', '!icmp6', '!tcp', '!udp', '!arp', '!carp', '!esp', '!pfsync', '!ospf');
409
/**
410
 * Constructs a tcpdump expression string based on untagged, outer-tag, and inner-tag (aka section) filters.
411
 * 
412
 * @param array $filterattributes array of FilterAttribute objects
413
 * 
414
 * @throws Exception Thrown by get_filter_attribute_string()
415
 * @return string Expression string; can be empty
416
 */
417
function get_filter_expression_string(array $filterattributes) {
418
	$expression_string = '';
419
	$fs_simplefilter = '';
420
	$fs_attribute = '';
421
	// The index of the following arrays relate to the filter section
422
	$fs_section_include = array(true, true, true);
423
	$fs_section_start = array(true, true, true);
424
	$fs_section_strings = array('', '', '');
425

  
426
	/* Generate each section's filter string */
427
	foreach ($filterattributes as $fa) {
428
		if ($fa->type == PCAP_ATYPE_SIMPLE && $fa->getMatch() != PCAP_FMATCH_CUSTOM) {
429
			// An expression string are not needed when using a simple filter, hence skip everything
430
			$fs_simplefilter = $fa->getMatch();
431
			break;
432
		}
433
		if ($fa->type == PCAP_ATYPE_MATCH && $fa->getMatch() == PCAP_AMATCH_NONE) {
434
			// Attribute filter strings are not needed when the respective session is exlcuded
435
			$fs_section_include[$fa->getMatchOffset()] = false;
436
			continue;
437
		} elseif (empty($fa->getInput())) {
438
			// Attribute filter strings are not needed when the attribute's input is empty
439
			continue;
440
		}
157 441

  
158
$input_errors = array();
442
		try {
443
			// Generate this attribute's filter string
444
			$fs_attribute = get_filter_attribute_string($fa);
445
		} catch (Exception $e) {
446
			throw new Exception($e->getMessage());
447
		}
159 448

  
160
$interfaces = get_configured_interface_with_descr();
161
if (ipsec_enabled()) {
162
	$interfaces['enc0'] = "IPsec";
163
}
164
$interfaces['lo0'] = "Localhost";
449
		if ($fa->getIsRequired() || $fa->type == PCAP_ATYPE_VLAN) {
450
			$fs_attributes_operator = 'and';
451
		} else {
452
			$fs_attributes_operator = 'or';
453
		}
165 454

  
166
foreach (array('server' => gettext('OpenVPN Server'), 'client' => gettext('OpenVPN Client')) as $mode => $mode_descr) {
167
	if (is_array($config['openvpn']["openvpn-{$mode}"])) {
168
		foreach ($config['openvpn']["openvpn-{$mode}"] as $id => $setting) {
169
			if (!isset($setting['disable'])) {
170
				$interfaces['ovpn' . substr($mode, 0, 1) . $setting['vpnid']] = $mode_descr . ": ".htmlspecialchars($setting['description']);
171
			}
455
		if ($fs_section_start[$fa->getMatchOffset()]) {
456
			// Avoid adding an attribute operator at the beginning of the attribute's filter string
457
			$fs_section_start[$fa->getMatchOffset()] = false;
458
			$fs_section_strings[$fa->getMatchOffset()] = sprintf('(%s)', $fs_attribute);
459
		} else {
460
			$fs_section_strings[$fa->getMatchOffset()] .= sprintf(' %1$s (%2$s)', $fs_attributes_operator, $fs_attribute);
172 461
		}
173 462
	}
174
}
175 463

  
176
$interfaces = array_merge($interfaces, interface_ipsec_vti_list_all());
464
	/* Use a simple expression string if possible */
465
	if ($fs_simplefilter == PCAP_FMATCH_ANY) {
466
		$expression_string = '';
467
	} elseif ($fs_simplefilter == PCAP_FMATCH_UNTAGGED) {
468
		$expression_string = 'not vlan';
469
	} elseif ($fs_simplefilter == PCAP_FMATCH_TAGGED) {
470
		$expression_string = 'vlan';
471
	} else {
472
		// Combine all section filter strings
473
		if (!$fs_section_include[PCAP_FSECTION_UNTAGGED]) {
474
			$s0 = '';
475
		} elseif (!empty($fs_section_strings[PCAP_FSECTION_UNTAGGED])) {
476
			$s0 = sprintf('(%s)', $fs_section_strings[PCAP_FSECTION_UNTAGGED]);
477
		} else {
478
			$s0 = '(not ether proto 0x8100 and not ether proto 0x88a8)';
479
		}
177 480

  
178
if ($_POST) {
179
	$host = $_POST['host'];
180
	$selectedif = $_POST['interface'];
181
	$promiscuous = isset($_POST['promiscuous']);
182
	$count = $_POST['count'];
183
	$snaplen = $_POST['snaplen'];
184
	$port = $_POST['port'];
185
	$detail = $_POST['detail'];
186
	$fam = $_POST['fam'];
187
	$proto = $_POST['proto'];
188

  
189
	if (!array_key_exists($selectedif, $interfaces)) {
190
		$input_errors[] = gettext("Invalid interface.");
191
	}
481
		if (!$fs_section_include[PCAP_FSECTION_SINGLETAGGED]) {
482
			$s1 = 'not vlan';
483
		} elseif (!empty($fs_section_strings[PCAP_FSECTION_SINGLETAGGED])) {
484
			$s1 = sprintf('(vlan and %s)', $fs_section_strings[PCAP_FSECTION_SINGLETAGGED]);
485
		} else {
486
			$s1 = '(vlan)';
487
		}
192 488

  
193
	if ($fam !== "" && $fam !== "ip" && $fam !== "ip6") {
194
		$input_errors[] = gettext("Invalid address family.");
195
	}
489
		if (!$fs_section_include[PCAP_FSECTION_DOUBLETAGGED]) {
490
			$s2 = 'not vlan';
491
		} elseif (!empty($fs_section_strings[PCAP_FSECTION_DOUBLETAGGED])) {
492
			$s2 = sprintf('(vlan and %s)', $fs_section_strings[PCAP_FSECTION_DOUBLETAGGED]);
493
		} else {
494
			$s2 = '(vlan)';
495
		}
196 496

  
197
	if ($fam !== "" && $proto !== "") {
198
		if ($fam == "ip" && $proto == "icmp6") {
199
			$input_errors[] = gettext("IPv4 with ICMPv6 is not valid.");
497
		if (empty($s0)) {
498
			$o1 = '';
499
		} elseif ($s1 == 'not vlan') {
500
			$o1 = ' and ';
501
		} else {
502
			$o1 = ' or ';
200 503
		}
201
		if ($fam == "ip6" && $proto == "icmp") {
202
			$input_errors[] = gettext("IPv6 with ICMP is not valid.");
504

  
505
		if ($s1 == 'not vlan') {
506
			$o2 = '';
507
		} elseif ($s2 == 'not vlan') {
508
			$o2 = ' and ';
509
		} else {
510
			$o2 = ' or ';
203 511
		}
204
		if ($proto =="arp") {
205
			$input_errors[] = gettext("Selecting an Address Family for ARP is not valid.");
512

  
513
		if (empty($o1) && $s1 == 'not vlan' || ($s0 == '(not ether proto 0x8100 and not ether proto 0x88a8)' && $s1 == '(vlan)' && $o2 == ' or ' && $s2 == '(vlan)')) {
514
			// Invalid selection, or custom filter is the same as "Everything" filter
515
			$expression_string = '';
516
		} elseif (empty($o2) || ($s1 == $s2 && $o2 == ' or ')) {
517
			$expression_string = sprintf('%1$s%2$s%3$s', $s0, $o1, $s1);
518
		} else {
519
			$expression_string = sprintf('%1$s%2$s%3$s%4$s%5$s', $s0, $o1, $s1, $o2, $s2);
206 520
		}
207 521
	}
208 522

  
209
	if ($proto !== "" && !in_array(strip_not($proto), $protos)) {
210
		$input_errors[] = gettext("Invalid protocol.");
211
	}
523
	return $expression_string;
524
}
212 525

  
213
	if ($host != "") {
214
		$hosts = precheck_hostport($host);
215
		foreach ($hosts as $h) {
216
			$h = strip_logic($h);
217
			if (!is_subnet($h) && !is_ipaddr($h) && !is_macaddr($h, true)) {
218
				$input_errors[] = sprintf(gettext("A valid IP address, CIDR block, or MAC address must be specified. [%s]"), $h);
219
			}
220
			/* Check length of partial MAC */
221
			if (!is_macaddr($h, false) && is_macaddr($h, true)) {
222
				$mac_parts = explode(':', $h);
223
				if (!in_array(count($mac_parts), array(1, 2, 4))) {
224
					$input_errors[] = gettext("Partial MAC addresses can only be matched using 1, 2, or 4 MAC segments (bytes).");
225
				}
526
/**
527
 * Constructs an ordered list of all (including unassigned) interfaces and their descriptions.
528
 * 
529
 * @return array Interface List ordered by config name and keyed by port name with description as the value
530
 */
531
function get_interfaces_sorted() {
532
	// Get all interfaces and their descriptions
533
	$i_ports = get_interface_arr();
534
	$i_names = convert_real_interface_to_friendly_interface_name_fast();
535
	$i_descriptions = get_configured_interface_with_descr();
536

  
537
	/* Group interfaces and their descriptions in a consistent order */
538
	$i_list_assigned = $i_names;
539
	$i_list_assigned_append = $i_names;
540
	$i_list_unassigned = array();
541
	$i_list_unassigned_append = array();
542
	foreach ($i_ports as $i) {
543
		$append = false;
544
		$assigned = false;
545
		$description = sprintf('unassigned (%s)', $i);
546
		if (in_array($i, array('pfsync0', 'pflog0'))) {
547
			// Ignore these interfaces
548
			continue;
549
		} elseif (preg_match('/(enc0|lo0|ovpn[sc]\d+|tun_wg\d+|ipsec\d+)/', $i)) {
550
			$append = true;
551
			if ($i == 'enc0') {
552
				$assigned = true;
553
				$description = 'IPsec (enc0)';
554
			} elseif ($i == 'lo0') {
555
				$assigned = true;
556
				$description = 'Loopback (lo0)';
226 557
			}
227 558
		}
228
	}
229 559

  
230
	if ($port != "") {
231
		$ports = precheck_hostport($port);
232
		foreach ($ports as $p) {
233
			$p = strip_logic($p);
234
			if (!is_port(strip_not($p))) {
235
				$input_errors[] = gettext("Invalid value specified for port.");
560
		// Set the interface description
561
		if (!$assigned && array_key_exists($i, $i_names)) {
562
			if (array_key_exists($i_names[$i], $i_descriptions)) {
563
				$assigned = true;
564
				$description = sprintf('%1$s (%2$s)', $i_descriptions[$i_names[$i]], $i);
236 565
			}
237 566
		}
238
	}
239 567

  
240
	if ($snaplen == "") {
241
		$snaplen = 0;
242
	} else {
243
		if (!is_numeric($snaplen) || $snaplen < 0) {
244
			$input_errors[] = gettext("Invalid value specified for packet length.");
568
		// Save the interface to the respective group
569
		if ($append) {
570
			if ($assigned) {
571
				$i_list_assigned_append[$i] = $description;
572
				unset($i_list_assigned[$i]);
573
			} else {
574
				$i_list_unassigned_append[$i] = $description;
575
			}
576
		} else {
577
			if ($assigned) {
578
				$i_list_assigned[$i] = $description;
579
				unset($i_list_assigned_append[$i]);
580
			} else {
581
				$i_list_unassigned[$i] = $description;
582
			}
245 583
		}
246 584
	}
247 585

  
248
	if ($count == "") {
249
		$count = 0;
250
	} else {
251
		if (!is_numeric($count) || $count < 0) {
252
			$input_errors[] = gettext("Invalid value specified for packet count.");
253
		}
254
	}
586
	/* return ordered interface list */
587
	return array_merge($i_list_assigned, $i_list_assigned_append, $i_list_unassigned, $i_list_unassigned_append);
588
}
255 589

  
256
	if (!count($input_errors)) {
257
		$do_tcpdump = true;
590
/* Page properties */
591
$allowautocomplete = true;
592
if ($_POST['downloadbtn'] != '') {
593
	$nocsrf = true;
594
}
595
$pgtitle = array(gettext('Diagnostics'), gettext('Packet Capture'));
596

  
597
/* Page variables */
598
$available_interfaces = get_interfaces_sorted();
599
$max_view_size = 50*1024*1024; // Limit viewing capture files to 50MB. See https://redmine.pfsense.org/issues/9239
600
$pcap_files_root = $g['tmp_path'];
601
$pcap_files_list = array();
602
foreach (array_filter(glob("{$pcap_files_root}/packetcapture-*.pcap"),'is_file') as $file) {
603
	if (preg_match('/.*-\d{14}\.pcap/', $file)) {
604
		// Include the file in the array with the start time as the key
605
		$pcap_files_list[strtotime(substr($file, -19, 14))] = $file;
606
	}
607
}
608
ksort($pcap_files_list, SORT_NUMERIC);
609
$pcap_file_last = empty($pcap_files_list) ? null : array_reverse($pcap_files_list)[0]; // example: packetcapture-WAN_1-20220701000101.pcap
610
$run_capture = false;
611
$expression_string = '';
612
$input_errors = '';
258 613

  
614
/* Actions taken on form button click */
615
if ($_POST) {
616
	if ($_POST['startbtn'] != '') {
617
		$action = 'start';
618
		$run_capture = true;
619
	} elseif ($_POST['stopbtn'] != '') {
620
		$action = 'stop';
621
	} elseif ($_POST['viewbtn'] != '') {
622
		$action = 'view';
623
	} elseif ($_POST['downloadbtn'] != '') {
624
		$action = 'download';
625
	} elseif ($_POST['clearbtn'] != '') {
626
		$action = 'clear';
627
	}
259 628

  
260
		if ($_POST['promiscuous']) {
261
			//if promiscuous mode is checked
262
			$disablepromiscuous = "";
263
		} else {
264
			//if promiscuous mode is unchecked
265
			$disablepromiscuous = "-p";
629
	try {
630
		/* Save previous input to use on page load after submission */
631
		// capture options
632
		$input_interface = $_POST['interface'];
633
		if (!array_key_exists($input_interface, $available_interfaces)) {
634
			throw new Exception('No valid interface selected.');
266 635
		}
267

  
268
		if ($_POST['dnsquery']) {
269
			//if dns lookup is checked
270
			$disabledns = "";
636
		$input_filter = $_POST['filter'];
637
		if ($_POST['count'] == '0') {
638
			$input_count = 0;
271 639
		} else {
272
			//if dns lookup is unchecked
273
			$disabledns = "-n";
640
			$input_count = empty($_POST['count']) ? 1000 : $_POST['count'];
274 641
		}
642
		$input_length = empty($_POST['length']) ? 0 : $_POST['length'];
643
		$input_promiscuous = empty($_POST['promiscuous']) ? false : $_POST['promiscuous'];
644
		// view options
645
		$input_detail = empty($_POST['detail']) ? 'normal' : $_POST['detail'];
646
		$input_lookup = empty($_POST['lookup']) ? false : $_POST['lookup'];
647

  
648
		// filter options
649
		$filterattributes = array();
650
		$filterattributes[] = new FilterAttribute(0, $input_filter, PCAP_ATYPE_SIMPLE);
651
		if ($input_filter == PCAP_FMATCH_CUSTOM) {
652
			foreach ($_POST as $key => $value) {
653
				unset($fa_offset);
654
				if (preg_match('/^untagged_.*_match$/', $key)) {
655
					$fa_offset = PCAP_FSECTION_UNTAGGED;
656
					$fa_name_type = substr_replace(substr($key, 9), '', -6);
657
				} elseif (preg_match('/^(singletagged)_.*_match$/', $key)) {
658
					$fa_offset = PCAP_FSECTION_SINGLETAGGED;
659
					$fa_name_type = substr_replace(substr($key, 13), '', -6);
660
				} elseif (preg_match('/^(doubletagged)_.*_match$/', $key)) {
661
					$fa_offset = PCAP_FSECTION_DOUBLETAGGED;
662
					$fa_name_type = substr_replace(substr($key, 13), '', -6);
663
				} else {
664
					continue;
665
				}
275 666

  
276
		if ($_POST['startbtn'] != "") {
277
			$action = gettext("Start");
278

  
279
			//delete previous packet capture if it exists
280
			if (file_exists($fp.$fn)) {
281
				unlink ($fp.$fn);
667
				if (isset($fa_offset)) {
668
					$fa_name = substr_replace($key, '', -6); // untagged_ethertype, singletagged_ethertype, doubletagged_ethertype
669

  
670
					switch ($fa_name_type) {
671
						case 'ethertype':
672
							$fa_type = PCAP_ATYPE_ETHERTYPE;
673
							break;
674
						case 'protocol':
675
							$fa_type = PCAP_ATYPE_PROTOCOL;
676
							break;
677
						case 'ipaddress':
678
							$fa_type = PCAP_ATYPE_IPADDRESS;
679
							break;
680
						case 'macaddress':
681
							$fa_type = PCAP_ATYPE_MACADDRESS;
682
							break;
683
						case 'port':
684
							$fa_type = PCAP_ATYPE_PORT;
685
							break;
686
						case 'tag':
687
							if ($value == PCAP_AMATCH_NONE) {
688
								$fa_type = PCAP_ATYPE_MATCH;
689
							} else {
690
								$fa_type = PCAP_ATYPE_VLAN;
691
							}
692
							break;
693
						case 'notag':
694
							$fa_type = PCAP_ATYPE_MATCH;
695
							break;
696
						default:
697
							unset($fa_type);
698
							break;
699
					}
700

  
701
					if (isset($fa_type)) {
702
						// Generate variable variables to pre-fill form input based on POST data
703
						${'input_' . $key} = $value;
704
						${'input_' . $fa_name} = $_POST[$fa_name];
705

  
706
						if ($fa_type == PCAP_ATYPE_MATCH) {
707
							// Create untagged section match operator
708
							$filterattributes[] = new FilterAttribute($fa_offset, $value, PCAP_ATYPE_MATCH);
709
						} elseif ($value == PCAP_AMATCH_NONE) {
710
							// If any filter match selection is none, treat it as a section match operator
711
							$filterattributes[] = new FilterAttribute($fa_offset, $value, PCAP_ATYPE_MATCH);
712
						} else {
713
							// Create filter attributes
714
							if (in_array($value, array(PCAP_AMATCH_NONEOF, PCAP_AMATCH_AND_ANYOF, PCAP_AMATCH_OR_ANYOF))) {
715
								// Match selection is the attribute operator
716
								$fa = new FilterAttribute($fa_offset, $value, $fa_type);
717
								$fa->setInput(empty($_POST[$fa_name]) ? '' : $_POST[$fa_name]);
718
							} else {
719
								// Match selection is the attribute input
720
								$fa = new FilterAttribute($fa_offset, PCAP_AMATCH_AND_ANYOF, $fa_type);
721
								$fa->setInput(empty($value) ? '' : $value);
722
							}
723
							$filterattributes[] = $fa;
724
						}
725
					}
726
				}
282 727
			}
728
		}
283 729

  
284
		} elseif ($_POST['stopbtn'] != "") {
285
			$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)'"));
287

  
288
			//explode processes into an array, (delimiter is new line)
289
			$processes_running_array = explode("\n", $processes_running);
290

  
291
			//kill each of the packetcapture processes
292
			foreach ($processes_running_array as $process) {
293
				$process_id_pos = strpos($process, ' ');
294
				$process_id = substr($process, 0, $process_id_pos);
295
				exec("kill $process_id");
296
			}
297
		} elseif ($_POST['downloadbtn'] != "") {
298
			//download file
299
			send_user_download('file', $fp.$fn);
730
		$expression_string = get_filter_expression_string($filterattributes);
731
		if (empty($expression_string) && $input_filter == PCAP_FMATCH_CUSTOM) {
732
			throw new Exception('Custom filter cannot be empty.');
300 733
		}
734
	} catch (Exception $e) {
735
		$run_capture = false;
736
		$input_errors = $e->getMessage();
301 737
	}
302
} else {
303
	$do_tcpdump = false;
304 738
}
305 739

  
306
$excl = gettext("Exclude");
740
/* Header page HTML */
741
include('head.inc');
307 742

  
308
$protocollist = array(
309
	'' => 'Any',
310
	'icmp' => 'ICMP',
311
	'!icmp' => $excl . ' ICMP',
743
/* Show input validation errors; shown betwen navigation breadcrumbs and page contents */
744
if (!empty($input_errors) && $action == 'start') {
745
	print_input_errors(array(gettext($input_errors)));
746
}
747

  
748
/* Form drop-down lists */
749
$form_filters = array(
750
	PCAP_FMATCH_ANY => 'Everything',
751
	PCAP_FMATCH_UNTAGGED => 'Only Untagged',
752
	PCAP_FMATCH_TAGGED => 'Only Tagged',
753
	PCAP_FMATCH_CUSTOM => 'Custom Filter'
754
);
755
$form_detail = array(
756
	'normal' => gettext('Normal'),
757
	'medium' => gettext('Medium'),
758
	'high' => gettext('High'),
759
	'full' => gettext('Full'),
760
);
761
$form_untagged = array(
762
	PCAP_AMATCH_NONE => 'NONE',
763
	PCAP_AMATCH_OR_ANYOF => 'ANY OF'
764
);
765
$form_singletagged = array(
766
	PCAP_AMATCH_NONE => 'NONE',
767
	PCAP_AMATCH_AND_ANYOF => 'ANY OF',
768
	PCAP_AMATCH_NONEOF => 'NONE OF'
769
);
770
$form_doubletagged = array(
771
	PCAP_AMATCH_NONE => 'NONE',
772
	PCAP_AMATCH_AND_ANYOF => 'ANY OF',
773
	PCAP_AMATCH_NONEOF => 'NONE OF'
774
);
775
$form_match_ethertype = array(
776
	PCAP_AMATCH_AND_ANYOF => 'ANY OF',
777
	PCAP_AMATCH_OR_ANYOF => 'ANY OF [OR]',
778
	PCAP_AMATCH_NONEOF => 'NONE OF',
779
	'ipv4' => 'IPv4',
780
	'ipv6' => 'IPv6',
781
	'arp' => 'ARP'
782
);
783
$form_match_protocol = array(
784
	PCAP_AMATCH_AND_ANYOF => 'ANY OF',
785
	PCAP_AMATCH_OR_ANYOF => 'ANY OF [OR]',
786
	PCAP_AMATCH_NONEOF => 'NONE OF',
787
	'icmp' => 'ICMPv4',
312 788
	'icmp6' => 'ICMPv6',
313
	'!icmp6' => $excl . ' ICMPv6',
314 789
	'tcp' => 'TCP',
315
	'!tcp' => $excl . ' TCP',
316 790
	'udp' => 'UDP',
317
	'!udp' => $excl . ' UDP',
318
	'arp' => 'ARP',
319
	'!arp' => $excl . ' ARP',
791
	'ipsec' => 'IPsec',
320 792
	'carp' => 'CARP',
321
	'!carp' => $excl . ' CARP',
322 793
	'pfsync' => 'pfsync',
323
	'!pfsync' => $excl . ' pfsync',
324
	'esp' => 'ESP',
325
	'!esp' => $excl . ' ESP',
326
	'ospf' => 'OSPF',
327
	'!ospf' => $excl . ' OSPF'
794
	'ospf' => 'OSPF'
328 795
);
329

  
330
include("head.inc");
331

  
332
if ($input_errors) {
333
	print_input_errors($input_errors);
334
}
335

  
336
$form = new Form(false); // No button yet. We add those later depending on the required action
337

  
796
$form_match_ipaddress = array(
797
	PCAP_AMATCH_AND_ANYOF => 'ANY OF',
798
	PCAP_AMATCH_OR_ANYOF => 'ANY OF [OR]',
799
	PCAP_AMATCH_NONEOF => 'NONE OF'
800
);
801
$form_match_macaddress = array(
802
	PCAP_AMATCH_AND_ANYOF => 'ANY OF',
803
	PCAP_AMATCH_OR_ANYOF => 'ANY OF [OR]',
804
	PCAP_AMATCH_NONEOF => 'NONE OF'
805
);
806
$form_match_port = array(
807
	PCAP_AMATCH_AND_ANYOF => 'ANY OF',
808
	PCAP_AMATCH_OR_ANYOF => 'ANY OF [OR]',
809
	PCAP_AMATCH_NONEOF => 'NONE OF'
810
);
811
/* pre-filled form input */
812
$input_filter = isset($input_filter) ? $input_filter : PCAP_FMATCH_ANY;
813
$input_promiscuous = isset($input_promiscuous) ? $input_promiscuous : true;
814
$input_detail = isset($input_detail) ? $input_detail : 'normal';
815
$input_lookup = isset($input_lookup) ? $input_lookup : false;
816
$input_untagged_notag_match = isset($input_untagged_notag_match) ? $input_untagged_notag_match : PCAP_AMATCH_OR_ANYOF;
817
$input_doubletagged_tag_match = isset($input_doubletagged_tag_match) ? $input_doubletagged_tag_match : PCAP_AMATCH_NONE;
818

  
819
/* Prepare the form */
820
$form = new Form(false);
338 821
$section = new Form_Section('Packet Capture Options');
339 822

  
340
$section->addInput(new Form_Select(
823
// CAPTURE OPTIONS
824
$group = new Form_Group('Capture Options');
825
$group->add(new Form_Select(
341 826
	'interface',
342
	'*Interface',
343
	$selectedif,
344
	$interfaces
345
))->setHelp('Select the interface on which to capture traffic. ');
346

  
347
$section->addInput(new Form_Checkbox(
827
	null,
828
	$input_interface,
829
	$available_interfaces
830
))->setHelp('Interface to caputer packets on.')->setWidth(4);
831
$group->add(new Form_Select(
832
	'filter',
833
	null,
834
	$input_filter,
835
	$form_filters
836
))->setHelp('Packet capture filter.')->addClass('match-selection')->setWidth(2);
837
$section->add($group);
838
$group = new Form_Group('');
839
$group->add(new Form_Input(
840
	'count',
841
	'Packet Count',
842
	null,
843
	$input_count,
844
	array('type' => 'number', 'min' => 0, 'step' => 1)
845
))->setHelp('Max number of packets to capture (default 1000). Enter 0 (zero) for no limit.')->setWidth(2);
846
$group->add(new Form_Input(
847
	'length',
848
	'Packet Length',
849
	null,
850
	$input_length,
851
	array('type' => 'number', 'min' => 0, 'step' => 1)
852
))->setHelp('Max bytes per packet (default 0). Enter 0 (zero) for no limit.')->setWidth(2);
853
$group->add(new Form_Checkbox(
348 854
	'promiscuous',
349
	'Promiscuous',
350
	'Enable promiscuous mode',
351
	$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' .
355
	'Promiscuous mode requires more kernel processing of packets. This puts a slightly higher demand on system resources, especially ' .
356
	'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
	'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

  
359
	'<p style="margin-bottom:2px;padding-bottom:0px">',
360
	'</p><p style="margin:0px;padding:0px">',
361
	'<a href="https://en.wikipedia.org/wiki/Promiscuous_mode">',
362
	'</a>',
363
	'<span class="infoblock" style="font-size:90%"><br />',
364
	'&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
		'&nbsp;<a href="https://nmap.org/nsedoc/scripts/sniffer-detect.html">[2]</a>',
366
	'&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
);
855
	null,
856
	'Promiscuous Mode',
857
	$input_promiscuous
858
))->setHelp('Capture all traffic seen by the interface. Disable this option to only capture traffic to and from the interface, including broadcast and multicast traffic.')->setWidth(5);
859
$section->add($group);
860

  
861
// VIEW OPTIONS
862
$group = new Form_Group('View Options');
863
$group->add(new Form_Select(
864
	'detail',
865
	'View Detail',
866
	$input_detail,
867
	$form_detail
868
))->setHelp('The level of detail shown when viewing the packet capture. Does not affect the packet capture itself.')->setWidth(4);
869
$group->add(new Form_Checkbox(
870
	'lookup',
871
	null,
872
	'Name Lookup',
873
	$input_lookup
874
))->setHelp('Perform a name lookup for the port and host address, including MAC OUI. This can cause delays when viewing large packet captures.')->setWidth(5);
875
$section->add($group);
876
$form->add($section);
369 877

  
370
$section->addInput(new Form_Select(
371
	'fam',
372
	'*Address Family',
373
	$fam,
374
	array('' => 'Any',
375
		  'ip' => gettext('IPv4 Only'),
376
		  'ip6' => gettext('IPv6 Only')
377
	)
378
))->setHelp('Select the type of traffic to be captured.');
379

  
380
$section->addInput(new Form_Select(
381
	'proto',
382
	'*Protocol',
383
	$proto,
384
	$protocollist
385
))->setHelp('Select the protocol to capture, or "Any". ');
386

  
387
$section->addInput(new Form_Input(
388
	'host',
389
	'Host Address',
878
// UNTAGGED OPTIONS
879
$section = new Form_Section('Custom Filter Options');
880
$section->addClass('custom-options');
881
$section->addInput(new Form_StaticText(
882
	'Hint',
883
	sprintf('All input is %1$sspace-separated%2$s. When selecting %1$sANY OF [OR]%2$s, at least two attributes should be ' .
884
	        'specified (e.g. Ethertype and Port). This will capture packets that match either attribute input instead of explicitly both.', '<b>', '</b>')
885
));
886
$section->addInput(new Form_StaticText(
887
	'Untagged Filter',
888
	sprintf('Filter options for packets without any VLAN tags. Select %1$sNONE%2$s to exclude all untagged packets.', '<b>', '</b>')
889
));
890
$group = new Form_Group('');
891
$group->add(new Form_Select(
892
	'untagged_notag_match',
893
	null,
894
	$input_untagged_notag_match,
895
	$form_untagged
896
))->setHelp('')->addClass('match-selection')->setWidth(2);
897
$group->add(new Form_StaticText(
898
	null,
899
	null
900
))->setWidth(3);
901
$group->add(new Form_Select(
902
	'untagged_ethertype_match',
903
	null,
904
	$input_untagged_ethertype_match,
905
	$form_match_ethertype
906
))->setHelp('')->addClass('match-selection')->setWidth(2);
907
$group->add(new Form_Input(
908
	'untagged_ethertype',
909
	'Ethertype',
390 910
	'text',
391
	$host
392
))->setHelp('This value is either the Source or Destination IP address, subnet in CIDR notation, or MAC address.%1$s' .
393
			'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 />');
398

  
399
$section->addInput(new Form_Input(
400
	'port',
401
	'Port',
911
	$input_untagged_ethertype,
912
	array('title' => 'EXAMPLE: arp 8100 0x8200')
913
))->setHelp('')->setWidth(3);
914
$section->add($group);
915
$group = new Form_Group('');
916
$group->add(new Form_Select(
917
	'untagged_port_match',
918
	null,
919
	$input_untagged_port_match,
920
	$form_match_port
921
))->setHelp('')->addClass('match-selection')->setWidth(2);
922
$group->add(new Form_Input(
923
	'untagged_port',
924
	'Port number',
402 925
	'text',
403
	$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.');
407

  
408
$section->addInput(new Form_Input(
409
	'snaplen',
410
	'Packet Length',
926
	$input_untagged_port,
927
	array('title' => 'EXAMPLE: 80 443')
928
))->setHelp('')->setWidth(3);
929
$group->add(new Form_Select(
930
	'untagged_protocol_match',
931
	null,
932
	$input_untagged_protocol_match,
933
	$form_match_protocol
934
))->setHelp('')->addClass('match-selection')->setWidth(2);
935
$group->add(new Form_Input(
936
	'untagged_protocol',
937
	'Protocol',
411 938
	'text',
412
	$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.');
415

  
416
$section->addInput(new Form_Input(
417
	'count',
418
	'Count',
939
	$input_untagged_protocol,
940
	array('title' => 'EXAMPLE: tcp 17')
941
))->setHelp('')->setWidth(3);
942
$section->add($group);
943
$group = new Form_Group('');
944
$group->add(new Form_Select(
945
	'untagged_ipaddress_match',
946
	null,
947
	$input_untagged_ipaddress_match,
948
	$form_match_ipaddress
949
))->setHelp('')->addClass('match-selection')->setWidth(2);
950
$group->add(new Form_Input(
951
	'untagged_ipaddress',
952
	'Host IP Address or Subnet',
419 953
	'text',
420
	$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 />');
424

  
425
$section->addInput(new Form_Select(
426
	'detail',
427
	'Level of detail',
428
	$detail,
429
	array('normal' => gettext('Normal'),
430
		  'medium' => gettext('Medium'),
431
		  'high' => gettext('High'),
432
		  'full' => gettext('Full'),
433
		  'none' => gettext('None'),
434
	)
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. ',
437
			'<br />');
438

  
439
$section->addInput(new Form_Checkbox(
440
	'dnsquery',
441
	'Reverse DNS Lookup',
442
	'Do reverse DNS lookup',
443
	$_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 />');
447

  
954
	$input_untagged_ipaddress,
955
	array('title' => 'EXAMPLE: 10.1.1.1 10.2.2.0/24')
956
))->setHelp('')->setWidth(3);
957
$group->add(new Form_Select(
958
	'untagged_macaddress_match',
959
	null,
960
	$input_untagged_macaddress_match,
961
	$form_match_macaddress
962
))->setHelp('')->addClass('match-selection')->setWidth(2);
963
$group->add(new Form_Input(
964
	'untagged_macaddress',
965
	'Host MAC Address',
966
	'text',
967
	$input_untagged_macaddress,
968
	array('title' => 'EXAMPLE: 00:02 00:00:00:04 00:00:00:00:00:06')
969
))->setHelp('')->setWidth(3);
970
$section->add($group);
971

  
972
// SINGLE-TAGGED options
973
$section->addInput(new Form_StaticText(
974
	'Single-Tagged Filter',
975
	sprintf('Filter options for packets with only an outer VLAN tag. Select %1$sNONE%2$s to exclude all single-tagged packets.', '<b>', '</b>')
976
));
977
$group = new Form_Group('');
978
$group->add(new Form_Select(
979
	'singletagged_tag_match',
980
	null,
981
	$input_singletagged_tag_match,
982
	$form_singletagged
983
))->setHelp('')->addClass('match-selection')->setWidth(2);
984
$group->add(new Form_Input(
985
	'singletagged_tag',
986
	'VLAN Tag',
987
	'text',
988
	$input_singletagged_tag,
989
	array('title' => 'EXAMPLE: 100 200')
990
))->setHelp('')->setWidth(3);
991
$group->add(new Form_Select(
992
	'singletagged_ethertype_match',
993
	null,
994
	$input_singletagged_ethertype_match,
995
	$form_match_ethertype
996
))->setHelp('')->addClass('match-selection')->setWidth(2);
997
$group->add(new Form_Input(
998
	'singletagged_ethertype',
999
	'Ethertype',
1000
	'text',
1001
	$input_singletagged_ethertype,
1002
	array('title' => 'EXAMPLE: arp 8100 0x8200')
1003
))->setHelp('')->setWidth(3);
1004
$section->add($group);
1005
$group = new Form_Group('');
1006
$group->add(new Form_Select(
1007
	'singletagged_port_match',
1008
	null,
1009
	$input_singletagged_port_match,
1010
	$form_match_port
1011
))->setHelp('')->addClass('match-selection')->setWidth(2);
1012
$group->add(new Form_Input(
1013
	'singletagged_port',
1014
	'Port number',
1015
	'text',
1016
	$input_singletagged_port,
1017
	array('title' => 'EXAMPLE: 80 443')
1018
))->setHelp('')->setWidth(3);
1019
$group->add(new Form_Select(
1020
	'singletagged_protocol_match',
1021
	null,
1022
	$input_singletagged_protocol_match,
1023
	$form_match_protocol
1024
))->setHelp('')->addClass('match-selection')->setWidth(2);
1025
$group->add(new Form_Input(
1026
	'singletagged_protocol',
1027
	'Protocol',
1028
	'text',
1029
	$input_singletagged_protocol,
1030
	array('title' => 'EXAMPLE: tcp 17')
1031
))->setHelp('')->setWidth(3);
1032
$section->add($group);
1033
$group = new Form_Group('');
1034
$group->add(new Form_Select(
1035
	'singletagged_ipaddress_match',
1036
	null,
1037
	$input_singletagged_ipaddress_match,
1038
	$form_match_ipaddress
1039
))->setHelp('')->addClass('match-selection')->setWidth(2);
1040
$group->add(new Form_Input(
1041
	'singletagged_ipaddress',
1042
	'Host IP Address or Subnet',
1043
	'text',
1044
	$input_singletagged_ipaddress,
1045
	array('title' => 'EXAMPLE: 10.1.1.1 10.2.2.0/24')
1046
))->setHelp('')->setWidth(3);
1047
$group->add(new Form_Select(
1048
	'singletagged_macaddress_match',
1049
	null,
1050
	$input_singletagged_macaddress_match,
1051
	$form_match_macaddress
1052
))->setHelp('')->addClass('match-selection')->setWidth(2);
1053
$group->add(new Form_Input(
1054
	'singletagged_macaddress',
1055
	'Host MAC Address',
1056
	'text',
1057
	$input_singletagged_macaddress,
1058
	array('title' => 'EXAMPLE: 00:02 00:00:00:04 00:00:00:00:00:06')
1059
))->setHelp('')->setWidth(3);
1060
$section->add($group);
1061

  
1062
// DOUBLE-TAGGED options
1063
$section->addInput(new Form_StaticText(
1064
	'Double-Tagged Filter',
1065
	sprintf('Filter options for packets with both an outer and inner VLAN tag. Select %1$sNONE%2$s to exclude all double-tagged packets.', '<b>', '</b>')
1066
));
1067
$group = new Form_Group('');
1068
$group->add(new Form_Select(
1069
	'doubletagged_tag_match',
1070
	null,
1071
	$input_doubletagged_tag_match,
1072
	$form_doubletagged
1073
))->setHelp('')->addClass('match-selection')->setWidth(2);
1074
$group->add(new Form_Input(
1075
	'doubletagged_tag',
1076
	'VLAN Tag',
1077
	'text',
1078
	$input_doubletagged_tag,
1079
	array('title' => 'EXAMPLE: 100 200')
1080
))->setHelp('')->setWidth(3);
1081
$group->add(new Form_Select(
1082
	'doubletagged_ethertype_match',
1083
	null,
1084
	$input_doubletagged_ethertype_match,
1085
	$form_match_ethertype
1086
))->setHelp('')->addClass('match-selection')->setWidth(2);
1087
$group->add(new Form_Input(
1088
	'doubletagged_ethertype',
1089
	'Ethertype',
1090
	'text',
1091
	$input_doubletagged_ethertype,
1092
	array('title' => 'EXAMPLE: arp 8100 0x8200')
1093
))->setHelp('')->setWidth(3);
1094
$section->add($group);
1095
$group = new Form_Group('');
1096
$group->add(new Form_Select(
1097
	'doubletagged_port_match',
1098
	null,
1099
	$input_doubletagged_port_match,
1100
	$form_match_port
1101
))->setHelp('')->addClass('match-selection')->setWidth(2);
1102
$group->add(new Form_Input(
1103
	'doubletagged_port',
1104
	'Port number',
1105
	'text',
1106
	$input_doubletagged_port,
1107
	array('title' => 'EXAMPLE: 80 443')
1108
))->setHelp('')->setWidth(3);
1109
$group->add(new Form_Select(
1110
	'doubletagged_protocol_match',
1111
	null,
1112
	$input_doubletagged_protocol_match,
1113
	$form_match_protocol
1114
))->setHelp('')->addClass('match-selection')->setWidth(2);
1115
$group->add(new Form_Input(
1116
	'doubletagged_protocol',
1117
	'Protocol',
1118
	'text',
1119
	$input_doubletagged_protocol,
1120
	array('title' => 'EXAMPLE: tcp 17')
1121
))->setHelp('')->setWidth(3);
1122
$section->add($group);
1123
$group = new Form_Group('');
1124
$group->add(new Form_Select(
1125
	'doubletagged_ipaddress_match',
1126
	null,
1127
	$input_doubletagged_ipaddress_match,
1128
	$form_match_ipaddress
1129
))->setHelp('')->addClass('match-selection')->setWidth(2);
1130
$group->add(new Form_Input(
1131
	'doubletagged_ipaddress',
1132
	'Host IP Address or Subnet',
1133
	'text',
1134
	$input_doubletagged_ipaddress,
1135
	array('title' => 'EXAMPLE: 10.1.1.1 10.2.2.0/24')
1136
))->setHelp('')->setWidth(3);
1137
$group->add(new Form_Select(
1138
	'doubletagged_macaddress_match',
1139
	null,
1140
	$input_doubletagged_macaddress_match,
1141
	$form_match_macaddress
1142
))->setHelp('')->addClass('match-selection')->setWidth(2);
1143
$group->add(new Form_Input(
1144
	'doubletagged_macaddress',
1145
	'Host MAC Address',
1146
	'text',
1147
	$input_doubletagged_macaddress,
1148
	array('title' => 'EXAMPLE: 00:02 00:00:00:04 00:00:00:00:00:06')
1149
))->setHelp('')->setWidth(3);
1150
$section->add($group);
448 1151
$form->add($section);
449 1152

  
450
/* 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)'")));
452

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

  
455
if (($action == gettext("Stop") or $action == "") and $processisrunning != true) {
456
	$form->addGlobal(new Form_Button(
457
		'startbtn',
458
		'Start',
459
		null,
460
		'fa-play-circle'
461
	))->addClass('btn-success');
462
} else {
1153
/* Display form buttons depending on process state and form action */
1154
// Check if any matching packet captures are running
1155
$processcheck = shell_exec("/bin/pgrep -f '^\/usr\/sbin\/tcpdump.*packetcapture-.*-[0-9]+\.pcap'");
1156
$processisrunning = (!empty($processcheck));
1157
if ($run_capture && ($action == 'start' || (empty($action) && $processisrunning))) {
463 1158
	$form->addGlobal(new Form_Button(
464 1159
		'stopbtn',
465 1160
		'Stop',
466 1161
		null,
467 1162
		'fa-stop-circle'
468 1163
	))->addClass('btn-warning');
469
	if ($action == gettext("Start")) {
470
		touch("/root/packetcapture.start");
471
	}
472
	if (file_exists($fp.$fns)) {
473
		$section->addInput(new Form_StaticText(
474
			'Last capture start',
... This diff was truncated because it exceeds the maximum size that can be displayed.
    (1-1/1)