Project

General

Profile

Download (147 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * shaper.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-2013 BSD Perimeter
7
 * Copyright (c) 2013-2016 Electric Sheep Fencing
8
 * Copyright (c) 2014-2025 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_get_next_queue_index($mypath) {
341
	if (!is_array($mypath)) {
342
		return 0;
343
	}
344

    
345
	$q = config_get_path(shaper_config_get_path($mypath) . "/queue", []);
346
	if (is_array($q) && count($q) > 0) {
347
		return max(array_keys($q)) + 1;
348
	} else {
349
		return 0;
350
	}
351
}
352

    
353
function shaper_config_del($mypath) {
354
	$path = 'shaper';
355
	for ($i = 0; $i < count($mypath) - 1; $i++) {
356
		$path .= "/queue/{$mypath[$i]}";
357
	}
358
	config_del_path("{$path}/queue/{$mypath[$i]}");
359
}
360

    
361
function shaper_dn_config_get_path($mypath) {
362
	$path = 'dnshaper';
363
	foreach ($mypath as $indeks) {
364
		$path .= "/queue/{$indeks}";
365
	}
366

    
367
	return $path;
368
}
369

    
370
function shaper_dn_config_get_next_queue_index($mypath) {
371
	if (!is_array($mypath)) {
372
		return 0;
373
	}
374

    
375
	$q = config_get_path(shaper_dn_config_get_path($mypath) . "/queue", []);
376
	if (is_array($q) && count($q) > 0) {
377
		return max(array_keys($q)) + 1;
378
	} else {
379
		return 0;
380
	}
381
}
382

    
383

    
384
function shaper_dn_config_del($mypath) {
385
	$path = 'dnshaper';
386
	for ($i = 0; $i < count($mypath) - 1; $i++) {
387
		$path .= "/queue/{$mypath[$i]}";
388
	}
389
	config_del_path("{$path}/queue/{$mypath[$i]}");
390
}
391

    
392
function clean_child_queues($type, $mypath) {
393
	$ref_path = shaper_config_get_path($mypath);
394
	$ref = config_get_path($ref_path);
395
	if (empty($ref)) {
396
		return;
397
	}
398

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

    
475
function get_bandwidthtype_scale($type) {
476
	switch ($type) {
477
		case "Gb":
478
			$factor = 1024 * 1024 * 1024;
479
			break;
480
		case "Mb":
481
			$factor = 1024 * 1024;
482
			break;
483
		case "Kb":
484
			$factor = 1024;
485
			break;
486
		case "b":
487
		default:
488
			$factor = 1;
489
			break;
490
	}
491
	return intval($factor);
492
}
493

    
494
function get_bandwidth($bw, $scale, $obj) {
495
	$bw = (int) $bw;
496
	$pattern= "/(b|Kb|Mb|Gb|%)/";
497
	if (!preg_match($pattern, $scale, $match))
498
		return 0;
499

    
500
	switch ($match[1]) {
501
		case '%':
502
			$objbw = ($bw / 100) * get_queue_bandwidth($obj);
503
			break;
504
		default:
505
			$objbw = $bw * get_bandwidthtype_scale($scale);
506
			break;
507
	}
508

    
509
	return floatval($objbw);
510
}
511

    
512
/*
513
 * XXX - unused
514
 *
515
function get_hfsc_bandwidth($object, $bw) {
516
	$pattern= "/[0-9]+/";
517
	if (preg_match($pattern, $bw, $match)) {
518
		$bw_1 = $match[1];
519
	} else {
520
		return 0;
521
	}
522
	$pattern= "/(b|Kb|Mb|Gb|%)/";
523
	if (preg_match($pattern, $bw, $match)) {
524
		switch ($match[1]) {
525
			case '%':
526
				$bw_1 = ($bw_1 / 100) * get_interface_bandwidth($object);
527
				break;
528
			default:
529
				$bw_1 = $bw_1 * get_bandwidthtype_scale($match[0]);
530
				break;
531
		}
532
		return floatval($bw_1);
533
	} else {
534
		return 0;
535
	}
536
}
537
*/
538

    
539
function get_queue_bandwidth($obj) {
540
	$bw = (int)$obj->GetBandwidth();
541
	$scale = $obj->GetBwscale();
542

    
543
	$pattern= "/(b|Kb|Mb|Gb|%)/";
544
	if (!preg_match($pattern, $scale, $match))
545
		return 0;
546

    
547
	switch ($match[1]) {
548
		case '%':
549
			if (method_exists($obj, 'GetParent')) {
550
				$getobjbw = get_queue_bandwidth($obj->GetParent());
551
			} else {
552
				$getobjbw = $obj->bandwidth;
553
			}
554
			$objbw = ($bw / 100) * $getobjbw;
555
			break;
556
		default:
557
			$objbw = $bw * get_bandwidthtype_scale($scale);
558
			break;
559
	}
560

    
561
	return floatval($objbw);
562
}
563

    
564
function get_interface_bandwidth($object) {
565
	global $altq_list_queues;
566

    
567
	$int = $object->GetInterface();
568
	if (isset($altq_list_queues[$int])) {
569
		$altq = &$altq_list_queues[$int];
570
		$bw_3 = (int)$altq->GetBandwidth();
571
		$bw_3 = $bw_3 * get_bandwidthtype_scale($altq->GetBwscale());
572
		return floatval($bw_3);
573
	} else {
574
		return 0;
575
	}
576
}
577

    
578
function cleanup_queue_from_rules($queue) {
579
	$rulelist = config_get_path('filter/rule', []);
580
	foreach ($rulelist as & $rule) {
581
		if ($rule['defaultqueue'] == $queue) {
582
			unset($rule['defaultqueue']);
583
			if (isset($rule['ackqueue'])) {
584
				unset($rule['ackqueue']);
585
			}
586
		}
587
		if ($rule['ackqueue'] == $queue) {
588
			unset($rule['ackqueue']);
589
		}
590
	}
591
	config_set_path('filter/rule', $rulelist);
592
}
593

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

    
607
function rename_queue_in_rules($name, $newname) {
608
	$rulelist = config_get_path('filter/rule', []);
609
	foreach ($rulelist as & $rule) {
610
		if ($rule['defaultqueue'] == $name) {
611
			$rule['defaultqueue'] = $newname;
612
		}
613
		if ($rule['ackqueue'] == $name) {
614
			$rule['ackqueue'] = $newname;
615
		}
616
	}
617
	config_set_path('filter/rule', $rulelist);
618
}
619

    
620
function rename_dnqueue_in_rules($name, $newname) {
621
	$rulelist = config_get_path('filter/rule', []);
622
	foreach ($rulelist as & $rule) {
623
		if ($rule['dnpipe'] == $name) {
624
			$rule['dnpipe'] = $newname;
625
		}
626
		if ($rule['pdnpipe'] == $name) {
627
			$rule['pdnpipe'] = $newname;
628
		}
629
	}
630
	config_set_path('filter/rule', $rulelist);
631
}
632

    
633
class altq_root_queue {
634
	var $interface;
635
	var $tbrconfig ;
636
	var $bandwidth;
637
	var $bandwidthtype; /* b, Kb, Mb, Gb, % */
638
	var $scheduler;
639
	var $qlimit;
640
	var $queues = array();
641
	var $qenabled = false;
642
	var $link;
643

    
644
	/* Accessor functions */
645
	function GetDefaultQueuePresent() {
646
		if (!empty($this->queues)) {
647
			foreach ($this->queues as $q) {
648
				if ($q->GetDefault()) {
649
					return true;
650
				}
651
			}
652
		}
653

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

    
729
	function GetBwscaleText() {
730
		switch ($this->bandwidthtype) {
731
			case "b":
732
				$bwscaletext = "Bit/s";
733
				break;
734
			case "Kb":
735
				$bwscaletext = "Kbit/s";
736
				break;
737
			case "Mb":
738
				$bwscaletext = "Mbit/s";
739
				break;
740
			case "Gb":
741
				$bwscaletext = "Gbit/s";
742
				break;
743
			default:
744
				/* For others that do not need translating like % */
745
				$bwscaletext = $this->bandwidthtype;
746
				break;
747
		}
748
		return $bwscaletext;
749
	}
750

    
751
	function CheckBandwidth($bw, $bwtype) {
752
		$bw = (int)$bw;
753
		$sum = $this->GetTotalBw();
754
		if ($sum > ($bw * get_bandwidthtype_scale($bwtype))) {
755
			return 1;
756
		}
757
		foreach ($this->queues as $q) {
758
			if ($q->CheckBandwidth(0, '')) {
759
				return 1;
760
			}
761
		}
762

    
763
		return 0;
764
	}
765

    
766
	function GetTotalBw($qignore = NULL) {
767
		$sum = 0;
768
		foreach ($this->queues as $q) {
769
			if (($qignore != NULL) && ($qignore == $q)) {
770
				continue;
771
			}
772
			$sum += get_bandwidth($q->GetBandwidth(), $q->GetBwscale(), $this);
773
		}
774

    
775
		return $sum;
776
	}
777

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

    
814
	/* Implement this to shorten some code on the frontend page */
815
	function ReadConfig(&$conf) {
816
		if (isset($conf['tbrconfig'])) {
817
			$this->SetTbrConfig($conf['tbrconfig']);
818
		} else {
819
			$this->SetTbrConfig($conf['tbrconfig']);
820
		}
821
		$this->SetBandwidth($conf['bandwidth']);
822
		if ($conf['bandwidthtype'] <> "") {
823
			$this->SetBwscale($conf['bandwidthtype']);
824
		}
825
		if (isset($conf['scheduler'])) {
826
			if ($this->GetScheduler() != $conf['scheduler']) {
827
				foreach ($this->queues as $q) {
828
					clean_child_queues($conf['scheduler'], $this->GetLink());
829
					$q->clean_queue($conf['scheduler']);
830
				}
831
			}
832
			$this->SetScheduler($conf['scheduler']);
833
		}
834
		if (isset($conf['qlimit']) && $conf['qlimit'] <> "") {
835
			$this->SetQlimit($conf['qlimit']);
836
		} else {
837
			$this->SetQlimit("");
838
		}
839
		if (isset($conf['name'])) {
840
			$this->SetQname($conf['name']);
841
		}
842
		if (!empty($conf['enabled'])) {
843
			$this->SetEnabled($conf['enabled']);
844
		} else {
845
			$this->SetEnabled("");
846
		}
847
	}
848

    
849
	function copy_queue($interface, &$cflink) {
850
		$cflink['interface'] = $interface;
851
		$cflink['name'] = $interface;
852
		$cflink['scheduler'] = $this->GetScheduler();
853
		$cflink['bandwidth'] = $this->GetBandwidth();
854
		$cflink['bandwidthtype'] = $this->GetBwscale();
855
		$cflink['qlimit'] = $this->GetQlimit();
856
		$cflink['tbrconfig'] = $this->GetTbrConfig();
857
		$cflink['enabled'] = $this->GetEnabled();
858
		if (is_array($this->queues)) {
859
			$cflink['queue'] = array();
860
			foreach ($this->queues as $q) {
861
				$cflink['queue'][$q->GetQname()] = array();
862
				$q->copy_queue($interface, $cflink['queue'][$q->GetQname()]);
863
			}
864
		}
865
	}
866

    
867
	function &get_queue_list(&$q = null) {
868
		$qlist = array();
869

    
870
		//$qlist[$this->GetQname()] = & $this;
871
		if (is_array($this->queues)) {
872
			foreach ($this->queues as $queue) {
873
				$queue->get_queue_list($qlist);
874
			}
875
		}
876
		return $qlist;
877
	}
878

    
879
	function &add_queue($interface, &$queue, &$path, &$input_errors) {
880

    
881
		if (!is_array($this->queues)) {
882
			$this->queues = array();
883
		}
884

    
885
		switch ($this->GetScheduler()) {
886
			case "PRIQ":
887
				$__tmp_q = new priq_queue(); $q =& $__tmp_q;
888
				break;
889
			case "HFSC":
890
				$__tmp_q = new hfsc_queue(); $q =& $__tmp_q;
891
				break;
892
			case "CBQ":
893
				$__tmp_q = new cbq_queue(); $q =& $__tmp_q;
894
				break;
895
			case "FAIRQ":
896
				$__tmp_q = new fairq_queue(); $q =& $__tmp_q;
897
				break;
898
			default:
899
				/* XXX: but should not happen anyway */
900
				return;
901
				break;
902
		}
903
		$q->SetLink($path);
904
		$q->SetInterface($this->GetInterface());
905
		$q->SetEnabled("on");
906
		$q->SetParent($this);
907
		$q->ReadConfig($queue);
908
		$q->validate_input($queue, $input_errors);
909

    
910
		$this->queues[$q->GetQname()] = &$q;
911
		ref_on_altq_queue_list($this->GetQname(), $q->GetQname());
912
		if (is_array($queue['queue'])) {
913
			foreach ($queue['queue'] as $key1 => $que) {
914
				array_push($path, $key1);
915
				$q->add_queue($q->GetInterface(), $que, $path, $input_errors);
916
				array_pop($path);
917
			}
918
		}
919

    
920
		return $q;
921
	}
922

    
923
	/* interface here might be optional */
924
	function &find_queue($interface, $qname) {
925
		if ($qname == $this->GetQname()) {
926
			return $this;
927
		}
928
		foreach ($this->queues as $q) {
929
			$result =& $q->find_queue("", $qname);
930
			if ($result) {
931
				return $result;
932
			}
933
		}
934
	}
935

    
936
	function &find_parentqueue($interface, $qname) {
937
		if ($qname == $interface) {
938
			$result = NULL;
939
		} else if ($this->queues[$qname]) {
940
			$result = $this;
941
		} else if ($this->GetScheduler() <> "PRIQ") {
942
			foreach ($this->queues as $q) {
943
				$result = $q->find_parentqueue("", $qname);
944
				if ($result) {
945
					return $result;
946
				}
947
			}
948
		}
949
	}
950

    
951
	function build_tree() {
952
		global $shaperIFlist;
953

    
954
		$tree = " <li><a href=\"firewall_shaper.php?interface=".$this->GetInterface()."&amp;queue=". $this->GetInterface()."&amp;action=show";
955
		$tree .= "\">" . $shaperIFlist[$this->GetInterface()] . "</a>";
956
		if (is_array($this->queues)) {
957
			$tree .= "<ul>";
958
			foreach ($this->queues as $q) {
959
				$tree .= $q->build_tree();
960
			}
961
			$tree .= "</ul>";
962
		}
963
		$tree .= "</li>";
964
		return $tree;
965
	}
966

    
967
	function delete_queue() {
968
		foreach ($this->queues as $q) {
969
			$q->delete_queue();
970
		}
971
		shaper_config_del($this->GetLink());
972
	}
973

    
974
	function delete_all() {
975
		if (count($this->queues)) {
976
			foreach ($this->queues as $q) {
977
				$q->delete_all();
978
				shaper_config_del($q->GetLink());
979
				unset($q);
980
			}
981
			unset($this->queues);
982
		}
983
	}
984

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

    
1029
			if ($default == false) {
1030
				$error = sprintf(gettext("SHAPER: no default queue specified for interface %s."), $this->GetInterface()) . " " . gettext("The interface queue will be enforced as default.");
1031
				file_notice("Shaper", $error, "Error occurred", "");
1032
				unset($error);
1033
				return "\n";
1034
			}
1035
			$frule .= $rules;
1036
		} else if ($this->GetEnabled() == "on" && $this->GetScheduler() == "CODELQ") {
1037
			$rules = " altq on " . get_real_interface($this->GetInterface());
1038
			if ($this->GetScheduler()) {
1039
				$rules .= " ".strtolower($this->GetScheduler());
1040
			}
1041
			if ($this->GetQlimit() > 0) {
1042
				$rules .= " ( qlimit " . $this->GetQlimit() . " ) ";
1043
			}
1044
			if ($this->GetBandwidth()) {
1045
				$rules .= " bandwidth ".trim($this->GetBandwidth());
1046
				if ($this->GetBwscale()) {
1047
					$rules .= $this->GetBwscale();
1048
				}
1049
			}
1050
			if ($this->GetTbrConfig()) {
1051
				$rules .= " tbrsize ".$this->GetTbrConfig();
1052
			}
1053

    
1054
			$rules .= " queue";
1055
		}
1056

    
1057
		$rules .= " \n";
1058
		return $rules;
1059
	}
1060

    
1061
	function build_javascript() {
1062
		$javascript = "<script type=\"text/javascript\">";
1063
		$javascript .= "//<![CDATA[\n";
1064
		$javascript .= "function mySuspend() {";
1065
		$javascript .= "if (document.layers && document.layers['shaperarea'] != null) ";
1066
		$javascript .= "document.layers['shaperarea'].visibility = 'hidden'; ";
1067
		$javascript .= "else if (document.all)";
1068
		$javascript .= "document.all['shaperarea'].style.visibility = 'hidden';";
1069
		$javascript .= "}";
1070

    
1071
		$javascript .= "function myResume() {";
1072
		$javascript .= "if (document.layers && document.layers['shaperarea'] != null) ";
1073
		$javascript .= "document.layers['shaperarea'].visibility = 'visible';";
1074
		$javascript .= "else if (document.all) ";
1075
		$javascript .= "document.all['shaperarea'].style.visibility = 'visible';";
1076
		$javascript .= "}";
1077
		$javascript .= "//]]>";
1078
		$javascript .= "</script>";
1079

    
1080
		return $javascript;
1081
	}
1082

    
1083
	function build_shortform() {
1084
		global $shaperIFlist;
1085

    
1086
		$altq =& $this;
1087

    
1088
		if ($altq) {
1089
			$scheduler = ": " . $altq->GetScheduler();
1090
		}
1091

    
1092
		$form = '<dl class="dl-horizontal">';
1093
		$form .= '	<dt>';
1094
		$form .= '		<a href="firewall_shaper.php?interface=' . $this->GetInterface() . '&amp;queue=' . $this->GetQname() . '&amp;action=show">' . $shaperIFlist[$this->GetInterface()] . '</a>';
1095
		$form .= '	</dt>';
1096
		$form .= '	<dd>';
1097
		$form .=		$scheduler;
1098
		$form .= '	</dd>';
1099

    
1100
		$form .= '	<dt>';
1101
		$form .=		'Bandwidth';
1102
		$form .= '	</dt>';
1103
		$form .= '	<dd>';
1104
		$form .=		$this->GetBandwidth() . '&nbsp;' . $this->GetBwscaleText();
1105
		$form .= '	</dd>';
1106

    
1107
		$form .= '	<dt>';
1108
		$form .= 'Disable';
1109
		$form .= '	<dt>';
1110
		$form .= '	<dd>';
1111

    
1112
		$form .= '<a class="btn btn-danger btn-xs" href="firewall_shaper_queues.php?interface=';
1113
		$form .= $this->GetInterface() . '&amp;queue=';
1114
		$form .= $this->GetQname() . '&amp;action=delete">';
1115
		$form .= '<i class="fa-solid fa-trash-can icon-embed-btn"></i>';
1116
		$form .= gettext("Remove shaper from this interface") . '</a>';
1117

    
1118
		$form .= '	</dd>';
1119

    
1120
		$form .= '</dl>';
1121

    
1122
		return $form;
1123

    
1124
	}
1125

    
1126
	/*
1127
	 * For requesting the parameters of the root queues
1128
	 * to the user like the traffic wizard does.
1129
	 */
1130
	function build_form() {
1131

    
1132
		$sform = new Form();
1133

    
1134
		$sform->setAction("firewall_shaper.php");
1135

    
1136
		$section = new Form_Section(null);
1137

    
1138
		$section->addInput(new Form_Checkbox(
1139
			'enabled',
1140
			'Enable/Disable',
1141
			'Enable/disable discipline and its children',
1142
			($this->GetEnabled() == "on"),
1143
			'on'
1144
		));
1145

    
1146
		$section->addInput(new Form_StaticText(
1147
			'*Name',
1148
			$this->GetQname()
1149
		));
1150

    
1151
		$section->addInput(new Form_Select(
1152
			'scheduler',
1153
			'Scheduler Type',
1154
			$this->GetScheduler(),
1155
			array('HFSC' => 'HFSC',
1156
				  'CBQ' => 'CBQ',
1157
				  'FAIRQ' => 'FAIRQ',
1158
				  'CODELQ' => 'CODELQ',
1159
				  'PRIQ' => 'PRIQ')
1160
		))->setHelp('Changing this changes all child queues! Beware information can be lost.');
1161

    
1162
		$group = new Form_group('Bandwidth');
1163

    
1164
		$group->add(new Form_Input(
1165
			'bandwidth',
1166
			null,
1167
			'number',
1168
			$this->GetBandwidth()
1169
		));
1170

    
1171
		$group->add(new Form_Select(
1172
			'bandwidthtype',
1173
			null,
1174
			$this->FormGetBwscale(),
1175
			array('Kb' => 'Kbit/s',
1176
				  'Mb' => 'Mbit/s',
1177
				  'Gb' => 'Gbit/s',
1178
				  'b' => 'Bit/s',
1179
				  '%' => '%')
1180
		));
1181

    
1182
		$section->add($group);
1183

    
1184
		$section->addInput(new Form_Input(
1185
			'qlimit',
1186
			'Queue Limit',
1187
			'number',
1188
			$this->GetQlimit()
1189
		));
1190

    
1191
		$section->addInput(new Form_Input(
1192
			'tbrconfig',
1193
			'TBR Size',
1194
			'number',
1195
			$this->GetTbrConfig()
1196
		))->setHelp('Adjusts the size, in bytes, of the token bucket regulator. If not specified, heuristics based on the interface ' .
1197
					'bandwidth are used to determine the size.');
1198

    
1199
		$section->addInput(new Form_Input(
1200
			'interface',
1201
			null,
1202
			'hidden',
1203
			$this->GetInterface()
1204
		));
1205

    
1206
		$section->addInput(new Form_Input(
1207
			'name',
1208
			null,
1209
			'hidden',
1210
			$this->GetQname()
1211
		));
1212

    
1213
		$sform->add($section);
1214

    
1215
		return($sform);
1216
	}
1217

    
1218
	function update_altq_queue_data(&$data) {
1219
		$this->ReadConfig($data);
1220
	}
1221

    
1222
	/*
1223
	 * Should call on each of it queues and subqueues
1224
	 * the same function much like build_rules();
1225
	 */
1226
	function wconfig() {
1227
		$cflink_path = shaper_config_get_path($this->GetLink());
1228
		$cflink = config_get_path($cflink_path, []);
1229
		$cflink['interface'] = $this->GetInterface();
1230
		$cflink['name'] = $this->GetQname();
1231
		$cflink['scheduler'] = $this->GetScheduler();
1232
		$cflink['bandwidth'] = $this->GetBandwidth();
1233
		$cflink['bandwidthtype'] = $this->GetBwscale();
1234
		$cflink['qlimit'] = trim($this->GetQlimit());
1235
		if (empty($cflink['qlimit'])) {
1236
			unset($cflink['qlimit']);
1237
		}
1238
		$cflink['tbrconfig'] = trim($this->GetTbrConfig());
1239
		if (empty($cflink['tbrconfig'])) {
1240
			unset($cflink['tbrconfig']);
1241
		}
1242
		$cflink['enabled'] = $this->GetEnabled();
1243
		if (empty($cflink['enabled'])) {
1244
			unset($cflink['enabled']);
1245
		}
1246
		config_set_path($cflink_path, $cflink);
1247
	}
1248

    
1249
}
1250

    
1251
class priq_queue {
1252
	var $qname;
1253
	var $qinterface;
1254
	var $qlimit;
1255
	var $qpriority;
1256
	var $description;
1257
	var $isparent;
1258
	var $qbandwidth;
1259
	var $qbandwidthtype;
1260
	var $qdefault = "";
1261
	var $qrio = "";
1262
	var $qred = "";
1263
	var $qcodel = "";
1264
	var $qecn = "";
1265
	var $qack;
1266
	var $qenabled = "";
1267
	var $qparent;
1268
	var $link;
1269
	var $firsttime;
1270

    
1271
	/* This is here to help with form building and building rules/lists */
1272
	var $subqueues = array();
1273

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

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

    
1353
		if ($bwtype != "%") {
1354
			$sum = $parent->GetTotalBw(($bw > 0) ? $this : NULL);
1355

    
1356
			if ($bw > 0) {
1357
				$sum += get_bandwidth($bw, $bwtype, $parent);
1358
			}
1359

    
1360
			if ($sum > get_queue_bandwidth($parent)) {
1361
				return 1;
1362
			}
1363
		}
1364

    
1365
		foreach ($this->subqueues as $q) {
1366
			if ($q->CheckBandwidth(0, '')) {
1367
				return 1;
1368
			}
1369
		}
1370

    
1371
		return 0;
1372
	}
1373
	function GetTotalBw($qignore = NULL) {
1374
		$sum = 0;
1375
		foreach ($this->subqueues as $q) {
1376
			if ($qignore != NULL && $qignore == $q)
1377
				continue;
1378
			$sum += get_bandwidth($q->GetBandwidth(), $q->GetBwscale(), $q->GetParent());
1379
		}
1380

    
1381
		return $sum;
1382
	}
1383
	function GetBwscale() {
1384
		return $this->qbandwidthtype;
1385
	}
1386
	function FormGetBwscale() {
1387
		if ($this->GetBwscale()) {
1388
			$bwscale = $this->GetBwscale();
1389
		} else {
1390
			$bwscale = 'Mb';
1391
		}
1392
		return $bwscale;
1393
	}
1394
	function SetBwscale($scale) {
1395
		$this->qbandwidthtype = $scale;
1396
	}
1397
	function GetDefaultQueuePresent() {
1398
		if ($this->GetDefault()) {
1399
			return true;
1400
		}
1401
		if (!empty($this->subqueues)) {
1402
			foreach ($this->subqueues as $q) {
1403
				if ($q->GetDefault()) {
1404
					return true;
1405
				}
1406
			}
1407
		}
1408

    
1409
		return false;
1410
	}
1411
	function GetDefault() {
1412
		return $this->qdefault;
1413
	}
1414
	function SetDefault($value = false) {
1415
		$this->qdefault = $value;
1416
	}
1417
	function GetCodel() {
1418
		return $this->qcodel;
1419
	}
1420
	function SetCodel($codel = false) {
1421
		$this->qcodel = $codel;
1422
	}
1423
	function GetRed() {
1424
		return $this->qred;
1425
	}
1426
	function SetRed($red = false) {
1427
		$this->qred = $red;
1428
	}
1429
	function GetRio() {
1430
		return $this->qrio;
1431
	}
1432
	function SetRio($rio = false) {
1433
		$this->qrio = $rio;
1434
	}
1435
	function GetEcn() {
1436
		return $this->qecn;
1437
	}
1438
	function SetEcn($ecn = false) {
1439
		$this->qecn = $ecn;
1440
	}
1441
	function GetAck() {
1442
		return $this->qack;
1443
	}
1444
	function SetAck($ack = false) {
1445
		$this->qack = $ack;
1446
	}
1447

    
1448
	function GetBwscaleText() {
1449
		switch ($this->qbandwidthtype) {
1450
			case "b":
1451
				$bwscaletext = "Bit/s";
1452
				break;
1453
			case "Kb":
1454
				$bwscaletext = "Kbit/s";
1455
				break;
1456
			case "Mb":
1457
				$bwscaletext = "Mbit/s";
1458
				break;
1459
			case "Gb":
1460
				$bwscaletext = "Gbit/s";
1461
				break;
1462
			default:
1463
				/* For others that do not need translating like % */
1464
				$bwscaletext = $this->qbandwidthtype;
1465
				break;
1466
		}
1467
		return $bwscaletext;
1468
	}
1469

    
1470
	function build_javascript() {
1471
		$javascript = "<script type=\"text/javascript\">";
1472
		$javascript .= "//<![CDATA[\n";
1473
		$javascript .= "function mySuspend() { \n";
1474
		$javascript .= "if (document.layers && document.layers['shaperarea'] != null)\n";
1475
		$javascript .= "document.layers['shaperarea'].visibility = 'hidden';\n";
1476
		$javascript .= "else if (document.all)\n";
1477
		$javascript .= "document.all['shaperarea'].style.visibility = 'hidden';\n";
1478
		$javascript .= "}\n";
1479

    
1480
		$javascript .= "function myResume() {\n";
1481
		$javascript .= "if (document.layers && document.layers['shaperarea'] != null)\n";
1482
		$javascript .= "document.layers['shaperarea'].visibility = 'visible';\n";
1483
		$javascript .= "else if (document.all)\n";
1484
		$javascript .= "document.all['shaperarea'].style.visibility = 'visible';\n";
1485
		$javascript .= "}\n";
1486
		$javascript .= "//]]>";
1487
		$javascript .= "</script>";
1488

    
1489
		return $javascript;
1490
	}
1491

    
1492
	function &add_queue($interface, &$qname, &$path, &$input_errors) { return; }
1493

    
1494
	/*
1495
	 * Currently this will not be called unless we decide to clone a whole
1496
	 * queue tree on the 'By Queues' view or support drag&drop on the tree/list
1497
	 */
1498
	function copy_queue($interface, &$cflink) {
1499

    
1500
		$cflink['name'] = $this->GetQname();
1501
		$cflink['interface'] = $interface;
1502
		$cflink['qlimit'] = $this->GetQlimit();
1503
		$cflink['priority'] = $this->GetQpriority();
1504
		$cflink['description'] = $this->GetDescription();
1505
		$cflink['enabled'] = $this->GetEnabled();
1506
		$cflink['default'] = $this->GetDefault();
1507
		$cflink['red'] = $this->GetRed();
1508
		$cflink['codel'] = $this->GetCodel();
1509
		$cflink['rio'] = $this->GetRio();
1510
		$cflink['ecn'] = $this->GetEcn();
1511

    
1512
		if (is_array($this->subqueues)) {
1513
			$cflinkp['queue'] = array();
1514
			foreach ($this->subqueues as $q) {
1515
				$cflink['queue'][$q->GetQname()] = array();
1516
				$q->copy_queue($interface, $cflink['queue'][$q->GetQname()]);
1517
			}
1518
		}
1519
	}
1520

    
1521
	function clean_queue($sched) {
1522
		clean_child_queues($sched, $this->GetLink());
1523
		if (is_array($this->subqueues)) {
1524
			foreach ($this->subqueues as $q) {
1525
				$q->clean_queue($sched);
1526
			}
1527
		}
1528
	}
1529

    
1530
	function &get_queue_list(&$qlist) {
1531

    
1532
		$qlist[$this->GetQname()] = & $this;
1533
		if (is_array($this->subqueues)) {
1534
			foreach ($this->subqueues as $queue) {
1535
				$queue->get_queue_list($qlist);
1536
			}
1537
		}
1538
	}
1539

    
1540
	function delete_queue() {
1541
		unref_on_altq_queue_list($this->GetQname());
1542
		cleanup_queue_from_rules($this->GetQname());
1543
		shaper_config_del($this->GetLink());
1544
	}
1545

    
1546
	function delete_all() {
1547
		if (count($this->subqueues)) {
1548
			foreach ($this->subqueues as $q) {
1549
				$q->delete_all();
1550
				shaper_config_del($q->GetLink());
1551
				unset($q);
1552
			}
1553
			unset($this->subqueues);
1554
		}
1555
	}
1556

    
1557
	function &find_queue($interface, $qname) {
1558
		if ($qname == $this->GetQname()) {
1559
			return $this;
1560
		}
1561
	}
1562

    
1563
	function find_parentqueue($interface, $qname) { return; }
1564

    
1565
	function validate_input($data, &$input_errors) {
1566
		global $altq_list_queues;
1567

    
1568
		$parent = $altq_list_queues[$this->GetInterface()];
1569

    
1570
		if ($data['bandwidth'] && !is_numeric($data['bandwidth'])) {
1571
			$input_errors[] = gettext("Bandwidth must be an integer.");
1572
		}
1573
		if ($data['bandwidth'] < 0) {
1574
			$input_errors[] = gettext("Bandwidth cannot be negative.");
1575
		}
1576
		if ($data['bandwidthtype'] == "%") {
1577
			if (($data['bandwidth'] > 100) || ($data['bandwidth'] < 0)) {
1578
				$input_errors[] = gettext("Bandwidth in percentage should be between 1 and 100.");
1579
			}
1580
		}
1581
		if ($this->CheckBandwidth($data['bandwidth'], $data['bandwidthtype']))
1582
			$input_errors[] = "The sum of child bandwidth is higher than parent.";
1583

    
1584
		if ($parent) {
1585
			if (($parent->GetScheduler() == 'PRIQ') && (!is_numeric($data['priority']) ||
1586
			    ($data['priority'] < 0) || ($data['priority'] > 15))) {
1587
				$input_errors[] = gettext("The priority must be an integer between 0 and 15.");
1588
			} elseif (in_array($parent->GetScheduler(), array('FAIRQ','CBQ')) && (!is_numeric($data['priority']) ||
1589
			    ($data['priority'] < 0) || ($data['priority'] > 7))) {
1590
				$input_errors[] = gettext("The priority must be an integer between 0 and 7.");
1591
			}
1592
			if (is_array($parent->queues)) {
1593
				foreach ($parent->queues as $q) {
1594
					if (($data['newname'] == $q->GetQname()) && ($data['name'] != $data['newname'])) { 
1595
						$input_errors[] = gettext("A queue with the same name already exists.");
1596
					}
1597
					if (in_array($parent->GetScheduler(), array('FAIRQ','PRIQ')) &&
1598
					    ($data['priority'] == $q->GetQpriority()) && ($data['name'] != $q->GetQname())) { 
1599
						$input_errors[] = sprintf(gettext("The priority %d is already used by queue %s."),
1600
								$data['priority'], $q->GetQname());
1601
					}
1602
				}
1603
			}
1604
		}
1605

    
1606
		if ($data['qlimit'] && (!is_numeric($data['qlimit']))) {
1607
				$input_errors[] = gettext("Queue limit must be an integer");
1608
		}
1609
		if ($data['qlimit'] < 0) {
1610
				$input_errors[] = gettext("Queue limit must be positive");
1611
		}
1612
		if (!empty($data['newname']) && !preg_match("/^[a-zA-Z0-9_-]*$/", $data['newname'])) {
1613
			$input_errors[] = gettext("Queue names must be alphanumeric and _ or - only.");
1614
		}
1615
		if (!empty($data['name']) && !preg_match("/^[a-zA-Z0-9_-]*$/", $data['name'])) {
1616
			$input_errors[] = gettext("Queue names must be alphanumeric and _ or - only.");
1617
		}
1618
		$default = $this->GetDefault();
1619
		if (!empty($data['default']) && altq_get_default_queue($data['interface']) && empty($default)) {
1620
			$input_errors[] = gettext("Only one default queue per interface is allowed.");
1621
		}
1622
	}
1623

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

    
1687
	function build_tree() {
1688
		$tree = " <li><a href=\"firewall_shaper.php?interface=". $this->GetInterface()."&amp;queue=". htmlspecialchars($this->GetQname())."&amp;action=show";
1689
		$tree .= "\" ";
1690
		$tmpvalue = $this->GetDefault();
1691
		if (!empty($tmpvalue)) {
1692
			$tree .= " class=\"navlnk\"";
1693
		}
1694
		$tree .= " >" . htmlspecialchars($this->GetQname()) . "</a>";
1695
		/*
1696
		 * Not needed here!
1697
		 * if (is_array($queues) {
1698
		 *	  $tree .= "<ul>";
1699
		 *	  foreach ($q as $queues)
1700
		 *		  $tree .= $queues['$q->GetName()']->build_tree();
1701
		 *	  endforeach
1702
		 *	  $tree .= "</ul>";
1703
		 * }
1704
		 */
1705

    
1706
		$tree .= "</li>";
1707

    
1708
		return $tree;
1709
	}
1710

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

    
1769
		$pfq_rule .= " \n";
1770

    
1771
		return $pfq_rule;
1772
	}
