Skip to content

Commit ada39b1

Browse files
Created APIFirewallTrafficShaperQueueCreate model to create new traffic shaper queues
1 parent d48f938 commit ada39b1

2 files changed

Lines changed: 341 additions & 0 deletions

File tree

pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1718,6 +1718,42 @@ function get($id, $data=[], $all=false) {
17181718
"return" => $id,
17191719
"message" => "Firewall traffic shaper does not exist for this interface"
17201720
],
1721+
4123 => [
1722+
"status" => "bad request",
1723+
"code" => 400,
1724+
"return" => $id,
1725+
"message" => "Firewall traffic shaper queue name is required"
1726+
],
1727+
4124 => [
1728+
"status" => "bad request",
1729+
"code" => 400,
1730+
"return" => $id,
1731+
"message" => "Firewall traffic shaper queue name must only contain alphanumerics, underscore, and hyphens"
1732+
],
1733+
4125 => [
1734+
"status" => "bad request",
1735+
"code" => 400,
1736+
"return" => $id,
1737+
"message" => "Firewall traffic shaper queue name must be between 1 and 15 characters"
1738+
],
1739+
4126 => [
1740+
"status" => "bad request",
1741+
"code" => 400,
1742+
"return" => $id,
1743+
"message" => "Firewall traffic shaper queue name already exists for this interface"
1744+
],
1745+
4127 => [
1746+
"status" => "bad request",
1747+
"code" => 400,
1748+
"return" => $id,
1749+
"message" => "Firewall traffic shaper queue priority must be between 0 and 7 for CBQ and FAIRQ schedulers"
1750+
],
1751+
4128 => [
1752+
"status" => "bad request",
1753+
"code" => 400,
1754+
"return" => $id,
1755+
"message" => "Firewall traffic shaper queue priority must be between 0 and 15 for PRIQ schedulers"
1756+
],
17211757

