iptables: Fix and refactor iterate_entries()
[platform/upstream/connman.git] / src / iptables.c
index e395af7..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;
@@ -295,6 +313,21 @@ static gboolean is_jump(struct connman_iptables_entry *e)
        return false;
 }
 
+static gboolean is_fallthrough(struct connman_iptables_entry *e)
+{
+       struct xt_entry_target *target;
+
+       target = ipt_get_target(e->entry);
+       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;
+}
+
 static gboolean is_chain(struct connman_iptables *table,
                                struct connman_iptables_entry *e)
 {
@@ -413,6 +446,16 @@ static void update_targets_reference(struct connman_iptables *table,
                                t->verdict += offset;
                }
        }
+
+       if (is_fallthrough(modified_entry)) {
+               t = (struct xt_standard_target *) ipt_get_target(modified_entry->entry);
+
+               t->verdict = entry_before->offset +
+                       modified_entry->entry->target_offset +
+                       ALIGN(sizeof(struct xt_standard_target));
+               t->target.u.target_size =
+                       ALIGN(sizeof(struct xt_standard_target));
+       }
 }
 
 static int iptables_add_entry(struct connman_iptables *table,
@@ -463,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;
 }
 
@@ -799,10 +843,16 @@ static gboolean is_same_ipt_entry(struct ipt_entry *i_e1,
 static gboolean is_same_target(struct xt_entry_target *xt_e_t1,
                                        struct xt_entry_target *xt_e_t2)
 {
+       unsigned int i;
+
        if (xt_e_t1 == NULL || xt_e_t2 == NULL)
                return FALSE;
 
-       if (strcmp(xt_e_t1->u.user.name, IPT_STANDARD_TARGET) == 0) {
+       if (strcmp(xt_e_t1->u.user.name, "") == 0 &&
+                       strcmp(xt_e_t2->u.user.name, "") == 0) {
+               /* fallthrough */
+               return TRUE;
+       } else if (strcmp(xt_e_t1->u.user.name, IPT_STANDARD_TARGET) == 0) {
                struct xt_standard_target *xt_s_t1;
                struct xt_standard_target *xt_s_t2;
 
@@ -817,6 +867,12 @@ static gboolean is_same_target(struct xt_entry_target *xt_e_t1,
 
                if (strcmp(xt_e_t1->u.user.name, xt_e_t2->u.user.name) != 0)
                        return FALSE;
+
+               for (i = 0; i < xt_e_t1->u.target_size -
+                               sizeof(struct xt_standard_target); i++) {
+                       if ((xt_e_t1->data[i] ^ xt_e_t2->data[i]) != 0)
+                               return FALSE;
+               }
        }
 
        return TRUE;
@@ -825,6 +881,8 @@ static gboolean is_same_target(struct xt_entry_target *xt_e_t1,
 static gboolean is_same_match(struct xt_entry_match *xt_e_m1,
                                struct xt_entry_match *xt_e_m2)
 {
+       unsigned int i;
+
        if (xt_e_m1 == NULL || xt_e_m2 == NULL)
                return FALSE;
 
@@ -837,6 +895,12 @@ static gboolean is_same_match(struct xt_entry_match *xt_e_m1,
        if (strcmp(xt_e_m1->u.user.name, xt_e_m2->u.user.name) != 0)
                return FALSE;
 
+       for (i = 0; i < xt_e_m1->u.match_size - sizeof(struct xt_entry_match);
+                       i++) {
+               if ((xt_e_m1->data[i] ^ xt_e_m2->data[i]) != 0)
+                       return FALSE;
+       }
+
        return TRUE;
 }
 
@@ -929,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;
@@ -943,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)
@@ -1221,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);
 }
@@ -1245,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)
@@ -1374,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);
 
@@ -1605,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;
@@ -1639,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;
@@ -1659,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;
 }
 
@@ -1866,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.
         */
@@ -1982,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);
 }
 
@@ -2088,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;
 }
@@ -2131,6 +2227,7 @@ int __connman_iptables_delete(const char *table_name,
                                ctx->xt_rm);
 out:
        cleanup_parse_context(ctx);
+       reset_xtables();
 
        return err;
 }
@@ -2197,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;
@@ -2209,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);
 
@@ -2223,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("");
@@ -2242,8 +2333,6 @@ int __connman_iptables_init(void)
 
        xtables_init_all(&iptables_globals, NFPROTO_IPV4);
 
-       flush_all_chains();
-
        return 0;
 }
 
@@ -2252,6 +2341,4 @@ void __connman_iptables_cleanup(void)
        DBG("");
 
        g_hash_table_destroy(table_hash);
-
-       xtables_free_opts(1);
 }