1773

    
1774
	/*
1775
	 * To return the html form to show to user
1776
	 * for getting the parameters.
1777
	 * Should do even for first time when the
1778
	 * object is created and later when we may
1779
	 * need to update it. (2)
1780
	 */
1781

    
1782
	function build_form() {
1783

    
1784
		$sform = new Form();
1785

    
1786
		$sform->setAction("firewall_shaper.php");
1787

    
1788
		$section = new Form_Section("");
1789

    
1790
		$section->addInput(new Form_Checkbox(
1791
			'enabled',
1792
			'Enable/Disable',
1793
			'Enable/disable discipline and its children',
1794
			($this->GetEnabled() == "on"),
1795
			'on'
1796
		));
1797

    
1798
		$section->addInput(new Form_Input(
1799
			'newname',
1800
			'*Name',
1801
			'text',
1802
			$this->GetQname()
1803
		))->setHelp('Enter the name of the queue here. Do not use spaces and limit the size to 15 characters.');
1804

    
1805
		$section->addInput(new Form_Input(
1806
			'name',
1807
			null,
1808
			'hidden',
1809
			$this->GetQname()
1810
		));
1811

    
1812
		if (!is_a($this, "hfsc_queue")) {
1813
			$section->addInput(new Form_Input(
1814
				'priority',
1815
				'Priority',
1816
				'number',
1817
				$this->GetQpriority(),
1818
				['min' => '0', 'max'=> '']
1819
			))->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.');
1820
		}
1821

    
1822
		$section->addInput(new Form_Input(
1823
			'qlimit',
1824
			'Queue Limit',
1825
			'number',
1826
			$this->GetQlimit()
1827
		))->setHelp('Queue limit in packets.');
1828

    
1829
		$group = new Form_Group('Scheduler options');
1830

    
1831
		if (empty($this->subqueues)) {
1832
			$group->add(new Form_Checkbox(
1833
				'default',
1834
				null,
1835
				null,
1836
				$this->GetDefault(),
1837
				'default'
1838
			))->setHelp('Default Queue');
1839
		}
1840

    
1841
		$group->add(new Form_Checkbox(
1842
			'red',
1843
			null,
1844
			null,
1845
			!empty($this->GetRed())
1846
		))->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>');
1847

    
1848
		$group->add(new Form_Checkbox(
1849
			'rio',
1850
			null,
1851
			null,
1852
			!empty($this->GetRio())
1853
		))->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>');
1854

    
1855
		$group->add(new Form_Checkbox(
1856
			'ecn',
1857
			null,
1858
			null,
1859
			!empty($this->GetEcn())
1860
		))->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>');
1861

    
1862
		$group->add(new Form_Checkbox(
1863
			'codel',
1864
			null,
1865
			null,
1866
			!empty($this->GetCodel())
1867
		))->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>');
1868

    
1869
		$group->setHelp('Select options for this queue');
1870

    
1871
		$section->add($group);
1872

    
1873
		$section->addInput(new Form_Input(
1874
			'description',
1875
			'Description',
1876
			'text',
1877
			$this->GetDescription()
1878
		));
1879

    
1880
		$sform->add($section);
1881

    
1882
		$sform->addGlobal(new Form_Input(
1883
			'interface',
1884
			null,
1885
			'hidden',
1886
			$this->GetInterface()
1887
		));
1888

    
1889
		$sform->addGlobal(new Form_Input(
1890
			'name',
1891
			null,
1892
			'hidden',
1893
			$this->GetQname()
1894
		));
1895

    
1896
		return($sform);
1897
	}
