Project

General

Profile

Download (147 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * shaper.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-2013 BSD Perimeter
7
 * Copyright (c) 2013-2016 Electric Sheep Fencing
8
 * Copyright (c) 2014-2024 Rubicon Communications, LLC (Netgate)
9
 * All rights reserved.
10
 *
11
 * originally based on m0n0wall (http://m0n0.ch/wall)
12
 * Copyright (c) 2003-2004 Manuel Kasper <mk@neon1.net>.
13
 * All rights reserved.
14
 *
15
 * Licensed under the Apache License, Version 2.0 (the "License");
16
 * you may not use this file except in compliance with the License.
17
 * You may obtain a copy of the License at
18
 *
19
 * http://www.apache.org/licenses/LICENSE-2.0
20
 *
21
 * Unless required by applicable law or agreed to in writing, software
22
 * distributed under the License is distributed on an "AS IS" BASIS,
23
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24
 * See the License for the specific language governing permissions and
25
 * limitations under the License.
26
 */
27

    
28
/* XXX: needs some reducing on include. */
29
/* include all configuration functions. */
30
require_once("globals.inc");
31
require_once("functions.inc");
32
require_once("util.inc");
33
require_once("notices.inc");
34
include_once("interfaces.inc");//required for convert_real_interface_to_friendly_interface_name() in get_queue_stats() to get altq interface name.
35

    
36
// Try to ceil $value, otherwise return $default is unable to do so
37
function ceil2($value, $default = NULL)
38
{
39
    return (is_numeric($value) ? ceil($value) : $default);
40
}
41

    
42
/* Limiter patch */
43
// I need this crazy looking model/struct to specify various algos, ecn caps, and params.
44
// update as needed when dummynet changes
45

    
46
// List of schedulers ('type' command on scheduler/pipe) that dummynet/ipfw is supposed to support
47
function getSchedulers() {
48
	return array(
49
		"wf2q+" => array(
50
			"name" => "Worst-case Weighted fair Queueing (default)",
51
			"parameter_format" => "type wf2q+",
52
			"description" => "Worst-case Weighted fair Queueing (WF2Q+) schedules flows with an associated weight. ".
53
					 "WF2Q+ is the default algorithm used by previous versions.",
54
			"parameters" => array(),
55
			"ecn" => false
56
		),
57
		"fifo" => array(
58
			"name" => "FIFO",
59
			"parameter_format" => "type fifo",
60
			"description" => "First-in-First-out is a fundamental queueing discipline which ensures packet sequence. ".
61
					 "Dynamic queues are not supported with the FIFO scheduler (all packets are stored in a single queue).",
62
			"parameters" => array(),
63
			"ecn" => false
64
		),
65
		"qfq" => array(
66
			"name" => "Quick Fair Queueing",
67
			"parameter_format" => "type qfq",
68
			"description" => "QFQ is a fast, low-complexity Approximated Fair Queueing scheduler.",
69
			"parameters" => array(),
70
			"ecn" => false
71
		),
72
		"rr" => array(
73
			"name" => "Round Robin",
74
			"parameter_format" => "type rr",
75
			"description" => "Round Robin (RR) schedules packets in a round-robin fashion.",
76
			"parameters" => array(),
77
			"ecn" => false
78
		),
79
		"prio" => array(
80
			"name" => "PRIO",
81
			"parameter_format" => "type prio",
82
			"description" => "PRIO schedules packets by their corresponding priority.",
83
			"parameters" => array(),
84
			"ecn" => false
85
		),
86
		"fq_codel" => array(
87
			"name" => "FQ_CODEL",
88
			"parameter_format" => "type fq_codel target %sms interval %sms quantum %s limit %s flows %s",
89
			"description" => "CoDel is a novel \"no knobs\", \"just works\", \"handles variable bandwidth and RTT\", and simple AQM algorithm."
90
							. " As a scheduler, FQ_CODEL implements several flows (defined below), each having their own CoDel AQM.",
91
			"parameters" => array(
92
				"target" => array("name" => "Target Delay (ms)", "type" => "number", "default" => intval(get_single_sysctl("net.inet.ip.dummynet.fqcodel.target")) / 1000),
93
				"interval" => array("name" => "Interval (ms)", "type" => "number", "default" => intval(get_single_sysctl("net.inet.ip.dummynet.fqcodel.interval")) / 1000),
94
				"quantum" => array("name" => "quantum", "type" => "number", "default" => get_single_sysctl("net.inet.ip.dummynet.fqcodel.quantum")),
95
				"limit" => array("name" => "limit", "type" => "number", "default" => get_single_sysctl("net.inet.ip.dummynet.fqcodel.limit")),
96
				"flows" => array("name" => "flows", "type" => "number", "default" => get_single_sysctl("net.inet.ip.dummynet.fqcodel.flows"))
97
			),
98
			"ecn" => true
99
		),
100
		"fq_pie" => array(
101
			"name" => "FQ_PIE",
102
			"parameter_format" => "type fq_pie target %sms tupdate %sms alpha %s beta %s max_burst %s max_ecnth %s quantum %s limit %s flows %s",
103
			"description" => "Fair Queue Proportional Integral controller Enhanced AQM (FQ_PIE) is similar to FQ_CODEL but uses a slightly different algorithm.",
104
			"parameters" => array(
105
				"target" => array("name" => "Target Delay (ms)", "type" => "number", "default" => intval(get_single_sysctl("net.inet.ip.dummynet.fqpie.target")) / 1000),
106
				"tupdate" => array("name" => "Interval (ms)", "type" => "number", "default" => intval(get_single_sysctl("net.inet.ip.dummynet.fqpie.tupdate")) / 1000),
107
				"alpha" => array("name" => "alpha", "type" => "number step=any", "default" => intval(get_single_sysctl("net.inet.ip.dummynet.fqpie.alpha")) / 1000),
108
				"beta" => array("name" => "beta", "type" => "number step=any", "default" => intval(get_single_sysctl("net.inet.ip.dummynet.fqpie.beta")) / 1000),
109
				"max_burst" => array("name" => "max_burst", "type" => "number", "default" => intval(get_single_sysctl("net.inet.ip.dummynet.fqpie.max_burst")) / 1000),
110
				"max_ecnth" => array("name" => "max_ecnth", "type" => "number step=any", "default" => intval(get_single_sysctl("net.inet.ip.dummynet.fqpie.max_ecnth")) / 1000),
111
				"quantum" => array("name" => "quantum", "type" => "number", "default" => get_single_sysctl("net.inet.ip.dummynet.fqpie.quantum")),
112
				"limit" => array("name" => "limit", "type" => "number", "default" => get_single_sysctl("net.inet.ip.dummynet.fqpie.limit")),
113
				"flows" => array("name" => "flows", "type" => "number", "default" => get_single_sysctl("net.inet.ip.dummynet.fqpie.flows"))
114
			),
115
			"ecn" => true,
116
			"pie_onoff" => true,
117
			"pie_capdrop" => true,
118
			"pie_qdelay" => true,
119
			"pie_pderand" => true
120
		)
121
	);
122
}
123
// list of AQMs that dummynet supports
124
function getAQMs() {
125
	return array(
126
		"droptail" => array(
127
			"name" => "Tail Drop",
128
			"description" => "Tail Drop is a fundamental queue management algorithm which drops inbound packets once the queue is full.",
129
			"parameter_format" => "droptail",
130
			"parameters" => array(),
131
			"ecn" => false
132
		),
133
		"codel" => array(
134
			"name" => "CoDel",
135
			"description" => "CoDel is a novel \"no knobs\", \"just works\", \"handles variable bandwidth and RTT\", and simple AQM algorithm.",
136
			"parameter_format" => "codel target %sms interval %sms",
137
			"parameters" => array(
138
				"target" => array("name" => "Target Delay (ms)", "type" => "number", "default" => intval(get_single_sysctl("net.inet.ip.dummynet.codel.target")) / 1000),
139
				"interval" => array("name" => "Interval (ms)", "type" => "number", "default" => intval(get_single_sysctl("net.inet.ip.dummynet.codel.interval")) / 1000),
140
			),
141
			"ecn" => true
142
		),
143
		"pie" => array(
144
			"name" => "PIE",
145
			"description" => "Proportional Integral controller Enhanced AQM (PIE) is similar to CoDel but uses a slightly different algorithm.",
146
			"parameter_format" => "pie target %sms tupdate %sms alpha %s beta %s max_burst %s max_ecnth %s",
147
			"parameters" => array(
148
				"target" => array("name" => "Target Delay (ms)", "type" => "number", "default" => intval(get_single_sysctl("net.inet.ip.dummynet.fqpie.target")) / 1000),
149
				"tupdate" => array("name" => "Interval (ms)", "type" => "number", "default" => intval(get_single_sysctl("net.inet.ip.dummynet.fqpie.tupdate")) / 1000),
150
				"alpha" => array("name" => "alpha", "type" => "number step=any", "default" => intval(get_single_sysctl("net.inet.ip.dummynet.fqpie.alpha")) / 1000),
151
				"beta" => array("name" => "beta", "type" => "number step=any", "default" => intval(get_single_sysctl("net.inet.ip.dummynet.fqpie.beta")) / 1000),
152
				"max_burst" => array("name" => "max_burst", "type" => "number", "default" => intval(get_single_sysctl("net.inet.ip.dummynet.fqpie.max_burst")) / 1000),
153
				"max_ecnth" => array("name" => "max_ecnth", "type" => "number step=any", "default" => intval(get_single_sysctl("net.inet.ip.dummynet.fqpie.max_ecnth")) / 1000)
154
			),
155
			"ecn" => true,
156
			"pie_onoff" => true,
157
			"pie_capdrop" => true,
158
			"pie_qdelay" => true,
159
			"pie_pderand" => true
160
		),
161
		"red" => array(
162
			"name" => "Random Early Detection (RED)",
163
			"description" => "Random Early Detection (RED) drops packets based on probability, which increases as the queue increases in size.",
164
			"parameter_format" => "red %s/%s/%s/%s",
165
			"parameters" => array(
166
				"w_q" => array("name" => "w_q", "type" => "number", "default" => 1),
167
				"min_th" => array("name" => "min_th", "type" => "number", "default" => 0),
168
				"max_th" => array("name" => "max_th", "type" => "number", "default" => 1),
169
				"max_p" => array("name" => "max_p", "type" => "number", "default" => 1)
170
			),
171
			"ecn" => true
172
		),
173
		"gred" => array(
174
			"name" => "Gentle Random Early Detection (GRED)",
175
			"description" => "Gentle Random Early Detection (GRED) drops packets based on probability, which increases as the queue increases in size.",
176
			"parameter_format" => "gred %s/%s/%s/%s",
177
			"parameters" => array(
178
				"w_q" => array("name" => "w_q", "type" => "number", "default" => 1),
179
				"min_th" => array("name" => "min_th", "type" => "number", "default" => 0),
180
				"max_th" => array("name" => "max_th", "type" => "number", "default" => 1),
181
				"max_p" => array("name" => "max_p", "type" => "number", "default" => 1)
182
			),
183
			"ecn" => true
184
		)
185
	);
186
}
187
// used to map above
188
function array_map_assoc(callable $f, array $a) {
189
	return array_column(array_map($f, array_keys($a), $a), 1, 0);
190
}
191
function build_queue_params($array, $selected, $params, $id) {
192
	$form .= '<div id="params_' . $id . '">';
193

    
194
	$selectedAlgorithm = $array[$selected];
195

    
196
	if ($selectedAlgorithm) {
197
		$form .= '<span class="help-block">' . gettext($selectedAlgorithm['description']) . '</span><br/>';
198
	}
199

    
200
	$parameters = $selectedAlgorithm["parameters"];
201

    
202
	if (!$parameters || sizeof($parameters) <= 0) {
203
		if (!$selectedAlgorithm) {
204
			$form .= 'No parameters for selected algorithm.';
205
		} else {
206
			$form .= 'No parameters for ' . $selectedAlgorithm["name"] . '.';
207
		}
208
	} else {
209
		$form .= '<div class="table-responsive">';
210
		$form .= '<table id="maintable" class="table table-hover table-striped">';
211
		$form .= "<thead><tr>";
212
		$form .= "<th>Parameter</th>";
213
		$form .= "<th>Value</th>";
214
		$form .= "</tr></thead>";
215
		$form .= "<tbody>";
216

    
217
		foreach ($parameters as $key => $value) {
218
			$form .= '<tr>';
219

    
220
			// get current value, or default.
221
			$currentValue = $params[$key];
222
			// check if param key is set. using isset allows the param key to be zero.
223
			if (! isset($currentValue)) {
224
				$currentValue = $value["default"]; // default to default
225
			}
226

    
227
			$form .= "<td class=\"col-xs-4\">" . htmlspecialchars($key) . "</td>";
228
			$form .= "<td class=\"col-xs-8\"><input class=\"form-control\" type=" . gettext($value["type"])
229
					. " name=\"param_" . $selected . "_" . $key . "\" placeholder=\"" .
230
					htmlspecialchars($value["default"]) . "\" value=\"" . htmlspecialchars($currentValue) . "\"/></td>";
231

    
232
			$form .= '</tr>';
233
		}
234

    
235
		$form .= "</tbody></table></div><br />";
236
	}
237

    
238
	$form .= '</div>';
239
	$form .= '<script type="text/javascript">';
240
	$form .= 'events.push(function() {$("#' . $id . '").change(function() {$("#params_' .
241
		gettext($id) . '").html("Save this limiter to see algorithm parameters.");})});</script>';
242

    
243
	return($form);
244
}
245
function FormatParameters($format_string, $params) {
246
	return vsprintf($format_string, $params);
247
}
248
/* End limiter patch */
249

    
250
function get_queue_stats() {
251
	// due to sysutils\qstats not providing accurate stats with 'currently' valid numbers
252
	// in its current implementation this php function does the job for now..
253
	$result = array();
254
	$result['timestamp'] = gettimeofday(true);
255
	$r = exec("/sbin/pfctl -s queue -v", $output, $ret);
256
	$interfacestats = array();
257
	foreach($output as $line) {
258
		$partial = explode('{', $line);
259
		$items = explode(" ", $partial[0]);
260
		if (isset($partial[1])) {
261
			$contains = explode(", ", substr($partial[1], 0, strlen($partial[1]) - 1));
262
		} else {
263
			unset($contains);
264
		}
265
		if ($items[0] == "queue") {
266
			$level = 1;
267
			while (empty($items[$level])) {
268
				$level++;
269
			}
270
			unset($newqueue);
271
			$newqueue = array();
272
			$currentqueuename = $items[$level];
273
			$currentqueueif = $items[$level+2];
274
			$newqueue['name'] = $currentqueuename;
275
			$newqueue['interface'] = $items[$level+2];
276

    
277
			if ($items[$level+3] == "bandwidth") {
278
				$level += 2;
279
			}
280
			if ($items[$level+3] == "priority") {
281
				$level += 2;
282
			}
283
			if ($items[$level+3] == "qlimit") {
284
				$level += 2;
285
			}
286
			$tmp = $items[$level+3];
287
			if (substr($tmp,strlen($tmp)-1) == "(") {
288
				$newqueue['shapertype'] = substr($tmp, 0, strlen($tmp)-1);
289
			}
290

    
291
			$interfacestats[$currentqueueif][$currentqueuename] = &$newqueue;
292
			if (is_array($contains)) {
293
				$newqueue['contains'] = $contains;
294
			}
295
		} elseif ($items[0] == "altq") {
296
			unset($newqueue);
297
			$newqueue = array();
298
			$currentqueuename = convert_real_interface_to_friendly_interface_name($items[2]);
299
			$currentqueueif = $items[2];
300
			$newqueue['name'] = $currentqueuename;
301
			$newqueue['interface'] = $items[2];
302
			$interfacestats[$currentqueueif][$currentqueuename] = &$newqueue;
303
		} else {
304
			if ($items[3] == "pkts:") {
305
				preg_match('/pkts:\s+(\d+)\s+bytes:\s+(\d+)\s+dropped pkts:\s+(\d+)\s+bytes:\s+(\d+)/', $line, $matches);
306
				$newqueue["pkts"] = $matches[1];
307
				$newqueue["bytes"] = $matches[2];
308
				$newqueue["droppedpkts"] = $matches[3];
309
				$newqueue["droppedbytes"] = $matches[4];
310
			}
311
			if ($items[3] == "qlength:") {
312
				$subitems = explode("/", substr($line, 13, 8));
313
				$newqueue["qlengthitems"] = trim($subitems[0]);
314
				$newqueue["qlengthsize"] = trim($subitems[1]);
315
				if (substr($line, 22, 8) == "borrows:") {
316
					$newqueue["borrows"] = trim(substr($line, 31, 7));
317
				}
318
				if (substr($line, 39, 9) == "suspends:") {
319
					$newqueue["suspends"] = trim(substr($line, 49, 7));
320
				}
321
			}
322
		}
323
	}
324
	$result['interfacestats'] = $interfacestats;
325
	return $result;
326
}
327

    
328
/*
329
 * I admit :) this is derived from xmlparse.inc StartElement()
330
 */
331
function &get_reference_to_me_in_config(&$mypath) {
332
	global $config;
333

    
334
	init_config_arr(array('shaper'));
335
	$ptr = &$config['shaper'];
336
	foreach ($mypath as $indeks) {
337
		if (!is_array($ptr)) {
338
			$ptr = array();
339
		}
340
		if (!is_array($ptr['queue'])) {
341
			$ptr['queue'] = array();
342
		}
343
		if (!is_array($ptr['queue'][$indeks])) {
344
			$ptr['queue'][$indeks] = array();
345
		}
346
		$ptr = &$ptr['queue'][$indeks];
347
	}
348

    
349
	return $ptr;
350
}
351

    
352
function unset_object_by_reference(&$mypath) {
353
	global $config;
354

    
355
	init_config_arr(array('shaper'));
356
	$ptr = &$config['shaper'];
357
	for ($i = 0; $i < count($mypath) - 1; $i++) {
358
		$ptr = &$ptr['queue'][$mypath[$i]];
359
	}
360
	unset($ptr['queue'][$mypath[$i]]);
361
}
362

    
363
function &get_dn_reference_to_me_in_config(&$mypath) {
364
	global $config;
365

    
366
	init_config_arr(array('dnshaper'));
367
	$ptr = &$config['dnshaper'];
368
	foreach ($mypath as $indeks) {
369
		$ptr = &$ptr['queue'][$indeks];
370
	}
371

    
372
	return $ptr;
373
}
374

    
375
function unset_dn_object_by_reference(&$mypath) {
376
	global $config;
377

    
378
	init_config_arr(array('dnshaper'));
379
	$ptr = &$config['dnshaper'];
380
	for ($i = 0; $i < count($mypath) - 1; $i++) {
381
		$ptr = &$ptr['queue'][$mypath[$i]];
382
	}
383
	unset($ptr['queue'][$mypath[$i]]);
384
}
385

    
386
function clean_child_queues($type, $mypath) {
387
	$ref = &get_reference_to_me_in_config($mypath);
388

    
389
	switch ($type) {
390
		case 'HFSC':
391
			if (isset($ref['borrow'])) {
392
				unset($ref['borrow']);
393
			}
394
			if (isset($ref['hogs'])) {
395
				unset($ref['hogs']);
396
			}
397
			if (isset($ref['buckets'])) {
398
				unset($ref['buckets']);
399
			}
400
			break;
401
		case 'PRIQ':
402
			if (isset($ref['borrow'])) {
403
				unset($ref['borrow']);
404
			}
405
			if (isset($ref['bandwidth'])) {
406
				unset($ref['bandwidth']);
407
			}
408
			if (isset($ref['bandwidthtype'])) {
409
				unset($ref['bandwidthtype']);
410
			}
411
			/* fall through */
412
		case 'FAIRQ':
413
			if (isset($ref['borrow'])) {
414
				unset($ref['borrow']);
415
			}
416
			/* fall through */
417
		case 'CBQ':
418
			if (isset($ref['realtime'])) {
419
				unset($ref['realtime']);
420
			}
421
			if (isset($ref['realtime1'])) {
422
				unset($ref['realtime1']);
423
			}
424
			if (isset($ref['realtime2'])) {
425
				unset($ref['realtime2']);
426
			}
427
			if (isset($ref['realtime3'])) {
428
				unset($ref['realtime3']);
429
			}
430
			if (isset($ref['upperlimit'])) {
431
				unset($ref['upperlimit']);
432
			}
433
			if (isset($ref['upperlimit1'])) {
434
				unset($ref['upperlimit1']);
435
			}
436
			if (isset($ref['upperlimit2'])) {
437
				unset($ref['upperlimit2']);
438
			}
439
			if (isset($ref['upperlimit3'])) {
440
				unset($ref['upperlimit3']);
441
			}
442
			if (isset($ref['linkshare'])) {
443
				unset($ref['linkshare']);
444
			}
445
			if (isset($ref['linkshare1'])) {
446
				unset($ref['linkshare1']);
447
			}
448
			if (isset($ref['linkshare2'])) {
449
				unset($ref['linkshare2']);
450
			}
451
			if (isset($ref['linkshare3'])) {
452
				unset($ref['linkshare3']);
453
			}
454
			if (isset($ref['hogs'])) {
455
				unset($ref['hogs']);
456
			}
457
			if (isset($ref['buckets'])) {
458
				unset($ref['buckets']);
459
			}
460
			break;
461
	}
462
}
463

    
464
function get_bandwidthtype_scale($type) {
465
	switch ($type) {
466
		case "Gb":
467
			$factor = 1024 * 1024 * 1024;
468
			break;
469
		case "Mb":
470
			$factor = 1024 * 1024;
471
			break;
472
		case "Kb":
473
			$factor = 1024;
474
			break;
475
		case "b":
476
		default:
477
			$factor = 1;
478
			break;
479
	}
480
	return intval($factor);
481
}
482

    
483
function get_bandwidth($bw, $scale, $obj) {
484
	$bw = (int) $bw;
485
	$pattern= "/(b|Kb|Mb|Gb|%)/";
486
	if (!preg_match($pattern, $scale, $match))
487
		return 0;
488

    
489
	switch ($match[1]) {
490
		case '%':
491
			$objbw = ($bw / 100) * get_queue_bandwidth($obj);
492
			break;
493
		default:
494
			$objbw = $bw * get_bandwidthtype_scale($scale);
495
			break;
496
	}
497

    
498
	return floatval($objbw);
499
}
500

    
501
/*
502
 * XXX - unused
503
 *
504
function get_hfsc_bandwidth($object, $bw) {
505
	$pattern= "/[0-9]+/";
506
	if (preg_match($pattern, $bw, $match)) {
507
		$bw_1 = $match[1];
508
	} else {
509
		return 0;
510
	}
511
	$pattern= "/(b|Kb|Mb|Gb|%)/";
512
	if (preg_match($pattern, $bw, $match)) {
513
		switch ($match[1]) {
514
			case '%':
515
				$bw_1 = ($bw_1 / 100) * get_interface_bandwidth($object);
516
				break;
517
			default:
518
				$bw_1 = $bw_1 * get_bandwidthtype_scale($match[0]);
519
				break;
520
		}
521
		return floatval($bw_1);
522
	} else {
523
		return 0;
524
	}
525
}
526
*/
527

    
528
function get_queue_bandwidth($obj) {
529
	$bw = (int)$obj->GetBandwidth();
530
	$scale = $obj->GetBwscale();
531

    
532
	$pattern= "/(b|Kb|Mb|Gb|%)/";
533
	if (!preg_match($pattern, $scale, $match))
534
		return 0;
535

    
536
	switch ($match[1]) {
537
		case '%':
538
			if (method_exists($obj, 'GetParent')) {
539
				$getobjbw = get_queue_bandwidth($obj->GetParent());
540
			} else {
541
				$getobjbw = $obj->bandwidth;
542
			}
543
			$objbw = ($bw / 100) * $getobjbw;
544
			break;
545
		default:
546
			$objbw = $bw * get_bandwidthtype_scale($scale);
547
			break;
548
	}
549

    
550
	return floatval($objbw);
551
}
552

    
553
function get_interface_bandwidth($object) {
554
	global $altq_list_queues;
555

    
556
	$int = $object->GetInterface();
557
	if (isset($altq_list_queues[$int])) {
558
		$altq = &$altq_list_queues[$int];
559
		$bw_3 = (int)$altq->GetBandwidth();
560
		$bw_3 = $bw_3 * get_bandwidthtype_scale($altq->GetBwscale());
561
		return floatval($bw_3);
562
	} else {
563
		return 0;
564
	}
565
}
566

    
567
function cleanup_queue_from_rules($queue) {
568
	$rulelist = config_get_path('filter/rule', []);
569
	foreach ($rulelist as & $rule) {
570
		if ($rule['defaultqueue'] == $queue) {
571
			unset($rule['defaultqueue']);
572
			if (isset($rule['ackqueue'])) {
573
				unset($rule['ackqueue']);
574
			}
575
		}
576
		if ($rule['ackqueue'] == $queue) {
577
			unset($rule['ackqueue']);
578
		}
579
	}
580
	config_set_path('filter/rule', $rulelist);
581
}
582

    
583
function cleanup_dnqueue_from_rules($queue) {
584
	$rulelist = config_get_path('filter/rule', []);
585
	foreach ($rulelist as & $rule) {
586
		if ($rule['dnpipe'] == $queue) {
587
			unset($rule['dnpipe']);
588
		}
589
		if ($rule['pdnpipe'] == $queue) {
590
			unset($rule['pdnpipe']);
591
		}
592
	}
593
	config_set_path('filter/rule', $rulelist);
594
}
595

    
596
function rename_queue_in_rules($name, $newname) {
597
	$rulelist = config_get_path('filter/rule', []);
598
	foreach ($rulelist as & $rule) {
599
		if ($rule['defaultqueue'] == $name) {
600
			$rule['defaultqueue'] = $newname;
601
		}
602
		if ($rule['ackqueue'] == $name) {
603
			$rule['ackqueue'] = $newname;
604
		}
605
	}
606
	config_set_path('filter/rule', $rulelist);
607
}
608

    
609
function rename_dnqueue_in_rules($name, $newname) {
610
	$rulelist = config_get_path('filter/rule', []);
611
	foreach ($rulelist as & $rule) {
612
		if ($rule['dnpipe'] == $name) {
613
			$rule['dnpipe'] = $newname;
614
		}
615
		if ($rule['pdnpipe'] == $name) {
616
			$rule['pdnpipe'] = $newname;
617
		}
618
	}
619
	config_set_path('filter/rule', $rulelist);
620
}
621

    
622
class altq_root_queue {
623
	var $interface;
624
	var $tbrconfig ;
625
	var $bandwidth;
626
	var $bandwidthtype; /* b, Kb, Mb, Gb, % */
627
	var $scheduler;
628
	var $qlimit;
629
	var $queues = array();
630
	var $qenabled = false;
631
	var $link;
632

    
633
	/* Accessor functions */
634
	function GetDefaultQueuePresent() {
635
		if (!empty($this->queues)) {
636
			foreach ($this->queues as $q) {
637
				if ($q->GetDefault()) {
638
					return true;
639
				}
640
			}
641
		}
642

    
643
		return false;
644
	}
645
	function SetLink($link) {
646
		$this->link = $link;
647
	}
648
	function GetLink() {
649
		return $this->link;
650
	}
651
	function GetEnabled() {
652
		return $this->qenabled;
653
	}
654
	function SetEnabled($value) {
655
		$this->qenabled = $value;
656
	}
657
	function CanHaveChildren() {
658
		if ($this->GetScheduler() == "CODELQ") {
659
			return false;
660
		} else {
661
			return true;
662
		}
663
	}
664
	function CanBeDeleted() {
665
		return false;
666
	}
667
	function GetQname() {
668
		return $this->interface;
669
	}
670
	function SetQname($name) {
671
		$this->interface = trim($name);
672
	}
673
	function GetInterface() {
674
		return $this->interface;
675
	}
676
	function SetInterface($name) {
677
		$this->interface = trim($name);
678
	}
679
	function GetTbrConfig() {
680
		return $this->tbrconfig;
681
	}
682
	function SetTbrConfig($tbrconfig) {
683
		$this->tbrconfig = $tbrconfig;
684
	}
685
	function GetBandwidth() {
686
		return $this->bandwidth;
687
	}
688
	function SetBandwidth($bw) {
689
		$this->bandwidth = $bw;
690
	}
691
	function GetBwscale() {
692
		return $this->bandwidthtype;
693
	}
694
	function FormGetBwscale() {
695
		if ($this->GetBwscale()) {
696
			$bwscale = $this->GetBwscale();
697
		} else {
698
			$bwscale = 'Mb';
699
		}
700
		return $bwscale;
701
	}
702
	function SetBwscale($bwscale) {
703
		$this->bandwidthtype = $bwscale;
704
	}
705
	function GetScheduler() {
706
		return $this->scheduler;
707
	}
708
	function SetScheduler($scheduler) {
709
		$this->scheduler = trim($scheduler);
710
	}
711
	function GetQlimit() {
712
		return $this->qlimit;
713
	}
714
	function SetQlimit($limit) {
715
		$this->qlimit = $limit;
716
	}
717

    
718
	function GetBwscaleText() {
719
		switch ($this->bandwidthtype) {
720
			case "b":
721
				$bwscaletext = "Bit/s";
722
				break;
723
			case "Kb":
724
				$bwscaletext = "Kbit/s";
725
				break;
726
			case "Mb":
727
				$bwscaletext = "Mbit/s";
728
				break;
729
			case "Gb":
730
				$bwscaletext = "Gbit/s";
731
				break;
732
			default:
733
				/* For others that do not need translating like % */
734
				$bwscaletext = $this->bandwidthtype;
735
				break;
736
		}
737
		return $bwscaletext;
738
	}
739

    
740
	function CheckBandwidth($bw, $bwtype) {
741
		$bw = (int)$bw;
742
		$sum = $this->GetTotalBw();
743
		if ($sum > ($bw * get_bandwidthtype_scale($bwtype))) {
744
			return 1;
745
		}
746
		foreach ($this->queues as $q) {
747
			if ($q->CheckBandwidth(0, '')) {
748
				return 1;
749
			}
750
		}
751

    
752
		return 0;
753
	}
754

    
755
	function GetTotalBw($qignore = NULL) {
756
		$sum = 0;
757
		foreach ($this->queues as $q) {
758
			if (($qignore != NULL) && ($qignore == $q)) {
759
				continue;
760
			}
761
			$sum += get_bandwidth($q->GetBandwidth(), $q->GetBwscale(), $this);
762
		}
763

    
764
		return $sum;
765
	}
766

    
767
	function validate_input($data, &$input_errors) {
768
		if (!isset($data['bandwidth']) || ($data['bandwidth'] == '')) {
769
			$input_errors[] = gettext("Bandwidth must be set.  This is usually the interface speed.");
770
		} else {
771
			if ((!is_numeric($data['bandwidth']))) {
772
				$input_errors[] = gettext("Bandwidth must be an integer.");
773
			}
774
			if ((int)$data['bandwidth'] < 0) {
775
				$input_errors[] = gettext("Bandwidth cannot be negative.");
776
			}
777
		}
778
		if (isset($data['bandwidthtype']) && ($data['bandwidthtype'] == "%")) {
779
			if ($data['bandwidth'] > 100 || $data['bandwidth'] < 0) {
780
				$input_errors[] = gettext("Bandwidth in percentage should be between 1 and 100.");
781
			}
782
		}
783
		if ($this->CheckBandwidth($data['bandwidth'], $data['bandwidthtype'])) {
784
			$input_errors[] = "The sum of child bandwidth is higher than parent.";
785
		}
786
		if (($data['qlimit'] != null) && ($data['scheduler'] == 'CODELQ')) {
787
			$input_errors[] = gettext("CODELQ scheduler doesn't support Qlimit parameter.");
788
		}
789
		if (($data['qlimit'] != null) && (!is_numeric($data['qlimit']))) {
790
			$input_errors[] = gettext("Qlimit must be an integer.");
791
		}
792
		if (($data['qlimit'] != null) && (int)$data['qlimit'] < 1) {
793
			$input_errors[] = gettext("Qlimit must be positive.");
794
		}
795
		if (($data['tbrconfig'] != null) && (!is_numeric($data['tbrconfig']))) {
796
			$input_errors[] = gettext("Tbrsize must be an integer.");
797
		}
798
		if (($data['tbrconfig'] != null) && (int)$data['tbrconfig'] < 1) {
799
			$input_errors[] = gettext("Tbrsize must be positive.");
800
		}
801
	}
802

    
803
	/* Implement this to shorten some code on the frontend page */
804
	function ReadConfig(&$conf) {
805
		if (isset($conf['tbrconfig'])) {
806
			$this->SetTbrConfig($conf['tbrconfig']);
807
		} else {
808
			$this->SetTbrConfig($conf['tbrconfig']);
809
		}
810
		$this->SetBandwidth($conf['bandwidth']);
811
		if ($conf['bandwidthtype'] <> "") {
812
			$this->SetBwscale($conf['bandwidthtype']);
813
		}
814
		if (isset($conf['scheduler'])) {
815
			if ($this->GetScheduler() != $conf['scheduler']) {
816
				foreach ($this->queues as $q) {
817
					clean_child_queues($conf['scheduler'], $this->GetLink());
818
					$q->clean_queue($conf['scheduler']);
819
				}
820
			}
821
			$this->SetScheduler($conf['scheduler']);
822
		}
823
		if (isset($conf['qlimit']) && $conf['qlimit'] <> "") {
824
			$this->SetQlimit($conf['qlimit']);
825
		} else {
826
			$this->SetQlimit("");
827
		}
828
		if (isset($conf['name'])) {
829
			$this->SetQname($conf['name']);
830
		}
831
		if (!empty($conf['enabled'])) {
832
			$this->SetEnabled($conf['enabled']);
833
		} else {
834
			$this->SetEnabled("");
835
		}
836
	}
837

    
838
	function copy_queue($interface, &$cflink) {
839
		$cflink['interface'] = $interface;
840
		$cflink['name'] = $interface;
841
		$cflink['scheduler'] = $this->GetScheduler();
842
		$cflink['bandwidth'] = $this->GetBandwidth();
843
		$cflink['bandwidthtype'] = $this->GetBwscale();
844
		$cflink['qlimit'] = $this->GetQlimit();
845
		$cflink['tbrconfig'] = $this->GetTbrConfig();
846
		$cflink['enabled'] = $this->GetEnabled();
847
		if (is_array($this->queues)) {
848
			$cflink['queue'] = array();
849
			foreach ($this->queues as $q) {
850
				$cflink['queue'][$q->GetQname()] = array();
851
				$q->copy_queue($interface, $cflink['queue'][$q->GetQname()]);
852
			}
853
		}
854
	}
855

    
856
	function &get_queue_list(&$q = null) {
857
		$qlist = array();
858

    
859
		//$qlist[$this->GetQname()] = & $this;
860
		if (is_array($this->queues)) {
861
			foreach ($this->queues as $queue) {
862
				$queue->get_queue_list($qlist);
863
			}
864
		}
865
		return $qlist;
866
	}
867

    
868
	function &add_queue($interface, &$queue, &$path, &$input_errors) {
869

    
870
		if (!is_array($this->queues)) {
871
			$this->queues = array();
872
		}
873

    
874
		switch ($this->GetScheduler()) {
875
			case "PRIQ":
876
				$__tmp_q = new priq_queue(); $q =& $__tmp_q;
877
				break;
878
			case "HFSC":
879
				$__tmp_q = new hfsc_queue(); $q =& $__tmp_q;
880
				break;
881
			case "CBQ":
882
				$__tmp_q = new cbq_queue(); $q =& $__tmp_q;
883
				break;
884
			case "FAIRQ":
885
				$__tmp_q = new fairq_queue(); $q =& $__tmp_q;
886
				break;
887
			default:
888
				/* XXX: but should not happen anyway */
889
				return;
890
				break;
891
		}
892
		$q->SetLink($path);
893
		$q->SetInterface($this->GetInterface());
894
		$q->SetEnabled("on");
895
		$q->SetParent($this);
896
		$q->ReadConfig($queue);
897
		$q->validate_input($queue, $input_errors);
898

    
899
		$this->queues[$q->GetQname()] = &$q;
900
		ref_on_altq_queue_list($this->GetQname(), $q->GetQname());
901
		if (is_array($queue['queue'])) {
902
			foreach ($queue['queue'] as $key1 => $que) {
903
				array_push($path, $key1);
904
				$q->add_queue($q->GetInterface(), $que, $path, $input_errors);
905
				array_pop($path);
906
			}
907
		}
908

    
909
		return $q;
910
	}
911

    
912
	/* interface here might be optional */
913
	function &find_queue($interface, $qname) {
914
		if ($qname == $this->GetQname()) {
915
			return $this;
916
		}
917
		foreach ($this->queues as $q) {
918
			$result =& $q->find_queue("", $qname);
919
			if ($result) {
920
				return $result;
921
			}
922
		}
923
	}
924

    
925
	function &find_parentqueue($interface, $qname) {
926
		if ($qname == $interface) {
927
			$result = NULL;
928
		} else if ($this->queues[$qname]) {
929
			$result = $this;
930
		} else if ($this->GetScheduler() <> "PRIQ") {
931
			foreach ($this->queues as $q) {
932
				$result = $q->find_parentqueue("", $qname);
933
				if ($result) {
934
					return $result;
935
				}
936
			}
937
		}
938
	}
939

    
940
	function build_tree() {
941
		global $shaperIFlist;
942

    
943
		$tree = " <li><a href=\"firewall_shaper.php?interface=".$this->GetInterface()."&amp;queue=". $this->GetInterface()."&amp;action=show";
944
		$tree .= "\">" . $shaperIFlist[$this->GetInterface()] . "</a>";
945
		if (is_array($this->queues)) {
946
			$tree .= "<ul>";
947
			foreach ($this->queues as $q) {
948
				$tree .= $q->build_tree();
949
			}
950
			$tree .= "</ul>";
951
		}
952
		$tree .= "</li>";
953
		return $tree;
954
	}
955

    
956
	function delete_queue() {
957
		foreach ($this->queues as $q) {
958
			$q->delete_queue();
959
		}
960
		unset_object_by_reference($this->GetLink());
961
	}
962

    
963
	function delete_all() {
964
		if (count($this->queues)) {
965
			foreach ($this->queues as $q) {
966
				$q->delete_all();
967
				unset_object_by_reference($q->GetLink());
968
				unset($q);
969
			}
970
			unset($this->queues);
971
		}
972
	}
973

    
974
	/*
975
	 * First it spits:
976
	 * altq on $interface ..............
977
	 *	then it goes like
978
	 *	foreach ($queues as $qkey => $queue) {
979
	 *		this->queues[$qkey]->build_rule();
980
	 *	}
981
	 */
982
	function build_rules(&$default = false) {
983
		if (count($this->queues) > 0 && $this->GetEnabled() == "on") {
984
			$default = false;
985
			$rules = " altq on " . get_real_interface($this->GetInterface());
986
			if ($this->GetScheduler()) {
987
				$rules .= " ".strtolower($this->GetScheduler());
988
			}
989
			if ($this->GetQlimit() > 0) {
990
				$rules .= " qlimit " . $this->GetQlimit() . " ";
991
			}
992
			if ($this->GetBandwidth()) {
993
				$rules .= " bandwidth ".trim($this->GetBandwidth());
994
				if ($this->GetBwscale()) {
995
					$rules .= $this->GetBwscale();
996
				}
997
			}
998
			if ($this->GetTbrConfig()) {
999
				$rules .= " tbrsize ".$this->GetTbrConfig();
1000
			}
1001
			if (count($this->queues)) {
1002
				$i = count($this->queues);
1003
				$rules .= " queue { ";
1004
				foreach ($this->queues as $qkey => $qnone) {
1005
					if ($i > 1) {
1006
						$i--;
1007
						$rules .= " {$qkey}, ";
1008
					} else {
1009
						$rules .= " {$qkey} ";
1010
					}
1011
				}
1012
				$rules .= " } \n";
1013
				foreach ($this->queues as $q) {
1014
					$rules .= $q->build_rules($default);
1015
				}
1016
			}
1017

    
1018
			if ($default == false) {
1019
				$error = sprintf(gettext("SHAPER: no default queue specified for interface %s."), $this->GetInterface()) . " " . gettext("The interface queue will be enforced as default.");
1020
				file_notice("Shaper", $error, "Error occurred", "");
1021
				unset($error);
1022
				return "\n";
1023
			}
1024
			$frule .= $rules;
1025
		} else if ($this->GetEnabled() == "on" && $this->GetScheduler() == "CODELQ") {
1026
			$rules = " altq on " . get_real_interface($this->GetInterface());
1027
			if ($this->GetScheduler()) {
1028
				$rules .= " ".strtolower($this->GetScheduler());
1029
			}
1030
			if ($this->GetQlimit() > 0) {
1031
				$rules .= " ( qlimit " . $this->GetQlimit() . " ) ";
1032
			}
1033
			if ($this->GetBandwidth()) {
1034
				$rules .= " bandwidth ".trim($this->GetBandwidth());
1035
				if ($this->GetBwscale()) {
1036
					$rules .= $this->GetBwscale();
1037
				}
1038
			}
1039
			if ($this->GetTbrConfig()) {
1040
				$rules .= " tbrsize ".$this->GetTbrConfig();
1041
			}
1042

    
1043
			$rules .= " queue";
1044
		}
1045

    
1046
		$rules .= " \n";
1047
		return $rules;
1048
	}
1049

    
1050
	function build_javascript() {
1051
		$javascript = "<script type=\"text/javascript\">";
1052
		$javascript .= "//<![CDATA[\n";
1053
		$javascript .= "function mySuspend() {";
1054
		$javascript .= "if (document.layers && document.layers['shaperarea'] != null) ";
1055
		$javascript .= "document.layers['shaperarea'].visibility = 'hidden'; ";
1056
		$javascript .= "else if (document.all)";
1057
		$javascript .= "document.all['shaperarea'].style.visibility = 'hidden';";
1058
		$javascript .= "}";
1059

    
1060
		$javascript .= "function myResume() {";
1061
		$javascript .= "if (document.layers && document.layers['shaperarea'] != null) ";
1062
		$javascript .= "document.layers['shaperarea'].visibility = 'visible';";
1063
		$javascript .= "else if (document.all) ";
1064
		$javascript .= "document.all['shaperarea'].style.visibility = 'visible';";
1065
		$javascript .= "}";
1066
		$javascript .= "//]]>";
1067
		$javascript .= "</script>";
1068

    
1069
		return $javascript;
1070
	}
1071

    
1072
	function build_shortform() {
1073
		global $g;
1074

    
1075
		$altq =& $this;
1076

    
1077
		if ($altq) {
1078
			$scheduler = ": " . $altq->GetScheduler();
1079
		}
1080

    
1081
		$form = '<dl class="dl-horizontal">';
1082
		$form .= '	<dt>';
1083
		$form .= '		<a href="firewall_shaper.php?interface=' . $this->GetInterface() . '&amp;queue=' . $this->GetQname() . '&amp;action=show">' . $shaperIFlist[$this->GetInterface()] . '</a>';
1084
		$form .= '	</dt>';
1085
		$form .= '	<dd>';
1086
		$form .=		$scheduler;
1087
		$form .= '	</dd>';
1088

    
1089
		$form .= '	<dt>';
1090
		$form .=		'Bandwidth';
1091
		$form .= '	</dt>';
1092
		$form .= '	<dd>';
1093
		$form .=		$this->GetBandwidth() . '&nbsp;' . $this->GetBwscaleText();
1094
		$form .= '	</dd>';
1095

    
1096
		$form .= '	<dt>';
1097
		$form .= 'Disable';
1098
		$form .= '	<dt>';
1099
		$form .= '	<dd>';
1100

    
1101
		$form .= '<a class="btn btn-danger btn-xs" href="firewall_shaper_queues.php?interface=';
1102
		$form .= $this->GetInterface() . '&amp;queue=';
1103
		$form .= $this->GetQname() . '&amp;action=delete">';
1104
		$form .= '<i class="fa-solid fa-trash-can icon-embed-btn"></i>';
1105
		$form .= gettext("Remove shaper from this interface") . '</a>';
1106

    
1107
		$form .= '	</dd>';
1108

    
1109
		$form .= '</dl>';
1110

    
1111
		return $form;
1112

    
1113
	}
1114

    
1115
	/*
1116
	 * For requesting the parameters of the root queues
1117
	 * to the user like the traffic wizard does.
1118
	 */
1119
	function build_form() {
1120

    
1121
		$sform = new Form();
1122

    
1123
		$sform->setAction("firewall_shaper.php");
1124

    
1125
		$section = new Form_Section(null);
1126

    
1127
		$section->addInput(new Form_Checkbox(
1128
			'enabled',
1129
			'Enable/Disable',
1130
			'Enable/disable discipline and its children',
1131
			($this->GetEnabled() == "on"),
1132
			'on'
1133
		));
1134

    
1135
		$section->addInput(new Form_StaticText(
1136
			'*Name',
1137
			$this->GetQname()
1138
		));
1139

    
1140
		$section->addInput(new Form_Select(
1141
			'scheduler',
1142
			'Scheduler Type',
1143
			$this->GetScheduler(),
1144
			array('HFSC' => 'HFSC',
1145
				  'CBQ' => 'CBQ',
1146
				  'FAIRQ' => 'FAIRQ',
1147
				  'CODELQ' => 'CODELQ',
1148
				  'PRIQ' => 'PRIQ')
1149
		))->setHelp('Changing this changes all child queues! Beware information can be lost.');
1150

    
1151
		$group = new Form_group('Bandwidth');
1152

    
1153
		$group->add(new Form_Input(
1154
			'bandwidth',
1155
			null,
1156
			'number',
1157
			$this->GetBandwidth()
1158
		));
1159

    
1160
		$group->add(new Form_Select(
1161
			'bandwidthtype',
1162
			null,
1163
			$this->FormGetBwscale(),
1164
			array('Kb' => 'Kbit/s',
1165
				  'Mb' => 'Mbit/s',
1166
				  'Gb' => 'Gbit/s',
1167
				  'b' => 'Bit/s',
1168
				  '%' => '%')
1169
		));
1170

    
1171
		$section->add($group);
1172

    
1173
		$section->addInput(new Form_Input(
1174
			'qlimit',
1175
			'Queue Limit',
1176
			'number',
1177
			$this->GetQlimit()
1178
		));
1179

    
1180
		$section->addInput(new Form_Input(
1181
			'tbrconfig',
1182
			'TBR Size',
1183
			'number',
1184
			$this->GetTbrConfig()
1185
		))->setHelp('Adjusts the size, in bytes, of the token bucket regulator. If not specified, heuristics based on the interface ' .
1186
					'bandwidth are used to determine the size.');
1187

    
1188
		$section->addInput(new Form_Input(
1189
			'interface',
1190
			null,
1191
			'hidden',
1192
			$this->GetInterface()
1193
		));
1194

    
1195
		$section->addInput(new Form_Input(
1196
			'name',
1197
			null,
1198
			'hidden',
1199
			$this->GetQname()
1200
		));
1201

    
1202
		$sform->add($section);
1203

    
1204
		return($sform);
1205
	}
1206

    
1207
	function update_altq_queue_data(&$data) {
1208
		$this->ReadConfig($data);
1209
	}
1210

    
1211
	/*
1212
	 * Should call on each of it queues and subqueues
1213
	 * the same function much like build_rules();
1214
	 */
1215
	function wconfig() {
1216
		$cflink = &get_reference_to_me_in_config($this->GetLink());
1217
		if (!is_array($cflink)) {
1218
			$cflink = array();
1219
		}
1220
		$cflink['interface'] = $this->GetInterface();
1221
		$cflink['name'] = $this->GetQname();
1222
		$cflink['scheduler'] = $this->GetScheduler();
1223
		$cflink['bandwidth'] = $this->GetBandwidth();
1224
		$cflink['bandwidthtype'] = $this->GetBwscale();
1225
		$cflink['qlimit'] = trim($this->GetQlimit());
1226
		if (empty($cflink['qlimit'])) {
1227
			unset($cflink['qlimit']);
1228
		}
1229
		$cflink['tbrconfig'] = trim($this->GetTbrConfig());
1230
		if (empty($cflink['tbrconfig'])) {
1231
			unset($cflink['tbrconfig']);
1232
		}
1233
		$cflink['enabled'] = $this->GetEnabled();
1234
		if (empty($cflink['enabled'])) {
1235
			unset($cflink['enabled']);
1236
		}
1237
	}
1238

    
1239
}
1240

    
1241
class priq_queue {
1242
	var $qname;
1243
	var $qinterface;
1244
	var $qlimit;
1245
	var $qpriority;
1246
	var $description;
1247
	var $isparent;
1248
	var $qbandwidth;
1249
	var $qbandwidthtype;
1250
	var $qdefault = "";
1251
	var $qrio = "";
1252
	var $qred = "";
1253
	var $qcodel = "";
1254
	var $qecn = "";
1255
	var $qack;
1256
	var $qenabled = "";
1257
	var $qparent;
1258
	var $link;
1259

    
1260
	/* This is here to help with form building and building rules/lists */
1261
	var $subqueues = array();
1262

    
1263
	/* Accessor functions */
1264
	function SetLink($link) {
1265
		$this->link = $link;
1266
	}
1267
	function GetLink() {
1268
		return $this->link;
1269
	}
1270
	function &GetParent() {
1271
		return $this->qparent;
1272
	}
1273
	function SetParent(&$parent) {
1274
		$this->qparent = &$parent;
1275
	}
1276
	function GetEnabled() {
1277
		return $this->qenabled;
1278
	}
1279
	function SetEnabled($value) {
1280
		$this->qenabled = $value;
1281
	}
1282
	function CanHaveChildren() {
1283
		return false;
1284
	}
1285
	function CanBeDeleted() {
1286
		return true;
1287
	}
1288
	function GetQname() {
1289
		return $this->qname;
1290
	}
1291
	function SetQname($name) {
1292
		$this->qname = trim($name);
1293
	}
1294
	function GetBandwidth() {
1295
		return $this->qbandwidth;
1296
	}
1297
	function SetBandwidth($bandwidth) {
1298
		$this->qbandwidth = $bandwidth;
1299
	}
1300
	function GetInterface() {
1301
		return $this->qinterface;
1302
	}
1303
	function SetInterface($name) {
1304
		$this->qinterface = trim($name);
1305
	}
1306
	function GetQlimit() {
1307
		return $this->qlimit;
1308
	}
1309
	function SetQlimit($limit) {
1310
		$this->qlimit = $limit;
1311
	}
1312
	function GetQpriority() {
1313
		if (is_numeric($this->qpriority)) {
1314
			return $this->qpriority;
1315
		} else {
1316
			return '1';
1317
		}
1318
	}
1319
	function SetQpriority($priority) {
1320
		$this->qpriority = $priority;
1321
	}
1322
	function GetDescription() {
1323
		return $this->description;
1324
	}
1325
	function SetDescription($str) {
1326
		$this->description = trim($str);
1327
	}
1328
	function GetFirstime() {
1329
		return $this->firsttime;
1330
	}
1331
	function SetFirsttime($number) {
1332
		$this->firsttime = $number;
1333
	}
1334
	function CheckBandwidth($bw, $bwtype) {
1335
		$parent = $this->GetParent();
1336

    
1337
		// If the child queues have bandwidth specified in %, there is no point trying
1338
		// to compare the total to the parent's bandwidth, which is defined in Kb/S or Mb/S etc.
1339
		// ToDo: Add check for total % > 100
1340
		//		 If one child is in %, they must all be
1341

    
1342
		if ($bwtype != "%") {
1343
			$sum = $parent->GetTotalBw(($bw > 0) ? $this : NULL);
1344

    
1345
			if ($bw > 0) {
1346
				$sum += get_bandwidth($bw, $bwtype, $parent);
1347
			}
1348

    
1349
			if ($sum > get_queue_bandwidth($parent)) {
1350
				return 1;
1351
			}
1352
		}
1353

    
1354
		foreach ($this->subqueues as $q) {
1355
			if ($q->CheckBandwidth(0, '')) {
1356
				return 1;
1357
			}
1358
		}
1359

    
1360
		return 0;
1361
	}
1362
	function GetTotalBw($qignore = NULL) {
1363
		$sum = 0;
1364
		foreach ($this->subqueues as $q) {
1365
			if ($qignore != NULL && $qignore == $q)
1366
				continue;
1367
			$sum += get_bandwidth($q->GetBandwidth(), $q->GetBwscale(), $q->GetParent());
1368
		}
1369

    
1370
		return $sum;
1371
	}
1372
	function GetBwscale() {
1373
		return $this->qbandwidthtype;
1374
	}
1375
	function FormGetBwscale() {
1376
		if ($this->GetBwscale()) {
1377
			$bwscale = $this->GetBwscale();
1378
		} else {
1379
			$bwscale = 'Mb';
1380
		}
1381
		return $bwscale;
1382
	}
1383
	function SetBwscale($scale) {
1384
		$this->qbandwidthtype = $scale;
1385
	}
1386
	function GetDefaultQueuePresent() {
1387
		if ($this->GetDefault()) {
1388
			return true;
1389
		}
1390
		if (!empty($this->subqueues)) {
1391
			foreach ($this->subqueues as $q) {
1392
				if ($q->GetDefault()) {
1393
					return true;
1394
				}
1395
			}
1396
		}
1397

    
1398
		return false;
1399
	}
1400
	function GetDefault() {
1401
		return $this->qdefault;
1402
	}
1403
	function SetDefault($value = false) {
1404
		$this->qdefault = $value;
1405
	}
1406
	function GetCodel() {
1407
		return $this->codel;
1408
	}
1409
	function SetCodel($codel = false) {
1410
		$this->codel = $codel;
1411
	}
1412
	function GetRed() {
1413
		return $this->qred;
1414
	}
1415
	function SetRed($red = false) {
1416
		$this->qred = $red;
1417
	}
1418
	function GetRio() {
1419
		return $this->qrio;
1420
	}
1421
	function SetRio($rio = false) {
1422
		$this->qrio = $rio;
1423
	}
1424
	function GetEcn() {
1425
		return $this->qecn;
1426
	}
1427
	function SetEcn($ecn = false) {
1428
		$this->qecn = $ecn;
1429
	}
1430
	function GetAck() {
1431
		return $this->qack;
1432
	}
1433
	function SetAck($ack = false) {
1434
		$this->qack = $ack;
1435
	}
1436

    
1437
	function GetBwscaleText() {
1438
		switch ($this->qbandwidthtype) {
1439
			case "b":
1440
				$bwscaletext = "Bit/s";
1441
				break;
1442
			case "Kb":
1443
				$bwscaletext = "Kbit/s";
1444
				break;
1445
			case "Mb":
1446
				$bwscaletext = "Mbit/s";
1447
				break;
1448
			case "Gb":
1449
				$bwscaletext = "Gbit/s";
1450
				break;
1451
			default:
1452
				/* For others that do not need translating like % */
1453
				$bwscaletext = $this->qbandwidthtype;
1454
				break;
1455
		}
1456
		return $bwscaletext;
1457
	}
1458

    
1459
	function build_javascript() {
1460
		$javascript = "<script type=\"text/javascript\">";
1461
		$javascript .= "//<![CDATA[\n";
1462
		$javascript .= "function mySuspend() { \n";
1463
		$javascript .= "if (document.layers && document.layers['shaperarea'] != null)\n";
1464
		$javascript .= "document.layers['shaperarea'].visibility = 'hidden';\n";
1465
		$javascript .= "else if (document.all)\n";
1466
		$javascript .= "document.all['shaperarea'].style.visibility = 'hidden';\n";
1467
		$javascript .= "}\n";
1468

    
1469
		$javascript .= "function myResume() {\n";
1470
		$javascript .= "if (document.layers && document.layers['shaperarea'] != null)\n";
1471
		$javascript .= "document.layers['shaperarea'].visibility = 'visible';\n";
1472
		$javascript .= "else if (document.all)\n";
1473
		$javascript .= "document.all['shaperarea'].style.visibility = 'visible';\n";
1474
		$javascript .= "}\n";
1475
		$javascript .= "//]]>";
1476
		$javascript .= "</script>";
1477

    
1478
		return $javascript;
1479
	}
1480

    
1481
	function &add_queue($interface, &$qname, &$path, &$input_errors) { return; }
1482

    
1483
	/*
1484
	 * Currently this will not be called unless we decide to clone a whole
1485
	 * queue tree on the 'By Queues' view or support drag&drop on the tree/list
1486
	 */
1487
	function copy_queue($interface, &$cflink) {
1488

    
1489
		$cflink['name'] = $this->GetQname();
1490
		$cflink['interface'] = $interface;
1491
		$cflink['qlimit'] = $this->GetQlimit();
1492
		$cflink['priority'] = $this->GetQpriority();
1493
		$cflink['description'] = $this->GetDescription();
1494
		$cflink['enabled'] = $this->GetEnabled();
1495
		$cflink['default'] = $this->GetDefault();
1496
		$cflink['red'] = $this->GetRed();
1497
		$cflink['codel'] = $this->GetCodel();
1498
		$cflink['rio'] = $this->GetRio();
1499
		$cflink['ecn'] = $this->GetEcn();
1500

    
1501
		if (is_array($this->subqueues)) {
1502
			$cflinkp['queue'] = array();
1503
			foreach ($this->subqueues as $q) {
1504
				$cflink['queue'][$q->GetQname()] = array();
1505
				$q->copy_queue($interface, $cflink['queue'][$q->GetQname()]);
1506
			}
1507
		}
1508
	}
1509

    
1510
	function clean_queue($sched) {
1511
		clean_child_queues($sched, $this->GetLink());
1512
		if (is_array($this->subqueues)) {
1513
			foreach ($this->subqueues as $q) {
1514
				$q->clean_queue($sched);
1515
			}
1516
		}
1517
	}
1518

    
1519
	function &get_queue_list(&$qlist) {
1520

    
1521
		$qlist[$this->GetQname()] = & $this;
1522
		if (is_array($this->subqueues)) {
1523
			foreach ($this->subqueues as $queue) {
1524
				$queue->get_queue_list($qlist);
1525
			}
1526
		}
1527
	}
1528

    
1529
	function delete_queue() {
1530
		unref_on_altq_queue_list($this->GetQname());
1531
		cleanup_queue_from_rules($this->GetQname());
1532
		unset_object_by_reference($this->GetLink());
1533
	}
1534

    
1535
	function delete_all() {
1536
		if (count($this->subqueues)) {
1537
			foreach ($this->subqueues as $q) {
1538
				$q->delete_all();
1539
				unset_object_by_reference($q->GetLink());
1540
				unset($q);
1541
			}
1542
			unset($this->subqueues);
1543
		}
1544
	}
1545

    
1546
	function &find_queue($interface, $qname) {
1547
		if ($qname == $this->GetQname()) {
1548
			return $this;
1549
		}
1550
	}
1551

    
1552
	function find_parentqueue($interface, $qname) { return; }
1553

    
1554
	function validate_input($data, &$input_errors) {
1555
		global $altq_list_queues;
1556

    
1557
		$parent = $altq_list_queues[$this->GetInterface()];
1558

    
1559
		if ($data['bandwidth'] && !is_numeric($data['bandwidth'])) {
1560
			$input_errors[] = gettext("Bandwidth must be an integer.");
1561
		}
1562
		if ($data['bandwidth'] < 0) {
1563
			$input_errors[] = gettext("Bandwidth cannot be negative.");
1564
		}
1565
		if ($data['bandwidthtype'] == "%") {
1566
			if (($data['bandwidth'] > 100) || ($data['bandwidth'] < 0)) {
1567
				$input_errors[] = gettext("Bandwidth in percentage should be between 1 and 100.");
1568
			}
1569
		}
1570
		if ($this->CheckBandwidth($data['bandwidth'], $data['bandwidthtype']))
1571
			$input_errors[] = "The sum of child bandwidth is higher than parent.";
1572

    
1573
		if ($parent) {
1574
			if (($parent->GetScheduler() == 'PRIQ') && (!is_numeric($data['priority']) ||
1575
			    ($data['priority'] < 0) || ($data['priority'] > 15))) {
1576
				$input_errors[] = gettext("The priority must be an integer between 0 and 15.");
1577
			} elseif (in_array($parent->GetScheduler(), array('FAIRQ','CBQ')) && (!is_numeric($data['priority']) ||
1578
			    ($data['priority'] < 0) || ($data['priority'] > 7))) {
1579
				$input_errors[] = gettext("The priority must be an integer between 0 and 7.");
1580
			}
1581
			if (is_array($parent->queues)) {
1582
				foreach ($parent->queues as $q) {
1583
					if (($data['newname'] == $q->GetQname()) && ($data['name'] != $data['newname'])) { 
1584
						$input_errors[] = gettext("A queue with the same name already exists.");
1585
					}
1586
					if (in_array($parent->GetScheduler(), array('FAIRQ','PRIQ')) &&
1587
					    ($data['priority'] == $q->GetQpriority()) && ($data['name'] != $q->GetQname())) { 
1588
						$input_errors[] = sprintf(gettext("The priority %d is already used by queue %s."),
1589
								$data['priority'], $q->GetQname());
1590
					}
1591
				}
1592
			}
1593
		}
1594

    
1595
		if ($data['qlimit'] && (!is_numeric($data['qlimit']))) {
1596
				$input_errors[] = gettext("Queue limit must be an integer");
1597
		}
1598
		if ($data['qlimit'] < 0) {
1599
				$input_errors[] = gettext("Queue limit must be positive");
1600
		}
1601
		if (!empty($data['newname']) && !preg_match("/^[a-zA-Z0-9_-]*$/", $data['newname'])) {
1602
			$input_errors[] = gettext("Queue names must be alphanumeric and _ or - only.");
1603
		}
1604
		if (!empty($data['name']) && !preg_match("/^[a-zA-Z0-9_-]*$/", $data['name'])) {
1605
			$input_errors[] = gettext("Queue names must be alphanumeric and _ or - only.");
1606
		}
1607
		$default = $this->GetDefault();
1608
		if (!empty($data['default']) && altq_get_default_queue($data['interface']) && empty($default)) {
1609
			$input_errors[] = gettext("Only one default queue per interface is allowed.");
1610
		}
1611
	}
1612

    
1613
	function ReadConfig(&$q) {
1614
		if (!empty($q['name']) && !empty($q['newname']) && ($q['name'] != $q['newname'])) {
1615
			$this->SetQname($q['newname']);
1616
			rename_queue_in_rules($q['name'], $q['newname']);
1617
		} else if (!empty($q['newname'])) {
1618
			$this->SetQname($q['newname']);
1619
		} elseif (isset($q['name'])) {
1620
			$this->SetQname($q['name']);
1621
		}
1622
		if (isset($q['interface'])) {
1623
			$this->SetInterface($q['interface']);
1624
		}
1625
		$this->SetBandwidth($q['bandwidth']);
1626
		if (!empty($q['bandwidthtype'])) {
1627
			$this->SetBwscale($q['bandwidthtype']);
1628
		}
1629
		if (!empty($q['qlimit'])) {
1630
			$this->SetQlimit($q['qlimit']);
1631
		} else {
1632
			$this->SetQlimit(""); // Default
1633
		}
1634
		if (is_numeric($q['priority'])) {
1635
			$this->SetQPriority($q['priority']);
1636
		} else {
1637
			$this->SetQpriority("");
1638
		}
1639
		if (!empty($q['description'])) {
1640
			$this->SetDescription($q['description']);
1641
		} else {
1642
			$this->SetDescription("");
1643
		}
1644
		if (!empty($q['red'])) {
1645
			$this->SetRed($q['red']);
1646
		} else {
1647
			$this->SetRed();
1648
		}
1649
		if (!empty($q['codel'])) {
1650
			$this->SetCodel($q['codel']);
1651
		} else {
1652
			$this->SetCodel();
1653
		}
1654
		if (!empty($q['rio'])) {
1655
			$this->SetRio($q['rio']);
1656
		} else {
1657
			$this->SetRio();
1658
		}
1659
		if (!empty($q['ecn'])) {
1660
			$this->SetEcn($q['ecn']);
1661
		} else {
1662
			$this->SetEcn();
1663
		}
1664
		if (!empty($q['default'])) {
1665
			$this->SetDefault($q['default']);
1666
		} else {
1667
			$this->SetDefault();
1668
		}
1669
		if (!empty($q['enabled'])) {
1670
			$this->SetEnabled($q['enabled']);
1671
		} else {
1672
			$this->SetEnabled("");
1673
		}
1674
	}
1675

    
1676
	function build_tree() {
1677
		$tree = " <li><a href=\"firewall_shaper.php?interface=". $this->GetInterface()."&amp;queue=". htmlspecialchars($this->GetQname())."&amp;action=show";
1678
		$tree .= "\" ";
1679
		$tmpvalue = $this->GetDefault();
1680
		if (!empty($tmpvalue)) {
1681
			$tree .= " class=\"navlnk\"";
1682
		}
1683
		$tree .= " >" . htmlspecialchars($this->GetQname()) . "</a>";
1684
		/*
1685
		 * Not needed here!
1686
		 * if (is_array($queues) {
1687
		 *	  $tree .= "<ul>";
1688
		 *	  foreach ($q as $queues)
1689
		 *		  $tree .= $queues['$q->GetName()']->build_tree();
1690
		 *	  endforeach
1691
		 *	  $tree .= "</ul>";
1692
		 * }
1693
		 */
1694

    
1695
		$tree .= "</li>";
1696

    
1697
		return $tree;
1698
	}
1699

    
1700
	/* Should return something like:
1701
	 * queue $qname on $qinterface bandwidth ....
1702
	 */
1703
	function build_rules(&$default = false) {
1704
		$pfq_rule = " queue ". $this->qname;
1705
		if ($this->GetInterface()) {
1706
			$pfq_rule .= " on ".get_real_interface($this->GetInterface());
1707
		}
1708
		$tmpvalue = $this->GetQpriority();
1709
		if (is_numeric($tmpvalue)) {
1710
			$pfq_rule .= " priority ".$this->GetQpriority();
1711
		}
1712
		$tmpvalue = $this->GetQlimit();
1713
		if (!empty($tmpvalue)) {
1714
			$pfq_rule .= " qlimit " . $this->GetQlimit();
1715
		}
1716
		if ($this->GetRed() || $this->GetRio() || $this->GetEcn() || $this->GetDefault() || $this->GetCodel()) {
1717
			$pfq_rule .= " priq ( ";
1718
			$tmpvalue = $this->GetRed();
1719
			if (!empty($tmpvalue)) {
1720
				$comma = 1;
1721
				$pfq_rule .= " red ";
1722
			}
1723
			$tmpvalue = $this->GetRio();
1724
			if (!empty($tmpvalue)) {
1725
				if ($comma) {
1726
					$pfq_rule .= " ,";
1727
				}
1728
				$comma = 1;
1729
				$pfq_rule .= " rio ";
1730
			}
1731
			$tmpvalue = $this->GetEcn();
1732
			if (!empty($tmpvalue)) {
1733
				if ($comma) {
1734
					$pfq_rule .= " ,";
1735
				}
1736
				$comma = 1;
1737
				$pfq_rule .= " ecn ";
1738
			}
1739
			$tmpvalue = $this->GetCodel();
1740
			if (!empty($tmpvalue)) {
1741
				if ($comma) {
1742
					$pfq_rule .= " ,";
1743
				}
1744
				$comma = 1;
1745
				$pfq_rule .= " codel ";
1746
			}
1747
			$tmpvalue = $this->GetDefault();
1748
			if (!empty($tmpvalue)) {
1749
				if ($comma) {
1750
					$pfq_rule .= " ,";
1751
				}
1752
				$pfq_rule .= " default ";
1753
				$default = true;
1754
			}
1755
			$pfq_rule .= " ) ";
1756
		}
1757

    
1758
		$pfq_rule .= " \n";
1759

    
1760
		return $pfq_rule;
1761
	}
1762

    
1763
	/*
1764
	 * To return the html form to show to user
1765
	 * for getting the parameters.
1766
	 * Should do even for first time when the
1767
	 * object is created and later when we may
1768
	 * need to update it. (2)
1769
	 */
1770

    
1771
	function build_form() {
1772

    
1773
		$sform = new Form();
1774

    
1775
		$sform->setAction("firewall_shaper.php");
1776

    
1777
		$section = new Form_Section("");
1778

    
1779
		$section->addInput(new Form_Checkbox(
1780
			'enabled',
1781
			'Enable/Disable',
1782
			'Enable/disable discipline and its children',
1783
			($this->GetEnabled() == "on"),
1784
			'on'
1785
		));
1786

    
1787
		$section->addInput(new Form_Input(
1788
			'newname',
1789
			'*Name',
1790
			'text',
1791
			$this->GetQname()
1792
		))->setHelp('Enter the name of the queue here. Do not use spaces and limit the size to 15 characters.');
1793

    
1794
		$section->addInput(new Form_Input(
1795
			'name',
1796
			null,
1797
			'hidden',
1798
			$this->GetQname()
1799
		));
1800

    
1801
		if (!is_a($this, "hfsc_queue")) {
1802
			$section->addInput(new Form_Input(
1803
				'priority',
1804
				'Priority',
1805
				'number',
1806
				$this->GetQpriority(),
1807
				['min' => '0', 'max'=> '']
1808
			))->setHelp('For cbq and fairq the range is 0 to 7. The default is 1. For priq the range is 0 to 15, queues with a higher priority are preferred in the case of overload.');
1809
		}
1810

    
1811
		$section->addInput(new Form_Input(
1812
			'qlimit',
1813
			'Queue Limit',
1814
			'number',
1815
			$this->GetQlimit()
1816
		))->setHelp('Queue limit in packets.');
1817

    
1818
		$group = new Form_Group('Scheduler options');
1819

    
1820
		if (empty($this->subqueues)) {
1821
			$group->add(new Form_Checkbox(
1822
				'default',
1823
				null,
1824
				null,
1825
				$this->GetDefault(),
1826
				'default'
1827
			))->setHelp('Default Queue');
1828
		}
1829

    
1830
		$group->add(new Form_Checkbox(
1831
			'red',
1832
			null,
1833
			null,
1834
			!empty($this->GetRed())
1835
		))->setHelp('%1$sRandom Early Detection%2$s', '<a target="_new" href="https://docs.netgate.com/pfsense/en/latest/trafficshaper/advanced.html#editing-shaper-queues">', '</a>');
1836

    
1837
		$group->add(new Form_Checkbox(
1838
			'rio',
1839
			null,
1840
			null,
1841
			!empty($this->GetRio())
1842
		))->setHelp('%1$sRandom Early Detection In and Out%2$s', '<a target="_new" href="https://docs.netgate.com/pfsense/en/latest/trafficshaper/advanced.html#editing-shaper-queues">', '</a>');
1843

    
1844
		$group->add(new Form_Checkbox(
1845
			'ecn',
1846
			null,
1847
			null,
1848
			!empty($this->GetEcn())
1849
		))->setHelp('%1$sExplicit Congestion Notification%2$s', '<a target="_new" href="https://docs.netgate.com/pfsense/en/latest/trafficshaper/advanced.html#editing-shaper-queues">', '</a>');
1850

    
1851
		$group->add(new Form_Checkbox(
1852
			'codel',
1853
			null,
1854
			null,
1855
			!empty($this->GetCodel())
1856
		))->setHelp('%1$sCodel Active Queue%2$s', '<a target="_new" href="https://docs.netgate.com/pfsense/en/latest/trafficshaper/altq-scheduler-types.html#codel-active-queue-management">', '</a>');
1857

    
1858
		$group->setHelp('Select options for this queue');
1859

    
1860
		$section->add($group);
1861

    
1862
		$section->addInput(new Form_Input(
1863
			'description',
1864
			'Description',
1865
			'text',
1866
			$this->GetDescription()
1867
		));
1868

    
1869
		$sform->add($section);
1870

    
1871
		$sform->addGlobal(new Form_Input(
1872
			'interface',
1873
			null,
1874
			'hidden',
1875
			$this->GetInterface()
1876
		));
1877

    
1878
		$sform->addGlobal(new Form_Input(
1879
			'name',
1880
			null,
1881
			'hidden',
1882
			$this->GetQname()
1883
		));
1884

    
1885
		return($sform);
1886
	}
1887

    
1888
	function build_shortform() {
1889
		/* XXX: Hacks in sight. Mostly layer violations!  */
1890
		global $g, $altq_list_queues;
1891
		global $shaperIFlist;
1892

    
1893
		$altq =& $altq_list_queues[$this->GetInterface()];
1894

    
1895
		if ($altq) {
1896
			$scheduler = $altq->GetScheduler();
1897
		}
1898

    
1899
		$form = '<dl class="dl-horizontal">';
1900
		$form .= '	<dt>';
1901
		$form .= '		<a href="firewall_shaper.php?interface=' . $this->GetInterface() . '&amp;queue=' . $this->GetQname() . '&amp;action=show">' . $shaperIFlist[$this->GetInterface()] . '</a>';
1902
		$form .= '	</dt>';
1903
		$form .= '	<dd>';
1904
		$form .=		$scheduler;
1905
		$form .= '	</dd>';
1906

    
1907
		$form .= '	<dt>';
1908
		$form .=		'Bandwidth';
1909
		$form .= '	</dt>';
1910
		$form .= '	<dd>';
1911
		$form .=		$this->GetBandwidth() . '&nbsp;' . $this->GetBwscaleText();
1912
		$form .= '	</dd>';
1913

    
1914
		$tmpvalue = $this->GetQpriority();
1915
		if (!empty($tmpvalue)) {
1916
			$form .= '	<dt>';
1917
			$form .=		'Priority';
1918
			$form .= '	<dt>';
1919
			$form .= '	<dd>';
1920
			$form .=		'On';
1921
			$form .= '	</dd>';
1922
		}
1923

    
1924
		$tmpvalue = $this->GetDefault();
1925
		if (!empty($tmpvalue)) {
1926
			$form .= '	<dt>';
1927
			$form .=		'Default';
1928
			$form .= '	<dt>';
1929
			$form .= '	<dd>';
1930
			$form .=		'On';
1931
			$form .= '	</dd>';
1932
		}
1933

    
1934
			$form .= '	<dt>';
1935
			$form .= 'Delete';
1936
			$form .= '	<dt>';
1937
			$form .= '	<dd>';
1938

    
1939
			$form .= '<a class="btn btn-danger btn-xs" href="firewall_shaper_queues.php?interface=';
1940
			$form .= $this->GetInterface() . '&amp;queue=';
1941
			$form .= $this->GetQname() . '&amp;action=delete">';
1942
			$form .= '<i class="fa-solid fa-trash-can icon-embed-btn"></i>';
1943
			$form .= gettext("Delete Queue from this Interface") . '</a>';
1944

    
1945
			$form .= '	</dd>';
1946

    
1947
			$form .= '</dl>';
1948

    
1949
		return $form;
1950

    
1951
	}
1952

    
1953
	function update_altq_queue_data(&$q) {
1954
		$this->ReadConfig($q);
1955
	}
1956

    
1957
	function wconfig() {
1958
		$cflink =& get_reference_to_me_in_config($this->GetLink());
1959
		if (!is_array($cflink)) {
1960
			$cflink = array();
1961
		}
1962
		$cflink['name'] = $this->GetQname();
1963
		$cflink['interface'] = $this->GetInterface();
1964
		$cflink['qlimit'] = trim($this->GetQlimit());
1965
		if (empty($cflink['qlimit'])) {
1966
			unset($cflink['qlimit']);
1967
		}
1968
		$cflink['priority'] = trim($this->GetQpriority());
1969
		if (!is_numeric($cflink['priority'])) {
1970
			unset($cflink['priority']);
1971
		}
1972
		$cflink['description'] = trim($this->GetDescription());
1973
		if (empty($cflink['description'])) {
1974
			unset($cflink['description']);
1975
		}
1976
		$cflink['enabled'] = trim($this->GetEnabled());
1977
		if (empty($cflink['enabled'])) {
1978
			unset($cflink['enabled']);
1979
		}
1980
		$cflink['default'] = trim($this->GetDefault());
1981
		if (empty($cflink['default'])) {
1982
			unset($cflink['default']);
1983
		}
1984
		$cflink['red'] = trim($this->GetRed());
1985
		if (empty($cflink['red'])) {
1986
			unset($cflink['red']);
1987
		}
1988
		$cflink['codel'] = trim($this->GetCodel());
1989
		if (empty($cflink['codel'])) {
1990
			unset($cflink['codel']);
1991
		}
1992
		$cflink['rio'] = trim($this->GetRio());
1993
		if (empty($cflink['rio'])) {
1994
			unset($cflink['rio']);
1995
		}
1996
		$cflink['ecn'] = trim($this->GetEcn());
1997
		if (empty($cflink['ecn'])) {
1998
			unset($cflink['ecn']);
1999
		}
2000
	}
2001
}
2002

    
2003
class hfsc_queue extends priq_queue {
2004
	/* realtime */
2005
	var $realtime;
2006
	var $r_m1;
2007
	var $r_d;
2008
	var $r_m2;
2009
	/* linkshare */
2010
	var $linkshare;
2011
	var $l_m1;
2012
	var $l_d;
2013
	var $l_m2;
2014
	/* upperlimit */
2015
	var $upperlimit;
2016
	var $u_m1;
2017
	var $u_d;
2018
	var $u_m2;
2019

    
2020
	/*
2021
	 * HFSC can have nested queues.
2022
	 */
2023
	function CanHaveChildren() {
2024
		return true;
2025
	}
2026
	function GetRealtime() {
2027
		return $this->realtime;
2028
	}
2029
	function GetR_m1() {
2030
		return $this->r_m1;
2031
	}
2032
	function GetR_d() {
2033
		return $this->r_d;
2034
	}
2035
	function GetR_m2() {
2036
		return $this->r_m2;
2037
	}
2038
	function SetRealtime() {
2039
		$this->realtime = "on";
2040
	}
2041
	function DisableRealtime() {
2042
		$this->realtime = "";
2043
	}
2044
	function SetR_m1($value) {
2045
		$this->r_m1 = $value;
2046
	}
2047
	function SetR_d($value) {
2048
		$this->r_d = $value;
2049
	}
2050
	function SetR_m2($value) {
2051
		$this->r_m2 = $value;
2052
	}
2053
	function GetLinkshare() {
2054
		return $this->linkshare;
2055
	}
2056
	function DisableLinkshare() {
2057
		$this->linkshare = "";
2058
	}
2059
	function GetL_m1() {
2060
		return $this->l_m1;
2061
	}
2062
	function GetL_d() {
2063
		return $this->l_d;
2064
	}
2065
	function GetL_m2() {
2066
		return $this->l_m2;
2067
	}
2068
	function SetLinkshare() {
2069
		$this->linkshare = "on";
2070
	}
2071
	function SetL_m1($value) {
2072
		$this->l_m1 = $value;
2073
	}
2074
	function SetL_d($value) {
2075
		$this->l_d = $value;
2076
	}
2077
	function SetL_m2($value) {
2078
		$this->l_m2 = $value;
2079
	}
2080
	function GetUpperlimit() {
2081
		return $this->upperlimit;
2082
	}
2083
	function GetU_m1() {
2084
		return $this->u_m1;
2085
	}
2086
	function GetU_d() {
2087
		return $this->u_d;
2088
	}
2089
	function GetU_m2() {
2090
		return $this->u_m2;
2091
	}
2092
	function SetUpperlimit() {
2093
		$this->upperlimit = "on";
2094
	}
2095
	function DisableUpperlimit() {
2096
		$this->upperlimit = "";
2097
	}
2098
	function SetU_m1($value) {
2099
		$this->u_m1 = $value;
2100
	}
2101
	function SetU_d($value) {
2102
		$this->u_d = $value;
2103
	}
2104
	function SetU_m2($value) {
2105
		$this->u_m2 = $value;
2106
	}
2107

    
2108
	function &add_queue($interface, &$qname, &$path, &$input_errors) {
2109

    
2110
		if (!is_array($this->subqueues)) {
2111
			$this->subqueues = array();
2112
		}
2113
		$__tmp_q = new hfsc_queue(); $q =& $__tmp_q;
2114
		$q->SetInterface($this->GetInterface());
2115
		$q->SetParent($this);
2116
		$q->ReadConfig($qname);
2117
		$q->validate_input($qname, $input_errors);
2118

    
2119
		$q->SetEnabled("on");
2120
		$q->SetLink($path);
2121

    
2122
		$this->subqueues[$q->GetQname()] =& $q; //new hfsc_queue()
2123
		ref_on_altq_queue_list($this->GetQname(), $q->GetQname());
2124
		if (is_array($qname['queue'])) {
2125
			foreach ($qname['queue'] as $key1 => $que) {
2126
				array_push($path, $key1);
2127
				$q->add_queue($q->GetInterface(), $que, $path, $input_errors);
2128
				array_pop($path);
2129
			}
2130
		}
2131

    
2132
		return $q;
2133
	}
2134

    
2135
	function copy_queue($interface, &$cflink) {
2136

    
2137
		$cflink['name'] = $this->GetQname();
2138
		$cflink['interface'] = $interface;
2139
		$cflink['qlimit'] = trim($this->GetQlimit());
2140
		if (empty($cflink['qlimit'])) {
2141
			unset($cflink['qlimit']);
2142
		}
2143
		$cflink['priority'] = trim($this->GetQpriority());
2144
		if (empty($cflink['priority'])) {
2145
			unset($cflink['priority']);
2146
		}
2147
		$cflink['description'] = trim($this->GetDescription());
2148
		if (empty($cflink['description'])) {
2149
			unset($cflink['description']);
2150
		}
2151
		$cflink['bandwidth'] = $this->GetBandwidth();
2152
		$cflink['bandwidthtype'] = $this->GetBwscale();
2153
		$cflink['enabled'] = trim($this->GetEnabled());
2154
		if (empty($cflink['enabled'])) {
2155
			unset($cflink['enabled']);
2156
		}
2157
		$cflink['default'] = trim($this->GetDefault());
2158
		if (empty($cflink['default'])) {
2159
			unset($cflink['default']);
2160
		}
2161
		$cflink['red'] = trim($this->GetRed());
2162
		if (empty($cflink['red'])) {
2163
			unset($cflink['red']);
2164
		}
2165
		$cflink['rio'] = trim($this->GetRio());
2166
		if (empty($cflink['rio'])) {
2167
			unset($cflink['rio']);
2168
		}
2169
		$cflink['ecn'] = trim($this->GetEcn());
2170
		if (empty($cflink['ecn'])) {
2171
			unset($cflink['ecn']);
2172
		}
2173
		if ($this->GetLinkshare() <> "") {
2174
			if ($this->GetL_m1() <> "") {
2175
				$cflink['linkshare1'] = $this->GetL_m1();
2176
				$cflink['linkshare2'] = $this->GetL_d();
2177
				$cflink['linkshare'] = "on";
2178
			} else {
2179
				unset($cflink['linkshare1']);
2180
				unset($cflink['linkshare2']);
2181
				unset($cflink['linkshare']);
2182
			}
2183
			if ($this->GetL_m2() <> "") {
2184
				$cflink['linkshare3'] = $this->GetL_m2();
2185
				$cflink['linkshare'] = "on";
2186
			} else {
2187
				unset($cflink['linkshare3']);
2188
				unset($cflink['linkshare']);
2189
			}
2190
		}
2191
		if ($this->GetRealtime() <> "") {
2192
			if ($this->GetR_m1() <> "") {
2193
				$cflink['realtime1'] = $this->GetR_m1();
2194
				$cflink['realtime2'] = $this->GetR_d();
2195
				$cflink['realtime'] = "on";
2196
			} else {
2197
				unset($cflink['realtime1']);
2198
				unset($cflink['realtime2']);
2199
				unset($cflink['realtime']);
2200
			}
2201
			if ($this->GetR_m2() <> "") {
2202
				$cflink['realtime3'] = $this->GetR_m2();
2203
				$cflink['realtime'] = "on";
2204
			} else {
2205
				unset($cflink['realtime3']);
2206
				unset($cflink['realtime']);
2207
			}
2208
		}
2209
		if ($this->GetUpperlimit() <> "") {
2210
			if ($this->GetU_m1() <> "") {
2211
				$cflink['upperlimit1'] = $this->GetU_m1();
2212
				$cflink['upperlimit2'] = $this->GetU_d();
2213
				$cflink['upperlimit'] = "on";
2214
			} else {
2215
				unset($cflink['upperlimit']);
2216
				unset($cflink['upperlimit1']);
2217
				unset($cflink['upperlimit2']);
2218
			}
2219
			if ($this->GetU_m2() <> "") {
2220
				$cflink['upperlimit3'] = $this->GetU_m2();
2221
				$cflink['upperlimit'] = "on";
2222
			} else {
2223
				unset($cflink['upperlimit3']);
2224
				unset($cflink['upperlimit']);
2225
			}
2226
		}
2227

    
2228
		if (is_array($this->subqueues)) {
2229
			$cflinkp['queue'] = array();
2230
			foreach ($this->subqueues as $q) {
2231
				$cflink['queue'][$q->GetQname()] = array();
2232
				$q->copy_queue($interface, $cflink['queue'][$q->GetQname()]);
2233
			}
2234
		}
2235
	}
2236

    
2237
	function delete_queue() {
2238
		unref_on_altq_queue_list($this->GetQname());
2239
		cleanup_queue_from_rules($this->GetQname());
2240
		$parent =& $this->GetParent();
2241
		foreach ($this->subqueues as $q) {
2242
			$q->delete_queue();
2243
		}
2244
		unset_object_by_reference($this->GetLink());
2245
	}
2246

    
2247
	/*
2248
	 * Should search even its children
2249
	 */
2250
	function &find_queue($interface, $qname) {
2251
		if ($qname == $this->GetQname()) {
2252
			return $this;
2253
		}
2254

    
2255
		foreach ($this->subqueues as $q) {
2256
			$result =& $q->find_queue("", $qname);
2257
			if ($result) {
2258
				return $result;
2259
			}
2260
		}
2261
	}
2262

    
2263
	function &find_parentqueue($interface, $qname) {
2264
		if ($this->subqueues[$qname]) {
2265
			return $this;
2266
		}
2267
		foreach ($this->subqueues as $q) {
2268
			$result = $q->find_parentqueue("", $qname);
2269
			if ($result) {
2270
				return $result;
2271
			}
2272
		}
2273
	}
2274

    
2275
	function validate_input($data, &$input_errors) {
2276
		parent::validate_input($data, $input_errors);
2277

    
2278
		if (isset($data['linkshare3']) && !empty($data['linkshare3'])) {
2279
			if (isset($data['bandwidth'])) {
2280
				if (!is_numeric($data['bandwidth'])) {
2281
					$input_errors[] = gettext("Bandwidth must be an integer.");
2282
				}
2283
				if ((int)$data['bandwidth'] < 0) {
2284
					$input_errors[] = gettext("Bandwidth cannot be negative.");
2285
				}
2286
			}
2287

    
2288
			if (isset($data['bandwidthtype']) && ($data['bandwidthtype'] == "%")) {
2289
				if (($data['bandwidth'] > 100) || ($data['bandwidth'] < 0)) {
2290
					$input_errors[] = gettext("Bandwidth in percentage should be between 1 and 100.");
2291
				}
2292
			}
2293
		}
2294

    
2295
		if (!empty($data['upperlimit1']) && empty($data['upperlimit2'])) {
2296
			$input_errors[] = gettext("upperlimit service curve defined but missing (d) value");
2297
		}
2298
		if (!empty($data['upperlimit2']) && empty($data['upperlimit1'])) {
2299
			$input_errors[] = gettext("upperlimit service curve defined but missing initial bandwidth (m1) value");
2300
		}
2301
		if (!empty($data['upperlimit1']) && !is_valid_shaperbw($data['upperlimit1'])) {
2302
			$input_errors[] = gettext("upperlimit m1 value needs to be Kb, Mb, Gb, or %");
2303
		}
2304
		if (!empty($data['upperlimit2']) && !is_numeric($data['upperlimit2'])) {
2305
			$input_errors[] = gettext("upperlimit d value needs to be numeric");
2306
		}
2307
		if (!empty($data['upperlimit3']) && !is_valid_shaperbw($data['upperlimit3'])) {
2308
			$input_errors[] = gettext("upperlimit m2 value needs to be Kb, Mb, Gb, or %");
2309
		}
2310

    
2311
		/*
2312
		if (isset($data['upperlimit']) && $data['upperlimit3'] <> "" && $data['upperlimit1'] <> "") {
2313
			$bw_1 = get_hfsc_bandwidth($this, $data['upperlimit1']);
2314
			$bw_2 = get_hfsc_bandwidth($this, $data['upperlimit3']);
2315
			if (floatval($bw_1) < floatval($bw_2)) {
2316
				$input_errors[] = ("upperlimit m1 cannot be smaller than m2");
2317
			}
2318

    
2319
			if (get_interface_bandwidth($this) < (0.8 * (floatval($bw_1) + floatval($bw_2)))) {
2320
				$input_errors[] = ("upperlimit specification exceeds 80% of allowable allocation.");
2321
			}
2322
		}
2323
		*/
2324
		if ($data['linkshare1'] <> "" && $data['linkshare2'] == "") {
2325
			$input_errors[] = gettext("linkshare service curve defined but missing (d) value");
2326
		}
2327
		if ($data['linkshare2'] <> "" && $data['linkshare1'] == "") {
2328
			$input_errors[] = gettext("linkshare service curve defined but missing initial bandwidth (m1) value");
2329
		}
2330
		if ($data['linkshare1'] <> "" && !is_valid_shaperbw($data['linkshare1'])) {
2331
			$input_errors[] = gettext("linkshare m1 value needs to be Kb, Mb, Gb, or %");
2332
		}
2333
		if ($data['linkshare2'] <> "" && !is_numeric($data['linkshare2'])) {
2334
			$input_errors[] = gettext("linkshare d value needs to be numeric");
2335
		}
2336
		if ($data['linkshare3'] <> "" && !is_valid_shaperbw($data['linkshare3'])) {
2337
			$input_errors[] = gettext("linkshare m2 value needs to be Kb, Mb, Gb, or %");
2338
		}
2339
		if ($data['realtime1'] <> "" && $data['realtime2'] == "") {
2340
			$input_errors[] = gettext("realtime service curve defined but missing (d) value");
2341
		}
2342
		if ($data['realtime2'] <> "" && $data['realtime1'] == "") {
2343
			$input_errors[] = gettext("realtime service curve defined but missing initial bandwidth (m1) value");
2344
		}
2345

    
2346
		/*
2347
		if (isset($data['linkshare']) && $data['linkshare3'] <> "" && $data['linkshare1'] <> "" && 0) {
2348
			$bw_1 = get_hfsc_bandwidth($this, $data['linkshare1']);
2349
			$bw_2 = get_hfsc_bandwidth($this, $data['linkshare3']);
2350
			if (floatval($bw_1) < floatval($bw_2)) {
2351
				$input_errors[] = ("linkshare m1 cannot be smaller than m2");
2352
			}
2353

    
2354
			if (get_interface_bandwidth($this) < (0.8 * (floatval($bw_1) + floatval($bw_2)))) {
2355
				$input_errors[] = ("linkshare specification exceeds 80% of allowable allocation.");
2356
			}
2357
		}
2358
		*/
2359

    
2360
		if ($data['realtime1'] <> "" && !is_valid_shaperbw($data['realtime1'])) {
2361
			$input_errors[] = gettext("realtime m1 value needs to be Kb, Mb, Gb, or %");
2362
		}
2363
		if ($data['realtime2'] <> "" && !is_numeric($data['realtime2'])) {
2364
			$input_errors[] = gettext("realtime d value needs to be numeric");
2365
		}
2366
		if ($data['realtime3'] <> "" && !is_valid_shaperbw($data['realtime3'])) {
2367
			$input_errors[] = gettext("realtime m2 value needs to be Kb, Mb, Gb, or %");
2368
		}
2369

    
2370
		/*
2371
		if (isset($data['realtime']) && $data['realtime3'] <> "" && $data['realtime1'] <> "" && 0) {
2372
			$bw_1 = get_hfsc_bandwidth($this, $data['realtime1']);
2373
			$bw_2 = get_hfsc_bandwidth($this, $data['realtime3']);
2374
			if (floatval($bw_1) < floatval($bw_2)) {
2375
				$input_errors[] = ("realtime m1 cannot be smaller than m2");
2376
			}
2377

    
2378
			if (get_interface_bandwidth($this) < (0.8 * (floatval($bw_1) + floatval($bw_2)))) {
2379
				$input_errors[] = ("realtime specification exceeds 80% of allowable allocation.");
2380
			}
2381
		}
2382
		*/
2383
	}
2384

    
2385
	function ReadConfig(&$cflink) {
2386
		if (!empty($cflink['linkshare'])) {
2387
			if (!empty($cflink['linkshare1'])) {
2388
				$this->SetL_m1($cflink['linkshare1']);
2389
				$this->SetL_d($cflink['linkshare2']);
2390
				$this->SetLinkshare();
2391
			} else {
2392
				$this->SetL_m1("");
2393
				$this->SetL_d("");
2394
				$this->DisableLinkshare();
2395
			}
2396
			if (!empty($cflink['linkshare3'])) {
2397
				$this->SetL_m2($cflink['linkshare3']);
2398
				$this->SetLinkshare();
2399
			}
2400
		} else {
2401
			$this->DisableLinkshare();
2402
		}
2403
		if (!empty($cflink['realtime'])) {
2404
			if (!empty($cflink['realtime1'])) {
2405
				$this->SetR_m1($cflink['realtime1']);
2406
				$this->SetR_d($cflink['realtime2']);
2407
				$this->SetRealtime();
2408
			} else {
2409
				$this->SetR_m1("");
2410
				$this->SetR_d("");
2411
				$this->DisableRealtime();
2412
			}
2413
			if (!empty($cflink['realtime3'])) {
2414
				$this->SetR_m2($cflink['realtime3']);
2415
				$this->SetRealtime();
2416
			}
2417
		} else {
2418
			$this->DisableRealtime();
2419
		}
2420
		if (!empty($cflink['upperlimit'])) {
2421
			if (!empty($cflink['upperlimit1'])) {
2422
				$this->SetU_m1($cflink['upperlimit1']);
2423
				$this->SetU_d($cflink['upperlimit2']);
2424
				$this->SetUpperlimit();
2425
			} else {
2426
				$this->SetU_m1("");
2427
				$this->SetU_d("");
2428
				$this->DisableUpperlimit();
2429
			}
2430
			if (!empty($cflink['upperlimit3'])) {
2431
				$this->SetU_m2($cflink['upperlimit3']);
2432
				$this->SetUpperlimit();
2433
			}
2434
		} else {
2435
			$this->DisableUpperlimit();
2436
		}
2437
		parent::ReadConfig($cflink);
2438
	}
2439

    
2440
	function build_tree() {
2441
		$tree = " <li><a href=\"firewall_shaper.php?interface=" . $this->GetInterface() ."&amp;queue=" . htmlspecialchars($this->GetQname())."&amp;action=show";
2442
		$tree .= "\" ";
2443
		$tmpvalue = $this->GetDefault();
2444
		if (!empty($tmpvalue)) {
2445
			$tree .= " class=\"navlnk\"";
2446
		}
2447
		$tree .= " >" . htmlspecialchars($this->GetQname()) . "</a>";
2448
		if (is_array($this->subqueues)) {
2449
			$tree .= "<ul>";
2450
			foreach ($this->subqueues as $q) {
2451
				$tree .= $q->build_tree();
2452
			}
2453
			$tree .= "</ul>";
2454
		}
2455
		$tree .= "</li>";
2456
		return $tree;
2457
	}
2458

    
2459
	/* Even this should take children into consideration */
2460
	function build_rules(&$default = false) {
2461

    
2462
		$pfq_rule = " queue ". $this->qname;
2463
		if ($this->GetInterface()) {
2464
			$pfq_rule .= " on ".get_real_interface($this->GetInterface());
2465
		}
2466
		if ($this->GetBandwidth() && $this->GetBwscale()) {
2467
			$pfq_rule .= " bandwidth ".trim($this->GetBandwidth()).$this->GetBwscale();
2468
		}
2469

    
2470
		$tmpvalue = $this->GetQlimit();
2471
		if (!empty($tmpvalue)) {
2472
			$pfq_rule .= " qlimit " . $this->GetQlimit();
2473
		}
2474
		if ($this->GetDefault() || $this->GetRed() || $this->GetRio() || $this->GetEcn() || $this->GetCodel() || $this->GetRealtime() <> "" || $this->GetLinkshare() <> "" || $this->GetUpperlimit() <> "") {
2475
			$pfq_rule .= " hfsc ( ";
2476
			$tmpvalue = $this->GetRed();
2477
			if (!empty($tmpvalue)) {
2478
				$comma = 1;
2479
				$pfq_rule .= " red ";
2480
			}
2481

    
2482
			$tmpvalue = $this->GetRio();
2483
			if (!empty($tmpvalue)) {
2484
				if ($comma) {
2485
					$pfq_rule .= " ,";
2486
				}
2487
				$comma = 1;
2488
				$pfq_rule .= " rio ";
2489
			}
2490
			$tmpvalue = $this->GetEcn();
2491
			if (!empty($tmpvalue)) {
2492
				if ($comma) {
2493
					$pfq_rule .= " ,";
2494
				}
2495
				$comma = 1;
2496
				$pfq_rule .= " ecn ";
2497
			}
2498
			$tmpvalue = $this->GetCodel();
2499
			if (!empty($tmpvalue)) {
2500
				if ($comma) {
2501
					$pfq_rule .= " ,";
2502
				}
2503
				$comma = 1;
2504
				$pfq_rule .= " codel ";
2505
			}
2506
			$tmpvalue = $this->GetDefault();
2507
			if (!empty($tmpvalue)) {
2508
				if ($comma) {
2509
					$pfq_rule .= " ,";
2510
				}
2511
				$comma = 1;
2512
				$pfq_rule .= " default ";
2513
				$default = true;
2514
			}
2515

    
2516
			if ($this->GetRealtime() <> "") {
2517
				if ($comma) {
2518
					$pfq_rule .= " , ";
2519
				}
2520
				if ($this->GetR_m1() <> "" && $this->GetR_d() <> "" && $this->GetR_m2() <> "") {
2521
					$pfq_rule .= " realtime (".$this->GetR_m1() . ", " . $this->GetR_d().", ". $this->GetR_m2() .") ";
2522
				} else if ($this->GetR_m2() <> "") {
2523
					$pfq_rule .= " realtime " . $this->GetR_m2();
2524
				}
2525
				$comma = 1;
2526
			}
2527
			if ($this->GetLinkshare() <> "") {
2528
				if ($comma) {
2529
					$pfq_rule .= " ,";
2530
				}
2531
				if ($this->GetL_m1() <> "" && $this->GetL_d() <> "" && $this->GetL_m2() <> "") {
2532
					$pfq_rule .= " linkshare (".$this->GetL_m1(). ", ". $this->GetL_d(). ", ". $this->GetL_m2(). ") ";
2533
				} else if ($this->GetL_m2() <> "") {
2534
					$pfq_rule .= " linkshare " . $this->GetL_m2() . " ";
2535
				}
2536
				$comma = 1;
2537
			}
2538
			if ($this->GetUpperlimit() <> "") {
2539
				if ($comma) {
2540
					$pfq_rule .= " ,";
2541
				}
2542
				if ($this->GetU_m1() <> "" && $this->GetU_d() <> "" && $this->GetU_m2() <> "") {
2543
							$pfq_rule .= " upperlimit (".$this->GetU_m1().", ". $this->GetU_d().", ". $this->GetU_m2(). ") ";
2544
				} else if ($this->GetU_m2() <> "") {
2545
					$pfq_rule .= " upperlimit " . $this->GetU_m2() . " ";
2546
				}
2547
			}
2548
			$pfq_rule .= " ) ";
2549
		}
2550
		if (count($this->subqueues)) {
2551
			$i = count($this->subqueues);
2552
			$pfq_rule .= " { ";
2553
			foreach ($this->subqueues as $qkey => $qnone) {
2554
				if ($i > 1) {
2555
					$i--;
2556
					$pfq_rule .= " {$qkey}, ";
2557
				} else {
2558
					$pfq_rule .= " {$qkey} ";
2559
				}
2560
			}
2561
			$pfq_rule .= " } \n";
2562
			foreach ($this->subqueues as $q) {
2563
				$pfq_rule .= $q->build_rules($default);
2564
			}
2565
		}
2566

    
2567
		$pfq_rule .= " \n";
2568

    
2569
		return $pfq_rule;
2570
	}
2571

    
2572
	function build_javascript() {
2573

    
2574
		$javascript = <<<EOJS
2575
<script type="text/javascript">
2576
//<![CDATA[
2577
	events.push(function(){
2578

    
2579
		// Disables the specified input element
2580
		function disableInput(id, disable) {
2581
			$('#' + id).prop("disabled", disable);
2582
		}
2583

    
2584
		// Upperlimit
2585
		function enable_upperlimit() {
2586
			disableInput('upperlimit1', !$('#upperlimit').prop('checked'));
2587
			disableInput('upperlimit2', !$('#upperlimit').prop('checked'));
2588
			disableInput('upperlimit3', !$('#upperlimit').prop('checked'));
2589
		}
2590

    
2591
		$('#upperlimit').click(function () {
2592
			enable_upperlimit();
2593
		});
2594

    
2595
		enable_upperlimit();
2596

    
2597
		// realtime
2598
		function enable_realtime() {
2599
			disableInput('realtime1', !$('#realtime').prop('checked'));
2600
			disableInput('realtime2', !$('#realtime').prop('checked'));
2601
			disableInput('realtime3', !$('#realtime').prop('checked'));
2602
		}
2603

    
2604
		$('#realtime').click(function () {
2605
			enable_realtime();
2606
		});
2607

    
2608
		enable_realtime();
2609

    
2610
		// linkshare
2611
		function enable_linkshare() {
2612
			disableInput('linkshare1', !$('#linkshare').prop('checked'));
2613
			disableInput('linkshare2', !$('#linkshare').prop('checked'));
2614
			disableInput('linkshare3', !$('#linkshare').prop('checked'));
2615
		}
2616

    
2617
		$('#linkshare').click(function () {
2618
			enable_linkshare();
2619
		});
2620

    
2621
		enable_linkshare();
2622
	});
2623
//]]>
2624
</script>
2625
EOJS;
2626

    
2627
		return $javascript;
2628
	}
2629

    
2630
	function build_form() {
2631

    
2632
		$sform = parent::build_form();
2633

    
2634
		$section = new Form_Section('Service Curve (sc)');
2635

    
2636
		$group = new Form_Group('Bandwidth');
2637

    
2638
		$group->add(new Form_Input(
2639
			'bandwidth',
2640
			null,
2641
			'number',
2642
			$this->GetBandwidth(),
2643
			['step' => 'any', 'min' => '0.000']
2644
		));
2645

    
2646
		$group->add(new Form_Select(
2647
			'bandwidthtype',
2648
			null,
2649
			$this->FormGetBwscale(),
2650
			array('Kb' => 'Kbit/s',
2651
				  'Mb' => 'Mbit/s',
2652
				  'Gb' => 'Gbit/s',
2653
				  'b' => 'Bit/s',
2654
				  '%' => '%')
2655
		));
2656

    
2657
		$group->setHelp('Choose the amount of bandwidth for this queue');
2658

    
2659
		$section->add($group);
2660

    
2661
		$group = new Form_Group('Max bandwidth for queue.');
2662

    
2663
		$group->add(new Form_Checkbox(
2664
			'upperlimit',
2665
			null,
2666
			'Upper Limit',
2667
			($this->GetUpperlimit()<> "")
2668
		));
2669

    
2670
		$group->add(new Form_Input(
2671
			'upperlimit1',
2672
			null,
2673
			'text',
2674
			$this->GetU_m1()
2675
		))->setHelp('m1');
2676

    
2677
		$group->add(new Form_Input(
2678
			'upperlimit2',
2679
			null,
2680
			'text',
2681
			$this->GetU_d()
2682
		))->setHelp('d');
2683

    
2684
		$group->add(new Form_Input(
2685
			'upperlimit3',
2686
			null,
2687
			'text',
2688
			$this->GetU_m2()
2689
		))->setHelp('m2');
2690

    
2691

    
2692
		$section->add($group);
2693

    
2694
		$group = new Form_Group('Min bandwidth for queue.');
2695

    
2696
		$group->add(new Form_Checkbox(
2697
			'realtime',
2698
			null,
2699
			'Real Time',
2700
			($this->GetRealtime()<> "")
2701
		));
2702

    
2703
		$group->add(new Form_Input(
2704
			'realtime1',
2705
			null,
2706
			'text',
2707
			$this->GetR_m1()
2708
		))->setHelp('m1');
2709

    
2710
		$group->add(new Form_Input(
2711
			'realtime2',
2712
			null,
2713
			'text',
2714
			$this->GetR_d()
2715
		))->setHelp('d');
2716

    
2717
		$group->add(new Form_Input(
2718
			'realtime3',
2719
			null,
2720
			'text',
2721
			$this->GetR_m2()
2722
		))->setHelp('m2');
2723

    
2724
		$section->add($group);
2725

    
2726
		$group = new Form_Group('B/W share of a backlogged queue.');
2727

    
2728
		$group->add(new Form_Checkbox(
2729
			'linkshare',
2730
			null,
2731
			'Link Share',
2732
			($this->GetLinkshare()<> "")
2733
		));
2734

    
2735
		$group->add(new Form_Input(
2736
			'linkshare1',
2737
			null,
2738
			'text',
2739
			$this->GetL_m1()
2740
		))->setHelp('m1');
2741

    
2742
		$group->add(new Form_Input(
2743
			'linkshare2',
2744
			null,
2745
			'text',
2746
			$this->GetL_d()
2747
		))->setHelp('d');
2748

    
2749
		$group->add(new Form_Input(
2750
			'linkshare3',
2751
			null,
2752
			'text',
2753
			$this->GetL_m2()
2754
		))->setHelp('m2');
2755

    
2756
		$group->sethelp('Bandwidth share overrides priority.%s' .
2757
						'The format for service curve specifications is (m1, d, m2). m2 controls the bandwidth assigned to the queue. ' .
2758
						'm1 and d are optional and can be used to control the initial bandwidth assignment. ' .
2759
						'For the first d milliseconds the queue gets the bandwidth given as m1, afterwards the value given in m2.',
2760
						'<br />');
2761

    
2762
		$section->add($group);
2763

    
2764
		$sform->add($section);
2765

    
2766
		return($sform);
2767
	}
2768

    
2769
	function update_altq_queue_data(&$data) {
2770
		$this->ReadConfig($data);
2771
	}
2772

    
2773
	function wconfig() {
2774
		$cflink =& get_reference_to_me_in_config($this->GetLink());
2775
		if (!is_array($cflink)) {
2776
			$cflink = array();
2777
		}
2778
		$cflink['name'] = $this->GetQname();
2779
		$cflink['interface'] = $this->GetInterface();
2780
		$cflink['qlimit'] = trim($this->GetQlimit());
2781
		if (empty($cflink['qlimit'])) {
2782
			unset($cflink['qlimit']);
2783
		}
2784
		$cflink['priority'] = $this->GetQpriority();
2785
		if (!is_numericint($cflink['priority'])) {
2786
			unset($cflink['priority']);
2787
		}
2788
		$cflink['description'] = $this->GetDescription();
2789
		if (empty($cflink['description'])) {
2790
			unset($cflink['description']);
2791
		}
2792
		$cflink['bandwidth'] = $this->GetBandwidth();
2793
		$cflink['bandwidthtype'] = $this->GetBwscale();
2794
		$cflink['enabled'] = $this->GetEnabled();
2795
		if (empty($cflink['enabled'])) {
2796
			unset($cflink['enabled']);
2797
		}
2798
		$cflink['default'] = $this->GetDefault();
2799
		if (empty($cflink['default'])) {
2800
			unset($cflink['default']);
2801
		}
2802
		$cflink['red'] = trim($this->GetRed());
2803
		if (empty($cflink['red'])) {
2804
			unset($cflink['red']);
2805
		}
2806
		$cflink['rio'] = $this->GetRio();
2807
		if (empty($cflink['rio'])) {
2808
			unset($cflink['rio']);
2809
		}
2810
		$cflink['ecn'] = trim($this->GetEcn());
2811
		if (empty($cflink['ecn'])) {
2812
			unset($cflink['ecn']);
2813
		}
2814
		$cflink['codel'] = trim($this->GetCodel());
2815
		if (empty($cflink['codel'])) {
2816
			unset($cflink['codel']);
2817
		}
2818
		if ($this->GetLinkshare() <> "") {
2819
			if ($this->GetL_m1() <> "") {
2820
				$cflink['linkshare1'] = $this->GetL_m1();
2821
				$cflink['linkshare2'] = $this->GetL_d();
2822
				$cflink['linkshare'] = "on";
2823
			} else {
2824
				unset($cflink['linkshare']);
2825
				unset($cflink['linkshare1']);
2826
				unset($cflink['linkshare2']);
2827
			}
2828
			if ($this->GetL_m2() <> "") {
2829
				$cflink['linkshare3'] = $this->GetL_m2();
2830
				$cflink['linkshare'] = "on";
2831
			} else {
2832
				unset($cflink['linkshare']);
2833
				unset($cflink['linkshare3']);
2834
			}
2835
		} else {
2836
			unset($cflink['linkshare']);
2837
			unset($cflink['linkshare1']);
2838
			unset($cflink['linkshare2']);
2839
			unset($cflink['linkshare3']);
2840
		}
2841
		if ($this->GetRealtime() <> "") {
2842
			if ($this->GetR_m1() <> "") {
2843
				$cflink['realtime1'] = $this->GetR_m1();
2844
				$cflink['realtime2'] = $this->GetR_d();
2845
				$cflink['realtime'] = "on";
2846
			} else {
2847
				unset($cflink['realtime']);
2848
				unset($cflink['realtime1']);
2849
				unset($cflink['realtime2']);
2850
			}
2851
			if ($this->GetR_m2() <> "") {
2852
				$cflink['realtime3'] = $this->GetR_m2();
2853
				$cflink['realtime'] = "on";
2854
			} else {
2855
				unset($cflink['realtime']);
2856
				unset($cflink['realtime3']);
2857
			}
2858
		} else {
2859
			unset($cflink['realtime']);
2860
			unset($cflink['realtime1']);
2861
			unset($cflink['realtime2']);
2862
			unset($cflink['realtime3']);
2863
		}
2864
		if ($this->GetUpperlimit() <> "") {
2865
			if ($this->GetU_m1() <> "") {
2866
				$cflink['upperlimit1'] = $this->GetU_m1();
2867
				$cflink['upperlimit2'] = $this->GetU_d();
2868
				$cflink['upperlimit'] = "on";
2869
			} else {
2870
				unset($cflink['upperlimit']);
2871
				unset($cflink['upperlimit1']);
2872
				unset($cflink['upperlimit2']);
2873
			}
2874
			if ($this->GetU_m2() <> "") {
2875
				$cflink['upperlimit3'] = $this->GetU_m2();
2876
				$cflink['upperlimit'] = "on";
2877
			} else {
2878
				unset($cflink['upperlimit']);
2879
				unset($cflink['upperlimit3']);
2880
			}
2881
		} else {
2882
			unset($cflink['upperlimit']);
2883
			unset($cflink['upperlimit1']);
2884
			unset($cflink['upperlimit2']);
2885
			unset($cflink['upperlimit3']);
2886
		}
2887
	}
2888
}
2889

    
2890
class cbq_queue extends priq_queue {
2891
	var $qborrow = "";
2892

    
2893
	function GetBorrow() {
2894
		return $this->qborrow;
2895
	}
2896
	function SetBorrow($borrow) {
2897
		$this->qborrow = $borrow;
2898
	}
2899
	function CanHaveChildren() {
2900
		return true;
2901
	}
2902

    
2903
	function &add_queue($interface, &$qname, &$path, &$input_errors) {
2904

    
2905
		if (!is_array($this->subqueues)) {
2906
			$this->subqueues = array();
2907
		}
2908
		$__tmp_q = new cbq_queue(); $q =& $__tmp_q;
2909
		$q->SetInterface($this->GetInterface());
2910
		$q->SetParent($this);
2911
		$q->ReadConfig($qname);
2912
		$q->validate_input($qname, $input_errors);
2913

    
2914
		$q->SetEnabled("on");
2915
		$q->SetLink($path);
2916
		$this->subqueues[$q->GetQName()] = &$q;
2917
		ref_on_altq_queue_list($this->GetQname(), $q->GetQname());
2918
		if (is_array($qname['queue'])) {
2919
			foreach ($qname['queue'] as $key1 => $que) {
2920
				array_push($path, $key1);
2921
				$q->add_queue($q->GetInterface(), $que, $path, $input_errors);
2922
				array_pop($path);
2923
			}
2924
		}
2925

    
2926
		return $q;
2927
	}
2928

    
2929
	function copy_queue($interface, &$cflink) {
2930

    
2931
		$cflink['interface'] = $interface;
2932
		$cflink['qlimit'] = trim($this->GetQlimit());
2933
		if (empty($clink['qlimit'])) {
2934
			unset($cflink['qlimit']);
2935
		}
2936
		$cflink['priority'] = trim($this->GetQpriority());
2937
		if (!is_numeric($cflink['priority'])) {
2938
			unset($cflink['priority']);
2939
		}
2940
		$cflink['name'] = $this->GetQname();
2941
		$cflink['description'] = trim($this->GetDescription());
2942
		if (empty($cflink['description'])) {
2943
			unset($cflink['description']);
2944
		}
2945
		$cflink['bandwidth'] = $this->GetBandwidth();
2946
		$cflink['bandwidthtype'] = $this->GetBwscale();
2947
		$cflink['enabled'] = trim($this->GetEnabled());
2948
		if (empty($cflink['enabled'])) {
2949
			unset($cflink['enabled']);
2950
		}
2951
		$cflink['default'] = trim($this->GetDefault());
2952
		if (empty($cflink['default'])) {
2953
			unset($cflink['default']);
2954
		}
2955
		$cflink['red'] = trim($this->GetRed());
2956
		if (empty($cflink['red'])) {
2957
			unset($cflink['red']);
2958
		}
2959
		$cflink['rio'] = trim($this->GetRio());
2960
		if (empty($cflink['rio'])) {
2961
			unset($cflink['rio']);
2962
		}
2963
		$cflink['ecn'] = trim($this->GetEcn());
2964
		if (empty($cflink['ecn'])) {
2965
			unset($cflink['ecn']);
2966
		}
2967
		$cflink['borrow'] = trim($this->GetBorrow());
2968
		if (empty($cflink['borrow'])) {
2969
			unset($cflink['borrow']);
2970
		}
2971
		if (is_array($this->queues)) {
2972
			$cflinkp['queue'] = array();
2973
			foreach ($this->subqueues as $q) {
2974
				$cflink['queue'][$q->GetQname()] = array();
2975
				$q->copy_queue($interface, $cflink['queue'][$q->GetQname()]);
2976
			}
2977
		}
2978
	}
2979

    
2980
	/*
2981
	 * Should search even its children
2982
	 */
2983
	function &find_queue($interface, $qname) {
2984
		if ($qname == $this->GetQname()) {
2985
			return $this;
2986
		}
2987
		foreach ($this->subqueues as $q) {
2988
			$result =& $q->find_queue("", $qname);
2989
			if ($result) {
2990
				return $result;
2991
			}
2992
		}
2993
	}
2994

    
2995
	function &find_parentqueue($interface, $qname) {
2996
		if ($this->subqueues[$qname]) {
2997
			return $this;
2998
		}
2999
		foreach ($this->subqueues as $q) {
3000
			$result = $q->find_parentqueue("", $qname);
3001
			if ($result) {
3002
				return $result;
3003
			}
3004
		}
3005
	}
3006

    
3007
	function delete_queue() {
3008
		unref_on_altq_queue_list($this->GetQname());
3009
		cleanup_queue_from_rules($this->GetQname());
3010
		foreach ($this->subqueues as $q) {
3011
			$q->delete_queue();
3012
		}
3013
		unset_object_by_reference($this->GetLink());
3014
	}
3015

    
3016
	function validate_input($data, &$input_errors) {
3017
		parent::validate_input($data, $input_errors);
3018

    
3019
		if ($data['priority'] > 7) {
3020
			$input_errors[] = gettext("Priority must be an integer between 0 and 7.");
3021
		}
3022

    
3023
		$parent = $this->GetParent();
3024
		if (method_exists($parent, 'GetParent') && ($parent->GetBorrow() != "on") &&
3025
		    ($data['borrow'] == 'yes')) {
3026
			$input_errors[] = gettext("You cannot set 'Borrow' if the parent queue also does not borrow.");
3027
		}
3028
	}
3029

    
3030
	function ReadConfig(&$q) {
3031
		parent::ReadConfig($q);
3032
		if (!empty($q['borrow'])) {
3033
			$this->SetBorrow("on");
3034
		} else {
3035
			$this->SetBorrow("");
3036
		}
3037
	}
3038

    
3039
	function build_javascript() {
3040
		return parent::build_javascript();
3041
	}
3042

    
3043
	function build_tree() {
3044
		$tree = " <li><a href=\"firewall_shaper.php?interface=" . $this->GetInterface()."&amp;queue=" . htmlspecialchars($this->GetQname())."&amp;action=show";
3045
		$tree .= "\" ";
3046
		$tmpvalue = trim($this->GetDefault());
3047
		if (!empty($tmpvalue)) {
3048
			$tree .= " class=\"navlnk\"";
3049
		}
3050
		$tree .= " >" . htmlspecialchars($this->GetQname()) . "</a>";
3051
		if (is_array($this->subqueues)) {
3052
			$tree .= "<ul>";
3053
			foreach ($this->subqueues as $q) {
3054
				$tree .= $q->build_tree();
3055
			}
3056
			$tree .= "</ul>";
3057
		}
3058
		$tree .= "</li>";
3059
		return $tree;
3060
	}
3061

    
3062
	/* Even this should take children into consideration */
3063
	function build_rules(&$default = false) {
3064
		$pfq_rule = "queue ". $this->qname;
3065
		if ($this->GetInterface()) {
3066
			$pfq_rule .= " on ".get_real_interface($this->GetInterface());
3067
		}
3068
		if ($this->GetBandwidth() && $this->GetBwscale()) {
3069
			$pfq_rule .= " bandwidth ".trim($this->GetBandwidth()).$this->GetBwscale();
3070
		}
3071
		$tmpvalue = $this->GetQpriority();
3072
		if (is_numeric($tmpvalue)) {
3073
			$pfq_rule .= " priority " . $this->GetQpriority();
3074
		}
3075
		$tmpvalue = trim($this->GetQlimit());
3076
		if (!empty($tmpvalue)) {
3077
			$pfq_rule .= " qlimit " . $this->GetQlimit();
3078
		}
3079
		if ($this->GetDefault() || $this->GetRed() || $this->GetRio() || $this->GetEcn() || $this->GetBorrow() || $this->GetCodel()) {
3080
			$pfq_rule .= " cbq ( ";
3081
			$tmpvalue = trim($this->GetRed());
3082
			if (!empty($tmpvalue)) {
3083
				$comma = 1;
3084
				$pfq_rule .= " red ";
3085
			}
3086
			$tmpvalue = trim($this->GetCodel());
3087
			if (!empty($tmpvalue)) {
3088
				$comma = 1;
3089
				$pfq_rule .= " codel ";
3090
			}
3091
			$tmpvalue = trim($this->GetRio());
3092
			if (!empty($tmpvalue)) {
3093
				if ($comma) {
3094
					$pfq_rule .= " ,";
3095
				}
3096
				$comma = 1;
3097
				$pfq_rule .= " rio ";
3098
			}
3099
			$tmpvalue = trim($this->GetEcn());
3100
			if (!empty($tmpvalue)) {
3101
				if ($comma) {
3102
					$pfq_rule .= " ,";
3103
				}
3104
				$comma = 1;
3105
				$pfq_rule .= " ecn ";
3106
			}
3107
			$tmpvalue = trim($this->GetDefault());
3108
			if (!empty($tmpvalue)) {
3109
				if ($comma) {
3110
					$pfq_rule .= " ,";
3111
				}
3112
				$comma = 1;
3113
				$pfq_rule .= " default ";
3114
				$default = true;
3115
			}
3116
			$tmpvalue = trim($this->GetBorrow());
3117
			if (!empty($tmpvalue)) {
3118
				if ($comma) {
3119
					$pfq_rule .= ", ";
3120
				}
3121
				$pfq_rule .= " borrow ";
3122
			}
3123
			$pfq_rule .= " ) ";
3124
		}
3125
		if (count($this->subqueues)) {
3126
			$i = count($this->subqueues);
3127
			$pfq_rule .= " { ";
3128
			foreach ($this->subqueues as $qkey => $qnone) {
3129
				if ($i > 1) {
3130
					$i--;
3131
					$pfq_rule .= " {$qkey}, ";
3132
				} else {
3133
					$pfq_rule .= " {$qkey} ";
3134
				}
3135
			}
3136
			$pfq_rule .= " } \n";
3137
			foreach ($this->subqueues as $q) {
3138
				$pfq_rule .= $q->build_rules($default);
3139
			}
3140
		}
3141

    
3142
		$pfq_rule .= " \n";
3143
		return $pfq_rule;
3144
	}
3145

    
3146
	function build_form() {
3147
		$sform = parent::build_form();
3148

    
3149
		$section = new Form_Section('NOTITLE');
3150

    
3151
		$group = new Form_Group('Bandwidth');
3152

    
3153
		$group->add(new Form_Input(
3154
			'bandwidth',
3155
			null,
3156
			'number',
3157
			$this->GetBandwidth()
3158
		));
3159

    
3160
		$group->add(new Form_Select(
3161
			'bandwidthtype',
3162
			null,
3163
			$this->FormGetBwscale(),
3164
			array('Kb' => 'Kbit/s',
3165
				  'Mb' => 'Mbit/s',
3166
				  'Gb' => 'Gbit/s',
3167
				  'b' => 'Bit/s',
3168
				  '%' => '%')
3169
		));
3170

    
3171
		$group->setHelp('Choose the amount of bandwidth for this queue');
3172

    
3173
		$section->add($group);
3174

    
3175
		$section->addInput(new Form_Checkbox(
3176
			'borrow',
3177
			'Scheduler option',
3178
			'Borrow from other queues when available',
3179
			($this->GetBorrow() == "on")
3180
		));
3181

    
3182
		$sform->add($section);
3183

    
3184
		return $sform;
3185
	}
3186

    
3187
	function update_altq_queue_data(&$data) {
3188
		$this->ReadConfig($data);
3189
	}
3190

    
3191
	function wconfig() {
3192
		$cflink =& get_reference_to_me_in_config($this->GetLink());
3193
		if (!is_array($cflink)) {
3194
			$cflink = array();
3195
		}
3196
		$cflink['interface'] = $this->GetInterface();
3197
		$cflink['qlimit'] = trim($this->GetQlimit());
3198
		if (empty($cflink['qlimit'])) {
3199
			unset($cflink['qlimit']);
3200
		}
3201
		$cflink['priority'] = $this->GetQpriority();
3202
		if (!is_numeric($cflink['priority'])) {
3203
			unset($cflink['priority']);
3204
		}
3205
		$cflink['name'] = $this->GetQname();
3206
		$cflink['description'] = $this->GetDescription();
3207
		if (empty($cflink['description'])) {
3208
			unset($cflink['description']);
3209
		}
3210
		$cflink['bandwidth'] = $this->GetBandwidth();
3211
		$cflink['bandwidthtype'] = $this->GetBwscale();
3212
		$cflink['enabled'] = trim($this->GetEnabled());
3213
		if (empty($cflink['enabled'])) {
3214
			unset($cflink['enabled']);
3215
		}
3216
		$cflink['default'] = trim($this->GetDefault());
3217
		if (empty($cflink['default'])) {
3218
			unset($cflink['default']);
3219
		}
3220
		$cflink['red'] = trim($this->GetRed());
3221
		if (empty($cflink['red'])) {
3222
			unset($cflink['red']);
3223
		}
3224
		$cflink['rio'] = trim($this->GetRio());
3225
		if (empty($cflink['rio'])) {
3226
			unset($cflink['rio']);
3227
		}
3228
		$cflink['ecn'] = trim($this->GetEcn());
3229
		if (empty($cflink['ecn'])) {
3230
			unset($cflink['ecn']);
3231
		}
3232
		$cflink['codel'] = trim($this->GetCodel());
3233
		if (empty($cflink['codel'])) {
3234
			unset($cflink['codel']);
3235
		}
3236
		$cflink['borrow'] = trim($this->GetBorrow());
3237
		if (empty($cflink['borrow'])) {
3238
			unset($cflink['borrow']);
3239
		}
3240
	}
3241
}
3242

    
3243
class fairq_queue extends priq_queue {
3244
	var $hogs;
3245
	var $buckets;
3246

    
3247
	function GetBuckets() {
3248
		return $this->buckets;
3249
	}
3250
	function SetBuckets($buckets) {
3251
		$this->buckets = $buckets;
3252
	}
3253
	function GetHogs() {
3254
		return $this->hogs;
3255
	}
3256
	function SetHogs($hogs) {
3257
		$this->hogs = $hogs;
3258
	}
3259
	function CanHaveChildren() {
3260
		return false;
3261
	}
3262

    
3263

    
3264
	function copy_queue($interface, &$cflink) {
3265
		$cflink['interface'] = $interface;
3266
		$cflink['qlimit'] = $this->GetQlimit();
3267
		$cflink['priority'] = $this->GetQpriority();
3268
		$cflink['name'] = $this->GetQname();
3269
		$cflink['description'] = $this->GetDescription();
3270
		$cflink['bandwidth'] = $this->GetBandwidth();
3271
		$cflink['bandwidthtype'] = $this->GetBwscale();
3272
		$cflink['enabled'] = $this->GetEnabled();
3273
		$cflink['default'] = $this->GetDefault();
3274
		$cflink['red'] = $this->GetRed();
3275
		$cflink['rio'] = $this->GetRio();
3276
		$cflink['ecn'] = $this->GetEcn();
3277
		$cflink['buckets'] = $this->GetBuckets();
3278
		$cflink['hogs'] = $this->GetHogs();
3279
	}
3280

    
3281
	/*
3282
	 * Should search even its children
3283
	 */
3284
	function &find_queue($interface, $qname) {
3285
		if ($qname == $this->GetQname()) {
3286
			return $this;
3287
		}
3288
	}
3289

    
3290
	function find_parentqueue($interface, $qname) { return; }
3291

    
3292
	function delete_queue() {
3293
		unref_on_altq_queue_list($this->GetQname());
3294
		cleanup_queue_from_rules($this->GetQname());
3295
		unset_object_by_reference($this->GetLink());
3296
	}
3297

    
3298
	function validate_input($data, &$input_errors) {
3299
		parent::validate_input($data, $input_errors);
3300

    
3301
		if ($data['priority'] > 7) {
3302
				$input_errors[] = gettext("Priority must be an integer between 0 and 7.");
3303
		}
3304
	}
3305

    
3306
	function ReadConfig(&$q) {
3307
		parent::ReadConfig($q);
3308
		if (!empty($q['buckets'])) {
3309
			$this->SetBuckets($q['buckets']);
3310
		} else {
3311
			$this->SetBuckets("");
3312
		}
3313
		if (!empty($q['hogs']) && is_valid_shaperbw($q['hogs'])) {
3314
			$this->SetHogs($q['hogs']);
3315
		} else {
3316
			$this->SetHogs("");
3317
		}
3318
	}
3319

    
3320
	function build_javascript() {
3321
		return parent::build_javascript();
3322
	}
3323

    
3324
	function build_tree() {
3325
		$tree = " <li><a href=\"firewall_shaper.php?interface=" .
3326
		$this->GetInterface()."&amp;queue=" . htmlspecialchars($this->GetQname())."&amp;action=show";
3327
		$tree .= "\" ";
3328
		$tmpvalue = trim($this->GetDefault());
3329
		if (!empty($tmpvalue)) {
3330
			$tree .= " class=\"navlnk\"";
3331
		}
3332
		$tree .= " >" . htmlspecialchars($this->GetQname()) . "</a>";
3333
		$tree .= "</li>";
3334
		return $tree;
3335
	}
3336

    
3337
	/* Even this should take children into consideration */
3338
	function build_rules(&$default = false) {
3339
		$pfq_rule = "queue ". $this->qname;
3340
		if ($this->GetInterface()) {
3341
			$pfq_rule .= " on ".get_real_interface($this->GetInterface());
3342
		}
3343
		if ($this->GetBandwidth() && $this->GetBwscale()) {
3344
			$pfq_rule .= " bandwidth ".trim($this->GetBandwidth()).$this->GetBwscale();
3345
		}
3346
		$tmpvalue = trim($this->GetQpriority());
3347
		if (is_numeric($tmpvalue)) {
3348
			$pfq_rule .= " priority " . $this->GetQpriority();
3349
		}
3350
		$tmpvalue = trim($this->GetQlimit());
3351
		if (!empty($tmpvalue)) {
3352
			$pfq_rule .= " qlimit " . $this->GetQlimit();
3353
		}
3354
		if ($this->GetDefault() || $this->GetRed() || $this->GetRio() ||
3355
		    $this->GetEcn() || $this->GetBuckets() || $this->GetHogs() || $this->GetCodel()) {
3356
			$pfq_rule .= " fairq ( ";
3357
			$tmpvalue = trim($this->GetRed());
3358
			if (!empty($tmpvalue)) {
3359
				$comma = 1;
3360
				$pfq_rule .= " red ";
3361
			}
3362
			$tmpvalue = trim($this->GetCodel());
3363
			if (!empty($tmpvalue)) {
3364
				$comma = 1;
3365
				$pfq_rule .= " codel ";
3366
			}
3367
			$tmpvalue = trim($this->GetRio());
3368
			if (!empty($tmpvalue)) {
3369
				if ($comma) {
3370
					$pfq_rule .= " ,";
3371
				}
3372
				$comma = 1;
3373
				$pfq_rule .= " rio ";
3374
			}
3375
			$tmpvalue = trim($this->GetEcn());
3376
			if (!empty($tmpvalue)) {
3377
				if ($comma) {
3378
					$pfq_rule .= " ,";
3379
				}
3380
				$comma = 1;
3381
				$pfq_rule .= " ecn ";
3382
			}
3383
			$tmpvalue = trim($this->GetDefault());
3384
			if (!empty($tmpvalue)) {
3385
				if ($comma) {
3386
					$pfq_rule .= " ,";
3387
				}
3388
				$comma = 1;
3389
				$pfq_rule .= " default ";
3390
				$default = true;
3391
			}
3392
			$tmpvalue = trim($this->GetBuckets());
3393
			if (!empty($tmpvalue)) {
3394
				if ($comma) {
3395
					$pfq_rule .= ", ";
3396
				}
3397
				$pfq_rule .= " buckets " . $this->GetBuckets() . " ";
3398
			}
3399
			$tmpvalue = trim($this->GetHogs());
3400
			if (!empty($tmpvalue)) {
3401
				if ($comma) {
3402
					$pfq_rule .= ", ";
3403
				}
3404
				$pfq_rule .= " hogs " . $this->GetHogs() . " ";
3405
			}
3406
			$pfq_rule .= " ) ";
3407
		}
3408

    
3409
		$pfq_rule .= " \n";
3410
		return $pfq_rule;
3411
	}
3412

    
3413
	function build_form() {
3414
		$form = parent::build_form();
3415

    
3416
		$section = new Form_Section('');
3417

    
3418
		$group = new Form_Group('Bandwidth');
3419

    
3420
		$group->add(new Form_Input(
3421
			'bandwidth',
3422
			null,
3423
			'number',
3424
			$this->GetBandwidth()
3425
		));
3426

    
3427
		$group->add(new Form_Select(
3428
			'bandwidthtype',
3429
			null,
3430
			$this->FormGetBwscale(),
3431
			array('Kb' => 'Kbit/s',
3432
				  'Mb' => 'Mbit/s',
3433
				  'Gb' => 'Gbit/s',
3434
				  'b' => 'Bit/s',
3435
				  '%' => '%')
3436
		));
3437

    
3438
		$group->setHelp('Choose the amount of bandwidth for this queue');
3439

    
3440
		$section->add($group);
3441

    
3442
		$section->addInput(new Form_Input(
3443
			'buckets',
3444
			'Scheduler specific options',
3445
			'text',
3446
			$this->GetBuckets()
3447
		))->setHelp('Number of buckets available');
3448

    
3449
		$section->addInput(new Form_Input(
3450
			'hogs',
3451
			'',
3452
			'text',
3453
			$this->GetHogs()
3454
			))->setHelp('Bandwidth limit for hosts to not saturate link');
3455

    
3456
		$form->add($section);
3457
		return $form;
3458
	}
3459

    
3460
	function update_altq_queue_data(&$data) {
3461
		$this->ReadConfig($data);
3462
	}
3463

    
3464
	function wconfig() {
3465
		$cflink =& get_reference_to_me_in_config($this->GetLink());
3466
		if (!is_array($cflink)) {
3467
			$cflink = array();
3468
		}
3469
		$cflink['interface'] = $this->GetInterface();
3470
		$cflink['qlimit'] = trim($this->GetQlimit());
3471
		if (empty($cflink['qlimit'])) {
3472
			unset($cflink['qlimit']);
3473
		}
3474
		$cflink['priority'] = trim($this->GetQpriority());
3475
		if (!is_numeric($cflink['priority'])) {
3476
			unset($cflink['priority']);
3477
		}
3478
		$cflink['name'] = $this->GetQname();
3479
		$cflink['description'] = trim($this->GetDescription());
3480
		if (empty($cflink['description'])) {
3481
			unset($cflink['description']);
3482
		}
3483
		$cflink['bandwidth'] = $this->GetBandwidth();
3484
		$cflink['bandwidthtype'] = $this->GetBwscale();
3485
		$cflink['enabled'] = $this->GetEnabled();
3486
		if (empty($cflink['enabled'])) {
3487
			unset($cflink['enabled']);
3488
		}
3489
		$cflink['default'] = trim($this->GetDefault());
3490
		if (empty($cflink['default'])) {
3491
			unset($cflink['default']);
3492
		}
3493
		$cflink['red'] = trim($this->GetRed());
3494
		if (empty($cflink['red'])) {
3495
			unset($cflink['red']);
3496
		}
3497
		$cflink['rio'] = trim($this->GetRio());
3498
		if (empty($cflink['rio'])) {
3499
			unset($cflink['rio']);
3500
		}
3501
		$cflink['ecn'] = trim($this->GetEcn());
3502
		if (empty($cflink['ecn'])) {
3503
			unset($cflink['ecn']);
3504
		}
3505
		$cflink['codel'] = trim($this->GetCodel());
3506
		if (empty($cflink['codel'])) {
3507
			unset($cflink['codel']);
3508
		}
3509
		$cflink['buckets'] = trim($this->GetBuckets());
3510
		if (empty($cflink['buckets'])) {
3511
			unset($cflink['buckets']);
3512
		}
3513
		$cflink['hogs'] = trim($this->GetHogs());
3514
		if (empty($cflink['hogs'])) {
3515
			unset($cflink['hogs']);
3516
		}
3517
	}
3518
}
3519

    
3520

    
3521
/*
3522
 * dummynet(4) wrappers.
3523
 */
3524

    
3525

    
3526
/*
3527
 * List of respective objects!
3528
 */
3529
$dummynet_pipe_list = array();
3530

    
3531
class dummynet_class {
3532
	var $qname;
3533
	var $qnumber; /* dummynet(4) uses numbers instead of names; maybe integrate with pf the same as altq does?! */
3534
	var $qlimit;
3535
	var $description;
3536
	var $qenabled;
3537
	var $link;
3538
	var $qparent; /* link to upper class so we do things easily on WF2Q+ rule creation */
3539
	var $plr;
3540

    
3541
	var $buckets;
3542
	/* mask parameters */
3543
	var $mask;
3544
	var $noerror;
3545

    
3546
	/* Accessor functions */
3547
	function SetLink($link) {
3548
		$this->link = $link;
3549
	}
3550
	function GetLink() {
3551
		return $this->link;
3552
	}
3553
	function GetMask() {
3554
		if (!isset($this->mask["type"])) {
3555
			$this->mask["type"] = "none";
3556
		}
3557
		return $this->mask;
3558
	}
3559
	function SetMask($mask) {
3560
		$this->mask = $mask;
3561
	}
3562
	function &GetParent() {
3563
		return $this->qparent;
3564
	}
3565
	function SetParent(&$parent) {
3566
		$this->qparent = &$parent;
3567
	}
3568
	function GetEnabled() {
3569
		return $this->qenabled;
3570
	}
3571
	function SetEnabled($value) {
3572
		$this->qenabled = $value;
3573
	}
3574
	function CanHaveChildren() {
3575
		return false;
3576
	}
3577
	function CanBeDeleted() {
3578
		return true;
3579
	}
3580
	function GetQname() {
3581
		return $this->qname;
3582
	}
3583
	function SetQname($name) {
3584
		$this->qname = trim($name);
3585
	}
3586
	function GetQlimit() {
3587
		return $this->qlimit;
3588
	}
3589
	function SetQlimit($limit) {
3590
		$this->qlimit = $limit;
3591
	}
3592
	function GetDescription() {
3593
		return $this->description;
3594
	}
3595
	function SetDescription($str) {
3596
		$this->description = trim($str);
3597
	}
3598
	function GetFirstime() {
3599
		return $this->firsttime;
3600
	}
3601
	function SetFirsttime($number) {
3602
		$this->firsttime = $number;
3603
	}
3604
	function GetBuckets() {
3605
		return $this->buckets;
3606
	}
3607
	function SetBuckets($buckets) {
3608
		$this->buckets = $buckets;
3609
	}
3610
	function SetNumber($number) {
3611
		$this->qnumber = $number;
3612
	}
3613
	function GetNumber() {
3614
		return $this->qnumber;
3615
	}
3616
	function GetPlr() {
3617
		return $this->plr;
3618
	}
3619
	function SetPlr($plr) {
3620
		$this->plr = $plr;
3621
	}
3622

    
3623
	function build_javascript() {
3624
		$javascript .= "<script type=\"text/javascript\">\n";
3625
		$javascript .= "//<![CDATA[\n";
3626
		$javascript .= "function enable_maskbits(enable_over) {\n";
3627
		$javascript .= "var e = document.getElementById(\"mask\");\n";
3628
		$javascript .= "if ((e.options[e.selectedIndex].text == \"none\") || enable_over) {\n";
3629
		$javascript .= "document.iform.maskbits.disabled = 1;\n";
3630
		$javascript .= "document.iform.maskbits.value = \"\";\n";
3631
		$javascript .= "document.iform.maskbitsv6.disabled = 1;\n";
3632
		$javascript .= "document.iform.maskbitsv6.value = \"\";\n";
3633
		$javascript .= "} else {\n";
3634
		$javascript .= "document.iform.maskbits.disabled = 0;\n";
3635
		$javascript .= "document.iform.maskbitsv6.disabled = 0;\n";
3636
		$javascript .= "}}\n";
3637
		$javascript .= "//]]>\n";
3638
		$javascript .= "</script>\n";
3639
		return $javascript;
3640
	}
3641

    
3642
	function validate_input($data, &$input_errors) {
3643
		if ($data['plr'] && (!is_numeric($data['plr']) ||
3644
		    ($data['plr'] < 0) || ($data['plr'] > 1))) {
3645
			$input_errors[] = gettext("Packet Loss Rate must be a value between 0 and 1.");
3646
		}
3647
		if ($data['buckets'] && (!is_numeric($data['buckets']) ||
3648
		    ($data['buckets'] < 16) || ($data['buckets'] > 65535))) {
3649
			$input_errors[] = gettext("Buckets must be an integer between 16 and 65535.");
3650
		}
3651
		if ($data['qlimit'] && (!is_numeric($data['qlimit']))) {
3652
			$input_errors[] = gettext("Queue limit must be an integer");
3653
		}
3654
		if (!empty($data['newname']) && !preg_match("/^[a-zA-Z0-9_-]+$/", $data['newname'])) {
3655
			$input_errors[] = gettext("Queue names must be alphanumeric and _ or - only.");
3656
		}
3657
		if (!empty($data['name']) && !preg_match("/^[a-zA-Z0-9_-]+$/", $data['name'])) {
3658
			$input_errors[] = gettext("Queue names must be alphanumeric and _ or - only.");
3659
		}
3660
		if (isset($data['maskbits']) && ($data['maskbits'] <> "")) {
3661
			if ((!is_numeric($data['maskbits'])) || ($data['maskbits'] <= 0) || ($data['maskbits'] > 32)) {
3662
				$input_errors[] = gettext("IPv4 bit mask must be blank or numeric value between 1 and 32.");
3663
			}
3664
		}
3665
		if (isset($data['maskbitsv6']) && ($data['maskbitsv6'] <> "")) {
3666
			if ((!is_numeric($data['maskbitsv6'])) || ($data['maskbitsv6'] <= 0) || ($data['maskbitsv6'] > 128)) {
3667
				$input_errors[] = gettext("IPv6 bit mask must be blank or numeric value between 1 and 128.");
3668
			}
3669
		}
3670
	}
3671

    
3672
	function build_mask_rules(&$pfq_rule) {
3673
		$mask = $this->GetMask();
3674
		if (!empty($mask['type'])) {
3675
			if ($mask['type'] <> 'none') {
3676
				$pfq_rule .= " mask";
3677
			}
3678
			switch ($mask['type']) {
3679
				case 'srcaddress':
3680
					if (!empty($mask['bitsv6']) && ($mask['bitsv6'] <> "")) {
3681
						$pfq_rule .= " src-ip6 /" . $mask['bitsv6'];
3682
					} else {
3683
						$pfq_rule .= " src-ip6 /128";
3684
					}
3685
					if (!empty($mask['bits']) && ($mask['bits'] <> "")) {
3686
						$pfq_rule .= sprintf(" src-ip 0x%x", gen_subnet_mask_long($mask['bits']));
3687
					} else {
3688
						$pfq_rule .= " src-ip 0xffffffff";
3689
					}
3690
					break;
3691
				case 'dstaddress':
3692
					if (!empty($mask['bitsv6']) && ($mask['bitsv6'] <> "")) {
3693
						$pfq_rule .= " dst-ip6 /" . $mask['bitsv6'];
3694
					} else {
3695
						$pfq_rule .= " dst-ip6 /128";
3696
					}
3697
					if (!empty($mask['bits']) && ($mask['bits'] <> "")) {
3698
						$pfq_rule .= sprintf(" dst-ip 0x%x", gen_subnet_mask_long($mask['bits']));
3699
					} else {
3700
						$pfq_rule .= " dst-ip 0xffffffff";
3701
					}
3702
					break;
3703
				default:
3704
					break;
3705
			}
3706
		}
3707
	}
3708

    
3709
}
3710

    
3711
class dnpipe_class extends dummynet_class {
3712
	var $delay;
3713
	var $qbandwidth = array();
3714
	var $qbandwidthtype;
3715

    
3716
	/* Limiter queue patch */
3717
	var $ecn; // ecn 'on' or 'off'
3718
	var $pie_onoff;
3719
	var $pie_capdrop;
3720
	var $pie_qdelay;
3721
	var $pie_pderand;
3722
	var $aqm; // key to aqm_map
3723
	var $aqm_params = array(); // AQM params
3724
	var $scheduler;	// key to scheduler_map
3725
	var $scheduler_params = array(); // AQM params
3726
	function GetAQM() {
3727
			return $this->aqm;
3728
	}
3729
	function SetAQM($aqm) {
3730
			$this->aqm = $aqm;
3731
	}
3732
	function GetAQMParameters() {
3733
			return $this->aqm_params;
3734
	}
3735
	function GetAQMParameter($parameter) {
3736
			return $this->aqm_params[$parameter];
3737
	}
3738
	function SetAQMParameter($key, $value) {
3739
			return $this->aqm_params[$key] = $value;
3740
	}
3741
	function SetAQMParameters($params) {
3742
			$this->aqm_params = $params;
3743
	}
3744
	function GetECN() {
3745
			return $this->ecn;
3746
	}
3747
	function SetECN($ecn) {
3748
			$this->ecn = $ecn;
3749
	}
3750
	function GetPIE_ONOFF() {
3751
			return $this->pie_onoff;
3752
	}
3753
	function SetPIE_ONOFF($pie_onoff) {
3754
			$this->pie_onoff = $pie_onoff;
3755
	}
3756
	function GetPIE_CAPDROP() {
3757
			return $this->pie_capdrop;
3758
	}
3759
	function SetPIE_CAPDROP($pie_capdrop) {
3760
			$this->pie_capdrop = $pie_capdrop;
3761
	}
3762
	function GetPIE_QDELAY() {
3763
			return $this->pie_qdelay;
3764
	}
3765
	function SetPIE_QDELAY($pie_qdelay) {
3766
			$this->pie_qdelay = $pie_qdelay;
3767
	}
3768
	function GetPIE_PDERAND() {
3769
			return $this->pie_pderand;
3770
	}
3771
	function SetPIE_PDERAND($pie_pderand) {
3772
			$this->pie_pderand = $pie_pderand;
3773
	}
3774
	function GetScheduler() {
3775
			return $this->scheduler;
3776
	}
3777
	function SetScheduler($scheduler) {
3778
			$this->scheduler = $scheduler;
3779
	}
3780
	function GetSchedulerParameters() {
3781
			return $this->scheduler_params;
3782
	}
3783
	function SetSchedulerParameters($params) {
3784
			$this->scheduler_params = $params;
3785
	}
3786
	function SetSchedulerParameter($key, $value) {
3787
			$this->scheduler_params[$key] = $value;
3788
	}
3789
	function GetSchedulerParameter($key) {
3790
			return $this->scheduler_params[$key];
3791
	}
3792
	/* End limiter queue patch */
3793

    
3794
		/* This is here to help on form building and building rules/lists */
3795
	var $subqueues = array();
3796

    
3797
	function CanHaveChildren() {
3798
		return true;
3799
	}
3800
	function SetDelay($delay) {
3801
		$this->delay = $delay;
3802
	}
3803
	function GetDelay() {
3804
		return $this->delay;
3805
	}
3806
	function delete_queue() {
3807
		cleanup_dnqueue_from_rules($this->GetQname());
3808
		foreach ($this->subqueues as $q) {
3809
			$q->delete_queue();
3810
		}
3811
		unset_dn_object_by_reference($this->GetLink());
3812
		mwexec("/sbin/dnctl pipe delete " . $this->GetNumber());
3813
		mwexec("/sbin/dnctl sched delete " . $this->GetNumber());
3814
	}
3815
	function GetBandwidth() {
3816
		return $this->qbandwidth;
3817
	}
3818
	function SetBandwidth($bandwidth) {
3819
		$this->qbandwidth = $bandwidth;
3820
	}
3821
	function GetBurst() {
3822
		return $this->qburst;
3823
	}
3824
	function SetBurst($burst) {
3825
		$this->qburst = $burst;
3826
	}
3827

    
3828
	function &add_queue($interface, &$queue, &$path, &$input_errors) {
3829

    
3830
		if (!is_array($this->subqueues)) {
3831
			$this->subqueues = array();
3832
		}
3833

    
3834
		$__tmp_q = new dnqueue_class(); $q =& $__tmp_q;
3835
		$q->SetLink($path);
3836
		$q->SetEnabled("on");
3837
		$q->SetPipe($this->GetQname());
3838
		$q->SetParent($this);
3839
		$q->ReadConfig($queue);
3840
		$q->validate_input($queue, $input_errors);
3841

    
3842
		if (!is_array($input_errors)) {
3843
			$input_errors = array();
3844
		}
3845

    
3846
		if (count($input_errors)) {
3847
			log_error(sprintf(gettext('SHAPER: Could not create queue %1$s on interface %2$s because: %3$s'), $q->GetQname(), $interface, print_r($input_errors, true)));
3848
			return $q;
3849
		}
3850
		$number = dnqueue_find_nextnumber();
3851
		$q->SetNumber($number);
3852
		$this->subqueues[$q->GetQname()] = &$q;
3853

    
3854
		return $q;
3855
	}
3856

    
3857
	function &get_queue_list(&$q = null) {
3858
		$qlist = array();
3859

    
3860
		$qlist[$this->GetQname()] = $this->GetNumber();
3861
		if (is_array($this->subqueues)) {
3862
			foreach ($this->subqueues as $queue) {
3863
				$queue->get_queue_list($qlist);
3864
			}
3865
		}
3866
		return $qlist;
3867
	}
3868

    
3869
	/*
3870
	 * Should search even its children
3871
	 */
3872
	function &find_queue($pipe, $qname) {
3873
		if ($qname == $this->GetQname()) {
3874
			return $this;
3875
		}
3876
		foreach ($this->subqueues as $q) {
3877
			$result =& $q->find_queue("", $qname);
3878
			if ($result) {
3879
				return $result;
3880
			}
3881
		}
3882
	}
3883

    
3884
	function &find_parentqueue($pipe, $qname) {
3885
		return NULL;
3886
	}
3887

    
3888
	function validate_input($data, &$input_errors) {
3889
		parent::validate_input($data, $input_errors);
3890

    
3891
		$schedule = 0;
3892
		$schedulenone = 0;
3893
		$entries = 0;
3894
		/* XXX: Really no better way? */
3895
		for ($i = 0; $i < 2900; $i++) {
3896
			if (!empty($data["bwsched{$i}"])) {
3897
				if ($data["bwsched{$i}"] != "none") {
3898
					$schedule++;
3899
				} else {
3900
					$schedulenone++;
3901
				}
3902
			}
3903
			if (!empty($data["bandwidth{$i}"])) {
3904
				if (!is_numeric($data["bandwidth{$i}"])) {
3905
					$input_errors[] = sprintf(gettext("Bandwidth for schedule %s must be an integer."), $data["bwsched{$i}"]);
3906
				} else if (($data["burst{$i}"] != "") && (!is_numeric($data["burst{$i}"]))) {
3907
					$input_errors[] = sprintf(gettext("Burst for schedule %s must be an integer."), $data["bwsched{$i}"]);
3908
				} else {
3909
					$entries++;
3910
				}
3911
			}
3912
		}
3913
		if ($schedule == 0 && $entries > 1) {
3914
			$input_errors[] = gettext("A schedule needs to be specified for every additional entry.");
3915
		}
3916
		if ($schedulenone > 0 && $entries > 1) {
3917
			$input_errors[] = gettext("If more than one bandwidth configured all schedules need to be selected.");
3918
		}
3919
		if ($entries == 0) {
3920
			$input_errors[] = gettext("At least one bw specification is necessary.");
3921
		}
3922
		if ($data['delay'] && (!is_numeric($data['delay']))) {
3923
			$input_errors[] = gettext("Delay must be an integer.");
3924
		}
3925
		if ($data['delay'] && is_numeric($data['delay']) &&
3926
		    (($data['delay'] < 0) || ($data['delay'] > 10000))) {
3927
			$input_errors[] = gettext("Delay must be an integer between 0 and 10000.");
3928
		}
3929

    
3930
		/* Limiter patch */
3931
		$selectedScheduler = getSchedulers()[$data['sched']];
3932
		if (!$selectedScheduler) {
3933
			$input_errors[] = gettext("Selected scheduler not recognized.");
3934
		}
3935
		$selectedAqm = getAQMs()[$data['aqm']];
3936
		if (!empty($data['aqm']) && !$selectedAqm) {
3937
			$input_errors[] = gettext("Selected AQM not recognized.");
3938
		}
3939
		/* End limiter patch */
3940
	}
3941

    
3942
	function ReadConfig(&$q) {
3943
		if (!empty($q['name']) && !empty($q['newname']) && $q['name'] != $q['newname']) {
3944
			$this->SetQname($q['newname']);
3945
			rename_dnqueue_in_rules($q['name'], $q['newname']);
3946
		} else if (!empty($q['newname'])) {
3947
			$this->SetQname($q['newname']);
3948
		} else {
3949
			$this->SetQname($q['name']);
3950
		}
3951
		$this->SetNumber($q['number']);
3952

    
3953
		if (!empty($_POST)) {
3954
			$bandwidth = array();
3955
			/* XXX: Really no better way? */
3956
			for ($i = 0; $i < 2900; $i++) {
3957
				if (isset($q["bandwidth{$i}"]) && $q["bandwidth{$i}"] <> "") {
3958
					$bw = array();
3959
					$bw['bw'] = $q["bandwidth{$i}"];
3960
					$bw['burst'] = $q["burst{$i}"];
3961
					if (isset($q["bwtype{$i}"]) && $q["bwtype{$i}"]) {
3962
						$bw['bwscale'] = $q["bwtype{$i}"];
3963
					}
3964
					if (isset($q["bwsched{$i}"]) && $q["bwsched{$i}"]) {
3965
						$bw['bwsched'] = $q["bwsched{$i}"];
3966
					}
3967
					$bandwidth[] = $bw;
3968
				}
3969
			}
3970
			$this->SetBandwidth($bandwidth);
3971
		}
3972

    
3973
		if (is_array($q['bandwidth']) && is_array($q['bandwidth']['item'])) {
3974
			$this->SetBandwidth($q['bandwidth']['item']);
3975
			$this->SetBurst($q['burst']['item']);
3976
		}
3977

    
3978
		if (isset($q['qlimit']) && $q['qlimit'] <> "") {
3979
			$this->SetQlimit($q['qlimit']);
3980
		} else {
3981
			$this->SetQlimit("");
3982
		}
3983
		if (isset($q['mask']) && $q['mask'] <> "") {
3984
			$masktype = $q['mask'];
3985
		} else {
3986
			$masktype = "";
3987
		}
3988
		if (isset($q['maskbits']) && $q['maskbits'] <> "") {
3989
			$maskbits = $q['maskbits'];
3990
		} else {
3991
			$maskbits = "";
3992
		}
3993
		if (isset($q['maskbitsv6']) && $q['maskbitsv6'] <> "") {
3994
			$maskbitsv6 = $q['maskbitsv6'];
3995
		} else {
3996
			$maskbitsv6 = "";
3997
		}
3998
		$this->SetMask(array("type" => $masktype, "bits" => $maskbits, "bitsv6" => $maskbitsv6));
3999
		if (isset($q['buckets']) && $q['buckets'] <> "") {
4000
			$this->SetBuckets($q['buckets']);
4001
		} else {
4002
			$this->SetBuckets("");
4003
		}
4004
		if (isset($q['plr']) && $q['plr'] <> "") {
4005
			$this->SetPlr($q['plr']);
4006
		} else {
4007
			$this->SetPlr("");
4008
		}
4009
		if (isset($q['delay']) && $q['delay'] <> "") {
4010
			$this->SetDelay($q['delay']);
4011
		} else {
4012
			$this->SetDelay(0);
4013
		}
4014
		if (isset($q['description']) && $q['description'] <> "") {
4015
			$this->SetDescription($q['description']);
4016
		} else {
4017
			$this->SetDescription("");
4018
		}
4019
		$this->SetEnabled($q['enabled']);
4020

    
4021
		/* Limiter patch */
4022
		if (isset($q['aqm']) && $q['aqm'] <> "") {
4023
				$this->SetAQM($q['aqm']);
4024
		} else {
4025
				$this->SetAQM('droptail');
4026
		}
4027
		// parse AQM arguments
4028
		$aqm_map = getAQMs();
4029
		$selectedParameters = $aqm_map[$this->getAQM()]["parameters"];
4030
		if (is_array($selectedParameters)) {
4031
			foreach ($selectedParameters as $key => $value) {
4032
				$config_key = 'param_' . $this->GetAQM() . '_' . $key;
4033
				if (isset($q[$config_key]) && $q[$config_key] <> "") {
4034
					$this->SetAQMParameter($key, $q[$config_key]);
4035
				} else {
4036
					$this->SetAQMParameter($key, $value["default"]);
4037
				}
4038
			}
4039
		}
4040

    
4041
		if (isset($q['sched']) && $q['sched'] <> "") {
4042
				$this->SetScheduler($q['sched']);
4043
		} else {
4044
				$this->SetScheduler('fifo');
4045
		}
4046
		$scheduler_map = getSchedulers();
4047
		$selectedParameters = $scheduler_map[$this->getScheduler()]["parameters"];
4048
		if (is_array($selectedParameters)) {
4049
			foreach ($selectedParameters as $key => $value) {
4050
				$config_key = 'param_' . $this->GetScheduler() . '_' . $key;
4051
				if (isset($q[$config_key]) && $q[$config_key] <> "") {
4052
					$this->SetSchedulerParameter($key, $q[$config_key]);
4053
				} else {
4054
					$this->SetSchedulerParameter($key, $value["default"]);
4055
				}
4056
			}
4057
		}
4058

    
4059
		// ecn flag
4060
		$this->SetECN($q['ecn']);
4061
		// PIE Flags.
4062
		$this->SetPIE_ONOFF($q['pie_onoff']);
4063
		$this->SetPIE_CAPDROP($q['pie_capdrop']);
4064
		$this->SetPIE_QDELAY($q['pie_qdelay']);
4065
		$this->SetPIE_PDERAND($q['pie_pderand']);
4066
		/* End limiter patch */
4067
	}
4068

    
4069
	function build_tree() {
4070
		$tree = " <li><a href=\"firewall_shaper_vinterface.php?pipe=" . htmlspecialchars($this->GetQname()) ."&amp;queue=".htmlspecialchars($this->GetQname()) ."&amp;action=show\">";
4071
		$tree .= htmlspecialchars($this->GetQname()) . "</a>";
4072
		if (is_array($this->subqueues)) {
4073
			$tree .= "<ul>";
4074
			foreach ($this->subqueues as $q) {
4075
				$tree .= $q->build_tree();
4076
			}
4077
			$tree .= "</ul>";
4078
		}
4079
		$tree .= "</li>";
4080

    
4081
		return $tree;
4082
	}
4083

    
4084
	function build_rules() {
4085
		global $config, $time_based_rules;
4086

    
4087
		if ($this->GetEnabled() == "") {
4088
			return;
4089
		}
4090

    
4091
		$pfq_rule = "\npipe ". $this->GetNumber() . " config ";
4092
		$found = false;
4093
		$bandwidth = $this->GetBandwidth();
4094
		if (is_array($bandwidth)) {
4095
			foreach ($bandwidth as $bw) {
4096
				if ($bw['bwsched'] != "none") {
4097
					if (is_array($config['schedules']) && is_array($config['schedules']['schedule'])) {
4098
						foreach (config_get_path('schedules/schedule', []) as $schedule) {
4099
							if ($bw['bwsched'] == $schedule['name']) {
4100
								if (filter_get_time_based_rule_status($schedule)) {
4101
									/* pipe throughputs must always be an integer, enforce that restriction again here. */
4102
									$pfq_rule .= " bw ".round(trim($bw['bw']),0).$bw['bwscale'];
4103
									if (is_numeric($bw['burst']) && ($bw['burst'] > 0)) {
4104
										$pfq_rule .= " burst ".trim($bw['burst']);
4105
									}
4106
									$found = true;
4107
									break;
4108
								}
4109
							}
4110
						}
4111
					} else {
4112
						$pfq_rule .= " bw 0";
4113
						$found = true;
4114
						break;
4115
					}
4116
				} else {
4117
					/* pipe throughputs must always be an integer, enforce that restriction again here. */
4118
					$pfq_rule .= " bw ".round(trim($bw['bw']), 0).$bw['bwscale'];
4119
					if (is_numeric($bw['burst']) && ($bw['burst'] > 0)) {
4120
						$pfq_rule .= " burst ".trim($bw['burst']);
4121
					}
4122
					$found = true;
4123
					break;
4124
				}
4125
			}
4126
			if ($found == false) {
4127
				$pfq_rule .= " bw 0";
4128
			}
4129
		} else {
4130
			$pfq_rule .= " bw 0";
4131
		}
4132

    
4133
		if ($this->GetQlimit()) {
4134
			$pfq_rule .= " queue " . $this->GetQlimit();
4135
		}
4136
		if ($this->GetPlr()) {
4137
			$pfq_rule .= " plr " . $this->GetPlr();
4138
		}
4139
		if ($this->GetBuckets()) {
4140
			$pfq_rule .= " buckets " . $this->GetBuckets();
4141
		}
4142
		if ($this->GetDelay()) {
4143
			$pfq_rule .= " delay " . $this->GetDelay();
4144
		}
4145
		$this->build_mask_rules($pfq_rule);
4146

    
4147
		/* Limiter patch */
4148
		$selectedAQM = getAQMs()[$this->getAQM()];
4149
		if ($selectedAQM) {
4150
			$pfq_rule .= " " . FormatParameters($selectedAQM["parameter_format"], $this->GetAQMParameters());
4151
			if ($selectedAQM["ecn"]) {
4152
				if ($this->getECN() == 'on') {
4153
					$pfq_rule .= ' ecn';
4154
				} elseif (($this->getAQM() != 'red') && ($this->getAQM() != 'gred')) {
4155
					$pfq_rule .= ' noecn';
4156
				}
4157
			}
4158
		}
4159
		$selectedScheduler = getSchedulers()[$this->getScheduler()];
4160
		if ($selectedScheduler) {
4161
			$pfq_rule .= "\nsched ". $this->GetNumber() . " config ";
4162
			$pfq_rule .= "pipe ". $this->GetNumber();
4163
			$this->build_mask_rules($pfq_rule);
4164
			$pfq_rule .= " " . FormatParameters($selectedScheduler["parameter_format"], $this->GetSchedulerParameters());			
4165
			if ($selectedScheduler["ecn"]) {
4166
				if ($this->getECN() == 'on') {
4167
					$pfq_rule .= ' ecn';
4168
				} else {
4169
					$pfq_rule .= ' noecn';
4170
				}
4171
			}
4172
			if ($selectedScheduler["pie_onoff"]) {
4173
				if ($this->getPIE_ONOFF() == 'on') {
4174
					$pfq_rule .= 'onoff';
4175
				} else {
4176
					$pfq_rule .= '';
4177
				}
4178
			}
4179
			if ($selectedScheduler["pie_capdrop"]) {
4180
				if ($this->getPIE_CAPDROP() == 'on') {
4181
					$pfq_rule .= ' capdrop';
4182
				} else {
4183
					$pfq_rule .= ' nocapdrop';
4184
				}
4185
			}
4186
			if ($selectedScheduler["pie_qdelay"]) {
4187
				if ($this->getPIE_QDELAY() == 'on') {
4188
					$pfq_rule .= ' ts';
4189
				} else {
4190
					$pfq_rule .= ' dre';
4191
				}
4192
			}
4193
			if ($selectedScheduler["pie_pderand"]) {
4194
				if ($this->getPIE_PDERAND() == 'on') {
4195
					$pfq_rule .= ' derand';
4196
				} else {
4197
					$pfq_rule .= ' noderand';
4198
				}
4199
			}
4200
		}
4201
		$pfq_rule .= "\n";
4202
		/* End patch */
4203

    
4204
		if (!empty($this->subqueues) && count($this->subqueues) > 0) {
4205
			foreach ($this->subqueues as $q) {
4206
				$pfq_rule .= $q->build_rules();
4207
			}
4208
		}
4209

    
4210
		$pfq_rule .= " \n";
4211

    
4212
		return $pfq_rule;
4213
	}
4214

    
4215
	function update_dn_data(&$data) {
4216
		$this->ReadConfig($data);
4217
	}
4218

    
4219
	function build_javascript() {
4220
		global $g, $config;
4221

    
4222
		$javasr = parent::build_javascript();
4223

    
4224
		//build list of schedules
4225
		$schedules = "<option value='none'>none</option>";
4226
		if (is_array($config['schedules']) && is_array($config['schedules']['schedule'])) {
4227
			foreach (config_get_path('schedules/schedule', []) as $schedule) {
4228
				if ($schedule['name'] <> "") {
4229
					$schedules .= "<option value='{$schedule['name']}'>{$schedule['name']}</option>";
4230
				}
4231
			}
4232
		}
4233
		$bwopt = "";
4234
		foreach (array("Kb" => "Kbit/s", "Mb" => "Mbit/s", "Gb" => "Gbit/s", "b" => "Bit/s") as $bwidx => $bw) {
4235
			$bwopt .= "<option value='{$bwidx}'>{$bw}</option>";
4236
		}
4237

    
4238
		$javasr .= <<<EOD
4239
<script type='text/javascript'>
4240
//<![CDATA[
4241
var addBwRowTo = (function() {
4242

    
4243
	return (function (tableId) {
4244

    
4245
	var table = document.getElementById(tableId);
4246
	var totalrows = table.rows.length -1;
4247

    
4248
	var row = table.insertRow(totalrows + 1);
4249
	var cell1 = row.insertCell(0);
4250
	var cell2 = row.insertCell(1);
4251
	var cell3 = row.insertCell(2);
4252
	var cell4 = row.insertCell(3);
4253

    
4254
	cell1.innerHTML = "<input type='hidden' value='" + totalrows +"' name='bandwidth_row-" + totalrows + "' /><input type='text' class='form-control' name='bandwidth" + totalrows + "' id='bandwidth" + totalrows + "' />";
4255
	cell2.innerHTML = "<input type='hidden' value='" + totalrows +"' name='bwtype_row-" + totalrows + "' /><select class='form-control' name='bwtype" + totalrows + "'>{$bwopt}</select>";
4256
	cell3.innerHTML = "<input type='hidden' value='" + totalrows +"' name='bwsched_row-" + totalrows + "' /><select class='form-control' name='bwsched" + totalrows + "'>{$schedules}</select>";
4257
	cell4.innerHTML = '<a class="btn btn-warning" onclick="removeBwRow(this); return false;" href="#"><i class="fa-solid fa-trash-can icon-embed-btn"></i>Delete</a>';
4258

    
4259
	});
4260
})();
4261

    
4262
function removeBwRow(el) {
4263
	var d = el.parentNode.parentNode.rowIndex;
4264
	document.getElementById('maintable').deleteRow(d);
4265
}
4266

    
4267
function ceil_func(el){
4268
	el.value = Math.ceil(el.value);
4269

    
4270
}
4271
//]]>
4272
</script>
4273

    
4274
EOD;
4275

    
4276
		return $javasr;
4277
	}
4278

    
4279
	// Compose a table of bandwidths that can then be inserted into the form using a Form_StaticText
4280
	// The table has been "Bootstrapped" to match the web design while maintaining compatibility with
4281
	// with the javascript in this class
4282
	function build_bwtable() {
4283
		global $config;
4284

    
4285
		$bandwidth = $this->GetBandwidth();
4286
				//build list of schedules
4287
		$schedules = array();
4288
		$schedules[] = "none";//leave none to leave rule enabled all the time
4289
		if (is_array($config['schedules']) && is_array($config['schedules']['schedule'])) {
4290
			foreach (config_get_path('schedules/schedule', []) as $schedule) {
4291
				if ($schedule['name'] != "") {
4292
					$schedules[] = $schedule['name'];
4293
				}
4294
			}
4295
		}
4296

    
4297
		$form = '<div class="table-responsive">';
4298
		$form .= '<table id="maintable" class="table table-hover table-striped">';
4299
		$form .= "<thead><tr>";
4300
		$form .= "<th>Bandwidth</th>";
4301
		//$form .= "<td width='35%'><div id='fifthcolumn'>Burst</div></td>";
4302
		$form .= "<th>Bw type</th>";
4303
		$form .= "<th>Schedule</th>";
4304
		$form .= "<th></th>";
4305
		$form .= "</tr></thead>";
4306
		$form .= "<tbody>";
4307

    
4308
		// If there are no bandwidths defined, make a blank one for convenience
4309
		if (empty($bandwidth)) {
4310
			$bandwidth = array(0 => array('bw' => '', 'bwscale' => 'Mb', 'bwsched' => 'none'));
4311
		}
4312

    
4313
		if (is_array($bandwidth)) {
4314
			foreach ($bandwidth as $bwidx => $bw) {
4315
				$form .= '<tr>';
4316
				$form .= '<td class="col-xs-4">';
4317
				$form .= "<input class='form-control' onchange=\"ceil_func(this)\" type=\"number\" id=\"bandwidth{$bwidx}\" name=\"bandwidth{$bwidx}\" value=\"" . ceil2($bw['bw']) ."\" min=\"0\" step=\"1\"/>";
4318
				//$form .= "</td><td width='20%'>";
4319
				//$form .= "<input class='formfld unknown' size='10' type=\"text\" id=\"burst{$bwidx}\" name=\"burst{$bwidx}\" value=\"{$bw['burst']}\" />";
4320
				$form .= "</td>";
4321
				$form .= '<td class="col-xs-4">';
4322
				$form .= "<select id=\"bwtype{$bwidx}\" name=\"bwtype{$bwidx}\" class=\"form-control\">";
4323

    
4324
				foreach (array("Kb" => "Kbit/s", "Mb" => "Mbit/s", "b" => "Bit/s") as $bwsidx => $bwscale) {
4325
					$form .= "<option value=\"{$bwsidx}\"";
4326

    
4327
					if ($bw['bwscale'] == $bwsidx) {
4328
						$form .= " selected";
4329
					}
4330

    
4331
					$form .= ">{$bwscale}</option>";
4332
				}
4333

    
4334
				$form .= "</select>";
4335
				$form .= "</td>";
4336
				$form .= '<td class="col-xs-4">';
4337
				$form .= "<select id=\"bwsched{$bwidx}\" name=\"bwsched{$bwidx}\" class=\"form-control\">";
4338

    
4339
				foreach ($schedules as $schd) {
4340
					$selected = "";
4341
					if ($bw['bwsched'] == $schd) {
4342
						$selected = "selected";
4343
					}
4344

    
4345
					$form .= "<option value='{$schd}' {$selected}>{$schd}</option>";
4346
				}
4347

    
4348
				$form .= "</select>";
4349
				$form .= "</td>";
4350
				$form .= '<td>';
4351
				$form .= '<a class="btn btn-warning" onclick="removeBwRow(this); return false;"><i class="fa-solid fa-trash-can icon-embed-btn"></i>' . gettext('Delete') . '</a>';
4352
				$form .= "</td></tr>";
4353
			}
4354
		}
4355
		$form .= "</tbody></table></div><br />";
4356

    
4357
		$form .= '<a class="btn btn-sm btn-success" onclick="javascript:addBwRowTo(\'maintable\'); return false;" >';
4358
		$form .= '<i class="fa-solid fa-plus icon-embed-btn"></i>';
4359
		$form .= gettext("Add Schedule") . "</a>";
4360

    
4361
		return($form);
4362
	}
4363

    
4364
	function build_form() {
4365
		global $g, $config, $pipe, $action, $qname;
4366

    
4367
		//build list of schedules
4368
		$schedules = array();
4369
		$schedules[] = "none";//leave none to leave rule enabled all the time
4370
		if (is_array($config['schedules']) && is_array($config['schedules']['schedule'])) {
4371
			foreach (config_get_path('schedules/schedule', []) as $schedule) {
4372
				if ($schedule['name'] <> "") {
4373
					$schedules[] = $schedule['name'];
4374
				}
4375
			}
4376
		}
4377

    
4378

    
4379
		$sform = new Form();
4380
		$sform->setAction("firewall_shaper.php");
4381

    
4382
		$section = new Form_Section('Limiters');
4383

    
4384
		$section->addInput(new Form_Checkbox(
4385
			'enabled',
4386
			'Enable',
4387
			'Enable limiter and its children',
4388
			($this->GetEnabled() == "on"),
4389
			'on'
4390
		));
4391

    
4392
		$section->addInput(new Form_Input(
4393
			'newname',
4394
			'*Name',
4395
			'text',
4396
			$this->GetQname()
4397
		));
4398

    
4399
		$section->addInput(new Form_Input(
4400
			'name',
4401
			null,
4402
			'hidden',
4403
			$this->GetQname()
4404
		));
4405

    
4406
		if ($this->GetNumber() > 0) {
4407
			$section->addInput(new Form_Input(
4408
				'number',
4409
				null,
4410
				'hidden',
4411
				$this->GetNumber()
4412
			));
4413
		}
4414

    
4415
		$bandwidth = $this->GetBandwidth();
4416

    
4417
		if (is_array($bandwidth)) {
4418
				$section->addInput(new Form_StaticText(
4419
				'Bandwidth',
4420
				$this->build_bwtable()
4421
			));
4422
		}
4423

    
4424
		$mask = $this->GetMask();
4425

    
4426
		$section->addInput(new Form_Select(
4427
			'mask',
4428
			'Mask',
4429
			$mask['type'],
4430
			array('none' => gettext('None'), 'srcaddress' => gettext('Source addresses'), 'dstaddress' => gettext('Destination addresses'))
4431
		))->setHelp('If "source" or "destination" slots is chosen a dynamic pipe with the bandwidth, delay, packet loss ' .
4432
					'and queue size given above will be created for each source/destination IP address encountered, respectively. ' .
4433
					'This makes it possible to easily specify bandwidth limits per host or subnet.');
4434

    
4435
		$group = new Form_Group(null);
4436

    
4437
		$group->add(new Form_Select(
4438
			'maskbits',
4439
			null,
4440
			$mask['bits'],
4441
			array_combine(range(32, 1, -1), range(32, 1, -1))
4442
		))->setHelp('IPv4 mask bits%1$s%2$s', '<br />', '255.255.255.255/?');
4443

    
4444
		$group->add(new Form_Select(
4445
			'maskbitsv6',
4446
			null,
4447
			$mask['bitsv6'],
4448
			array_combine(range(128, 1, -1), range(128, 1, -1))
4449
		))->setHelp('IPv6 mask bits%1$s%2$s', '<br />', '<span style="font-family:consolas">ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/?</span>');
4450

    
4451
		$section->add($group);
4452

    
4453
		$section->addInput(new Form_Input(
4454
			'description',
4455
			'Description',
4456
			'text',
4457
			$this->GetDescription()
4458
		))->setHelp('A description may be entered here for administrative reference (not parsed).');
4459

    
4460
		$sform->add($section);
4461

    
4462
		/* Begin limiter patch */
4463
		$aqm_map = getAQMs();
4464
		$scheduler_map = getSchedulers();
4465

    
4466
		$section = new Form_Section('Queue');
4467
		$section->addInput(new Form_Select(
4468
				'aqm',
4469
				'Queue Management Algorithm',
4470
				$this->GetAQM(),
4471
				array_map_assoc(function ($k, $v) {
4472
					return [$k, $v["name"]];
4473
				}, $aqm_map)
4474
		))->setHelp('Active queue management (AQM) is the intelligent drop of network packets inside the limiter, ' .
4475
						'when it becomes full or gets close to becoming full, with the goal of reducing ' .
4476
						'network congestion.');
4477

    
4478
		$section->addInput(new Form_StaticText(
4479
			'',
4480
			build_queue_params($aqm_map, $this->GetAQM(), $this->GetAQMParameters(), "aqm")
4481
		))->setHelp('Specifies the queue management algorithm parameters.');
4482

    
4483
		$section->addInput(new Form_Select(
4484
				'sched',
4485
				'Scheduler',
4486
				$this->GetScheduler(),
4487
				array_map_assoc(function ($k, $v) {
4488
					return [$k, $v["name"]];
4489
				}, $scheduler_map)
4490
		))->setHelp('The scheduler manages the sequence of network packets in the limiter\'s queue.');
4491

    
4492
		$section->addInput(new Form_StaticText(
4493
			'',
4494
			build_queue_params($scheduler_map, $this->GetScheduler(), $this->GetSchedulerParameters(), "sched")
4495
		))->setHelp('Specifies the scheduler parameters.');
4496

    
4497
		$section->addInput(new Form_Input(
4498
				'qlimit',
4499
				'Queue length',
4500
				'number',
4501
				$this->GetQlimit()
4502
		))->setHelp('Specifies the length of the limiter\'s queue, which the scheduler and AQM are responsible for. ' .
4503
			'This field may be left empty.');
4504

    
4505
		$selectedScheduler = getSchedulers()[$this->getScheduler()];
4506

    
4507
		if ($selectedScheduler["ecn"]) {
4508
			$section->addInput(new Form_Checkbox(
4509
				'ecn',
4510
				'ECN',
4511
				'Enable Explicit Congestion Notification (ECN)',
4512
				($this->GetECN() == "on"),
4513
				'on'
4514
			))->setHelp('ECN sets a reserved TCP flag when the queue is nearing or exceeding capacity. Not all AQMs or schedulers support this.');
4515
		}
4516

    
4517
		if ($selectedScheduler["pie_onoff"]) {
4518
			$section->addInput(new Form_Checkbox(
4519
				'pie_onoff',
4520
				'ONOFF',
4521
				'Enable Onoff (onoff,)',
4522
				($this->GetPIE_ONOFF() == "on"),
4523
				'on'
4524
                	))->setHelp('Enable turning PIE on and off depending on queue load.');
4525
		}
4526

    
4527
		if ($selectedScheduler["pie_capdrop"]) {
4528
			$section->addInput(new Form_Checkbox(
4529
				'pie_capdrop',
4530
				'CAPDROP',
4531
				'Enable Capdrop (capdrop,nocapdrop)',
4532
				($this->GetPIE_CAPDROP() == "on"),
4533
				'on'
4534
			))->setHelp('Enable cap drop adjustment.');
4535
		}
4536

    
4537
		if ($selectedScheduler["pie_qdelay"]) {
4538
                        $section->addInput(new Form_Checkbox(
4539
                                'pie_qdelay',
4540
                                'QUEUE DELAY TYPE',
4541
                                'Enable Type Of Qdelay (ts,dre)',
4542
                                ($this->GetPIE_QDELAY() == "on"),
4543
                                'on'
4544
                        ))->setHelp('Set queue delay type to timestamps (checked) or departure rate estimation (unchecked).');
4545
                }
4546

    
4547
		if ($selectedScheduler["pie_pderand"]) {
4548
			$section->addInput(new Form_Checkbox(
4549
				'pie_pderand',
4550
				'PROB DERAND',
4551
				'Enable Drop Probability De-randomisation (derand,noderand)',
4552
				($this->GetPIE_PDERAND() == "on"),
4553
				'on'
4554
			))->setHelp('Enable (checked) or disable (unchecked) drop probability de-randomisation.');
4555
		}
4556

    
4557
		$sform->add($section);
4558
		/* End limiter patch */
4559

    
4560
		$section = new Form_Section('Advanced Options');
4561

    
4562
		$section->addInput(new Form_Input(
4563
			'delay',
4564
			'Delay (ms)',
4565
			'text',
4566
			$this->GetDelay() > 0 ? $this->GetDelay():null
4567
		))->setHelp('In most cases, zero (0) should specified here (or leave the field empty).');
4568

    
4569
		$section->addInput(new Form_Input(
4570
			'plr',
4571
			'Packet Loss Rate',
4572
			'number',
4573
			$this->GetPlr(),
4574
			['step' => '0.001', 'min' => '0.000']
4575
		))->setHelp('In most cases, zero (0) should be specified here (or leave the field empty). ' .
4576
					'A value of 0.001 means one packet in 1000 gets dropped.');
4577

    
4578
		$section->addInput(new Form_Input(
4579
			'buckets',
4580
			'Bucket size (slots)',
4581
			'number',
4582
			$this->GetBuckets()
4583
		))->setHelp('In most cases, this field should be left empty. It increases the hash size set.');
4584

    
4585
		$sform->add($section);
4586

    
4587
		return($sform);
4588
		}
4589

    
4590
	function wconfig() {
4591
		$cflink =& get_dn_reference_to_me_in_config($this->GetLink());
4592
		if (!is_array($cflink)) {
4593
			$cflink = array();
4594
		}
4595
		$cflink['name'] = $this->GetQname();
4596
		$cflink['number'] = $this->GetNumber();
4597
		$cflink['qlimit'] = $this->GetQlimit();
4598
		$cflink['plr'] = $this->GetPlr();
4599
		$cflink['description'] = $this->GetDescription();
4600

    
4601
		$bandwidth = $this->GetBandwidth();
4602
		if (is_array($bandwidth)) {
4603
			$cflink['bandwidth'] = array();
4604
			$cflink['bandwidth']['item'] = array();
4605
			foreach ($bandwidth as $bwidx => $bw) {
4606
				$cflink['bandwidth']['item'][] = $bw;
4607
			}
4608
		}
4609

    
4610
		$cflink['enabled'] = $this->GetEnabled();
4611
		$cflink['buckets'] = $this->GetBuckets();
4612
		$mask = $this->GetMask();
4613
		$cflink['mask'] = $mask['type'];
4614
		$cflink['maskbits'] = $mask['bits'];
4615
		$cflink['maskbitsv6'] = $mask['bitsv6'];
4616
		$cflink['delay'] = $this->GetDelay();
4617

    
4618
		/* Limiter queue patch */
4619
		$cflink['sched'] = $this->GetScheduler();
4620
		$scheduler_map = getSchedulers();
4621
		$selectedParameters = $scheduler_map[$this->getScheduler()]["parameters"];
4622
		foreach ($selectedParameters as $key => $value) {
4623
			$config_key = 'param_' . $this->GetScheduler() . '_' . $key;
4624
			$cflink[$config_key] = $this->GetSchedulerParameter($key);
4625
		}
4626
		$cflink['aqm'] = $this->GetAQM();
4627
		$aqm_map = GetAQMs();
4628
		$selectedParameters = $aqm_map[$this->getAQM()]["parameters"];
4629
		foreach ($selectedParameters as $key => $value) {
4630
			$config_key = 'param_' . $this->GetAQM() . '_' . $key;
4631
			$cflink[$config_key] = $this->GetAQMParameter($key);
4632
		}
4633
		$cflink['ecn'] = $this->GetECN();
4634

    
4635
		$selectedScheduler = getSchedulers()[$this->getScheduler()];
4636
		if ($selectedScheduler["pie_onoff"]) {
4637
			$cflink['pie_onoff'] = $this->GetPIE_ONOFF();
4638
		}
4639
		if ($selectedScheduler["pie_capdrop"]) {
4640
			$cflink['pie_capdrop'] = $this->GetPIE_CAPDROP();
4641
		}
4642
		if ($selectedScheduler["pie_qdelay"]) {
4643
			$cflink['pie_qdelay'] = $this->GetPIE_QDELAY();
4644
		}
4645
		if ($selectedScheduler["pie_pderand"]) {
4646
			$cflink['pie_pderand'] = $this->GetPIE_PDERAND();
4647
		}
4648
		/* End limiter queue patch */
4649
	}
4650

    
4651
}
4652

    
4653
class dnqueue_class extends dummynet_class {
4654
	var $pipeparent;
4655
	var $weight;
4656
	/* Limiter queue patch */
4657
    var $ecn; // ecn 'on' or 'off'
4658
	var $pie_onoff;
4659
	var $pie_capdrop;
4660
	var $pie_qdelay;
4661
	var $pie_pderand;
4662
    var $aqm; // key to aqm_map
4663
    var $aqm_params = array(); // AQM params
4664
	function GetAQM() {
4665
			return $this->aqm;
4666
	}
4667
	function SetAQM($aqm) {
4668
			$this->aqm = $aqm;
4669
	}
4670
	function GetAQMParameters() {
4671
			return $this->aqm_params;
4672
	}
4673
	function GetAQMParameter($parameter) {
4674
			return $this->aqm_params[$parameter];
4675
	}
4676
	function SetAQMParameter($key, $value) {
4677
			return $this->aqm_params[$key] = $value;
4678
	}
4679
	function SetAQMParameters($params) {
4680
			$this->aqm_params = $params;
4681
	}
4682
	function GetECN() {
4683
			return $this->ecn;
4684
	}
4685
	function SetECN($ecn) {
4686
			$this->ecn = $ecn;
4687
	}
4688
	function GetPIE_ONOFF() {
4689
			return $this->pie_onoff;
4690
	}
4691
	function SetPIE_ONOFF($pie_onoff) {
4692
			$this->pie_onoff = $pie_onoff;
4693
	}
4694
	function GetPIE_CAPDROP() {
4695
			return $this->pie_capdrop;
4696
	}
4697
	function SetPIE_CAPDROP($pie_capdrop) {
4698
			$this->pie_capdrop = $pie_capdrop;
4699
	}
4700
	function GetPIE_QDELAY() {
4701
			return $this->pie_qdelay;
4702
	}
4703
	function SetPIE_QDELAY($pie_qdelay) {
4704
			$this->pie_qdelay = $pie_qdelay;
4705
	}
4706
	function GetPIE_PDERAND() {
4707
			return $this->pie_pderand;
4708
	}
4709
	function SetPIE_PDERAND($pie_pderand) {
4710
			$this->pie_pderand = $pie_pderand;
4711
	}
4712
	/* End limiter queue patch */
4713

    
4714
	function GetWeight() {
4715
		return $this->weight;
4716
	}
4717
	function SetWeight($weight) {
4718
		$this->weight = $weight;
4719
	}
4720
	function GetPipe() {
4721
		return $this->pipeparent;
4722
	}
4723
	function SetPipe($pipe) {
4724
		$this->pipeparent = $pipe;
4725
	}
4726

    
4727
	/* Just a stub in case we ever try to call this from the frontend. */
4728
	function &add_queue($interface, &$queue, &$path, &$input_errors) {
4729
		return;
4730
	}
4731

    
4732
	function delete_queue() {
4733
		cleanup_dnqueue_from_rules($this->GetQname());
4734
		unset_dn_object_by_reference($this->GetLink());
4735
		mwexec("/sbin/dnctl queue delete " . $this->GetNumber());
4736
	}
4737

    
4738
	function validate_input($data, &$input_errors) {
4739
		parent::validate_input($data, $input_errors);
4740

    
4741

    
4742
		/* Limiter patch */
4743
		$selectedAqm = getAQMs()[$data['aqm']];
4744
		if (!empty($data['aqm']) && !$selectedAqm) {
4745
			$input_errors[] = gettext("Selected AQM not recognized.");
4746
		}
4747
		/* End limiter patch */
4748

    
4749
		if ($data['weight'] && ((!is_numeric($data['weight'])) ||
4750
		    ($data['weight'] < 1 && $data['weight'] > 100))) {
4751
			$input_errors[] = gettext("Weight must be an integer between 1 and 100.");
4752
		}
4753
	}
4754

    
4755
	/*
4756
	 * Should search even its children
4757
	 */
4758
	function &find_queue($pipe, $qname) {
4759
		if ($qname == $this->GetQname()) {
4760
			return $this;
4761
		} else {
4762
			return NULL;
4763
		}
4764
	}
4765

    
4766
	function &find_parentqueue($pipe, $qname) {
4767
		return $this->qparent;
4768
	}
4769

    
4770
	function &get_queue_list(&$qlist) {
4771
		if ($this->GetEnabled() == "") {
4772
			return;
4773
		}
4774
		$qlist[$this->GetQname()] = "?" .$this->GetNumber();
4775
	}
4776

    
4777
	function ReadConfig(&$q) {
4778
		if (!empty($q['name']) && !empty($q['newname']) && $q['name'] != $q['newname']) {
4779
			$this->SetQname($q['newname']);
4780
		} else if (!empty($q['newname'])) {
4781
			$this->SetQname($q['newname']);
4782
		} else {
4783
			$this->SetQname($q['name']);
4784
		}
4785
		$this->SetNumber($q['number']);
4786
		if (isset($q['qlimit']) && $q['qlimit'] <> "") {
4787
			$this->SetQlimit($q['qlimit']);
4788
		} else {
4789
			$this->SetQlimit("");
4790
		}
4791
		if (isset($q['mask']) && $q['mask'] <> "") {
4792
			$masktype = $q['mask'];
4793
		} else {
4794
			$masktype = "";
4795
		}
4796
		if (isset($q['maskbits']) && $q['maskbits'] <> "") {
4797
			$maskbits = $q['maskbits'];
4798
		} else {
4799
			$maskbits = "";
4800
		}
4801
		if (isset($q['maskbitsv6']) && $q['maskbitsv6'] <> "") {
4802
			$maskbitsv6 = $q['maskbitsv6'];
4803
		} else {
4804
			$maskbitsv6 = "";
4805
		}
4806
		$this->SetMask(array("type" => $masktype, "bits" => $maskbits, "bitsv6" => $maskbitsv6));
4807
		if (isset($q['buckets']) && $q['buckets'] <> "") {
4808
			$this->SetBuckets($q['buckets']);
4809
		} else {
4810
			$this->SetBuckets("");
4811
		}
4812
		if (isset($q['plr']) && $q['plr'] <> "") {
4813
			$this->SetPlr($q['plr']);
4814
		} else {
4815
			$this->SetPlr("");
4816
		}
4817
		if (isset($q['weight']) && $q['weight'] <> "") {
4818
			$this->SetWeight($q['weight']);
4819
		} else {
4820
			$this->SetWeight("");
4821
		}
4822
		if (isset($q['description']) && $q['description'] <> "") {
4823
			$this->SetDescription($q['description']);
4824
		} else {
4825
			$this->SetDescription("");
4826
		}
4827
		$this->SetEnabled($q['enabled']);
4828

    
4829
		/* Limiter patch */
4830
		if (isset($q['aqm']) && $q['aqm'] <> "") {
4831
				$this->SetAQM($q['aqm']);
4832
		} else {
4833
				$this->SetAQM('droptail');
4834
		}
4835
		// parse AQM arguments
4836
		$aqm_map = getAQMs();
4837
		$selectedParameters = $aqm_map[$this->getAQM()]["parameters"];
4838
		foreach ($selectedParameters as $key => $value) {
4839
			$config_key = 'param_' . $this->GetAQM() . '_' . $key;
4840
			if (isset($q[$config_key]) && $q[$config_key] <> "") {
4841
				$this->SetAQMParameter($key, $q[$config_key]);
4842
			} else {
4843
				$this->SetAQMParameter($key, $value["default"]);
4844
			}
4845
		}
4846

    
4847
		// ecn flag
4848
		$this->SetECN($q['ecn']);
4849
		// PIE Flags.
4850
		$this->SetPIE_ONOFF($q['pie_onoff']);
4851
		$this->SetPIE_CAPDROP($q['pie_capdrop']);
4852
		$this->SetPIE_QDELAY($q['pie_qdelay']);
4853
		$this->SetPIE_PDERAND($q['pie_pderand']);
4854
		/* End limiter patch */
4855
	}
4856

    
4857
	function build_tree() {
4858
		$parent =& $this->GetParent();
4859
		$tree = " <li><a href=\"firewall_shaper_vinterface.php?pipe=" . htmlspecialchars($parent->GetQname()) ."&amp;queue=" . htmlspecialchars($this->GetQname()) ."&amp;action=show\">";
4860
		$tree .= htmlspecialchars($this->GetQname()) . "</a>";
4861
		$tree .= "</li>";
4862

    
4863
		return $tree;
4864
	}
4865

    
4866
	function build_rules() {
4867
		if ($this->GetEnabled() == "") {
4868
			return;
4869
		}
4870

    
4871
		$parent =& $this->GetParent();
4872
		$pfq_rule = "queue ". $this->GetNumber() . " config pipe " . $parent->GetNumber();
4873
		if ($this->GetQlimit()) {
4874
			$pfq_rule .= " queue " . $this->GetQlimit();
4875
		}
4876
		if ($this->GetWeight()) {
4877
			$pfq_rule .= " weight " . $this->GetWeight();
4878
		}
4879
		if ($this->GetBuckets()) {
4880
			$pfq_rule .= " buckets " . $this->GetBuckets();
4881
		}
4882
		$this->build_mask_rules($pfq_rule);
4883

    
4884
		/* Limiter patch */
4885
		$selectedAQM = getAQMs()[$this->getAQM()];
4886
		if ($selectedAQM) {
4887
			$pfq_rule .= " " . FormatParameters($selectedAQM["parameter_format"], $this->GetAQMParameters());
4888
			if ($selectedAQM["ecn"]) {
4889
				if ($this->getECN() == 'on') {
4890
					$pfq_rule .= ' ecn';
4891
				} elseif (($this->getAQM() != 'red') && ($this->getAQM() != 'gred')) {
4892
					$pfq_rule .= ' noecn';
4893
				}
4894
			}
4895
			if ($selectedAQM["pie_onoff"]) {
4896
				if ($this->getPIE_ONOFF() == 'on') {
4897
				$pfq_rule .= 'onoff';
4898
				} else {
4899
					$pfq_rule .= '';
4900
				}
4901
			}
4902
			if ($selectedAQM["pie_capdrop"]) {
4903
				if ($this->getPIE_CAPDROP() == 'on') {
4904
					$pfq_rule .= ' capdrop';
4905
				} else {
4906
					$pfq_rule .= ' nocapdrop';
4907
				}
4908
			}
4909
			if ($selectedAQM["pie_qdelay"]) {
4910
				if ($this->getPIE_QDELAY() == 'on') {
4911
					$pfq_rule .= ' ts';
4912
				} else {
4913
					$pfq_rule .= ' dre';
4914
				}
4915
			}
4916
			if ($selectedAQM["pie_pderand"]) {
4917
				if ($this->getPIE_PDERAND() == 'on') {
4918
					$pfq_rule .= ' derand';
4919
				} else {
4920
					$pfq_rule .= ' noderand';
4921
				}
4922
			}
4923
		}
4924
		/* End patch */
4925

    
4926
		$pfq_rule .= "\n";
4927

    
4928
		return $pfq_rule;
4929
	}
4930

    
4931
	function build_javascript() {
4932
		return parent::build_javascript();
4933
	}
4934

    
4935
	function build_form() {
4936
		global $g, $config, $pipe, $action, $qname;
4937

    
4938
		//build list of schedules
4939
		$schedules = array();
4940
		$schedules[] = "none";//leave none to leave rule enabled all the time
4941
		if (is_array($config['schedules']) && is_array($config['schedules']['schedule'])) {
4942
			foreach (config_get_path('schedules/schedule', []) as $schedule) {
4943
				if ($schedule['name'] <> "") {
4944
					$schedules[] = $schedule['name'];
4945
				}
4946
			}
4947
		}
4948

    
4949

    
4950
		$sform = new Form();
4951
		$sform->setAction("firewall_shaper.php");
4952
		$section = new Form_Section('Limiters');
4953

    
4954
		$section->addInput(new Form_Checkbox(
4955
			'enabled',
4956
			'Enable',
4957
			'Enable this queue',
4958
			($this->GetEnabled() == "on"),
4959
			'on'
4960
		));
4961

    
4962
		$section->addInput(new Form_Input(
4963
			'newname',
4964
			'*Name',
4965
			'text',
4966
			$this->GetQname()
4967
		));
4968

    
4969
		$section->addInput(new Form_Input(
4970
			'name',
4971
			null,
4972
			'hidden',
4973
			$this->GetQname()
4974
		));
4975

    
4976
		if ($this->GetNumber() > 0) {
4977
			$section->addInput(new Form_Input(
4978
				'number',
4979
				null,
4980
				'hidden',
4981
				$this->GetNumber()
4982
			));
4983
		}
4984

    
4985
		$mask = $this->GetMask();
4986

    
4987
		$section->addInput(new Form_Select(
4988
			'mask',
4989
			'Mask',
4990
			$mask['type'],
4991
			array('none' => gettext('None'), 'srcaddress' => gettext('Source addresses'), 'dstaddress' => gettext('Destination addresses'))
4992
		))->setHelp('If "source" or "destination" slots is chosen a dynamic queue with the bandwidth, delay, packet loss ' .
4993
					'and queue size given above will be created for each source/destination IP address encountered, respectively. ' .
4994
 					'This makes it possible to easily specify bandwidth limits per ' .
4995
 					'host or subnet, usually capped by the bandwidth of the parent ' .
4996
 					'limiter.');
4997

    
4998
		$group = new Form_Group(null);
4999

    
5000
		$group->add(new Form_Select(
5001
			'maskbits',
5002
			null,
5003
			$mask['bits'],
5004
			array_combine(range(32, 1, -1), range(32, 1, -1))
5005
		))->setHelp('IPv4 mask bits%1$s%2$s', '<br />', '255.255.255.255/?');
5006

    
5007
		$group->add(new Form_Select(
5008
			'maskbitsv6',
5009
			null,
5010
			$mask['bitsv6'],
5011
			array_combine(range(128, 1, -1), range(128, 1, -1))
5012
		))->setHelp('IPv6 mask bits%1$s%2$s', '<br />', '<span style="font-family:consolas">ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/?</span>');
5013

    
5014
		$section->add($group);
5015

    
5016
		$section->addInput(new Form_Input(
5017
			'description',
5018
			'Description',
5019
			'text',
5020
			$this->GetDescription()
5021
		))->setHelp('A description may be entered here for administrative reference (not parsed).');
5022

    
5023
		$sform->add($section);
5024

    
5025
		/* Begin limiter patch */
5026
		$aqm_map = getAQMs();
5027

    
5028
		$section = new Form_Section('Queue');
5029
		$section->addInput(new Form_Select(
5030
				'aqm',
5031
				'Queue Management Algorithm',
5032
				$this->GetAQM(),
5033
				array_map_assoc(function ($k, $v) {
5034
					return [$k, $v["name"]];
5035
				}, $aqm_map)
5036
		))->setHelp('Active queue management (AQM) is the intelligent drop of network packets inside this limiter\'s queue, ' .
5037
						'when it becomes full or gets close to becoming full, with the goal of reducing ' .
5038
						'network congestion.');
5039

    
5040
		$section->addInput(new Form_StaticText(
5041
			'',
5042
			build_queue_params($aqm_map, $this->GetAQM(), $this->GetAQMParameters(), "aqm")
5043
		))->setHelp('Specifies the queue management algorithm parameters.');
5044

    
5045
		$section->addInput(new Form_Input(
5046
				'qlimit',
5047
				'Queue length',
5048
				'number',
5049
				$this->GetQlimit()
5050
		))->setHelp('Specifies the length of this queue, which the AQM is responsible for.  This field may be left empty.');
5051

    
5052
		$selectedAQM = getAQMs()[$this->getAQM()];
5053

    
5054
		if ($selectedAQM["ecn"]) {
5055
			$section->addInput(new Form_Checkbox(
5056
				'ecn',
5057
				'ECN',
5058
				'Enable Explicit Congestion Notification (ECN)',
5059
				($this->GetECN() == "on"),
5060
				'on'
5061
			))->setHelp('ECN sets a reserved TCP flag when the queue is nearing or exceeding capacity. Not all AQMs or schedulers support this.');
5062
		}
5063

    
5064
		if ($selectedAQM["pie_onoff"]) {
5065
			$section->addInput(new Form_Checkbox(
5066
				'pie_onoff',
5067
				'ONOFF',
5068
				'Enable Onoff (onoff,)',
5069
				($this->GetPIE_ONOFF() == "on"),
5070
				'on'
5071
			))->setHelp('Enable turning PIE on and off depending on queue load.');
5072
		}
5073
                                 
5074
		if ($selectedAQM["pie_capdrop"]) {
5075
			$section->addInput(new Form_Checkbox(
5076
				'pie_capdrop',
5077
				'CAPDROP',
5078
				'Enable Capdrop (capdrop,nocapdrop)',
5079
				($this->GetPIE_CAPDROP() == "on"),
5080
				'on'
5081
			))->setHelp('Enable cap drop adjustment.');
5082
		}
5083
                 
5084
		if ($selectedAQM["pie_qdelay"]) {
5085
			$section->addInput(new Form_Checkbox(
5086
				'pie_qdelay',
5087
				'QUEUE DELAY TYPE',
5088
				'Enable Type Of Qdelay (ts,dre)',
5089
				($this->GetPIE_QDELAY() == "on"),
5090
				'on'
5091
			))->setHelp('Set queue delay type to timestamps (checked) or departure rate estimation (unchecked).');
5092
		}
5093

    
5094
		if ($selectedAQM["pie_pderand"]) {
5095
			$section->addInput(new Form_Checkbox(
5096
				'pie_pderand',
5097
				'PROB DERAND',
5098
				'Enable Drop Probability De-randomisation (derand,noderand)',
5099
				($this->GetPIE_PDERAND() == "on"),
5100
				'on'
5101
			))->setHelp('Enable (checked) or disable (unchecked) drop probability de-randomisation.');
5102
		}
5103
		$sform->add($section);
5104
		/* End limiter patch */
5105

    
5106
		$section = new Form_Section('Advanced Options');
5107

    
5108
		$section->addInput(new Form_Input(
5109
			'weight',
5110
			'Weight',
5111
			'number',
5112
			$this->GetWeight(),
5113
			['min' => '1', 'max' => '100']
5114
		))->setHelp('For queues under the same parent this specifies the share that a queue gets(values range from 1 to 100),' .
5115
					' it can be left blank otherwise.');
5116

    
5117
		$section->addInput(new Form_Input(
5118
			'plr',
5119
			'Packet Loss Rate',
5120
			'number',
5121
			$this->GetPlr(),
5122
			['step' => '0.001', 'min' => '0.000']
5123
		))->setHelp('In most cases, zero (0) should be specified here (or leave the field empty). ' .
5124
					'A value of 0.001 means one packet in 1000 gets dropped');
5125

    
5126
		$section->addInput(new Form_Input(
5127
			'buckets',
5128
			'Bucket size (slots)',
5129
			'number',
5130
			$this->GetBuckets()
5131
		))->setHelp('In most cases, this field should be left empty. It increases the hash size set');
5132

    
5133
		$section->addInput(new Form_Input(
5134
			'pipe',
5135
			null,
5136
			'hidden',
5137
			$this->GetPipe()
5138
		));
5139

    
5140
		$sform->add($section);
5141

    
5142
		return($sform);
5143
	}
5144

    
5145
	function update_dn_data(&$data) {
5146
		$this->ReadConfig($data);
5147
	}
5148

    
5149
	function wconfig() {
5150
		$cflink =& get_dn_reference_to_me_in_config($this->GetLink());
5151
		if (!is_array($cflink)) {
5152
			$cflink = array();
5153
		}
5154
		$cflink['name'] = $this->GetQname();
5155
		$cflink['number'] = $this->GetNumber();
5156
		$cflink['qlimit'] = $this->GetQlimit();
5157
		$cflink['description'] = $this->GetDescription();
5158
		$cflink['weight'] = $this->GetWeight();
5159
		$cflink['enabled'] = $this->GetEnabled();
5160
		$cflink['buckets'] = $this->GetBuckets();
5161
		$mask = $this->GetMask();
5162
		$cflink['mask'] = $mask['type'];
5163
		$cflink['maskbits'] = $mask['bits'];
5164
		$cflink['maskbitsv6'] = $mask['bitsv6'];
5165

    
5166
		/* Limiter queue patch */
5167
		$cflink['aqm'] = $this->GetAQM();
5168
		$aqm_map = GetAQMs();
5169
		$selectedParameters = $aqm_map[$this->getAQM()]["parameters"];
5170
		foreach ($selectedParameters as $key => $value) {
5171
			$config_key = 'param_' . $this->GetAQM() . '_' . $key;
5172
			$cflink[$config_key] = $this->GetAQMParameter($key);
5173
		}
5174
		$cflink['ecn'] = $this->GetECN();
5175

    
5176
		$selectedAQM = getAQMs()[$this->getAQM()];
5177
		if ($selectedAQM["pie_onoff"]) {
5178
			$cflink['pie_onoff'] = $this->GetPIE_ONOFF();
5179
		}
5180
		if ($selectedAQM["pie_capdrop"]) {
5181
			$cflink['pie_capdrop'] = $this->GetPIE_CAPDROP();
5182
		}
5183
		if ($selectedAQM["pie_qdelay"]) {
5184
			$cflink['pie_qdelay'] = $this->GetPIE_QDELAY();
5185
		}
5186
		if ($selectedAQM["pie_pderand"]) {
5187
			$cflink['pie_pderand'] = $this->GetPIE_PDERAND();
5188
		}
5189
		/* End limiter queue patch */
5190
	}
5191
}
5192

    
5193
function get_dummynet_name_list() {
5194

    
5195
	$dn_name_list =& get_unique_dnqueue_list();
5196
	$dn_name = array();
5197
	if (is_array($dn_name_list)) {
5198
		foreach ($dn_name_list as $key => $value) {
5199
			$dn_name[] = $key;
5200
		}
5201
	}
5202

    
5203
	return $dn_name;
5204

    
5205
}
5206

    
5207
function get_altq_name_list() {
5208
	$altq_name_list =& get_unique_queue_list();
5209
	$altq_name = array();
5210
	if (is_array($altq_name_list)) {
5211
		foreach ($altq_name_list as $key => $aqobj) {
5212
			$altq_name[] = $key;
5213
		}
5214
	}
5215

    
5216
	return $altq_name;
5217
}
5218

    
5219
/*
5220
 * XXX: TODO Make a class shaper to hide all these functions
5221
 * from the global namespace.
5222
 */
5223

    
5224
/*
5225
 * This is a layer violation but for now there is no way
5226
 * I can find to properly do this with PHP.
5227
 */
5228
function altq_get_default_queue($interface) {
5229
	global $altq_list_queues;
5230

    
5231
	$altq_tmp = $altq_list_queues[$interface];
5232
	if ($altq_tmp) {
5233
		return $altq_tmp->GetDefaultQueuePresent();
5234
	} else {
5235
		return false;
5236
	}
5237
}
5238

    
5239
function altq_check_default_queues() {
5240
	global $altq_list_queues;
5241

    
5242
	$count = 0;
5243
	if (is_array($altq_list_queues)) {
5244
		foreach ($altq_list_queues as $altq) {
5245
			if ($altq->GetDefaultQueuePresent()) {
5246
				$count++;
5247
			}
5248
		}
5249
	}
5250
	else {
5251
		$count++;
5252
	}
5253

    
5254
	return 0;
5255
}
5256

    
5257
function &get_unique_queue_list() {
5258
	global $altq_list_queues;
5259

    
5260
	$qlist = array();
5261
	if (is_array($altq_list_queues)) {
5262
		foreach ($altq_list_queues as $altq) {
5263
			if ($altq->GetEnabled() == "") {
5264
				continue;
5265
			}
5266
			$tmplist =& $altq->get_queue_list();
5267
			foreach ($tmplist as $qname => $link) {
5268
				if ($link->GetEnabled() <> "") {
5269
					$qlist[$qname] = $link;
5270
				}
5271
			}
5272
		}
5273
	}
5274
	return $qlist;
5275
}
5276

    
5277
function &get_unique_dnqueue_list() {
5278
	global $dummynet_pipe_list;
5279

    
5280
	$qlist = array();
5281
	if (is_array($dummynet_pipe_list)) {
5282
		foreach ($dummynet_pipe_list as $dn) {
5283
			if ($dn->GetEnabled() == "") {
5284
				continue;
5285
			}
5286
			$tmplist =& $dn->get_queue_list();
5287
			foreach ($tmplist as $qname => $link) {
5288
				$qlist[$qname] = $link;
5289
			}
5290
		}
5291
	}
5292
	return $qlist;
5293
}
5294

    
5295
function ref_on_altq_queue_list($parent, $qname) {
5296
	if (isset($GLOBALS['queue_list'][$qname])) {
5297
		$GLOBALS['queue_list'][$qname]++;
5298
	} else {
5299
		$GLOBALS['queue_list'][$qname] = 1;
5300
	}
5301

    
5302
	unref_on_altq_queue_list($parent);
5303
}
5304

    
5305
function unref_on_altq_queue_list($qname) {
5306
	$GLOBALS['queue_list'][$qname]--;
5307
	if ($GLOBALS['queue_list'][$qname] <= 1) {
5308
		unset($GLOBALS['queue_list'][$qname]);
5309
	}
5310
}
5311

    
5312
function read_altq_config() {
5313
	global $altq_list_queues, $config;
5314
	$path = array();
5315

    
5316
	init_config_arr(array('shaper', 'queue'));
5317
	$a_int = &$config['shaper']['queue'];
5318

    
5319
	$altq_list_queues = array();
5320

    
5321
	if (!is_array($config['shaper']['queue'])) {
5322
		return;
5323
	}
5324

    
5325
	foreach ($a_int as $key => $conf) {
5326
		$int = $conf['interface'];
5327
		$root = new altq_root_queue();
5328
		$root->SetInterface($int);
5329
		$altq_list_queues[$root->GetInterface()] = $root;
5330
		$root->ReadConfig($conf);
5331
		array_push($path, $key);
5332
		$root->SetLink($path);
5333
		if (is_array($conf['queue'])) {
5334
			foreach ($conf['queue'] as $key1 => $q) {
5335
				array_push($path, $key1);
5336
				/*
5337
				 * XXX: we completely ignore errors here but anyway we must have
5338
				 *	checked them before so no harm should be come from this.
5339
				 */
5340
				$root->add_queue($root->GetInterface(), $q, $path, $input_errors);
5341
				array_pop($path);
5342
			}
5343
		}
5344
		array_pop($path);
5345
	}
5346
}
5347

    
5348
function read_dummynet_config() {
5349
	global $dummynet_pipe_list, $config;
5350
	$path = array();
5351

    
5352
	init_config_arr(array('dnshaper', 'queue'));
5353
	$a_int = &$config['dnshaper']['queue'];
5354

    
5355
	$dummynet_pipe_list = array();
5356

    
5357
	if (!count($config['dnshaper']['queue'])) {
5358
		return;
5359
	}
5360

    
5361
	foreach ($a_int as $key => $conf) {
5362
		if (empty($conf['name'])) {
5363
			continue; /* XXX: grrrrrr at php */
5364
		}
5365
		$root = new dnpipe_class();
5366
		$root->ReadConfig($conf);
5367
		$dummynet_pipe_list[$root->GetQname()] = $root;
5368
		array_push($path, $key);
5369
		$root->SetLink($path);
5370
		if (is_array($conf['queue'])) {
5371
			foreach ($conf['queue'] as $key1 => $q) {
5372
				array_push($path, $key1);
5373
				/*
5374
				 * XXX: we completely ignore errors here but anyway we must have
5375
				 *	checked them before so no harm should be come from this.
5376
				 */
5377
				$root->add_queue($root->GetQname(), $q, $path, $input_errors);
5378
				array_pop($path);
5379
			}
5380
		}
5381
		array_pop($path);
5382
	}
5383
}
5384

    
5385
function get_interface_list_to_show() {
5386
	global $altq_list_queues, $config;
5387
	global $shaperIFlist;
5388

    
5389
	$tree = "";
5390
	foreach ($shaperIFlist as $shif => $shDescr) {
5391
		if ($altq_list_queues[$shif]) {
5392
			continue;
5393
		} else {
5394
			if (!is_altq_capable(get_real_interface($shif))) {
5395
				continue;
5396
			}
5397
			$tree .= " <li><a href=\"firewall_shaper.php?interface=".$shif."&amp;action=add\">".$shDescr."</a></li>";
5398
		}
5399
	}
5400

    
5401
	return $tree;
5402
}
5403

    
5404
function filter_generate_altq_queues() {
5405
	global $altq_list_queues;
5406

    
5407
	read_altq_config();
5408

    
5409
	$altq_rules = "";
5410
	foreach ($altq_list_queues as $altq) {
5411
		$altq_rules .= $altq->build_rules();
5412
	}
5413

    
5414
	return $altq_rules;
5415
}
5416

    
5417
function dnqueue_find_nextnumber() {
5418
	global $dummynet_pipe_list;
5419

    
5420
	$dnused = array();
5421
	if (is_array($dummynet_pipe_list)) {
5422
		foreach ($dummynet_pipe_list as $dn) {
5423
			$tmplist =& $dn->get_queue_list();
5424
			foreach ($tmplist as $qname => $link) {
5425
				if ($link[0] == "?") {
5426
					$dnused[$qname] = substr($link, 1);
5427
				}
5428
			}
5429
		}
5430
	}
5431

    
5432
	sort($dnused, SORT_NUMERIC);
5433
	$dnnumber = 0;
5434
	$found = false;
5435
	foreach ($dnused as $dnnum) {
5436
		if (($dnnum - $dnnumber) > 1) {
5437
			$dnnumber = $dnnum - 1;
5438
			$found = true;
5439
			break;
5440
		} else {
5441
			$dnnumber = $dnnum;
5442
		}
5443
	}
5444

    
5445
	if ($found == false) {
5446
		$dnnumber++;
5447
	}
5448

    
5449
	unset($dnused, $dnnum, $found);
5450
	return $dnnumber;
5451
}
5452

    
5453
function dnpipe_find_nextnumber() {
5454
	global $dummynet_pipe_list;
5455

    
5456
	$dnused = array();
5457
	foreach ($dummynet_pipe_list as $dn) {
5458
		$dnused[] = $dn->GetNumber();
5459
	}
5460

    
5461
	sort($dnused, SORT_NUMERIC);
5462
	$dnnumber = 0;
5463
	$found = false;
5464
	foreach ($dnused as $dnnum) {
5465
		if (($dnnum - $dnnumber) > 1) {
5466
			$dnnumber = $dnnum - 1;
5467
			$found = true;
5468
			break;
5469
		} else {
5470
			$dnnumber = $dnnum;
5471
		}
5472
	}
5473

    
5474
	if ($found == false) {
5475
		$dnnumber++;
5476
	}
5477

    
5478
	unset($dnused, $dnnum, $found);
5479
	return $dnnumber;
5480
}
5481

    
5482
function filter_generate_dummynet_rules() {
5483
	global $config, $g, $dummynet_pipe_list;
5484

    
5485
	read_dummynet_config();
5486

    
5487
	$dn_rules = "";
5488
	$max_qlimit = "100"; // OS default
5489
	foreach ($dummynet_pipe_list as $dn) {
5490
		$dn_rules .= $dn->build_rules();
5491
		$this_qlimit = $dn->GetQlimit();
5492
		if ($this_qlimit > $max_qlimit) {
5493
			$max_qlimit = $this_qlimit;
5494
		}
5495
	}
5496
	if (!is_numericint($max_qlimit)) {
5497
		$max_qlimit = "100";
5498
	}
5499
	if (!empty($dn_rules)) {
5500
		dummynet_load_module($max_qlimit);
5501
		file_put_contents("{$g['tmp_path']}/rules.limiter", $dn_rules);
5502
		mwexec("/sbin/dnctl {$g['tmp_path']}/rules.limiter");
5503
	}
5504
}
5505

    
5506
function build_iface_without_this_queue($iface, $qname) {
5507
	global $g, $altq_list_queues;
5508
	global $shaperIFlist;
5509

    
5510
	$altq =& $altq_list_queues[$iface];
5511

    
5512
	if ($altq) {
5513
		$scheduler = $altq->GetScheduler();
5514
	}
5515

    
5516
	$form = '<dl class="dl-horizontal">';
5517

    
5518
	$form .= '	<dt>';
5519
	$form .= '		<a href="firewall_shaper.php?interface=' . $iface . '&amp;queue=' . $iface . '&amp;action=show">' . $shaperIFlist[$iface] . '</a>';
5520
	$form .= '	</dt>';
5521
	$form .= '	<dd>';
5522
	$form .=		$scheduler;
5523
	$form .= '	</dd>';
5524

    
5525
	$form .= '	<dt>';
5526
	$form .= 'Clone';
5527
	$form .= '	</dt>';
5528
	$form .= '	<dd>';
5529
	$form .= '<a class="btn btn-info btn-xs" href="firewall_shaper_queues.php?interface=';
5530
	$form .= $iface . '&amp;queue=';
5531
	$form .= $qname . '&amp;action=add">';
5532
	$form .= '<i class="fa-regular fa-clone icon-embed-btn"></i>';
5533
	$form .= gettext("Clone Shaper to this Interface") . '</a>';
5534
	$form .= '	</dd>';
5535

    
5536
	$form .= '</dl>';
5537

    
5538
	return $form;
5539

    
5540
}
5541

    
5542
$default_shaper_msg = sprintf(gettext("Welcome to the %s Traffic Shaper."), g_get('product_label')) . "<br />";
5543
$dn_default_shaper_msg = $default_shaper_msg;
5544

    
5545
$shaper_msg = gettext("The tree on the left navigates through the %s.");
5546
$default_shaper_msg .= sprintf($shaper_msg, gettext("queues")) . "<br />";
5547
$dn_default_shaper_msg .= sprintf($shaper_msg, gettext("limiters")) . "<br />";
5548

    
5549
$shaper_msg = gettext("Buttons at the bottom represent %s actions and are activated accordingly.");
5550
$default_shaper_msg .= sprintf($shaper_msg, gettext("queue"));
5551
$dn_default_shaper_msg .= sprintf($shaper_msg, gettext("limiter"));
5552

    
5553
// Check to see if the specified interface has a queue configured
5554
function interface_has_queue($if) {
5555
	global $config;
5556

    
5557
	if ($config['shaper']) {
5558
		foreach (config_get_path('shaper/queue', []) as $queue) {
5559
			if ($queue['interface'] === $if) {
5560
				return true;
5561
			}
5562
		}
5563
	}
5564

    
5565
	return false;
5566
}
5567
?>
(48-48/61)