Skip to content

Commit 97e8230

Browse files
Added APIFirewallTrafficShaperUpdate model to update traffic shaper objects, added unit test to test updates
1 parent ed884cc commit 97e8230

5 files changed

Lines changed: 289 additions & 4 deletions

File tree

pfSense-pkg-API/files/etc/inc/api/endpoints/APIFirewallTrafficShaper.inc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,8 @@ class APIFirewallTrafficShaper extends APIEndpoint {
2828
return (new APIFirewallTrafficShaperCreate())->call();
2929
}
3030

31+
protected function put() {
32+
return (new APIFirewallTrafficShaperUpdate())->call();
33+
}
34+
3135
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1712,6 +1712,12 @@ function get($id, $data=[], $all=false) {
17121712
"return" => $id,
17131713
"message" => "Firewall traffic shaper TBR size must be 1 or greater"
17141714
],
1715+
4122 => [
1716+
"status" => "bad request",
1717+
"code" => 400,
1718+
"return" => $id,
1719+
"message" => "Firewall traffic shaper does not exist for this interface"
1720+
],
17151721

17161722
//5000-5999 reserved for /users API calls
17171723
5000 => [

pfSense-pkg-API/files/etc/inc/api/models/APIFirewallTrafficShaperCreate.inc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class APIFirewallTrafficShaperCreate extends APIModel {
4646
public function validate_payload() {
4747
$this->__validate_interface();
4848
$this->__validate_scheduler();
49-
$this->__validate_bandwidthtype(); // Must run before __validate_bandwith()
49+
$this->__validate_bandwidthtype(); // Must run before __validate_banddwith()
5050
$this->__validate_bandwidth();
5151
$this->__validate_enabled();
5252
$this->__validate_qlimit();
@@ -110,7 +110,7 @@ class APIFirewallTrafficShaperCreate extends APIModel {
110110
# Check that the bandwidth is 1 or greater
111111
if (intval($this->initial_data["bandwidth"]) >= 1) {
112112
# If bandwidth type % is used, enforce value to be less than 100
113-
if ($this->validated_data["bandwidthtype"] == "%" and intval($this->initial_data["bandwidth"]) > 100) {
113+
if ($this->validated_data["bandwidthtype"] === "%" and intval($this->initial_data["bandwidth"]) > 100) {
114114
$this->errors[] = APIResponse\get(4119);
115115
} else {
116116
$this->validated_data["bandwidth"] = intval($this->initial_data["bandwidth"]);
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
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 APIFirewallTrafficShaperUpdate extends APIModel {
20+
private $type_changed;
21+
22+
# Create our method constructor
23+
public function __construct() {
24+
parent::__construct();
25+
$this->privileges = ["page-all", "page-firewall-trafficshaper"];
26+
$this->change_note = "Modified firewall traffic shaper via API";
27+
}
28+
29+
public function action() {
30+
# Save the updated shaper, mark the subsystem as un-applied
31+
$this->config["shaper"]["queue"][$this->id] = $this->validated_data;
32+
$this->write_config();
33+
mark_subsystem_dirty('shaper');
34+
35+
# Only reload the filter immediately if it was requested by the client
36+
if ($this->initial_data["apply"] === true) {
37+
# Reload the filter, reset RRD logs and mark the subsystem as applied
38+
filter_configure();
39+
system("rm -f /var/db/rrd/*queuedrops.rrd");
40+
system("rm -f /var/db/rrd/*queues.rrd");
41+
enable_rrd_graphing();
42+
clear_subsystem_dirty('shaper');
43+
}
44+
return APIResponse\get(0, $this->validated_data);
45+
}
46+
47+
public function validate_payload() {
48+
$this->__validate_interface();
49+
$this->__validate_scheduler();
50+
$this->__validate_bandwidthtype(); // Must run before __validate_bandwidth()
51+
$this->__validate_bandwidth();
52+
$this->__validate_enabled();
53+
$this->__validate_qlimit();
54+
$this->__validate_tbrconfig();
55+
}
56+
57+
private function __validate_interface() {
58+
# Check for our required `interface` payload value
59+
if (isset($this->initial_data["interface"])) {
60+
$this->initial_data["interface"] = APITools\get_pfsense_if_id($this->initial_data["interface"]);
61+
62+
# Check that the requested interface exists
63+
if ($this->initial_data["interface"]) {
64+
# Check that a traffic shaper does not already exist for this interface
65+
if ($this->get_shaper_id_by_interface($this->initial_data["interface"], true)) {
66+
# Fetch the traffic shaper object associated with this interface
67+
$this->id = $this->get_shaper_id_by_interface($this->initial_data["interface"]);
68+
$this->validated_data = $this->config["shaper"]["queue"][$this->id];
69+
} else {
70+
$this->errors[] = APIResponse\get(4122);
71+
}
72+
} else {
73+
$this->errors[] = APIResponse\get(4111);
74+
}
75+
} else {
76+
$this->errors[] = APIResponse\get(4110);
77+
}
78+
}
79+
80+
private function __validate_scheduler() {
81+
# Check for our optional `scheduler` payload value
82+
if (isset($this->initial_data["scheduler"])) {
83+
# Check that the scheduler type is supported
84+
if (in_array($this->initial_data["scheduler"], ["HFSC", "CBQ", "FAIRQ", "CODELQ", "PRIQ"])) {
85+
$this->validated_data["scheduler"] = $this->initial_data["scheduler"];
86+
} else {
87+
$this->errors[] = APIResponse\get(4114);
88+
}
89+
}
90+
}
91+
92+
private function __validate_bandwidthtype() {
93+
# Check for our required `bandwidthtype` payload value
94+
if (isset($this->initial_data["bandwidthtype"])) {
95+
# Only update the bandwidth type if it has changed
96+
if ($this->initial_data["bandwidthtype"] !== $this->validated_data["bandwidthtype"]) {
97+
# Check that the scheduler type is supported
98+
if (in_array($this->initial_data["bandwidthtype"], ["%", "b", "Kb", "Mb", "Gb"])) {
99+
$this->validated_data["bandwidthtype"] = $this->initial_data["bandwidthtype"];
100+
$this->type_changed = true;
101+
} else {
102+
$this->errors[] = APIResponse\get(4116);
103+
}
104+
}
105+
}
106+
}
107+
108+
private function __validate_bandwidth() {
109+
# Check for our optional `bandwidth` payload value
110+
if (isset($this->initial_data["bandwidth"])) {
111+
# Check that the bandwidth is 1 or greater
112+
if (intval($this->initial_data["bandwidth"]) >= 1) {
113+
# If bandwidth type % is used, enforce value to be less than 100
114+
if ($this->validated_data["bandwidthtype"] === "%" and intval($this->initial_data["bandwidth"]) > 100) {
115+
$this->errors[] = APIResponse\get(4119);
116+
} else {
117+
$this->validated_data["bandwidth"] = intval($this->initial_data["bandwidth"]);
118+
}
119+
} else {
120+
$this->errors[] = APIResponse\get(4118);
121+
}
122+
}
123+
# Require a new bandwidth value be specified if the type was changed
124+
elseif ($this->type_changed) {
125+
$this->errors[] = APIResponse\get(4117);
126+
}
127+
}
128+
129+
private function __validate_enabled() {
130+
# Enable this shaper if requested
131+
if ($this->initial_data["enabled"] === true) {
132+
$this->validated_data["enabled"] = "on";
133+
}
134+
# Disabled this shaper if requested
135+
elseif ($this->validated_data["enabled"] === false) {
136+
unset($this->validated_data["enabled"]);
137+
}
138+
}
139+
140+
private function __validate_qlimit() {
141+
# Check for our optional `qlimit` payload value
142+
if (isset($this->initial_data["qlimit"])) {
143+
# Ensure the qlimit is 1 or greater
144+
if (is_numeric($this->initial_data["qlimit"]) and intval($this->initial_data["qlimit"]) >= 1) {
145+
$this->validated_data["qlimit"] = intval($this->initial_data["qlimit"]);
146+
} else {
147+
$this->errors[] = APIResponse\get(4120);
148+
}
149+
}
150+
}
151+
152+
private function __validate_tbrconfig() {
153+
# Check for our optional `tbrconfig` payload value
154+
if (isset($this->initial_data["tbrconfig"])) {
155+
# Ensure the tbrconfig is 1 or greater
156+
if (is_numeric($this->initial_data["tbrconfig"]) and intval($this->initial_data["tbrconfig"]) >= 1) {
157+
$this->validated_data["tbrconfig"] = intval($this->initial_data["tbrconfig"]);
158+
} else {
159+
$this->errors[] = APIResponse\get(4121);
160+
}
161+
}
162+
}
163+
164+
public function get_shaper_id_by_interface($interface, $as_bool=false) {
165+
# Loop through each configured shaper
166+
foreach ($this->config["shaper"]["queue"] as $id=>$shaper) {
167+
# Check if this is the shaper for our interface, if so return it's ID
168+
if ($interface === $shaper["interface"]) {
169+
return ($as_bool) ? true : intval($id);
170+
}
171+
}
172+
return ($as_bool) ? false : null;
173+
}
174+
}

tests/test_api_v1_firewall_traffic_shaper.py

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import unit_test_framework
1616

17-
class APIUnitTestFirewallRule(unit_test_framework.APIUnitTest):
17+
class APIUnitTestFirewallTrafficShaper(unit_test_framework.APIUnitTest):
1818
url = "/api/v1/firewall/traffic_shaper"
1919
get_tests = [{"name": "Read all traffic shapers"}]
2020
post_tests = [
@@ -145,5 +145,106 @@ class APIUnitTestFirewallRule(unit_test_framework.APIUnitTest):
145145
}
146146
},
147147
]
148+
put_tests = [
149+
{
150+
"name": "Create a traffic shaper",
151+
"payload": {
152+
"interface": "lan",
153+
"scheduler": "HFSC",
154+
"bandwidthtype": "Mb",
155+
"bandwidth": 100,
156+
"enabled": True,
157+
"qlimit": 500,
158+
"tbrconfig": 500,
159+
"apply": True
160+
}
161+
},
162+
{
163+
"name": "Check interface requirement",
164+
"status": 400,
165+
"return": 4110
166+
},
167+
{
168+
"name": "Check interface validation",
169+
"status": 400,
170+
"return": 4111,
171+
"payload": {
172+
"interface": "INVALID"
173+
}
174+
},
175+
{
176+
"name": "Check non-existing traffic shaper",
177+
"status": 400,
178+
"return": 4122,
179+
"payload": {
180+
"interface": "wan"
181+
}
182+
},
183+
{
184+
"name": "Check scheduler validation",
185+
"status": 400,
186+
"return": 4114,
187+
"payload": {
188+
"interface": "lan",
189+
"scheduler": "INVALID"
190+
}
191+
},
192+
{
193+
"name": "Check bandwidth requirement when changing bandwidth types",
194+
"status": 400,
195+
"return": 4117,
196+
"payload": {
197+
"interface": "lan",
198+
"bandwidthtype": "b"
199+
}
200+
},
201+
{
202+
"name": "Check bandwidth type validation",
203+
"status": 400,
204+
"return": 4116,
205+
"payload": {
206+
"interface": "lan",
207+
"bandwidthtype": "INVALID"
208+
}
209+
},
210+
211+
{
212+
"name": "Check bandwidth minimum constraint",
213+
"status": 400,
214+
"return": 4118,
215+
"payload": {
216+
"interface": "lan",
217+
"bandwidth": 0
218+
}
219+
},
220+
{
221+
"name": "Check bandwidth maximum constraint when percentage type is chosen",
222+
"status": 400,
223+
"return": 4119,
224+
"payload": {
225+
"interface": "lan",
226+
"bandwidthtype": "%",
227+
"bandwidth": 101
228+
}
229+
},
230+
{
231+
"name": "Check queue limit minimum constraint",
232+
"status": 400,
233+
"return": 4120,
234+
"payload": {
235+
"interface": "lan",
236+
"qlimit": 0
237+
}
238+
},
239+
{
240+
"name": "Check TBR size minimum constraint",
241+
"status": 400,
242+
"return": 4121,
243+
"payload": {
244+
"interface": "lan",
245+
"tbrconfig": 0
246+
}
247+
},
248+
]
148249

149-
APIUnitTestFirewallRule()
250+
APIUnitTestFirewallTrafficShaper()

0 commit comments

Comments
 (0)