iptables: Fix and refactor iterate_entries()
[platform/upstream/connman.git] / src / iptables.c
index 97b9539..9e56d0b 100644 (file)
@@ -36,6 +36,7 @@
 
 #include "connman.h"
 
+void flush_table(const char *name);
 
 /*
  * Some comments on how the iptables API works (some of them from the
@@ -181,48 +182,65 @@ typedef int (*iterate_entries_cb_t)(struct ipt_entry *entry, int builtin,
                                        unsigned int hook,size_t size,
                                        unsigned int offset, void *user_data);
 
+static unsigned int next_hook_entry_index(unsigned int *valid_hooks)
+{
+       unsigned int h;
+
+       if (*valid_hooks == 0)
+               return NF_INET_NUMHOOKS;
+
+       h = __builtin_ffs(*valid_hooks) - 1;
+       *valid_hooks ^= (1 << h);
+
+       return h;
+}
+
 static int iterate_entries(struct ipt_entry *entries,
                                unsigned int valid_hooks,
                                unsigned int *hook_entry,
+                               unsigned int *underflow,
                                size_t size, iterate_entries_cb_t cb,
                                void *user_data)
 {
-       unsigned int i, h;
+       unsigned int offset, h, hook;
        int builtin, err;
        struct ipt_entry *entry;
 
-       if (valid_hooks != 0)
-               h = __builtin_ffs(valid_hooks) - 1;
-       else
-               h = NF_INET_NUMHOOKS;
+       h = next_hook_entry_index(&valid_hooks);
+       hook = h;
 
-       for (i = 0, entry = entries; i < size;
-                       i += entry->next_offset) {
+       for (offset = 0, entry = entries; offset < size;
+                       offset += entry->next_offset) {
                builtin = -1;
-               entry = (void *)entries + i;
+               entry = (void *)entries + offset;
 
                /*
-                * Find next valid hook which offset is higher
-                * or equal with the current offset.
+                * Updating builtin, hook and h is very tricky.
+                * The rules are:
+                * - builtin is only set to the current hook number
+                *   if the current entry is the hook entry (aka chain
+                *   head). And only for builtin chains, never for
+                *   the user chains.
+                * - hook is the current hook number. If we
+                *   look at user chains it needs to be NF_INET_NETNUMHOOKS.
+                * - h is the next hook entry. Thous we need to be carefully
+                *   not to access the table when h is NF_INET_NETNUMHOOKS.
                 */
-               if (h < NF_INET_NUMHOOKS) {
-                       if (hook_entry[h] < i) {
-                               valid_hooks ^= (1 << h);
-
-                               if (valid_hooks != 0)
-                                       h = __builtin_ffs(valid_hooks) - 1;
-                               else
-                                       h = NF_INET_NUMHOOKS;
-                       }
+               if (h < NF_INET_NUMHOOKS && hook_entry[h] == offset) {
+                       builtin = h;
+                       hook = h;
+               }
 
-                       if (hook_entry[h] == i)
-                               builtin = h;
+               if (h == NF_INET_NUMHOOKS)
+                       hook = h;
+
+               if (h < NF_INET_NUMHOOKS && underflow[h] <= offset) {
+                       h = next_hook_entry_index(&valid_hooks);
                }
 
-               err = cb(entry, builtin, h, size, i, user_data);
+               err = cb(entry, builtin, hook, size, offset, user_data);
                if (err < 0)
                        return err;
-
        }
 
        return 0;
@@ -300,9 +318,13 @@ static gboolean is_fallthrough(struct connman_iptables_entry *e)
        struct xt_entry_target *target;
 
        target = ipt_get_target(e->entry);
-       if (!strcmp(target->u.user.name, ""))
-               return true;
+       if (!g_strcmp0(target->u.user.name, IPT_STANDARD_TARGET)) {
+               struct xt_standard_target *t;
 
+               t = (struct xt_standard_target *)target;
+               if (t->verdict == 0)
+                       return true;
+       }
        return false;
 }
 
