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
			if (empty($currentqueuename)) {
300
				continue;
301
			}
302
			$currentqueueif = $items[2];
303
			$newqueue['name'] = $currentqueuename;
304
			$newqueue['interface'] = $items[2];
305
			$interfacestats[$currentqueueif][$currentqueuename] = &$newqueue;
306
		} else {
307
			if ($items[3] == "pkts:") {
308
				preg_match('/pkts:\s+(\d+)\s+bytes:\s+(\d+)\s+dropped pkts:\s+(\d+)\s+bytes:\s+(\d+)/', $line, $matches);
309
				$newqueue["pkts"] = $matches[1];
310
				$newqueue["bytes"] = $matches[2];
311
				$newqueue["droppedpkts"] = $matches[3];
312
				$newqueue["droppedbytes"] = $matches[4];
313
			}
314
			if ($items[3] == "qlength:") {
315
				$subitems = explode("/", substr($line, 13, 8));
316
				$newqueue["qlengthitems"] = trim($subitems[0]);
317
				$newqueue["qlengthsize"] = trim($subitems[1]);
318
				if (substr($line, 22, 8) == "borrows:") {
319
					$newqueue["borrows"] = trim(substr($line, 31, 7));
320
				}
321
				if (substr($line, 39, 9) == "suspends:") {
322
					$newqueue["suspends"] = trim(substr($line, 49, 7));
323
				}
324
			}
325
		}
326
	}
327
	$result['interfacestats'] = $interfacestats;
328
	return $result;
329
}
330

    
331
function shaper_config_get_path($mypath) {
332
	$path = 'shaper';
333
	foreach ($mypath as $indeks) {
334
		$path .= "/queue/{$indeks}";
335
	}
336

    
337
	return $path;
338
}
339

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

    
348
function shaper_dn_config_get_path($mypath) {
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
	$path = 'dnshaper';
359
	for ($i = 0; $i < count($mypath) - 1; $i++) {
360
		$path .= "/queue/{$mypath[$i]}";
361
	}
362
	config_del_path("{$path}/queue/{$mypath[$i]}");
363
}
364

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

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

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

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

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

    
482
	return floatval($objbw);
483
}
484

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

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

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

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

    
534
	return floatval($objbw);
535
}
536

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

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

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

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

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

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

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

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

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

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

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

    
736
		return 0;
737
	}
738

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

    
748
		return $sum;
749
	}
750

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

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

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

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

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

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

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

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

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

    
893
		return $q;
894
	}
895

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

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

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

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

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

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

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

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

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

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

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

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

    
1053
		return $javascript;
1054
	}
1055

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

    
1059
		$altq =& $this;
1060

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

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

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

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

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

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

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

    
1095
		return $form;
1096

    
1097
	}
1098

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

    
1105
		$sform = new Form();
1106

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

    
1109
		$section = new Form_Section(null);
1110

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

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

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

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

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

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

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

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

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

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

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

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

    
1188
		return($sform);
1189
	}
1190

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

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

    
1222
}
1223

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1462
		return $javascript;
1463
	}
1464

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

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

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

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

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

    
1503
	function &get_queue_list(&$qlist) {
1504

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1681
		return $tree;
1682
	}
1683

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

    
1742
		$pfq_rule .= " \n";
1743

    
1744
		return $pfq_rule;
1745
	}
1746

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

    
1755
	function build_form() {
1756

    
1757
		$sform = new Form();
1758

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1869
		return($sform);
1870
	}
1871

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

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

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

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

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

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

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

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

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

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

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

    
1933
		return $form;
1934

    
1935
	}
1936

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

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

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

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

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

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

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

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

    
2115
		return $q;
2116
	}
2117

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2550
		$pfq_rule .= " \n";
2551

    
2552
		return $pfq_rule;
2553
	}
2554

    
2555
	function build_javascript() {
2556

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

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

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

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

    
2578
		enable_upperlimit();
2579

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

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

    
2591
		enable_realtime();
2592

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

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

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

    
2610
		return $javascript;
2611
	}
2612

    
2613
	function build_form() {
2614

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

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

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

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

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

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

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

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

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

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

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

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

    
2674

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2749
		return($sform);
2750
	}
2751

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

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

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

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

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

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

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

    
2908
		return $q;
2909
	}
2910

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
3166
		return $sform;
3167
	}
3168

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

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

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

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

    
3244

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
3500

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

    
3505

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

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

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

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

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

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

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

    
3690
}
3691

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

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

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

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

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

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

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

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

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

    
3836
		return $q;
3837
	}
3838

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
4063
		return $tree;
4064
	}
4065

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

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

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

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

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

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

    
4193
		$pfq_rule .= " \n";
4194

    
4195
		return $pfq_rule;
4196
	}
4197

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

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

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

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

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

    
4224
	return (function (tableId) {
4225

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

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

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

    
4240
	});
