iptables-test: Fix builtin chain rule addition
[framework/connectivity/connman.git] / tools / iptables-test.c
index 891d219..5634eae 100644 (file)
@@ -82,9 +82,8 @@ struct ipt_error_target {
 };
 
 struct connman_iptables_entry {
+       int offset;
        int builtin;
-       int std_target;
-       int jump_offset;
 
        struct ipt_entry *entry;
 };
@@ -99,6 +98,9 @@ struct connman_iptables {
        unsigned int old_entries;
        unsigned int size;
 
+       unsigned int underflow[NF_INET_NUMHOOKS];
+       unsigned int hook_entry[NF_INET_NUMHOOKS];
+
        GList *entries;
 };
 
@@ -158,16 +160,41 @@ static gboolean is_builtin_target(char *target_name)
        return FALSE;
 }
 
+static gboolean is_jump(struct connman_iptables_entry *e)
+{
+       struct xt_entry_target *target;
+
+       target = ipt_get_target(e->entry);
+
+       if (!strcmp(target->u.user.name, IPT_STANDARD_TARGET)) {
+               struct xt_standard_target *t;
+
+               t = (struct xt_standard_target *)target;
+
+               switch (t->verdict) {
+               case XT_RETURN:
+               case -NF_ACCEPT - 1:
+               case -NF_DROP - 1:
+               case -NF_QUEUE - 1:
+               case -NF_STOP - 1:
+                       return false;
+
+               default:
+                       return true;
+               }
+       }
+
+       return false;
+}
+
 static gboolean is_chain(struct connman_iptables *table,
                                struct connman_iptables_entry *e)
 {
-       int builtin;
        struct ipt_entry *entry;
        struct xt_entry_target *target;
 
        entry = e->entry;
-       builtin = is_hook_entry(table, entry);
-       if (builtin >= 0)
+       if (e->builtin >= 0)
                return TRUE;
 
        target = ipt_get_target(entry);
@@ -177,6 +204,34 @@ static gboolean is_chain(struct connman_iptables *table,
        return FALSE;
 }
 
+static GList *find_chain_head(struct connman_iptables *table,
+                               char *chain_name)
+{
+       GList *list;
+       struct connman_iptables_entry *head;
+       struct ipt_entry *entry;
+       struct xt_entry_target *target;
+       int builtin;
+
+       for (list = table->entries; list; list = list->next) {
+               head = list->data;
+               entry = head->entry;
+
+               /* Buit-in chain */
+               builtin = head->builtin;
+               if (builtin >= 0 && !strcmp(hooknames[builtin], chain_name))
+                       break;
+
+               /* User defined chain */
+               target = ipt_get_target(entry);
+               if (!strcmp(target->u.user.name, IPT_ERROR_TARGET) &&
+                   !strcmp((char *)target->data, chain_name))
+                       break;
+       }
+
+       return list;
+}
+
 static GList *find_chain_tail(struct connman_iptables *table,
                                char *chain_name)
 {
@@ -192,7 +247,7 @@ static GList *find_chain_tail(struct connman_iptables *table,
                entry = head->entry;
 
                /* Buit-in chain */
-               builtin = is_hook_entry(table, entry);
+               builtin = head->builtin;
                if (builtin >= 0 && !strcmp(hooknames[builtin], chain_name))
                        break;
 
@@ -221,10 +276,35 @@ static GList *find_chain_tail(struct connman_iptables *table,
        return g_list_last(table->entries);
 }
 
+static void update_offsets(struct connman_iptables *table)
+{
+       GList *list, *prev;
+       struct connman_iptables_entry *entry, *prev_entry;
+
+       for (list = table->entries; list; list = list->next) {
+               entry = list->data;
+
+               if (list == table->entries) {
+                       entry->offset = 0;
+
+                       continue;
+               }
+
+               prev = list->prev;
+               prev_entry = prev->data;
+
+               entry->offset = prev_entry->offset +
+                                       prev_entry->entry->next_offset;
+       }
+}
+
 static int connman_add_entry(struct connman_iptables *table,
-                               struct ipt_entry *entry, GList *before)
+                               struct ipt_entry *entry, GList *before,
+                                       int builtin)
 {
-       struct connman_iptables_entry *e;
+       GList *list;
+       struct connman_iptables_entry *e, *tmp, *entry_before;
+       struct xt_standard_target *t;
 
        if (table == NULL)
                return -1;
@@ -234,11 +314,71 @@ static int connman_add_entry(struct connman_iptables *table,
                return -1;
 
        e->entry = entry;
+       e->builtin = builtin;
 
        table->entries = g_list_insert_before(table->entries, before, e);
        table->num_entries++;
        table->size += entry->next_offset;
 
+       if (before == NULL) {
+               e->offset = table->size - entry->next_offset;
+
+               return 0;
+       }
+
+       entry_before = before->data;
+
+       /*
+        * We've just insterted a new entry. All references before it
+        * should be bumped accordingly.
+        */
+       for (list = table->entries; list != before; list = list->next) {
+               tmp = list->data;
+
+               if (!is_jump(tmp))
+                       continue;
+
+               t = (struct xt_standard_target *)ipt_get_target(tmp->entry);
+
+               if (t->verdict >= entry_before->offset)
+                       t->verdict += entry->next_offset;
+       }
+
+       update_offsets(table);
+
+       return 0;
+}
+
+static int connman_iptables_delete_chain(struct connman_iptables *table,
+                                               char *name)
+{
+       GList *chain_head, *chain_tail, *list, *next;
+       struct connman_iptables_entry *entry;
+
+       chain_head = find_chain_head(table, name);
+       if (chain_head == NULL)
+               return -EINVAL;
+
+       chain_tail = find_chain_tail(table, name);
+       if (chain_tail == NULL)
+               return -EINVAL;
+
+       list = chain_head;
+
+       while (list != chain_tail) {
+               entry = list->data;
+               next = g_list_next(list);
+
+               table->num_entries--;
+               table->size -= entry->entry->next_offset;
+
+               table->entries = g_list_remove(table->entries, list->data);
+
+               list = next;
+       }
+
+       update_offsets(table);
+
        return 0;
 }
 
@@ -280,7 +420,7 @@ static int connman_iptables_add_chain(struct connman_iptables *table,
        error->t.u.user.target_size = ALIGN(sizeof(struct ipt_error_target));
        strcpy(error->error, name);
 
-       if (connman_add_entry(table, entry_head, last) < 0)
+       if (connman_add_entry(table, entry_head, last, -1) < 0)
                goto err;
 
        /* tail entry */
@@ -300,7 +440,7 @@ static int connman_iptables_add_chain(struct connman_iptables *table,
                                ALIGN(sizeof(struct ipt_standard_target));
        standard->verdict = XT_RETURN;
 
-       if (connman_add_entry(table, entry_return, last) < 0)
+       if (connman_add_entry(table, entry_return, last, -1) < 0)
                goto err;
 
        return 0;
@@ -313,26 +453,23 @@ err:
 }
 
 static struct ipt_entry *
-new_builtin_rule(char *target_name,
-               char *match_name, int match_argc, char **match_argv)
+new_rule(struct connman_iptables *table,
+               char *target_name, struct xtables_target *xt_t,
+               char *match_name, struct xtables_match *xt_m)
 {
        struct ipt_entry *new_entry;
        size_t match_size, target_size;
-       struct xtables_match *xt_m;
-       struct xt_standard_target *target;
-
-       xt_m = NULL;
-       match_size = 0;
-
-       if (match_name) {
-               xt_m = xtables_find_match(match_name, XTF_TRY_LOAD, NULL);
-               if (xt_m == NULL)
-                       return NULL;
+       int is_builtin = is_builtin_target(target_name);
 
-               match_size = ALIGN(sizeof(struct xt_entry_match)) + xt_m->size;
-       }
+       if (xt_m)
+               match_size = xt_m->m->u.match_size;
+       else
+               match_size = 0;
 
-       target_size = ALIGN(sizeof(struct xt_standard_target));
+       if (xt_t)
+               target_size = ALIGN(xt_t->t->u.target_size);
+       else
+               target_size = ALIGN(sizeof(struct xt_standard_target));
 
        new_entry = g_try_malloc0(sizeof(struct ipt_entry) + target_size +
                                                                match_size);
@@ -342,69 +479,124 @@ new_builtin_rule(char *target_name,
        new_entry->target_offset = sizeof(struct ipt_entry) + match_size;
        new_entry->next_offset = sizeof(struct ipt_entry) + target_size +
                                                                match_size;
-
        if (xt_m) {
                struct xt_entry_match *entry_match;
 
                entry_match = (struct xt_entry_match *)new_entry->elems;
-               entry_match->u.match_size = match_size;
-               strcpy(entry_match->u.user.name, xt_m->name);
-               entry_match->u.user.revision = xt_m->revision;
-               if (xt_m->init != NULL)
-                       xt_m->init(entry_match);
+               memcpy(entry_match, xt_m->m, match_size);
        }
 
-       target = (struct xt_standard_target *)(new_entry->elems + match_size);
-       strcpy(target->target.u.user.name, IPT_STANDARD_TARGET);
-       target->target.u.user.target_size =
-                               ALIGN(sizeof(struct ipt_standard_target));
-       target->verdict = target_to_verdict(target_name);
+       if (xt_t) {
+               struct xt_entry_target *entry_target;
+
+               if (is_builtin) {
+                       struct xt_standard_target *target;
+
+                       target = (struct xt_standard_target *)(xt_t->t);
+                       strcpy(target->target.u.user.name, IPT_STANDARD_TARGET);
+                       target->verdict = target_to_verdict(target_name);
+               }
+
+               entry_target = ipt_get_target(new_entry);
+               memcpy(entry_target, xt_t->t, target_size);
+       } else {
+               struct connman_iptables_entry *target_rule;
+               struct xt_standard_target *target;
+               GList *chain_head;
+
+               /*
+                * This is a user defined target, i.e. a chain jump.
+                * We search for the chain head, and the target verdict
+                * is the first rule's offset on this chain.
+                * The offset is from the beginning of the table.
+                */
+
+               chain_head = find_chain_head(table, target_name);
+               if (chain_head == NULL || chain_head->next == NULL) {
+                       g_free(new_entry);
+                       return NULL;
+               }
+
+               target_rule = chain_head->next->data;
+
+               target = (struct xt_standard_target *)ipt_get_target(new_entry);
+               strcpy(target->target.u.user.name, IPT_STANDARD_TARGET);
+               target->target.u.user.target_size = target_size;
+               target->verdict = target_rule->offset;
+       }
 
        return new_entry;
 }
 
-static struct ipt_entry *
-new_custom_rule(char *target_name, int target_argc, char **target_argv,
-               char *match_name, int match_argc, char **match_argv)
+static void update_hooks(struct connman_iptables *table, GList *chain_head, struct ipt_entry *entry)
 {
-       return NULL;
-}
+       GList *list;
+       struct connman_iptables_entry *head, *e;
+       int builtin;
 
-static struct ipt_entry *
-new_rule(char *target_name, int target_argc, char **target_argv,
-               char *match_name, int match_argc, char **match_argv)
-{
-       struct ipt_entry *new_entry;
+       if (chain_head == NULL)
+               return;
 
-       if (is_builtin_target(target_name))
-               new_entry = new_builtin_rule(target_name,
-                                       match_name, match_argc, match_argv);
-       else
-               new_entry = new_custom_rule(target_name,
-                                       target_argc, target_argv,
-                                       match_name, match_argc, match_argv);
+       head = chain_head->data;
 
-       return new_entry;
+       builtin = head->builtin;
+       if (builtin < 0)
+               return;
+
+       table->underflow[builtin] += entry->next_offset;
+
+       for (list = chain_head->next; list; list = list->next) {
+               e = list->data;
+
+               builtin = e->builtin;
+               if (builtin < 0)
+                       continue;
+
+               table->hook_entry[builtin] += entry->next_offset;
+               table->underflow[builtin] += entry->next_offset;
+       }
 }
 
 static int
 connman_iptables_add_rule(struct connman_iptables *table, char *chain_name,
-                       char *target_name, int target_argc, char **target_argv,
-                       char *match_name, int match_argc, char **match_argv)
+                               char *target_name, struct xtables_target *xt_t,
+                               char *match_name, struct xtables_match *xt_m)
 {
-       GList *chain_tail;
+       GList *chain_tail, *chain_head;
        struct ipt_entry *new_entry;
+       struct connman_iptables_entry *head;
+       int builtin = -1;
+
+       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;
 
-       new_entry = new_rule(target_name, target_argc, target_argv,
-                               match_name, match_argc, match_argv);
+       new_entry = new_rule(table,
+                               target_name, xt_t,
+                               match_name, xt_m);
        if (new_entry == NULL)
                return -EINVAL;
 
-       return connman_add_entry(table, new_entry, chain_tail->prev);
+       update_hooks(table, chain_head, new_entry);
+
+       /*
+        * If the chain is builtin, and does not have any rule,
+        * then the one that we're inserting is becoming the head
+        * and thus needs the builtin flag.
+        */
+       head = chain_head->data;
+       if (head->builtin < 0)
+               builtin = -1;
+       else if (chain_head == chain_tail->prev) {
+               head->builtin = -1;
+               builtin = head->builtin;
+       }
+
+       return connman_add_entry(table, new_entry, chain_tail->prev, builtin);
 }
 
 static struct ipt_replace *
@@ -435,10 +627,8 @@ connman_iptables_blob(struct connman_iptables *table)
        r->num_counters = table->old_entries;
        r->valid_hooks  = table->info->valid_hooks;
 
-       memcpy(r->hook_entry, table->info->hook_entry,
-                               sizeof(table->info->hook_entry));
-       memcpy(r->underflow, table->info->underflow,
-                               sizeof(table->info->underflow));
+       memcpy(r->hook_entry, table->hook_entry, sizeof(table->hook_entry));
+       memcpy(r->underflow, table->underflow, sizeof(table->underflow));
 
        entry_index = (unsigned char *)r->entries;
        for (list = table->entries; list; list = list->next) {
@@ -499,14 +689,17 @@ static void dump_target(struct connman_iptables *table,
                if(xt_t->print != NULL)
                        xt_t->print(NULL, target, 1);
        } else {
-               printf("\ttarget %s\n", target->u.user.name);
-
                xt_t = xtables_find_target(target->u.user.name, XTF_TRY_LOAD);
-               if (xt_t == NULL)
+               if (xt_t == NULL) {
+                       printf("\ttarget %s\n", target->u.user.name);
                        return;
+               }
 
-               if(xt_t->print != NULL)
+               if(xt_t->print != NULL) {
+                       printf("\ttarget ");
                        xt_t->print(NULL, target, 1);
+                       printf("\n");
+               }
        }
 }
 
@@ -515,6 +708,9 @@ static void dump_match(struct connman_iptables *table, struct ipt_entry *entry)
        struct xtables_match *xt_m;
        struct xt_entry_match *match;
 
+       if (entry->elems == (unsigned char *)entry + entry->target_offset)
+               return;
+
        match = (struct xt_entry_match *) entry->elems;
 
        if (!strlen(match->u.user.name))
@@ -579,6 +775,22 @@ static int connman_iptables_dump_entry(struct ipt_entry *entry,
        return 0;
 }
 
+static void connman_iptables_dump_hook(struct connman_iptables *table)
+{
+       int i;
+       printf("hooks: \n");
+       for (i = 0; i < NF_INET_NUMHOOKS; i++) {
+               if ((table->info->valid_hooks & (1 << i)))
+                       printf("%s entry 0x%x underflow 0x%x (0x%x)\n",
+                               hooknames[i],
+                               (unsigned int)table->blob_entries->entrytable +
+                                               table->info->hook_entry[i],
+                               (unsigned int)table->blob_entries->entrytable +
+                                               table->info->underflow[i],
+                                       table->info->underflow[i]);
+       }
+}
+
 static void connman_iptables_dump(struct connman_iptables *table)
 {
        printf("%s valid_hooks=0x%08x, num_entries=%u, size=%u\n",
@@ -586,6 +798,8 @@ static void connman_iptables_dump(struct connman_iptables *table)
                table->info->valid_hooks, table->info->num_entries,
                table->info->size);
 
+       connman_iptables_dump_hook(table);
+
        ENTRY_ITERATE(table->blob_entries->entrytable,
                        table->blob_entries->size,
                        connman_iptables_dump_entry, table);
@@ -611,10 +825,22 @@ static int connman_iptables_replace(struct connman_iptables *table,
 
 static void connman_iptables_cleanup(struct connman_iptables *table)
 {
+       GList *list;
+       struct connman_iptables_entry *entry;
+
        close(table->ipt_sock);
+
+       for (list = table->entries; list; list = list->next) {
+               entry = list->data;
+
+               g_free(entry->entry);
+       }
+
        g_free(table->info);
        g_free(table->blob_entries);
        g_free(table);
+
+       xtables_free_opts(1);
 }
 
 static int connman_iptables_commit(struct connman_iptables *table)
@@ -628,7 +854,18 @@ static int connman_iptables_commit(struct connman_iptables *table)
 
 static int add_entry(struct ipt_entry *entry, struct connman_iptables *table)
 {
-       return connman_add_entry(table, entry, NULL);
+       struct ipt_entry *new_entry;
+       int builtin;
+
+       new_entry = g_try_malloc0(entry->next_offset);
+       if (new_entry == NULL)
+               return -ENOMEM;
+
+       memcpy(new_entry, entry, entry->next_offset);
+
+       builtin = is_hook_entry(table, entry);
+
+       return connman_add_entry(table, new_entry, NULL, builtin);
 }
 
 static struct connman_iptables *connman_iptables_init(const char *table_name)
@@ -669,6 +906,11 @@ static struct connman_iptables *connman_iptables_init(const char *table_name)
        table->old_entries = table->info->num_entries;
        table->size = 0;
 
+       memcpy(table->underflow, table->info->underflow,
+                               sizeof(table->info->underflow));
+       memcpy(table->hook_entry, table->info->hook_entry,
+                               sizeof(table->info->hook_entry));
+
        ENTRY_ITERATE(table->blob_entries->entrytable,
                        table->blob_entries->size,
                                add_entry, table);
@@ -688,47 +930,87 @@ static struct option connman_iptables_opts[] = {
        {.name = "append",        .has_arg = 1, .val = 'A'},
        {.name = "list",          .has_arg = 2, .val = 'L'},
        {.name = "new-chain",     .has_arg = 1, .val = 'N'},
+       {.name = "delete-chain",  .has_arg = 1, .val = 'X'},
        {.name = "in-interface",  .has_arg = 1, .val = 'i'},
        {.name = "jump",          .has_arg = 1, .val = 'j'},
        {.name = "match",         .has_arg = 1, .val = 'm'},
        {.name = "out-interface", .has_arg = 1, .val = 'o'},
+       {.name = "table",         .has_arg = 1, .val = 't'},
        {NULL},
 };
 
+struct xtables_globals connman_iptables_globals = {
+       .option_offset = 0,
+       .opts = connman_iptables_opts,
+       .orig_opts = connman_iptables_opts,
+};
+
 int main(int argc, char *argv[])
 {
        struct connman_iptables *table;
-       char *chain, *new_chain, *match_name, *target_name;
+       struct xtables_match *xt_m;
+       struct xtables_target *xt_t;
+       char *table_name, *chain, *new_chain, *match_name, *target_name;
+       char *delete_chain;
        int c;
-       gboolean dump;
+       size_t size;
+       gboolean dump, invert, delete;
 
-       xtables_init();
-       xtables_set_nfproto(NFPROTO_IPV4);
-
-       table = connman_iptables_init("filter");
-       if (table == NULL)
-               return -1;
+       xtables_init_all(&connman_iptables_globals, NFPROTO_IPV4);
 
        dump = FALSE;
-       chain = new_chain = match_name = target_name = NULL;
+       invert = FALSE;
+       delete = FALSE;
+       table_name = chain = new_chain = match_name = target_name = NULL;
+       delete_chain = NULL;
+       table = NULL;
+       xt_m = NULL;
+       xt_t = NULL;
 
        while ((c = getopt_long(argc, argv,
-          "-A:L::N:j:i:m:o:", connman_iptables_opts, NULL)) != -1) {
+          "-A:L::N:X:j:i:m:o:t:", connman_iptables_globals.opts, NULL)) != -1) {
                switch (c) {
                case 'A':
                        chain = optarg;
                        break;
 
                case 'L':
-                       dump = TRUE;
+                       dump = true;
                        break;
 
                case 'N':
                        new_chain = optarg;
                        break;
 
+               case 'X':
+                       delete = true;
+                       delete_chain = optarg;
+                       break;
+
                case 'j':
                        target_name = optarg;
+                       xt_t = xtables_find_target(target_name, XTF_TRY_LOAD);
+
+                       if (xt_t == NULL)
+                               break;
+
+                       size = ALIGN(sizeof(struct ipt_entry_target)) + xt_t->size;
+
+                       xt_t->t = g_try_malloc0(size);
+                       if (xt_t->t == NULL)
+                               goto out;
+                       xt_t->t->u.target_size = size;
+                       strcpy(xt_t->t->u.user.name, target_name);
+                       xt_t->t->u.user.revision = xt_t->revision;
+                       if (xt_t->init != NULL)
+                               xt_t->init(xt_t->t);
+                       connman_iptables_globals.opts =
+                               xtables_merge_options(connman_iptables_globals.opts,
+                                                    xt_t->extra_opts,
+                                                    &xt_t->option_offset);
+                       if (connman_iptables_globals.opts == NULL)
+                               goto out;
+
                        break;
 
                case 'i':
@@ -736,17 +1018,82 @@ int main(int argc, char *argv[])
 
                case 'm':
                        match_name = optarg;
+
+                       xt_m = xtables_find_match(optarg, XTF_LOAD_MUST_SUCCEED, NULL);
+                       size = ALIGN(sizeof(struct ipt_entry_match)) + xt_m->size;
+                       xt_m->m = g_try_malloc0(size);
+                       if (xt_m == NULL)
+                               goto out;
+                       xt_m->m->u.match_size = size;
+                       strcpy(xt_m->m->u.user.name, xt_m->name);
+                       xt_m->m->u.user.revision = xt_m->revision;
+                       if (xt_m->init != NULL)
+                               xt_m->init(xt_m->m);
+                       if (xt_m != xt_m->next) {
+                               connman_iptables_globals.opts =
+                                       xtables_merge_options(connman_iptables_globals.opts,
+                                               xt_m->extra_opts,
+                                               &xt_m->option_offset);
+                               if (connman_iptables_globals.opts == NULL)
+                                       goto out;
+                       }
+
                        break;
 
                case 'o':
                        break;
 
+               case 't':
+                       table_name = optarg;
+                       break;
+
+               case 1:
+                       if (optarg[0] == '!' && optarg[1] == '\0') {
+                               if (invert)
+                                       printf("Consecutive ! not allowed\n");
+
+                               invert = TRUE;
+                               optarg[0] = '\0';
+                               continue;
+                       }
+
+                       printf("Invalid option\n");
+
+                       return -1;
+
                default:
-                       printf("default %s\n", optarg);
+                       if (xt_t == NULL || xt_t->parse == NULL ||
+                           !xt_t->parse(c - xt_t->option_offset, argv, invert,
+                                       &xt_t->tflags, NULL, &xt_t->t)) {
+                               if (xt_m == NULL || xt_m->parse == NULL)
+                                       break;
 
+                               xt_m->parse(c - xt_m->option_offset, argv,
+                                       invert, &xt_m->mflags, NULL, &xt_m->m);
+                       }
+
+                       break;
                }
        }
 
+       if (table_name == NULL)
+               table_name = "filter";
+
+       table = connman_iptables_init(table_name);
+       if (table == NULL)
+               return -1;
+
+       if (delete) {
+               if (delete_chain == NULL)
+                       goto out;
+
+               printf("Delete chain %s\n", delete_chain);
+
+               connman_iptables_delete_chain(table, delete_chain);
+
+               goto commit;
+       }
+
        if (dump) {
                connman_iptables_dump(table);
 
@@ -770,9 +1117,8 @@ int main(int argc, char *argv[])
 
                printf("Adding %s to %s (match %s)\n", target_name, chain, match_name);
 
-               connman_iptables_add_rule(table, chain,
-                                       target_name, 0, NULL,
-                                       match_name, 0, NULL);
+               connman_iptables_add_rule(table, chain, target_name, xt_t,
+                                       match_name, xt_m);
 
                goto commit;
        }
@@ -781,7 +1127,14 @@ commit:
 
        connman_iptables_commit(table);
 
+out:
        connman_iptables_cleanup(table);
 
+       if (xt_t)
+               g_free(xt_t->t);
+
+       if (xt_m)
+               g_free(xt_m->m);
+
        return 0;
 }