@@ -484,10 +506,11 @@ static int remove_table_entry(struct connman_iptables *table,
        table->size -= entry->entry->next_offset;
        removed = entry->entry->next_offset;
 
-       g_free(entry->entry);
-
        table->entries = g_list_remove(table->entries, entry);
 
+       g_free(entry->entry);
+       g_free(entry);
+
        return removed;
 }
 
@@ -970,11 +993,15 @@ static int iptables_delete_rule(struct connman_iptables *table,
                                struct xtables_rule_match *xt_rm)
 {
        struct connman_iptables_entry *entry;
-       GList *chain_tail, *list;
+       GList *chain_head, *chain_tail, *list;
        int builtin, removed;
 
        removed = 0;
 
+       chain_head = find_chain_head(table, chain_name);
+       if (chain_head == NULL)
+               return -EINVAL;
+
        chain_tail = find_chain_tail(table, chain_name);
        if (chain_tail == NULL)
                return -EINVAL;
@@ -984,12 +1011,13 @@ static int iptables_delete_rule(struct connman_iptables *table,
        if (list == NULL)
                return -EINVAL;
 
+       entry = chain_head->data;
+       builtin = entry->builtin;
+
        entry = list->data;
        if (entry == NULL)
                return -EINVAL;
 
-       builtin = entry->builtin;
-
        /* We have deleted a rule,
         * all references should be bumped accordingly */
        if (list->next != NULL)
@@ -1262,6 +1290,7 @@ static void dump_table(struct connman_iptables *table)
        iterate_entries(table->blob_entries->entrytable,
                        table->info->valid_hooks,
                        table->info->hook_entry,
+                       table->info->underflow,
                        table->blob_entries->size,
                        print_entry, dump_entry);
 }
@@ -1286,7 +1315,8 @@ static void dump_ipt_replace(struct ipt_replace *repl)
                repl->underflow[NF_IP_POST_ROUTING]);
 
        iterate_entries(repl->entries, repl->valid_hooks,
-                       repl->hook_entry, repl->size, print_entry, dump_entry);
+                       repl->hook_entry, repl->underflow,
+                       repl->size, print_entry, dump_entry);
 }
 
 static int iptables_get_entries(struct connman_iptables *table)
@@ -1415,7 +1445,8 @@ static struct connman_iptables *iptables_init(const char *table_name)
 
        iterate_entries(table->blob_entries->entrytable,
                        table->info->valid_hooks, table->info->hook_entry,
-                       table->blob_entries->size, add_entry, table);
+                       table->info->underflow, table->blob_entries->size,
+                       add_entry, table);
 
        g_hash_table_insert(table_hash, g_strdup(table_name), table);
 
@@ -1646,24 +1677,6 @@ static struct connman_iptables *pre_load_table(const char *table_name,
        return iptables_init(table_name);
 }
 
-static void clear_tables_flags(void)
-{
-       struct xtables_match *xt_m;
-       struct xtables_target *xt_t;
-
-       /*
-        * Clear all flags because the flags are only valid
-        * for one rule.
-        */
-       for (xt_m = xtables_matches; xt_m != NULL; xt_m = xt_m->next)
-               xt_m->mflags = 0;
-
-       for (xt_t = xtables_targets; xt_t != NULL; xt_t = xt_t->next) {
-               xt_t->tflags = 0;
-               xt_t->used = 0;
-       }
-}
-
 struct parse_context {
        int argc;
        char **argv;
@@ -1680,7 +1693,7 @@ static int prepare_getopt_args(const char *str, struct parse_context *ctx)
 
        tokens = g_strsplit_set(str, " ", -1);
 
-       for (i = 0; tokens[i]; i++);
+       i = g_strv_length(tokens);
 
        /* Add space for the argv[0] value */
        ctx->argc = i + 1;
@@ -1700,6 +1713,8 @@ static int prepare_getopt_args(const char *str, struct parse_context *ctx)
        for (i = 1; i < ctx->argc; i++)
                ctx->argv[i] = tokens[i - 1];
 
+       g_free(tokens);
+
        return 0;
 }
 