1898

    
1899
	function build_shortform() {
1900
		/* XXX: Hacks in sight. Mostly layer violations!  */
1901
		global $g, $altq_list_queues;
1902
		global $shaperIFlist;
1903

    
1904
		$altq =& $altq_list_queues[$this->GetInterface()];
1905

    
1906
		if ($altq) {
1907
			$scheduler = $altq->GetScheduler();
1908
		}
1909

    
1910
		$form = '<dl class="dl-horizontal">';
1911
		$form .= '	<dt>';
1912
		$form .= '		<a href="firewall_shaper.php?interface=' . $this->GetInterface() . '&amp;queue=' . $this->GetQname() . '&amp;action=show">' . $shaperIFlist[$this->GetInterface()] . '</a>';
1913
		$form .= '	</dt>';
1914
		$form .= '	<dd>';
1915
		$form .=		$scheduler;
1916
		$form .= '	</dd>';
1917

    
1918
		$form .= '	<dt>';
1919
		$form .=		'Bandwidth';
1920
		$form .= '	</dt>';
1921
		$form .= '	<dd>';
1922
		$form .=		$this->GetBandwidth() . '&nbsp;' . $this->GetBwscaleText();
1923
		$form .= '	</dd>';
1924

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

    
1935
		$tmpvalue = $this->GetDefault();
1936
		if (!empty($tmpvalue)) {
1937
			$form .= '	<dt>';
1938
			$form .=		'Default';
1939
			$form .= '	<dt>';
1940
			$form .= '	<dd>';
1941
			$form .=		'On';
1942
			$form .= '	</dd>';
1943
		}
1944

    
1945
			$form .= '	<dt>';
1946
			$form .= 'Delete';
1947
			$form .= '	<dt>';
1948
			$form .= '	<dd>';
1949

    
1950
			$form .= '<a class="btn btn-danger btn-xs" href="firewall_shaper_queues.php?interface=';
1951
			$form .= $this->GetInterface() . '&amp;queue=';
1952
			$form .= $this->GetQname() . '&amp;action=delete">';
1953
			$form .= '<i class="fa-solid fa-trash-can icon-embed-btn"></i>';
1954
			$form .= gettext("Delete Queue from this Interface") . '</a>';
1955

    
1956
			$form .= '	</dd>';
1957

    
1958
			$form .= '</dl>';
1959

    
1960
		return $form;
1961

    
1962
	}
