Project

General

Profile

Download (146 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
function shaper_config_get_path($mypath) {
329
	config_init_path('shaper');
330
	$path = 'shaper';
331
	foreach ($mypath as $indeks) {
332
		$path .= "/queue/{$indeks}";
333
	}
334

    
335
	return $path;
336
}
337

    
338
function shaper_config_del($mypath) {
339
	config_init_path('shaper');
340
	$path = 'shaper';
341
	for ($i = 0; $i < count($mypath) - 1; $i++) {
342
		$path .= "/queue/{$mypath[$i]}";
343
	}
344
	config_del_path("{$path}/queue/{$mypath[$i]}");
345
}
346

    
347
function shaper_dn_config_get_path($mypath) {
348
	config_init_path('dnshaper');
349
	$path = 'dnshaper';
350
	foreach ($mypath as $indeks) {
351
		$path .= "/queue/{$indeks}";
352
	}
353

    
354
	return $path;
355
}
356

    
357
function shaper_dn_config_del($mypath) {
358
	config_init_path('dnshaper');
359
	$path = 'dnshaper';
360
	for ($i = 0; $i < count($mypath) - 1; $i++) {
361
		$path .= "/queue/{$mypath[$i]}";
362
	}
363
	config_del_path("{$path}/queue/{$mypath[$i]}");
364
}
365

    
366
function clean_child_queues($type, $mypath) {
367
	$ref_path = shaper_config_get_path($mypath);
368
	$ref = config_get_path($ref_path);
369
	if (empty($ref)) {
370
		return;
371
	}
372

    
373
	switch ($type) {
374
		case 'HFSC':
375
			if (isset($ref['borrow'])) {
376
				unset($ref['borrow']);
377
			}
378
			if (isset($ref['hogs'])) {
379
				unset($ref['hogs']);
380
			}
381
			if (isset($ref['buckets'])) {
382
				unset($ref['buckets']);
383
			}
384
			break;
385
		case 'PRIQ':
386
			if (isset($ref['borrow'])) {
387
				unset($ref['borrow']);
388
			}
389
			if (isset($ref['bandwidth'])) {
390
				unset($ref['bandwidth']);
391
			}
392
			if (isset($ref['bandwidthtype'])) {
393
				unset($ref['bandwidthtype']);
394
			}
395
			/* fall through */
396
		case 'FAIRQ':
397
			if (isset($ref['borrow'])) {
398
				unset($ref['borrow']);
399
			}
400
			/* fall through */
401
		case 'CBQ':
402
			if (isset($ref['realtime'])) {
403
				unset($ref['realtime']);
404
			}
405
			if (isset($ref['realtime1'])) {
406
				unset($ref['realtime1']);
407
			}
408
			if (isset($ref['realtime2'])) {
409
				unset($ref['realtime2']);
410
			}
411
			if (isset($ref['realtime3'])) {
412
				unset($ref['realtime3']);
413
			}
414
			if (isset($ref['upperlimit'])) {
415
				unset($ref['upperlimit']);
416
			}
417
			if (isset($ref['upperlimit1'])) {
418
				unset($ref['upperlimit1']);
419
			}
420
			if (isset($ref['upperlimit2'])) {
421
				unset($ref['upperlimit2']);
422
			}
423
			if (isset($ref['upperlimit3'])) {
424
				unset($ref['upperlimit3']);
425
			}
426
			if (isset($ref['linkshare'])) {
427
				unset($ref['linkshare']);
428
			}
429
			if (isset($ref['linkshare1'])) {
430
				unset($ref['linkshare1']);
431
			}
432
			if (isset($ref['linkshare2'])) {
433
				unset($ref['linkshare2']);
434
			}
435
			if (isset($ref['linkshare3'])) {
436
				unset($ref['linkshare3']);
437
			}
438
			if (isset($ref['hogs'])) {
439
				unset($ref['hogs']);
440
			}
441
			if (isset($ref['buckets'])) {
442
				unset($ref['buckets']);
443
			}
444
			break;
445
	}
446
	config_set_path($ref_path, $ref);
447
}
448

    
449
function get_bandwidthtype_scale($type) {
450
	switch ($type) {
451
		case "Gb":
452
			$factor = 1024 * 1024 * 1024;
453
			break;
454
		case "Mb":
455
			$factor = 1024 * 1024;
456
			break;
457
		case "Kb":
458
			$factor = 1024;
459
			break;
460
		case "b":
461
		default:
462
			$factor = 1;
463
			break;
464
	}
465
	return intval($factor);
466
}
467

    
468
function get_bandwidth($bw, $scale, $obj) {
469
	$bw = (int) $bw;
470
	$pattern= "/(b|Kb|Mb|Gb|%)/";
471
	if (!preg_match($pattern, $scale, $match))
472
		return 0;
473

    
474
	switch ($match[1]) {
475
		case '%':
476
			$objbw = ($bw / 100) * get_queue_bandwidth($obj);
477
			break;
478
		default:
479
			$objbw = $bw * get_bandwidthtype_scale($scale);
480
			break;
481
	}
482

    
483
	return floatval($objbw);
484
}
485

    
486
/*
487
 * XXX - unused
488
 *
489
function get_hfsc_bandwidth($object, $bw) {
490
	$pattern= "/[0-9]+/";
491
	if (preg_match($pattern, $bw, $match)) {
492
		$bw_1 = $match[1];
493
	} else {
494
		return 0;
495
	}
496
	$pattern= "/(b|Kb|Mb|Gb|%)/";
497
	if (preg_match($pattern, $bw, $match)) {
498
		switch ($match[1]) {
499
			case '%':
500
				$bw_1 = ($bw_1 / 100) * get_interface_bandwidth($object);
501
				break;
502
			default:
503
				$bw_1 = $bw_1 * get_bandwidthtype_scale($match[0]);
504
				break;
505
		}
506
		return floatval($bw_1);
507
	} else {
508
		return 0;
509
	}
510
}
511
*/
512

    
513
function get_queue_bandwidth($obj) {
514
	$bw = (int)$obj->GetBandwidth();
515
	$scale = $obj->GetBwscale();
516

    
517
	$pattern= "/(b|Kb|Mb|Gb|%)/";
518
	if (!preg_match($pattern, $scale, $match))
519
		return 0;
520

    
521
	switch ($match[1]) {
522
		case '%':
523
			if (method_exists($obj, 'GetParent')) {
524
				$getobjbw = get_queue_bandwidth($obj->GetParent());
525
			} else {
526
				$getobjbw = $obj->bandwidth;
527
			}
528
			$objbw = ($bw / 100) * $getobjbw;
529
			break;
530
		default:
531
			$objbw = $bw * get_bandwidthtype_scale($scale);
532
			break;
533
	}
534

    
535
	return floatval($objbw);
536
}
537

    
538
function get_interface_bandwidth($object) {
539
	global $altq_list_queues;
540

    
541
	$int = $object->GetInterface();
542
	if (isset($altq_list_queues[$int])) {
543
		$altq = &$altq_list_queues[$int];
544
		$bw_3 = (int)$altq->GetBandwidth();
545
		$bw_3 = $bw_3 * get_bandwidthtype_scale($altq->GetBwscale());
546
		return floatval($bw_3);
547
	} else {
548
		return 0;
549
	}
550
}
551

    
552
function cleanup_queue_from_rules($queue) {
553
	$rulelist = config_get_path('filter/rule', []);
554
	foreach ($rulelist as & $rule) {
555
		if ($rule['defaultqueue'] == $queue) {
556
			unset($rule['defaultqueue']);
557
			if (isset($rule['ackqueue'])) {
558
				unset($rule['ackqueue']);
559
			}
560
		}
561
		if ($rule['ackqueue'] == $queue) {
562
			unset($rule['ackqueue']);
563
		}
564
	}
565
	config_set_path('filter/rule', $rulelist);
566
}
567

    
568
function cleanup_dnqueue_from_rules($queue) {
569
	$rulelist = config_get_path('filter/rule', []);
570
	foreach ($rulelist as & $rule) {
571
		if ($rule['dnpipe'] == $queue) {
572
			unset($rule['dnpipe']);
573
		}
574
		if ($rule['pdnpipe'] == $queue) {
575
			unset($rule['pdnpipe']);
576
		}
577
	}
578
	config_set_path('filter/rule', $rulelist);
579
}
580

    
581
function rename_queue_in_rules($name, $newname) {
582
	$rulelist = config_get_path('filter/rule', []);
583
	foreach ($rulelist as & $rule) {
584
		if ($rule['defaultqueue'] == $name) {
585
			$rule['defaultqueue'] = $newname;
586
		}
587
		if ($rule['ackqueue'] == $name) {
588
			$rule['ackqueue'] = $newname;
589
		}
590
	}
591
	config_set_path('filter/rule', $rulelist);
592
}
593

    
594
function rename_dnqueue_in_rules($name, $newname) {
595
	$rulelist = config_get_path('filter/rule', []);
596
	foreach ($rulelist as & $rule) {
597
		if ($rule['dnpipe'] == $name) {
598
			$rule['dnpipe'] = $newname;
599
		}
600
		if ($rule['pdnpipe'] == $name) {
601
			$rule['pdnpipe'] = $newname;
602
		}
603
	}
604
	config_set_path('filter/rule', $rulelist);
605
}
606

    
607
class altq_root_queue {
608
	var $interface;
609
	var $tbrconfig ;
610
	var $bandwidth;
611
	var $bandwidthtype; /* b, Kb, Mb, Gb, % */
612
	var $scheduler;
613
	var $qlimit;
614
	var $queues = array();
615
	var $qenabled = false;
616
	var $link;
617

    
618
	/* Accessor functions */
619
	function GetDefaultQueuePresent() {
620
		if (!empty($this->queues)) {
621
			foreach ($this->queues as $q) {
622
				if ($q->GetDefault()) {
623
					return true;
624
				}
625
			}
626
		}
627

    
628
		return false;
629
	}
630
	function SetLink($link) {
631
		$this->link = $link;
632
	}
633
	function GetLink() {
634
		return $this->link;
635
	}
636
	function GetEnabled() {
637
		return $this->qenabled;
638
	}
639
	function SetEnabled($value) {
640
		$this->qenabled = $value;
641
	}
642
	function CanHaveChildren() {
643
		if ($this->GetScheduler() == "CODELQ") {
644
			return false;
645
		} else {
646
			return true;
647
		}
648
	}
649
	function CanBeDeleted() {
650
		return false;
651
	}
652
	function GetQname() {
653
		return $this->interface;
654
	}
655
	function SetQname($name) {
656
		$this->interface = trim($name);
657
	}
658
	function GetInterface() {
659
		return $this->interface;
660
	}
661
	function SetInterface($name) {
662
		$this->interface = trim($name);
663
	}
664
	function GetTbrConfig() {
665
		return $this->tbrconfig;
666
	}
667
	function SetTbrConfig($tbrconfig) {
668
		$this->tbrconfig = $tbrconfig;
669
	}
670
	function GetBandwidth() {
671
		return $this->bandwidth;
672
	}
673
	function SetBandwidth($bw) {
674
		$this->bandwidth = $bw;
675
	}
676
	function GetBwscale() {
677
		return $this->bandwidthtype;
678
	}
679
	function FormGetBwscale() {
680
		if ($this->GetBwscale()) {
681
			$bwscale = $this->GetBwscale();
682
		} else {
683
			$bwscale = 'Mb';
684
		}
685
		return $bwscale;
686
	}
687
	function SetBwscale($bwscale) {
688
		$this->bandwidthtype = $bwscale;
689
	}
690
	function GetScheduler() {
691
		return $this->scheduler;
692
	}
693
	function SetScheduler($scheduler) {
694
		$this->scheduler = trim($scheduler);
695
	}
696
	function GetQlimit() {
697
		return $this->qlimit;
698
	}
699
	function SetQlimit($limit) {
700
		$this->qlimit = $limit;
701
	}
702

    
703
	function GetBwscaleText() {
704
		switch ($this->bandwidthtype) {
705
			case "b":
706
				$bwscaletext = "Bit/s";
707
				break;
708
			case "Kb":
709
				$bwscaletext = "Kbit/s";
710
				break;
711
			case "Mb":
712
				$bwscaletext = "Mbit/s";
713
				break;
714
			case "Gb":
715
				$bwscaletext = "Gbit/s";
716
				break;
717
			default:
718
				/* For others that do not need translating like % */
719
				$bwscaletext = $this->bandwidthtype;
720
				break;
721
		}
722
		return $bwscaletext;
723
	}
724

    
725
	function CheckBandwidth($bw, $bwtype) {
726
		$bw = (int)$bw;
727
		$sum = $this->GetTotalBw();
728
		if ($sum > ($bw * get_bandwidthtype_scale($bwtype))) {
729
			return 1;
730
		}
731
		foreach ($this->queues as $q) {
732
			if ($q->CheckBandwidth(0, '')) {
733
				return 1;
734
			}
735
		}
736

    
737
		return 0;
738
	}
739

    
740
	function GetTotalBw($qignore = NULL) {
741
		$sum = 0;
742
		foreach ($this->queues as $q) {
743
			if (($qignore != NULL) && ($qignore == $q)) {
744
				continue;
745
			}
746
			$sum += get_bandwidth($q->GetBandwidth(), $q->GetBwscale(), $this);
747
		}
748

    
749
		return $sum;
750
	}
751

    
752
	function validate_input($data, &$input_errors) {
753
		if (!isset($data['bandwidth']) || ($data['bandwidth'] == '')) {
754
			$input_errors[] = gettext("Bandwidth must be set.  This is usually the interface speed.");
755
		} else {
756
			if ((!is_numeric($data['bandwidth']))) {
757
				$input_errors[] = gettext("Bandwidth must be an integer.");
758
			}
759
			if ((int)$data['bandwidth'] < 0) {
760
				$input_errors[] = gettext("Bandwidth cannot be negative.");
761
			}
762
		}
763
		if (isset($data['bandwidthtype']) && ($data['bandwidthtype'] == "%")) {
764
			if ($data['bandwidth'] > 100 || $data['bandwidth'] < 0) {
765
				$input_errors[] = gettext("Bandwidth in percentage should be between 1 and 100.");
766
			}
767
		}
768
		if ($this->CheckBandwidth($data['bandwidth'], $data['bandwidthtype'])) {
769
			$input_errors[] = "The sum of child bandwidth is higher than parent.";
770
		}
771
		if (($data['qlimit'] != null) && ($data['scheduler'] == 'CODELQ')) {
772
			$input_errors[] = gettext("CODELQ scheduler doesn't support Qlimit parameter.");
773
		}
774
		if (($data['qlimit'] != null) && (!is_numeric($data['qlimit']))) {
775
			$input_errors[] = gettext("Qlimit must be an integer.");
776
		}
777
		if (($data['qlimit'] != null) && (int)$data['qlimit'] < 1) {
778
			$input_errors[] = gettext("Qlimit must be positive.");
779
		}
780
		if (($data['tbrconfig'] != null) && (!is_numeric($data['tbrconfig']))) {
781
			$input_errors[] = gettext("Tbrsize must be an integer.");
782
		}
783
		if (($data['tbrconfig'] != null) && (int)$data['tbrconfig'] < 1) {
784
			$input_errors[] = gettext("Tbrsize must be positive.");
785
		}
786
	}
787

    
788
	/* Implement this to shorten some code on the frontend page */
789
	function ReadConfig(&$conf) {
790
		if (isset($conf['tbrconfig'])) {
791
			$this->SetTbrConfig($conf['tbrconfig']);
792
		} else {
793
			$this->SetTbrConfig($conf['tbrconfig']);
794
		}
795
		$this->SetBandwidth($conf['bandwidth']);
796
		if ($conf['bandwidthtype'] <> "") {
797
			$this->SetBwscale($conf['bandwidthtype']);
798
		}
799
		if (isset($conf['scheduler'])) {
800
			if ($this->GetScheduler() != $conf['scheduler']) {
801
				foreach ($this->queues as $q) {
802
					clean_child_queues($conf['scheduler'], $this->GetLink());
803
					$q->clean_queue($conf['scheduler']);
804
				}
805
			}
806
			$this->SetScheduler($conf['scheduler']);
807
		}
808
		if (isset($conf['qlimit']) && $conf['qlimit'] <> "") {
809
			$this->SetQlimit($conf['qlimit']);
810
		} else {
811
			$this->SetQlimit("");
812
		}
813
		if (isset($conf['name'])) {
814
			$this->SetQname($conf['name']);
815
		}
816
		if (!empty($conf['enabled'])) {
817
			$this->SetEnabled($conf['enabled']);
818
		} else {
819
			$this->SetEnabled("");
820
		}
821
	}
822

    
823
	function copy_queue($interface, &$cflink) {
824
		$cflink['interface'] = $interface;
825
		$cflink['name'] = $interface;
826
		$cflink['scheduler'] = $this->GetScheduler();
827
		$cflink['bandwidth'] = $this->GetBandwidth();
828
		$cflink['bandwidthtype'] = $this->GetBwscale();
829
		$cflink['qlimit'] = $this->GetQlimit();
830
		$cflink['tbrconfig'] = $this->GetTbrConfig();
831
		$cflink['enabled'] = $this->GetEnabled();
832
		if (is_array($this->queues)) {
833
			$cflink['queue'] = array();
834
			foreach ($this->queues as $q) {
835
				$cflink['queue'][$q->GetQname()] = array();
836
				$q->copy_queue($interface, $cflink['queue'][$q->GetQname()]);
837
			}
838
		}
839
	}
840

    
841
	function &get_queue_list(&$q = null) {
842
		$qlist = array();
843

    
844
		//$qlist[$this->GetQname()] = & $this;
845
		if (is_array($this->queues)) {
846
			foreach ($this->queues as $queue) {
847
				$queue->get_queue_list($qlist);
848
			}
849
		}
850
		return $qlist;
851
	}
852

    
853
	function &add_queue($interface, &$queue, &$path, &$input_errors) {
854

    
855
		if (!is_array($this->queues)) {
856
			$this->queues = array();
857
		}
858

    
859
		switch ($this->GetScheduler()) {
860
			case "PRIQ":
861
				$__tmp_q = new priq_queue(); $q =& $__tmp_q;
862
				break;
863
			case "HFSC":
864
				$__tmp_q = new hfsc_queue(); $q =& $__tmp_q;
865
				break;
866
			case "CBQ":
867
				$__tmp_q = new cbq_queue(); $q =& $__tmp_q;
868
				break;
869
			case "FAIRQ":
870
				$__tmp_q = new fairq_queue(); $q =& $__tmp_q;
871
				break;
872
			default:
873
				/* XXX: but should not happen anyway */
874
				return;
875
				break;
876
		}
877
		$q->SetLink($path);
878
		$q->SetInterface($this->GetInterface());
879
		$q->SetEnabled("on");
880
		$q->SetParent($this);
881
		$q->ReadConfig($queue);
882
		$q->validate_input($queue, $input_errors);
883

    
884
		$this->queues[$q->GetQname()] = &$q;
885
		ref_on_altq_queue_list($this->GetQname(), $q->GetQname());
886
		if (is_array($queue['queue'])) {
887
			foreach ($queue['queue'] as $key1 => $que) {
888
				array_push($path, $key1);
889
				$q->add_queue($q->GetInterface(), $que, $path, $input_errors);
890
				array_pop($path);
891
			}
892
		}
893

    
894
		return $q;
895
	}
896

    
897
	/* interface here might be optional */
898
	function &find_queue($interface, $qname) {
899
		if ($qname == $this->GetQname()) {
900
			return $this;
901
		}
902
		foreach ($this->queues as $q) {
903
			$result =& $q->find_queue("", $qname);
904
			if ($result) {
905
				return $result;
906
			}
907
		}
908
	}
909

    
910
	function &find_parentqueue($interface, $qname) {
911
		if ($qname == $interface) {
912
			$result = NULL;
913
		} else if ($this->queues[$qname]) {
914
			$result = $this;
915
		} else if ($this->GetScheduler() <> "PRIQ") {
916
			foreach ($this->queues as $q) {
917
				$result = $q->find_parentqueue("", $qname);
918
				if ($result) {
919
					return $result;
920
				}
921
			}
922
		}
923
	}
924

    
925
	function build_tree() {
926
		global $shaperIFlist;
927

    
928
		$tree = " <li><a href=\"firewall_shaper.php?interface=".$this->GetInterface()."&amp;queue=". $this->GetInterface()."&amp;action=show";
929
		$tree .= "\">" . $shaperIFlist[$this->GetInterface()] . "</a>";
930
		if (is_array($this->queues)) {
931
			$tree .= "<ul>";
932
			foreach ($this->queues as $q) {
933
				$tree .= $q->build_tree();
934
			}
935
			$tree .= "</ul>";
936
		}
937
		$tree .= "</li>";
938
		return $tree;
939
	}
940

    
941
	function delete_queue() {
942
		foreach ($this->queues as $q) {
943
			$q->delete_queue();
944
		}
945
		shaper_config_del($this->GetLink());
946
	}
947

    
948
	function delete_all() {
949
		if (count($this->queues)) {
950
			foreach ($this->queues as $q) {
951
				$q->delete_all();
952
				shaper_config_del($q->GetLink());
953
				unset($q);
954
			}
955
			unset($this->queues);
956
		}
957
	}
958

    
959
	/*
960
	 * First it spits:
961
	 * altq on $interface ..............
962
	 *	then it goes like
963
	 *	foreach ($queues as $qkey => $queue) {
964
	 *		this->queues[$qkey]->build_rule();
965
	 *	}
966
	 */
967
	function build_rules(&$default = false) {
968
		if (count($this->queues) > 0 && $this->GetEnabled() == "on") {
969
			$default = false;
970
			$rules = " altq on " . get_real_interface($this->GetInterface());
971
			if ($this->GetScheduler()) {
972
				$rules .= " ".strtolower($this->GetScheduler());
973
			}
974
			if ($this->GetQlimit() > 0) {
975
				$rules .= " qlimit " . $this->GetQlimit() . " ";
976
			}
977
			if ($this->GetBandwidth()) {
978
				$rules .= " bandwidth ".trim($this->GetBandwidth());
979
				if ($this->GetBwscale()) {
980
					$rules .= $this->GetBwscale();
981
				}
982
			}
983
			if ($this->GetTbrConfig()) {
984
				$rules .= " tbrsize ".$this->GetTbrConfig();
985
			}
986
			if (count($this->queues)) {
987
				$i = count($this->queues);
988
				$rules .= " queue { ";
989
				foreach ($this->queues as $qkey => $qnone) {
990
					if ($i > 1) {
991
						$i--;
992
						$rules .= " {$qkey}, ";
993
					} else {
994
						$rules .= " {$qkey} ";
995
					}
996
				}
997
				$rules .= " } \n";
998
				foreach ($this->queues as $q) {
999
					$rules .= $q->build_rules($default);
1000
				}
1001
			}
1002

    
1003
			if ($default == false) {
1004
				$error = sprintf(gettext("SHAPER: no default queue specified for interface %s."), $this->GetInterface()) . " " . gettext("The interface queue will be enforced as default.");
1005
				file_notice("Shaper", $error, "Error occurred", "");
1006
				unset($error);
1007
				return "\n";
1008
			}
1009
			$frule .= $rules;
1010
		} else if ($this->GetEnabled() == "on" && $this->GetScheduler() == "CODELQ") {
1011
			$rules = " altq on " . get_real_interface($this->GetInterface());
1012
			if ($this->GetScheduler()) {
1013
				$rules .= " ".strtolower($this->GetScheduler());
1014
			}
1015
			if ($this->GetQlimit() > 0) {
1016
				$rules .= " ( qlimit " . $this->GetQlimit() . " ) ";
1017
			}
1018
			if ($this->GetBandwidth()) {
1019
				$rules .= " bandwidth ".trim($this->GetBandwidth());
1020
				if ($this->GetBwscale()) {
1021
					$rules .= $this->GetBwscale();
1022
				}
1023
			}
1024
			if ($this->GetTbrConfig()) {
1025
				$rules .= " tbrsize ".$this->GetTbrConfig();
1026
			}
1027

    
1028
			$rules .= " queue";
1029
		}
1030

    
1031
		$rules .= " \n";
1032
		return $rules;
1033
	}
1034

    
1035
	function build_javascript() {
1036
		$javascript = "<script type=\"text/javascript\">";
1037
		$javascript .= "//<![CDATA[\n";
1038
		$javascript .= "function mySuspend() {";
1039
		$javascript .= "if (document.layers && document.layers['shaperarea'] != null) ";
1040
		$javascript .= "document.layers['shaperarea'].visibility = 'hidden'; ";
1041
		$javascript .= "else if (document.all)";
1042
		$javascript .= "document.all['shaperarea'].style.visibility = 'hidden';";
1043
		$javascript .= "}";
1044

    
1045
		$javascript .= "function myResume() {";
1046
		$javascript .= "if (document.layers && document.layers['shaperarea'] != null) ";
1047
		$javascript .= "document.layers['shaperarea'].visibility = 'visible';";
1048
		$javascript .= "else if (document.all) ";
1049
		$javascript .= "document.all['shaperarea'].style.visibility = 'visible';";
1050
		$javascript .= "}";
1051
		$javascript .= "//]]>";
1052
		$javascript .= "</script>";
1053

    
1054
		return $javascript;
1055
	}
1056

    
1057
	function build_shortform() {
1058
		global $shaperIFlist;
1059

    
1060
		$altq =& $this;
1061

    
1062
		if ($altq) {
1063
			$scheduler = ": " . $altq->GetScheduler();
1064
		}
1065

    
1066
		$form = '<dl class="dl-horizontal">';
1067
		$form .= '	<dt>';
1068
		$form .= '		<a href="firewall_shaper.php?interface=' . $this->GetInterface() . '&amp;queue=' . $this->GetQname() . '&amp;action=show">' . $shaperIFlist[$this->GetInterface()] . '</a>';
1069
		$form .= '	</dt>';
1070
		$form .= '	<dd>';
1071
		$form .=		$scheduler;
1072
		$form .= '	</dd>';
1073

    
1074
		$form .= '	<dt>';
1075
		$form .=		'Bandwidth';
1076
		$form .= '	</dt>';
1077
		$form .= '	<dd>';
1078
		$form .=		$this->GetBandwidth() . '&nbsp;' . $this->GetBwscaleText();
1079
		$form .= '	</dd>';
1080

    
1081
		$form .= '	<dt>';
1082
		$form .= 'Disable';
1083
		$form .= '	<dt>';
1084
		$form .= '	<dd>';
1085

    
1086
		$form .= '<a class="btn btn-danger btn-xs" href="firewall_shaper_queues.php?interface=';
1087
		$form .= $this->GetInterface() . '&amp;queue=';
1088
		$form .= $this->GetQname() . '&amp;action=delete">';
1089
		$form .= '<i class="fa-solid fa-trash-can icon-embed-btn"></i>';
1090
		$form .= gettext("Remove shaper from this interface") . '</a>';
1091

    
1092
		$form .= '	</dd>';
1093

    
1094
		$form .= '</dl>';
1095

    
1096
		return $form;
1097

    
1098
	}
1099

    
1100
	/*
1101
	 * For requesting the parameters of the root queues
1102
	 * to the user like the traffic wizard does.
1103
	 */
1104
	function build_form() {
1105

    
1106
		$sform = new Form();
1107

    
1108
		$sform->setAction("firewall_shaper.php");
1109

    
1110
		$section = new Form_Section(null);
1111

    
1112
		$section->addInput(new Form_Checkbox(
1113
			'enabled',
1114
			'Enable/Disable',
1115
			'Enable/disable discipline and its children',
1116
			($this->GetEnabled() == "on"),
1117
			'on'
1118
		));
1119

    
1120
		$section->addInput(new Form_StaticText(
1121
			'*Name',
1122
			$this->GetQname()
1123
		));
1124

    
1125
		$section->addInput(new Form_Select(
1126
			'scheduler',
1127
			'Scheduler Type',
1128
			$this->GetScheduler(),
1129
			array('HFSC' => 'HFSC',
1130
				  'CBQ' => 'CBQ',
1131
				  'FAIRQ' => 'FAIRQ',
1132
				  'CODELQ' => 'CODELQ',
1133
				  'PRIQ' => 'PRIQ')
1134
		))->setHelp('Changing this changes all child queues! Beware information can be lost.');
1135

    
1136
		$group = new Form_group('Bandwidth');
1137

    
1138
		$group->add(new Form_Input(
1139
			'bandwidth',
1140
			null,
1141
			'number',
1142
			$this->GetBandwidth()
1143
		));
1144

    
1145
		$group->add(new Form_Select(
1146
			'bandwidthtype',
1147
			null,
1148
			$this->FormGetBwscale(),
1149
			array('Kb' => 'Kbit/s',
1150
				  'Mb' => 'Mbit/s',
1151
				  'Gb' => 'Gbit/s',
1152
				  'b' => 'Bit/s',
1153
				  '%' => '%')
1154
		));
1155

    
1156
		$section->add($group);
1157

    
1158
		$section->addInput(new Form_Input(
1159
			'qlimit',
1160
			'Queue Limit',
1161
			'number',
1162
			$this->GetQlimit()
1163
		));
1164

    
1165
		$section->addInput(new Form_Input(
1166
			'tbrconfig',
1167
			'TBR Size',
1168
			'number',
1169
			$this->GetTbrConfig()
1170
		))->setHelp('Adjusts the size, in bytes, of the token bucket regulator. If not specified, heuristics based on the interface ' .
1171
					'bandwidth are used to determine the size.');
1172

    
1173
		$section->addInput(new Form_Input(
1174
			'interface',
1175
			null,
1176
			'hidden',
1177
			$this->GetInterface()
1178
		));
1179

    
1180
		$section->addInput(new Form_Input(
1181
			'name',
1182
			null,
1183
			'hidden',
1184
			$this->GetQname()
1185
		));
1186

    
1187
		$sform->add($section);
1188

    
1189
		return($sform);
1190
	}
1191

    
1192
	function update_altq_queue_data(&$data) {
1193
		$this->ReadConfig($data);
1194
	}
1195

    
1196
	/*
1197
	 * Should call on each of it queues and subqueues
1198
	 * the same function much like build_rules();
1199
	 */
1200
	function wconfig() {
1201
		$cflink_path = shaper_config_get_path($this->GetLink());
1202
		$cflink = config_get_path($cflink_path, []);
1203
		$cflink['interface'] = $this->GetInterface();
1204
		$cflink['name'] = $this->GetQname();
1205
		$cflink['scheduler'] = $this->GetScheduler();
1206
		$cflink['bandwidth'] = $this->GetBandwidth();
1207
		$cflink['bandwidthtype'] = $this->GetBwscale();
1208
		$cflink['qlimit'] = trim($this->GetQlimit());
1209
		if (empty($cflink['qlimit'])) {
1210
			unset($cflink['qlimit']);
1211
		}
1212
		$cflink['tbrconfig'] = trim($this->GetTbrConfig());
1213
		if (empty($cflink['tbrconfig'])) {
1214
			unset($cflink['tbrconfig']);
1215
		}
1216
		$cflink['enabled'] = $this->GetEnabled();
1217
		if (empty($cflink['enabled'])) {
1218
			unset($cflink['enabled']);
1219
		}
1220
		config_set_path($cflink_path, $cflink);
1221
	}
1222

    
1223
}
1224

    
1225
class priq_queue {
1226
	var $qname;
1227
	var $qinterface;
1228
	var $qlimit;
1229
	var $qpriority;
1230
	var $description;
1231
	var $isparent;
1232
	var $qbandwidth;
1233
	var $qbandwidthtype;
1234
	var $qdefault = "";
1235
	var $qrio = "";
1236
	var $qred = "";
1237
	var $qcodel = "";
1238
	var $qecn = "";
1239
	var $qack;
1240
	var $qenabled = "";
1241
	var $qparent;
1242
	var $link;
1243
	var $firsttime;
1244

    
1245
	/* This is here to help with form building and building rules/lists */
1246
	var $subqueues = array();
1247

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

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

    
1327
		if ($bwtype != "%") {
1328
			$sum = $parent->GetTotalBw(($bw > 0) ? $this : NULL);
1329

    
1330
			if ($bw > 0) {
1331
				$sum += get_bandwidth($bw, $bwtype, $parent);
1332
			}
1333

    
1334
			if ($sum > get_queue_bandwidth($parent)) {
1335
				return 1;
1336
			}
1337
		}
1338

    
1339
		foreach ($this->subqueues as $q) {
1340
			if ($q->CheckBandwidth(0, '')) {
1341
				return 1;
1342
			}
1343
		}
1344

    
1345
		return 0;
1346
	}
1347
	function GetTotalBw($qignore = NULL) {
1348
		$sum = 0;
1349
		foreach ($this->subqueues as $q) {
1350
			if ($qignore != NULL && $qignore == $q)
1351
				continue;
1352
			$sum += get_bandwidth($q->GetBandwidth(), $q->GetBwscale(), $q->GetParent());
1353
		}
1354

    
1355
		return $sum;
1356
	}
1357
	function GetBwscale() {
1358
		return $this->qbandwidthtype;
1359
	}
1360
	function FormGetBwscale() {
1361
		if ($this->GetBwscale()) {
1362
			$bwscale = $this->GetBwscale();
1363
		} else {
1364
			$bwscale = 'Mb';
1365
		}
1366
		return $bwscale;
1367
	}
1368
	function SetBwscale($scale) {
1369
		$this->qbandwidthtype = $scale;
1370
	}
1371
	function GetDefaultQueuePresent() {
1372
		if ($this->GetDefault()) {
1373
			return true;
1374
		}
1375
		if (!empty($this->subqueues)) {
1376
			foreach ($this->subqueues as $q) {
1377
				if ($q->GetDefault()) {
1378
					return true;
1379
				}
1380
			}
1381
		}
1382

    
1383
		return false;
1384
	}
1385
	function GetDefault() {
1386
		return $this->qdefault;
1387
	}
1388
	function SetDefault($value = false) {
1389
		$this->qdefault = $value;
1390
	}
1391
	function GetCodel() {
1392
		return $this->qcodel;
1393
	}
1394
	function SetCodel($codel = false) {
1395
		$this->qcodel = $codel;
1396
	}
1397
	function GetRed() {
1398
		return $this->qred;
1399
	}
1400
	function SetRed($red = false) {
1401
		$this->qred = $red;
1402
	}
1403
	function GetRio() {
1404
		return $this->qrio;
1405
	}
1406
	function SetRio($rio = false) {
1407
		$this->qrio = $rio;
1408
	}
1409
	function GetEcn() {
1410
		return $this->qecn;
1411
	}
1412
	function SetEcn($ecn = false) {
1413
		$this->qecn = $ecn;
1414
	}
1415
	function GetAck() {
1416
		return $this->qack;
1417
	}
1418
	function SetAck($ack = false) {
1419
		$this->qack = $ack;
1420
	}
1421

    
1422
	function GetBwscaleText() {
1423
		switch ($this->qbandwidthtype) {
1424
			case "b":
1425
				$bwscaletext = "Bit/s";
1426
				break;
1427
			case "Kb":
1428
				$bwscaletext = "Kbit/s";
1429
				break;
1430
			case "Mb":
1431
				$bwscaletext = "Mbit/s";
1432
				break;
1433
			case "Gb":
1434
				$bwscaletext = "Gbit/s";
1435
				break;
1436
			default:
1437
				/* For others that do not need translating like % */
1438
				$bwscaletext = $this->qbandwidthtype;
1439
				break;
1440
		}
1441
		return $bwscaletext;
1442
	}
1443

    
1444
	function build_javascript() {
1445
		$javascript = "<script type=\"text/javascript\">";
1446
		$javascript .= "//<![CDATA[\n";
1447
		$javascript .= "function mySuspend() { \n";
1448
		$javascript .= "if (document.layers && document.layers['shaperarea'] != null)\n";
1449
		$javascript .= "document.layers['shaperarea'].visibility = 'hidden';\n";
1450
		$javascript .= "else if (document.all)\n";
1451
		$javascript .= "document.all['shaperarea'].style.visibility = 'hidden';\n";
1452
		$javascript .= "}\n";
1453

    
1454
		$javascript .= "function myResume() {\n";
1455
		$javascript .= "if (document.layers && document.layers['shaperarea'] != null)\n";
1456
		$javascript .= "document.layers['shaperarea'].visibility = 'visible';\n";
1457
		$javascript .= "else if (document.all)\n";
1458
		$javascript .= "document.all['shaperarea'].style.visibility = 'visible';\n";
1459
		$javascript .= "}\n";
1460
		$javascript .= "//]]>";
1461
		$javascript .= "</script>";
1462

    
1463
		return $javascript;
1464
	}
1465

    
1466
	function &add_queue($interface, &$qname, &$path, &$input_errors) { return; }
1467

    
1468
	/*
1469
	 * Currently this will not be called unless we decide to clone a whole
1470
	 * queue tree on the 'By Queues' view or support drag&drop on the tree/list
1471
	 */
1472
	function copy_queue($interface, &$cflink) {
1473

    
1474
		$cflink['name'] = $this->GetQname();
1475
		$cflink['interface'] = $interface;
1476
		$cflink['qlimit'] = $this->GetQlimit();
1477
		$cflink['priority'] = $this->GetQpriority();
1478
		$cflink['description'] = $this->GetDescription();
1479
		$cflink['enabled'] = $this->GetEnabled();
1480
		$cflink['default'] = $this->GetDefault();
1481
		$cflink['red'] = $this->GetRed();
1482
		$cflink['codel'] = $this->GetCodel();
1483
		$cflink['rio'] = $this->GetRio();
1484
		$cflink['ecn'] = $this->GetEcn();
1485

    
1486
		if (is_array($this->subqueues)) {
1487
			$cflinkp['queue'] = array();
1488
			foreach ($this->subqueues as $q) {
1489
				$cflink['queue'][$q->GetQname()] = array();
1490
				$q->copy_queue($interface, $cflink['queue'][$q->GetQname()]);
1491
			}
1492
		}
1493
	}
1494

    
1495
	function clean_queue($sched) {
1496
		clean_child_queues($sched, $this->GetLink());
1497
		if (is_array($this->subqueues)) {
1498
			foreach ($this->subqueues as $q) {
1499
				$q->clean_queue($sched);
1500
			}
1501
		}
1502
	}
1503

    
1504
	function &get_queue_list(&$qlist) {
1505

    
1506
		$qlist[$this->GetQname()] = & $this;
1507
		if (is_array($this->subqueues)) {
1508
			foreach ($this->subqueues as $queue) {
1509
				$queue->get_queue_list($qlist);
1510
			}
1511
		}
1512
	}
1513

    
1514
	function delete_queue() {
1515
		unref_on_altq_queue_list($this->GetQname());
1516
		cleanup_queue_from_rules($this->GetQname());
1517
		shaper_config_del($this->GetLink());
1518
	}
1519

    
1520
	function delete_all() {
1521
		if (count($this->subqueues)) {
1522
			foreach ($this->subqueues as $q) {
1523
				$q->delete_all();
1524
				shaper_config_del($q->GetLink());
1525
				unset($q);
1526
			}
1527
			unset($this->subqueues);
1528
		}
1529
	}
1530

    
1531
	function &find_queue($interface, $qname) {
1532
		if ($qname == $this->GetQname()) {
1533
			return $this;
1534
		}
1535
	}
1536

    
1537
	function find_parentqueue($interface, $qname) { return; }
1538

    
1539
	function validate_input($data, &$input_errors) {
1540
		global $altq_list_queues;
1541

    
1542
		$parent = $altq_list_queues[$this->GetInterface()];
1543

    
1544
		if ($data['bandwidth'] && !is_numeric($data['bandwidth'])) {
1545
			$input_errors[] = gettext("Bandwidth must be an integer.");
1546
		}
1547
		if ($data['bandwidth'] < 0) {
1548
			$input_errors[] = gettext("Bandwidth cannot be negative.");
1549
		}
1550
		if ($data['bandwidthtype'] == "%") {
1551
			if (($data['bandwidth'] > 100) || ($data['bandwidth'] < 0)) {
1552
				$input_errors[] = gettext("Bandwidth in percentage should be between 1 and 100.");
1553
			}
1554
		}
1555
		if ($this->CheckBandwidth($data['bandwidth'], $data['bandwidthtype']))
1556
			$input_errors[] = "The sum of child bandwidth is higher than parent.";
1557

    
1558
		if ($parent) {
1559
			if (($parent->GetScheduler() == 'PRIQ') && (!is_numeric($data['priority']) ||
1560
			    ($data['priority'] < 0) || ($data['priority'] > 15))) {
1561
				$input_errors[] = gettext("The priority must be an integer between 0 and 15.");
1562
			} elseif (in_array($parent->GetScheduler(), array('FAIRQ','CBQ')) && (!is_numeric($data['priority']) ||
1563
			    ($data['priority'] < 0) || ($data['priority'] > 7))) {
1564
				$input_errors[] = gettext("The priority must be an integer between 0 and 7.");
1565
			}
1566
			if (is_array($parent->queues)) {
1567
				foreach ($parent->queues as $q) {
1568
					if (($data['newname'] == $q->GetQname()) && ($data['name'] != $data['newname'])) { 
1569
						$input_errors[] = gettext("A queue with the same name already exists.");
1570
					}
1571
					if (in_array($parent->GetScheduler(), array('FAIRQ','PRIQ')) &&
1572
					    ($data['priority'] == $q->GetQpriority()) && ($data['name'] != $q->GetQname())) { 
1573
						$input_errors[] = sprintf(gettext("The priority %d is already used by queue %s."),
1574
								$data['priority'], $q->GetQname());
1575
					}
1576
				}
1577
			}
1578
		}
1579

    
1580
		if ($data['qlimit'] && (!is_numeric($data['qlimit']))) {
1581
				$input_errors[] = gettext("Queue limit must be an integer");
1582
		}
1583
		if ($data['qlimit'] < 0) {
1584
				$input_errors[] = gettext("Queue limit must be positive");
1585
		}
1586
		if (!empty($data['newname']) && !preg_match("/^[a-zA-Z0-9_-]*$/", $data['newname'])) {
1587
			$input_errors[] = gettext("Queue names must be alphanumeric and _ or - only.");
1588
		}
1589
		if (!empty($data['name']) && !preg_match("/^[a-zA-Z0-9_-]*$/", $data['name'])) {
1590
			$input_errors[] = gettext("Queue names must be alphanumeric and _ or - only.");
1591
		}
1592
		$default = $this->GetDefault();
1593
		if (!empty($data['default']) && altq_get_default_queue($data['interface']) && empty($default)) {
1594
			$input_errors[] = gettext("Only one default queue per interface is allowed.");
1595
		}
1596
	}
1597

    
1598
	function ReadConfig(&$q) {
1599
		if (!empty($q['name']) && !empty($q['newname']) && ($q['name'] != $q['newname'])) {
1600
			$this->SetQname($q['newname']);
1601
			rename_queue_in_rules($q['name'], $q['newname']);
1602
		} else if (!empty($q['newname'])) {
1603
			$this->SetQname($q['newname']);
1604
		} elseif (isset($q['name'])) {
1605
			$this->SetQname($q['name']);
1606
		}
1607
		if (isset($q['interface'])) {
1608
			$this->SetInterface($q['interface']);
1609
		}
1610
		$this->SetBandwidth($q['bandwidth']);
1611
		if (!empty($q['bandwidthtype'])) {
1612
			$this->SetBwscale($q['bandwidthtype']);
1613
		}
1614
		if (!empty($q['qlimit'])) {
1615
			$this->SetQlimit($q['qlimit']);
1616
		} else {
1617
			$this->SetQlimit(""); // Default
1618
		}
1619
		if (is_numeric($q['priority'])) {
1620
			$this->SetQPriority($q['priority']);
1621
		} else {
1622
			$this->SetQpriority("");
1623
		}
1624
		if (!empty($q['description'])) {
1625
			$this->SetDescription($q['description']);
1626
		} else {
1627
			$this->SetDescription("");
1628
		}
1629
		if (!empty($q['red'])) {
1630
			$this->SetRed($q['red']);
1631
		} else {
1632
			$this->SetRed();
1633
		}
1634
		if (!empty($q['codel'])) {
1635
			$this->SetCodel($q['codel']);
1636
		} else {
1637
			$this->SetCodel();
1638
		}
1639
		if (!empty($q['rio'])) {
1640
			$this->SetRio($q['rio']);
1641
		} else {
1642
			$this->SetRio();
1643
		}
1644
		if (!empty($q['ecn'])) {
1645
			$this->SetEcn($q['ecn']);
1646
		} else {
1647
			$this->SetEcn();
1648
		}
1649
		if (!empty($q['default'])) {
1650
			$this->SetDefault($q['default']);
1651
		} else {
1652
			$this->SetDefault();
1653
		}
1654
		if (!empty($q['enabled'])) {
1655
			$this->SetEnabled($q['enabled']);
1656
		} else {
1657
			$this->SetEnabled("");
1658
		}
1659
	}
1660

    
1661
	function build_tree() {
1662
		$tree = " <li><a href=\"firewall_shaper.php?interface=". $this->GetInterface()."&amp;queue=". htmlspecialchars($this->GetQname())."&amp;action=show";
1663
		$tree .= "\" ";
1664
		$tmpvalue = $this->GetDefault();
1665
		if (!empty($tmpvalue)) {
1666
			$tree .= " class=\"navlnk\"";
1667
		}
1668
		$tree .= " >" . htmlspecialchars($this->GetQname()) . "</a>";
1669
		/*
1670
		 * Not needed here!
1671
		 * if (is_array($queues) {
1672
		 *	  $tree .= "<ul>";
1673
		 *	  foreach ($q as $queues)
1674
		 *		  $tree .= $queues['$q->GetName()']->build_tree();
1675
		 *	  endforeach
1676
		 *	  $tree .= "</ul>";
1677
		 * }
1678
		 */
1679

    
1680
		$tree .= "</li>";
1681

    
1682
		return $tree;
1683
	}
1684

    
1685
	/* Should return something like:
1686
	 * queue $qname on $qinterface bandwidth ....
1687
	 */
1688
	function build_rules(&$default = false) {
1689
		$pfq_rule = " queue ". $this->qname;
1690
		if ($this->GetInterface()) {
1691
			$pfq_rule .= " on ".get_real_interface($this->GetInterface());
1692
		}
1693
		$tmpvalue = $this->GetQpriority();
1694
		if (is_numeric($tmpvalue)) {
1695
			$pfq_rule .= " priority ".$this->GetQpriority();
1696
		}
1697
		$tmpvalue = $this->GetQlimit();
1698
		if (!empty($tmpvalue)) {
1699
			$pfq_rule .= " qlimit " . $this->GetQlimit();
1700
		}
1701
		if ($this->GetRed() || $this->GetRio() || $this->GetEcn() || $this->GetDefault() || $this->GetCodel()) {
1702
			$pfq_rule .= " priq ( ";
1703
			$tmpvalue = $this->GetRed();
1704
			if (!empty($tmpvalue)) {
1705
				$comma = 1;
1706
				$pfq_rule .= " red ";
1707
			}
1708
			$tmpvalue = $this->GetRio();
1709
			if (!empty($tmpvalue)) {
1710
				if ($comma) {
1711
					$pfq_rule .= " ,";
1712
				}
1713
				$comma = 1;
1714
				$pfq_rule .= " rio ";
1715
			}
1716
			$tmpvalue = $this->GetEcn();
1717
			if (!empty($tmpvalue)) {
1718
				if ($comma) {
1719
					$pfq_rule .= " ,";
1720
				}
1721
				$comma = 1;
1722
				$pfq_rule .= " ecn ";
1723
			}
1724
			$tmpvalue = $this->GetCodel();
1725
			if (!empty($tmpvalue)) {
1726
				if ($comma) {
1727
					$pfq_rule .= " ,";
1728
				}
1729
				$comma = 1;
1730
				$pfq_rule .= " codel ";
1731
			}
1732
			$tmpvalue = $this->GetDefault();
1733
			if (!empty($tmpvalue)) {
1734
				if ($comma) {
1735
					$pfq_rule .= " ,";
1736
				}
1737
				$pfq_rule .= " default ";
1738
				$default = true;
1739
			}
1740
			$pfq_rule .= " ) ";
1741
		}
1742

    
1743
		$pfq_rule .= " \n";
1744

    
1745
		return $pfq_rule;
1746
	}
1747

    
1748
	/*
1749
	 * To return the html form to show to user
1750
	 * for getting the parameters.
1751
	 * Should do even for first time when the
1752
	 * object is created and later when we may
1753
	 * need to update it. (2)
1754
	 */
1755

    
1756
	function build_form() {
1757

    
1758
		$sform = new Form();
1759

    
1760
		$sform->setAction("firewall_shaper.php");
1761

    
1762
		$section = new Form_Section("");
1763

    
1764
		$section->addInput(new Form_Checkbox(
1765
			'enabled',
1766
			'Enable/Disable',
1767
			'Enable/disable discipline and its children',
1768
			($this->GetEnabled() == "on"),
1769
			'on'
1770
		));
1771

    
1772
		$section->addInput(new Form_Input(
1773
			'newname',
1774
			'*Name',
1775
			'text',
1776
			$this->GetQname()
1777
		))->setHelp('Enter the name of the queue here. Do not use spaces and limit the size to 15 characters.');
1778

    
1779
		$section->addInput(new Form_Input(
1780
			'name',
1781
			null,
1782
			'hidden',
1783
			$this->GetQname()
1784
		));
1785

    
1786
		if (!is_a($this, "hfsc_queue")) {
1787
			$section->addInput(new Form_Input(
1788
				'priority',
1789
				'Priority',
1790
				'number',
1791
				$this->GetQpriority(),
1792
				['min' => '0', 'max'=> '']
1793
			))->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.');
1794
		}
1795

    
1796
		$section->addInput(new Form_Input(
1797
			'qlimit',
1798
			'Queue Limit',
1799
			'number',
1800
			$this->GetQlimit()
1801
		))->setHelp('Queue limit in packets.');
1802

    
1803
		$group = new Form_Group('Scheduler options');
1804

    
1805
		if (empty($this->subqueues)) {
1806
			$group->add(new Form_Checkbox(
1807
				'default',
1808
				null,
1809
				null,
1810
				$this->GetDefault(),
1811
				'default'
1812
			))->setHelp('Default Queue');
1813
		}
1814

    
1815
		$group->add(new Form_Checkbox(
1816
			'red',
1817
			null,
1818
			null,
1819
			!empty($this->GetRed())
1820
		))->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>');
1821

    
1822
		$group->add(new Form_Checkbox(
1823
			'rio',
1824
			null,
1825
			null,
1826
			!empty($this->GetRio())
1827
		))->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>');
1828

    
1829
		$group->add(new Form_Checkbox(
1830
			'ecn',
1831
			null,
1832
			null,
1833
			!empty($this->GetEcn())
1834
		))->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>');
1835

    
1836
		$group->add(new Form_Checkbox(
1837
			'codel',
1838
			null,
1839
			null,
1840
			!empty($this->GetCodel())
1841
		))->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>');
1842

    
1843
		$group->setHelp('Select options for this queue');
1844

    
1845
		$section->add($group);
1846

    
1847
		$section->addInput(new Form_Input(
1848
			'description',
1849
			'Description',
1850
			'text',
1851
			$this->GetDescription()
1852
		));
1853

    
1854
		$sform->add($section);
1855

    
1856
		$sform->addGlobal(new Form_Input(
1857
			'interface',
1858
			null,
1859
			'hidden',
1860
			$this->GetInterface()
1861
		));
1862

    
1863
		$sform->addGlobal(new Form_Input(
1864
			'name',
1865
			null,
1866
			'hidden',
1867
			$this->GetQname()
1868
		));
1869

    
1870
		return($sform);
1871
	}
1872

    
1873
	function build_shortform() {
1874
		/* XXX: Hacks in sight. Mostly layer violations!  */
1875
		global $g, $altq_list_queues;
1876
		global $shaperIFlist;
1877

    
1878
		$altq =& $altq_list_queues[$this->GetInterface()];
1879

    
1880
		if ($altq) {
1881
			$scheduler = $altq->GetScheduler();
1882
		}
1883

    
1884
		$form = '<dl class="dl-horizontal">';
1885
		$form .= '	<dt>';
1886
		$form .= '		<a href="firewall_shaper.php?interface=' . $this->GetInterface() . '&amp;queue=' . $this->GetQname() . '&amp;action=show">' . $shaperIFlist[$this->GetInterface()] . '</a>';
1887
		$form .= '	</dt>';
1888
		$form .= '	<dd>';
1889
		$form .=		$scheduler;
1890
		$form .= '	</dd>';
1891

    
1892
		$form .= '	<dt>';
1893
		$form .=		'Bandwidth';
1894
		$form .= '	</dt>';
1895
		$form .= '	<dd>';
1896
		$form .=		$this->GetBandwidth() . '&nbsp;' . $this->GetBwscaleText();
1897
		$form .= '	</dd>';
1898

    
1899
		$tmpvalue = $this->GetQpriority();
1900
		if (!empty($tmpvalue)) {
1901
			$form .= '	<dt>';
1902
			$form .=		'Priority';
1903
			$form .= '	<dt>';
1904
			$form .= '	<dd>';
1905
			$form .=		'On';
1906
			$form .= '	</dd>';
1907
		}
1908

    
1909
		$tmpvalue = $this->GetDefault();
1910
		if (!empty($tmpvalue)) {
1911
			$form .= '	<dt>';
1912
			$form .=		'Default';
1913
			$form .= '	<dt>';
1914
			$form .= '	<dd>';
1915
			$form .=		'On';
1916
			$form .= '	</dd>';
1917
		}
1918

    
1919
			$form .= '	<dt>';
1920
			$form .= 'Delete';
1921
			$form .= '	<dt>';
1922
			$form .= '	<dd>';
1923

    
1924
			$form .= '<a class="btn btn-danger btn-xs" href="firewall_shaper_queues.php?interface=';
1925
			$form .= $this->GetInterface() . '&amp;queue=';
1926
			$form .= $this->GetQname() . '&amp;action=delete">';
1927
			$form .= '<i class="fa-solid fa-trash-can icon-embed-btn"></i>';
1928
			$form .= gettext("Delete Queue from this Interface") . '</a>';
1929

    
1930
			$form .= '	</dd>';
1931

    
1932
			$form .= '</dl>';
1933

    
1934
		return $form;
1935

    
1936
	}
1937

    
1938
	function update_altq_queue_data(&$q) {
1939
		$this->ReadConfig($q);
1940
	}
1941

    
1942
	function wconfig() {
1943
		$cflink_path = shaper_config_get_path($this->GetLink());
1944
		$cflink = config_get_path($cflink_path, []);
1945
		$cflink['name'] = $this->GetQname();
1946
		$cflink['interface'] = $this->GetInterface();
1947
		$cflink['qlimit'] = trim($this->GetQlimit());
1948
		if (empty($cflink['qlimit'])) {
1949
			unset($cflink['qlimit']);
1950
		}
1951
		$cflink['priority'] = trim($this->GetQpriority());
1952
		if (!is_numeric($cflink['priority'])) {
1953
			unset($cflink['priority']);
1954
		}
1955
		$cflink['description'] = trim($this->GetDescription());
1956
		if (empty($cflink['description'])) {
1957
			unset($cflink['description']);
1958
		}
1959
		$cflink['enabled'] = trim($this->GetEnabled());
1960
		if (empty($cflink['enabled'])) {
1961
			unset($cflink['enabled']);
1962
		}
1963
		$cflink['default'] = trim($this->GetDefault());
1964
		if (empty($cflink['default'])) {
1965
			unset($cflink['default']);
1966
		}
1967
		$cflink['red'] = trim($this->GetRed());
1968
		if (empty($cflink['red'])) {
1969
			unset($cflink['red']);
1970
		}
1971
		$cflink['codel'] = trim($this->GetCodel());
1972
		if (empty($cflink['codel'])) {
1973
			unset($cflink['codel']);
1974
		}
1975
		$cflink['rio'] = trim($this->GetRio());
1976
		if (empty($cflink['rio'])) {
1977
			unset($cflink['rio']);
1978
		}
1979
		$cflink['ecn'] = trim($this->GetEcn());
1980
		if (empty($cflink['ecn'])) {
1981
			unset($cflink['ecn']);
1982
		}
1983
		config_set_path($cflink_path, $cflink);
1984
	}
1985
}
1986

    
1987
class hfsc_queue extends priq_queue {
1988
	/* realtime */
1989
	var $realtime;
1990
	var $r_m1;
1991
	var $r_d;
1992
	var $r_m2;
1993
	/* linkshare */
1994
	var $linkshare;
1995
	var $l_m1;
1996
	var $l_d;
1997
	var $l_m2;
1998
	/* upperlimit */
1999
	var $upperlimit;
2000
	var $u_m1;
2001
	var $u_d;
2002
	var $u_m2;
2003

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

    
2092
	function &add_queue($interface, &$qname, &$path, &$input_errors) {
2093

    
2094
		if (!is_array($this->subqueues)) {
2095
			$this->subqueues = array();
2096
		}
2097
		$__tmp_q = new hfsc_queue(); $q =& $__tmp_q;
2098
		$q->SetInterface($this->GetInterface());
2099
		$q->SetParent($this);
2100
		$q->ReadConfig($qname);
2101
		$q->validate_input($qname, $input_errors);
2102

    
2103
		$q->SetEnabled("on");
2104
		$q->SetLink($path);
2105

    
2106
		$this->subqueues[$q->GetQname()] =& $q; //new hfsc_queue()
2107
		ref_on_altq_queue_list($this->GetQname(), $q->GetQname());
2108
		if (is_array($qname['queue'])) {
2109
			foreach ($qname['queue'] as $key1 => $que) {
2110
				array_push($path, $key1);
2111
				$q->add_queue($q->GetInterface(), $que, $path, $input_errors);
2112
				array_pop($path);
2113
			}
2114
		}
2115

    
2116
		return $q;
2117
	}
2118

    
2119
	function copy_queue($interface, &$cflink) {
2120

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

    
2212
		if (is_array($this->subqueues)) {
2213
			$cflinkp['queue'] = array();
2214
			foreach ($this->subqueues as $q) {
2215
				$cflink['queue'][$q->GetQname()] = array();
2216
				$q->copy_queue($interface, $cflink['queue'][$q->GetQname()]);
2217
			}
2218
		}
2219
	}
2220

    
2221
	function delete_queue() {
2222
		unref_on_altq_queue_list($this->GetQname());
2223
		cleanup_queue_from_rules($this->GetQname());
2224
		$parent =& $this->GetParent();
2225
		foreach ($this->subqueues as $q) {
2226
			$q->delete_queue();
2227
		}
2228
		shaper_config_del($this->GetLink());
2229
	}
2230

    
2231
	/*
2232
	 * Should search even its children
2233
	 */
2234
	function &find_queue($interface, $qname) {
2235
		if ($qname == $this->GetQname()) {
2236
			return $this;
2237
		}
2238

    
2239
		foreach ($this->subqueues as $q) {
2240
			$result =& $q->find_queue("", $qname);
2241
			if ($result) {
2242
				return $result;
2243
			}
2244
		}
2245
	}
2246

    
2247
	function &find_parentqueue($interface, $qname) {
2248
		if ($this->subqueues[$qname]) {
2249
			return $this;
2250
		}
2251
		foreach ($this->subqueues as $q) {
2252
			$result = $q->find_parentqueue("", $qname);
2253
			if ($result) {
2254
				return $result;
2255
			}
2256
		}
2257
	}
2258

    
2259
	function validate_input($data, &$input_errors) {
2260
		parent::validate_input($data, $input_errors);
2261

    
2262
		if (isset($data['linkshare3']) && !empty($data['linkshare3'])) {
2263
			if (isset($data['bandwidth'])) {
2264
				if (!is_numeric($data['bandwidth'])) {
2265
					$input_errors[] = gettext("Bandwidth must be an integer.");
2266
				}
2267
				if ((int)$data['bandwidth'] < 0) {
2268
					$input_errors[] = gettext("Bandwidth cannot be negative.");
2269
				}
2270
			}
2271

    
2272
			if (isset($data['bandwidthtype']) && ($data['bandwidthtype'] == "%")) {
2273
				if (($data['bandwidth'] > 100) || ($data['bandwidth'] < 0)) {
2274
					$input_errors[] = gettext("Bandwidth in percentage should be between 1 and 100.");
2275
				}
2276
			}
2277
		}
2278

    
2279
		if (!empty($data['upperlimit1']) && empty($data['upperlimit2'])) {
2280
			$input_errors[] = gettext("upperlimit service curve defined but missing (d) value");
2281
		}
2282
		if (!empty($data['upperlimit2']) && empty($data['upperlimit1'])) {
2283
			$input_errors[] = gettext("upperlimit service curve defined but missing initial bandwidth (m1) value");
2284
		}
2285
		if (!empty($data['upperlimit1']) && !is_valid_shaperbw($data['upperlimit1'])) {
2286
			$input_errors[] = gettext("upperlimit m1 value needs to be Kb, Mb, Gb, or %");
2287
		}
2288
		if (!empty($data['upperlimit2']) && !is_numeric($data['upperlimit2'])) {
2289
			$input_errors[] = gettext("upperlimit d value needs to be numeric");
2290
		}
2291
		if (!empty($data['upperlimit3']) && !is_valid_shaperbw($data['upperlimit3'])) {
2292
			$input_errors[] = gettext("upperlimit m2 value needs to be Kb, Mb, Gb, or %");
2293
		}
2294

    
2295
		/*
2296
		if (isset($data['upperlimit']) && $data['upperlimit3'] <> "" && $data['upperlimit1'] <> "") {
2297
			$bw_1 = get_hfsc_bandwidth($this, $data['upperlimit1']);
2298
			$bw_2 = get_hfsc_bandwidth($this, $data['upperlimit3']);
2299
			if (floatval($bw_1) < floatval($bw_2)) {
2300
				$input_errors[] = ("upperlimit m1 cannot be smaller than m2");
2301
			}
2302

    
2303
			if (get_interface_bandwidth($this) < (0.8 * (floatval($bw_1) + floatval($bw_2)))) {
2304
				$input_errors[] = ("upperlimit specification exceeds 80% of allowable allocation.");
2305
			}
2306
		}
2307
		*/
2308
		if ($data['linkshare1'] <> "" && $data['linkshare2'] == "") {
2309
			$input_errors[] = gettext("linkshare service curve defined but missing (d) value");
2310
		}
2311
		if ($data['linkshare2'] <> "" && $data['linkshare1'] == "") {
2312
			$input_errors[] = gettext("linkshare service curve defined but missing initial bandwidth (m1) value");
2313
		}
2314
		if ($data['linkshare1'] <> "" && !is_valid_shaperbw($data['linkshare1'])) {
2315
			$input_errors[] = gettext("linkshare m1 value needs to be Kb, Mb, Gb, or %");
2316
		}
2317
		if ($data['linkshare2'] <> "" && !is_numeric($data['linkshare2'])) {
2318
			$input_errors[] = gettext("linkshare d value needs to be numeric");
2319
		}
2320
		if ($data['linkshare3'] <> "" && !is_valid_shaperbw($data['linkshare3'])) {
2321
			$input_errors[] = gettext("linkshare m2 value needs to be Kb, Mb, Gb, or %");
2322
		}
2323
		if ($data['realtime1'] <> "" && $data['realtime2'] == "") {
2324
			$input_errors[] = gettext("realtime service curve defined but missing (d) value");
2325
		}
2326
		if ($data['realtime2'] <> "" && $data['realtime1'] == "") {
2327
			$input_errors[] = gettext("realtime service curve defined but missing initial bandwidth (m1) value");
2328
		}
2329

    
2330
		/*
2331
		if (isset($data['linkshare']) && $data['linkshare3'] <> "" && $data['linkshare1'] <> "" && 0) {
2332
			$bw_1 = get_hfsc_bandwidth($this, $data['linkshare1']);
2333
			$bw_2 = get_hfsc_bandwidth($this, $data['linkshare3']);
2334
			if (floatval($bw_1) < floatval($bw_2)) {
2335
				$input_errors[] = ("linkshare m1 cannot be smaller than m2");
2336
			}
2337

    
2338
			if (get_interface_bandwidth($this) < (0.8 * (floatval($bw_1) + floatval($bw_2)))) {
2339
				$input_errors[] = ("linkshare specification exceeds 80% of allowable allocation.");
2340
			}
2341
		}
2342
		*/
2343

    
2344
		if ($data['realtime1'] <> "" && !is_valid_shaperbw($data['realtime1'])) {
2345
			$input_errors[] = gettext("realtime m1 value needs to be Kb, Mb, Gb, or %");
2346
		}
2347
		if ($data['realtime2'] <> "" && !is_numeric($data['realtime2'])) {
2348
			$input_errors[] = gettext("realtime d value needs to be numeric");
2349
		}
2350
		if ($data['realtime3'] <> "" && !is_valid_shaperbw($data['realtime3'])) {
2351
			$input_errors[] = gettext("realtime m2 value needs to be Kb, Mb, Gb, or %");
2352
		}
2353

    
2354
		/*
2355
		if (isset($data['realtime']) && $data['realtime3'] <> "" && $data['realtime1'] <> "" && 0) {
2356
			$bw_1 = get_hfsc_bandwidth($this, $data['realtime1']);
2357
			$bw_2 = get_hfsc_bandwidth($this, $data['realtime3']);
2358
			if (floatval($bw_1) < floatval($bw_2)) {
2359
				$input_errors[] = ("realtime m1 cannot be smaller than m2");
2360
			}
2361

    
2362
			if (get_interface_bandwidth($this) < (0.8 * (floatval($bw_1) + floatval($bw_2)))) {
2363
				$input_errors[] = ("realtime specification exceeds 80% of allowable allocation.");
2364
			}
2365
		}
2366
		*/
2367
	}
2368

    
2369
	function ReadConfig(&$cflink) {
2370
		if (!empty($cflink['linkshare'])) {
2371
			if (!empty($cflink['linkshare1'])) {
2372
				$this->SetL_m1($cflink['linkshare1']);
2373
				$this->SetL_d($cflink['linkshare2']);
2374
				$this->SetLinkshare();
2375
			} else {
2376
				$this->SetL_m1("");
2377
				$this->SetL_d("");
2378
				$this->DisableLinkshare();
2379
			}
2380
			if (!empty($cflink['linkshare3'])) {
2381
				$this->SetL_m2($cflink['linkshare3']);
2382
				$this->SetLinkshare();
2383
			}
2384
		} else {
2385
			$this->DisableLinkshare();
2386
		}
2387
		if (!empty($cflink['realtime'])) {
2388
			if (!empty($cflink['realtime1'])) {
2389
				$this->SetR_m1($cflink['realtime1']);
2390
				$this->SetR_d($cflink['realtime2']);
2391
				$this->SetRealtime();
2392
			} else {
2393
				$this->SetR_m1("");
2394
				$this->SetR_d("");
2395
				$this->DisableRealtime();
2396
			}
2397
			if (!empty($cflink['realtime3'])) {
2398
				$this->SetR_m2($cflink['realtime3']);
2399
				$this->SetRealtime();
2400
			}
2401
		} else {
2402
			$this->DisableRealtime();
2403
		}
2404
		if (!empty($cflink['upperlimit'])) {
2405
			if (!empty($cflink['upperlimit1'])) {
2406
				$this->SetU_m1($cflink['upperlimit1']);
2407
				$this->SetU_d($cflink['upperlimit2']);
2408
				$this->SetUpperlimit();
2409
			} else {
2410
				$this->SetU_m1("");
2411
				$this->SetU_d("");
2412
				$this->DisableUpperlimit();
2413
			}
2414
			if (!empty($cflink['upperlimit3'])) {
2415
				$this->SetU_m2($cflink['upperlimit3']);
2416
				$this->SetUpperlimit();
2417
			}
2418
		} else {
2419
			$this->DisableUpperlimit();
2420
		}
2421
		parent::ReadConfig($cflink);
2422
	}
2423

    
2424
	function build_tree() {
2425
		$tree = " <li><a href=\"firewall_shaper.php?interface=" . $this->GetInterface() ."&amp;queue=" . htmlspecialchars($this->GetQname())."&amp;action=show";
2426
		$tree .= "\" ";
2427
		$tmpvalue = $this->GetDefault();
2428
		if (!empty($tmpvalue)) {
2429
			$tree .= " class=\"navlnk\"";
2430
		}
2431
		$tree .= " >" . htmlspecialchars($this->GetQname()) . "</a>";
2432
		if (is_array($this->subqueues)) {
2433
			$tree .= "<ul>";
2434
			foreach ($this->subqueues as $q) {
2435
				$tree .= $q->build_tree();
2436
			}
2437
			$tree .= "</ul>";
2438
		}
2439
		$tree .= "</li>";
2440
		return $tree;
2441
	}
2442

    
2443
	/* Even this should take children into consideration */
2444
	function build_rules(&$default = false) {
2445

    
2446
		$pfq_rule = " queue ". $this->qname;
2447
		if ($this->GetInterface()) {
2448
			$pfq_rule .= " on ".get_real_interface($this->GetInterface());
2449
		}
2450
		if ($this->GetBandwidth() && $this->GetBwscale()) {
2451
			$pfq_rule .= " bandwidth ".trim($this->GetBandwidth()).$this->GetBwscale();
2452
		}
2453

    
2454
		$tmpvalue = $this->GetQlimit();
2455
		if (!empty($tmpvalue)) {
2456
			$pfq_rule .= " qlimit " . $this->GetQlimit();
2457
		}
2458
		if ($this->GetDefault() || $this->GetRed() || $this->GetRio() || $this->GetEcn() || $this->GetCodel() || $this->GetRealtime() <> "" || $this->GetLinkshare() <> "" || $this->GetUpperlimit() <> "") {
2459
			$pfq_rule .= " hfsc ( ";
2460
			$tmpvalue = $this->GetRed();
2461
			if (!empty($tmpvalue)) {
2462
				$comma = 1;
2463
				$pfq_rule .= " red ";
2464
			}
2465

    
2466
			$tmpvalue = $this->GetRio();
2467
			if (!empty($tmpvalue)) {
2468
				if ($comma) {
2469
					$pfq_rule .= " ,";
2470
				}
2471
				$comma = 1;
2472
				$pfq_rule .= " rio ";
2473
			}
2474
			$tmpvalue = $this->GetEcn();
2475
			if (!empty($tmpvalue)) {
2476
				if ($comma) {
2477
					$pfq_rule .= " ,";
2478
				}
2479
				$comma = 1;
2480
				$pfq_rule .= " ecn ";
2481
			}
2482
			$tmpvalue = $this->GetCodel();
2483
			if (!empty($tmpvalue)) {
2484
				if ($comma) {
2485
					$pfq_rule .= " ,";
2486
				}
2487
				$comma = 1;
2488
				$pfq_rule .= " codel ";
2489
			}
2490
			$tmpvalue = $this->GetDefault();
2491
			if (!empty($tmpvalue)) {
2492
				if ($comma) {
2493
					$pfq_rule .= " ,";
2494
				}
2495
				$comma = 1;
2496
				$pfq_rule .= " default ";
2497
				$default = true;
2498
			}
2499

    
2500
			if ($this->GetRealtime() <> "") {
2501
				if ($comma) {
2502
					$pfq_rule .= " , ";
2503
				}
2504
				if ($this->GetR_m1() <> "" && $this->GetR_d() <> "" && $this->GetR_m2() <> "") {
2505
					$pfq_rule .= " realtime (".$this->GetR_m1() . ", " . $this->GetR_d().", ". $this->GetR_m2() .") ";
2506
				} else if ($this->GetR_m2() <> "") {
2507
					$pfq_rule .= " realtime " . $this->GetR_m2();
2508
				}
2509
				$comma = 1;
2510
			}
2511
			if ($this->GetLinkshare() <> "") {
2512
				if ($comma) {
2513
					$pfq_rule .= " ,";
2514
				}
2515
				if ($this->GetL_m1() <> "" && $this->GetL_d() <> "" && $this->GetL_m2() <> "") {
2516
					$pfq_rule .= " linkshare (".$this->GetL_m1(). ", ". $this->GetL_d(). ", ". $this->GetL_m2(). ") ";
2517
				} else if ($this->GetL_m2() <> "") {
2518
					$pfq_rule .= " linkshare " . $this->GetL_m2() . " ";
2519
				}
2520
				$comma = 1;
2521
			}
2522
			if ($this->GetUpperlimit() <> "") {
2523
				if ($comma) {
2524
					$pfq_rule .= " ,";
2525
				}
2526
				if ($this->GetU_m1() <> "" && $this->GetU_d() <> "" && $this->GetU_m2() <> "") {
2527
							$pfq_rule .= " upperlimit (".$this->GetU_m1().", ". $this->GetU_d().", ". $this->GetU_m2(). ") ";
2528
				} else if ($this->GetU_m2() <> "") {
2529
					$pfq_rule .= " upperlimit " . $this->GetU_m2() . " ";
2530
				}
2531
			}
2532
			$pfq_rule .= " ) ";
2533
		}
2534
		if (count($this->subqueues)) {
2535
			$i = count($this->subqueues);
2536
			$pfq_rule .= " { ";
2537
			foreach ($this->subqueues as $qkey => $qnone) {
2538
				if ($i > 1) {
2539
					$i--;
2540
					$pfq_rule .= " {$qkey}, ";
2541
				} else {
2542
					$pfq_rule .= " {$qkey} ";
2543
				}
2544
			}
2545
			$pfq_rule .= " } \n";
2546
			foreach ($this->subqueues as $q) {
2547
				$pfq_rule .= $q->build_rules($default);
2548
			}
2549
		}
2550

    
2551
		$pfq_rule .= " \n";
2552

    
2553
		return $pfq_rule;
2554
	}
2555

    
2556
	function build_javascript() {
2557

    
2558
		$javascript = <<<EOJS
2559
<script type="text/javascript">
2560
//<![CDATA[
2561
	events.push(function(){
2562

    
2563
		// Disables the specified input element
2564
		function disableInput(id, disable) {
2565
			$('#' + id).prop("disabled", disable);
2566
		}
2567

    
2568
		// Upperlimit
2569
		function enable_upperlimit() {
2570
			disableInput('upperlimit1', !$('#upperlimit').prop('checked'));
2571
			disableInput('upperlimit2', !$('#upperlimit').prop('checked'));
2572
			disableInput('upperlimit3', !$('#upperlimit').prop('checked'));
2573
		}
2574

    
2575
		$('#upperlimit').click(function () {
2576
			enable_upperlimit();
2577
		});
2578

    
2579
		enable_upperlimit();
2580

    
2581
		// realtime
2582
		function enable_realtime() {
2583
			disableInput('realtime1', !$('#realtime').prop('checked'));
2584
			disableInput('realtime2', !$('#realtime').prop('checked'));
2585
			disableInput('realtime3', !$('#realtime').prop('checked'));
2586
		}
2587

    
2588
		$('#realtime').click(function () {
2589
			enable_realtime();
2590
		});
2591

    
2592
		enable_realtime();
2593

    
2594
		// linkshare
2595
		function enable_linkshare() {
2596
			disableInput('linkshare1', !$('#linkshare').prop('checked'));
2597
			disableInput('linkshare2', !$('#linkshare').prop('checked'));
2598
			disableInput('linkshare3', !$('#linkshare').prop('checked'));
2599
		}
2600

    
2601
		$('#linkshare').click(function () {
2602
			enable_linkshare();
2603
		});
2604

    
2605
		enable_linkshare();
2606
	});
2607
//]]>
2608
</script>
2609
EOJS;
2610

    
2611
		return $javascript;
2612
	}
2613

    
2614
	function build_form() {
2615

    
2616
		$sform = parent::build_form();
2617

    
2618
		$section = new Form_Section('Service Curve (sc)');
2619

    
2620
		$group = new Form_Group('Bandwidth');
2621

    
2622
		$group->add(new Form_Input(
2623
			'bandwidth',
2624
			null,
2625
			'number',
2626
			$this->GetBandwidth(),
2627
			['step' => 'any', 'min' => '0.000']
2628
		));
2629

    
2630
		$group->add(new Form_Select(
2631
			'bandwidthtype',
2632
			null,
2633
			$this->FormGetBwscale(),
2634
			array('Kb' => 'Kbit/s',
2635
				  'Mb' => 'Mbit/s',
2636
				  'Gb' => 'Gbit/s',
2637
				  'b' => 'Bit/s',
2638
				  '%' => '%')
2639
		));
2640

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

    
2643
		$section->add($group);
2644

    
2645
		$group = new Form_Group('Max bandwidth for queue.');
2646

    
2647
		$group->add(new Form_Checkbox(
2648
			'upperlimit',
2649
			null,
2650
			'Upper Limit',
2651
			($this->GetUpperlimit()<> "")
2652
		));
2653

    
2654
		$group->add(new Form_Input(
2655
			'upperlimit1',
2656
			null,
2657
			'text',
2658
			$this->GetU_m1()
2659
		))->setHelp('m1');
2660

    
2661
		$group->add(new Form_Input(
2662
			'upperlimit2',
2663
			null,
2664
			'text',
2665
			$this->GetU_d()
2666
		))->setHelp('d');
2667

    
2668
		$group->add(new Form_Input(
2669
			'upperlimit3',
2670
			null,
2671
			'text',
2672
			$this->GetU_m2()
2673
		))->setHelp('m2');
2674

    
2675

    
2676
		$section->add($group);
2677

    
2678
		$group = new Form_Group('Min bandwidth for queue.');
2679

    
2680
		$group->add(new Form_Checkbox(
2681
			'realtime',
2682
			null,
2683
			'Real Time',
2684
			($this->GetRealtime()<> "")
2685
		));
2686

    
2687
		$group->add(new Form_Input(
2688
			'realtime1',
2689
			null,
2690
			'text',
2691
			$this->GetR_m1()
2692
		))->setHelp('m1');
2693

    
2694
		$group->add(new Form_Input(
2695
			'realtime2',
2696
			null,
2697
			'text',
2698
			$this->GetR_d()
2699
		))->setHelp('d');
2700

    
2701
		$group->add(new Form_Input(
2702
			'realtime3',
2703
			null,
2704
			'text',
2705
			$this->GetR_m2()
2706
		))->setHelp('m2');
2707

    
2708
		$section->add($group);
2709

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

    
2712
		$group->add(new Form_Checkbox(
2713
			'linkshare',
2714
			null,
2715
			'Link Share',
2716
			($this->GetLinkshare()<> "")
2717
		));
2718

    
2719
		$group->add(new Form_Input(
2720
			'linkshare1',
2721
			null,
2722
			'text',
2723
			$this->GetL_m1()
2724
		))->setHelp('m1');
2725

    
2726
		$group->add(new Form_Input(
2727
			'linkshare2',
2728
			null,
2729
			'text',
2730
			$this->GetL_d()
2731
		))->setHelp('d');
2732

    
2733
		$group->add(new Form_Input(
2734
			'linkshare3',
2735
			null,
2736
			'text',
2737
			$this->GetL_m2()
2738
		))->setHelp('m2');
2739

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

    
2746
		$section->add($group);
2747

    
2748
		$sform->add($section);
2749

    
2750
		return($sform);
2751
	}
2752

    
2753
	function update_altq_queue_data(&$data) {
2754
		$this->ReadConfig($data);
2755
	}
2756

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

    
2873
class cbq_queue extends priq_queue {
2874
	var $qborrow = "";
2875

    
2876
	function GetBorrow() {
2877
		return $this->qborrow;
2878
	}
2879
	function SetBorrow($borrow) {
2880
		$this->qborrow = $borrow;
2881
	}
2882
	function CanHaveChildren() {
2883
		return true;
2884
	}
2885

    
2886
	function &add_queue($interface, &$qname, &$path, &$input_errors) {
2887

    
2888
		if (!is_array($this->subqueues)) {
2889
			$this->subqueues = array();
2890
		}
2891
		$__tmp_q = new cbq_queue(); $q =& $__tmp_q;
2892
		$q->SetInterface($this->GetInterface());
2893
		$q->SetParent($this);
2894
		$q->ReadConfig($qname);
2895
		$q->validate_input($qname, $input_errors);
2896

    
2897
		$q->SetEnabled("on");
2898
		$q->SetLink($path);
2899
		$this->subqueues[$q->GetQName()] = &$q;
2900
		ref_on_altq_queue_list($this->GetQname(), $q->GetQname());
2901
		if (is_array($qname['queue'])) {
2902
			foreach ($qname['queue'] as $key1 => $que) {
2903
				array_push($path, $key1);
2904
				$q->add_queue($q->GetInterface(), $que, $path, $input_errors);
2905
				array_pop($path);
2906
			}
2907
		}
2908

    
2909
		return $q;
2910
	}
2911

    
2912
	function copy_queue($interface, &$cflink) {
2913

    
2914
		$cflink['interface'] = $interface;
2915
		$cflink['qlimit'] = trim($this->GetQlimit());
2916
		if (empty($clink['qlimit'])) {
2917
			unset($cflink['qlimit']);
2918
		}
2919
		$cflink['priority'] = trim($this->GetQpriority());
2920
		if (!is_numeric($cflink['priority'])) {
2921
			unset($cflink['priority']);
2922
		}
2923
		$cflink['name'] = $this->GetQname();
2924
		$cflink['description'] = trim($this->GetDescription());
2925
		if (empty($cflink['description'])) {
2926
			unset($cflink['description']);
2927
		}
2928
		$cflink['bandwidth'] = $this->GetBandwidth();
2929
		$cflink['bandwidthtype'] = $this->GetBwscale();
2930
		$cflink['enabled'] = trim($this->GetEnabled());
2931
		if (empty($cflink['enabled'])) {
2932
			unset($cflink['enabled']);
2933
		}
2934
		$cflink['default'] = trim($this->GetDefault());
2935
		if (empty($cflink['default'])) {
2936
			unset($cflink['default']);
2937
		}
2938
		$cflink['red'] = trim($this->GetRed());
2939
		if (empty($cflink['red'])) {
2940
			unset($cflink['red']);
2941
		}
2942
		$cflink['rio'] = trim($this->GetRio());
2943
		if (empty($cflink['rio'])) {
2944
			unset($cflink['rio']);
2945
		}
2946
		$cflink['ecn'] = trim($this->GetEcn());
2947
		if (empty($cflink['ecn'])) {
2948
			unset($cflink['ecn']);
2949
		}
2950
		$cflink['borrow'] = trim($this->GetBorrow());
2951
		if (empty($cflink['borrow'])) {
2952
			unset($cflink['borrow']);
2953
		}
2954
		if (is_array($this->subqueues)) {
2955
			$cflinkp['queue'] = array();
2956
			foreach ($this->subqueues as $q) {
2957
				$cflink['queue'][$q->GetQname()] = array();
2958
				$q->copy_queue($interface, $cflink['queue'][$q->GetQname()]);
2959
			}
2960
		}
2961
	}
2962

    
2963
	/*
2964
	 * Should search even its children
2965
	 */
2966
	function &find_queue($interface, $qname) {
2967
		if ($qname == $this->GetQname()) {
2968
			return $this;
2969
		}
2970
		foreach ($this->subqueues as $q) {
2971
			$result =& $q->find_queue("", $qname);
2972
			if ($result) {
2973
				return $result;
2974
			}
2975
		}
2976
	}
2977

    
2978
	function &find_parentqueue($interface, $qname) {
2979
		if ($this->subqueues[$qname]) {
2980
			return $this;
2981
		}
2982
		foreach ($this->subqueues as $q) {
2983
			$result = $q->find_parentqueue("", $qname);
2984
			if ($result) {
2985
				return $result;
2986
			}
2987
		}
2988
	}
2989

    
2990
	function delete_queue() {
2991
		unref_on_altq_queue_list($this->GetQname());
2992
		cleanup_queue_from_rules($this->GetQname());
2993
		foreach ($this->subqueues as $q) {
2994
			$q->delete_queue();
2995
		}
2996
		shaper_config_del($this->GetLink());
2997
	}
2998

    
2999
	function validate_input($data, &$input_errors) {
3000
		parent::validate_input($data, $input_errors);
3001

    
3002
		if ($data['priority'] > 7) {
3003
			$input_errors[] = gettext("Priority must be an integer between 0 and 7.");
3004
		}
3005

    
3006
		$parent = $this->GetParent();
3007
		if (method_exists($parent, 'GetParent') && ($parent->GetBorrow() != "on") &&
3008
		    ($data['borrow'] == 'yes')) {
3009
			$input_errors[] = gettext("You cannot set 'Borrow' if the parent queue also does not borrow.");
3010
		}
3011
	}
3012

    
3013
	function ReadConfig(&$q) {
3014
		parent::ReadConfig($q);
3015
		if (!empty($q['borrow'])) {
3016
			$this->SetBorrow("on");
3017
		} else {
3018
			$this->SetBorrow("");
3019
		}
3020
	}
3021

    
3022
	function build_javascript() {
3023
		return parent::build_javascript();
3024
	}
3025

    
3026
	function build_tree() {
3027
		$tree = " <li><a href=\"firewall_shaper.php?interface=" . $this->GetInterface()."&amp;queue=" . htmlspecialchars($this->GetQname())."&amp;action=show";
3028
		$tree .= "\" ";
3029
		$tmpvalue = trim($this->GetDefault());
3030
		if (!empty($tmpvalue)) {
3031
			$tree .= " class=\"navlnk\"";
3032
		}
3033
		$tree .= " >" . htmlspecialchars($this->GetQname()) . "</a>";
3034
		if (is_array($this->subqueues)) {
3035
			$tree .= "<ul>";
3036
			foreach ($this->subqueues as $q) {
3037
				$tree .= $q->build_tree();
3038
			}
3039
			$tree .= "</ul>";
3040
		}
3041
		$tree .= "</li>";
3042
		return $tree;
3043
	}
3044

    
3045
	/* Even this should take children into consideration */
3046
	function build_rules(&$default = false) {
3047
		$pfq_rule = "queue ". $this->qname;
3048
		if ($this->GetInterface()) {
3049
			$pfq_rule .= " on ".get_real_interface($this->GetInterface());
3050
		}
3051
		if ($this->GetBandwidth() && $this->GetBwscale()) {
3052
			$pfq_rule .= " bandwidth ".trim($this->GetBandwidth()).$this->GetBwscale();
3053
		}
3054
		$tmpvalue = $this->GetQpriority();
3055
		if (is_numeric($tmpvalue)) {
3056
			$pfq_rule .= " priority " . $this->GetQpriority();
3057
		}
3058
		$tmpvalue = trim($this->GetQlimit());
3059
		if (!empty($tmpvalue)) {
3060
			$pfq_rule .= " qlimit " . $this->GetQlimit();
3061
		}
3062
		if ($this->GetDefault() || $this->GetRed() || $this->GetRio() || $this->GetEcn() || $this->GetBorrow() || $this->GetCodel()) {
3063
			$pfq_rule .= " cbq ( ";
3064
			$tmpvalue = trim($this->GetRed());
3065
			if (!empty($tmpvalue)) {
3066
				$comma = 1;
3067
				$pfq_rule .= " red ";
3068
			}
3069
			$tmpvalue = trim($this->GetCodel());
3070
			if (!empty($tmpvalue)) {
3071
				$comma = 1;
3072
				$pfq_rule .= " codel ";
3073
			}
3074
			$tmpvalue = trim($this->GetRio());
3075
			if (!empty($tmpvalue)) {
3076
				if ($comma) {
3077
					$pfq_rule .= " ,";
3078
				}
3079
				$comma = 1;
3080
				$pfq_rule .= " rio ";
3081
			}
3082
			$tmpvalue = trim($this->GetEcn());
3083
			if (!empty($tmpvalue)) {
3084
				if ($comma) {
3085
					$pfq_rule .= " ,";
3086
				}
3087
				$comma = 1;
3088
				$pfq_rule .= " ecn ";
3089
			}
3090
			$tmpvalue = trim($this->GetDefault());
3091
			if (!empty($tmpvalue)) {
3092
				if ($comma) {
3093
					$pfq_rule .= " ,";
3094
				}
3095
				$comma = 1;
3096
				$pfq_rule .= " default ";
3097
				$default = true;
3098
			}
3099
			$tmpvalue = trim($this->GetBorrow());
3100
			if (!empty($tmpvalue)) {
3101
				if ($comma) {
3102
					$pfq_rule .= ", ";
3103
				}
3104
				$pfq_rule .= " borrow ";
3105
			}
3106
			$pfq_rule .= " ) ";
3107
		}
3108
		if (count($this->subqueues)) {
3109
			$i = count($this->subqueues);
3110
			$pfq_rule .= " { ";
3111
			foreach ($this->subqueues as $qkey => $qnone) {
3112
				if ($i > 1) {
3113
					$i--;
3114
					$pfq_rule .= " {$qkey}, ";
3115
				} else {
3116
					$pfq_rule .= " {$qkey} ";
3117
				}
3118
			}
3119
			$pfq_rule .= " } \n";
3120
			foreach ($this->subqueues as $q) {
3121
				$pfq_rule .= $q->build_rules($default);
3122
			}
3123
		}
3124

    
3125
		$pfq_rule .= " \n";
3126
		return $pfq_rule;
3127
	}
3128

    
3129
	function build_form() {
3130
		$sform = parent::build_form();
3131

    
3132
		$section = new Form_Section('NOTITLE');
3133

    
3134
		$group = new Form_Group('Bandwidth');
3135

    
3136
		$group->add(new Form_Input(
3137
			'bandwidth',
3138
			null,
3139
			'number',
3140
			$this->GetBandwidth()
3141
		));
3142

    
3143
		$group->add(new Form_Select(
3144
			'bandwidthtype',
3145
			null,
3146
			$this->FormGetBwscale(),
3147
			array('Kb' => 'Kbit/s',
3148
				  'Mb' => 'Mbit/s',
3149
				  'Gb' => 'Gbit/s',
3150
				  'b' => 'Bit/s',
3151
				  '%' => '%')
3152
		));
3153

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

    
3156
		$section->add($group);
3157

    
3158
		$section->addInput(new Form_Checkbox(
3159
			'borrow',
3160
			'Scheduler option',
3161
			'Borrow from other queues when available',
3162
			($this->GetBorrow() == "on")
3163
		));
3164

    
3165
		$sform->add($section);
3166

    
3167
		return $sform;
3168
	}
3169

    
3170
	function update_altq_queue_data(&$data) {
3171
		$this->ReadConfig($data);
3172
	}
3173

    
3174
	function wconfig() {
3175
		$cflink_path = shaper_config_get_path($this->GetLink());
3176
		$cflink = config_get_path($cflink_path, []);
3177
		$cflink['interface'] = $this->GetInterface();
3178
		$cflink['qlimit'] = trim($this->GetQlimit());
3179
		if (empty($cflink['qlimit'])) {
3180
			unset($cflink['qlimit']);
3181
		}
3182
		$cflink['priority'] = $this->GetQpriority();
3183
		if (!is_numeric($cflink['priority'])) {
3184
			unset($cflink['priority']);
3185
		}
3186
		$cflink['name'] = $this->GetQname();
3187
		$cflink['description'] = $this->GetDescription();
3188
		if (empty($cflink['description'])) {
3189
			unset($cflink['description']);
3190
		}
3191
		$cflink['bandwidth'] = $this->GetBandwidth();
3192
		$cflink['bandwidthtype'] = $this->GetBwscale();
3193
		$cflink['enabled'] = trim($this->GetEnabled());
3194
		if (empty($cflink['enabled'])) {
3195
			unset($cflink['enabled']);
3196
		}
3197
		$cflink['default'] = trim($this->GetDefault());
3198
		if (empty($cflink['default'])) {
3199
			unset($cflink['default']);
3200
		}
3201
		$cflink['red'] = trim($this->GetRed());
3202
		if (empty($cflink['red'])) {
3203
			unset($cflink['red']);
3204
		}
3205
		$cflink['rio'] = trim($this->GetRio());
3206
		if (empty($cflink['rio'])) {
3207
			unset($cflink['rio']);
3208
		}
3209
		$cflink['ecn'] = trim($this->GetEcn());
3210
		if (empty($cflink['ecn'])) {
3211
			unset($cflink['ecn']);
3212
		}
3213
		$cflink['codel'] = trim($this->GetCodel());
3214
		if (empty($cflink['codel'])) {
3215
			unset($cflink['codel']);
3216
		}
3217
		$cflink['borrow'] = trim($this->GetBorrow());
3218
		if (empty($cflink['borrow'])) {
3219
			unset($cflink['borrow']);
3220
		}
3221
		config_set_path($cflink_path, $cflink);
3222
	}
3223
}
3224

    
3225
class fairq_queue extends priq_queue {
3226
	var $hogs;
3227
	var $buckets;
3228

    
3229
	function GetBuckets() {
3230
		return $this->buckets;
3231
	}
3232
	function SetBuckets($buckets) {
3233
		$this->buckets = $buckets;
3234
	}
3235
	function GetHogs() {
3236
		return $this->hogs;
3237
	}
3238
	function SetHogs($hogs) {
3239
		$this->hogs = $hogs;
3240
	}
3241
	function CanHaveChildren() {
3242
		return false;
3243
	}
3244

    
3245

    
3246
	function copy_queue($interface, &$cflink) {
3247
		$cflink['interface'] = $interface;
3248
		$cflink['qlimit'] = $this->GetQlimit();
3249
		$cflink['priority'] = $this->GetQpriority();
3250
		$cflink['name'] = $this->GetQname();
3251
		$cflink['description'] = $this->GetDescription();
3252
		$cflink['bandwidth'] = $this->GetBandwidth();
3253
		$cflink['bandwidthtype'] = $this->GetBwscale();
3254
		$cflink['enabled'] = $this->GetEnabled();
3255
		$cflink['default'] = $this->GetDefault();
3256
		$cflink['red'] = $this->GetRed();
3257
		$cflink['rio'] = $this->GetRio();
3258
		$cflink['ecn'] = $this->GetEcn();
3259
		$cflink['buckets'] = $this->GetBuckets();
3260
		$cflink['hogs'] = $this->GetHogs();
3261
	}
3262

    
3263
	/*
3264
	 * Should search even its children
3265
	 */
3266
	function &find_queue($interface, $qname) {
3267
		if ($qname == $this->GetQname()) {
3268
			return $this;
3269
		}
3270
	}
3271

    
3272
	function find_parentqueue($interface, $qname) { return; }
3273

    
3274
	function delete_queue() {
3275
		unref_on_altq_queue_list($this->GetQname());
3276
		cleanup_queue_from_rules($this->GetQname());
3277
		shaper_config_del($this->GetLink());
3278
	}
3279

    
3280
	function validate_input($data, &$input_errors) {
3281
		parent::validate_input($data, $input_errors);
3282

    
3283
		if ($data['priority'] > 7) {
3284
				$input_errors[] = gettext("Priority must be an integer between 0 and 7.");
3285
		}
3286
	}
3287

    
3288
	function ReadConfig(&$q) {
3289
		parent::ReadConfig($q);
3290
		if (!empty($q['buckets'])) {
3291
			$this->SetBuckets($q['buckets']);
3292
		} else {
3293
			$this->SetBuckets("");
3294
		}
3295
		if (!empty($q['hogs']) && is_valid_shaperbw($q['hogs'])) {
3296
			$this->SetHogs($q['hogs']);
3297
		} else {
3298
			$this->SetHogs("");
3299
		}
3300
	}
3301

    
3302
	function build_javascript() {
3303
		return parent::build_javascript();
3304
	}
3305

    
3306
	function build_tree() {
3307
		$tree = " <li><a href=\"firewall_shaper.php?interface=" .
3308
		$this->GetInterface()."&amp;queue=" . htmlspecialchars($this->GetQname())."&amp;action=show";
3309
		$tree .= "\" ";
3310
		$tmpvalue = trim($this->GetDefault());
3311
		if (!empty($tmpvalue)) {
3312
			$tree .= " class=\"navlnk\"";
3313
		}
3314
		$tree .= " >" . htmlspecialchars($this->GetQname()) . "</a>";
3315
		$tree .= "</li>";
3316
		return $tree;
3317
	}
3318

    
3319
	/* Even this should take children into consideration */
3320
	function build_rules(&$default = false) {
3321
		$pfq_rule = "queue ". $this->qname;
3322
		if ($this->GetInterface()) {
3323
			$pfq_rule .= " on ".get_real_interface($this->GetInterface());
3324
		}
3325
		if ($this->GetBandwidth() && $this->GetBwscale()) {
3326
			$pfq_rule .= " bandwidth ".trim($this->GetBandwidth()).$this->GetBwscale();
3327
		}
3328
		$tmpvalue = trim($this->GetQpriority());
3329
		if (is_numeric($tmpvalue)) {
3330
			$pfq_rule .= " priority " . $this->GetQpriority();
3331
		}
3332
		$tmpvalue = trim($this->GetQlimit());
3333
		if (!empty($tmpvalue)) {
3334
			$pfq_rule .= " qlimit " . $this->GetQlimit();
3335
		}
3336
		if ($this->GetDefault() || $this->GetRed() || $this->GetRio() ||
3337
		    $this->GetEcn() || $this->GetBuckets() || $this->GetHogs() || $this->GetCodel()) {
3338
			$pfq_rule .= " fairq ( ";
3339
			$tmpvalue = trim($this->GetRed());
3340
			if (!empty($tmpvalue)) {
3341
				$comma = 1;
3342
				$pfq_rule .= " red ";
3343
			}
3344
			$tmpvalue = trim($this->GetCodel());
3345
			if (!empty($tmpvalue)) {
3346
				$comma = 1;
3347
				$pfq_rule .= " codel ";
3348
			}
3349
			$tmpvalue = trim($this->GetRio());
3350
			if (!empty($tmpvalue)) {
3351
				if ($comma) {
3352
					$pfq_rule .= " ,";
3353
				}
3354
				$comma = 1;
3355
				$pfq_rule .= " rio ";
3356
			}
3357
			$tmpvalue = trim($this->GetEcn());
3358
			if (!empty($tmpvalue)) {
3359
				if ($comma) {
3360
					$pfq_rule .= " ,";
3361
				}
3362
				$comma = 1;
3363
				$pfq_rule .= " ecn ";
3364
			}
3365
			$tmpvalue = trim($this->GetDefault());
3366
			if (!empty($tmpvalue)) {
3367
				if ($comma) {
3368
					$pfq_rule .= " ,";
3369
				}
3370
				$comma = 1;
3371
				$pfq_rule .= " default ";
3372
				$default = true;
3373
			}
3374
			$tmpvalue = trim($this->GetBuckets());
3375
			if (!empty($tmpvalue)) {
3376
				if ($comma) {
3377
					$pfq_rule .= ", ";
3378
				}
3379
				$pfq_rule .= " buckets " . $this->GetBuckets() . " ";
3380
			}
3381
			$tmpvalue = trim($this->GetHogs());
3382
			if (!empty($tmpvalue)) {
3383
				if ($comma) {
3384
					$pfq_rule .= ", ";
3385
				}
3386
				$pfq_rule .= " hogs " . $this->GetHogs() . " ";
3387
			}
3388
			$pfq_rule .= " ) ";
3389
		}
3390

    
3391
		$pfq_rule .= " \n";
3392
		return $pfq_rule;
3393
	}
3394

    
3395
	function build_form() {
3396
		$form = parent::build_form();
3397

    
3398
		$section = new Form_Section('');
3399

    
3400
		$group = new Form_Group('Bandwidth');
3401

    
3402
		$group->add(new Form_Input(
3403
			'bandwidth',
3404
			null,
3405
			'number',
3406
			$this->GetBandwidth()
3407
		));
3408

    
3409
		$group->add(new Form_Select(
3410
			'bandwidthtype',
3411
			null,
3412
			$this->FormGetBwscale(),
3413
			array('Kb' => 'Kbit/s',
3414
				  'Mb' => 'Mbit/s',
3415
				  'Gb' => 'Gbit/s',
3416
				  'b' => 'Bit/s',
3417
				  '%' => '%')
3418
		));
3419

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

    
3422
		$section->add($group);
3423

    
3424
		$section->addInput(new Form_Input(
3425
			'buckets',
3426
			'Scheduler specific options',
3427
			'text',
3428
			$this->GetBuckets()
3429
		))->setHelp('Number of buckets available');
3430

    
3431
		$section->addInput(new Form_Input(
3432
			'hogs',
3433
			'',
3434
			'text',
3435
			$this->GetHogs()
3436
			))->setHelp('Bandwidth limit for hosts to not saturate link');
3437

    
3438
		$form->add($section);
3439
		return $form;
3440
	}
3441

    
3442
	function update_altq_queue_data(&$data) {
3443
		$this->ReadConfig($data);
3444
	}
3445

    
3446
	function wconfig() {
3447
		$cflink_path = shaper_config_get_path($this->GetLink());
3448
		$cflink = config_get_path($cflink_path, []);
3449
		$cflink['interface'] = $this->GetInterface();
3450
		$cflink['qlimit'] = trim($this->GetQlimit());
3451
		if (empty($cflink['qlimit'])) {
3452
			unset($cflink['qlimit']);
3453
		}
3454
		$cflink['priority'] = trim($this->GetQpriority());
3455
		if (!is_numeric($cflink['priority'])) {
3456
			unset($cflink['priority']);
3457
		}
3458
		$cflink['name'] = $this->GetQname();
3459
		$cflink['description'] = trim($this->GetDescription());
3460
		if (empty($cflink['description'])) {
3461
			unset($cflink['description']);
3462
		}
3463
		$cflink['bandwidth'] = $this->GetBandwidth();
3464
		$cflink['bandwidthtype'] = $this->GetBwscale();
3465
		$cflink['enabled'] = $this->GetEnabled();
3466
		if (empty($cflink['enabled'])) {
3467
			unset($cflink['enabled']);
3468
		}
3469
		$cflink['default'] = trim($this->GetDefault());
3470
		if (empty($cflink['default'])) {
3471
			unset($cflink['default']);
3472
		}
3473
		$cflink['red'] = trim($this->GetRed());
3474
		if (empty($cflink['red'])) {
3475
			unset($cflink['red']);
3476
		}
3477
		$cflink['rio'] = trim($this->GetRio());
3478
		if (empty($cflink['rio'])) {
3479
			unset($cflink['rio']);
3480
		}
3481
		$cflink['ecn'] = trim($this->GetEcn());
3482
		if (empty($cflink['ecn'])) {
3483
			unset($cflink['ecn']);
3484
		}
3485
		$cflink['codel'] = trim($this->GetCodel());
3486
		if (empty($cflink['codel'])) {
3487
			unset($cflink['codel']);
3488
		}
3489
		$cflink['buckets'] = trim($this->GetBuckets());
3490
		if (empty($cflink['buckets'])) {
3491
			unset($cflink['buckets']);
3492
		}
3493
		$cflink['hogs'] = trim($this->GetHogs());
3494
		if (empty($cflink['hogs'])) {
3495
			unset($cflink['hogs']);
3496
		}
3497
		config_set_path($cflink_path, $cflink);
3498
	}
3499
}
3500

    
3501

    
3502
/*
3503
 * dummynet(4) wrappers.
3504
 */
3505

    
3506

    
3507
/*
3508
 * List of respective objects!
3509
 */
3510
$dummynet_pipe_list = array();
3511

    
3512
class dummynet_class {
3513
	var $qname;
3514
	var $qnumber; /* dummynet(4) uses numbers instead of names; maybe integrate with pf the same as altq does?! */
3515
	var $qlimit;
3516
	var $description;
3517
	var $qenabled;
3518
	var $link;
3519
	var $qparent; /* link to upper class so we do things easily on WF2Q+ rule creation */
3520
	var $plr;
3521

    
3522
	var $buckets;
3523
	/* mask parameters */
3524
	var $mask;
3525
	var $noerror;
3526
	var $firsttime;
3527

    
3528
	/* Accessor functions */
3529
	function SetLink($link) {
3530
		$this->link = $link;
3531
	}
3532
	function GetLink() {
3533
		return $this->link;
3534
	}
3535
	function GetMask() {
3536
		if (!isset($this->mask["type"])) {
3537
			$this->mask["type"] = "none";
3538
		}
3539
		return $this->mask;
3540
	}
3541
	function SetMask($mask) {
3542
		$this->mask = $mask;
3543
	}
3544
	function &GetParent() {
3545
		return $this->qparent;
3546
	}
3547
	function SetParent(&$parent) {
3548
		$this->qparent = &$parent;
3549
	}
3550
	function GetEnabled() {
3551
		return $this->qenabled;
3552
	}
3553
	function SetEnabled($value) {
3554
		$this->qenabled = $value;
3555
	}
3556
	function CanHaveChildren() {
3557
		return false;
3558
	}
3559
	function CanBeDeleted() {
3560
		return true;
3561
	}
3562
	function GetQname() {
3563
		return $this->qname;
3564
	}
3565
	function SetQname($name) {
3566
		$this->qname = trim($name);
3567
	}
3568
	function GetQlimit() {
3569
		return $this->qlimit;
3570
	}
3571
	function SetQlimit($limit) {
3572
		$this->qlimit = $limit;
3573
	}
3574
	function GetDescription() {
3575
		return $this->description;
3576
	}
3577
	function SetDescription($str) {
3578
		$this->description = trim($str);
3579
	}
3580
	function GetFirstime() {
3581
		return $this->firsttime;
3582
	}
3583
	function SetFirsttime($number) {
3584
		$this->firsttime = $number;
3585
	}
3586
	function GetBuckets() {
3587
		return $this->buckets;
3588
	}
3589
	function SetBuckets($buckets) {
3590
		$this->buckets = $buckets;
3591
	}
3592
	function SetNumber($number) {
3593
		$this->qnumber = $number;
3594
	}
3595
	function GetNumber() {
3596
		return $this->qnumber;
3597
	}
3598
	function GetPlr() {
3599
		return $this->plr;
3600
	}
3601
	function SetPlr($plr) {
3602
		$this->plr = $plr;
3603
	}
3604

    
3605
	function build_javascript() {
3606
		$javascript .= "<script type=\"text/javascript\">\n";
3607
		$javascript .= "//<![CDATA[\n";
3608
		$javascript .= "function enable_maskbits(enable_over) {\n";
3609
		$javascript .= "var e = document.getElementById(\"mask\");\n";
3610
		$javascript .= "if ((e.options[e.selectedIndex].text == \"none\") || enable_over) {\n";
3611
		$javascript .= "document.iform.maskbits.disabled = 1;\n";
3612
		$javascript .= "document.iform.maskbits.value = \"\";\n";
3613
		$javascript .= "document.iform.maskbitsv6.disabled = 1;\n";
3614
		$javascript .= "document.iform.maskbitsv6.value = \"\";\n";
3615
		$javascript .= "} else {\n";
3616
		$javascript .= "document.iform.maskbits.disabled = 0;\n";
3617
		$javascript .= "document.iform.maskbitsv6.disabled = 0;\n";
3618
		$javascript .= "}}\n";
3619
		$javascript .= "//]]>\n";
3620
		$javascript .= "</script>\n";
3621
		return $javascript;
3622
	}
3623

    
3624
	function validate_input($data, &$input_errors) {
3625
		if ($data['plr'] && (!is_numeric($data['plr']) ||
3626
		    ($data['plr'] < 0) || ($data['plr'] > 1))) {
3627
			$input_errors[] = gettext("Packet Loss Rate must be a value between 0 and 1.");
3628
		}
3629
		if ($data['buckets'] && (!is_numeric($data['buckets']) ||
3630
		    ($data['buckets'] < 16) || ($data['buckets'] > 65535))) {
3631
			$input_errors[] = gettext("Buckets must be an integer between 16 and 65535.");
3632
		}
3633
		if ($data['qlimit'] && (!is_numeric($data['qlimit']))) {
3634
			$input_errors[] = gettext("Queue limit must be an integer");
3635
		}
3636
		if (!empty($data['newname']) && !preg_match("/^[a-zA-Z0-9_-]+$/", $data['newname'])) {
3637
			$input_errors[] = gettext("Queue names must be alphanumeric and _ or - only.");
3638
		}
3639
		if (!empty($data['name']) && !preg_match("/^[a-zA-Z0-9_-]+$/", $data['name'])) {
3640
			$input_errors[] = gettext("Queue names must be alphanumeric and _ or - only.");
3641
		}
3642
		if (isset($data['maskbits']) && ($data['maskbits'] <> "")) {
3643
			if ((!is_numeric($data['maskbits'])) || ($data['maskbits'] <= 0) || ($data['maskbits'] > 32)) {
3644
				$input_errors[] = gettext("IPv4 bit mask must be blank or numeric value between 1 and 32.");
3645
			}
3646
		}
3647
		if (isset($data['maskbitsv6']) && ($data['maskbitsv6'] <> "")) {
3648
			if ((!is_numeric($data['maskbitsv6'])) || ($data['maskbitsv6'] <= 0) || ($data['maskbitsv6'] > 128)) {
3649
				$input_errors[] = gettext("IPv6 bit mask must be blank or numeric value between 1 and 128.");
3650
			}
3651
		}
3652
	}
3653

    
3654
	function build_mask_rules(&$pfq_rule) {
3655
		$mask = $this->GetMask();
3656
		if (!empty($mask['type'])) {
3657
			if ($mask['type'] <> 'none') {
3658
				$pfq_rule .= " mask";
3659
			}
3660
			switch ($mask['type']) {
3661
				case 'srcaddress':
3662
					if (!empty($mask['bitsv6']) && ($mask['bitsv6'] <> "")) {
3663
						$pfq_rule .= " src-ip6 /" . $mask['bitsv6'];
3664
					} else {
3665
						$pfq_rule .= " src-ip6 /128";
3666
					}
3667
					if (!empty($mask['bits']) && ($mask['bits'] <> "")) {
3668
						$pfq_rule .= sprintf(" src-ip 0x%x", gen_subnet_mask_long($mask['bits']));
3669
					} else {
3670
						$pfq_rule .= " src-ip 0xffffffff";
3671
					}
3672
					break;
3673
				case 'dstaddress':
3674
					if (!empty($mask['bitsv6']) && ($mask['bitsv6'] <> "")) {
3675
						$pfq_rule .= " dst-ip6 /" . $mask['bitsv6'];
3676
					} else {
3677
						$pfq_rule .= " dst-ip6 /128";
3678
					}
3679
					if (!empty($mask['bits']) && ($mask['bits'] <> "")) {
3680
						$pfq_rule .= sprintf(" dst-ip 0x%x", gen_subnet_mask_long($mask['bits']));
3681
					} else {
3682
						$pfq_rule .= " dst-ip 0xffffffff";
3683
					}
3684
					break;
3685
				default:
3686
					break;
3687
			}
3688
		}
3689
	}
3690

    
3691
}
3692

    
3693
class dnpipe_class extends dummynet_class {
3694
	var $delay;
3695
	var $qbandwidth = array();
3696
	var $qbandwidthtype;
3697
	var $qburst;
3698

    
3699
	/* Limiter queue patch */
3700
	var $ecn; // ecn 'on' or 'off'
3701
	var $pie_onoff;
3702
	var $pie_capdrop;
3703
	var $pie_qdelay;
3704
	var $pie_pderand;
3705
	var $aqm; // key to aqm_map
3706
	var $aqm_params = array(); // AQM params
3707
	var $scheduler;	// key to scheduler_map
3708
	var $scheduler_params = array(); // AQM params
3709
	function GetAQM() {
3710
			return $this->aqm;
3711
	}
3712
	function SetAQM($aqm) {
3713
			$this->aqm = $aqm;
3714
	}
3715
	function GetAQMParameters() {
3716
			return $this->aqm_params;
3717
	}
3718
	function GetAQMParameter($parameter) {
3719
			return $this->aqm_params[$parameter];
3720
	}
3721
	function SetAQMParameter($key, $value) {
3722
			return $this->aqm_params[$key] = $value;
3723
	}
3724
	function SetAQMParameters($params) {
3725
			$this->aqm_params = $params;
3726
	}
3727
	function GetECN() {
3728
			return $this->ecn;
3729
	}
3730
	function SetECN($ecn) {
3731
			$this->ecn = $ecn;
3732
	}
3733
	function GetPIE_ONOFF() {
3734
			return $this->pie_onoff;
3735
	}
3736
	function SetPIE_ONOFF($pie_onoff) {
3737
			$this->pie_onoff = $pie_onoff;
3738
	}
3739
	function GetPIE_CAPDROP() {
3740
			return $this->pie_capdrop;
3741
	}
3742
	function SetPIE_CAPDROP($pie_capdrop) {
3743
			$this->pie_capdrop = $pie_capdrop;
3744
	}
3745
	function GetPIE_QDELAY() {
3746
			return $this->pie_qdelay;
3747
	}
3748
	function SetPIE_QDELAY($pie_qdelay) {
3749
			$this->pie_qdelay = $pie_qdelay;
3750
	}
3751
	function GetPIE_PDERAND() {
3752
			return $this->pie_pderand;
3753
	}
3754
	function SetPIE_PDERAND($pie_pderand) {
3755
			$this->pie_pderand = $pie_pderand;
3756
	}
3757
	function GetScheduler() {
3758
			return $this->scheduler;
3759
	}
3760
	function SetScheduler($scheduler) {
3761
			$this->scheduler = $scheduler;
3762
	}
3763
	function GetSchedulerParameters() {
3764
			return $this->scheduler_params;
3765
	}
3766
	function SetSchedulerParameters($params) {
3767
			$this->scheduler_params = $params;
3768
	}
3769
	function SetSchedulerParameter($key, $value) {
3770
			$this->scheduler_params[$key] = $value;
3771
	}
3772
	function GetSchedulerParameter($key) {
3773
			return $this->scheduler_params[$key];
3774
	}
3775
	/* End limiter queue patch */
3776

    
3777
		/* This is here to help on form building and building rules/lists */
3778
	var $subqueues = array();
3779

    
3780
	function CanHaveChildren() {
3781
		return true;
3782
	}
3783
	function SetDelay($delay) {
3784
		$this->delay = $delay;
3785
	}
3786
	function GetDelay() {
3787
		return $this->delay;
3788
	}
3789
	function delete_queue() {
3790
		cleanup_dnqueue_from_rules($this->GetQname());
3791
		foreach ($this->subqueues as $q) {
3792
			$q->delete_queue();
3793
		}
3794
		shaper_dn_config_del($this->GetLink());
3795
		mwexec("/sbin/dnctl pipe delete " . $this->GetNumber());
3796
		mwexec("/sbin/dnctl sched delete " . $this->GetNumber());
3797
	}
3798
	function GetBandwidth() {
3799
		return $this->qbandwidth;
3800
	}
3801
	function SetBandwidth($bandwidth) {
3802
		$this->qbandwidth = $bandwidth;
3803
	}
3804
	function GetBurst() {
3805
		return $this->qburst;
3806
	}
3807
	function SetBurst($burst) {
3808
		$this->qburst = $burst;
3809
	}
3810

    
3811
	function &add_queue($interface, &$queue, &$path, &$input_errors) {
3812

    
3813
		if (!is_array($this->subqueues)) {
3814
			$this->subqueues = array();
3815
		}
3816

    
3817
		$__tmp_q = new dnqueue_class(); $q =& $__tmp_q;
3818
		$q->SetLink($path);
3819
		$q->SetEnabled("on");
3820
		$q->SetPipe($this->GetQname());
3821
		$q->SetParent($this);
3822
		$q->ReadConfig($queue);
3823
		$q->validate_input($queue, $input_errors);
3824

    
3825
		if (!is_array($input_errors)) {
3826
			$input_errors = array();
3827
		}
3828

    
3829
		if (count($input_errors)) {
3830
			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)));
3831
			return $q;
3832
		}
