Firewall: Schedule - add missing schedules support in "Firewall: Rules [new]" and refactor existing usage to avoid duplication of logic. closes https://github.com/opnsense/core/issues/9690

This commit moves the schedule logic out of filter_core_rules_user() where it didn't belong in the first place.
Since we need legacy code to determine schedule behavior, we cannot move it to the plugin classes easily, instead sweep all registered rules after registration so we can process "sched" for all of them in the same way.

We can next add a simple action into the model to ask if there actually is a schedule, which pf_cron() needs to schedule the rule updates.

Finally add an icon and link into the mvc page to refer to the schedule itself.
This commit is contained in:
Ad Schellevis 2026-02-01 13:21:26 +01:00
parent 3bcdae70f7
commit a5fed616a5
7 changed files with 78 additions and 39 deletions

View file

@ -187,8 +187,8 @@ function filter_configure_sync($verbose = false, $load_aliases = true)
filter_core_bootstrap($fw);
$cnfint = iterator_to_array($fw->getInterfaceMapping());
plugins_firewall($fw);
// register user rules, returns kill states for schedules
$sched_kill_states = filter_core_rules_user($fw);
// register legacy user rules
filter_core_rules_user($fw);
// manual outbound nat rules
if (
@ -256,6 +256,33 @@ function filter_configure_sync($verbose = false, $load_aliases = true)
foreach ((new OPNsense\Firewall\DNat())->rule->sortedBy(['sequence']) as $key => $rule) {
$fw->registerForwardRule(600, $rule->getNodeContent());
}
/**
* XXX: Apply schedules on all installed rules so we can use both legacy and MVC ones.
* Eventually this needs to be replaced, but as this requires legacy code, we cannot do that easily now
* without increasing impact.
*/
$sched_kill_states = [];
foreach ($fw->iterateFilterRules() as $rule) {
$rrule = $rule->getRawRule();
if (!empty($rrule['sched'])) {
$descr = $rrule['descr'] ?? '';
$descr .= " ({$rrule['sched']})";
$rule->updateDescription($descr);
foreach ($config['schedules']['schedule'] as $sched) {
if ($sched['name'] == $rrule['sched']) {
if (!filter_get_time_based_rule_status($sched)) {
if (!isset($config['system']['schedule_states'])) {
$sched_kill_states[] = $rrule['label'];
}
/* disable rule, suffix label to mark end of schedule */
$rule->disable();
$rule->updateDescription("[FIN] " . $descr);
}
break;
}
}
}
}
openlog("firewall", LOG_DAEMON, LOG_LOCAL4);

View file

@ -667,14 +667,12 @@ function filter_core_rules_system($fw, $defaults)
}
/**
* register user rules, returns kill states for schedules
* register user rules
*/
function filter_core_rules_user($fw)
{
global $config;
$sched_kill_states = [];
foreach ((new \OPNsense\Firewall\Group())->ifgroupentry->iterateItems() as $node) {
$ifgroups[(string)$node->ifname] = !empty((string)$node->sequence) ? (string)$node->sequence : 0;
}
@ -696,9 +694,6 @@ function filter_core_rules_user($fw)
if (empty($rule['descr'])) {
$rule['descr'] = '';
}
if (!empty($rule['sched'])) {
$rule['descr'] .= " ({$rule['sched']})";
}
if (isset($rule['floating'])) {
$prio = 200000;
@ -707,25 +702,7 @@ function filter_core_rules_user($fw)
} else {
$prio = 400000;
}
/* is a time based rule schedule attached? */
if (!empty($rule['sched']) && !empty($config['schedules'])) {
foreach ($config['schedules']['schedule'] as $sched) {
if ($sched['name'] == $rule['sched']) {
if (!filter_get_time_based_rule_status($sched)) {
if (!isset($config['system']['schedule_states'])) {
$sched_kill_states[] = $rule['label'];
}
/* disable rule, suffix label to mark end of schedule */
$rule['disabled'] = true;
$rule['descr'] = "[FIN] " . $rule['descr'];
}
break;
}
}
}
$fw->registerFilterRule($prio, $rule);
}
}
return $sched_kill_states;
}