1963

    
1964
	function update_altq_queue_data(&$q) {
1965
		$this->ReadConfig($q);
1966
	}
1967

    
1968
	function wconfig() {
1969
		$cflink_path = shaper_config_get_path($this->GetLink());
1970
		$cflink = config_get_path($cflink_path, []);
1971
		$cflink['name'] = $this->GetQname();
1972
		$cflink['interface'] = $this->GetInterface();
1973
		$cflink['qlimit'] = trim($this->GetQlimit());
1974
		if (empty($cflink['qlimit'])) {
1975
			unset($cflink['qlimit']);
1976
		}
1977
		$cflink['priority'] = trim($this->GetQpriority());
1978
		if (!is_numeric($cflink['priority'])) {
1979
			unset($cflink['priority']);
1980
		}
1981
		$cflink['description'] = trim($this->GetDescription());
1982
		if (empty($cflink['description'])) {
1983
			unset($cflink['description']);
1984
		}
1985
		$cflink['enabled'] = trim($this->GetEnabled());
1986
		if (empty($cflink['enabled'])) {
1987
			unset($cflink['enabled']);
1988
		}
1989
		$cflink['default'] = trim($this->GetDefault());
1990
		if (empty($cflink['default'])) {
1991
			unset($cflink['default']);
1992
		}
1993
		$cflink['red'] = trim($this->GetRed());
1994
		if (empty($cflink['red'])) {
1995
			unset($cflink['red']);
1996
		}
1997
		$cflink['codel'] = trim($this->GetCodel());
1998
		if (empty($cflink['codel'])) {
1999
			unset($cflink['codel']);
2000
		}
2001
		$cflink['rio'] = trim($this->GetRio());
2002
		if (empty($cflink['rio'])) {
2003
			unset($cflink['rio']);
2004
		}
2005
		$cflink['ecn'] = trim($this->GetEcn());
2006
		if (empty($cflink['ecn'])) {
2007
			unset($cflink['ecn']);
2008
		}
2009
		config_set_path($cflink_path, $cflink);
2010
	}