3833
		$number = dnqueue_find_nextnumber();
3834
		$q->SetNumber($number);
3835
		$this->subqueues[$q->GetQname()] = &$q;
3836

    
3837
		return $q;
3838
	}
3839

    
3840
	function &get_queue_list(&$q = null) {
3841
		$qlist = array();
3842

    
3843
		$qlist[$this->GetQname()] = $this->GetNumber();
3844
		if (is_array($this->subqueues)) {
3845
			foreach ($this->subqueues as $queue) {
3846
				$queue->get_queue_list($qlist);
3847
			}
3848
		}
3849
		return $qlist;
3850
	}
3851

    
3852
	/*
3853
	 * Should search even its children
3854
	 */
3855
	function &find_queue($pipe, $qname) {
3856
		if ($qname == $this->GetQname()) {
3857
			return $this;
3858
		}
3859
		foreach ($this->subqueues as $q) {
3860
			$result =& $q->find_queue("", $qname);
3861
			if ($result) {
3862
				return $result;
3863
			}
3864
		}
3865
	}
3866

    
3867
	function &find_parentqueue($pipe, $qname) {
3868
		return NULL;
3869
	}
3870

    
3871
	function validate_input($data, &$input_errors) {
3872
		parent::validate_input($data, $input_errors);
3873

    
3874
		$schedule = 0;
3875
		$schedulenone = 0;
3876
		$entries = 0;
3877
		/* XXX: Really no better way? */
3878
		for ($i = 0; $i < 2900; $i++) {
3879
			if (!empty($data["bwsched{$i}"])) {
3880
				if ($data["bwsched{$i}"] != "none") {
3881
					$schedule++;
3882
				} else {
3883
					$schedulenone++;
3884
				}
3885
			}
3886
			if (!empty($data["bandwidth{$i}"])) {
3887
				if (!is_numeric($data["bandwidth{$i}"])) {
3888
					$input_errors[] = sprintf(gettext("Bandwidth for schedule %s must be an integer."), $data["bwsched{$i}"]);
3889
				} else if (($data["burst{$i}"] != "") && (!is_numeric($data["burst{$i}"]))) {
3890
					$input_errors[] = sprintf(gettext("Burst for schedule %s must be an integer."), $data["bwsched{$i}"]);
3891
				} else {
3892
					$entries++;
3893
				}
3894
			}
3895
		}
3896
		if ($schedule == 0 && $entries > 1) {
3897
			$input_errors[] = gettext("A schedule needs to be specified for every additional entry.");
3898
		}
3899
		if ($schedulenone > 0 && $entries > 1) {
3900
			$input_errors[] = gettext("If more than one bandwidth configured all schedules need to be selected.");
3901
		}
3902
		if ($entries == 0) {
3903
			$input_errors[] = gettext("At least one bw specification is necessary.");
3904
		}
3905
		if ($data['delay'] && (!is_numeric($data['delay']))) {
3906
			$input_errors[] = gettext("Delay must be an integer.");
3907
		}
3908
		if ($data['delay'] && is_numeric($data['delay']) &&
3909
		    (($data['delay'] < 0) || ($data['delay'] > 10000))) {
3910
			$input_errors[] = gettext("Delay must be an integer between 0 and 10000.");
3911
		}
3912

    
3913
		/* Limiter patch */
3914
		$selectedScheduler = getSchedulers()[$data['sched']];
3915
		if (!$selectedScheduler) {
3916
			$input_errors[] = gettext("Selected scheduler not recognized.");
3917
		}
3918
		$selectedAqm = getAQMs()[$data['aqm']];
3919
		if (!empty($data['aqm']) && !$selectedAqm) {
3920
			$input_errors[] = gettext("Selected AQM not recognized.");
3921
		}
3922
		/* End limiter patch */
3923
	}
