X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=tools%2Fiptables-test.c;h=5ee1ec39c302da4af2503eed5eac4baf26f3d80c;hb=1e1cf8166f48eef131738d672292750e32301d73;hp=b97c520b3a9b285798c53abe325cec04b07b873e;hpb=ab47a2a87da6969877e0cde5f1eea51b30ed59a3;p=framework%2Fconnectivity%2Fconnman.git diff --git a/tools/iptables-test.c b/tools/iptables-test.c index b97c520..5ee1ec3 100644 --- a/tools/iptables-test.c +++ b/tools/iptables-test.c @@ -1,7 +1,7 @@ /* * Connection Manager * - * Copyright (C) 2007-2010 Intel Corporation. All rights reserved. + * Copyright (C) 2007-2011 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -18,190 +18,1569 @@ * */ +#include #include #include #include #include #include -#include -#include +#include +#include +#include #include -#include -#include +#include -static void print_match(const struct ipt_entry *e) +#include + +static const char *hooknames[] = { + [NF_IP_PRE_ROUTING] = "PREROUTING", + [NF_IP_LOCAL_IN] = "INPUT", + [NF_IP_FORWARD] = "FORWARD", + [NF_IP_LOCAL_OUT] = "OUTPUT", + [NF_IP_POST_ROUTING] = "POSTROUTING", +}; + +#define LABEL_ACCEPT "ACCEPT" +#define LABEL_DROP "DROP" +#define LABEL_QUEUE "QUEUE" +#define LABEL_RETURN "RETURN" + +/* fn returns 0 to continue iteration */ +#define _XT_ENTRY_ITERATE_CONTINUE(type, entries, size, n, fn, args...) \ +({ \ + unsigned int __i; \ + int __n; \ + int __ret = 0; \ + type *__entry; \ + \ + for (__i = 0, __n = 0; __i < (size); \ + __i += __entry->next_offset, __n++) { \ + __entry = (void *)(entries) + __i; \ + if (__n < n) \ + continue; \ + \ + __ret = fn(__entry, ## args); \ + if (__ret != 0) \ + break; \ + } \ + __ret; \ +}) + +/* fn returns 0 to continue iteration */ +#define _XT_ENTRY_ITERATE(type, entries, size, fn, args...) \ + _XT_ENTRY_ITERATE_CONTINUE(type, entries, size, 0, fn, args) + +#define ENTRY_ITERATE(entries, size, fn, args...) \ + _XT_ENTRY_ITERATE(struct ipt_entry, entries, size, fn, ## args) + +#define MIN_ALIGN (__alignof__(struct ipt_entry)) + +#define ALIGN(s) (((s) + ((MIN_ALIGN)-1)) & ~((MIN_ALIGN)-1)) + +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; +}; + +struct connman_iptables { + int ipt_sock; + + struct ipt_getinfo *info; + struct ipt_get_entries *blob_entries; + + unsigned int num_entries; + unsigned int old_entries; + unsigned int size; + + unsigned int underflow[NF_INET_NUMHOOKS]; + unsigned int hook_entry[NF_INET_NUMHOOKS]; + + GList *entries; +}; + + +static struct ipt_entry *get_entry(struct connman_iptables *table, + unsigned int offset) { - struct xt_entry_match *match; - struct xtables_match *xt_match; + return (struct ipt_entry *)((char *)table->blob_entries->entrytable + + offset); +} - match = (struct xt_entry_match *)e->elems; - if (match == NULL) - return; +static int is_hook_entry(struct connman_iptables *table, + struct ipt_entry *entry) +{ + unsigned int i; - xt_match = xtables_find_match(match->u.user.name, XTF_TRY_LOAD, NULL); - if (xt_match == NULL) - return; + for (i = 0; i < NF_INET_NUMHOOKS; i++) { + if ((table->info->valid_hooks & (1 << i)) + && get_entry(table, table->info->hook_entry[i]) == entry) + return i; + } - printf("\tMATCH:%s\n", xt_match->m->u.user.name); + return -1; } -static void print_target(const struct ipt_entry *e) +static unsigned long entry_to_offset(struct connman_iptables *table, + struct ipt_entry *entry) +{ + return (void *)entry - (void *)table->blob_entries->entrytable; +} + +static int target_to_verdict(char *target_name) +{ + if (!strcmp(target_name, LABEL_ACCEPT)) + return -NF_ACCEPT - 1; + + if (!strcmp(target_name, LABEL_DROP)) + return -NF_DROP - 1; + + if (!strcmp(target_name, LABEL_QUEUE)) + return -NF_QUEUE - 1; + + if (!strcmp(target_name, LABEL_RETURN)) + return XT_RETURN; + + return 0; +} + +static gboolean is_builtin_target(char *target_name) +{ + if (!strcmp(target_name, LABEL_ACCEPT) || + !strcmp(target_name, LABEL_DROP) || + !strcmp(target_name, LABEL_QUEUE) || + !strcmp(target_name, LABEL_RETURN)) + return TRUE; + + return FALSE; +} + +static gboolean is_jump(struct connman_iptables_entry *e) { struct xt_entry_target *target; - struct xtables_target *xt_target; - target = (void *)e + e->target_offset; - if (target == NULL) - return; + target = ipt_get_target(e->entry); - xt_target = xtables_find_target(target->u.user.name, XTF_TRY_LOAD); - if (xt_target == NULL) - return; + 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; + } + } - printf("\tTARGET: %s\n", xt_target->t->u.user.name); + return false; } +static gboolean is_chain(struct connman_iptables *table, + struct connman_iptables_entry *e) +{ + struct ipt_entry *entry; + struct xt_entry_target *target; + + entry = e->entry; + if (e->builtin >= 0) + return TRUE; + + target = ipt_get_target(entry); + if (!strcmp(target->u.user.name, IPT_ERROR_TARGET)) + return TRUE; -static void print_rule(const struct ipt_entry *e, const char *chain) + return FALSE; +} + +static GList *find_chain_head(struct connman_iptables *table, + char *chain_name) { - /* print chain name */ - printf("CHAIN %s:\n", chain); + GList *list; + struct connman_iptables_entry *head; + struct ipt_entry *entry; + struct xt_entry_target *target; + int builtin; - print_match(e); - print_target(e); + 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 void print_tables(struct iptc_handle *h) +static GList *find_chain_tail(struct connman_iptables *table, + char *chain_name) { - const char *chain; - const struct ipt_entry *rule; + GList *chain_head, *list; + struct connman_iptables_entry *tail; - chain = iptc_first_chain(h); + chain_head = find_chain_head(table, chain_name); + if (chain_head == NULL) + return NULL; + + /* Then we look for the next chain */ + for (list = chain_head->next; list; list = list->next) { + tail = list->data; - while(chain) { - rule = iptc_first_rule(chain, h); - while (rule) { - print_rule(rule, chain); + if (is_chain(table, tail)) + return list; + } - rule = iptc_next_rule(rule, h); + /* Nothing found, we return the table end */ + 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; } - chain = iptc_next_chain(h); + prev = list->prev; + prev_entry = prev->data; + + entry->offset = prev_entry->offset + + prev_entry->entry->next_offset; + } +} + +static void update_targets_reference(struct connman_iptables *table, + struct connman_iptables_entry *entry_before, + struct connman_iptables_entry *modified_entry, + gboolean is_removing) +{ + struct connman_iptables_entry *tmp; + struct xt_standard_target *t; + GList *list; + int offset; + + offset = modified_entry->entry->next_offset; + + for (list = table->entries; list; list = list->next) { + tmp = list->data; + + if (!is_jump(tmp)) + continue; + + t = (struct xt_standard_target *)ipt_get_target(tmp->entry); + + if (is_removing == TRUE) { + if (t->verdict >= entry_before->offset) + t->verdict -= offset; + } else { + if (t->verdict > entry_before->offset) + t->verdict += offset; + } + } +} + +static int connman_add_entry(struct connman_iptables *table, + struct ipt_entry *entry, GList *before, + int builtin) +{ + struct connman_iptables_entry *e, *entry_before; + + if (table == NULL) + return -1; + + e = g_try_malloc0(sizeof(struct connman_iptables_entry)); + if (e == NULL) + 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 appended/insterted a new entry. All references + * should be bumped accordingly. + */ + update_targets_reference(table, entry_before, e, FALSE); + + update_offsets(table); + + return 0; +} + +static int remove_table_entry(struct connman_iptables *table, + struct connman_iptables_entry *entry) +{ + int removed = 0; + + 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, entry); + + return removed; +} + +static int connman_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); + + removed += remove_table_entry(table, entry); + + 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 connman_iptables_delete_chain(struct connman_iptables *table, + char *name) +{ + GList *chain_head, *chain_tail; + struct connman_iptables_entry *entry; + + chain_head = find_chain_head(table, name); + if (chain_head == NULL) + return -EINVAL; + + entry = chain_head->data; + + /* We cannot remove builtin chain */ + if (entry->builtin >= 0) + return -EINVAL; + + chain_tail = find_chain_tail(table, name); + if (chain_tail == NULL) + return -EINVAL; + + /* Chain must be flushed */ + if (chain_head->next != chain_tail->prev) + return -EINVAL; + + remove_table_entry(table, entry); + + entry = chain_tail->prev->data; + remove_table_entry(table, entry); + + update_offsets(table); + + return 0; } -static struct ipt_entry *build_quota_drop_entry(void) +static int connman_iptables_add_chain(struct connman_iptables *table, + char *name) { - struct ipt_entry *e; + GList *last; + struct ipt_entry *entry_head; + struct ipt_entry *entry_return; + struct error_target *error; + struct ipt_standard_target *standard; + u_int16_t entry_head_size, entry_return_size; + + last = g_list_last(table->entries); + + /* + * An empty chain is composed of: + * - A head entry, with no match and an error target. + * The error target data is the chain name. + * - A tail entry, with no match and a standard target. + * The standard target verdict is XT_RETURN (return to the + * caller). + */ + + /* head entry */ + entry_head_size = sizeof(struct ipt_entry) + + sizeof(struct error_target); + entry_head = g_try_malloc0(entry_head_size); + if (entry_head == NULL) + 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 error_target *) entry_head->elems; + strcpy(error->t.u.user.name, IPT_ERROR_TARGET); + error->t.u.user.target_size = ALIGN(sizeof(struct error_target)); + strcpy(error->error, name); + + if (connman_add_entry(table, entry_head, last, -1) < 0) + goto err_head; + + /* tail entry */ + entry_return_size = sizeof(struct ipt_entry) + + sizeof(struct ipt_standard_target); + entry_return = g_try_malloc0(entry_return_size); + if (entry_return == NULL) + goto err; + + memset(entry_return, 0, entry_return_size); + + entry_return->target_offset = sizeof(struct ipt_entry); + entry_return->next_offset = entry_return_size; + + standard = (struct ipt_standard_target *) entry_return->elems; + standard->target.u.user.target_size = + ALIGN(sizeof(struct ipt_standard_target)); + standard->verdict = XT_RETURN; + + if (connman_add_entry(table, entry_return, last, -1) < 0) + goto err; + + return 0; + +err: + g_free(entry_return); +err_head: + g_free(entry_head); + + return -ENOMEM; +} + +static struct ipt_entry *new_rule(struct ipt_ip *ip, + 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_target *t; - struct xtables_match *m; - struct xtables_rule_match *matches = NULL; - m = xtables_find_match("quota", XTF_LOAD_MUST_SUCCEED, &matches); - if (m == NULL) + if (xt_m) + match_size = xt_m->m->u.match_size; + else + match_size = 0; + + 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); + if (new_entry == NULL) return NULL; - match_size = IPT_ALIGN(sizeof(struct ipt_entry_match)) + m->size; + 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; + if (xt_m) { + struct xt_entry_match *entry_match; + + entry_match = (struct xt_entry_match *)new_entry->elems; + memcpy(entry_match, xt_m->m, match_size); + } + + if (xt_t) { + struct xt_entry_target *entry_target; + + entry_target = ipt_get_target(new_entry); + memcpy(entry_target, xt_t->t, target_size); + } + + 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; - m->m = xtables_calloc(1, match_size); - if (m->m == NULL) + builtin = e->builtin; + if (builtin < 0) + continue; + + table->hook_entry[builtin] += entry->next_offset; + table->underflow[builtin] += entry->next_offset; + } +} + +static struct ipt_entry *prepare_rule_inclusion(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, + int *builtin) +{ + GList *chain_tail, *chain_head; + struct ipt_entry *new_entry; + struct connman_iptables_entry *head; + + chain_head = find_chain_head(table, chain_name); + if (chain_head == NULL) return NULL; - m->m->u.match_size = match_size; - strcpy(m->m->u.user.name, m->name); - m->m->u.user.revision = m->revision; - if (m->init != NULL) - m->init(m->m); - - t = xtables_find_target("DROP", XTF_TRY_LOAD); - if (t == NULL) { - free(m->m); + + chain_tail = find_chain_tail(table, chain_name); + if (chain_tail == NULL) return NULL; + + new_entry = new_rule(ip, target_name, xt_t, match_name, xt_m); + if (new_entry == NULL) + return NULL; + + 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; } - target_size = IPT_ALIGN(sizeof(struct ipt_entry_target)) + t->size; + return new_entry; +} + +static int connman_iptables_append_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; + struct ipt_entry *new_entry; + int builtin = -1, ret; + + chain_tail = find_chain_tail(table, chain_name); + if (chain_tail == NULL) + return -EINVAL; + + new_entry = prepare_rule_inclusion(table, ip, chain_name, + target_name, xt_t, match_name, xt_m, &builtin); + if (new_entry == NULL) + return -EINVAL; + + ret = connman_add_entry(table, new_entry, chain_tail->prev, builtin); + if (ret < 0) + g_free(new_entry); + + return ret; +} + +static int connman_iptables_insert_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_head; + struct ipt_entry *new_entry; + int builtin = -1, ret; + + chain_head = find_chain_head(table, chain_name); + if (chain_head == NULL) + return -EINVAL; + + new_entry = prepare_rule_inclusion(table, ip, chain_name, + target_name, xt_t, match_name, xt_m, &builtin); + if (new_entry == NULL) + return -EINVAL; + + ret = connman_add_entry(table, new_entry, chain_head->next, builtin); + if (ret < 0) + g_free(new_entry); + + return ret; +} + +static gboolean is_same_ipt_entry(struct ipt_entry *i_e1, + struct ipt_entry *i_e2) +{ + if (memcmp(&i_e1->ip, &i_e2->ip, sizeof(struct ipt_ip)) != 0) + return FALSE; + + if (i_e1->target_offset != i_e2->target_offset) + return FALSE; - t->t = xtables_calloc(1, target_size); - t->t->u.target_size = target_size; - strcpy(t->t->u.user.name, "DROP"); - t->t->u.user.revision = t->revision; - if (t->init != NULL) - t->init(t->t); + if (i_e1->next_offset != i_e2->next_offset) + return FALSE; - e = calloc(1, sizeof(struct ipt_entry) + match_size + target_size); - if (e == NULL) { - free(m->m); - free(t->t); + return TRUE; +} + +static gboolean is_same_target(struct xt_entry_target *xt_e_t1, + struct xt_entry_target *xt_e_t2) +{ + if (xt_e_t1 == NULL || xt_e_t2 == NULL) + return FALSE; + + 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; + + xt_s_t1 = (struct xt_standard_target *) xt_e_t1; + xt_s_t2 = (struct xt_standard_target *) xt_e_t2; + + if (xt_s_t1->verdict != xt_s_t2->verdict) + return FALSE; + } else { + if (xt_e_t1->u.target_size != xt_e_t2->u.target_size) + return FALSE; + + if (strcmp(xt_e_t1->u.user.name, xt_e_t2->u.user.name) != 0) + return FALSE; } - e->target_offset = sizeof(struct ipt_entry) + match_size; - e->next_offset = sizeof(struct ipt_entry) + match_size + target_size; + return TRUE; +} - memcpy(e->elems, m->m, match_size); - memcpy(e->elems + match_size, t->t, target_size); +static gboolean is_same_match(struct xt_entry_match *xt_e_m1, + struct xt_entry_match *xt_e_m2) +{ + if (xt_e_m1 == NULL || xt_e_m2 == NULL) + return FALSE; + + if (xt_e_m1->u.match_size != xt_e_m2->u.match_size) + return FALSE; + + if (xt_e_m1->u.user.revision != xt_e_m2->u.user.revision) + return FALSE; + + if (strcmp(xt_e_m1->u.user.name, xt_e_m2->u.user.name) != 0) + return FALSE; - return e; + return TRUE; } -static int add_rule(const ipt_chainlabel chain, struct ipt_entry *e, - struct iptc_handle *h) +static int connman_iptables_delete_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) { - if (!iptc_create_chain(chain, h)) { - printf("Chain creation error (%s)\n", iptc_strerror(errno)); - return -1; + GList *chain_tail, *chain_head, *list; + struct xt_entry_target *xt_e_t = NULL; + struct xt_entry_match *xt_e_m = NULL; + struct connman_iptables_entry *entry; + struct ipt_entry *entry_test; + 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; + + if (!xt_t && !xt_m) + return -EINVAL; + + entry_test = new_rule(ip, target_name, xt_t, match_name, xt_m); + if (entry_test == NULL) + return -EINVAL; + + if (xt_t != NULL) + xt_e_t = ipt_get_target(entry_test); + if (xt_m != NULL) + xt_e_m = (struct xt_entry_match *)entry_test->elems; + + entry = chain_head->data; + builtin = entry->builtin; + + if (builtin >= 0) + list = chain_head; + else + list = chain_head->next; + + for (entry = NULL; list != chain_tail->prev; list = list->next) { + struct connman_iptables_entry *tmp; + struct ipt_entry *tmp_e; + + tmp = list->data; + tmp_e = tmp->entry; + + if (is_same_ipt_entry(entry_test, tmp_e) == FALSE) + continue; + + if (xt_t != NULL) { + struct xt_entry_target *tmp_xt_e_t; + + tmp_xt_e_t = ipt_get_target(tmp_e); + + if (!is_same_target(tmp_xt_e_t, xt_e_t)) + continue; + } + + if (xt_m != NULL) { + struct xt_entry_match *tmp_xt_e_m; + + tmp_xt_e_m = (struct xt_entry_match *)tmp_e->elems; + + if (!is_same_match(tmp_xt_e_m, xt_e_m)) + continue; + } + + entry = tmp; + break; } - if (!iptc_insert_entry(chain, e, 0, h)) { - printf("Entry insertion error (%s)\n", iptc_strerror(errno)); - return -1; + if (entry == NULL) { + g_free(entry_test); + return -EINVAL; } - if (!iptc_commit(h)) { - printf("Commit error (%s)\n", iptc_strerror(errno)); - return -1; + /* We have deleted a rule, + * all references should be bumped accordingly */ + if (list->next != NULL) + update_targets_reference(table, list->next->data, + list->data, TRUE); + + removed += remove_table_entry(table, entry); + + if (builtin >= 0) { + list = list->next; + if (list) { + entry = list->data; + entry->builtin = builtin; + } + + table->underflow[builtin] -= removed; + for (list = chain_tail; list; list = list->next) { + entry = list->data; + + builtin = entry->builtin; + if (builtin < 0) + continue; + + table->hook_entry[builtin] -= removed; + table->underflow[builtin] -= removed; + } } + update_offsets(table); + return 0; } -static void remove_rule(const ipt_chainlabel chain, struct iptc_handle *h) +static struct ipt_replace *connman_iptables_blob(struct connman_iptables *table) { - iptc_flush_entries(chain, h); - iptc_delete_chain(chain, h); - iptc_commit(h); + struct ipt_replace *r; + GList *list; + struct connman_iptables_entry *e; + unsigned char *entry_index; + + r = g_try_malloc0(sizeof(struct ipt_replace) + table->size); + if (r == NULL) + return NULL; + + memset(r, 0, sizeof(*r) + table->size); + + r->counters = g_try_malloc0(sizeof(struct xt_counters) + * table->old_entries); + if (r->counters == NULL) { + g_free(r); + return NULL; + } + + strcpy(r->name, table->info->name); + r->num_entries = table->num_entries; + r->size = table->size; + + r->num_counters = table->old_entries; + r->valid_hooks = table->info->valid_hooks; + + 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) { + e = list->data; + + memcpy(entry_index, e->entry, e->entry->next_offset); + entry_index += e->entry->next_offset; + } + + return r; } +static void dump_target(struct connman_iptables *table, + struct ipt_entry *entry) + +{ + struct xtables_target *xt_t; + struct xt_entry_target *target; + + target = ipt_get_target(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: + printf("\ttarget RETURN\n"); + break; + + case -NF_ACCEPT - 1: + printf("\ttarget ACCEPT\n"); + break; + + case -NF_DROP - 1: + printf("\ttarget DROP\n"); + break; + + case -NF_QUEUE - 1: + printf("\ttarget QUEUE\n"); + break; + + case -NF_STOP - 1: + printf("\ttarget STOP\n"); + break; + + default: + printf("\tJUMP @%p (0x%x)\n", + (char*)table->blob_entries->entrytable + + t->verdict, t->verdict); + break; + } + + xt_t = xtables_find_target(IPT_STANDARD_TARGET, + XTF_LOAD_MUST_SUCCEED); + + if(xt_t->print != NULL) + xt_t->print(NULL, target, 1); + } else { + xt_t = xtables_find_target(target->u.user.name, XTF_TRY_LOAD); + if (xt_t == NULL) { + printf("\ttarget %s\n", target->u.user.name); + return; + } + + if(xt_t->print != NULL) { + printf("\ttarget "); + xt_t->print(NULL, target, 1); + printf("\n"); + } + } +} + +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)) + return; + + xt_m = xtables_find_match(match->u.user.name, XTF_TRY_LOAD, NULL); + if (xt_m == NULL) + goto out; + + if(xt_m->print != NULL) { + printf("\tmatch "); + xt_m->print(NULL, match, 1); + printf("\n"); + + return; + } + +out: + printf("\tmatch %s\n", match->u.user.name); + +} + +static int connman_iptables_dump_entry(struct ipt_entry *entry, + struct connman_iptables *table) +{ + struct xt_entry_target *target; + unsigned int offset; + int builtin; + + offset = (char *)entry - (char *)table->blob_entries->entrytable; + target = ipt_get_target(entry); + builtin = is_hook_entry(table, entry); + + if (entry_to_offset(table, entry) + entry->next_offset == + table->blob_entries->size) { + printf("End of CHAIN 0x%x\n", offset); + return 0; + } + + if (!strcmp(target->u.user.name, IPT_ERROR_TARGET)) { + printf("USER CHAIN (%s) %p match %p target %p size %d\n", + target->data, entry, entry->elems, + (char *)entry + entry->target_offset, + entry->next_offset); + + return 0; + } else if (builtin >= 0) { + printf("CHAIN (%s) %p match %p target %p size %d\n", + hooknames[builtin], entry, entry->elems, + (char *)entry + entry->target_offset, + entry->next_offset); + } else { + printf("RULE %p match %p target %p size %d\n", entry, + entry->elems, + (char *)entry + entry->target_offset, + entry->next_offset); + } + + dump_match(table, entry); + dump_target(table, 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 %p underflow %p (%#x)\n", + hooknames[i], + table->blob_entries->entrytable + + table->info->hook_entry[i], + 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", + table->info->name, + 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); + +} + +static int connman_iptables_get_entries(struct connman_iptables *table) +{ + socklen_t entry_size; + + entry_size = sizeof(struct ipt_get_entries) + table->info->size; + + return getsockopt(table->ipt_sock, IPPROTO_IP, IPT_SO_GET_ENTRIES, + table->blob_entries, &entry_size); +} + +static int connman_iptables_replace(struct connman_iptables *table, + struct ipt_replace *r) +{ + return setsockopt(table->ipt_sock, IPPROTO_IP, IPT_SO_SET_REPLACE, r, + sizeof(*r) + r->size); +} + +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) +{ + struct ipt_replace *repl; + + repl = connman_iptables_blob(table); + + return connman_iptables_replace(table, repl); +} + +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) + 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) +{ + struct connman_iptables *table = NULL; + char *module = NULL; + socklen_t s; + + if (xtables_insmod("ip_tables", NULL, TRUE) != 0) + goto err; + + module = g_strconcat("iptable_", table_name, NULL); + if (module == NULL) + goto err; + + if (xtables_insmod(module, NULL, TRUE) != 0) + goto err; + + g_free(module); + module = NULL; + + table = g_try_new0(struct connman_iptables, 1); + if (table == NULL) + return NULL; + + table->info = g_try_new0(struct ipt_getinfo, 1); + if (table->info == NULL) + goto err; + + table->ipt_sock = socket(AF_INET, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_RAW); + if (table->ipt_sock < 0) + goto err; + + s = sizeof(*table->info); + strcpy(table->info->name, table_name); + if (getsockopt(table->ipt_sock, IPPROTO_IP, IPT_SO_GET_INFO, + table->info, &s) < 0) + goto err; + + table->blob_entries = g_try_malloc0(sizeof(struct ipt_get_entries) + + table->info->size); + if (table->blob_entries == NULL) + goto err; + + strcpy(table->blob_entries->name, table_name); + table->blob_entries->size = table->info->size; + + if (connman_iptables_get_entries(table) < 0) + goto err; + + table->num_entries = 0; + 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); + + return table; + +err: + g_free(module); + + connman_iptables_cleanup(table); + + return NULL; +} + +static struct option connman_iptables_opts[] = { + {.name = "append", .has_arg = 1, .val = 'A'}, + {.name = "delete", .has_arg = 1, .val = 'D'}, + {.name = "flush-chain", .has_arg = 1, .val = 'F'}, + {.name = "insert", .has_arg = 1, .val = 'I'}, + {.name = "list", .has_arg = 2, .val = 'L'}, + {.name = "new-chain", .has_arg = 1, .val = 'N'}, + {.name = "delete-chain", .has_arg = 1, .val = 'X'}, + {.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}, +}; + +struct xtables_globals connman_iptables_globals = { + .option_offset = 0, + .opts = connman_iptables_opts, + .orig_opts = connman_iptables_opts, +}; + +static struct xtables_target *prepare_target(struct connman_iptables *table, + char *target_name) +{ + struct xtables_target *xt_t = NULL; + gboolean is_builtin, is_user_defined; + GList *chain_head = NULL; + size_t target_size; + + is_builtin = FALSE; + is_user_defined = FALSE; + + if (is_builtin_target(target_name)) + is_builtin = TRUE; + else { + chain_head = find_chain_head(table, target_name); + if (chain_head != NULL && chain_head->next != NULL) + is_user_defined = TRUE; + } + + if (is_builtin || is_user_defined) + xt_t = xtables_find_target(IPT_STANDARD_TARGET, + XTF_LOAD_MUST_SUCCEED); + else + xt_t = xtables_find_target(target_name, XTF_TRY_LOAD); + + if (xt_t == NULL) + return NULL; + + target_size = ALIGN(sizeof(struct ipt_entry_target)) + xt_t->size; + + xt_t->t = g_try_malloc0(target_size); + if (xt_t->t == NULL) + return NULL; + + xt_t->t->u.target_size = target_size; + + if (is_builtin || is_user_defined) { + struct xt_standard_target *target; + + target = (struct xt_standard_target *)(xt_t->t); + strcpy(target->target.u.user.name, IPT_STANDARD_TARGET); + + if (is_builtin == TRUE) + target->verdict = target_to_verdict(target_name); + else if (is_user_defined == TRUE) { + struct connman_iptables_entry *target_rule; + + if (chain_head == NULL) { + g_free(xt_t->t); + return NULL; + } + + target_rule = chain_head->next->data; + target->verdict = target_rule->offset; + } + } else { + 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( +#if XTABLES_VERSION_CODE > 5 + connman_iptables_globals.orig_opts, +#endif + connman_iptables_globals.opts, + xt_t->extra_opts, + &xt_t->option_offset); + + if (connman_iptables_globals.opts == NULL) { + g_free(xt_t->t); + xt_t = NULL; + } + + return xt_t; +} int main(int argc, char *argv[]) { - struct iptc_handle *h; - struct ipt_entry *e; + 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; + char *delete_chain, *flush_chain; + int c, in_len, out_len; + size_t size; + gboolean dump, invert, delete, insert, delete_rule; + struct in_addr src, dst; + + xtables_init_all(&connman_iptables_globals, NFPROTO_IPV4); + + dump = FALSE; + invert = FALSE; + delete = FALSE; + insert = FALSE; + delete_rule = FALSE; + table_name = chain = new_chain = match_name = target_name = NULL; + delete_chain = flush_chain = NULL; + memset(&ip, 0, sizeof(struct ipt_ip)); + table = NULL; + xt_m = NULL; + xt_t = NULL; + + while ((c = getopt_long(argc, argv, "-A:D:F:I:L::N:X:d:i:j:m:o:s:t:", + connman_iptables_globals.opts, NULL)) != -1) { + switch (c) { + case 'A': + /* It is either -A, -D or -I at once */ + if (chain) + goto out; + + chain = optarg; + break; + + case 'D': + /* It is either -A, -D or -I at once */ + if (chain) + goto out; + + chain = optarg; + delete_rule = TRUE; + break; + + case 'F': + flush_chain = optarg; + break; + + case 'I': + /* It is either -A, -D or -I at once */ + if (chain) + goto out; + + chain = optarg; + insert = TRUE; + break; + + case 'L': + dump = true; + break; + + case 'N': + new_chain = optarg; + break; + + case 'X': + delete = true; + delete_chain = optarg; + break; - if (argc < 2) { - printf("Usage: iptables-test [chain-name]\n"); + 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; + break; + + 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( +#if XTABLES_VERSION_CODE > 5 + connman_iptables_globals.orig_opts, +#endif + connman_iptables_globals.opts, + xt_m->extra_opts, + &xt_m->option_offset); + if (connman_iptables_globals.opts == NULL) + goto out; + } + + 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': + 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: + 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; } - h = iptc_init("filter"); - if (!h) { - printf("libiptc initialization error (%s)\n", - iptc_strerror(errno)); - exit(errno); + if (flush_chain) { + printf("Flush chain %s\n", flush_chain); + + connman_iptables_flush_chain(table, flush_chain); + + goto commit; } - xtables_init(); - xtables_set_nfproto(NFPROTO_IPV4); + if (dump) { + connman_iptables_dump(table); - e = build_quota_drop_entry(); - if (e == NULL) + return 0; + } + + if (chain && new_chain) return -1; - add_rule(argv[1], e, h); + if (new_chain) { + printf("New chain %s\n", new_chain); + + connman_iptables_add_chain(table, new_chain); + + goto commit; + } + + if (chain) { + xt_t = prepare_target(table, target_name); + if (xt_t == NULL) + goto out; + + if (delete_rule == TRUE) { + printf("Deleting %s to %s (match %s)\n", target_name, + chain, match_name); + + connman_iptables_delete_rule(table, &ip, chain, + target_name, xt_t, match_name, xt_m); + + goto commit; + } + + if (insert == TRUE) { + printf("Inserting %s to %s (match %s)\n", target_name, + chain, match_name); + + connman_iptables_insert_rule(table, &ip, chain, + target_name, xt_t, match_name, xt_m); + } else { + printf("Appending %s to %s (match %s)\n", target_name, + chain, match_name); + + connman_iptables_append_rule(table, &ip, chain, + target_name, xt_t, match_name, xt_m); + } + } + +commit: + + connman_iptables_commit(table); + +out: + connman_iptables_cleanup(table); - print_tables(h); + if (xt_t) + g_free(xt_t->t); - remove_rule(argv[1], h); + if (xt_m) + g_free(xt_m->m); return 0; }