2011
}
2012

    
2013
class hfsc_queue extends priq_queue {
2014
	/* realtime */
2015
	var $realtime;
2016
	var $r_m1;
2017
	var $r_d;
2018
	var $r_m2;
2019
	/* linkshare */
2020
	var $linkshare;
2021
	var $l_m1;
2022
	var $l_d;
2023
	var $l_m2;
2024
	/* upperlimit */
2025
	var $upperlimit;
2026
	var $u_m1;
2027
	var $u_d;
2028
	var $u_m2;
2029

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

    
2118
	function &add_queue($interface, &$qname, &$path, &$input_errors) {
2119

    
2120
		if (!is_array($this->subqueues)) {
2121
			$this->subqueues = array();
2122
		}
2123
		$__tmp_q = new hfsc_queue(); $q =& $__tmp_q;
2124
		$q->SetInterface($this->GetInterface());
2125
		$q->SetParent($this);
2126
		$q->ReadConfig($qname);
2127
		$q->validate_input($qname, $input_errors);
2128

    
2129
		$q->SetEnabled("on");
2130
		$q->SetLink($path);
2131

    
2132
		$this->subqueues[$q->GetQname()] =& $q; //new hfsc_queue()
2133
		ref_on_altq_queue_list($this->GetQname(), $q->GetQname());
2134
		if (is_array($qname['queue'])) {
2135
			foreach ($qname['queue'] as $key1 => $que) {
2136
				array_push($path, $key1);
2137
				$q->add_queue($q->GetInterface(), $que, $path, $input_errors);
2138
				array_pop($path);
2139
			}
2140
		}
2141

    
2142
		return $q;
2143
	}
2144

    
2145
	function copy_queue($interface, &$cflink) {
2146

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

    
2238
		if (is_array($this->subqueues)) {
2239
			$cflinkp['queue'] = array();
2240
			foreach ($this->subqueues as $q) {
2241
				$cflink['queue'][$q->GetQname()] = array();
2242
				$q->copy_queue($interface, $cflink['queue'][$q->GetQname()]);
2243
			}
2244
		}
2245
	}
2246

    
2247
	function delete_queue() {
2248
		unref_on_altq_queue_list($this->GetQname());
2249
		cleanup_queue_from_rules($this->GetQname());
2250
		$parent =& $this->GetParent();
2251
		foreach ($this->subqueues as $q) {
2252
			$q->delete_queue();
2253
		}
2254
		shaper_config_del($this->GetLink());
2255
	}
2256

    
2257
	/*
2258
	 * Should search even its children
2259
	 */
2260
	function &find_queue($interface, $qname) {
2261
		if ($qname == $this->GetQname()) {
2262
			return $this;
2263
		}
2264

    
2265
		foreach ($this->subqueues as $q) {
2266
			$result =& $q->find_queue("", $qname);
2267
			if ($result) {
2268
				return $result;
2269
			}
2270
		}
2271
	}
2272

    
2273
	function &find_parentqueue($interface, $qname) {
2274
		if ($this->subqueues[$qname]) {
2275
			return $this;
2276
		}
2277
		foreach ($this->subqueues as $q) {
2278
			$result = $q->find_parentqueue("", $qname);
2279
			if ($result) {
2280
				return $result;
2281
			}
2282
		}
2283
	}
2284

    
2285
	function validate_input($data, &$input_errors) {
2286
		parent::validate_input($data, $input_errors);
2287

    
2288
		if (isset($data['linkshare3']) && !empty($data['linkshare3'])) {
2289
			if (isset($data['bandwidth'])) {
2290
				if (!is_numeric($data['bandwidth'])) {
2291
					$input_errors[] = gettext("Bandwidth must be an integer.");
2292
				}
2293
				if ((int)$data['bandwidth'] < 0) {
2294
					$input_errors[] = gettext("Bandwidth cannot be negative.");
2295
				}
2296
			}
2297

    
2298
			if (isset($data['bandwidthtype']) && ($data['bandwidthtype'] == "%")) {
2299
				if (($data['bandwidth'] > 100) || ($data['bandwidth'] < 0)) {
2300
					$input_errors[] = gettext("Bandwidth in percentage should be between 1 and 100.");
2301
				}
2302
			}
2303
		}
2304

    
2305
		if (!empty($data['upperlimit1']) && empty($data['upperlimit2'])) {
2306
			$input_errors[] = gettext("upperlimit service curve defined but missing (d) value");
2307
		}
2308
		if (!empty($data['upperlimit2']) && empty($data['upperlimit1'])) {
2309
			$input_errors[] = gettext("upperlimit service curve defined but missing initial bandwidth (m1) value");
2310
		}
2311
		if (!empty($data['upperlimit1']) && !is_valid_shaperbw($data['upperlimit1'])) {
2312
			$input_errors[] = gettext("upperlimit m1 value needs to be Kb, Mb, Gb, or %");
2313
		}
2314
		if (!empty($data['upperlimit2']) && !is_numeric($data['upperlimit2'])) {
2315
			$input_errors[] = gettext("upperlimit d value needs to be numeric");
2316
		}
2317
		if (!empty($data['upperlimit3']) && !is_valid_shaperbw($data['upperlimit3'])) {
2318
			$input_errors[] = gettext("upperlimit m2 value needs to be Kb, Mb, Gb, or %");
2319
		}
2320

    
2321
		/*
2322
		if (isset($data['upperlimit']) && $data['upperlimit3'] <> "" && $data['upperlimit1'] <> "") {
2323
			$bw_1 = get_hfsc_bandwidth($this, $data['upperlimit1']);
2324
			$bw_2 = get_hfsc_bandwidth($this, $data['upperlimit3']);
2325
			if (floatval($bw_1) < floatval($bw_2)) {
2326
				$input_errors[] = ("upperlimit m1 cannot be smaller than m2");
2327
			}
2328

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

    
2356
		/*
2357
		if (isset($data['linkshare']) && $data['linkshare3'] <> "" && $data['linkshare1'] <> "" && 0) {
2358
			$bw_1 = get_hfsc_bandwidth($this, $data['linkshare1']);
2359
			$bw_2 = get_hfsc_bandwidth($this, $data['linkshare3']);
2360
			if (floatval($bw_1) < floatval($bw_2)) {
2361
				$input_errors[] = ("linkshare m1 cannot be smaller than m2");
2362
			}
2363

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

    
2370
		if ($data['realtime1'] <> "" && !is_valid_shaperbw($data['realtime1'])) {
2371
			$input_errors[] = gettext("realtime m1 value needs to be Kb, Mb, Gb, or %");
2372
		}
2373
		if ($data['realtime2'] <> "" && !is_numeric($data['realtime2'])) {
2374
			$input_errors[] = gettext("realtime d value needs to be numeric");
2375
		}
2376
		if ($data['realtime3'] <> "" && !is_valid_shaperbw($data['realtime3'])) {
2377
			$input_errors[] = gettext("realtime m2 value needs to be Kb, Mb, Gb, or %");
2378
		}
2379

    
2380
		/*
2381
		if (isset($data['realtime']) && $data['realtime3'] <> "" && $data['realtime1'] <> "" && 0) {
2382
			$bw_1 = get_hfsc_bandwidth($this, $data['realtime1']);
2383
			$bw_2 = get_hfsc_bandwidth($this, $data['realtime3']);
2384
			if (floatval($bw_1) < floatval($bw_2)) {
2385
				$input_errors[] = ("realtime m1 cannot be smaller than m2");
2386
			}
2387

    
2388
			if (get_interface_bandwidth($this) < (0.8 * (floatval($bw_1) + floatval($bw_2)))) {
2389
				$input_errors[] = ("realtime specification exceeds 80% of allowable allocation.");
2390
			}
2391
		}
2392
		*/
2393
	}
2394

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

    
2450
	function build_tree() {
2451
		$tree = " <li><a href=\"firewall_shaper.php?interface=" . $this->GetInterface() ."&amp;queue=" . htmlspecialchars($this->GetQname())."&amp;action=show";
2452
		$tree .= "\" ";
2453
		$tmpvalue = $this->GetDefault();
2454
		if (!empty($tmpvalue)) {
2455
			$tree .= " class=\"navlnk\"";
2456
		}
2457
		$tree .= " >" . htmlspecialchars($this->GetQname()) . "</a>";
2458
		if (is_array($this->subqueues)) {
2459
			$tree .= "<ul>";
2460
			foreach ($this->subqueues as $q) {
2461
				$tree .= $q->build_tree();
2462
			}
2463
			$tree .= "</ul>";
2464
		}
2465
		$tree .= "</li>";
2466
		return $tree;
2467
	}
2468

    
2469
	/* Even this should take children into consideration */
2470
	function build_rules(&$default = false) {
2471

    
2472
		$pfq_rule = " queue ". $this->qname;
2473
		if ($this->GetInterface()) {
2474
			$pfq_rule .= " on ".get_real_interface($this->GetInterface());
2475
		}
2476
		if ($this->GetBandwidth() && $this->GetBwscale()) {
2477
			$pfq_rule .= " bandwidth ".trim($this->GetBandwidth()).$this->GetBwscale();
2478
		}
2479

    
2480
		$tmpvalue = $this->GetQlimit();
2481
		if (!empty($tmpvalue)) {
2482
			$pfq_rule .= " qlimit " . $this->GetQlimit();
2483
		}
2484
		if ($this->GetDefault() || $this->GetRed() || $this->GetRio() || $this->GetEcn() || $this->GetCodel() || $this->GetRealtime() <> "" || $this->GetLinkshare() <> "" || $this->GetUpperlimit() <> "") {
2485
			$pfq_rule .= " hfsc ( ";
2486
			$tmpvalue = $this->GetRed();
2487
			if (!empty($tmpvalue)) {
2488
				$comma = 1;
2489
				$pfq_rule .= " red ";
2490
			}
2491

    
2492
			$tmpvalue = $this->GetRio();
2493
			if (!empty($tmpvalue)) {
2494
				if ($comma) {
2495
					$pfq_rule .= " ,";
2496
				}
2497
				$comma = 1;
2498
				$pfq_rule .= " rio ";
2499
			}
2500
			$tmpvalue = $this->GetEcn();
2501
			if (!empty($tmpvalue)) {
2502
				if ($comma) {
2503
					$pfq_rule .= " ,";
2504
				}
2505
				$comma = 1;
2506
				$pfq_rule .= " ecn ";
2507
			}
2508
			$tmpvalue = $this->GetCodel();
2509
			if (!empty($tmpvalue)) {
2510
				if ($comma) {
2511
					$pfq_rule .= " ,";
2512
				}
2513
				$comma = 1;
2514
				$pfq_rule .= " codel ";
2515
			}
2516
			$tmpvalue = $this->GetDefault();
2517
			if (!empty($tmpvalue)) {
2518
				if ($comma) {
2519
					$pfq_rule .= " ,";
2520
				}
2521
				$comma = 1;
2522
				$pfq_rule .= " default ";
2523
				$default = true;
2524
			}
2525

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

    
2577
		$pfq_rule .= " \n";
2578

    
2579
		return $pfq_rule;
2580
	}
2581

    
2582
	function build_javascript() {
2583

    
2584
		$javascript = <<<EOJS
2585
<script type="text/javascript">
2586
//<![CDATA[
2587
	events.push(function(){
2588

    
2589
		// Disables the specified input element
2590
		function disableInput(id, disable) {
2591
			$('#' + id).prop("disabled", disable);
2592
		}
2593

    
2594
		// Upperlimit
2595
		function enable_upperlimit() {
2596
			disableInput('upperlimit1', !$('#upperlimit').prop('checked'));
2597
			disableInput('upperlimit2', !$('#upperlimit').prop('checked'));
2598
			disableInput('upperlimit3', !$('#upperlimit').prop('checked'));
2599
		}
2600

    
2601
		$('#upperlimit').click(function () {
2602
			enable_upperlimit();
2603
		});
2604

    
2605
		enable_upperlimit();
2606

    
2607
		// realtime
2608
		function enable_realtime() {
2609
			disableInput('realtime1', !$('#realtime').prop('checked'));
2610
			disableInput('realtime2', !$('#realtime').prop('checked'));
2611
			disableInput('realtime3', !$('#realtime').prop('checked'));
2612
		}
2613

    
2614
		$('#realtime').click(function () {
2615
			enable_realtime();
2616
		});
2617

    
2618
		enable_realtime();
2619

    
2620
		// linkshare
2621
		function enable_linkshare() {
2622
			disableInput('linkshare1', !$('#linkshare').prop('checked'));
2623
			disableInput('linkshare2', !$('#linkshare').prop('checked'));
2624
			disableInput('linkshare3', !$('#linkshare').prop('checked'));
2625
		}
2626

    
2627
		$('#linkshare').click(function () {
2628
			enable_linkshare();
2629
		});
2630

    
2631
		enable_linkshare();
2632
	});
2633
//]]>
2634
</script>
2635
EOJS;
2636

    
2637
		return $javascript;
2638
	}
2639

    
2640
	function build_form() {
2641

    
2642
		$sform = parent::build_form();
2643

    
2644
		$section = new Form_Section('Service Curve (sc)');
2645

    
2646
		$group = new Form_Group('Bandwidth');
2647

    
2648
		$group->add(new Form_Input(
2649
			'bandwidth',
2650
			null,
2651
			'number',
2652
			$this->GetBandwidth(),
2653
			['step' => 'any', 'min' => '0.000']
2654
		));
2655

    
2656
		$group->add(new Form_Select(
2657
			'bandwidthtype',
2658
			null,
2659
			$this->FormGetBwscale(),
2660
			array('Kb' => 'Kbit/s',
2661
				  'Mb' => 'Mbit/s',
2662
				  'Gb' => 'Gbit/s',
2663
				  'b' => 'Bit/s',
2664
				  '%' => '%')
2665
		));
2666

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

    
2669
		$section->add($group);
2670

    
2671
		$group = new Form_Group('Max bandwidth for queue.');
2672

    
2673
		$group->add(new Form_Checkbox(
2674
			'upperlimit',
2675
			null,
2676
			'Upper Limit',
2677
			($this->GetUpperlimit()<> "")
2678
		));
2679

    
2680
		$group->add(new Form_Input(
2681
			'upperlimit1',
2682
			null,
2683
			'text',
2684
			$this->GetU_m1()
2685
		))->setHelp('m1');
2686

    
2687
		$group->add(new Form_Input(
2688
			'upperlimit2',
2689
			null,
2690
			'text',
2691
			$this->GetU_d()
2692
		))->setHelp('d');
2693

    
2694
		$group->add(new Form_Input(
2695
			'upperlimit3',
2696
			null,
2697
			'text',
2698
			$this->GetU_m2()
2699
		))->setHelp('m2');
2700

    
2701

    
2702
		$section->add($group);
2703

    
2704
		$group = new Form_Group('Min bandwidth for queue.');
2705

    
2706
		$group->add(new Form_Checkbox(
2707
			'realtime',
2708
			null,
2709
			'Real Time',
2710
			($this->GetRealtime()<> "")
2711
		));
2712

    
2713
		$group->add(new Form_Input(
2714
			'realtime1',
2715
			null,
2716
			'text',
2717
			$this->GetR_m1()
2718
		))->setHelp('m1');
2719

    
2720
		$group->add(new Form_Input(
2721
			'realtime2',
2722
			null,
2723
			'text',
2724
			$this->GetR_d()
2725
		))->setHelp('d');
2726

    
2727
		$group->add(new Form_Input(
2728
			'realtime3',
2729
			null,
2730
			'text',
2731
			$this->GetR_m2()
2732
		))->setHelp('m2');
2733

    
2734
		$section->add($group);
2735

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

    
2738
		$group->add(new Form_Checkbox(
2739
			'linkshare',
2740
			null,
2741
			'Link Share',
2742
			($this->GetLinkshare()<> "")
2743
		));
2744

    
2745
		$group->add(new Form_Input(
2746
			'linkshare1',
2747
			null,
2748
			'text',
2749
			$this->GetL_m1()
2750
		))->setHelp('m1');
2751

    
2752
		$group->add(new Form_Input(
2753
			'linkshare2',
2754
			null,
2755
			'text',
2756
			$this->GetL_d()
2757
		))->setHelp('d');
2758

    
2759
		$group->add(new Form_Input(
2760
			'linkshare3',
2761
			null,
2762
			'text',
2763
			$this->GetL_m2()
2764
		))->setHelp('m2');
2765

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

    
2772
		$section->add($group);
2773

    
2774
		$sform->add($section);
2775

    
2776
		return($sform);
2777
	}
2778

    
2779
	function update_altq_queue_data(&$data) {
2780
		$this->ReadConfig($data);
2781
	}
2782

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

    
2899
class cbq_queue extends priq_queue {
2900
	var $qborrow = "";
2901

    
2902
	function GetBorrow() {
2903
		return $this->qborrow;
2904
	}
2905
	function SetBorrow($borrow) {
2906
		$this->qborrow = $borrow;
2907
	}
2908
	function CanHaveChildren() {
2909
		return true;
2910
	}
2911

    
2912
	function &add_queue($interface, &$qname, &$path, &$input_errors) {
2913

    
2914
		if (!is_array($this->subqueues)) {
2915
			$this->subqueues = array();
2916
		}
2917
		$__tmp_q = new cbq_queue(); $q =& $__tmp_q;
2918
		$q->SetInterface($this->GetInterface());
2919
		$q->SetParent($this);
2920
		$q->ReadConfig($qname);
2921
		$q->validate_input($qname, $input_errors);
2922

    
2923
		$q->SetEnabled("on");
2924
		$q->SetLink($path);
2925
		$this->subqueues[$q->GetQName()] = &$q;
2926
		ref_on_altq_queue_list($this->GetQname(), $q->GetQname());
2927
		if (is_array($qname['queue'])) {
2928
			foreach ($qname['queue'] as $key1 => $que) {
2929
				array_push($path, $key1);
2930
				$q->add_queue($q->GetInterface(), $que, $path, $input_errors);
2931
				array_pop($path);
2932
			}
2933
		}
2934

    
2935
		return $q;
2936
	}
2937

    
2938
	function copy_queue($interface, &$cflink) {
2939

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

    
2989
	/*
2990
	 * Should search even its children
2991
	 */
2992
	function &find_queue($interface, $qname) {
2993
		if ($qname == $this->GetQname()) {
2994
			return $this;
2995
		}
2996
		foreach ($this->subqueues as $q) {
2997
			$result =& $q->find_queue("", $qname);
2998
			if ($result) {
2999
				return $result;
3000
			}
3001
		}
3002
	}
3003

    
3004
	function &find_parentqueue($interface, $qname) {
3005
		if ($this->subqueues[$qname]) {
3006
			return $this;
3007
		}
3008
		foreach ($this->subqueues as $q) {
3009
			$result = $q->find_parentqueue("", $qname);
3010
			if ($result) {
3011
				return $result;
3012
			}
3013
		}
3014
	}
3015

    
3016
	function delete_queue() {
3017
		unref_on_altq_queue_list($this->GetQname());
3018
		cleanup_queue_from_rules($this->GetQname());
3019
		foreach ($this->subqueues as $q) {
3020
			$q->delete_queue();
3021
		}
3022
		shaper_config_del($this->GetLink());
3023
	}
3024

    
3025
	function validate_input($data, &$input_errors) {
3026
		parent::validate_input($data, $input_errors);
3027

    
3028
		if ($data['priority'] > 7) {
3029
			$input_errors[] = gettext("Priority must be an integer between 0 and 7.");
3030
		}
3031

    
3032
		$parent = $this->GetParent();
3033
		if (method_exists($parent, 'GetParent') && ($parent->GetBorrow() != "on") &&
3034
		    ($data['borrow'] == 'yes')) {
3035
			$input_errors[] = gettext("You cannot set 'Borrow' if the parent queue also does not borrow.");
3036
		}
3037
	}
3038

    
3039
	function ReadConfig(&$q) {
3040
		parent::ReadConfig($q);
3041
		if (!empty($q['borrow'])) {
3042
			$this->SetBorrow("on");
3043
		} else {
3044
			$this->SetBorrow("");
3045
		}
3046
	}
3047

    
3048
	function build_javascript() {
3049
		return parent::build_javascript();
3050
	}
3051

    
3052
	function build_tree() {
3053
		$tree = " <li><a href=\"firewall_shaper.php?interface=" . $this->GetInterface()."&amp;queue=" . htmlspecialchars($this->GetQname())."&amp;action=show";
3054
		$tree .= "\" ";
3055
		$tmpvalue = trim($this->GetDefault());
3056
		if (!empty($tmpvalue)) {
3057
			$tree .= " class=\"navlnk\"";
3058
		}
3059
		$tree .= " >" . htmlspecialchars($this->GetQname()) . "</a>";
3060
		if (is_array($this->subqueues)) {
3061
			$tree .= "<ul>";
3062
			foreach ($this->subqueues as $q) {
3063
				$tree .= $q->build_tree();
3064
			}
3065
			$tree .= "</ul>";
3066
		}
3067
		$tree .= "</li>";
3068
		return $tree;
3069
	}
3070

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

    
3151
		$pfq_rule .= " \n";
3152
		return $pfq_rule;
3153
	}
3154

    
3155
	function build_form() {
3156
		$sform = parent::build_form();
3157

    
3158
		$section = new Form_Section('NOTITLE');
3159

    
3160
		$group = new Form_Group('Bandwidth');
3161

    
3162
		$group->add(new Form_Input(
3163
			'bandwidth',
3164
			null,
3165
			'number',
3166
			$this->GetBandwidth()
3167
		));
3168

    
3169
		$group->add(new Form_Select(
3170
			'bandwidthtype',
3171
			null,
3172
			$this->FormGetBwscale(),
3173
			array('Kb' => 'Kbit/s',
3174
				  'Mb' => 'Mbit/s',
3175
				  'Gb' => 'Gbit/s',
3176
				  'b' => 'Bit/s',
3177
				  '%' => '%')
3178
		));
3179

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

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

    
3184
		$section->addInput(new Form_Checkbox(
3185
			'borrow',
3186
			'Scheduler option',
3187
			'Borrow from other queues when available',
3188
			($this->GetBorrow() == "on")
3189
		));
3190

    
3191
		$sform->add($section);
3192

    
3193
		return $sform;
3194
	}
3195

    
3196
	function update_altq_queue_data(&$data) {
3197
		$this->ReadConfig($data);
3198
	}
3199

    
3200
	function wconfig() {
3201
		$cflink_path = shaper_config_get_path($this->GetLink());
3202
		$cflink = config_get_path($cflink_path, []);
3203
		$cflink['interface'] = $this->GetInterface();
3204
		$cflink['qlimit'] = trim($this->GetQlimit());
3205
		if (empty($cflink['qlimit'])) {
3206
			unset($cflink['qlimit']);
3207
		}
3208
		$cflink['priority'] = $this->GetQpriority();
3209
		if (!is_numeric($cflink['priority'])) {
3210
			unset($cflink['priority']);
3211
		}
3212
		$cflink['name'] = $this->GetQname();
3213
		$cflink['description'] = $this->GetDescription();
3214
		if (empty($cflink['description'])) {
3215
			unset($cflink['description']);
3216
		}
3217
		$cflink['bandwidth'] = $this->GetBandwidth();
3218
		$cflink['bandwidthtype'] = $this->GetBwscale();
3219
		$cflink['enabled'] = trim($this->GetEnabled());
3220
		if (empty($cflink['enabled'])) {
3221
			unset($cflink['enabled']);
3222
		}
3223
		$cflink['default'] = trim($this->GetDefault());
3224
		if (empty($cflink['default'])) {
3225
			unset($cflink['default']);
3226
		}
3227
		$cflink['red'] = trim($this->GetRed());
3228
		if (empty($cflink['red'])) {
3229
			unset($cflink['red']);
3230
		}
3231
		$cflink['rio'] = trim($this->GetRio());
3232
		if (empty($cflink['rio'])) {
3233
			unset($cflink['rio']);
3234
		}
3235
		$cflink['ecn'] = trim($this->GetEcn());
3236
		if (empty($cflink['ecn'])) {
3237
			unset($cflink['ecn']);
3238
		}
3239
		$cflink['codel'] = trim($this->GetCodel());
3240
		if (empty($cflink['codel'])) {
3241
			unset($cflink['codel']);
3242
		}
3243
		$cflink['borrow'] = trim($this->GetBorrow());
3244
		if (empty($cflink['borrow'])) {
3245
			unset($cflink['borrow']);
3246
		}
3247
		config_set_path($cflink_path, $cflink);
3248
	}
3249
}
3250

    
3251
class fairq_queue extends priq_queue {
3252
	var $hogs;
3253
	var $buckets;
3254

    
3255
	function GetBuckets() {
3256
		return $this->buckets;
3257
	}
3258
	function SetBuckets($buckets) {
3259
		$this->buckets = $buckets;
3260
	}
3261
	function GetHogs() {
3262
		return $this->hogs;
3263
	}
3264
	function SetHogs($hogs) {
3265
		$this->hogs = $hogs;
3266
	}
3267
	function CanHaveChildren() {
3268
		return false;
3269
	}
3270

    
3271

    
3272
	function copy_queue($interface, &$cflink) {
3273
		$cflink['interface'] = $interface;
3274
		$cflink['qlimit'] = $this->GetQlimit();
3275
		$cflink['priority'] = $this->GetQpriority();
3276
		$cflink['name'] = $this->GetQname();
3277
		$cflink['description'] = $this->GetDescription();
3278
		$cflink['bandwidth'] = $this->GetBandwidth();
3279
		$cflink['bandwidthtype'] = $this->GetBwscale();
3280
		$cflink['enabled'] = $this->GetEnabled();
3281
		$cflink['default'] = $this->GetDefault();
3282
		$cflink['red'] = $this->GetRed();
3283
		$cflink['rio'] = $this->GetRio();
3284
		$cflink['ecn'] = $this->GetEcn();
3285
		$cflink['buckets'] = $this->GetBuckets();
3286
		$cflink['hogs'] = $this->GetHogs();
3287
	}
3288

    
3289
	/*
3290
	 * Should search even its children
3291
	 */
3292
	function &find_queue($interface, $qname) {
3293
		if ($qname == $this->GetQname()) {
3294
			return $this;
3295
		}
3296
	}
3297

    
3298
	function find_parentqueue($interface, $qname) { return; }
3299

    
3300
	function delete_queue() {
3301
		unref_on_altq_queue_list($this->GetQname());
3302
		cleanup_queue_from_rules($this->GetQname());
3303
		shaper_config_del($this->GetLink());
3304
	}
3305

    
3306
	function validate_input($data, &$input_errors) {
3307
		parent::validate_input($data, $input_errors);
3308

    
3309
		if ($data['priority'] > 7) {
3310
				$input_errors[] = gettext("Priority must be an integer between 0 and 7.");
3311
		}
3312
	}
3313

    
3314
	function ReadConfig(&$q) {
3315
		parent::ReadConfig($q);
3316
		if (!empty($q['buckets'])) {
3317
			$this->SetBuckets($q['buckets']);
3318
		} else {
3319
			$this->SetBuckets("");
3320
		}
3321
		if (!empty($q['hogs']) && is_valid_shaperbw($q['hogs'])) {
3322
			$this->SetHogs($q['hogs']);
3323
		} else {
3324
			$this->SetHogs("");
3325
		}
3326
	}
3327

    
3328
	function build_javascript() {
3329
		return parent::build_javascript();
3330
	}
3331

    
3332
	function build_tree() {
3333
		$tree = " <li><a href=\"firewall_shaper.php?interface=" .
3334
		$this->GetInterface()."&amp;queue=" . htmlspecialchars($this->GetQname())."&amp;action=show";
3335
		$tree .= "\" ";
3336
		$tmpvalue = trim($this->GetDefault());
3337
		if (!empty($tmpvalue)) {
3338
			$tree .= " class=\"navlnk\"";
3339
		}
3340
		$tree .= " >" . htmlspecialchars($this->GetQname()) . "</a>";
3341
		$tree .= "</li>";
3342
		return $tree;
3343
	}
3344

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

    
3417
		$pfq_rule .= " \n";
3418
		return $pfq_rule;
3419
	}
3420

    
3421
	function build_form() {
3422
		$form = parent::build_form();
3423

    
3424
		$section = new Form_Section('');
3425

    
3426
		$group = new Form_Group('Bandwidth');
3427

    
3428
		$group->add(new Form_Input(
3429
			'bandwidth',
3430
			null,
3431
			'number',
3432
			$this->GetBandwidth()
3433
		));
3434

    
3435
		$group->add(new Form_Select(
3436
			'bandwidthtype',
3437
			null,
3438
			$this->FormGetBwscale(),
3439
			array('Kb' => 'Kbit/s',
3440
				  'Mb' => 'Mbit/s',
3441
				  'Gb' => 'Gbit/s',
3442
				  'b' => 'Bit/s',
3443
				  '%' => '%')
3444
		));
3445

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

    
3448
		$section->add($group);
3449

    
3450
		$section->addInput(new Form_Input(
3451
			'buckets',
3452
			'Scheduler specific options',
3453
			'text',
3454
			$this->GetBuckets()
3455
		))->setHelp('Number of buckets available');
3456

    
3457
		$section->addInput(new Form_Input(
3458
			'hogs',
3459
			'',
3460
			'text',
3461
			$this->GetHogs()
3462
			))->setHelp('Bandwidth limit for hosts to not saturate link');
3463

    
3464
		$form->add($section);
3465
		return $form;
3466
	}
3467

    
3468
	function update_altq_queue_data(&$data) {
3469
		$this->ReadConfig($data);
3470
	}
3471

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

    
3527

    
3528
/*
3529
 * dummynet(4) wrappers.
3530
 */
3531

    
3532

    
3533
/*
3534
 * List of respective objects!
3535
 */
3536
$dummynet_pipe_list = array();
3537

    
3538
class dummynet_class {
3539
	var $qname;
3540
	var $qnumber; /* dummynet(4) uses numbers instead of names; maybe integrate with pf the same as altq does?! */
3541
	var $qlimit;
3542
	var $description;
3543
	var $qenabled;
3544
	var $link;
3545
	var $qparent; /* link to upper class so we do things easily on WF2Q+ rule creation */
3546
	var $plr;
3547

    
3548
	var $buckets;
3549
	/* mask parameters */
3550
	var $mask;
3551
	var $noerror;
3552
	var $firsttime;
3553

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

    
3631
	function build_javascript() {
3632
		$javascript .= "<script type=\"text/javascript\">\n";
3633
		$javascript .= "//<![CDATA[\n";
3634
		$javascript .= "function enable_maskbits(enable_over) {\n";
3635
		$javascript .= "var e = document.getElementById(\"mask\");\n";
3636
		$javascript .= "if ((e.options[e.selectedIndex].text == \"none\") || enable_over) {\n";
3637
		$javascript .= "document.iform.maskbits.disabled = 1;\n";
3638
		$javascript .= "document.iform.maskbits.value = \"\";\n";
3639
		$javascript .= "document.iform.maskbitsv6.disabled = 1;\n";
3640
		$javascript .= "document.iform.maskbitsv6.value = \"\";\n";
3641
		$javascript .= "} else {\n";
3642
		$javascript .= "document.iform.maskbits.disabled = 0;\n";
3643
		$javascript .= "document.iform.maskbitsv6.disabled = 0;\n";
3644
		$javascript .= "}}\n";
3645
		$javascript .= "//]]>\n";
3646
		$javascript .= "</script>\n";
3647
		return $javascript;
3648
	}
3649

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

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

    
3717
}
3718

    
3719
class dnpipe_class extends dummynet_class {
3720
	var $delay;
3721
	var $qbandwidth = array();
3722
	var $qbandwidthtype;
3723
	var $qburst;
3724

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

    
3803
		/* This is here to help on form building and building rules/lists */
3804
	var $subqueues = array();
3805

    
3806
	function CanHaveChildren() {
3807
		return true;
3808
	}
3809
	function SetDelay($delay) {
3810
		$this->delay = $delay;
3811
	}
3812
	function GetDelay() {
3813
		return $this->delay;
3814
	}
3815
	function delete_queue() {
3816
		cleanup_dnqueue_from_rules($this->GetQname());
3817
		foreach ($this->subqueues as $q) {
3818
			$q->delete_queue();
3819
		}
3820
		shaper_dn_config_del($this->GetLink());
3821
		mwexec("/sbin/dnctl pipe delete " . $this->GetNumber());
3822
		mwexec("/sbin/dnctl sched delete " . $this->GetNumber());
3823
	}
3824
	function GetBandwidth() {
3825
		return $this->qbandwidth;
3826
	}
3827
	function SetBandwidth($bandwidth) {
3828
		$this->qbandwidth = $bandwidth;
3829
	}
3830
	function GetBurst() {
3831
		return $this->qburst;
3832
	}
3833
	function SetBurst($burst) {
3834
		$this->qburst = $burst;
3835
	}
3836

    
3837
	function &add_queue($interface, &$queue, &$path, &$input_errors) {
3838

    
3839
		if (!is_array($this->subqueues)) {
3840
			$this->subqueues = array();
3841
		}
3842

    
3843
		$__tmp_q = new dnqueue_class(); $q =& $__tmp_q;
3844
		$q->SetLink($path);
3845
		$q->SetEnabled("on");
3846
		$q->SetPipe($this->GetQname());
3847
		$q->SetParent($this);
3848
		$q->ReadConfig($queue);
3849
		$q->validate_input($queue, $input_errors);
3850

    
3851
		if (!is_array($input_errors)) {
3852
			$input_errors = array();
3853
		}
3854

    
3855
		if (count($input_errors)) {
3856
			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)));
3857
			return $q;
3858
		}
3859
		$number = dnqueue_find_nextnumber();
3860
		$q->SetNumber($number);
3861
		$this->subqueues[$q->GetQname()] = &$q;
3862

    
3863
		return $q;
3864
	}