3924

    
3925
	function ReadConfig(&$q) {
3926
		if (!empty($q['name']) && !empty($q['newname']) && $q['name'] != $q['newname']) {
3927
			$this->SetQname($q['newname']);
3928
			rename_dnqueue_in_rules($q['name'], $q['newname']);
3929
		} else if (!empty($q['newname'])) {
3930
			$this->SetQname($q['newname']);
3931
		} else {
3932
			$this->SetQname($q['name']);
3933
		}
3934
		$this->SetNumber($q['number']);
3935

    
3936
		if (!empty($_POST)) {
3937
			$bandwidth = array();
3938
			/* XXX: Really no better way? */
3939
			for ($i = 0; $i < 2900; $i++) {
3940
				if (isset($q["bandwidth{$i}"]) && $q["bandwidth{$i}"] <> "") {
3941
					$bw = array();
3942
					$bw['bw'] = $q["bandwidth{$i}"];
3943
					$bw['burst'] = $q["burst{$i}"];
3944
					if (isset($q["bwtype{$i}"]) && $q["bwtype{$i}"]) {
3945
						$bw['bwscale'] = $q["bwtype{$i}"];
3946
					}
3947
					if (isset($q["bwsched{$i}"]) && $q["bwsched{$i}"]) {
3948
						$bw['bwsched'] = $q["bwsched{$i}"];
3949
					}
3950
					$bandwidth[] = $bw;
3951
				}
3952
			}
3953
			$this->SetBandwidth($bandwidth);
3954
		}
3955

    
3956
		if (is_array($q['bandwidth']) && is_array($q['bandwidth']['item'])) {
3957
			$this->SetBandwidth($q['bandwidth']['item']);
3958
			$this->SetBurst($q['burst']['item']);
3959
		}
3960

    
3961
		if (isset($q['qlimit']) && $q['qlimit'] <> "") {
3962
			$this->SetQlimit($q['qlimit']);
3963
		} else {
3964
			$this->SetQlimit("");
3965
		}
3966
		if (isset($q['mask']) && $q['mask'] <> "") {
3967
			$masktype = $q['mask'];
3968
		} else {
3969
			$masktype = "";
3970
		}
3971
		if (isset($q['maskbits']) && $q['maskbits'] <> "") {
3972
			$maskbits = $q['maskbits'];
3973
		} else {
3974
			$maskbits = "";
3975
		}
3976
		if (isset($q['maskbitsv6']) && $q['maskbitsv6'] <> "") {
3977
			$maskbitsv6 = $q['maskbitsv6'];
3978
		} else {
3979
			$maskbitsv6 = "";
3980
		}
3981
		$this->SetMask(array("type" => $masktype, "bits" => $maskbits, "bitsv6" => $maskbitsv6));
3982
		if (isset($q['buckets']) && $q['buckets'] <> "") {
3983
			$this->SetBuckets($q['buckets']);
3984
		} else {
3985
			$this->SetBuckets("");
3986
		}
3987
		if (isset($q['plr']) && $q['plr'] <> "") {
3988
			$this->SetPlr($q['plr']);
3989
		} else {
3990
			$this->SetPlr("");
3991
		}
3992
		if (isset($q['delay']) && $q['delay'] <> "") {
3993
			$this->SetDelay($q['delay']);
3994
		} else {
3995
			$this->SetDelay(0);
3996
		}
3997
		if (isset($q['description']) && $q['description'] <> "") {
3998
			$this->SetDescription($q['description']);
3999
		} else {
4000
			$this->SetDescription("");
4001
		}
4002
		$this->SetEnabled($q['enabled']);
4003

    
4004
		/* Limiter patch */
4005
		if (isset($q['aqm']) && $q['aqm'] <> "") {
4006
				$this->SetAQM($q['aqm']);
4007
		} else {
4008
				$this->SetAQM('droptail');
4009
		}
4010
		// parse AQM arguments
4011
		$aqm_map = getAQMs();
4012
		$selectedParameters = $aqm_map[$this->getAQM()]["parameters"];
4013
		if (is_array($selectedParameters)) {
4014
			foreach ($selectedParameters as $key => $value) {
4015
				$config_key = 'param_' . $this->GetAQM() . '_' . $key;
4016
				if (isset($q[$config_key]) && $q[$config_key] <> "") {
4017
					$this->SetAQMParameter($key, $q[$config_key]);
4018
				} else {
4019
					$this->SetAQMParameter($key, $value["default"]);
4020
				}
4021
			}
4022
		}
4023

    
4024
		if (isset($q['sched']) && $q['sched'] <> "") {
4025
				$this->SetScheduler($q['sched']);
4026
		} else {
4027
				$this->SetScheduler('fifo');
4028
		}
4029
		$scheduler_map = getSchedulers();
4030
		$selectedParameters = $scheduler_map[$this->getScheduler()]["parameters"];
4031
		if (is_array($selectedParameters)) {
4032
			foreach ($selectedParameters as $key => $value) {
4033
				$config_key = 'param_' . $this->GetScheduler() . '_' . $key;
4034
				if (isset($q[$config_key]) && $q[$config_key] <> "") {
4035
					$this->SetSchedulerParameter($key, $q[$config_key]);
4036
				} else {
4037
					$this->SetSchedulerParameter($key, $value["default"]);
4038
				}
4039
			}
4040
		}
4041

    
4042
		// ecn flag
4043
		$this->SetECN($q['ecn']);
4044
		// PIE Flags.
4045
		$this->SetPIE_ONOFF($q['pie_onoff']);
4046
		$this->SetPIE_CAPDROP($q['pie_capdrop']);
4047
		$this->SetPIE_QDELAY($q['pie_qdelay']);
4048
		$this->SetPIE_PDERAND($q['pie_pderand']);
4049
		/* End limiter patch */
4050
	}