@@ -1907,13 +1922,6 @@ static int parse_rule_spec(struct connman_iptables *table,
                return -ENOMEM;
 
        /*
-        * As side effect parsing a rule sets some global flags
-        * which will be evaluated/verified. Let's reset them
-        * to ensure we can parse more than one rule.
-        */
-       clear_tables_flags();
-
-       /*
         * Tell getopt_long not to generate error messages for unknown
         * options and also reset optind back to 0.
         */
@@ -2023,14 +2031,60 @@ out:
        return err;
 }
 
+static void reset_xtables(void)
+{
+       struct xtables_match *xt_m;
+       struct xtables_target *xt_t;
+
+       /*
+        * As side effect parsing a rule sets some global flags
+        * which will be evaluated/verified. Let's reset them
+        * to ensure we can parse more than one rule.
+        *
+        * Clear all flags because the flags are only valid
+        * for one rule.
+        */
+       for (xt_m = xtables_matches; xt_m != NULL; xt_m = xt_m->next)
+               xt_m->mflags = 0;
+
+       for (xt_t = xtables_targets; xt_t != NULL; xt_t = xt_t->next) {
+               xt_t->tflags = 0;
+               xt_t->used = 0;
+       }
+
+       /*
+        * We need also to free the memory implicitly allocated
+        * during parsing (see xtables_options_xfrm()).
+        * Note xt_params is actually iptables_globals.
+        */
+       if (xt_params->opts != xt_params->orig_opts) {
+               g_free(xt_params->opts);
+               xt_params->opts = xt_params->orig_opts;
+       }
+       xt_params->option_offset = 0;
+}
+
 static void cleanup_parse_context(struct parse_context *ctx)
 {
+       struct xtables_rule_match *rm, *tmp;
+
        g_strfreev(ctx->argv);
        g_free(ctx->ip);
-       if (ctx->xt_t != NULL)
+       if (ctx->xt_t != NULL) {
                g_free(ctx->xt_t->t);
-       if (ctx->xt_m != NULL)
+               ctx->xt_t->t = NULL;
+       }
+       if (ctx->xt_m != NULL) {
                g_free(ctx->xt_m->m);
+               ctx->xt_m->m = NULL;
+       }
+       for (tmp = NULL, rm = ctx->xt_rm; rm != NULL; rm = rm->next) {
+               if (tmp != NULL)
+                       g_free(tmp);
+               tmp = rm;
+       }
+       g_free(tmp);
+
        g_free(ctx);
 }
 
@@ -2129,6 +2183,7 @@ int __connman_iptables_append(const char *table_name,
                                target_name, ctx->xt_t, ctx->xt_rm);
 out:
        cleanup_parse_context(ctx);
+       reset_xtables();
 
        return err;
 }
@@ -2172,6 +2227,7 @@ int __connman_iptables_delete(const char *table_name,
                                ctx->xt_rm);
 out:
        cleanup_parse_context(ctx);
+       reset_xtables();
 
        return err;
 }
@@ -2238,7 +2294,7 @@ static int flush_table_cb(struct ipt_entry *entry, int builtin,
        return 0;
 }
 
-static void flush_table(const char *name)
+void flush_table(const char *name)
 {
        GSList *chains = NULL, *list;
        struct connman_iptables *table;
@@ -2250,6 +2306,7 @@ static void flush_table(const char *name)
        iterate_entries(table->blob_entries->entrytable,
                        table->info->valid_hooks,
                        table->info->hook_entry,
+                       table->info->underflow,
                        table->blob_entries->size,
                        flush_table_cb, &chains);
 
@@ -2264,13 +2321,6 @@ static void flush_table(const char *name)
        g_slist_free_full(chains, g_free);
 }
 
-static void flush_all_chains(void)
-{
-       flush_table("filter");
-       flush_table("mangle");
-       flush_table("nat");
-}
-
 int __connman_iptables_init(void)
 {
        DBG("");
@@ -2283,8 +2333,6 @@ int __connman_iptables_init(void)
 
        xtables_init_all(&iptables_globals, NFPROTO_IPV4);
 
-       flush_all_chains();
-
        return 0;
 }
 
@@ -2293,6 +2341,4 @@ void __connman_iptables_cleanup(void)
        DBG("");
 
        g_hash_table_destroy(table_hash);
-
-       xtables_free_opts(1);
 }