3865

    
3866
	function &get_queue_list(&$q = null) {
3867
		$qlist = array();
3868

    
3869
		$qlist[$this->GetQname()] = $this->GetNumber();
3870
		if (is_array($this->subqueues)) {
3871
			foreach ($this->subqueues as $queue) {
3872
				$queue->get_queue_list($qlist);
3873
			}
3874
		}
3875
		return $qlist;
3876
	}
3877

    
3878
	/*
3879
	 * Should search even its children
3880
	 */
3881
	function &find_queue($pipe, $qname) {
3882
		if ($qname == $this->GetQname()) {
3883
			return $this;
3884
		}
3885
		foreach ($this->subqueues as $q) {
3886
			$result =& $q->find_queue("", $qname);
3887
			if ($result) {
3888
				return $result;
3889
			}
3890
		}
3891
	}
3892

    
3893
	function &find_parentqueue($pipe, $qname) {
3894
		return NULL;
3895
	}
3896

    
3897
	function validate_input($data, &$input_errors) {
3898
		parent::validate_input($data, $input_errors);
3899

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

    
3939
		/* Limiter patch */
3940
		$selectedScheduler = getSchedulers()[$data['sched']];
3941
		if (!$selectedScheduler) {
3942
			$input_errors[] = gettext("Selected scheduler not recognized.");
3943
		}
3944
		$selectedAqm = getAQMs()[$data['aqm']];
3945
		if (!empty($data['aqm']) && !$selectedAqm) {
3946
			$input_errors[] = gettext("Selected AQM not recognized.");
3947
		}
3948
		/* End limiter patch */
3949
	}
