#include "connman.h"
+void flush_table(const char *name);
/*
* Some comments on how the iptables API works (some of them from the
unsigned int hook,size_t size,
unsigned int offset, void *user_data);
+static unsigned int next_hook_entry_index(unsigned int *valid_hooks)
+{
+ unsigned int h;
+
+ if (*valid_hooks == 0)
+ return NF_INET_NUMHOOKS;
+
+ h = __builtin_ffs(*valid_hooks) - 1;
+ *valid_hooks ^= (1 << h);
+
+ return h;
+}
+
static int iterate_entries(struct ipt_entry *entries,
unsigned int valid_hooks,
unsigned int *hook_entry,
+ unsigned int *underflow,
size_t size, iterate_entries_cb_t cb,
void *user_data)
{
- unsigned int i, h;
+ unsigned int offset, h, hook;
int builtin, err;
struct ipt_entry *entry;
- if (valid_hooks != 0)
- h = __builtin_ffs(valid_hooks) - 1;
- else
- h = NF_INET_NUMHOOKS;
+ h = next_hook_entry_index(&valid_hooks);
+ hook = h;
- for (i = 0, entry = entries; i < size;
- i += entry->next_offset) {
+ for (offset = 0, entry = entries; offset < size;
+ offset += entry->next_offset) {
builtin = -1;
- entry = (void *)entries + i;
+ entry = (void *)entries + offset;
/*
- * Find next valid hook which offset is higher
- * or equal with the current offset.
+ * Updating builtin, hook and h is very tricky.
+ * The rules are:
+ * - builtin is only set to the current hook number
+ * if the current entry is the hook entry (aka chain
+ * head). And only for builtin chains, never for
+ * the user chains.
+ * - hook is the current hook number. If we
+ * look at user chains it needs to be NF_INET_NETNUMHOOKS.
+ * - h is the next hook entry. Thous we need to be carefully
+ * not to access the table when h is NF_INET_NETNUMHOOKS.
*/
- if (h < NF_INET_NUMHOOKS) {
- if (hook_entry[h] < i) {
- valid_hooks ^= (1 << h);
-
- if (valid_hooks != 0)
- h = __builtin_ffs(valid_hooks) - 1;
- else
- h = NF_INET_NUMHOOKS;
- }
+ if (h < NF_INET_NUMHOOKS && hook_entry[h] == offset) {
+ builtin = h;
+ hook = h;
+ }
- if (hook_entry[h] == i)
- builtin = h;
+ if (h == NF_INET_NUMHOOKS)
+ hook = h;
+
+ if (h < NF_INET_NUMHOOKS && underflow[h] <= offset) {
+ h = next_hook_entry_index(&valid_hooks);
}
- err = cb(entry, builtin, h, size, i, user_data);
+ err = cb(entry, builtin, hook, size, offset, user_data);
if (err < 0)
return err;
-
}
return 0;
return false;
}
+static gboolean is_fallthrough(struct connman_iptables_entry *e)
+{
+ struct xt_entry_target *target;
+
+ target = ipt_get_target(e->entry);
+ if (!g_strcmp0(target->u.user.name, IPT_STANDARD_TARGET)) {
+ struct xt_standard_target *t;
+
+ t = (struct xt_standard_target *)target;
+ if (t->verdict == 0)
+ return true;
+ }
+ return false;
+}
+
static gboolean is_chain(struct connman_iptables *table,
struct connman_iptables_entry *e)
{
t->verdict += offset;
}
}
+
+ if (is_fallthrough(modified_entry)) {
+ t = (struct xt_standard_target *) ipt_get_target(modified_entry->entry);
+
+ t->verdict = entry_before->offset +
+ modified_entry->entry->target_offset +
+ ALIGN(sizeof(struct xt_standard_target));
+ t->target.u.target_size =
+ ALIGN(sizeof(struct xt_standard_target));
+ }
}
static int iptables_add_entry(struct connman_iptables *table,
table->size -= entry->entry->next_offset;
removed = entry->entry->next_offset;
- g_free(entry->entry);
-
table->entries = g_list_remove(table->entries, entry);
+ g_free(entry->entry);
+ g_free(entry);
+
return removed;
}
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 int iptables_insert_rule(struct connman_iptables *table,
struct ipt_ip *ip, const char *chain_name,
const char *target_name,
static gboolean is_same_target(struct xt_entry_target *xt_e_t1,
struct xt_entry_target *xt_e_t2)
{
+ unsigned int i;
+
if (xt_e_t1 == NULL || xt_e_t2 == NULL)
return FALSE;
- if (strcmp(xt_e_t1->u.user.name, IPT_STANDARD_TARGET) == 0) {
+ if (strcmp(xt_e_t1->u.user.name, "") == 0 &&
+ strcmp(xt_e_t2->u.user.name, "") == 0) {
+ /* fallthrough */
+ return TRUE;
+ } else if (strcmp(xt_e_t1->u.user.name, IPT_STANDARD_TARGET) == 0) {
struct xt_standard_target *xt_s_t1;
struct xt_standard_target *xt_s_t2;
if (strcmp(xt_e_t1->u.user.name, xt_e_t2->u.user.name) != 0)
return FALSE;
+
+ for (i = 0; i < xt_e_t1->u.target_size -
+ sizeof(struct xt_standard_target); i++) {
+ if ((xt_e_t1->data[i] ^ xt_e_t2->data[i]) != 0)
+ return FALSE;
+ }
}
return TRUE;
static gboolean is_same_match(struct xt_entry_match *xt_e_m1,
struct xt_entry_match *xt_e_m2)
{
+ unsigned int i;
+
if (xt_e_m1 == NULL || xt_e_m2 == NULL)
return FALSE;
if (strcmp(xt_e_m1->u.user.name, xt_e_m2->u.user.name) != 0)
return FALSE;
+ for (i = 0; i < xt_e_m1->u.match_size - sizeof(struct xt_entry_match);
+ i++) {
+ if ((xt_e_m1->data[i] ^ xt_e_m2->data[i]) != 0)
+ return FALSE;
+ }
+
return TRUE;
}
struct xtables_rule_match *xt_rm)
{
struct connman_iptables_entry *entry;
- GList *chain_tail, *list;
+ GList *chain_head, *chain_tail, *list;
int builtin, removed;
removed = 0;
+ chain_head = find_chain_head(table, chain_name);
+ if (chain_head == NULL)
+ return -EINVAL;
+
chain_tail = find_chain_tail(table, chain_name);
if (chain_tail == NULL)
return -EINVAL;
if (list == NULL)
return -EINVAL;
+ entry = chain_head->data;
+ builtin = entry->builtin;
+
entry = list->data;
if (entry == NULL)
return -EINVAL;
- builtin = entry->builtin;
-
/* We have deleted a rule,
* all references should be bumped accordingly */
if (list->next != NULL)
return 0;
}
-static int iptables_compare_rule(struct connman_iptables *table,
- struct ipt_ip *ip, const char *chain_name,
- const 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,
const char *chain_name, const char *policy)
{
iterate_entries(table->blob_entries->entrytable,
table->info->valid_hooks,
table->info->hook_entry,
+ table->info->underflow,
table->blob_entries->size,
print_entry, dump_entry);
}
repl->underflow[NF_IP_POST_ROUTING]);
iterate_entries(repl->entries, repl->valid_hooks,
- repl->hook_entry, repl->size, print_entry, dump_entry);
+ repl->hook_entry, repl->underflow,
+ repl->size, print_entry, dump_entry);
}
static int iptables_get_entries(struct connman_iptables *table)
iterate_entries(table->blob_entries->entrytable,
table->info->valid_hooks, table->info->hook_entry,
- table->blob_entries->size, add_entry, table);
+ table->info->underflow, table->blob_entries->size,
+ add_entry, table);
g_hash_table_insert(table_hash, g_strdup(table_name), table);
return iptables_init(table_name);
}
-static void clear_tables_flags(void)
-{
- struct xtables_match *xt_m;
- struct xtables_target *xt_t;
-
- /*
- * Clear all flags because the flags are only valid
- * for one rule.
- */
- for (xt_m = xtables_matches; xt_m != NULL; xt_m = xt_m->next)
- xt_m->mflags = 0;
-
- for (xt_t = xtables_targets; xt_t != NULL; xt_t = xt_t->next) {
- xt_t->tflags = 0;
- xt_t->used = 0;
- }
-}
-
-static int iptables_command(int argc, char *argv[])
-{
- struct connman_iptables *table;
- 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;
- gboolean dump, invert, insert, delete, compare;
-
- if (argc == 0)
- return -EINVAL;
-
- dump = FALSE;
- invert = FALSE;
- insert = FALSE;
- delete = FALSE;
- compare = FALSE;
- chain = new_chain = match_name = target_name = NULL;
- flush_chain = delete_chain = policy = table_name = NULL;
- memset(&ip, 0, sizeof(struct ipt_ip));
- table = NULL;
- xt_rm = NULL;
- xt_m = NULL;
- xt_t = NULL;
- /* Default code for options parsing */
- ret = -EINVAL;
-
- clear_tables_flags();
-
- /* extension's options will generate false-positives errors */
- opterr = 0;
-
- optind = 0;
-
- while ((c = getopt_long(argc, argv,
- "-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;
-
- case 'N':
- 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 (!parse_ip_and_mask(optarg, &ip.dst, &ip.dmsk))
- break;
-
- 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;
-
- table = pre_load_table(table_name, table);
- if (table == NULL)
- goto out;
-
- xt_t = prepare_target(table, target_name);
- if (xt_t == NULL)
- goto out;
-
- break;
-
- case 'm':
- match_name = optarg;
-
- table = pre_load_table(table_name, table);
- if (table == NULL)
- goto out;
-
- xt_m = prepare_matches(table, &xt_rm, match_name);
- if (xt_m == 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 (!parse_ip_and_mask(optarg, &ip.src, &ip.smsk))
- break;
-
- if (invert)
- ip.invflags |= IPT_INV_SRCIP;
-
- break;
-
- case 't':
- table_name = optarg;
-
- table = pre_load_table(table_name, table);
- if (table == NULL)
- goto out;
-
- break;
-
- case 1:
- if (optarg[0] == '!' && optarg[1] == '\0') {
- invert = TRUE;
- optarg[0] = '\0';
- continue;
- }
-
- connman_error("Invalid option");
-
- goto out;
-
- default:
-#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);
-
- 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 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 = pre_load_table(table_name, table);
- if (table == NULL)
- goto out;
-
- /* Option parsing went fine, falling back to succes code */
- ret = 0;
-
- if (delete_chain != NULL) {
- printf("Delete chain %s\n", delete_chain);
-
- iptables_delete_chain(table, delete_chain);
-
- goto out;
- }
-
- if (dump) {
- dump_table(table);
-
- 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;
- }
-
- if (new_chain) {
- DBG("New chain %s", new_chain);
-
- ret = iptables_add_chain(table, new_chain);
- goto out;
- }
-
- if (chain) {
- if (policy != NULL) {
- printf("Changing policy of %s to %s\n", chain, policy);
-
- iptables_change_policy(table, chain, policy);
-
- goto out;
- }
-
- if (xt_t == NULL)
- 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:
- if (xt_t)
- g_free(xt_t->t);
-
- if (xt_m)
- g_free(xt_m->m);
-
- return ret;
-}
-
-int __connman_iptables_command(const char *format, ...)
-{
- char **argv, **arguments, *command;
- int argc, i, ret;
- va_list args;
-
- if (format == NULL)
- return -EINVAL;
-
- va_start(args, format);
-
- command = g_strdup_vprintf(format, args);
-
- va_end(args);
-
- if (command == NULL)
- return -ENOMEM;
-
- arguments = g_strsplit_set(command, " ", -1);
-
- for (argc = 0; arguments[argc]; argc++);
- ++argc;
-
- DBG("command %s argc %d", command, argc);
-
- argv = g_try_malloc0(argc * sizeof(char *));
- if (argv == NULL) {
- g_free(command);
- g_strfreev(arguments);
- return -ENOMEM;
- }
-
- argv[0] = "iptables";
- for (i = 1; i < argc; i++)
- argv[i] = arguments[i - 1];
-
- ret = iptables_command(argc, argv);
-
- g_free(command);
- g_strfreev(arguments);
- g_free(argv);
-
- return ret;
-}
-
struct parse_context {
int argc;
char **argv;
tokens = g_strsplit_set(str, " ", -1);
- for (i = 0; tokens[i]; i++);
+ i = g_strv_length(tokens);
/* Add space for the argv[0] value */
ctx->argc = i + 1;
for (i = 1; i < ctx->argc; i++)
ctx->argv[i] = tokens[i - 1];
+ g_free(tokens);
+
return 0;
}
return -ENOMEM;
/*
- * As side effect parsing a rule sets some global flags
- * which will be evaluated/verified. Let's reset them
- * to ensure we can parse more than one rule.
- */
- clear_tables_flags();
-
- /*
* Tell getopt_long not to generate error messages for unknown
* options and also reset optind back to 0.
*/
return err;
}
+static void reset_xtables(void)
+{
+ struct xtables_match *xt_m;
+ struct xtables_target *xt_t;
+
+ /*
+ * As side effect parsing a rule sets some global flags
+ * which will be evaluated/verified. Let's reset them
+ * to ensure we can parse more than one rule.
+ *
+ * Clear all flags because the flags are only valid
+ * for one rule.
+ */
+ for (xt_m = xtables_matches; xt_m != NULL; xt_m = xt_m->next)
+ xt_m->mflags = 0;
+
+ for (xt_t = xtables_targets; xt_t != NULL; xt_t = xt_t->next) {
+ xt_t->tflags = 0;
+ xt_t->used = 0;
+ }
+
+ /*
+ * We need also to free the memory implicitly allocated
+ * during parsing (see xtables_options_xfrm()).
+ * Note xt_params is actually iptables_globals.
+ */
+ if (xt_params->opts != xt_params->orig_opts) {
+ g_free(xt_params->opts);
+ xt_params->opts = xt_params->orig_opts;
+ }
+ xt_params->option_offset = 0;
+}
+
static void cleanup_parse_context(struct parse_context *ctx)
{
+ struct xtables_rule_match *rm, *tmp;
+
g_strfreev(ctx->argv);
g_free(ctx->ip);
- if (ctx->xt_t != NULL)
+ if (ctx->xt_t != NULL) {
g_free(ctx->xt_t->t);
- if (ctx->xt_m != NULL)
+ ctx->xt_t->t = NULL;
+ }
+ if (ctx->xt_m != NULL) {
g_free(ctx->xt_m->m);
+ ctx->xt_m->m = NULL;
+ }
+ for (tmp = NULL, rm = ctx->xt_rm; rm != NULL; rm = rm->next) {
+ if (tmp != NULL)
+ g_free(tmp);
+ tmp = rm;
+ }
+ g_free(tmp);
+
g_free(ctx);
}
target_name, ctx->xt_t, ctx->xt_rm);
out:
cleanup_parse_context(ctx);
+ reset_xtables();
return err;
}
ctx->xt_rm);
out:
cleanup_parse_context(ctx);
+ reset_xtables();
return err;
}
return 0;
}
-static void flush_table(const char *name)
+void flush_table(const char *name)
{
GSList *chains = NULL, *list;
struct connman_iptables *table;
iterate_entries(table->blob_entries->entrytable,
table->info->valid_hooks,
table->info->hook_entry,
+ table->info->underflow,
table->blob_entries->size,
flush_table_cb, &chains);
g_slist_free_full(chains, g_free);
}
-static void flush_all_chains(void)
-{
- flush_table("filter");
- flush_table("mangle");
- flush_table("nat");
-}
-
int __connman_iptables_init(void)
{
DBG("");
xtables_init_all(&iptables_globals, NFPROTO_IPV4);
- flush_all_chains();
-
return 0;
}
DBG("");
g_hash_table_destroy(table_hash);
-
- xtables_free_opts(1);
}