4051

    
4052
	function build_tree() {
4053
		$tree = " <li><a href=\"firewall_shaper_vinterface.php?pipe=" . htmlspecialchars($this->GetQname()) ."&amp;queue=".htmlspecialchars($this->GetQname()) ."&amp;action=show\">";
4054
		$tree .= htmlspecialchars($this->GetQname()) . "</a>";
4055
		if (is_array($this->subqueues)) {
4056
			$tree .= "<ul>";
4057
			foreach ($this->subqueues as $q) {
4058
				$tree .= $q->build_tree();
4059
			}
4060
			$tree .= "</ul>";
4061
		}
4062
		$tree .= "</li>";
4063

    
4064
		return $tree;
4065
	}
4066

    
4067
	function build_rules() {
4068
		global $time_based_rules;
4069

    
4070
		if ($this->GetEnabled() == "") {
4071
			return;
4072
		}
4073

    
4074
		$pfq_rule = "\npipe ". $this->GetNumber() . " config ";
4075
		$found = false;
4076
		$bandwidth = $this->GetBandwidth();
4077
		if (is_array($bandwidth)) {
4078
			foreach ($bandwidth as $bw) {
4079
				if ($bw['bwsched'] != "none") {
4080
					$schedules = config_get_path('schedules/schedule');
4081
					if (is_array($schedules)) {
4082
						foreach ($schedules as $schedule) {
4083
							if ($bw['bwsched'] == $schedule['name']) {
4084
								if (filter_get_time_based_rule_status($schedule)) {
4085
									/* pipe throughputs must always be an integer, enforce that restriction again here. */
4086
									$pfq_rule .= " bw ".round(trim($bw['bw']),0).$bw['bwscale'];
4087
									if (is_numeric($bw['burst']) && ($bw['burst'] > 0)) {
4088
										$pfq_rule .= " burst ".trim($bw['burst']);
4089
									}
4090
									$found = true;
4091
									break;
4092
								}
4093
							}
4094
						}
4095
					} else {
4096
						$pfq_rule .= " bw 0";
4097
						$found = true;
4098
						break;
4099
					}
4100
				} else {
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
			if ($found == false) {
4111
				$pfq_rule .= " bw 0";
4112
			}
4113
		} else {
4114
			$pfq_rule .= " bw 0";
4115
		}
4116

    
4117
		if ($this->GetQlimit()) {
4118
			$pfq_rule .= " queue " . $this->GetQlimit();
4119
		}
4120
		if ($this->GetPlr()) {
4121
			$pfq_rule .= " plr " . $this->GetPlr();
4122
		}
4123
		if ($this->GetBuckets()) {
4124
			$pfq_rule .= " buckets " . $this->GetBuckets();
4125
		}
4126
		if ($this->GetDelay()) {
4127
			$pfq_rule .= " delay " . $this->GetDelay();
4128
		}
4129
		$this->build_mask_rules($pfq_rule);
4130

    
4131
		/* Limiter patch */
4132
		$selectedAQM = getAQMs()[$this->getAQM()];
4133
		if ($selectedAQM) {
4134
			$pfq_rule .= " " . FormatParameters($selectedAQM["parameter_format"], $this->GetAQMParameters());
4135
			if ($selectedAQM["ecn"]) {
4136
				if ($this->getECN() == 'on') {
4137
					$pfq_rule .= ' ecn';
4138
				} elseif (($this->getAQM() != 'red') && ($this->getAQM() != 'gred')) {
4139
					$pfq_rule .= ' noecn';
4140
				}
4141
			}
4142
		}
4143
		$selectedScheduler = getSchedulers()[$this->getScheduler()];
4144
		if ($selectedScheduler) {
4145
			$pfq_rule .= "\nsched ". $this->GetNumber() . " config ";
4146
			$pfq_rule .= "pipe ". $this->GetNumber();
4147
			$this->build_mask_rules($pfq_rule);
4148
			$pfq_rule .= " " . FormatParameters($selectedScheduler["parameter_format"], $this->GetSchedulerParameters());			
4149
			if ($selectedScheduler["ecn"]) {
4150
				if ($this->getECN() == 'on') {
4151
					$pfq_rule .= ' ecn';
4152
				} else {
4153
					$pfq_rule .= ' noecn';
4154
				}
4155
			}
4156
			if ($selectedScheduler["pie_onoff"]) {
4157
				if ($this->getPIE_ONOFF() == 'on') {
4158
					$pfq_rule .= 'onoff';
4159
				} else {
4160
					$pfq_rule .= '';
4161
				}
4162
			}
4163
			if ($selectedScheduler["pie_capdrop"]) {
4164
				if ($this->getPIE_CAPDROP() == 'on') {
4165
					$pfq_rule .= ' capdrop';
4166
				} else {
4167
					$pfq_rule .= ' nocapdrop';
4168
				}
4169
			}
4170
			if ($selectedScheduler["pie_qdelay"]) {
4171
				if ($this->getPIE_QDELAY() == 'on') {
4172
					$pfq_rule .= ' ts';
4173
				} else {
4174
					$pfq_rule .= ' dre';
4175
				}
4176
			}
4177
			if ($selectedScheduler["pie_pderand"]) {
4178
				if ($this->getPIE_PDERAND() == 'on') {
4179
					$pfq_rule .= ' derand';
4180
				} else {
4181
					$pfq_rule .= ' noderand';
4182
				}
4183
			}
4184
		}
4185
		$pfq_rule .= "\n";
4186
		/* End patch */
4187

    
4188
		if (!empty($this->subqueues) && count($this->subqueues) > 0) {
4189
			foreach ($this->subqueues as $q) {
4190
				$pfq_rule .= $q->build_rules();
4191
			}
4192
		}
4193

    
4194
		$pfq_rule .= " \n";
4195

    
4196
		return $pfq_rule;
4197
	}
4198

    
4199
	function update_dn_data(&$data) {
4200
		$this->ReadConfig($data);
4201
	}
4202

    
4203
	function build_javascript() {
4204
		global $g;
4205

    
4206
		$javasr = parent::build_javascript();
4207

    
4208
		//build list of schedules
4209
		$schedules = "<option value='none'>none</option>";
4210
		foreach (config_get_path('schedules/schedule', []) as $schedule) {
4211
			if ($schedule['name'] <> "") {
4212
				$schedules .= "<option value='{$schedule['name']}'>{$schedule['name']}</option>";
4213
			}
4214
		}
4215
		$bwopt = "";
4216
		foreach (array("Kb" => "Kbit/s", "Mb" => "Mbit/s", "Gb" => "Gbit/s", "b" => "Bit/s") as $bwidx => $bw) {
4217
			$bwopt .= "<option value='{$bwidx}'>{$bw}</option>";
4218
		}
4219

    
4220
		$javasr .= <<<EOD
4221
<script type='text/javascript'>
4222
//<![CDATA[
4223
var addBwRowTo = (function() {
4224

    
4225
	return (function (tableId) {
4226

    
4227
	var table = document.getElementById(tableId);
4228
	var totalrows = table.rows.length -1;
4229

    
4230
	var row = table.insertRow(totalrows + 1);
4231
	var cell1 = row.insertCell(0);
4232
	var cell2 = row.insertCell(1);
4233
	var cell3 = row.insertCell(2);
4234
	var cell4 = row.insertCell(3);
4235

    
4236
	cell1.innerHTML = "<input type='hidden' value='" + totalrows +"' name='bandwidth_row-" + totalrows + "' /><input type='text' class='form-control' name='bandwidth" + totalrows + "' id='bandwidth" + totalrows + "' />";
4237
	cell2.innerHTML = "<input type='hidden' value='" + totalrows +"' name='bwtype_row-" + totalrows + "' /><select class='form-control' name='bwtype" + totalrows + "'>{$bwopt}</select>";
4238
	cell3.innerHTML = "<input type='hidden' value='" + totalrows +"' name='bwsched_row-" + totalrows + "' /><select class='form-control' name='bwsched" + totalrows + "'>{$schedules}</select>";
4239
	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>';
4240

    
4241
	});
4242
})();
4243

    
4244
function removeBwRow(el) {
4245
	var d = el.parentNode.parentNode.rowIndex;
4246
	document.getElementById('maintable').deleteRow(d);
4247
}
4248

    
4249
function ceil_func(el){
4250
	el.value = Math.ceil(el.value);
4251

    
4252
}
4253
//]]>
4254
</script>
4255

    
4256
EOD;
4257

    
4258
		return $javasr;
4259
	}
4260

    
4261
	// Compose a table of bandwidths that can then be inserted into the form using a Form_StaticText
4262
	// The table has been "Bootstrapped" to match the web design while maintaining compatibility with
4263
	// with the javascript in this class
4264
	function build_bwtable() {
4265
		$bandwidth = $this->GetBandwidth();
4266
				//build list of schedules
4267
		$schedules = array();
4268
		$schedules[] = "none";//leave none to leave rule enabled all the time
4269
		foreach (config_get_path('schedules/schedule', []) as $schedule) {
4270
			if ($schedule['name'] != "") {
4271
				$schedules[] = $schedule['name'];
4272
			}
4273
		}
4274

    
4275
		$form = '<div class="table-responsive">';
4276
		$form .= '<table id="maintable" class="table table-hover table-striped">';
4277
		$form .= "<thead><tr>";
4278
		$form .= "<th>Bandwidth</th>";
4279
		//$form .= "<td width='35%'><div id='fifthcolumn'>Burst</div></td>";
4280
		$form .= "<th>Bw type</th>";
4281
		$form .= "<th>Schedule</th>";
4282
		$form .= "<th></th>";
4283
		$form .= "</tr></thead>";
4284
		$form .= "<tbody>";
4285

    
4286
		// If there are no bandwidths defined, make a blank one for convenience
4287
		if (empty($bandwidth)) {
4288
			$bandwidth = array(0 => array('bw' => '', 'bwscale' => 'Mb', 'bwsched' => 'none'));
4289
		}
4290

    
4291
		if (is_array($bandwidth)) {
4292
			foreach ($bandwidth as $bwidx => $bw) {
4293
				$form .= '<tr>';
4294
				$form .= '<td class="col-xs-4">';
4295
				$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\"/>";
4296
				//$form .= "</td><td width='20%'>";
4297
				//$form .= "<input class='formfld unknown' size='10' type=\"text\" id=\"burst{$bwidx}\" name=\"burst{$bwidx}\" value=\"{$bw['burst']}\" />";
4298
				$form .= "</td>";
4299
				$form .= '<td class="col-xs-4">';
4300
				$form .= "<select id=\"bwtype{$bwidx}\" name=\"bwtype{$bwidx}\" class=\"form-control\">";
4301

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

    
4305
					if ($bw['bwscale'] == $bwsidx) {
4306
						$form .= " selected";
4307
					}
4308

    
4309
					$form .= ">{$bwscale}</option>";
4310
				}
4311

    
4312
				$form .= "</select>";
4313
				$form .= "</td>";
4314
				$form .= '<td class="col-xs-4">';
4315
				$form .= "<select id=\"bwsched{$bwidx}\" name=\"bwsched{$bwidx}\" class=\"form-control\">";
4316

    
4317
				foreach ($schedules as $schd) {
4318
					$selected = "";
4319
					if ($bw['bwsched'] == $schd) {
4320
						$selected = "selected";
4321
					}
4322

    
4323
					$form .= "<option value='{$schd}' {$selected}>{$schd}</option>";
4324
				}
4325

    
4326
				$form .= "</select>";
4327
				$form .= "</td>";
4328
				$form .= '<td>';
4329
				$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>';
4330
				$form .= "</td></tr>";
4331
			}
4332
		}
4333
		$form .= "</tbody></table></div><br />";
4334

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

    
4339
		return($form);
4340
	}
4341

    
4342
	function build_form() {
4343
		global $g, $pipe, $action, $qname;
4344

    
4345
		//build list of schedules
4346
		$schedules = array();
4347
		$schedules[] = "none";//leave none to leave rule enabled all the time
4348
		foreach (config_get_path('schedules/schedule', []) as $schedule) {
4349
			if ($schedule['name'] <> "") {
4350
				$schedules[] = $schedule['name'];
4351
			}
4352
		}
4353

    
4354

    
4355
		$sform = new Form();
4356
		$sform->setAction("firewall_shaper.php");
4357

    
4358
		$section = new Form_Section('Limiters');
4359

    
4360
		$section->addInput(new Form_Checkbox(
4361
			'enabled',
4362
			'Enable',
4363
			'Enable limiter and its children',
4364
			($this->GetEnabled() == "on"),
4365
			'on'
4366
		));
4367

    
4368
		$section->addInput(new Form_Input(
4369
			'newname',
4370
			'*Name',
4371
			'text',
4372
			$this->GetQname()
4373
		));
4374

    
4375
		$section->addInput(new Form_Input(
4376
			'name',
4377
			null,
4378
			'hidden',
4379
			$this->GetQname()
4380
		));
4381

    
4382
		if ($this->GetNumber() > 0) {
4383
			$section->addInput(new Form_Input(
4384
				'number',
4385
				null,
4386
				'hidden',
4387
				$this->GetNumber()
4388
			));
4389
		}
4390

    
4391
		$bandwidth = $this->GetBandwidth();
4392

    
4393
		if (is_array($bandwidth)) {
4394
				$section->addInput(new Form_StaticText(
4395
				'Bandwidth',
4396
				$this->build_bwtable()
4397
			));
4398
		}
4399

    
4400
		$mask = $this->GetMask();
4401

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

    
4411
		$group = new Form_Group(null);
4412

    
4413
		$group->add(new Form_Select(
4414
			'maskbits',
4415
			null,
4416
			$mask['bits'],
4417
			array_combine(range(32, 1, -1), range(32, 1, -1))
4418
		))->setHelp('IPv4 mask bits%1$s%2$s', '<br />', '255.255.255.255/?');
4419

    
4420
		$group->add(new Form_Select(
4421
			'maskbitsv6',
4422
			null,
4423
			$mask['bitsv6'],
4424
			array_combine(range(128, 1, -1), range(128, 1, -1))
4425
		))->setHelp('IPv6 mask bits%1$s%2$s', '<br />', '<span style="font-family:consolas">ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/?</span>');
4426

    
4427
		$section->add($group);
4428

    
4429
		$section->addInput(new Form_Input(
4430
			'description',
4431
			'Description',
4432
			'text',
4433
			$this->GetDescription()
4434
		))->setHelp('A description may be entered here for administrative reference (not parsed).');
4435

    
4436
		$sform->add($section);
4437

    
4438
		/* Begin limiter patch */
4439
		$aqm_map = getAQMs();
4440
		$scheduler_map = getSchedulers();
4441

    
4442
		$section = new Form_Section('Queue');
4443
		$section->addInput(new Form_Select(
4444
				'aqm',
4445
				'Queue Management Algorithm',
4446
				$this->GetAQM(),
4447
				array_map_assoc(function ($k, $v) {
4448
					return [$k, $v["name"]];
4449
				}, $aqm_map)
4450
		))->setHelp('Active queue management (AQM) is the intelligent drop of network packets inside the limiter, ' .
4451
						'when it becomes full or gets close to becoming full, with the goal of reducing ' .
4452
						'network congestion.');
4453

    
4454
		$section->addInput(new Form_StaticText(
4455
			'',
4456
			build_queue_params($aqm_map, $this->GetAQM(), $this->GetAQMParameters(), "aqm")
4457
		))->setHelp('Specifies the queue management algorithm parameters.');
4458

    
4459
		$section->addInput(new Form_Select(
4460
				'sched',
4461
				'Scheduler',
4462
				$this->GetScheduler(),
4463
				array_map_assoc(function ($k, $v) {
4464
					return [$k, $v["name"]];
4465
				}, $scheduler_map)
4466
		))->setHelp('The scheduler manages the sequence of network packets in the limiter\'s queue.');
4467

    
4468
		$section->addInput(new Form_StaticText(
4469
			'',
4470
			build_queue_params($scheduler_map, $this->GetScheduler(), $this->GetSchedulerParameters(), "sched")
4471
		))->setHelp('Specifies the scheduler parameters.');
4472

    
4473
		$section->addInput(new Form_Input(
4474
				'qlimit',
4475
				'Queue length',
4476
				'number',
4477
				$this->GetQlimit()
4478
		))->setHelp('Specifies the length of the limiter\'s queue, which the scheduler and AQM are responsible for. ' .
4479
			'This field may be left empty.');
4480

    
4481
		$selectedScheduler = getSchedulers()[$this->getScheduler()];
4482

    
4483
		if ($selectedScheduler["ecn"]) {
4484
			$section->addInput(new Form_Checkbox(
4485
				'ecn',
4486
				'ECN',
4487
				'Enable Explicit Congestion Notification (ECN)',
4488
				($this->GetECN() == "on"),
4489
				'on'
4490
			))->setHelp('ECN sets a reserved TCP flag when the queue is nearing or exceeding capacity. Not all AQMs or schedulers support this.');
4491
		}
4492

    
4493
		if ($selectedScheduler["pie_onoff"]) {
4494
			$section->addInput(new Form_Checkbox(
4495
				'pie_onoff',
4496
				'ONOFF',
4497
				'Enable Onoff (onoff,)',
4498
				($this->GetPIE_ONOFF() == "on"),
4499
				'on'
4500
                	))->setHelp('Enable turning PIE on and off depending on queue load.');
4501
		}
4502

    
4503
		if ($selectedScheduler["pie_capdrop"]) {
4504
			$section->addInput(new Form_Checkbox(
4505
				'pie_capdrop',
4506
				'CAPDROP',
4507
				'Enable Capdrop (capdrop,nocapdrop)',
4508
				($this->GetPIE_CAPDROP() == "on"),
4509
				'on'
4510
			))->setHelp('Enable cap drop adjustment.');
4511
		}
4512

    
4513
		if ($selectedScheduler["pie_qdelay"]) {
4514
                        $section->addInput(new Form_Checkbox(
4515
                                'pie_qdelay',
4516
                                'QUEUE DELAY TYPE',
4517
                                'Enable Type Of Qdelay (ts,dre)',
4518
                                ($this->GetPIE_QDELAY() == "on"),
4519
                                'on'
4520
                        ))->setHelp('Set queue delay type to timestamps (checked) or departure rate estimation (unchecked).');
4521
                }
4522

    
4523
		if ($selectedScheduler["pie_pderand"]) {
4524
			$section->addInput(new Form_Checkbox(
4525
				'pie_pderand',
4526
				'PROB DERAND',
4527
				'Enable Drop Probability De-randomisation (derand,noderand)',
4528
				($this->GetPIE_PDERAND() == "on"),
4529
				'on'
4530
			))->setHelp('Enable (checked) or disable (unchecked) drop probability de-randomisation.');
4531
		}
4532

    
4533
		$sform->add($section);
4534
		/* End limiter patch */
4535

    
4536
		$section = new Form_Section('Advanced Options');
4537

    
4538
		$section->addInput(new Form_Input(
4539
			'delay',
4540
			'Delay (ms)',
4541
			'text',
4542
			$this->GetDelay() > 0 ? $this->GetDelay():null
4543
		))->setHelp('In most cases, zero (0) should specified here (or leave the field empty).');
4544

    
4545
		$section->addInput(new Form_Input(
4546
			'plr',
4547
			'Packet Loss Rate',
4548
			'number',
4549
			$this->GetPlr(),
4550
			['step' => '0.001', 'min' => '0.000']
4551
		))->setHelp('In most cases, zero (0) should be specified here (or leave the field empty). ' .
4552
					'A value of 0.001 means one packet in 1000 gets dropped.');
4553

    
4554
		$section->addInput(new Form_Input(
4555
			'buckets',
4556
			'Bucket size (slots)',
4557
			'number',
4558
			$this->GetBuckets()
4559
		))->setHelp('In most cases, this field should be left empty. It increases the hash size set.');
4560

    
4561
		$sform->add($section);
4562

    
4563
		return($sform);
4564
		}
4565

    
4566
	function wconfig() {
4567
		$cflink_path = shaper_dn_config_get_path($this->GetLink());
4568
		$cflink = config_get_path($cflink_path, []);
4569
		$cflink['name'] = $this->GetQname();
4570
		$cflink['number'] = $this->GetNumber();
4571
		$cflink['qlimit'] = $this->GetQlimit();
4572
		$cflink['plr'] = $this->GetPlr();
4573
		$cflink['description'] = $this->GetDescription();
4574

    
4575
		$bandwidth = $this->GetBandwidth();
4576
		if (is_array($bandwidth)) {
4577
			$cflink['bandwidth'] = array();
4578
			$cflink['bandwidth']['item'] = array();
4579
			foreach ($bandwidth as $bwidx => $bw) {
4580
				$cflink['bandwidth']['item'][] = $bw;
4581
			}
4582
		}
4583

    
4584
		$cflink['enabled'] = $this->GetEnabled();
4585
		$cflink['buckets'] = $this->GetBuckets();
4586
		$mask = $this->GetMask();
4587
		$cflink['mask'] = $mask['type'];
4588
		$cflink['maskbits'] = $mask['bits'];
4589
		$cflink['maskbitsv6'] = $mask['bitsv6'];
4590
		$cflink['delay'] = $this->GetDelay();
4591

    
4592
		/* Limiter queue patch */
4593
		$cflink['sched'] = $this->GetScheduler();
4594
		$scheduler_map = getSchedulers();
4595
		$selectedParameters = $scheduler_map[$this->getScheduler()]["parameters"];
4596
		foreach ($selectedParameters as $key => $value) {
4597
			$config_key = 'param_' . $this->GetScheduler() . '_' . $key;
4598
			$cflink[$config_key] = $this->GetSchedulerParameter($key);
4599
		}
4600
		$cflink['aqm'] = $this->GetAQM();
4601
		$aqm_map = GetAQMs();
4602
		$selectedParameters = $aqm_map[$this->getAQM()]["parameters"];
4603
		foreach ($selectedParameters as $key => $value) {
4604
			$config_key = 'param_' . $this->GetAQM() . '_' . $key;
4605
			$cflink[$config_key] = $this->GetAQMParameter($key);
4606
		}
4607
		$cflink['ecn'] = $this->GetECN();
4608

    
4609
		$selectedScheduler = getSchedulers()[$this->getScheduler()];
4610
		if ($selectedScheduler["pie_onoff"]) {
4611
			$cflink['pie_onoff'] = $this->GetPIE_ONOFF();
4612
		}
4613
		if ($selectedScheduler["pie_capdrop"]) {
4614
			$cflink['pie_capdrop'] = $this->GetPIE_CAPDROP();
4615
		}
4616
		if ($selectedScheduler["pie_qdelay"]) {
4617
			$cflink['pie_qdelay'] = $this->GetPIE_QDELAY();
4618
		}
4619
		if ($selectedScheduler["pie_pderand"]) {
4620
			$cflink['pie_pderand'] = $this->GetPIE_PDERAND();
4621
		}
4622
		config_set_path($cflink_path, $cflink);
4623
		/* End limiter queue patch */
4624
	}
4625

    
4626
}
4627

    
4628
class dnqueue_class extends dummynet_class {
4629
	var $pipeparent;
4630
	var $weight;
4631
	/* Limiter queue patch */
4632
    var $ecn; // ecn 'on' or 'off'
4633
	var $pie_onoff;
4634
	var $pie_capdrop;
4635
	var $pie_qdelay;
4636
	var $pie_pderand;
4637
    var $aqm; // key to aqm_map
4638
    var $aqm_params = array(); // AQM params
4639
	function GetAQM() {
4640
			return $this->aqm;
4641
	}
4642
	function SetAQM($aqm) {
4643
			$this->aqm = $aqm;
4644
	}
4645
	function GetAQMParameters() {
4646
			return $this->aqm_params;
4647
	}
4648
	function GetAQMParameter($parameter) {
4649
			return $this->aqm_params[$parameter];
4650
	}
4651
	function SetAQMParameter($key, $value) {
4652
			return $this->aqm_params[$key] = $value;
4653
	}
4654
	function SetAQMParameters($params) {
4655
			$this->aqm_params = $params;
4656
	}
4657
	function GetECN() {
4658
			return $this->ecn;
4659
	}
4660
	function SetECN($ecn) {
4661
			$this->ecn = $ecn;
4662
	}
4663
	function GetPIE_ONOFF() {
4664
			return $this->pie_onoff;
4665
	}
4666
	function SetPIE_ONOFF($pie_onoff) {
4667
			$this->pie_onoff = $pie_onoff;
4668
	}
4669
	function GetPIE_CAPDROP() {
4670
			return $this->pie_capdrop;
4671
	}
4672
	function SetPIE_CAPDROP($pie_capdrop) {
4673
			$this->pie_capdrop = $pie_capdrop;
4674
	}
4675
	function GetPIE_QDELAY() {
4676
			return $this->pie_qdelay;
4677
	}
4678
	function SetPIE_QDELAY($pie_qdelay) {
4679
			$this->pie_qdelay = $pie_qdelay;
4680
	}
4681
	function GetPIE_PDERAND() {
4682
			return $this->pie_pderand;
4683
	}
4684
	function SetPIE_PDERAND($pie_pderand) {
4685
			$this->pie_pderand = $pie_pderand;
4686
	}
4687
	/* End limiter queue patch */
4688

    
4689
	function GetWeight() {
4690
		return $this->weight;
4691
	}
4692
	function SetWeight($weight) {
4693
		$this->weight = $weight;
4694
	}
4695
	function GetPipe() {
4696
		return $this->pipeparent;
4697
	}
4698
	function SetPipe($pipe) {
4699
		$this->pipeparent = $pipe;
4700
	}
4701

    
4702
	/* Just a stub in case we ever try to call this from the frontend. */
4703
	function &add_queue($interface, &$queue, &$path, &$input_errors) {
4704
		return;
4705
	}
4706

    
4707
	function delete_queue() {
4708
		cleanup_dnqueue_from_rules($this->GetQname());
4709
		shaper_dn_config_del($this->GetLink());
4710
		mwexec("/sbin/dnctl queue delete " . $this->GetNumber());
4711
	}
4712

    
4713
	function validate_input($data, &$input_errors) {
4714
		parent::validate_input($data, $input_errors);
4715

    
4716

    
4717
		/* Limiter patch */
4718
		$selectedAqm = getAQMs()[$data['aqm']];
4719
		if (!empty($data['aqm']) && !$selectedAqm) {
4720
			$input_errors[] = gettext("Selected AQM not recognized.");
4721
		}
4722
		/* End limiter patch */
4723

    
4724
		if ($data['weight'] && ((!is_numeric($data['weight'])) ||
4725
		    ($data['weight'] < 1 && $data['weight'] > 100))) {
4726
			$input_errors[] = gettext("Weight must be an integer between 1 and 100.");
4727
		}
4728
	}
4729

    
4730
	/*
4731
	 * Should search even its children
4732
	 */
4733
	function &find_queue($pipe, $qname) {
4734
		if ($qname == $this->GetQname()) {
4735
			return $this;
4736
		} else {
4737
			return NULL;
4738
		}
4739
	}
4740

    
4741
	function &find_parentqueue($pipe, $qname) {
4742
		return $this->qparent;
4743
	}
4744

    
4745
	function &get_queue_list(&$qlist) {
4746
		if ($this->GetEnabled() == "") {
4747
			return;
4748
		}
4749
		$qlist[$this->GetQname()] = "?" .$this->GetNumber();
4750
	}
4751

    
4752
	function ReadConfig(&$q) {
4753
		if (!empty($q['name']) && !empty($q['newname']) && $q['name'] != $q['newname']) {
4754
			$this->SetQname($q['newname']);
4755
		} else if (!empty($q['newname'])) {
4756
			$this->SetQname($q['newname']);
4757
		} else {
4758
			$this->SetQname($q['name']);
4759
		}
4760
		$this->SetNumber($q['number']);
4761
		if (isset($q['qlimit']) && $q['qlimit'] <> "") {
4762
			$this->SetQlimit($q['qlimit']);
4763
		} else {
4764
			$this->SetQlimit("");
4765
		}
4766
		if (isset($q['mask']) && $q['mask'] <> "") {
4767
			$masktype = $q['mask'];
4768
		} else {
4769
			$masktype = "";
4770
		}
4771
		if (isset($q['maskbits']) && $q['maskbits'] <> "") {
4772
			$maskbits = $q['maskbits'];
4773
		} else {
4774
			$maskbits = "";
4775
		}
4776
		if (isset($q['maskbitsv6']) && $q['maskbitsv6'] <> "") {
4777
			$maskbitsv6 = $q['maskbitsv6'];
4778
		} else {
4779
			$maskbitsv6 = "";
4780
		}
4781
		$this->SetMask(array("type" => $masktype, "bits" => $maskbits, "bitsv6" => $maskbitsv6));
4782
		if (isset($q['buckets']) && $q['buckets'] <> "") {
4783
			$this->SetBuckets($q['buckets']);
4784
		} else {
4785
			$this->SetBuckets("");
4786
		}
4787
		if (isset($q['plr']) && $q['plr'] <> "") {
4788
			$this->SetPlr($q['plr']);
4789
		} else {
4790
			$this->SetPlr("");
4791
		}
4792
		if (isset($q['weight']) && $q['weight'] <> "") {
4793
			$this->SetWeight($q['weight']);
4794
		} else {
4795
			$this->SetWeight("");
4796
		}
4797
		if (isset($q['description']) && $q['description'] <> "") {
4798
			$this->SetDescription($q['description']);
4799
		} else {
4800
			$this->SetDescription("");
4801
		}
4802
		$this->SetEnabled($q['enabled']);
4803

    
4804
		/* Limiter patch */
4805
		if (isset($q['aqm']) && $q['aqm'] <> "") {
4806
				$this->SetAQM($q['aqm']);
4807
		} else {
4808
				$this->SetAQM('droptail');
4809
		}
4810
		// parse AQM arguments
4811
		$aqm_map = getAQMs();
4812
		$selectedParameters = $aqm_map[$this->getAQM()]["parameters"];
4813
		foreach ($selectedParameters as $key => $value) {
4814
			$config_key = 'param_' . $this->GetAQM() . '_' . $key;
4815
			if (isset($q[$config_key]) && $q[$config_key] <> "") {
4816
				$this->SetAQMParameter($key, $q[$config_key]);
4817
			} else {
4818
				$this->SetAQMParameter($key, $value["default"]);
4819
			}
4820
		}
4821

    
4822
		// ecn flag
4823
		$this->SetECN($q['ecn']);
4824
		// PIE Flags.
4825
		$this->SetPIE_ONOFF($q['pie_onoff']);
4826
		$this->SetPIE_CAPDROP($q['pie_capdrop']);
4827
		$this->SetPIE_QDELAY($q['pie_qdelay']);
4828
		$this->SetPIE_PDERAND($q['pie_pderand']);
4829
		/* End limiter patch */
4830
	}
4831

    
4832
	function build_tree() {
4833
		$parent =& $this->GetParent();
4834
		$tree = " <li><a href=\"firewall_shaper_vinterface.php?pipe=" . htmlspecialchars($parent->GetQname()) ."&amp;queue=" . htmlspecialchars($this->GetQname()) ."&amp;action=show\">";
4835
		$tree .= htmlspecialchars($this->GetQname()) . "</a>";
4836
		$tree .= "</li>";
4837

    
4838
		return $tree;
4839
	}
4840

    
4841
	function build_rules() {
4842
		if ($this->GetEnabled() == "") {
4843
			return;
4844
		}
4845

    
4846
		$parent =& $this->GetParent();
4847
		$pfq_rule = "queue ". $this->GetNumber() . " config pipe " . $parent->GetNumber();
4848
		if ($this->GetQlimit()) {
4849
			$pfq_rule .= " queue " . $this->GetQlimit();
4850
		}
4851
		if ($this->GetWeight()) {
4852
			$pfq_rule .= " weight " . $this->GetWeight();
4853
		}
4854
		if ($this->GetBuckets()) {
4855
			$pfq_rule .= " buckets " . $this->GetBuckets();
4856
		}
4857
		$this->build_mask_rules($pfq_rule);
4858

    
4859
		/* Limiter patch */
4860
		$selectedAQM = getAQMs()[$this->getAQM()];
4861
		if ($selectedAQM) {
4862
			$pfq_rule .= " " . FormatParameters($selectedAQM["parameter_format"], $this->GetAQMParameters());
4863
			if ($selectedAQM["ecn"]) {
4864
				if ($this->getECN() == 'on') {
4865
					$pfq_rule .= ' ecn';
4866
				} elseif (($this->getAQM() != 'red') && ($this->getAQM() != 'gred')) {
4867
					$pfq_rule .= ' noecn';
4868
				}
4869
			}
4870
			if ($selectedAQM["pie_onoff"]) {
4871
				if ($this->getPIE_ONOFF() == 'on') {
4872
				$pfq_rule .= 'onoff';
4873
				} else {
4874
					$pfq_rule .= '';
4875
				}
4876
			}
4877
			if ($selectedAQM["pie_capdrop"]) {
4878
				if ($this->getPIE_CAPDROP() == 'on') {
4879
					$pfq_rule .= ' capdrop';
4880
				} else {
4881
					$pfq_rule .= ' nocapdrop';
4882
				}
4883
			}
4884
			if ($selectedAQM["pie_qdelay"]) {
4885
				if ($this->getPIE_QDELAY() == 'on') {
4886
					$pfq_rule .= ' ts';
4887
				} else {
4888
					$pfq_rule .= ' dre';
4889
				}
4890
			}
4891
			if ($selectedAQM["pie_pderand"]) {
4892
				if ($this->getPIE_PDERAND() == 'on') {
4893
					$pfq_rule .= ' derand';
4894
				} else {
4895
					$pfq_rule .= ' noderand';
4896
				}
4897
			}
4898
		}
4899
		/* End patch */
4900

    
4901
		$pfq_rule .= "\n";
4902

    
4903
		return $pfq_rule;
4904
	}
4905

    
4906
	function build_javascript() {
4907
		return parent::build_javascript();
4908
	}
4909

    
4910
	function build_form() {
4911
		global $g, $pipe, $action, $qname;
4912

    
4913
		//build list of schedules
4914
		$schedules = array();
4915
		$schedules[] = "none";//leave none to leave rule enabled all the time
4916
		foreach (config_get_path('schedules/schedule', []) as $schedule) {
4917
			if ($schedule['name'] <> "") {
4918
				$schedules[] = $schedule['name'];
4919
			}
4920
		}
4921

    
4922
		$sform = new Form();
4923
		$sform->setAction("firewall_shaper.php");
4924
		$section = new Form_Section('Limiters');
4925

    
4926
		$section->addInput(new Form_Checkbox(
4927
			'enabled',
4928
			'Enable',
4929
			'Enable this queue',
4930
			($this->GetEnabled() == "on"),
4931
			'on'
4932
		));
4933

    
4934
		$section->addInput(new Form_Input(
4935
			'newname',
4936
			'*Name',
4937
			'text',
4938
			$this->GetQname()
4939
		));
4940

    
4941
		$section->addInput(new Form_Input(
4942
			'name',
4943
			null,
4944
			'hidden',
4945
			$this->GetQname()
4946
		));
4947

    
4948
		if ($this->GetNumber() > 0) {
4949
			$section->addInput(new Form_Input(
4950
				'number',
4951
				null,
4952
				'hidden',
4953
				$this->GetNumber()
4954
			));
4955
		}
4956

    
4957
		$mask = $this->GetMask();
4958

    
4959
		$section->addInput(new Form_Select(
4960
			'mask',
4961
			'Mask',
4962
			$mask['type'],
4963
			array('none' => gettext('None'), 'srcaddress' => gettext('Source addresses'), 'dstaddress' => gettext('Destination addresses'))
4964
		))->setHelp('If "source" or "destination" slots is chosen a dynamic queue with the bandwidth, delay, packet loss ' .
4965
					'and queue size given above will be created for each source/destination IP address encountered, respectively. ' .
4966
 					'This makes it possible to easily specify bandwidth limits per ' .
4967
 					'host or subnet, usually capped by the bandwidth of the parent ' .
4968
 					'limiter.');
4969

    
4970
		$group = new Form_Group(null);
4971

    
4972
		$group->add(new Form_Select(
4973
			'maskbits',
4974
			null,
4975
			$mask['bits'],
4976
			array_combine(range(32, 1, -1), range(32, 1, -1))
4977
		))->setHelp('IPv4 mask bits%1$s%2$s', '<br />', '255.255.255.255/?');
4978

    
4979
		$group->add(new Form_Select(
4980
			'maskbitsv6',
4981
			null,
4982
			$mask['bitsv6'],
4983
			array_combine(range(128, 1, -1), range(128, 1, -1))
4984
		))->setHelp('IPv6 mask bits%1$s%2$s', '<br />', '<span style="font-family:consolas">ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/?</span>');
4985

    
4986
		$section->add($group);
4987

    
4988
		$section->addInput(new Form_Input(
4989
			'description',
4990
			'Description',
4991
			'text',
4992
			$this->GetDescription()
4993
		))->setHelp('A description may be entered here for administrative reference (not parsed).');
4994

    
4995
		$sform->add($section);
4996

    
4997
		/* Begin limiter patch */
4998
		$aqm_map = getAQMs();
4999

    
5000
		$section = new Form_Section('Queue');
5001
		$section->addInput(new Form_Select(
5002
				'aqm',
5003
				'Queue Management Algorithm',
5004
				$this->GetAQM(),
5005
				array_map_assoc(function ($k, $v) {
5006
					return [$k, $v["name"]];
5007
				}, $aqm_map)
5008
		))->setHelp('Active queue management (AQM) is the intelligent drop of network packets inside this limiter\'s queue, ' .
5009
						'when it becomes full or gets close to becoming full, with the goal of reducing ' .
5010
						'network congestion.');
5011

    
5012
		$section->addInput(new Form_StaticText(
5013
			'',
5014
			build_queue_params($aqm_map, $this->GetAQM(), $this->GetAQMParameters(), "aqm")
5015
		))->setHelp('Specifies the queue management algorithm parameters.');
5016

    
5017
		$section->addInput(new Form_Input(
5018
				'qlimit',
5019
				'Queue length',
5020
				'number',
5021
				$this->GetQlimit()
5022
		))->setHelp('Specifies the length of this queue, which the AQM is responsible for.  This field may be left empty.');
5023

    
5024
		$selectedAQM = getAQMs()[$this->getAQM()];
5025

    
5026
		if ($selectedAQM["ecn"]) {
5027
			$section->addInput(new Form_Checkbox(
5028
				'ecn',
5029
				'ECN',
5030
				'Enable Explicit Congestion Notification (ECN)',
5031
				($this->GetECN() == "on"),
5032
				'on'
5033
			))->setHelp('ECN sets a reserved TCP flag when the queue is nearing or exceeding capacity. Not all AQMs or schedulers support this.');
5034
		}
5035

    
5036
		if ($selectedAQM["pie_onoff"]) {
5037
			$section->addInput(new Form_Checkbox(
5038
				'pie_onoff',
5039
				'ONOFF',
5040
				'Enable Onoff (onoff,)',
5041
				($this->GetPIE_ONOFF() == "on"),
5042
				'on'
5043
			))->setHelp('Enable turning PIE on and off depending on queue load.');
5044
		}
5045
                                 
5046
		if ($selectedAQM["pie_capdrop"]) {
5047
			$section->addInput(new Form_Checkbox(
5048
				'pie_capdrop',
5049
				'CAPDROP',
5050
				'Enable Capdrop (capdrop,nocapdrop)',
5051
				($this->GetPIE_CAPDROP() == "on"),
5052
				'on'
5053
			))->setHelp('Enable cap drop adjustment.');
5054
		}
5055
                 
5056
		if ($selectedAQM["pie_qdelay"]) {
5057
			$section->addInput(new Form_Checkbox(
5058
				'pie_qdelay',
5059
				'QUEUE DELAY TYPE',
5060
				'Enable Type Of Qdelay (ts,dre)',
5061
				($this->GetPIE_QDELAY() == "on"),
5062
				'on'
5063
			))->setHelp('Set queue delay type to timestamps (checked) or departure rate estimation (unchecked).');
5064
		}
5065

    
5066
		if ($selectedAQM["pie_pderand"]) {
5067
			$section->addInput(new Form_Checkbox(
5068
				'pie_pderand',
5069
				'PROB DERAND',
5070
				'Enable Drop Probability De-randomisation (derand,noderand)',
5071
				($this->GetPIE_PDERAND() == "on"),
5072
				'on'
5073
			))->setHelp('Enable (checked) or disable (unchecked) drop probability de-randomisation.');
5074
		}
5075
		$sform->add($section);
5076
		/* End limiter patch */
5077

    
5078
		$section = new Form_Section('Advanced Options');
5079

    
5080
		$section->addInput(new Form_Input(
5081
			'weight',
5082
			'Weight',
5083
			'number',
5084
			$this->GetWeight(),
5085
			['min' => '1', 'max' => '100']
5086
		))->setHelp('For queues under the same parent this specifies the share that a queue gets(values range from 1 to 100),' .
5087
					' it can be left blank otherwise.');
5088

    
5089
		$section->addInput(new Form_Input(
5090
			'plr',
5091
			'Packet Loss Rate',
5092
			'number',
5093
			$this->GetPlr(),
5094
			['step' => '0.001', 'min' => '0.000']
5095
		))->setHelp('In most cases, zero (0) should be specified here (or leave the field empty). ' .
5096
					'A value of 0.001 means one packet in 1000 gets dropped');
5097

    
5098
		$section->addInput(new Form_Input(
5099
			'buckets',
5100
			'Bucket size (slots)',
5101
			'number',
5102
			$this->GetBuckets()
5103
		))->setHelp('In most cases, this field should be left empty. It increases the hash size set');
5104

    
5105
		$section->addInput(new Form_Input(
5106
			'pipe',
5107
			null,
5108
			'hidden',
5109
			$this->GetPipe()
5110
		));
5111

    
5112
		$sform->add($section);
5113

    
5114
		return($sform);
5115
	}
5116

    
5117
	function update_dn_data(&$data) {
5118
		$this->ReadConfig($data);
5119
	}
5120

    
5121
	function wconfig() {
5122
		$cflink_path = shaper_dn_config_get_path($this->GetLink());
5123
		$cflink = config_get_path($cflink_path, []);
5124
		$cflink['name'] = $this->GetQname();
5125
		$cflink['number'] = $this->GetNumber();
5126
		$cflink['qlimit'] = $this->GetQlimit();
5127
		$cflink['description'] = $this->GetDescription();
5128
		$cflink['weight'] = $this->GetWeight();
5129
		$cflink['enabled'] = $this->GetEnabled();
5130
		$cflink['buckets'] = $this->GetBuckets();
5131
		$mask = $this->GetMask();
5132
		$cflink['mask'] = $mask['type'];
5133
		$cflink['maskbits'] = $mask['bits'];
5134
		$cflink['maskbitsv6'] = $mask['bitsv6'];
5135

    
5136
		/* Limiter queue patch */
5137
		$cflink['aqm'] = $this->GetAQM();
5138
		$aqm_map = GetAQMs();
5139
		$selectedParameters = $aqm_map[$this->getAQM()]["parameters"];
5140
		foreach ($selectedParameters as $key => $value) {
5141
			$config_key = 'param_' . $this->GetAQM() . '_' . $key;
5142
			$cflink[$config_key] = $this->GetAQMParameter($key);
5143
		}
5144
		$cflink['ecn'] = $this->GetECN();
5145

    
5146
		$selectedAQM = getAQMs()[$this->getAQM()];
5147
		if ($selectedAQM["pie_onoff"]) {
5148
			$cflink['pie_onoff'] = $this->GetPIE_ONOFF();
5149
		}
5150
		if ($selectedAQM["pie_capdrop"]) {
5151
			$cflink['pie_capdrop'] = $this->GetPIE_CAPDROP();
5152
		}
5153
		if ($selectedAQM["pie_qdelay"]) {
5154
			$cflink['pie_qdelay'] = $this->GetPIE_QDELAY();
5155
		}
5156
		if ($selectedAQM["pie_pderand"]) {
5157
			$cflink['pie_pderand'] = $this->GetPIE_PDERAND();
5158
		}
5159
		config_set_path($cflink_path, $cflink);
5160
		/* End limiter queue patch */
5161
	}
5162
}
5163

    
5164
function get_dummynet_name_list() {
5165

    
5166
	$dn_name_list =& get_unique_dnqueue_list();
5167
	$dn_name = array();
5168
	if (is_array($dn_name_list)) {
5169
		foreach ($dn_name_list as $key => $value) {
5170
			$dn_name[] = $key;
5171
		}
5172
	}
5173

    
5174
	return $dn_name;
5175

    
5176
}
5177

    
5178
function get_altq_name_list() {
5179
	$altq_name_list =& get_unique_queue_list();
5180
	$altq_name = array();
5181
	if (is_array($altq_name_list)) {
5182
		foreach ($altq_name_list as $key => $aqobj) {
5183
			$altq_name[] = $key;
5184
		}
5185
	}
5186

    
5187
	return $altq_name;
5188
}
5189

    
5190
/*
5191
 * XXX: TODO Make a class shaper to hide all these functions
5192
 * from the global namespace.
5193
 */
