Hi all,
I've recently switched to updating my OPNsense firewall via the API, adding/removing IPs from an alias and refreshing the rules proactively rather than polling. My floating firewall rule drops all packets to/from IPs in that alias.
The problem is that even with API-driven updates, the firewall can take upwards of 20 minutes to actually reflect changes. I've seen similar reports in:
https://www.reddit.com/r/OPNsenseFirewall/comments/13l6alq/opnsense_states/
https://forum.opnsense.org/index.php?topic=31995.0
https://forum.opnsense.org/index.php?topic=21074.0
https://forum.opnsense.org/index.php?topic=30962.0
https://forum.opnsense.org/index.php?topic=38305.0
https://forum.opnsense.org/index.php?topic=31706.0
https://github.com/opnsense/core/issues/6404
From what I understand, using flush_states or kill_states via the API can force a refresh, but both seem to disconnect all active connections, which isn't a viable solution for me.
My question:
Is there a way to force a single firewall state (or a set of states related to a specific IP) to refresh without dropping all other connections?
I haven't found any official documentation on this, so any guidance, undocumented API endpoints, or workarounds would be greatly appreciated.
Thanks!
well it seems it's all boiled down to one thing
https://docs.opnsense.org/development/api/core/diagnostics.html
(https://cqdx.uk/cj.%5Bpng%5D.CG)
https://forum.opnsense.org/index.php?topic=31706.0
how to operate this api
the documentation on it is... well, it's totally non existent.
so...
does anyone have a clue how this works?
https://github.com/opnsense/core/blob/d9ea39fdbda543b9c73ee6e228747cdaff5a56e3/src/opnsense/mvc/app/controllers/OPNsense/Diagnostics/Api/FirewallController.php#L282
https://github.com/opnsense/core/blob/d9ea39fdbda543b9c73ee6e228747cdaff5a56e3/src/opnsense/mvc/app/controllers/OPNsense/Base/ApiControllerBase.php#L96C88-L96C100
https://github.com/opnsense/core/blob/d9ea39fdbda543b9c73ee6e228747cdaff5a56e3/src/opnsense/mvc/app/controllers/OPNsense/Diagnostics/Api/FirewallController.php#L244
[solved]
del url is not del_state it's delstate, docs are wrong.
$del_url = $opnsense_host . "/api/diagnostics/firewall/delState";
query states first
$query_url = $opnsense_host . "/api/diagnostics/firewall/query_states";
get response
{
"label": "21d61fb65e9a253dc46b79181dc7044c",
"descr": "Default allow LAN to any rule",
"nat_addr": "",
"nat_port": "",
"gateway": "",
"iface": "all",
"proto": "udp",
"ipproto": "ipv4",
"flags": [],
"direction": "in",
"dst_addr": "185.211.73.104",
"dst_port": "41677",
"src_addr": "192.168.2.189",
"src_port": "3232",
"state": "MULTIPLE:MULTIPLE",
"age": "32:21:44",
"expires": "00:00:58",
"pkts": {
"out": 18123,
"in": 24102
},
"bytes": {
"out": 2349563,
"in": 2946399
},
"rule": 59,
"id": "09048e6900000000/f1c4a296",
"interface": "all"
}
id field:
"id": "09048e6900000000/f1c4a296",
is actually the stateid and creator id (anemic docs, non existent/wrong.)
list($stateid, $creatorid) = explode('/', $full_id);
pass as url params (anemic docs, non existent/wrong, says post, wrong.)
$del_url_full = $del_url . '/' . $stateid . '/' . $creatorid;
$ch = curl_init($del_url_full);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_USERPWD, $opnsense_key . ':' . $opnsense_secret);
curl_setopt($ch, CURLOPT_POSTFIELDS, '');
$del_resp = curl_exec($ch);
curl_close($ch);
attention; setting length, despite blank body (not mentioned in docs/non existent/wrong.)
curl_setopt($ch, CURLOPT_POSTFIELDS, '');
and by parsing through and deleting this way you can purge the states by ip.
function purgeStatesByIP($opnsense_host, $opnsense_key, $opnsense_secret, $ip) {
// Query all states
$query_url = $opnsense_host . "/api/diagnostics/firewall/query_states";
$ch = curl_init($query_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, $opnsense_key . ':' . $opnsense_secret);
$resp = curl_exec($ch);
curl_close($ch);
if (!$resp) {
throw new Exception("Failed to query states.");
}
$states = json_decode($resp, true);
if (!is_array($states)) {
throw new Exception("Invalid response: " . $resp);
}
$del_url = $opnsense_host . "/api/diagnostics/firewall/delState";
$deleted = [];
foreach ($states as $state) {
if ($state['src_addr'] === $ip || $state['dst_addr'] === $ip) {
// Split the "id" field into stateid + creatorid
list($stateid, $creatorid) = explode('/', $state['id']);
$del_url_full = $del_url . '/' . $stateid . '/' . $creatorid;
$ch = curl_init($del_url_full);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_USERPWD, $opnsense_key . ':' . $opnsense_secret);
curl_setopt($ch, CURLOPT_POSTFIELDS, ''); // Required even if empty
$del_resp = curl_exec($ch);
curl_close($ch);
$deleted[] = [
'state' => $state,
'response' => $del_resp
];
}
}
return $deleted;
}
// Example usage:
try {
$deletedStates = purgeStatesByIP(
"https://firewall.example.com",
"your_api_key",
"your_api_secret",
"185.211.73.104"
);
echo "Deleted " . count($deletedStates) . " states.\n";
foreach ($deletedStates as $d) {
echo " - " . $d['state']['src_addr'] . " → " . $d['state']['dst_addr'] . "\n";
}
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
hope this helps someone...
https://docs.opnsense.org/development/api/core/diagnostics.html
this page needs serious work
issue since 2021