wispr: Added portal web request and result handling logic
[framework/connectivity/connman.git] / src / iptables.c
index 8c3a096..e852e01 100644 (file)
@@ -82,13 +82,14 @@ static const char *hooknames[] = {
 
 #define ALIGN(s) (((s) + ((MIN_ALIGN)-1)) & ~((MIN_ALIGN)-1))
 
-struct ipt_error_target {
+struct error_target {
        struct xt_entry_target t;
        char error[IPT_TABLE_MAXNAMELEN];
 };
 
 struct connman_iptables_entry {
        int offset;
+       int builtin;
 
        struct ipt_entry *entry;
 };
@@ -103,6 +104,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;
 };
 
@@ -193,13 +197,11 @@ static gboolean is_jump(struct connman_iptables_entry *e)
 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);
@@ -223,7 +225,7 @@ static GList *find_chain_head(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;
 
@@ -252,7 +254,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;
 
@@ -305,7 +307,8 @@ static void update_offsets(struct connman_iptables *table)
 }
 
 static int iptables_add_entry(struct connman_iptables *table,
-                               struct ipt_entry *entry, GList *before)
+                               struct ipt_entry *entry, GList *before,
+                                       int builtin)
 {
        GList *list;
        struct connman_iptables_entry *e, *tmp, *entry_before;
@@ -319,6 +322,7 @@ static int iptables_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++;
@@ -353,13 +357,80 @@ static int iptables_add_entry(struct connman_iptables *table,
        return 0;
 }
 
+static int iptables_flush_chain(struct connman_iptables *table,
+                                               char *name)
+{
+       GList *chain_head, *chain_tail, *list, *next;
+       struct connman_iptables_entry *entry;
+       int builtin, removed = 0;
+
+       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;
+
+       entry = chain_head->data;
+       builtin = entry->builtin;
+
+       if (builtin >= 0)
+               list = chain_head;
+       else
+               list = chain_head->next;
+
+       if (list == chain_tail->prev)
+               return 0;
+
+       while (list != chain_tail->prev) {
+               entry = list->data;
+               next = g_list_next(list);
+
+               table->num_entries--;
+               table->size -= entry->entry->next_offset;
+               removed += entry->entry->next_offset;
+
+               g_free(entry->entry);
+
+               table->entries = g_list_remove(table->entries, list->data);
+
+               list = next;
+       }
+
+       if (builtin >= 0) {
+               struct connman_iptables_entry *e;
+
+               entry = list->data;
+
+               entry->builtin = builtin;
+
+               table->underflow[builtin] -= removed;
+
+               for (list = chain_tail; list; list = list->next) {
+                       e = list->data;
+
+                       builtin = e->builtin;
+                       if (builtin < 0)
+                               continue;
+
+                       table->hook_entry[builtin] -= removed;
+                       table->underflow[builtin] -= removed;
+               }
+       }
+
+       update_offsets(table);
+
+       return 0;
+}
+
 static int iptables_add_chain(struct connman_iptables *table,
                                        char *name)
 {
        GList *last;
        struct ipt_entry *entry_head;
        struct ipt_entry *entry_return;
-       struct ipt_error_target *error;
+       struct error_target *error;
        struct ipt_standard_target *standard;
        u_int16_t entry_head_size, entry_return_size;
 
@@ -376,23 +447,23 @@ static int iptables_add_chain(struct connman_iptables *table,
 
        /* head entry */
        entry_head_size = sizeof(struct ipt_entry) +
-                               sizeof(struct ipt_error_target);
+                               sizeof(struct error_target);
        entry_head = g_try_malloc0(entry_head_size);
        if (entry_head == NULL)
-               goto err;
+               goto err_head;
 
        memset(entry_head, 0, entry_head_size);
 
        entry_head->target_offset = sizeof(struct ipt_entry);
        entry_head->next_offset = entry_head_size;
 
-       error = (struct ipt_error_target *) entry_head->elems;
+       error = (struct error_target *) entry_head->elems;
        strcpy(error->t.u.user.name, IPT_ERROR_TARGET);
-       error->t.u.user.target_size = ALIGN(sizeof(struct ipt_error_target));
+       error->t.u.user.target_size = ALIGN(sizeof(struct error_target));
        strcpy(error->error, name);
 
-       if (iptables_add_entry(table, entry_head, last) < 0)
-               goto err;
+       if (iptables_add_entry(table, entry_head, last, -1) < 0)
+               goto err_head;
 
        /* tail entry */
        entry_return_size = sizeof(struct ipt_entry) +
@@ -411,20 +482,21 @@ static int iptables_add_chain(struct connman_iptables *table,
                                ALIGN(sizeof(struct ipt_standard_target));
        standard->verdict = XT_RETURN;
 
-       if (iptables_add_entry(table, entry_return, last) < 0)
+       if (iptables_add_entry(table, entry_return, last, -1) < 0)
                goto err;
 
        return 0;
 
 err:
-       g_free(entry_head);
        g_free(entry_return);
+err_head:
+       g_free(entry_head);
 
        return -ENOMEM;
 }
 
 static struct ipt_entry *
-new_rule(struct connman_iptables *table,
+new_rule(struct connman_iptables *table, struct ipt_ip *ip,
                char *target_name, struct xtables_target *xt_t,
                char *match_name, struct xtables_match *xt_m)
 {
@@ -447,6 +519,8 @@ new_rule(struct connman_iptables *table,
        if (new_entry == NULL)
                return NULL;
 
+       memcpy(&new_entry->ip, ip, sizeof(struct ipt_ip));
+
        new_entry->target_offset = sizeof(struct ipt_entry) + match_size;
        new_entry->next_offset = sizeof(struct ipt_entry) + target_size +
                                                                match_size;
@@ -499,25 +573,79 @@ new_rule(struct connman_iptables *table,
        return new_entry;
 }
 
+static void update_hooks(struct connman_iptables *table, GList *chain_head,
+                               struct ipt_entry *entry)
+{
+       GList *list;
+       struct connman_iptables_entry *head, *e;
+       int builtin;
+
+       if (chain_head == NULL)
+               return;
+
+       head = chain_head->data;
+
+       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
-iptables_add_rule(struct connman_iptables *table, char *chain_name,
+iptables_add_rule(struct connman_iptables *table,
+                               struct ipt_ip *ip, char *chain_name,
                                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;
+
+       DBG("");
+
+       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(table,
+       new_entry = new_rule(table, ip,
                                target_name, xt_t,
                                match_name, xt_m);
        if (new_entry == NULL)
                return -EINVAL;
 
-       return iptables_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) {
+               builtin = head->builtin;
+               head->builtin = -1;
+       }
+
+       return iptables_add_entry(table, new_entry, chain_tail->prev, builtin);
 }
 
 static struct ipt_replace *
@@ -535,7 +663,7 @@ iptables_blob(struct connman_iptables *table)
        memset(r, 0, sizeof(*r) + table->size);
 
        r->counters = g_try_malloc0(sizeof(struct xt_counters)
-                               * table->num_entries);
+                               * table->old_entries);
        if (r->counters == NULL) {
                g_free(r);
                return NULL;
@@ -548,10 +676,8 @@ 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) {
@@ -567,12 +693,24 @@ iptables_blob(struct connman_iptables *table)
 static void dump_ip(struct ipt_entry *entry)
 {
        struct ipt_ip *ip = &entry->ip;
+       char ip_string[INET6_ADDRSTRLEN];
+       char ip_mask[INET6_ADDRSTRLEN];
 
        if (strlen(ip->iniface))
                connman_info("\tin %s", ip->iniface);
 
        if (strlen(ip->outiface))
                connman_info("\tout %s", ip->outiface);
+
+       if (inet_ntop(AF_INET, &ip->src, ip_string, INET6_ADDRSTRLEN) != NULL &&
+                       inet_ntop(AF_INET, &ip->smsk,
+                                       ip_mask, INET6_ADDRSTRLEN) != NULL)
+               connman_info("\tsrc %s/%s", ip_string, ip_mask);
+
+       if (inet_ntop(AF_INET, &ip->dst, ip_string, INET6_ADDRSTRLEN) != NULL &&
+                       inet_ntop(AF_INET, &ip->dmsk,
+                                       ip_mask, INET6_ADDRSTRLEN) != NULL)
+               connman_info("\tdst %s/%s", ip_string, ip_mask);
 }
 
 static void dump_target(struct connman_iptables *table,
@@ -741,6 +879,7 @@ static int iptables_replace(struct connman_iptables *table,
 static int add_entry(struct ipt_entry *entry, struct connman_iptables *table)
 {
        struct ipt_entry *new_entry;
+       int builtin;
 
        new_entry = g_try_malloc0(entry->next_offset);
        if (new_entry == NULL)
@@ -748,7 +887,9 @@ static int add_entry(struct ipt_entry *entry, struct connman_iptables *table)
 
        memcpy(new_entry, entry, entry->next_offset);
 
-       return iptables_add_entry(table, new_entry, NULL);
+       builtin = is_hook_entry(table, entry);
+
+       return iptables_add_entry(table, new_entry, NULL, builtin);
 }
 
 static void table_cleanup(struct connman_iptables *table)
@@ -762,6 +903,7 @@ static void table_cleanup(struct connman_iptables *table)
                entry = list->data;
 
                g_free(entry->entry);
+               g_free(entry);
        }
 
        g_list_free(table->entries);
@@ -775,6 +917,8 @@ static struct connman_iptables *iptables_init(char *table_name)
        struct connman_iptables *table;
        socklen_t s;
 
+       DBG("%s", table_name);
+
        table = g_hash_table_lookup(table_hash, table_name);
        if (table != NULL)
                return table;
@@ -812,11 +956,16 @@ static struct connman_iptables *iptables_init(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);
 
-       g_hash_table_insert(table_hash, table_name, table);
+       g_hash_table_insert(table_hash, g_strdup(table_name), table);
 
        return table;
 
@@ -829,12 +978,15 @@ err:
 
 static struct option iptables_opts[] = {
        {.name = "append",        .has_arg = 1, .val = 'A'},
+       {.name = "flush-chain",   .has_arg = 1, .val = 'F'},
        {.name = "list",          .has_arg = 2, .val = 'L'},
        {.name = "new-chain",     .has_arg = 1, .val = 'N'},
+       {.name = "destination",   .has_arg = 1, .val = 'd'},
        {.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 = "source",        .has_arg = 1, .val = 's'},
        {.name = "table",         .has_arg = 1, .val = 't'},
        {NULL},
 };
@@ -850,10 +1002,13 @@ static int iptables_command(int argc, char *argv[])
        struct connman_iptables *table;
        struct xtables_match *xt_m;
        struct xtables_target *xt_t;
+       struct ipt_ip ip;
        char *table_name, *chain, *new_chain, *match_name, *target_name;
-       int c, ret;
+       char *flush_chain;
+       int c, ret, in_len, out_len;
        size_t size;
        gboolean dump, invert;
+       struct in_addr src, dst;
 
        if (argc == 0)
                return -EINVAL;
@@ -861,6 +1016,8 @@ static int iptables_command(int argc, char *argv[])
        dump = FALSE;
        invert = FALSE;
        table_name = chain = new_chain = match_name = target_name = NULL;
+       flush_chain = NULL;
+       memset(&ip, 0, sizeof(struct ipt_ip));
        table = NULL;
        xt_m = NULL;
        xt_t = NULL;
@@ -869,12 +1026,16 @@ static int iptables_command(int argc, char *argv[])
        optind = 0;
 
        while ((c = getopt_long(argc, argv,
-          "-A:L::N:j:i:m:o:t:", iptables_globals.opts, NULL)) != -1) {
+          "-A:F:L::N:d:j:i:m:o:s:t:", iptables_globals.opts, NULL)) != -1) {
                switch (c) {
                case 'A':
                        chain = optarg;
                        break;
 
+               case 'F':
+                       flush_chain = optarg;
+                       break;
+
                case 'L':
                        dump = TRUE;
                        break;
@@ -883,6 +1044,32 @@ static int iptables_command(int argc, char *argv[])
                        new_chain = optarg;
                        break;
 
+               case 'd':
+                       if (!inet_pton(AF_INET, optarg, &dst))
+                               break;
+
+                       ip.dst = dst;
+                       inet_pton(AF_INET, "255.255.255.255", &ip.dmsk);
+
+                       if (invert)
+                               ip.invflags |= IPT_INV_DSTIP;
+
+                       break;
+
+               case 'i':
+                       in_len = strlen(optarg);
+
+                       if (in_len + 1 > IFNAMSIZ)
+                               break;
+
+                       strcpy(ip.iniface, optarg);
+                       memset(ip.iniface_mask, 0xff, in_len + 1);
+
+                       if (invert)
+                               ip.invflags |= IPT_INV_VIA_IN;
+
+                       break;
+
                case 'j':
                        target_name = optarg;
                        xt_t = xtables_find_target(target_name, XTF_TRY_LOAD);
@@ -902,7 +1089,11 @@ static int iptables_command(int argc, char *argv[])
                        if (xt_t->init != NULL)
                                xt_t->init(xt_t->t);
                        iptables_globals.opts =
-                               xtables_merge_options(iptables_globals.opts,
+                               xtables_merge_options(
+#if XTABLES_VERSION_CODE > 5
+                                                    iptables_globals.orig_opts,
+#endif
+                                                    iptables_globals.opts,
                                                     xt_t->extra_opts,
                                                     &xt_t->option_offset);
                        if (iptables_globals.opts == NULL)
@@ -910,9 +1101,6 @@ static int iptables_command(int argc, char *argv[])
 
                        break;
 
-               case 'i':
-                       break;
-
                case 'm':
                        match_name = optarg;
 
@@ -929,7 +1117,11 @@ static int iptables_command(int argc, char *argv[])
                                xt_m->init(xt_m->m);
                        if (xt_m != xt_m->next) {
                                iptables_globals.opts =
-                               xtables_merge_options(iptables_globals.opts,
+                               xtables_merge_options(
+#if XTABLES_VERSION_CODE > 5
+                                               iptables_globals.orig_opts,
+#endif
+                                               iptables_globals.opts,
                                                xt_m->extra_opts,
                                                &xt_m->option_offset);
                                if (iptables_globals.opts == NULL)
@@ -939,6 +1131,29 @@ static int iptables_command(int argc, char *argv[])
                        break;
 
                case 'o':
+                       out_len = strlen(optarg);
+
+                       if (out_len + 1 > IFNAMSIZ)
+                               break;
+
+                       strcpy(ip.outiface, optarg);
+                       memset(ip.outiface_mask, 0xff, out_len + 1);
+
+                       if (invert)
+                               ip.invflags |= IPT_INV_VIA_OUT;
+
+                       break;
+
+               case 's':
+                       if (!inet_pton(AF_INET, optarg, &src))
+                               break;
+
+                       ip.src = src;
+                       inet_pton(AF_INET, "255.255.255.255", &ip.smsk);
+
+                       if (invert)
+                               ip.invflags |= IPT_INV_SRCIP;
+
                        break;
 
                case 't':
@@ -970,6 +1185,8 @@ static int iptables_command(int argc, char *argv[])
 
                        break;
                }
+
+               invert = FALSE;
        }
 
        if (table_name == NULL)
@@ -988,6 +1205,14 @@ static int iptables_command(int argc, char *argv[])
                goto out;
        }
 
+       if (flush_chain) {
+               DBG("Flush chain %s", flush_chain);
+
+               iptables_flush_chain(table, flush_chain);
+
+               goto out;
+       }
+
        if (chain && new_chain) {
                ret = -EINVAL;
                goto out;
@@ -1007,7 +1232,7 @@ static int iptables_command(int argc, char *argv[])
                DBG("Adding %s to %s (match %s)",
                                target_name, chain, match_name);
 
-               ret = iptables_add_rule(table, chain, target_name, xt_t,
+               ret = iptables_add_rule(table, &ip, chain, target_name, xt_t,
                                        match_name, xt_m);
 
                goto out;
@@ -1044,8 +1269,9 @@ int __connman_iptables_command(const char *format, ...)
        arguments = g_strsplit_set(command, " ", -1);
 
        for (argc = 0; arguments[argc]; argc++);
+       ++argc;
 
-       DBG("command %s argc %d", command, ++argc);
+       DBG("command %s argc %d", command, argc);
 
        argv = g_try_malloc0(argc * sizeof(char *));
        if (argv == NULL) {
@@ -1072,6 +1298,9 @@ int __connman_iptables_commit(const char *table_name)
 {
        struct connman_iptables *table;
        struct ipt_replace *repl;
+       int err;
+
+       DBG("%s", table_name);
 
        table = g_hash_table_lookup(table_hash, table_name);
        if (table == NULL)
@@ -1079,7 +1308,17 @@ int __connman_iptables_commit(const char *table_name)
 
        repl = iptables_blob(table);
 
-       return iptables_replace(table, repl);
+       err = iptables_replace(table, repl);
+
+       g_free(repl->counters);
+       g_free(repl);
+
+       if (err < 0)
+           return err;
+
+       g_hash_table_remove(table_hash, table_name);
+
+       return 0;
 }
 
 static void remove_table(gpointer user_data)
@@ -1094,7 +1333,7 @@ int __connman_iptables_init(void)
        DBG("");
 
        table_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
-                                               NULL, remove_table);
+                                               g_free, remove_table);
 
        xtables_init_all(&iptables_globals, NFPROTO_IPV4);
 
@@ -1104,6 +1343,8 @@ int __connman_iptables_init(void)
 
 void __connman_iptables_cleanup(void)
 {
+       DBG("");
+
        g_hash_table_destroy(table_hash);
 
        xtables_free_opts(1);