17221758
//5000-5999 reserved for /users API calls
17231759
5000 => [
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
<?php
2+
// Copyright 2021 Jared Hendrickson
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
require_once("api/framework/APIModel.inc");
17+
require_once("api/framework/APIResponse.inc");
18+
19+
class APIFirewallTrafficShaperQueueCreate extends APIModel {
20+
public $p_sched;
21+
22+
# Create our method constructor
23+
public function __construct() {
24+
parent::__construct();
25+
$this->privileges = ["page-all", "page-firewall-trafficshaper-queues"];
26+
$this->change_note = "Added firewall traffic shaper queue via API";
27+
}
28+
29+
public function action() {
30+
# Initialize the traffic shaper configuration and save our shaper, mark the subsystem as un-applied
31+
$this->__init_config();
32+
$this->config["shaper"]["queue"][$this->id]["queue"][] = $this->validated_data;
33+
$this->write_config();
34+
mark_subsystem_dirty('shaper');
35+
36+
# Only reload the filter immediately if it was requested by the client
37+
if ($this->initial_data["apply"] === true) {
38+
# Reload the filter, reset RRD logs and mark the subsystem as applied
39+
filter_configure();
40+
system("rm -f /var/db/rrd/*queuedrops.rrd");
41+
system("rm -f /var/db/rrd/*queues.rrd");
42+
enable_rrd_graphing();
43+
clear_subsystem_dirty('shaper');
44+
}
45+
return APIResponse\get(0, $this->validated_data);
46+
}
47+
48+
public function validate_payload() {
49+
$this->__validate_interface();
50+
$this->__validate_name();
51+
$this->__validate_priority();
52+
$this->__validate_qlimit();
53+
$this->__validate_description();
54+
$this->__validate_enabled();
55+
$this->__validate_default();
56+
$this->__validate_red();
57+
$this->__validate_rio();
58+
$this->__validate_ecn();
59+
$this->__validate_codel();
60+
$this->__validate_bandwidthtype();
61+
$this->__validate_bandwidth();
62+
$this->__validate_buckets();
63+
$this->__validate_hogs();
64+
$this->__validate_borrow();
65+
}
66+
67+
private function __validate_interface() {
68+
# Check for our required `interface` payload value
69+
if (isset($this->initial_data["interface"])) {
70+
$this->initial_data["interface"] = APITools\get_pfsense_if_id($this->initial_data["interface"]);
71+
72+
# Check that the requested interface exists
73+
if ($this->initial_data["interface"]) {
74+
# Check that a traffic shaper is configured for this interface
75+
if ($this->get_shaper_id_by_interface($this->initial_data["interface"], true)) {
76+
# Set the interface this queue will be applied to
77+
$this->validated_data["interface"] = $this->initial_data["interface"];
78+
$this->p_sched = $this->config["shaper"]["queue"][$this->id]["scheduler"];
79+
} else {
80+
$this->errors[] = APIResponse\get(4122);
81+
}
82+
} else {
83+
$this->errors[] = APIResponse\get(4111);
84+
}
85+
} else {
86+
$this->errors[] = APIResponse\get(4110);
87+
}
88+
}
89+
90+
private function __validate_name() {
91+
# Check for our required `name` payload value
92+
if (isset($this->initial_data["name"])) {
93+
# Ensure the name only contains alphanumeric, underscores, and hyphens
94+
if (preg_match('/^[\w-]+$/', $this->initial_data["name"])) {
95+
# Ensure the name is greater than 1 character and less than equal to 15 characters
96+
if (strlen($this->initial_data["name"]) >= 1 and strlen($this->initial_data["name"]) <= 15) {
97+
# Ensure the name is not already in use
98+
if (!$this->is_queue_name_in_use($this->initial_data["name"], $this->validated_data["interface"])) {
99+
$this->validated_data["name"] = $this->initial_data["name"];
100+
} else {
101+
$this->errors[] = APIResponse\get(4126);
102+
}
103+
} else {
104+
$this->errors[] = APIResponse\get(4125);
105+
}
106+
} else {
107+
$this->errors[] = APIResponse\get(4124);
108+
}
109+
} else {
110+
$this->errors[] = APIResponse\get(4123);
111+
}
112+
}
113+
114+
private function __validate_priority() {
115+
# Only validate this field if the parent shaper's scheduler is set to FAIRQ CBQ or PRIQ
116+
if (in_array($this->p_sched, ["FAIRQ", "CBQ", "PRIQ"])) {
117+
# Check for our optional `priority` value
118+
if (isset($this->initial_data["priority"])) {
119+
# For FAIRQ and CBQ schedulers, force priority to be between 0 and 7
120+
if (in_array($this->p_sched, ["FAIRQ", "CBQ"])) {
121+
if (intval($this->initial_data["priority"]) < 0 or intval($this->initial_data["priority"]) > 7) {
122+
$this->errors[] = APIResponse\get(4127);
123+
} else {
124+
$this->validated_data = intval($this->initial_data["priority"]);
125+
}
126+
}
127+
# For PRIQ schedulers, force priority to be between 0 and 15
128+
elseif (in_array($this->p_sched, ["PRIQ"])) {
129+
if (intval($this->initial_data["priority"]) < 0 or intval($this->initial_data["priority"]) > 15) {
130+
$this->errors[] = APIResponse\get(4128);
131+
} else {
132+
$this->validated_data = intval($this->initial_data["priority"]);
133+
}
134+
}
135+
} else {
136+
$this->validated_data["priority"] = 1;
137+
}
138+
}
139+
}
140+
141+
private function __validate_description() {
142+
# Check for the optional `description` payload value
143+
if (isset($this->initial_data["description"])) {
144+
$this->validated_data = strval($this->initial_data["description"]);
145+
}
146+
}
147+
148+
private function __validate_enabled() {
149+
# Enable this shaper by default if a non-false value was provided
150+
if ($this->initial_data["enabled"] !== false) {
151+
$this->validated_data["enabled"] = "on";
152+
}
153+
}
154+
155+
private function __validate_default() {
156+
# Set this queue as the default if requested
157+
if ($this->initial_data["default"] === true) {
158+
$this->validated_data["default"] = "default";
159+
}
160+
}
161+
162+
private function __validate_red() {
163+
# Set the Random Early Detection option if requested
164+
if ($this->initial_data["red"] === true) {
165+
$this->validated_data["red"] = "yes";
166+
}
167+
}
168+
169+
private function __validate_rio() {
170+
# Set the Random Early Detection In and Out option if requested
171+
if ($this->initial_data["rio"] === true) {
172+
$this->validated_data["rio"] = "yes";
173+
}
174+
}
175+
176+
private function __validate_ecn() {
177+
# Set the Explicit Congestion Notification option if requested
178+
if ($this->initial_data["ecn"] === true) {
179+
$this->validated_data["ecn"] = "yes";
180+
}
181+
}
182+
183+
private function __validate_codel() {
184+
# Set the Codel Active Queue option if requested
185+
if ($this->initial_data["codel"] === true) {
186+
$this->validated_data["codel"] = "yes";
187+
}
188+
}
189+
190+
private function __validate_qlimit() {
191+
# Check for our optional `qlimit` payload value
192+
if (isset($this->initial_data["qlimit"])) {
193+
# Ensure the qlimit is 1 or greater
194+
if (is_numeric($this->initial_data["qlimit"]) and intval($this->initial_data["qlimit"]) >= 1) {
195+
$this->validated_data["qlimit"] = intval($this->initial_data["qlimit"]);
196+
} else {
197+
$this->errors[] = APIResponse\get(4120);
198+
}
199+
}
200+
}
201+
202+
private function __validate_bandwidthtype() {
203+
# Only validate this field if the parent shaper's scheduler is set to FAIRQ or CBQ
204+
if (in_array($this->p_sched, ["FAIRQ", "CBQ"])) {
205+
# Check for our optional `bandwidthtype` payload value
206+
if (isset($this->initial_data["bandwidthtype"])) {
207+
# Check that the scheduler type is supported
208+
if (in_array($this->initial_data["bandwidthtype"], ["%", "b", "Kb", "Mb", "Gb"])) {
209+
$this->validated_data["bandwidthtype"] = $this->initial_data["bandwidthtype"];
210+
} else {
211+
$this->errors[] = APIResponse\get(4116);
212+
}
213+
} else {
214+
$this->validated_data["bandwidthtype"] = "Mb";
215+
}
216+
}
217+
}
218+
219+
private function __validate_bandwidth() {
220+
# Only validate this field if the parent shaper's scheduler is set to FAIRQ or CBQ
221+
if (in_array($this->p_sched, ["FAIRQ", "CBQ"])) {
222+
# Check for our optional `bandwidth` payload value
223+
if (isset($this->initial_data["bandwidth"])) {
224+
# Check that the bandwidth is 1 or greater
225+
if (intval($this->initial_data["bandwidth"]) >= 1) {
226+
# If bandwidth type % is used, enforce value to be less than 100
227+
if ($this->validated_data["bandwidthtype"] === "%" and intval($this->initial_data["bandwidth"]) > 100) {
228+
$this->errors[] = APIResponse\get(4119);
229+
} else {
230+
$this->validated_data["bandwidth"] = intval($this->initial_data["bandwidth"]);
231+
}
232+
} else {
233+
$this->errors[] = APIResponse\get(4118);
234+
}
235+
} else {
236+
$this->validated_data["bandwidth"] = "";
237+
}
238+
}
239+
}
240+
241+
private function __validate_buckets() {
242+
# Only validate this field if the parent shaper's scheduler is set to FAIRQ
243+
if (in_array($this->p_sched, ["FAIRQ"])) {
244+
# Check for the optional `buckets` payload value
245+
if (isset($this->initial_data["buckets"])) {
246+
$this->validated_data = strval($this->initial_data["buckets"]);
247+
}
248+
}
249+
}
250+
251+
private function __validate_hogs() {
252+
# Only validate this field if the parent shaper's scheduler is set to FAIRQ
253+
if (in_array($this->p_sched, ["FAIRQ"])) {
254+
# Check for the optional `hogs` payload value
255+
if (isset($this->initial_data["hogs"])) {
256+
$this->validated_data = strval($this->initial_data["hogs"]);
257+
}
258+
}
259+
}
260+
261+
private function __validate_borrow() {
262+
# Only validate this field if the parent shaper's scheduler is set to FAIRQ
263+
if (in_array($this->p_sched, ["FAIRQ"])) {
264+
# Set the borrows options if requested
265+
if ($this->initial_data["borrow"] === true) {
266+
$this->validated_data["borrow"] = "yes";
267+
}
268+
}
269+
}
270+
271+
private function __init_config() {
272+
# Check if there is no shaper queue array in the config
273+
if (empty($this->config["shaper"]["queue"][$this->id]["queue"])) {
274+
$this->config["shaper"]["queue"][$this->id]["queue"] = [];
275+
}
276+
}
277+
278+
public function get_shaper_id_by_interface($interface, $as_bool=false) {
279+
# Loop through each configured shaper
280+
foreach ($this->config["shaper"]["queue"] as $id=>$shaper) {
281+
# Check if this is the shaper for our interface, if so return it's ID
282+
if ($interface === $shaper["interface"]) {
283+
return ($as_bool) ? true : intval($id);
284+
}
285+
}
286+
return ($as_bool) ? false : null;
287+
}
288+
289+
public function is_queue_name_in_use($name, $interface) {
290+
# Only proceed if interface has a traffic shaper configured
291+
if ($this->get_shaper_id_by_interface($interface, true)) {
292+
# Fetch our shaper config ID
293+
$id = $this->get_shaper_id_by_interface($interface);
294+
295+
# Loop through each queue configured for this shaper
296+
foreach ($this->config["shaper"]["queue"][$id]["queue"] as $queue) {
297+
# Check if this queue's name matches our requested name
298+
if ($name === $queue["name"]) {
299+
return true;
300+
}
301+
}
302+
return false;
303+
}
304+
}
305+
}

0 commit comments

Comments
 (0)