View file

@ -58,36 +58,31 @@ function pf_cron()
{
global $config;
$jobs = array();
$jobs = [];
if (isset($config['filter']['rule'])) {
foreach ($config['filter']['rule'] as $rule) {
if (empty($rule['disabled']) && !empty($rule['sched'])) {
$jobs[]['autocron'] = array('/usr/bin/logger "reload filter for configured schedules" ; /usr/local/etc/rc.filter_configure', '1,16,31,46');
break;
}
}
if ((new OPNsense\Firewall\Filter(true))->hasSchedule()) {
$jobs[]['autocron'] = ['/usr/bin/logger "reload filter for configured schedules" ; /usr/local/etc/rc.filter_configure', '1,16,31,46'];
}
/* bogons fetch always set in default config.xml */
switch ($config['system']['bogons']['interval']) {
case 'daily':
$jobs[]['autocron'] = array('/usr/local/sbin/configctl -d filter schedule bogons', '1', '3', '*', '*', '*');
$jobs[]['autocron'] = ['/usr/local/sbin/configctl -d filter schedule bogons', '1', '3', '*', '*', '*'];
break;
case 'weekly':
$jobs[]['autocron'] = array('/usr/local/sbin/configctl -d filter schedule bogons', '1', '3', '*', '*', '0');
$jobs[]['autocron'] = ['/usr/local/sbin/configctl -d filter schedule bogons', '1', '3', '*', '*', '0'];
break;
case 'monthly':
default:
$jobs[]['autocron'] = array('/usr/local/sbin/configctl -d filter schedule bogons', '1', '3', '1', '*', '*');
$jobs[]['autocron'] = ['/usr/local/sbin/configctl -d filter schedule bogons', '1', '3', '1', '*', '*'];
break;
}
$jobs[]['autocron'] = array(
$jobs[]['autocron'] = [
'/usr/local/bin/flock -n -E 0 -o /tmp/filter_update_tables.lock ' .
'/usr/local/opnsense/scripts/filter/update_tables.py --quick',
'*'
);
];
return $jobs;
}

View file

@ -286,6 +286,7 @@
<advanced>true</advanced>
<grid_view>
<visible>false</visible>
<formatter>sched</formatter>
</grid_view>
</field>
<!-- XXX: start as advanced, promote to non-advanced feature in a later version -->

View file

@ -476,6 +476,16 @@ abstract class Rule
return 'internal2'; // late
}
public function updateDescription($descr)
{
$this->rule['descr'] = $descr;
}
public function disable()
{
$this->rule['disabled'] = 1;
}
/**
* return raw rule
*/

View file

@ -366,4 +366,22 @@ class Filter extends BaseModel
}
return false;
}
public function hasSchedule()
{
foreach ($this->rules->rule->iterateItems() as $rule) {
if (!$rule->sched->isEmpty() && !$rule->enabled->isEmpty()) {
return true;
}
}
$cfg = (\OPNsense\Core\Config::getInstance())->object();
if (isset($cfg->filter->rule)) {
foreach ($cfg->filter->children() as $tag => $rule) {
if ($tag === 'rule' && !empty($rule->sched) && empty((string)$rule->disabled)) {
return true;
}
}
}
return false;
}
}

View file

@ -561,6 +561,17 @@
</div>
`;
},
sched: function(column, row) {
if (row[column.id] === '') {
return "";
}
return `
${row[column.id]} &nbsp;
<a href="/firewall_schedule_edit.php?name=${row[column.id]}" data-toggle="tooltip" title="{{ lang._('Edit') }}">
<i class="fa fa-calendar text-muted"></i>
</a>
`;
},
},
},
commands: {