-/* $OpenBSD: pf.c,v 1.1151 2022/11/11 15:02:31 dlg Exp $ */
+/* $OpenBSD: pf.c,v 1.1152 2022/11/11 16:12:08 dlg Exp $ */
/*
* Copyright (c) 2001 Daniel Hartmeier
int pf_tcp_secret_init;
int pf_tcp_iss_off;
-int pf_npurge;
-struct task pf_purge_task = TASK_INITIALIZER(pf_purge, &pf_npurge);
-struct timeout pf_purge_to = TIMEOUT_INITIALIZER(pf_purge_timeout, NULL);
-
enum pf_test_status {
PF_TEST_FAIL = -1,
PF_TEST_OK,
/* END state table stuff */
-void
-pf_purge_timeout(void *unused)
-{
- /* XXX move to systqmp to avoid KERNEL_LOCK */
- task_add(systq, &pf_purge_task);
-}
+void pf_purge_states(void *);
+struct task pf_purge_states_task =
+ TASK_INITIALIZER(pf_purge_states, NULL);
+
+void pf_purge_states_tick(void *);
+struct timeout pf_purge_states_to =
+ TIMEOUT_INITIALIZER(pf_purge_states_tick, NULL);
+
+unsigned int pf_purge_expired_states(unsigned int, unsigned int);
+
+/*
+ * how many states to scan this interval.
+ *
+ * this is set when the timeout fires, and reduced by the task. the
+ * task will reschedule itself until the limit is reduced to zero,
+ * and then it adds the timeout again.
+ */
+unsigned int pf_purge_states_limit;
+
+/*
+ * limit how many states are processed with locks held per run of
+ * the state purge task.
+ */
+unsigned int pf_purge_states_collect = 64;
void
-pf_purge(void *xnloops)
+pf_purge_states_tick(void *null)
{
- int *nloops = xnloops;
+ unsigned int limit = pf_status.states;
+ unsigned int interval = pf_default_rule.timeout[PFTM_INTERVAL];
+
+ if (limit == 0) {
+ timeout_add_sec(&pf_purge_states_to, 1);
+ return;
+ }
/*
* process a fraction of the state table every second
- * Note:
- * we no longer need PF_LOCK() here, because
- * pf_purge_expired_states() uses pf_state_lock to maintain
- * consistency.
*/
- if (pf_default_rule.timeout[PFTM_INTERVAL] > 0)
- pf_purge_expired_states(1 + (pf_status.states
- / pf_default_rule.timeout[PFTM_INTERVAL]));
+ if (interval > 1)
+ limit /= interval;
+
+ pf_purge_states_limit = limit;
+ task_add(systqmp, &pf_purge_states_task);
+}
+
+void
+pf_purge_states(void *null)
+{
+ unsigned int limit;
+ unsigned int scanned;
+
+ limit = pf_purge_states_limit;
+ if (limit < pf_purge_states_collect)
+ limit = pf_purge_states_collect;
+
+ scanned = pf_purge_expired_states(limit, pf_purge_states_collect);
+ if (scanned >= pf_purge_states_limit) {
+ /* we've run out of states to scan this "interval" */
+ timeout_add_sec(&pf_purge_states_to, 1);
+ return;
+ }
+
+ pf_purge_states_limit -= scanned;
+ task_add(systqmp, &pf_purge_states_task);
+}
+
+void pf_purge_tick(void *);
+struct timeout pf_purge_to =
+ TIMEOUT_INITIALIZER(pf_purge_tick, NULL);
+
+void pf_purge(void *);
+struct task pf_purge_task =
+ TASK_INITIALIZER(pf_purge, NULL);
+
+void
+pf_purge_tick(void *null)
+{
+ task_add(systqmp, &pf_purge_task);
+}
+
+void
+pf_purge(void *null)
+{
+ unsigned int interval = max(1, pf_default_rule.timeout[PFTM_INTERVAL]);
+
+ /* XXX is NET_LOCK necessary? */
NET_LOCK();
PF_LOCK();
- /* purge other expired types every PFTM_INTERVAL seconds */
- if (++(*nloops) >= pf_default_rule.timeout[PFTM_INTERVAL])
- pf_purge_expired_src_nodes();
+
+ pf_purge_expired_src_nodes();
+
PF_UNLOCK();
/*
* Fragments don't require PF_LOCK(), they use their own lock.
*/
- if ((*nloops) >= pf_default_rule.timeout[PFTM_INTERVAL]) {
- pf_purge_expired_fragments();
- *nloops = 0;
- }
+ pf_purge_expired_fragments();
NET_UNLOCK();
- timeout_add_sec(&pf_purge_to, 1);
+ /* interpret the interval as idle time between runs */
+ timeout_add_sec(&pf_purge_to, interval);
}
int32_t
pf_status.states--;
}
-void
-pf_purge_expired_states(u_int32_t maxcheck)
+unsigned int
+pf_purge_expired_states(const unsigned int limit, const unsigned int collect)
{
/*
* this task/thread/context/whatever is the only thing that
struct pf_state *st;
SLIST_HEAD(pf_state_gcl, pf_state) gcl = SLIST_HEAD_INITIALIZER(gcl);
time_t now;
+ unsigned int scanned;
+ unsigned int collected = 0;
PF_ASSERT_UNLOCKED();
if (head == NULL) {
/* the list is empty */
rw_exit_read(&pf_state_list.pfs_rwl);
- return;
+ return (limit);
}
/* (re)start at the front of the list */
now = getuptime();
- do {
+ for (scanned = 0; scanned < limit; scanned++) {
uint8_t stimeout = cur->timeout;
+ unsigned int limited = 0;
if ((stimeout == PFTM_UNLINKED) ||
(pf_state_expires(cur, stimeout) <= now)) {
st = pf_state_ref(cur);
SLIST_INSERT_HEAD(&gcl, st, gc_list);
+
+ if (++collected >= collect)
+ limited = 1;
}
/* don't iterate past the end of our view of the list */
if (cur == tail) {
+ scanned = limit;
cur = NULL;
break;
}
cur = TAILQ_NEXT(cur, entry_list);
- } while (maxcheck--);
+
+ /* don't spend too much time here. */
+ if (ISSET(READ_ONCE(curcpu()->ci_schedstate.spc_schedflags),
+ SPCF_SHOULDYIELD) || limited)
+ break;
+ }
rw_exit_read(&pf_state_list.pfs_rwl);
if (SLIST_EMPTY(&gcl))
- return;
+ return (scanned);
NET_LOCK();
rw_enter_write(&pf_state_list.pfs_rwl);
SLIST_REMOVE_HEAD(&gcl, gc_list);
pf_state_unref(st);
}
+
+ return (scanned);
}
int