5194

    
5195
/*
5196
 * This is a layer violation but for now there is no way
5197
 * I can find to properly do this with PHP.
5198
 */
5199
function altq_get_default_queue($interface) {
5200
	global $altq_list_queues;
5201

    
5202
	$altq_tmp = $altq_list_queues[$interface];
5203
	if ($altq_tmp) {
5204
		return $altq_tmp->GetDefaultQueuePresent();
5205
	} else {
5206
		return false;
5207
	}
5208
}
5209

    
5210
function altq_check_default_queues() {
5211
	global $altq_list_queues;
5212

    
5213
	$count = 0;
5214
	if (is_array($altq_list_queues)) {
5215
		foreach ($altq_list_queues as $altq) {
5216
			if ($altq->GetDefaultQueuePresent()) {
5217
				$count++;
5218
			}
5219
		}
5220
	}
5221
	else {
5222
		$count++;
5223
	}
5224

    
5225
	return 0;
5226
}
5227

    
5228
function &get_unique_queue_list() {
5229
	global $altq_list_queues;
5230

    
5231
	$qlist = array();
5232
	if (is_array($altq_list_queues)) {
5233
		foreach ($altq_list_queues as $altq) {
5234
			if ($altq->GetEnabled() == "") {
5235
				continue;
5236
			}
5237
			$tmplist =& $altq->get_queue_list();
5238
			foreach ($tmplist as $qname => $link) {
5239
				if ($link->GetEnabled() <> "") {
5240
					$qlist[$qname] = $link;
5241
				}
5242
			}
5243
		}
5244
	}
5245
	return $qlist;
5246
}
5247

    
5248
function &get_unique_dnqueue_list() {
5249
	global $dummynet_pipe_list;
5250

    
5251
	$qlist = array();
5252
	if (is_array($dummynet_pipe_list)) {
5253
		foreach ($dummynet_pipe_list as $dn) {
5254
			if ($dn->GetEnabled() == "") {
5255
				continue;
5256
			}
5257
			$tmplist =& $dn->get_queue_list();
5258
			foreach ($tmplist as $qname => $link) {
5259
				$qlist[$qname] = $link;
5260
			}
5261
		}
5262
	}
5263
	return $qlist;
5264
}
5265

    
5266
function ref_on_altq_queue_list($parent, $qname) {
5267
	if (isset($GLOBALS['queue_list'][$qname])) {
5268
		$GLOBALS['queue_list'][$qname]++;
5269
	} else {
5270
		$GLOBALS['queue_list'][$qname] = 1;
5271
	}
5272

    
5273
	unref_on_altq_queue_list($parent);
5274
}
5275

    
5276
function unref_on_altq_queue_list($qname) {
5277
	$GLOBALS['queue_list'][$qname]--;
5278
	if ($GLOBALS['queue_list'][$qname] <= 1) {
5279
		unset($GLOBALS['queue_list'][$qname]);
5280
	}
5281
}
5282

    
5283
function read_altq_config() {
5284
	global $altq_list_queues;
5285
	$path = array();
5286

    
5287
	config_init_path('shaper/queue');
5288

    
5289
	$altq_list_queues = array();
5290

    
5291
	foreach (config_get_path('shaper/queue', []) as $key => $conf) {
5292
		$int = $conf['interface'];
5293
		$root = new altq_root_queue();
5294
		$root->SetInterface($int);
5295
		$altq_list_queues[$root->GetInterface()] = $root;
5296
		$root->ReadConfig($conf);
5297
		array_push($path, $key);
5298
		$root->SetLink($path);
5299
		if (is_array($conf['queue'])) {
5300
			foreach ($conf['queue'] as $key1 => $q) {
5301
				array_push($path, $key1);
5302
				/*
5303
				 * XXX: we completely ignore errors here but anyway we must have
5304
				 *	checked them before so no harm should be come from this.
5305
				 */
5306
				$root->add_queue($root->GetInterface(), $q, $path, $input_errors);
5307
				array_pop($path);
5308
			}
5309
		}
5310
		array_pop($path);
5311
	}
5312
}
5313

    
5314
function read_dummynet_config() {
5315
	global $dummynet_pipe_list;
5316
	$path = array();
5317

    
5318
	config_init_path('dnshaper/queue');
5319
	$a_int = config_get_path('dnshaper/queue', []);
5320

    
5321
	$dummynet_pipe_list = array();
5322

    
5323
	if (!count($a_int)) {
5324
		return;
5325
	}
5326

    
5327
	foreach ($a_int as $key => $conf) {
5328
		if (empty($conf['name'])) {
5329
			continue; /* XXX: grrrrrr at php */
5330
		}
5331
		$root = new dnpipe_class();
5332
		$root->ReadConfig($conf);
5333
		$dummynet_pipe_list[$root->GetQname()] = $root;
5334
		array_push($path, $key);
5335
		$root->SetLink($path);
5336
		if (is_array($conf['queue'])) {
5337
			foreach ($conf['queue'] as $key1 => $q) {
5338
				array_push($path, $key1);
5339
				/*
5340
				 * XXX: we completely ignore errors here but anyway we must have
5341
				 *	checked them before so no harm should be come from this.
5342
				 */
5343
				$root->add_queue($root->GetQname(), $q, $path, $input_errors);
5344
				array_pop($path);
5345
			}
5346
		}
5347
		array_pop($path);
5348
	}
5349
}
5350

    
5351
function get_interface_list_to_show() {
5352
	global $altq_list_queues;
5353
	global $shaperIFlist;
5354

    
5355
	$tree = "";
5356
	foreach ($shaperIFlist as $shif => $shDescr) {
5357
		if ($altq_list_queues[$shif]) {
5358
			continue;
5359
		} else {
5360
			if (!is_altq_capable(get_real_interface($shif))) {
5361
				continue;
5362
			}
5363
			$tree .= " <li><a href=\"firewall_shaper.php?interface=".$shif."&amp;action=add\">".$shDescr."</a></li>";
5364
		}
5365
	}
5366

    
5367
	return $tree;
5368
}
5369

    
5370
function filter_generate_altq_queues() {
5371
	global $altq_list_queues;
5372

    
5373
	read_altq_config();
5374

    
5375
	$altq_rules = "";
5376
	foreach ($altq_list_queues as $altq) {
5377
		$altq_rules .= $altq->build_rules();
5378
	}
5379

    
5380
	return $altq_rules;
5381
}
5382

    
5383
function dnqueue_find_nextnumber() {
5384
	global $dummynet_pipe_list;
5385

    
5386
	$dnused = array();
5387
	if (is_array($dummynet_pipe_list)) {
5388
		foreach ($dummynet_pipe_list as $dn) {
5389
			$tmplist =& $dn->get_queue_list();
5390
			foreach ($tmplist as $qname => $link) {
5391
				if ($link[0] == "?") {
5392
					$dnused[$qname] = substr($link, 1);
5393
				}
5394
			}
5395
		}
5396
	}
5397

    
5398
	sort($dnused, SORT_NUMERIC);
5399
	$dnnumber = 0;
5400
	$found = false;
5401
	foreach ($dnused as $dnnum) {
5402
		if (($dnnum - $dnnumber) > 1) {
5403
			$dnnumber = $dnnum - 1;
5404
			$found = true;
5405
			break;
5406
		} else {
5407
			$dnnumber = $dnnum;
5408
		}
5409
	}
5410

    
5411
	if ($found == false) {
5412
		$dnnumber++;
5413
	}
5414

    
5415
	unset($dnused, $dnnum, $found);
5416
	return $dnnumber;
5417
}
5418

    
5419
function dnpipe_find_nextnumber() {
5420
	global $dummynet_pipe_list;
5421

    
5422
	$dnused = array();
5423
	foreach ($dummynet_pipe_list as $dn) {
5424
		$dnused[] = $dn->GetNumber();
5425
	}
5426

    
5427
	sort($dnused, SORT_NUMERIC);
5428
	$dnnumber = 0;
5429
	$found = false;
5430
	foreach ($dnused as $dnnum) {
5431
		if (($dnnum - $dnnumber) > 1) {
5432
			$dnnumber = $dnnum - 1;
5433
			$found = true;
5434
			break;
5435
		} else {
5436
			$dnnumber = $dnnum;
5437
		}
5438
	}
5439

    
5440
	if ($found == false) {
5441
		$dnnumber++;
5442
	}
5443

    
5444
	unset($dnused, $dnnum, $found);
5445
	return $dnnumber;
5446
}
5447

    
5448
function filter_generate_dummynet_rules() {
5449
	global $g, $dummynet_pipe_list;
5450

    
5451
	read_dummynet_config();
5452

    
5453
	$dn_rules = "";
5454
	$max_qlimit = "100"; // OS default
5455
	foreach ($dummynet_pipe_list as $dn) {
5456
		$dn_rules .= $dn->build_rules();
5457
		$this_qlimit = $dn->GetQlimit();
5458
		if ($this_qlimit > $max_qlimit) {
5459
			$max_qlimit = $this_qlimit;
5460
		}
5461
	}
5462
	if (!is_numericint($max_qlimit)) {
5463
		$max_qlimit = "100";
5464
	}
5465
	if (!empty($dn_rules)) {
5466
		dummynet_load_module($max_qlimit);
5467
		file_put_contents("{$g['tmp_path']}/rules.limiter", $dn_rules);
5468
		mwexec("/sbin/dnctl {$g['tmp_path']}/rules.limiter");
5469
	}
5470
}
5471

    
5472
function build_iface_without_this_queue($iface, $qname) {
5473
	global $g, $altq_list_queues;
5474
	global $shaperIFlist;
5475

    
5476
	$altq =& $altq_list_queues[$iface];
5477

    
5478
	if ($altq) {
5479
		$scheduler = $altq->GetScheduler();
5480
	}
5481

    
5482
	$form = '<dl class="dl-horizontal">';
5483

    
5484
	$form .= '	<dt>';
5485
	$form .= '		<a href="firewall_shaper.php?interface=' . $iface . '&amp;queue=' . $iface . '&amp;action=show">' . $shaperIFlist[$iface] . '</a>';
5486
	$form .= '	</dt>';
5487
	$form .= '	<dd>';
5488
	$form .=		$scheduler;
5489
	$form .= '	</dd>';
5490

    
5491
	$form .= '	<dt>';
5492
	$form .= 'Clone';
5493
	$form .= '	</dt>';
5494
	$form .= '	<dd>';
5495
	$form .= '<a class="btn btn-info btn-xs" href="firewall_shaper_queues.php?interface=';
5496
	$form .= $iface . '&amp;queue=';
5497
	$form .= $qname . '&amp;action=add">';
5498
	$form .= '<i class="fa-regular fa-clone icon-embed-btn"></i>';
5499
	$form .= gettext("Clone Shaper to this Interface") . '</a>';
5500
	$form .= '	</dd>';
5501

    
5502
	$form .= '</dl>';
5503

    
5504
	return $form;
5505

    
5506
}
5507

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

    
5511
$shaper_msg = gettext("The tree on the left navigates through the %s.");
5512
$default_shaper_msg .= sprintf($shaper_msg, gettext("queues")) . "<br />";
5513
$dn_default_shaper_msg .= sprintf($shaper_msg, gettext("limiters")) . "<br />";
5514

    
5515
$shaper_msg = gettext("Buttons at the bottom represent %s actions and are activated accordingly.");
5516
$default_shaper_msg .= sprintf($shaper_msg, gettext("queue"));
5517
$dn_default_shaper_msg .= sprintf($shaper_msg, gettext("limiter"));
5518

    
5519
// Check to see if the specified interface has a queue configured
5520
function interface_has_queue($if) {
5521
	foreach (config_get_path('shaper/queue', []) as $queue) {
5522
		if ($queue['interface'] === $if) {
5523
			return true;
5524
		}
5525
	}
5526

    
5527
	return false;
5528
}
5529
?>
(48-48/61)