3950

    
3951
	function ReadConfig(&$q) {
3952
		if (!empty($q['name']) && !empty($q['newname']) && $q['name'] != $q['newname']) {
3953
			$this->SetQname($q['newname']);
3954
			rename_dnqueue_in_rules($q['name'], $q['newname']);
3955
		} else if (!empty($q['newname'])) {
3956
			$this->SetQname($q['newname']);
3957
		} else {
3958
			$this->SetQname($q['name']);
3959
		}
3960
		$this->SetNumber($q['number']);
3961

    
3962
		if (!empty($_POST)) {
3963
			$bandwidth = array();
3964
			/* XXX: Really no better way? */
3965
			for ($i = 0; $i < 2900; $i++) {
3966
				if (isset($q["bandwidth{$i}"]) && $q["bandwidth{$i}"] <> "") {
3967
					$bw = array();
3968
					$bw['bw'] = $q["bandwidth{$i}"];
3969
					$bw['burst'] = $q["burst{$i}"];
3970
					if (isset($q["bwtype{$i}"]) && $q["bwtype{$i}"]) {
3971
						$bw['bwscale'] = $q["bwtype{$i}"];
3972
					}
3973
					if (isset($q["bwsched{$i}"]) && $q["bwsched{$i}"]) {
3974
						$bw['bwsched'] = $q["bwsched{$i}"];
3975
					}
3976
					$bandwidth[] = $bw;
3977
				}
3978
			}
3979
			$this->SetBandwidth($bandwidth);
3980
		}
3981

    
3982
		if (is_array($q['bandwidth']) && is_array($q['bandwidth']['item'])) {
3983
			$this->SetBandwidth($q['bandwidth']['item']);
3984
			$this->SetBurst($q['burst']['item']);
3985
		}
3986

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

    
4030
		/* Limiter patch */
4031
		if (isset($q['aqm']) && $q['aqm'] <> "") {
4032
				$this->SetAQM($q['aqm']);
4033
		} else {
4034
				$this->SetAQM('droptail');
4035
		}
4036
		// parse AQM arguments
4037
		$aqm_map = getAQMs();
4038
		$selectedParameters = $aqm_map[$this->getAQM()]["parameters"];
4039
		if (is_array($selectedParameters)) {
4040
			foreach ($selectedParameters as $key => $value) {
4041
				$config_key = 'param_' . $this->GetAQM() . '_' . $key;
4042
				if (isset($q[$config_key]) && $q[$config_key] <> "") {
4043
					$this->SetAQMParameter($key, $q[$config_key]);
4044
				} else {
4045
					$this->SetAQMParameter($key, $value["default"]);
4046
				}
4047
			}
4048
		}
4049

    
4050
		if (isset($q['sched']) && $q['sched'] <> "") {
4051
				$this->SetScheduler($q['sched']);
4052
		} else {
4053
				$this->SetScheduler('fifo');
4054
		}
4055
		$scheduler_map = getSchedulers();
4056
		$selectedParameters = $scheduler_map[$this->getScheduler()]["parameters"];
4057
		if (is_array($selectedParameters)) {
4058
			foreach ($selectedParameters as $key => $value) {
4059
				$config_key = 'param_' . $this->GetScheduler() . '_' . $key;
4060
				if (isset($q[$config_key]) && $q[$config_key] <> "") {
4061
					$this->SetSchedulerParameter($key, $q[$config_key]);
4062
				} else {
4063
					$this->SetSchedulerParameter($key, $value["default"]);
4064
				}
4065
			}
4066
		}
4067

    
4068
		// ecn flag
4069
		$this->SetECN($q['ecn']);
4070
		// PIE Flags.
4071
		$this->SetPIE_ONOFF($q['pie_onoff']);
4072
		$this->SetPIE_CAPDROP($q['pie_capdrop']);
4073
		$this->SetPIE_QDELAY($q['pie_qdelay']);
4074
		$this->SetPIE_PDERAND($q['pie_pderand']);
4075
		/* End limiter patch */
4076
	}
4077

    
4078
	function build_tree() {
4079
		$tree = " <li><a href=\"firewall_shaper_vinterface.php?pipe=" . htmlspecialchars($this->GetQname()) ."&amp;queue=".htmlspecialchars($this->GetQname()) ."&amp;action=show\">";
4080
		$tree .= htmlspecialchars($this->GetQname()) . "</a>";
4081
		if (is_array($this->subqueues)) {
4082
			$tree .= "<ul>";
4083
			foreach ($this->subqueues as $q) {
4084
				$tree .= $q->build_tree();
4085
			}
4086
			$tree .= "</ul>";
4087
		}
4088
		$tree .= "</li>";
4089

    
4090
		return $tree;
4091
	}
4092

    
4093
	function build_rules() {
4094
		global $time_based_rules;
4095

    
4096
		if ($this->GetEnabled() == "") {
4097
			return;
4098
		}
4099

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

    
4143
		if ($this->GetQlimit()) {
4144
			$pfq_rule .= " queue " . $this->GetQlimit();
4145
		}
4146
		if ($this->GetPlr()) {
4147
			$pfq_rule .= " plr " . $this->GetPlr();
4148
		}
4149
		if ($this->GetBuckets()) {
4150
			$pfq_rule .= " buckets " . $this->GetBuckets();
4151
		}
4152
		if ($this->GetDelay()) {
4153
			$pfq_rule .= " delay " . $this->GetDelay();
4154
		}
4155
		$this->build_mask_rules($pfq_rule);
4156

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

    
4214
		if (!empty($this->subqueues) && count($this->subqueues) > 0) {
4215
			foreach ($this->subqueues as $q) {
4216
				$pfq_rule .= $q->build_rules();
4217
			}
4218
		}
4219

    
4220
		$pfq_rule .= " \n";
4221

    
4222
		return $pfq_rule;
4223
	}
4224

    
4225
	function update_dn_data(&$data) {
4226
		$this->ReadConfig($data);
4227
	}
4228

    
4229
	function build_javascript() {
4230
		global $g;
4231

    
4232
		$javasr = parent::build_javascript();
4233

    
4234
		//build list of schedules
4235
		$schedules = "<option value='none'>none</option>";
4236
		foreach (config_get_path('schedules/schedule', []) as $schedule) {
4237
			if ($schedule['name'] <> "") {
4238
				$schedules .= "<option value='{$schedule['name']}'>{$schedule['name']}</option>";
4239
			}
4240
		}
4241
		$bwopt = "";
4242
		foreach (array("Kb" => "Kbit/s", "Mb" => "Mbit/s", "Gb" => "Gbit/s", "b" => "Bit/s") as $bwidx => $bw) {
4243
			$bwopt .= "<option value='{$bwidx}'>{$bw}</option>";
4244
		}
4245

    
4246
		$javasr .= <<<EOD
4247
<script type='text/javascript'>
4248
//<![CDATA[
4249
var addBwRowTo = (function() {
4250

    
4251
	return (function (tableId) {
4252

    
4253
	var table = document.getElementById(tableId);
4254
	var totalrows = table.rows.length -1;
4255

    
4256
	var row = table.insertRow(totalrows + 1);
4257
	var cell1 = row.insertCell(0);
4258
	var cell2 = row.insertCell(1);
4259
	var cell3 = row.insertCell(2);
4260
	var cell4 = row.insertCell(3);
4261

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

    
4267
	});
4268
})();
4269

    
4270
function removeBwRow(el) {
4271
	var d = el.parentNode.parentNode.rowIndex;
4272
	document.getElementById('maintable').deleteRow(d);
4273
}
4274

    
4275
function ceil_func(el){
4276
	el.value = Math.ceil(el.value);
4277

    
4278
}
4279
//]]>
4280
</script>
4281

    
4282
EOD;
4283

    
4284
		return $javasr;
4285
	}
4286

    
4287
	// Compose a table of bandwidths that can then be inserted into the form using a Form_StaticText
4288
	// The table has been "Bootstrapped" to match the web design while maintaining compatibility with
4289
	// with the javascript in this class
