Skip to content

Commit 4373ec2

Browse files
test: make reading pf rules context aware
1 parent 1972124 commit 4373ec2

File tree

1 file changed

+45
-12
lines changed

1 file changed

+45
-12
lines changed

pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsFirewallRuleTestCase.inc

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,44 @@ use RESTAPI\Models\RoutingGateway;
1111
use RESTAPI\Models\TrafficShaper;
1212
use RESTAPI\Models\TrafficShaperLimiter;
1313
use RESTAPI\Models\TrafficShaperLimiterQueue;
14+
use RESTAPI\Responses\ServerError;
1415

1516
class APIModelsFirewallRuleTestCase extends TestCase {
17+
/**
18+
* Reads the active ruleset directly from pfctl.
19+
*/
20+
public function read_pfctl_rules(): Command {
21+
# Keywords that indicate pf is not ready yet
22+
$not_ready_keywords = ['pfctl: DIOCGETRULE: Device busy'];
23+
24+
# Check the pf ruleset until it appears to be fully loaded, or until we've tried 5 times
25+
$attempt = 0;
26+
$max_attempts = 5;
27+
while ($attempt < $max_attempts) {
28+
$cmd = new Command('/sbin/pfctl -sr');
29+
$ready = true;
30+
31+
foreach ($not_ready_keywords as $keyword) {
32+
# pf is not ready if any of the keywords are found in the output
33+
if (str_contains($cmd->output, $keyword)) {
34+
$ready = false;
35+
$attempt++;
36+
sleep(1);
37+
break;
38+
}
39+
}
40+
41+
if ($ready) {
42+
return $cmd;
43+
}
44+
}
45+
46+
throw new ServerError(
47+
message: "pfctl ruleset was not ready after $max_attempts attempts.",
48+
response_id: 'API_MODELS_FIREWALL_RULE_TEST_CASE_PFCTL_NOT_READY',
49+
);
50+
}
51+
1652
/**
1753
* Checks that multiple interfaces cannot be assigned to a FirewallRule unless `floating` is enabled.
1854
*/
@@ -508,20 +544,20 @@ class APIModelsFirewallRuleTestCase extends TestCase {
508544
$rule->create(apply: true);
509545

510546
# Ensure the rule with the queue is seen in pfctl
511-
$pfctl = new Command('pfctl -sr');
547+
$pfctl = $this->read_pfctl_rules();
512548
$this->assert_str_contains($pfctl->output, 'ridentifier ' . $rule->tracker->value . ' queue TestQueue1');
513549

514550
# Update the rule to use a different queue and ensure it is seen in pfctl
515551
$rule->defaultqueue->value = 'TestQueue2';
516552
$rule->update(apply: true);
517-
$pfctl = new Command('pfctl -sr');
553+
$pfctl = $this->read_pfctl_rules();
518554
$this->assert_str_contains($pfctl->output, 'ridentifier ' . $rule->tracker->value . ' queue TestQueue2');
519555

520556
# Delete the rule and ensure the rule referencing the queue no longer exists
521557
$rule->delete();
522558
$shaper1->delete();
523559
$rule->apply();
524-
$pfctl = new Command('pfctl -sr');
560+
$pfctl = $this->read_pfctl_rules();
525561
$this->assert_str_does_not_contain(
526562
$pfctl->output,
527563
'ridentifier ' . $rule->tracker->value . ' queue TestQueue1',
@@ -577,7 +613,7 @@ class APIModelsFirewallRuleTestCase extends TestCase {
577613
$rule->create(apply: true);
578614

579615
# Ensure the rule with the queue is seen in pfctl
580-
$pfctl = new Command('pfctl -sr');
616+
$pfctl = $this->read_pfctl_rules();
581617
$this->assert_str_contains(
582618
$pfctl->output,
583619
'ridentifier ' . $rule->tracker->value . ' queue(TestQueue1, TestQueue2)',
@@ -587,7 +623,7 @@ class APIModelsFirewallRuleTestCase extends TestCase {
587623
$rule->defaultqueue->value = 'TestQueue2';
588624
$rule->ackqueue->value = 'TestQueue1';
589625
$rule->update(apply: true);
590-
$pfctl = new Command('pfctl -sr');
626+
$pfctl = $this->read_pfctl_rules();
591627
$this->assert_str_contains(
592628
$pfctl->output,
593629
'ridentifier ' . $rule->tracker->value . ' queue(TestQueue2, TestQueue1)',
@@ -597,7 +633,7 @@ class APIModelsFirewallRuleTestCase extends TestCase {
597633
$rule->delete();
598634
$shaper1->delete();
599635
$rule->apply();
600-
$pfctl = new Command('pfctl -sr');
636+
$pfctl = $this->read_pfctl_rules();
601637
$this->assert_str_does_not_contain(
602638
$pfctl->output,
603639
'ridentifier ' . $rule->tracker->value . ' queue(TestQueue1, TestQueue2)',
@@ -795,10 +831,9 @@ class APIModelsFirewallRuleTestCase extends TestCase {
795831
async: false,
796832
);
797833
$rule->create(apply: true);
798-
sleep(3); // Wait a bit to ensure device is not busy
799834

800835
# Ensure the dnpipe is correctly represented
801-
$pfctl = new Command('pfctl -sr');
836+
$pfctl = $this->read_pfctl_rules();
802837
$this->assert_str_contains(
803838
$pfctl->output,
804839
"ridentifier {$rule->tracker->value} dnpipe {$limiter->number->value}",
@@ -843,10 +878,9 @@ class APIModelsFirewallRuleTestCase extends TestCase {
843878
async: false,
844879
);
845880
$rule->create(apply: true);
846-
sleep(3); // Wait a bit to ensure device is not busy
847881

848882
# Check pfctl rules and ensure the dnpipe is correctly represented
849-
$pfctl = new Command('pfctl -sr');
883+
$pfctl = $this->read_pfctl_rules();
850884
$this->assert_str_contains(
851885
$pfctl->output,
852886
"ridentifier {$rule->tracker->value} dnqueue {$queue->number->value}",
@@ -894,10 +928,9 @@ class APIModelsFirewallRuleTestCase extends TestCase {
894928
async: false,
895929
);
896930
$rule->create(apply: true);
897-
sleep(3); // Wait a bit to ensure device is not busy
898931

899932
# Check pfctl rules and ensure the dnpipe is correctly represented
900-
$pfctl = new Command('pfctl -sr');
933+
$pfctl = $this->read_pfctl_rules();
901934
$this->assert_str_contains(
902935
$pfctl->output,
903936
"ridentifier {$rule->tracker->value} dnpipe({$limiter1->number->value}, {$limiter2->number->value})",

0 commit comments

Comments
 (0)