X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=src%2Fiptables.c;h=531e93373c810f07ce26650eb6bccce6300d0930;hb=89077bfd2abf7984027552c8cee223a0c6dab57c;hp=0bfcbcfa8e0e44d49b5ba8f1acb06a250ed7706c;hpb=ff55128f383f51a66a2cea9e874295e5f6bb62c5;p=framework%2Fconnectivity%2Fconnman.git diff --git a/src/iptables.c b/src/iptables.c index 0bfcbcf..531e933 100644 --- a/src/iptables.c +++ b/src/iptables.c @@ -2,7 +2,7 @@ * * Connection Manager * - * Copyright (C) 2007-2010 Intel Corporation. All rights reserved. + * Copyright (C) 2007-2012 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 @@ -50,6 +50,8 @@ static const char *hooknames[] = { #define LABEL_QUEUE "QUEUE" #define LABEL_RETURN "RETURN" +#define XT_OPTION_OFFSET_SCALE 256 + /* fn returns 0 to continue iteration */ #define _XT_ENTRY_ITERATE_CONTINUE(type, entries, size, n, fn, args...) \ ({ \ @@ -59,7 +61,7 @@ static const char *hooknames[] = { type *__entry; \ \ for (__i = 0, __n = 0; __i < (size); \ - __i += __entry->next_offset, __n++) { \ + __i += __entry->next_offset, __n++) { \ __entry = (void *)(entries) + __i; \ if (__n < n) \ continue; \ @@ -82,7 +84,7 @@ 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]; }; @@ -242,38 +244,16 @@ static GList *find_chain_head(struct connman_iptables *table, static GList *find_chain_tail(struct connman_iptables *table, char *chain_name) { + struct connman_iptables_entry *tail; GList *chain_head, *list; - struct connman_iptables_entry *head, *tail; - struct ipt_entry *entry; - struct xt_entry_target *target; - int builtin; - - /* First we look for the head */ - 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; - } - - if (list == NULL) + chain_head = find_chain_head(table, chain_name); + if (chain_head == NULL) return NULL; - chain_head = list; - /* Then we look for the next chain */ for (list = chain_head->next; list; list = list->next) { tail = list->data; - entry = tail->entry; if (is_chain(table, tail)) return list; @@ -306,13 +286,41 @@ static void update_offsets(struct connman_iptables *table) } } +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 iptables_add_entry(struct connman_iptables *table, struct ipt_entry *entry, GList *before, int builtin) { - GList *list; - struct connman_iptables_entry *e, *tmp, *entry_before; - struct xt_standard_target *t; + struct connman_iptables_entry *e, *entry_before; if (table == NULL) return -1; @@ -337,19 +345,86 @@ static int iptables_add_entry(struct connman_iptables *table, entry_before = before->data; /* - * We've just insterted a new entry. All references before it + * We've just appended/insterted a new entry. All references * should be bumped accordingly. */ - for (list = table->entries; list != before; list = list->next) { - tmp = list->data; + update_targets_reference(table, entry_before, e, FALSE); - if (!is_jump(tmp)) - continue; + update_offsets(table); - t = (struct xt_standard_target *)ipt_get_target(tmp->entry); + 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); - if (t->verdict >= entry_before->offset) - t->verdict += entry->next_offset; + table->entries = g_list_remove(table->entries, entry); + + return removed; +} + +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); + + 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); @@ -363,7 +438,7 @@ static int iptables_add_chain(struct connman_iptables *table, 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; @@ -380,23 +455,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, -1) < 0) - goto err; + goto err_head; /* tail entry */ entry_return_size = sizeof(struct ipt_entry) + @@ -421,25 +496,57 @@ static int iptables_add_chain(struct connman_iptables *table, 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, struct ipt_ip *ip, +static int iptables_delete_chain(struct connman_iptables *table, char *name) +{ + struct connman_iptables_entry *entry; + GList *chain_head, *chain_tail; + + 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 *new_rule(struct ipt_ip *ip, char *target_name, struct xtables_target *xt_t, - char *match_name, struct xtables_match *xt_m) + struct xtables_rule_match *xt_rm) { + struct xtables_rule_match *tmp_xt_rm; struct ipt_entry *new_entry; size_t match_size, target_size; - int is_builtin = is_builtin_target(target_name); - if (xt_m) - match_size = xt_m->m->u.match_size; - else - match_size = 0; + match_size = 0; + for (tmp_xt_rm = xt_rm; tmp_xt_rm != NULL; tmp_xt_rm = tmp_xt_rm->next) + match_size += tmp_xt_rm->match->m->u.match_size; if (xt_t) target_size = ALIGN(xt_t->t->u.target_size); @@ -456,50 +563,20 @@ new_rule(struct connman_iptables *table, struct ipt_ip *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); + match_size = 0; + for (tmp_xt_rm = xt_rm; tmp_xt_rm != NULL; + tmp_xt_rm = tmp_xt_rm->next) { + memcpy(new_entry->elems + match_size, tmp_xt_rm->match->m, + tmp_xt_rm->match->m->u.match_size); + match_size += tmp_xt_rm->match->m->u.match_size; } 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; @@ -535,30 +612,26 @@ static void update_hooks(struct connman_iptables *table, GList *chain_head, } } -static int -iptables_add_rule(struct connman_iptables *table, +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, struct xtables_rule_match *xt_rm) { GList *chain_tail, *chain_head; struct ipt_entry *new_entry; struct connman_iptables_entry *head; - int builtin = -1; - - chain_tail = find_chain_tail(table, chain_name); - if (chain_tail == NULL) - return -EINVAL; chain_head = find_chain_head(table, chain_name); if (chain_head == NULL) - return -EINVAL; + return NULL; + + chain_tail = find_chain_tail(table, chain_name); + if (chain_tail == NULL) + return NULL; - new_entry = new_rule(table, ip, - target_name, xt_t, - match_name, xt_m); + new_entry = new_rule(ip, target_name, xt_t, xt_rm); if (new_entry == NULL) - return -EINVAL; + return NULL; update_hooks(table, chain_head, new_entry); @@ -569,17 +642,321 @@ iptables_add_rule(struct connman_iptables *table, */ head = chain_head->data; if (head->builtin < 0) - builtin = -1; + *builtin = -1; else if (chain_head == chain_tail->prev) { + *builtin = head->builtin; head->builtin = -1; - builtin = head->builtin; } - return iptables_add_entry(table, new_entry, chain_tail->prev, builtin); + return new_entry; +} + +static int iptables_append_rule(struct connman_iptables *table, + struct ipt_ip *ip, char *chain_name, + char *target_name, struct xtables_target *xt_t, + struct xtables_rule_match *xt_rm) +{ + GList *chain_tail; + struct ipt_entry *new_entry; + int builtin = -1, ret; + + DBG(""); + + 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, &builtin, xt_rm); + if (new_entry == NULL) + return -EINVAL; + + ret = iptables_add_entry(table, new_entry, chain_tail->prev, builtin); + if (ret < 0) + g_free(new_entry); + + return ret; } -static struct ipt_replace * -iptables_blob(struct connman_iptables *table) +static int iptables_insert_rule(struct connman_iptables *table, + struct ipt_ip *ip, char *chain_name, + char *target_name, struct xtables_target *xt_t, + struct xtables_rule_match *xt_rm) +{ + struct ipt_entry *new_entry; + int builtin = -1, ret; + GList *chain_head; + + 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, &builtin, xt_rm); + if (new_entry == NULL) + return -EINVAL; + + if (builtin == -1) + chain_head = chain_head->next; + + ret = iptables_add_entry(table, new_entry, chain_head, 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; + + if (i_e1->next_offset != i_e2->next_offset) + return FALSE; + + 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; + } + + return TRUE; +} + +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 TRUE; +} + +static GList *find_existing_rule(struct connman_iptables *table, + struct ipt_ip *ip, char *chain_name, + char *target_name, struct xtables_target *xt_t, + struct xtables_match *xt_m, + struct xtables_rule_match *xt_rm) +{ + 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; + + chain_head = find_chain_head(table, chain_name); + if (chain_head == NULL) + return NULL; + + chain_tail = find_chain_tail(table, chain_name); + if (chain_tail == NULL) + return NULL; + + if (!xt_t && !xt_m) + return NULL; + + entry_test = new_rule(ip, target_name, xt_t, xt_rm); + if (entry_test == NULL) + return NULL; + + 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 (; 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; + } + + break; + } + + g_free(entry_test); + + if (list != chain_tail->prev) + return list; + + return NULL; +} + +static int iptables_delete_rule(struct connman_iptables *table, + struct ipt_ip *ip, char *chain_name, + char *target_name, struct xtables_target *xt_t, + struct xtables_match *xt_m, + struct xtables_rule_match *xt_rm) +{ + struct connman_iptables_entry *entry; + GList *chain_tail, *list; + int builtin, removed; + + removed = 0; + + chain_tail = find_chain_tail(table, chain_name); + if (chain_tail == NULL) + return -EINVAL; + + list = find_existing_rule(table, ip, chain_name, target_name, + xt_t, xt_m, xt_rm); + if (list == NULL) + return -EINVAL; + + 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) + 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 int iptables_compare_rule(struct connman_iptables *table, + struct ipt_ip *ip, char *chain_name, + char *target_name, struct xtables_target *xt_t, + struct xtables_match *xt_m, + struct xtables_rule_match *xt_rm) +{ + struct connman_iptables_entry *entry; + GList *found; + + found = find_existing_rule(table, ip, chain_name, target_name, + xt_t, xt_m, xt_rm); + if (found == NULL) + return -EINVAL; + + entry = found->data; + if (entry == NULL) + return -EINVAL; + + return 0; +} + + +static int iptables_change_policy(struct connman_iptables *table, + char *chain_name, char *policy) +{ + GList *chain_head; + struct connman_iptables_entry *entry; + struct xt_entry_target *target; + struct xt_standard_target *t; + int verdict; + + verdict = target_to_verdict(policy); + if (verdict == 0) + return -EINVAL; + + chain_head = find_chain_head(table, chain_name); + if (chain_head == NULL) + return -EINVAL; + + entry = chain_head->data; + if (entry->builtin < 0) + return -EINVAL; + + target = ipt_get_target(entry->entry); + + t = (struct xt_standard_target *)target; + t->verdict = verdict; + + return 0; +} + +static struct ipt_replace *iptables_blob(struct connman_iptables *table) { struct ipt_replace *r; GList *list; @@ -593,7 +970,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; @@ -827,12 +1204,16 @@ static void table_cleanup(struct connman_iptables *table) GList *list; struct connman_iptables_entry *entry; + if (table == NULL) + return; + close(table->ipt_sock); for (list = table->entries; list; list = list->next) { entry = list->data; g_free(entry->entry); + g_free(entry); } g_list_free(table->entries); @@ -843,9 +1224,26 @@ static void table_cleanup(struct connman_iptables *table) static struct connman_iptables *iptables_init(char *table_name) { - struct connman_iptables *table; + struct connman_iptables *table = NULL; + char *module = NULL; socklen_t s; + DBG("%s", table_name); + + if (xtables_insmod("ip_tables", NULL, TRUE) != 0) + return NULL; + + module = g_strconcat("iptable_", table_name, NULL); + if (module == NULL) + return NULL; + + if (xtables_insmod(module, NULL, TRUE) != 0) { + g_free(module); + return NULL; + } + + g_free(module); + table = g_hash_table_lookup(table_hash, table_name); if (table != NULL) return table; @@ -858,7 +1256,7 @@ static struct connman_iptables *iptables_init(char *table_name) if (table->info == NULL) goto err; - table->ipt_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + table->ipt_sock = socket(AF_INET, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_RAW); if (table->ipt_sock < 0) goto err; @@ -892,12 +1290,11 @@ static struct connman_iptables *iptables_init(char *table_name) 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; err: - table_cleanup(table); return NULL; @@ -905,8 +1302,14 @@ err: static struct option iptables_opts[] = { {.name = "append", .has_arg = 1, .val = 'A'}, + {.name = "compare", .has_arg = 1, .val = 'C'}, + {.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 = "policy", .has_arg = 1, .val = 'P'}, + {.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'}, @@ -923,39 +1326,263 @@ struct xtables_globals iptables_globals = { .orig_opts = 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); + } + +#if XTABLES_VERSION_CODE > 5 + if (xt_t->x6_options != NULL) + iptables_globals.opts = + xtables_options_xfrm( + iptables_globals.orig_opts, + iptables_globals.opts, + xt_t->x6_options, + &xt_t->option_offset); + else +#endif + 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) { + g_free(xt_t->t); + xt_t = NULL; + } + + return xt_t; +} + +static struct xtables_match *prepare_matches(struct connman_iptables *table, + struct xtables_rule_match **xt_rm, char *match_name) +{ + struct xtables_match *xt_m; + size_t match_size; + + if (match_name == NULL) + return NULL; + + xt_m = xtables_find_match(match_name, XTF_LOAD_MUST_SUCCEED, xt_rm); + match_size = ALIGN(sizeof(struct ipt_entry_match)) + xt_m->size; + + xt_m->m = g_try_malloc0(match_size); + if (xt_m->m == NULL) + return NULL; + + xt_m->m->u.match_size = match_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) + goto done; + +#if XTABLES_VERSION_CODE > 5 + if (xt_m->x6_options != NULL) + iptables_globals.opts = + xtables_options_xfrm( + iptables_globals.orig_opts, + iptables_globals.opts, + xt_m->x6_options, + &xt_m->option_offset); + else +#endif + 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) { + g_free(xt_m->m); + xt_m = NULL; + } + +done: + return xt_m; +} + +static int parse_ip_and_mask(const char *str, struct in_addr *ip, struct in_addr *mask) +{ + char **tokens; + uint32_t prefixlength; + uint32_t tmp; + int err; + + tokens = g_strsplit(str, "/", 2); + if (tokens == NULL) + return -1; + + if (!inet_pton(AF_INET, tokens[0], ip)) { + err = -1; + goto out; + } + + if (tokens[1] != NULL) { + prefixlength = strtol(tokens[1], NULL, 10); + if (prefixlength > 31) { + err = -1; + goto out; + } + + tmp = ~(0xffffffff >> prefixlength); + } else { + tmp = 0xffffffff; + } + + mask->s_addr = htonl(tmp); + ip->s_addr = ip->s_addr & mask->s_addr; + err = 0; +out: + g_strfreev(tokens); + + return err; +} + static int iptables_command(int argc, char *argv[]) { struct connman_iptables *table; - struct xtables_match *xt_m; + struct xtables_rule_match *xt_rm, *tmp_xt_rm; + struct xtables_match *xt_m, *xt_m_t; struct xtables_target *xt_t; struct ipt_ip ip; char *table_name, *chain, *new_chain, *match_name, *target_name; + char *flush_chain, *delete_chain, *policy; int c, ret, in_len, out_len; - size_t size; - gboolean dump, invert; - struct in_addr src, dst; + gboolean dump, invert, insert, delete, compare; if (argc == 0) return -EINVAL; dump = FALSE; invert = FALSE; - table_name = chain = new_chain = match_name = target_name = NULL; + insert = FALSE; + delete = FALSE; + compare = FALSE; + chain = new_chain = match_name = target_name = NULL; + flush_chain = delete_chain = policy = NULL; memset(&ip, 0, sizeof(struct ipt_ip)); table = NULL; + xt_rm = NULL; xt_m = NULL; xt_t = NULL; ret = 0; + /* extension's options will generate false-positives errors */ + opterr = 0; + optind = 0; while ((c = getopt_long(argc, argv, - "-A:L::N:d:j:i:m:o:s:t:", iptables_globals.opts, NULL)) != -1) { + "-A:C:D:F:I:L::N:P:X:d:j:i:m:o:s:t:", + iptables_globals.opts, NULL)) != -1) { switch (c) { case 'A': + /* It is either -A, -C, -D or -I at once */ + if (chain) + goto out; + chain = optarg; break; + case 'C': + /* It is either -A, -C, -D or -I at once */ + if (chain) + goto out; + + chain = optarg; + compare = TRUE; + break; + + case 'D': + /* It is either -A, -C, -D or -I at once */ + if (chain) + goto out; + + chain = optarg; + delete = TRUE; + break; + + case 'F': + flush_chain = optarg; + break; + + case 'I': + /* It is either -A, -C, -D or -I at once */ + if (chain) + goto out; + + chain = optarg; + insert = TRUE; + break; + case 'L': dump = TRUE; break; @@ -964,13 +1591,23 @@ static int iptables_command(int argc, char *argv[]) new_chain = optarg; break; + case 'P': + chain = optarg; + if (optind < argc) + policy = argv[optind++]; + else + goto out; + + break; + + case 'X': + delete_chain = optarg; + break; + case 'd': - if (!inet_pton(AF_INET, optarg, &dst)) + if (!parse_ip_and_mask(optarg, &ip.dst, &ip.dmsk)) break; - ip.dst = dst; - inet_pton(AF_INET, "255.255.255.255", &ip.dmsk); - if (invert) ip.invflags |= IPT_INV_DSTIP; @@ -992,53 +1629,17 @@ static int iptables_command(int argc, char *argv[]) case 'j': target_name = optarg; - xt_t = xtables_find_target(target_name, XTF_TRY_LOAD); - + xt_t = prepare_target(table, target_name); 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); - iptables_globals.opts = - xtables_merge_options(iptables_globals.opts, - xt_t->extra_opts, - &xt_t->option_offset); - if (iptables_globals.opts == NULL) goto out; 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); + xt_m = prepare_matches(table, &xt_rm, match_name); 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) { - iptables_globals.opts = - xtables_merge_options(iptables_globals.opts, - xt_m->extra_opts, - &xt_m->option_offset); - if (iptables_globals.opts == NULL) - goto out; - } break; @@ -1057,12 +1658,9 @@ static int iptables_command(int argc, char *argv[]) break; case 's': - if (!inet_pton(AF_INET, optarg, &src)) + if (!parse_ip_and_mask(optarg, &ip.src, &ip.smsk)) break; - ip.src = src; - inet_pton(AF_INET, "255.255.255.255", &ip.smsk); - if (invert) ip.invflags |= IPT_INV_SRCIP; @@ -1070,6 +1668,13 @@ static int iptables_command(int argc, char *argv[]) case 't': table_name = optarg; + + table = iptables_init(table_name); + if (table == NULL) { + ret = -EINVAL; + goto out; + } + break; case 1: @@ -1085,28 +1690,96 @@ static int iptables_command(int argc, char *argv[]) goto out; 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; +#if XTABLES_VERSION_CODE > 5 + if (xt_t != NULL && (xt_t->x6_parse != NULL || + xt_t->parse != NULL) && + (c >= (int) xt_t->option_offset && + c < (int) xt_t->option_offset + + XT_OPTION_OFFSET_SCALE)) { + xtables_option_tpcall(c, argv, + invert, xt_t, NULL); - xt_m->parse(c - xt_m->option_offset, argv, - invert, &xt_m->mflags, NULL, &xt_m->m); + break; } + for (tmp_xt_rm = xt_rm; tmp_xt_rm != NULL; + tmp_xt_rm = tmp_xt_rm->next) { + xt_m_t = tmp_xt_rm->match; + + if (tmp_xt_rm->completed || + (xt_m_t->x6_parse == NULL && + xt_m_t->parse == NULL)) + continue; + + if (c < (int) xt_m_t->option_offset || + c >= (int) xt_m_t->option_offset + + XT_OPTION_OFFSET_SCALE) + continue; + + xtables_option_mpcall(c, argv, + invert, xt_m_t, NULL); + + break; + } +#else + 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)) { + + for (tmp_xt_rm = xt_rm; tmp_xt_rm != NULL; + tmp_xt_rm = tmp_xt_rm->next) { + xt_m_t = tmp_xt_rm->match; + + if (tmp_xt_rm->completed || + xt_m_t->parse == NULL) + continue; + + if (xt_m->parse(c - xt_m->option_offset, + argv, invert, &xt_m->mflags, + NULL, &xt_m->m)) + break; + } + } +#endif break; } invert = FALSE; } - if (table_name == NULL) - table_name = "filter"; +#if XTABLES_VERSION_CODE > 5 + for (tmp_xt_rm = xt_rm; tmp_xt_rm != NULL; + tmp_xt_rm = tmp_xt_rm->next) + xtables_option_mfcall(tmp_xt_rm->match); + + if (xt_t != NULL) + xtables_option_tfcall(xt_t); +#else + for (tmp_xt_rm = xt_rm; tmp_xt_rm != NULL; + tmp_xt_rm = tmp_xt_rm->next) + if (tmp_xt_rm->match->final_check != NULL) + tmp_xt_rm->match->final_check( + tmp_xt_rm->match->mflags); + + if (xt_t != NULL && xt_t->final_check != NULL) + xt_t->final_check(xt_t->tflags); +#endif - table = iptables_init(table_name); if (table == NULL) { - ret = -EINVAL; + table_name = "filter"; + + table = iptables_init(table_name); + if (table == NULL) { + ret = -EINVAL; + goto out; + } + } + + if (delete_chain != NULL) { + printf("Delete chain %s\n", delete_chain); + + iptables_delete_chain(table, delete_chain); + goto out; } @@ -1117,6 +1790,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; @@ -1130,16 +1811,50 @@ static int iptables_command(int argc, char *argv[]) } if (chain) { - if (target_name == NULL) - return -1; + if (policy != NULL) { + printf("Changing policy of %s to %s\n", chain, policy); - DBG("Adding %s to %s (match %s)", - target_name, chain, match_name); + iptables_change_policy(table, chain, policy); + + goto out; + } - ret = iptables_add_rule(table, &ip, chain, target_name, xt_t, - match_name, xt_m); + if (xt_t == NULL) + goto out; - goto out; + if (compare == TRUE) { + ret = iptables_compare_rule(table, &ip, chain, + target_name, xt_t, xt_m, xt_rm); + goto out; + } + + if (delete == TRUE) { + DBG("Deleting %s to %s (match %s)\n", + target_name, chain, match_name); + + ret = iptables_delete_rule(table, &ip, chain, + target_name, xt_t, xt_m, xt_rm); + + goto out; + } + + if (insert == TRUE) { + DBG("Inserting %s to %s (match %s)", + target_name, chain, match_name); + + ret = iptables_insert_rule(table, &ip, chain, + target_name, xt_t, xt_rm); + + goto out; + } else { + DBG("Adding %s to %s (match %s)", + target_name, chain, match_name); + + ret = iptables_append_rule(table, &ip, chain, + target_name, xt_t, xt_rm); + + goto out; + } } out: @@ -1173,8 +1888,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) { @@ -1201,6 +1917,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) @@ -1208,7 +1927,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) @@ -1223,7 +1952,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); @@ -1233,6 +1962,8 @@ int __connman_iptables_init(void) void __connman_iptables_cleanup(void) { + DBG(""); + g_hash_table_destroy(table_hash); xtables_free_opts(1);