summaryrefslogtreecommitdiffstats
path: root/iptables
diff options
context:
space:
mode:
authorPhil Sutter <phil@nwl.cc>2019-09-17 20:27:27 +0200
committerPhil Sutter <phil@nwl.cc>2019-11-06 13:42:02 +0100
commit09cb517949e69c6ebfc4e755057b270f0dc99291 (patch)
tree7ec80e884adf9dd853da634757d618a2b4777058 /iptables
parent92991bbe12d77c6129b3a71b25c36fad6a27d75a (diff)
xtables-restore: Improve performance of --noflush operation
The reason for that full cache fetching when called with --noflush even before looking at any input data was that there might be a command requiring a rule cache following some rule add/insert ones which don't. At that point one needs to fetch rules from kernel and try to insert the local ones at the right spot which is non-trivial. At the same time there is a performance-critical use-case for --noflush, namely fast insertion of a bunch of rules in one go, avoiding the process spawn overhead. Optimize for this use-case by preloading input into a 64KB buffer to see if it fits. If so, search for commands requiring a rule cache. If there are none, skip initial full cache fetching. The above algorithm may abort at any point, so actual input parsing must happen in three stages: 1) parse all preloaded lines from 64KB buffer 2) parse any leftover line in line buffer (happens if input exceeds the preload buffer size) 3) parse remaining input from input file pointer Signed-off-by: Phil Sutter <phil@nwl.cc> Acked-by: Pablo Neira Ayuso <pablo@netfilter.org>
Diffstat (limited to 'iptables')
-rw-r--r--iptables/xtables-restore.c89
1 files changed, 82 insertions, 7 deletions
diff --git a/iptables/xtables-restore.c b/iptables/xtables-restore.c
index 5a534ca2..282aa153 100644
--- a/iptables/xtables-restore.c
+++ b/iptables/xtables-restore.c
@@ -248,22 +248,97 @@ static void xtables_restore_parse_line(struct nft_handle *h,
}
}
+/* Return true if given iptables-restore line will require a full cache.
+ * Typically these are commands referring to an existing rule
+ * (either by number or content) or commands listing the ruleset. */
+static bool cmd_needs_full_cache(char *cmd)
+{
+ char c, chain[32];
+ int rulenum, mcount;
+
+ mcount = sscanf(cmd, "-%c %31s %d", &c, chain, &rulenum);
+
+ if (mcount == 3)
+ return true;
+ if (mcount < 1)
+ return false;
+
+ switch (c) {
+ case 'D':
+ case 'C':
+ case 'S':
+ case 'L':
+ return true;
+ }
+
+ return false;
+}
+
+#define PREBUFSIZ 65536
+
void xtables_restore_parse(struct nft_handle *h,
const struct nft_xt_restore_parse *p)
{
struct nft_xt_restore_state state = {};
- char buffer[10240];
+ char preload_buffer[PREBUFSIZ] = {}, buffer[10240], *ptr;
- line = 0;
-
- if (!h->noflush)
+ if (!h->noflush) {
nft_fake_cache(h);
- else
- nft_build_cache(h, NULL);
+ } else {
+ ssize_t pblen = sizeof(preload_buffer);
+ bool do_cache = false;
+
+ ptr = preload_buffer;
+ while (fgets(buffer, sizeof(buffer), p->in)) {
+ size_t blen = strlen(buffer);
+
+ /* drop trailing newline; xtables_restore_parse_line()
+ * uses strtok() which replaces them by nul-characters,
+ * causing unpredictable string delimiting in
+ * preload_buffer */
+ if (buffer[blen - 1] == '\n')
+ buffer[blen - 1] = '\0';
+ else
+ blen++;
+
+ pblen -= blen;
+ if (pblen <= 0) {
+ /* buffer exhausted */
+ do_cache = true;
+ break;
+ }
- /* Grab standard input. */
+ if (cmd_needs_full_cache(buffer)) {
+ do_cache = true;
+ break;
+ }
+
+ /* copy string including terminating nul-char */
+ memcpy(ptr, buffer, blen);
+ ptr += blen;
+ buffer[0] = '\0';
+ }
+
+ if (do_cache)
+ nft_build_cache(h, NULL);
+ }
+
+ line = 0;
+ ptr = preload_buffer;
+ while (*ptr) {
+ h->error.lineno = ++line;
+ DEBUGP("%s: buffered line %d: '%s'\n", __func__, line, ptr);
+ xtables_restore_parse_line(h, p, &state, ptr);
+ ptr += strlen(ptr) + 1;
+ }
+ if (*buffer) {
+ h->error.lineno = ++line;
+ DEBUGP("%s: overrun line %d: '%s'\n", __func__, line, buffer);
+ xtables_restore_parse_line(h, p, &state, buffer);
+ }
while (fgets(buffer, sizeof(buffer), p->in)) {
h->error.lineno = ++line;
+ DEBUGP("%s: input line %d: '%s'\n", __func__, line, buffer);
xtables_restore_parse_line(h, p, &state, buffer);
}
if (state.in_table && p->commit) {