Adapted version of commit 999eaa241212d3952ddff39a99d0d55a74e3639e Author: Lorenzo Colitti Date: Thu Mar 16 16:55:02 2017 +0900 iptables-restore: support acquiring the lock. Currently, ip[6]tables-restore does not perform any locking, so it is not safe to use concurrently with ip[6]tables. This patch makes ip[6]tables-restore wait for the lock if -w was specified. Arguments to -w and -W are supported in the same was as they are in ip[6]tables. The lock is not acquired on startup. Instead, it is acquired when a new table handle is created (on encountering '*') and released when the table is committed (COMMIT). This makes it possible to keep long-running iptables-restore processes in the background (for example, reading commands from a pipe opened by a system management daemon) and simultaneously run iptables commands. If -w is not specified, then the command proceeds without taking the lock. Tested as follows: 1. Run iptables-restore -w, and check that iptables commands work with or without -w. 2. Type "*filter" into the iptables-restore input. Verify that a) ip[6]tables commands without -w fail with "another app is currently holding the xtables lock...". b) ip[6]tables commands with "-w 2" fail after 2 seconds. c) ip[6]tables commands with "-w" hang until "COMMIT" is typed into the iptables-restore window. 3. With the lock held by an ip6tables-restore process: strace -e flock /tmp/iptables/sbin/iptables-restore -w 1 -W 100000 shows 11 calls to flock and fails. 4. Run an iptables-restore with -w and one without -w, and check: a) Type "*filter" in the first and then the second, and the second exits with an error. b) Type "*filter" in the second and "*filter" "-S" "COMMIT" into the first. The rules are listed only when the first copy sees "COMMIT". Signed-off-by: Narayan Kamath Signed-off-by: Lorenzo Colitti Signed-off-by: Pablo Neira Ayuso diff -up iptables-1.4.21/iptables/ip6tables.c.restore_support_acquiring_the_lock iptables-1.4.21/iptables/ip6tables.c --- iptables-1.4.21/iptables/ip6tables.c.restore_support_acquiring_the_lock 2017-04-05 14:55:52.561008864 +0200 +++ iptables-1.4.21/iptables/ip6tables.c 2017-04-05 14:55:52.564008888 +0200 @@ -1767,7 +1767,7 @@ int do_command6(int argc, char *argv[], generic_opt_check(command, cs.options); /* Attempt to acquire the xtables lock */ - if (!restore && !xtables_lock(wait, &wait_interval)) { + if (!restore && xtables_lock(wait, &wait_interval) == XT_LOCK_BUSY) { fprintf(stderr, "Another app is currently holding the xtables lock. "); if (wait == 0) fprintf(stderr, "Perhaps you want to use the -w option?\n"); diff -up iptables-1.4.21/iptables/ip6tables-restore.c.restore_support_acquiring_the_lock iptables-1.4.21/iptables/ip6tables-restore.c --- iptables-1.4.21/iptables/ip6tables-restore.c.restore_support_acquiring_the_lock 2013-11-22 12:18:13.000000000 +0100 +++ iptables-1.4.21/iptables/ip6tables-restore.c 2017-04-05 14:58:41.513393942 +0200 @@ -15,6 +15,7 @@ #include #include #include "ip6tables.h" +#include "xshared.h" #include "xtables.h" #include "libiptc/libip6tc.h" #include "ip6tables-multi.h" @@ -25,18 +26,24 @@ #define DEBUGP(x, args...) #endif -static int binary = 0, counters = 0, verbose = 0, noflush = 0; +static int binary = 0, counters = 0, verbose = 0, noflush = 0, wait = 0; + +static struct timeval wait_interval = { + .tv_sec = 1, +}; /* Keeping track of external matches and targets. */ static const struct option options[] = { - {.name = "binary", .has_arg = false, .val = 'b'}, - {.name = "counters", .has_arg = false, .val = 'c'}, - {.name = "verbose", .has_arg = false, .val = 'v'}, - {.name = "test", .has_arg = false, .val = 't'}, - {.name = "help", .has_arg = false, .val = 'h'}, - {.name = "noflush", .has_arg = false, .val = 'n'}, - {.name = "modprobe", .has_arg = true, .val = 'M'}, - {.name = "table", .has_arg = true, .val = 'T'}, + {.name = "binary", .has_arg = 0, .val = 'b'}, + {.name = "counters", .has_arg = 0, .val = 'c'}, + {.name = "verbose", .has_arg = 0, .val = 'v'}, + {.name = "test", .has_arg = 0, .val = 't'}, + {.name = "help", .has_arg = 0, .val = 'h'}, + {.name = "noflush", .has_arg = 0, .val = 'n'}, + {.name = "modprobe", .has_arg = 1, .val = 'M'}, + {.name = "table", .has_arg = 1, .val = 'T'}, + {.name = "wait", .has_arg = 2, .val = 'w'}, + {.name = "wait-interval", .has_arg = 2, .val = 'W'}, {NULL}, }; @@ -44,14 +51,16 @@ static void print_usage(const char *name static void print_usage(const char *name, const char *version) { - fprintf(stderr, "Usage: %s [-b] [-c] [-v] [-t] [-h]\n" + fprintf(stderr, "Usage: %s [-b] [-c] [-v] [-t] [-h] [-w secs] [-W usecs]\n" " [ --binary ]\n" " [ --counters ]\n" " [ --verbose ]\n" " [ --test ]\n" " [ --help ]\n" + " [ --wait=\n" + " [ --wait-interval=\n" " [ --noflush ]\n" - " [ --modprobe=]\n", name); + " [ --modprobe=]\n", name); exit(1); } @@ -182,7 +191,7 @@ int ip6tables_restore_main(int argc, cha { struct xtc_handle *handle = NULL; char buffer[10240]; - int c; + int c, lock; char curtable[XT_TABLE_MAXNAMELEN + 1]; FILE *in; int in_table = 0, testing = 0; @@ -190,6 +199,7 @@ int ip6tables_restore_main(int argc, cha const struct xtc_ops *ops = &ip6tc_ops; line = 0; + lock = XT_LOCK_NOT_ACQUIRED; ip6tables_globals.program_name = "ip6tables-restore"; c = xtables_init_all(&ip6tables_globals, NFPROTO_IPV6); @@ -204,7 +214,7 @@ int ip6tables_restore_main(int argc, cha init_extensions6(); #endif - while ((c = getopt_long(argc, argv, "bcvthnM:T:", options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "bcvthnwWM:T:", options, NULL)) != -1) { switch (c) { case 'b': binary = 1; @@ -225,6 +235,12 @@ int ip6tables_restore_main(int argc, cha case 'n': noflush = 1; break; + case 'w': + wait = parse_wait_time(argc, argv); + break; + case 'W': + parse_wait_interval(argc, argv, &wait_interval); + break; case 'M': xtables_modprobe_program = optarg; break; @@ -269,8 +285,23 @@ int ip6tables_restore_main(int argc, cha DEBUGP("Not calling commit, testing\n"); ret = 1; } + + /* Done with the current table, release the lock. */ + if (lock >= 0) { + xtables_unlock(lock); + lock = XT_LOCK_NOT_ACQUIRED; + } + in_table = 0; } else if ((buffer[0] == '*') && (!in_table)) { + /* Acquire a lock before we create a new table handle */ + lock = xtables_lock(wait, &wait_interval); + if (lock == XT_LOCK_BUSY) { + fprintf(stderr, "Another app is currently holding the xtables lock. " + "Perhaps you want to use the -w option?\n"); + exit(RESOURCE_PROBLEM); + } + /* New table */ char *table; diff -up iptables-1.4.21/iptables/iptables.c.restore_support_acquiring_the_lock iptables-1.4.21/iptables/iptables.c --- iptables-1.4.21/iptables/iptables.c.restore_support_acquiring_the_lock 2017-04-05 14:55:52.562008872 +0200 +++ iptables-1.4.21/iptables/iptables.c 2017-04-05 14:55:52.564008888 +0200 @@ -1754,7 +1754,7 @@ int do_command4(int argc, char *argv[], generic_opt_check(command, cs.options); /* Attempt to acquire the xtables lock */ - if (!restore && !xtables_lock(wait, &wait_interval)) { + if (!restore && xtables_lock(wait, &wait_interval) == XT_LOCK_BUSY) { fprintf(stderr, "Another app is currently holding the xtables lock. "); if (wait == 0) fprintf(stderr, "Perhaps you want to use the -w option?\n"); diff -up iptables-1.4.21/iptables/iptables-restore.c.restore_support_acquiring_the_lock iptables-1.4.21/iptables/iptables-restore.c --- iptables-1.4.21/iptables/iptables-restore.c.restore_support_acquiring_the_lock 2013-11-22 12:18:13.000000000 +0100 +++ iptables-1.4.21/iptables/iptables-restore.c 2017-04-05 15:00:17.389179935 +0200 @@ -12,6 +12,7 @@ #include #include #include "iptables.h" +#include "xshared.h" #include "xtables.h" #include "libiptc/libiptc.h" #include "iptables-multi.h" @@ -22,18 +23,24 @@ #define DEBUGP(x, args...) #endif -static int binary = 0, counters = 0, verbose = 0, noflush = 0; +static int binary = 0, counters = 0, verbose = 0, noflush = 0, wait = 0; + +static struct timeval wait_interval = { + .tv_sec = 1, +}; /* Keeping track of external matches and targets. */ static const struct option options[] = { - {.name = "binary", .has_arg = false, .val = 'b'}, - {.name = "counters", .has_arg = false, .val = 'c'}, - {.name = "verbose", .has_arg = false, .val = 'v'}, - {.name = "test", .has_arg = false, .val = 't'}, - {.name = "help", .has_arg = false, .val = 'h'}, - {.name = "noflush", .has_arg = false, .val = 'n'}, - {.name = "modprobe", .has_arg = true, .val = 'M'}, - {.name = "table", .has_arg = true, .val = 'T'}, + {.name = "binary", .has_arg = 0, .val = 'b'}, + {.name = "counters", .has_arg = 0, .val = 'c'}, + {.name = "verbose", .has_arg = 0, .val = 'v'}, + {.name = "test", .has_arg = 0, .val = 't'}, + {.name = "help", .has_arg = 0, .val = 'h'}, + {.name = "noflush", .has_arg = 0, .val = 'n'}, + {.name = "modprobe", .has_arg = 1, .val = 'M'}, + {.name = "table", .has_arg = 1, .val = 'T'}, + {.name = "wait", .has_arg = 2, .val = 'w'}, + {.name = "wait-interval", .has_arg = 2, .val = 'W'}, {NULL}, }; @@ -43,15 +50,17 @@ static void print_usage(const char *name static void print_usage(const char *name, const char *version) { - fprintf(stderr, "Usage: %s [-b] [-c] [-v] [-t] [-h]\n" + fprintf(stderr, "Usage: %s [-b] [-c] [-v] [-t] [-h] [-W usecs]\n" " [ --binary ]\n" " [ --counters ]\n" " [ --verbose ]\n" " [ --test ]\n" " [ --help ]\n" " [ --noflush ]\n" + " [ --wait=\n" + " [ --wait-interval=\n" " [ --table= ]\n" - " [ --modprobe=]\n", name); + " [ --modprobe=]\n", name); exit(1); } @@ -182,7 +191,7 @@ iptables_restore_main(int argc, char *ar { struct xtc_handle *handle = NULL; char buffer[10240]; - int c; + int c, lock; char curtable[XT_TABLE_MAXNAMELEN + 1]; FILE *in; int in_table = 0, testing = 0; @@ -190,6 +199,7 @@ iptables_restore_main(int argc, char *ar const struct xtc_ops *ops = &iptc_ops; line = 0; + lock = XT_LOCK_NOT_ACQUIRED; iptables_globals.program_name = "iptables-restore"; c = xtables_init_all(&iptables_globals, NFPROTO_IPV4); @@ -204,7 +214,7 @@ iptables_restore_main(int argc, char *ar init_extensions4(); #endif - while ((c = getopt_long(argc, argv, "bcvthnM:T:", options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "bcvthnwWM:T:", options, NULL)) != -1) { switch (c) { case 'b': binary = 1; @@ -225,6 +235,12 @@ iptables_restore_main(int argc, char *ar case 'n': noflush = 1; break; + case 'w': + wait = parse_wait_time(argc, argv); + break; + case 'W': + parse_wait_interval(argc, argv, &wait_interval); + break; case 'M': xtables_modprobe_program = optarg; break; @@ -269,8 +285,23 @@ iptables_restore_main(int argc, char *ar DEBUGP("Not calling commit, testing\n"); ret = 1; } + + /* Done with the current table, release the lock. */ + if (lock >= 0) { + xtables_unlock(lock); + lock = XT_LOCK_NOT_ACQUIRED; + } + in_table = 0; } else if ((buffer[0] == '*') && (!in_table)) { + /* Acquire a lock before we create a new table handle */ + lock = xtables_lock(wait, &wait_interval); + if (lock == XT_LOCK_BUSY) { + fprintf(stderr, "Another app is currently holding the xtables lock. " + "Perhaps you want to use the -w option?\n"); + exit(RESOURCE_PROBLEM); + } + /* New table */ char *table; diff -up iptables-1.4.21/iptables/xshared.c.restore_support_acquiring_the_lock iptables-1.4.21/iptables/xshared.c --- iptables-1.4.21/iptables/xshared.c.restore_support_acquiring_the_lock 2017-04-05 14:55:52.562008872 +0200 +++ iptables-1.4.21/iptables/xshared.c 2017-04-05 14:55:52.565008896 +0200 @@ -246,7 +246,7 @@ void xs_init_match(struct xtables_match match->init(match->m); } -bool xtables_lock(int wait, struct timeval *wait_interval) +int xtables_lock(int wait, struct timeval *wait_interval) { struct timeval time_left, wait_time; int fd, i = 0; @@ -256,22 +256,22 @@ bool xtables_lock(int wait, struct timev fd = open(XT_LOCK_NAME, O_CREAT, 0600); if (fd < 0) - return true; + return XT_LOCK_UNSUPPORTED; if (wait == -1) { if (flock(fd, LOCK_EX) == 0) - return true; + return fd; fprintf(stderr, "Can't lock %s: %s\n", XT_LOCK_NAME, strerror(errno)); - return false; + return XT_LOCK_BUSY; } while (1) { if (flock(fd, LOCK_EX | LOCK_NB) == 0) - return true; + return fd; else if (timercmp(&time_left, wait_interval, <)) - return false; + return XT_LOCK_BUSY; if (++i % 10 == 0) { fprintf(stderr, "Another app is currently holding the xtables lock; " @@ -285,6 +285,12 @@ bool xtables_lock(int wait, struct timev } } +void xtables_unlock(int lock) +{ + if (lock >= 0) + close(lock); +} + int parse_wait_time(int argc, char *argv[]) { int wait = -1; diff -up iptables-1.4.21/iptables/xshared.h.restore_support_acquiring_the_lock iptables-1.4.21/iptables/xshared.h --- iptables-1.4.21/iptables/xshared.h.restore_support_acquiring_the_lock 2017-04-05 14:55:52.562008872 +0200 +++ iptables-1.4.21/iptables/xshared.h 2017-04-05 14:55:52.565008896 +0200 @@ -84,7 +84,28 @@ extern struct xtables_match *load_proto( extern int subcmd_main(int, char **, const struct subcommand *); extern void xs_init_target(struct xtables_target *); extern void xs_init_match(struct xtables_match *); -bool xtables_lock(int wait, struct timeval *wait_interval); + +/** + * Values for the iptables lock. + * + * A value >= 0 indicates the lock filedescriptor. Other values are: + * + * XT_LOCK_UNSUPPORTED : The system does not support locking, execution will + * proceed lockless. + * + * XT_LOCK_BUSY : The lock was held by another process. xtables_lock only + * returns this value when |wait| == false. If |wait| == true, xtables_lock + * will not return unless the lock has been acquired. + * + * XT_LOCK_NOT_ACQUIRED : We have not yet attempted to acquire the lock. + */ +enum { + XT_LOCK_BUSY = -1, + XT_LOCK_UNSUPPORTED = -2, + XT_LOCK_NOT_ACQUIRED = -3, +}; +extern int xtables_lock(int wait, struct timeval *tv); +extern void xtables_unlock(int lock); int parse_wait_time(int argc, char *argv[]); void parse_wait_interval(int argc, char *argv[], struct timeval *wait_interval);