4290
	function build_bwtable() {
4291
		$bandwidth = $this->GetBandwidth();
4292
				//build list of schedules
4293
		$schedules = array();
4294
		$schedules[] = "none";//leave none to leave rule enabled all the time
4295
		foreach (config_get_path('schedules/schedule', []) as $schedule) {
4296
			if ($schedule['name'] != "") {
4297
				$schedules[] = $schedule['name'];
4298
			}
4299
		}
4300

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

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

    
4317
		if (is_array($bandwidth)) {
4318
			foreach ($bandwidth as $bwidx => $bw) {
4319
				$form .= '<tr>';
4320
				$form .= '<td class="col-xs-4">';
4321
				$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\"/>";
4322
				//$form .= "</td><td width='20%'>";
4323
				//$form .= "<input class='formfld unknown' size='10' type=\"text\" id=\"burst{$bwidx}\" name=\"burst{$bwidx}\" value=\"{$bw['burst']}\" />";
4324
				$form .= "</td>";
4325
				$form .= '<td class="col-xs-4">';
4326
				$form .= "<select id=\"bwtype{$bwidx}\" name=\"bwtype{$bwidx}\" class=\"form-control\">";
4327

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

    
4331
					if ($bw['bwscale'] == $bwsidx) {
4332
						$form .= " selected";
4333
					}
4334

    
4335
					$form .= ">{$bwscale}</option>";
4336
				}
4337

    
4338
				$form .= "</select>";
4339
				$form .= "</td>";
4340
				$form .= '<td class="col-xs-4">';
4341
				$form .= "<select id=\"bwsched{$bwidx}\" name=\"bwsched{$bwidx}\" class=\"form-control\">";
4342

    
4343
				foreach ($schedules as $schd) {
4344
					$selected = "";
4345
					if ($bw['bwsched'] == $schd) {
4346
						$selected = "selected";
4347
					}
4348

    
4349
					$form .= "<option value='{$schd}' {$selected}>{$schd}</option>";
4350
				}
4351

    
4352
				$form .= "</select>";
4353
				$form .= "</td>";
4354
				$form .= '<td>';
4355
				$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>';
4356
				$form .= "</td></tr>";
4357
			}
4358
		}
4359
		$form .= "</tbody></table></div><br />";
4360

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

    
4365
		return($form);
4366
	}
4367

    
4368
	function build_form() {
4369
		global $g, $pipe, $action, $qname;
4370

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

    
4380

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

    
4384
		$section = new Form_Section('Limiters');
4385

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

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

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

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

    
4417
		$bandwidth = $this->GetBandwidth();
4418

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

    
4426
		$mask = $this->GetMask();
4427

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

    
4437
		$group = new Form_Group(null);
4438

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

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

    
4453
		$section->add($group);
4454

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

    
4462
		$sform->add($section);
4463

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

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

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

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

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

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

    
4507
		$selectedScheduler = getSchedulers()[$this->getScheduler()];
4508

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

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

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

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

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

    
4559
		$sform->add($section);
4560
		/* End limiter patch */
4561

    
4562
		$section = new Form_Section('Advanced Options');
4563

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

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

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

    
4587
		$sform->add($section);
4588

    
4589
		return($sform);
4590
		}
4591

    
4592
	function wconfig() {
4593
		$cflink_path = shaper_dn_config_get_path($this->GetLink());
4594
		$cflink = config_get_path($cflink_path, []);
4595
		$cflink['name'] = $this->GetQname();
4596
		$cflink['number'] = $this->GetNumber();
4597
		$cflink['qlimit'] = $this->GetQlimit();
4598
		$cflink['plr'] = $this->GetPlr();
4599
		$cflink['description'] = $this->GetDescription();
4600

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

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

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

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

    
4652
}
4653

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

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

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

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

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

    
4742

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

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

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

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

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

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

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

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

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

    
4864
		return $tree;
4865
	}
4866

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

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

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

    
4927
		$pfq_rule .= "\n";
4928

    
4929
		return $pfq_rule;
4930
	}
4931

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

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

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

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

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

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

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

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

    
4983
		$mask = $this->GetMask();
4984

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

    
4996
		$group = new Form_Group(null);
4997

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

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

    
5012
		$section->add($group);
5013

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

    
5021
		$sform->add($section);
5022

    
5023
		/* Begin limiter patch */
5024
		$aqm_map = getAQMs();
5025

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

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

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

    
5050
		$selectedAQM = getAQMs()[$this->getAQM()];
5051

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

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

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

    
5104
		$section = new Form_Section('Advanced Options');
5105

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

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

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

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

    
5138
		$sform->add($section);
5139

    
5140
		return($sform);
5141
	}
5142

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

    
5147
	function wconfig() {
5148
		$cflink_path = shaper_dn_config_get_path($this->GetLink());
5149
		$cflink = config_get_path($cflink_path, []);
5150
		$cflink['name'] = $this->GetQname();
5151
		$cflink['number'] = $this->GetNumber();
5152
		$cflink['qlimit'] = $this->GetQlimit();
5153
		$cflink['description'] = $this->GetDescription();
5154
		$cflink['weight'] = $this->GetWeight();
5155
		$cflink['enabled'] = $this->GetEnabled();
5156
		$cflink['buckets'] = $this->GetBuckets();
5157
		$mask = $this->GetMask();
5158
		$cflink['mask'] = $mask['type'];
5159
		$cflink['maskbits'] = $mask['bits'];
5160
		$cflink['maskbitsv6'] = $mask['bitsv6'];
5161

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

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

    
5190
function get_dummynet_name_list() {
5191

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

    
5200
	return $dn_name;
5201

    
5202
}
5203

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

    
5213
	return $altq_name;
5214
}
5215

    
5216
/*
5217
 * XXX: TODO Make a class shaper to hide all these functions
5218
 * from the global namespace.
5219
 */
5220

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

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

    
5236
function altq_check_default_queues() {
5237
	global $altq_list_queues;
5238

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

    
5251
	return 0;
5252
}
5253

    
5254
function &get_unique_queue_list() {
5255
	global $altq_list_queues;
5256

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

    
5274
function &get_unique_dnqueue_list() {
5275
	global $dummynet_pipe_list;
5276

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

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

    
5299
	unref_on_altq_queue_list($parent);
5300
}
5301

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

    
5309
function read_altq_config() {
5310
	global $altq_list_queues;
5311
	$path = array();
5312

    
5313
	$altq_list_queues = array();
5314

    
5315
	foreach (config_get_path('shaper/queue', []) as $key => $conf) {
5316
		$int = $conf['interface'];
5317
		$root = new altq_root_queue();
5318
		$root->SetInterface($int);
5319
		$altq_list_queues[$root->GetInterface()] = $root;
5320
		$root->ReadConfig($conf);
5321
		array_push($path, $key);
5322
		$root->SetLink($path);
5323
		if (is_array($conf['queue'])) {
5324
			foreach ($conf['queue'] as $key1 => $q) {
5325
				array_push($path, $key1);
5326
				/*
5327
				 * XXX: we completely ignore errors here but anyway we must have
5328
				 *	checked them before so no harm should be come from this.
5329
				 */
5330
				$root->add_queue($root->GetInterface(), $q, $path, $input_errors);
5331
				array_pop($path);
5332
			}
5333
		}
5334
		array_pop($path);
5335
	}
5336
}
5337

    
5338
function read_dummynet_config() {
5339
	global $dummynet_pipe_list;
5340
	$path = array();
5341

    
5342
	$a_int = config_get_path('dnshaper/queue', []);
5343

    
5344
	$dummynet_pipe_list = array();
5345

    
5346
	if (!count($a_int)) {
5347
		return;
5348
	}
5349

    
5350
	foreach ($a_int as $key => $conf) {
5351
		if (empty($conf['name'])) {
5352
			continue; /* XXX: grrrrrr at php */
5353
		}
5354
		$root = new dnpipe_class();
5355
		$root->ReadConfig($conf);
5356
		$dummynet_pipe_list[$root->GetQname()] = $root;
5357
		array_push($path, $key);
5358
		$root->SetLink($path);
5359
		if (is_array($conf['queue'])) {
5360
			foreach ($conf['queue'] as $key1 => $q) {
5361
				array_push($path, $key1);
5362
				/*
5363
				 * XXX: we completely ignore errors here but anyway we must have
5364
				 *	checked them before so no harm should be come from this.
5365
				 */
5366
				$root->add_queue($root->GetQname(), $q, $path, $input_errors);
5367
				array_pop($path);
5368
			}
5369
		}
5370
		array_pop($path);
5371
	}
5372
}
5373

    
5374
function get_interface_list_to_show() {
5375
	global $altq_list_queues;
5376
	global $shaperIFlist;
5377

    
5378
	$tree = "";
5379
	foreach ($shaperIFlist as $shif => $shDescr) {
5380
		if ($altq_list_queues[$shif]) {
5381
			continue;
5382
		} else {
5383
			if (!is_altq_capable(get_real_interface($shif))) {
5384
				continue;
5385
			}
5386
			$tree .= " <li><a href=\"firewall_shaper.php?interface=".$shif."&amp;action=add\">".$shDescr."</a></li>";
5387
		}
5388
	}
5389

    
5390
	return $tree;
5391
}
5392

    
5393
function filter_generate_altq_queues() {
5394
	global $altq_list_queues;
5395

    
5396
	read_altq_config();
5397

    
5398
	$altq_rules = "";
5399
	foreach ($altq_list_queues as $altq) {
5400
		$altq_rules .= $altq->build_rules();
5401
	}
5402

    
5403
	return $altq_rules;
5404
}
5405

    
5406
function dnqueue_find_nextnumber() {
5407
	global $dummynet_pipe_list;
5408

    
5409
	$dnused = array();
5410
	if (is_array($dummynet_pipe_list)) {
5411
		foreach ($dummynet_pipe_list as $dn) {
5412
			$tmplist =& $dn->get_queue_list();
5413
			foreach ($tmplist as $qname => $link) {
5414
				if ($link[0] == "?") {
5415
					$dnused[$qname] = substr($link, 1);
5416
				}
5417
			}
5418
		}
5419
	}
5420

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

    
5434
	if ($found == false) {
5435
		$dnnumber++;
5436
	}
5437

    
5438
	unset($dnused, $dnnum, $found);
5439
	return $dnnumber;
5440
}
5441

    
5442
function dnpipe_find_nextnumber() {
5443
	global $dummynet_pipe_list;
5444

    
5445
	$dnused = array();
5446
	foreach ($dummynet_pipe_list as $dn) {
5447
		$dnused[] = $dn->GetNumber();
5448
	}
5449

    
5450
	sort($dnused, SORT_NUMERIC);
5451
	$dnnumber = 0;
5452
	$found = false;
5453
	foreach ($dnused as $dnnum) {
5454
		if (($dnnum - $dnnumber) > 1) {
5455
			$dnnumber = $dnnum - 1;
5456
			$found = true;
5457
			break;
5458
		} else {
5459
			$dnnumber = $dnnum;
5460
		}
5461
	}
5462

    
5463
	if ($found == false) {
5464
		$dnnumber++;
5465
	}
5466

    
5467
	unset($dnused, $dnnum, $found);
5468
	return $dnnumber;
5469
}
5470

    
5471
function filter_generate_dummynet_rules() {
5472
	global $g, $dummynet_pipe_list;
5473

    
5474
	read_dummynet_config();
5475

    
5476
	$dn_rules = "";
5477
	$max_qlimit = "100"; // OS default
5478
	foreach ($dummynet_pipe_list as $dn) {
5479
		$dn_rules .= $dn->build_rules();
5480
		$this_qlimit = $dn->GetQlimit();
5481
		if ($this_qlimit > $max_qlimit) {
5482
			$max_qlimit = $this_qlimit;
5483
		}
5484
		if (is_array($dn->subqueues)) {
5485
			foreach ($dn->subqueues as $queue) {
5486
				$this_qlimit = $queue->GetQlimit();
5487
				if ($this_qlimit > $max_qlimit) {
5488
					$max_qlimit = $this_qlimit;
5489
				}
5490
			}
5491
		}
5492
	}
5493
	if (!is_numericint($max_qlimit)) {
5494
		$max_qlimit = "100";
5495
	}
5496
	if (!empty($dn_rules)) {
5497
		dummynet_load_module($max_qlimit);
5498
		file_put_contents("{$g['tmp_path']}/rules.limiter", $dn_rules);
5499
		mwexec("/sbin/dnctl {$g['tmp_path']}/rules.limiter");
5500
	}
5501
}
5502

    
5503
function build_iface_without_this_queue($iface, $qname) {
5504
	global $g, $altq_list_queues;
5505
	global $shaperIFlist;
5506

    
5507
	$altq =& $altq_list_queues[$iface];
5508

    
5509
	if ($altq) {
5510
		$scheduler = $altq->GetScheduler();
5511
	}
5512

    
5513
	$form = '<dl class="dl-horizontal">';
5514

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

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

    
5533
	$form .= '</dl>';
5534

    
5535
	return $form;
5536

    
5537
}
5538

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

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

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

    
5550
// Check to see if the specified interface has a queue configured
5551
function interface_has_queue($if) {
5552
	foreach (config_get_path('shaper/queue', []) as $queue) {
5553
		if ($queue['interface'] === $if) {
5554
			return true;
5555
		}
5556
	}
5557

    
5558
	return false;
5559
}
5560
?>
(48-48/61)