4241
})();
4242

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

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

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

    
4255
EOD;
4256

    
4257
		return $javasr;
4258
	}
4259

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

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

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

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

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

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

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

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

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

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

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

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

    
4338
		return($form);
4339
	}
4340

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

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

    
4353

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

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

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

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

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

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

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

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

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

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

    
4410
		$group = new Form_Group(null);
4411

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
4562
		return($sform);
4563
		}
4564

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

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

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

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

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

    
4625
}
4626

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

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

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

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

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

    
4715

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

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

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

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

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

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

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

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

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

    
4837
		return $tree;
4838
	}
4839

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

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

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

    
4900
		$pfq_rule .= "\n";
4901

    
4902
		return $pfq_rule;
4903
	}
4904

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

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

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

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

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

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

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

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

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

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

    
4969
		$group = new Form_Group(null);
4970

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
5113
		return($sform);
5114
	}
5115

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

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

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

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

    
5163
function get_dummynet_name_list() {
5164

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

    
5173
	return $dn_name;
5174

    
5175
}
5176

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

    
5186
	return $altq_name;
5187
}
5188

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

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

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

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

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

    
5224
	return 0;
5225
}
5226

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

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

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

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

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

    
5272
	unref_on_altq_queue_list($parent);
5273
}
5274

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

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

    
5286
	$altq_list_queues = array();
5287

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

    
5311
function read_dummynet_config() {
5312
	global $dummynet_pipe_list;
5313
	$path = array();
5314

    
5315
	$a_int = config_get_path('dnshaper/queue', []);
5316

    
5317
	$dummynet_pipe_list = array();
5318

    
5319
	if (!count($a_int)) {
5320
		return;
5321
	}
5322

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

    
5347
function get_interface_list_to_show() {
5348
	global $altq_list_queues;
5349
	global $shaperIFlist;
5350

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

    
5363
	return $tree;
5364
}
5365

    
5366
function filter_generate_altq_queues() {
5367
	global $altq_list_queues;
5368

    
5369
	read_altq_config();
5370

    
5371
	$altq_rules = "";
5372
	foreach ($altq_list_queues as $altq) {
5373
		$altq_rules .= $altq->build_rules();
5374
	}
5375

    
5376
	return $altq_rules;
5377
}
5378

    
5379
function dnqueue_find_nextnumber() {
5380
	global $dummynet_pipe_list;
5381

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

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

    
5407
	if ($found == false) {
5408
		$dnnumber++;
5409
	}
5410

    
5411
	unset($dnused, $dnnum, $found);
5412
	return $dnnumber;
5413
}
5414

    
5415
function dnpipe_find_nextnumber() {
5416
	global $dummynet_pipe_list;
5417

    
5418
	$dnused = array();
5419
	foreach ($dummynet_pipe_list as $dn) {
5420
		$dnused[] = $dn->GetNumber();
5421
	}
5422

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

    
5436
	if ($found == false) {
5437
		$dnnumber++;
5438
	}
5439

    
5440
	unset($dnused, $dnnum, $found);
5441
	return $dnnumber;
5442
}
5443

    
5444
function filter_generate_dummynet_rules() {
5445
	global $g, $dummynet_pipe_list;
5446

    
5447
	read_dummynet_config();
5448

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

    
5468
function build_iface_without_this_queue($iface, $qname) {
5469
	global $g, $altq_list_queues;
5470
	global $shaperIFlist;
5471

    
5472
	$altq =& $altq_list_queues[$iface];
5473

    
5474
	if ($altq) {
5475
		$scheduler = $altq->GetScheduler();
5476
	}
5477

    
5478
	$form = '<dl class="dl-horizontal">';
5479

    
5480
	$form .= '	<dt>';
5481
	$form .= '		<a href="firewall_shaper.php?interface=' . $iface . '&amp;queue=' . $iface . '&amp;action=show">' . $shaperIFlist[$iface] . '</a>';
5482
	$form .= '	</dt>';
5483
	$form .= '	<dd>';
5484
	$form .=		$scheduler;
5485
	$form .= '	</dd>';
5486

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

    
5498
	$form .= '</dl>';
5499

    
5500
	return $form;
5501

    
5502
}
5503

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

    
5507
$shaper_msg = gettext("The tree on the left navigates through the %s.");
5508
$default_shaper_msg .= sprintf($shaper_msg, gettext("queues")) . "<br />";
5509
$dn_default_shaper_msg .= sprintf($shaper_msg, gettext("limiters")) . "<br />";
5510

    
5511
$shaper_msg = gettext("Buttons at the bottom represent %s actions and are activated accordingly.");
5512
$default_shaper_msg .= sprintf($shaper_msg, gettext("queue"));
5513
$dn_default_shaper_msg .= sprintf($shaper_msg, gettext("limiter"));
5514

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

    
5523
	return false;
5524
}
5525
?>
(48-48/61)