/* Library which manipulates firewall rules. Version 0.1. */ /* Architecture of firewall rules is as follows: * * Chains go INPUT, FORWARD, OUTPUT then user chains. * Each user chain starts with an ERROR node. * Every chain ends with an unconditional jump: a RETURN for user chains, * and a POLICY for built-ins. */ /* (C)1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See COPYING for details). */ #ifndef IPT_LIB_DIR #define IPT_LIB_DIR "/usr/local/lib/iptables" #endif #ifndef __OPTIMIZE__ static STRUCT_ENTRY_TARGET * GET_TARGET(STRUCT_ENTRY *e) { return (void *)e + e->target_offset; } #endif static int sockfd = -1; static void *iptc_fn = NULL; static const char *hooknames[] = { [HOOK_PRE_ROUTING] "PREROUTING", [HOOK_LOCAL_IN] "INPUT", [HOOK_FORWARD] "FORWARD", [HOOK_LOCAL_OUT] "OUTPUT", [HOOK_POST_ROUTING] "POSTROUTING" }; struct counter_map { enum { COUNTER_MAP_NOMAP, COUNTER_MAP_NORMAL_MAP, COUNTER_MAP_ZEROED } maptype; unsigned int mappos; }; /* Convenience structures */ struct ipt_error_target { STRUCT_ENTRY_TARGET t; char error[TABLE_MAXNAMELEN]; }; struct chain_cache { char name[TABLE_MAXNAMELEN]; /* This is the first rule in chain. */ STRUCT_ENTRY *start; /* Last rule in chain */ STRUCT_ENTRY *end; }; STRUCT_TC_HANDLE { /* Have changes been made? */ int changed; /* Size in here reflects original state. */ STRUCT_GETINFO info; struct counter_map *counter_map; /* Array of hook names */ const char **hooknames; /* Cached position of chain heads (NULL = no cache). */ unsigned int cache_num_chains; unsigned int cache_num_builtins; struct chain_cache *cache_chain_heads; /* Chain iterator: current chain cache entry. */ struct chain_cache *cache_chain_iteration; /* Rule iterator: terminal rule */ STRUCT_ENTRY *cache_rule_end; /* Number in here reflects current state. */ unsigned int new_number; STRUCT_GET_ENTRIES entries; }; static void set_changed(TC_HANDLE_T h) { if (h->cache_chain_heads) { free(h->cache_chain_heads); h->cache_chain_heads = NULL; h->cache_num_chains = 0; h->cache_chain_iteration = NULL; h->cache_rule_end = NULL; } h->changed = 1; } #ifndef NDEBUG static void do_check(TC_HANDLE_T h, unsigned int line); #define CHECK(h) do { if (!getenv("IPTC_NO_CHECK")) do_check((h), __LINE__); } while(0) #else #define CHECK(h) #endif static inline int get_number(const STRUCT_ENTRY *i, const STRUCT_ENTRY *seek, unsigned int *pos) { if (i == seek) return 1; (*pos)++; return 0; } static unsigned int entry2index(const TC_HANDLE_T h, const STRUCT_ENTRY *seek) { unsigned int pos = 0; if (ENTRY_ITERATE(h->entries.entrytable, h->entries.size, get_number, seek, &pos) == 0) { fprintf(stderr, "ERROR: offset %i not an entry!\n", (char *)seek - (char *)h->entries.entrytable); abort(); } return pos; } static inline int get_entry_n(STRUCT_ENTRY *i, unsigned int number, unsigned int *pos, STRUCT_ENTRY **pe) { if (*pos == number) { *pe = i; return 1; } (*pos)++; return 0; } static STRUCT_ENTRY * index2entry(TC_HANDLE_T h, unsigned int index) { unsigned int pos = 0; STRUCT_ENTRY *ret = NULL; ENTRY_ITERATE(h->entries.entrytable, h->entries.size, get_entry_n, index, &pos, &ret); return ret; } static inline STRUCT_ENTRY * get_entry(TC_HANDLE_T h, unsigned int offset) { return (STRUCT_ENTRY *)((char *)h->entries.entrytable + offset); } static inline unsigned long entry2offset(const TC_HANDLE_T h, const STRUCT_ENTRY *e) { return (char *)e - (char *)h->entries.entrytable; } static unsigned long index2offset(TC_HANDLE_T h, unsigned int index) { return entry2offset(h, index2entry(h, index)); } static const char * get_errorlabel(TC_HANDLE_T h, unsigned int offset) { STRUCT_ENTRY *e; e = get_entry(h, offset); if (strcmp(GET_TARGET(e)->u.user.name, ERROR_TARGET) != 0) { fprintf(stderr, "ERROR: offset %u not an error node!\n", offset); abort(); } return (const char *)GET_TARGET(e)->data; } /* Allocate handle of given size */ static TC_HANDLE_T alloc_handle(const char *tablename, unsigned int size, unsigned int num_rules) { size_t len; TC_HANDLE_T h; len = sizeof(STRUCT_TC_HANDLE) + size + num_rules * sizeof(struct counter_map); if ((h = malloc(len)) == NULL) { errno = ENOMEM; return NULL; } h->changed = 0; h->cache_num_chains = 0; h->cache_chain_heads = NULL; h->counter_map = (void *)h + sizeof(STRUCT_TC_HANDLE) + size; strcpy(h->info.name, tablename); strcpy(h->entries.name, tablename); return h; } TC_HANDLE_T TC_INIT(const char *tablename) { TC_HANDLE_T h; STRUCT_GETINFO info; unsigned int i; int tmp; socklen_t s; iptc_fn = TC_INIT; sockfd = socket(TC_AF, SOCK_RAW, IPPROTO_RAW); if (sockfd < 0) return NULL; s = sizeof(info); if (strlen(tablename) >= TABLE_MAXNAMELEN) { errno = EINVAL; return NULL; } strcpy(info.name, tablename); if (getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s) < 0) return NULL; if ((h = alloc_handle(info.name, info.size, info.num_entries)) == NULL) return NULL; /* Too hard --RR */ #if 0 sprintf(pathname, "%s/%s", IPT_LIB_DIR, info.name); dynlib = dlopen(pathname, RTLD_NOW); if (!dynlib) { errno = ENOENT; return NULL; } h->hooknames = dlsym(dynlib, "hooknames"); if (!h->hooknames) { errno = ENOENT; return NULL; } #else h->hooknames = hooknames; #endif /* Initialize current state */ h->info = info; h->new_number = h->info.num_entries; for (i = 0; i < h->info.num_entries; i++) h->counter_map[i] = ((struct counter_map){COUNTER_MAP_NORMAL_MAP, i}); h->entries.size = h->info.size; tmp = sizeof(STRUCT_GET_ENTRIES) + h->info.size; if (getsockopt(sockfd, TC_IPPROTO, SO_GET_ENTRIES, &h->entries, &tmp) < 0) { free(h); return NULL; } CHECK(h); return h; } static inline int print_match(const STRUCT_ENTRY_MATCH *m) { printf("Match name: `%s'\n", m->u.user.name); return 0; } static int dump_entry(STRUCT_ENTRY *e, const TC_HANDLE_T handle); void TC_DUMP_ENTRIES(const TC_HANDLE_T handle) { CHECK(handle); printf("libiptc v%s. %u entries, %u bytes.\n", NETFILTER_VERSION, handle->new_number, handle->entries.size); printf("Table `%s'\n", handle->info.name); printf("Hooks: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n", handle->info.hook_entry[HOOK_PRE_ROUTING], handle->info.hook_entry[HOOK_LOCAL_IN], handle->info.hook_entry[HOOK_FORWARD], handle->info.hook_entry[HOOK_LOCAL_OUT], handle->info.hook_entry[HOOK_POST_ROUTING]); printf("Underflows: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n", handle->info.underflow[HOOK_PRE_ROUTING], handle->info.underflow[HOOK_LOCAL_IN], handle->info.underflow[HOOK_FORWARD], handle->info.underflow[HOOK_LOCAL_OUT], handle->info.underflow[HOOK_POST_ROUTING]); ENTRY_ITERATE(handle->entries.entrytable, handle->entries.size, dump_entry, handle); } /* Returns 0 if not hook entry, else hooknumber + 1 */ static inline unsigned int is_hook_entry(STRUCT_ENTRY *e, TC_HANDLE_T h) { unsigned int i; for (i = 0; i < NUMHOOKS; i++) { if ((h->info.valid_hooks & (1 << i)) && get_entry(h, h->info.hook_entry[i]) == e) return i+1; } return 0; } static inline int add_chain(STRUCT_ENTRY *e, TC_HANDLE_T h, STRUCT_ENTRY **prev) { unsigned int builtin; /* Last entry. End it. */ if (entry2offset(h, e) + e->next_offset == h->entries.size) { /* This is the ERROR node at end of the table */ h->cache_chain_heads[h->cache_num_chains-1].end = *prev; return 0; } /* We know this is the start of a new chain if it's an ERROR target, or a hook entry point */ if (strcmp(GET_TARGET(e)->u.user.name, ERROR_TARGET) == 0) { /* prev was last entry in previous chain */ h->cache_chain_heads[h->cache_num_chains-1].end = *prev; strcpy(h->cache_chain_heads[h->cache_num_chains].name, (const char *)GET_TARGET(e)->data); h->cache_chain_heads[h->cache_num_chains].start = (void *)e + e->next_offset; h->cache_num_chains++; } else if ((builtin = is_hook_entry(e, h)) != 0) { if (h->cache_num_chains > 0) /* prev was last entry in previous chain */ h->cache_chain_heads[h->cache_num_chains-1].end = *prev; strcpy(h->cache_chain_heads[h->cache_num_chains].name, h->hooknames[builtin-1]); h->cache_chain_heads[h->cache_num_chains].start = (void *)e; h->cache_num_chains++; } *prev = e; return 0; } static int alphasort(const void *a, const void *b) { return strcmp(((struct chain_cache *)a)->name, ((struct chain_cache *)b)->name); } static int populate_cache(TC_HANDLE_T h) { unsigned int i; STRUCT_ENTRY *prev; /* # chains < # rules / 2 + num builtins - 1 */ h->cache_chain_heads = malloc((h->new_number / 2 + 4) * sizeof(struct chain_cache)); if (!h->cache_chain_heads) { errno = ENOMEM; return 0; } h->cache_num_chains = 0; h->cache_num_builtins = 0; /* Count builtins */ for (i = 0; i < NUMHOOKS; i++) { if (h->info.valid_hooks & (1 << i)) h->cache_num_builtins++; } prev = NULL; ENTRY_ITERATE(h->entries.entrytable, h->entries.size, add_chain, h, &prev); qsort(h->cache_chain_heads + h->cache_num_builtins, h->cache_num_chains - h->cache_num_builtins, sizeof(struct chain_cache), alphasort); return 1; } /* Returns cache ptr if found, otherwise NULL. */ static struct chain_cache * find_label(const char *name, TC_HANDLE_T handle) { unsigned int i; if (handle->cache_chain_heads == NULL && !populate_cache(handle)) return NULL; /* FIXME: Linear search through builtins, then binary --RR */ for (i = 0; i < handle->cache_num_chains; i++) { if (strcmp(handle->cache_chain_heads[i].name, name) == 0) return &handle->cache_chain_heads[i]; } return NULL; } /* Does this chain exist? */ int TC_IS_CHAIN(const char *chain, const TC_HANDLE_T handle) { return find_label(chain, handle) != NULL; } /* Returns the position of the final (ie. unconditional) element. */ static unsigned int get_chain_end(const TC_HANDLE_T handle, unsigned int start) { unsigned int last_off, off; STRUCT_ENTRY *e; last_off = start; e = get_entry(handle, start); /* Terminate when we meet a error label or a hook entry. */ for (off = start + e->next_offset; off < handle->entries.size; last_off = off, off += e->next_offset) { STRUCT_ENTRY_TARGET *t; unsigned int i; e = get_entry(handle, off); /* We hit an entry point. */ for (i = 0; i < NUMHOOKS; i++) { if ((handle->info.valid_hooks & (1 << i)) && off == handle->info.hook_entry[i]) return last_off; } /* We hit a user chain label */ t = GET_TARGET(e); if (strcmp(t->u.user.name, ERROR_TARGET) == 0) return last_off; } /* SHOULD NEVER HAPPEN */ fprintf(stderr, "ERROR: Off end (%u) of chain from %u!\n", handle->entries.size, off); abort(); } /* Iterator functions to run through the chains. */ const char * TC_FIRST_CHAIN(TC_HANDLE_T *handle) { if ((*handle)->cache_chain_heads == NULL && !populate_cache(*handle)) return NULL; (*handle)->cache_chain_iteration = &(*handle)->cache_chain_heads[0]; return (*handle)->cache_chain_iteration->name; } /* Iterator functions to run through the chains. Returns NULL at end. */ const char * TC_NEXT_CHAIN(TC_HANDLE_T *handle) { (*handle)->cache_chain_iteration++; if ((*handle)->cache_chain_iteration - (*handle)->cache_chain_heads == (*handle)->cache_num_chains) return NULL; return (*handle)->cache_chain_iteration->name; } /* Get first rule in the given chain: NULL for empty chain. */ const STRUCT_ENTRY * TC_FIRST_RULE(const char *chain, TC_HANDLE_T *handle) { struct chain_cache *c; c = find_label(chain, *handle); if (!c) { errno = ENOENT; return NULL; } /* Empty chain: single return/policy rule */ if (c->start == c->end) return NULL; (*handle)->cache_rule_end = c->end; return c->start; } /* Returns NULL when rules run out. */ const STRUCT_ENTRY * TC_NEXT_RULE(const STRUCT_ENTRY *prev, TC_HANDLE_T *handle) { if ((void *)prev + prev->next_offset == (void *)(*handle)->cache_rule_end) return NULL; return (void *)prev + prev->next_offset; } #if 0 /* How many rules in this chain? */ unsigned int TC_NUM_RULES(const char *chain, TC_HANDLE_T *handle) { unsigned int off = 0; STRUCT_ENTRY *start, *end; CHECK(*handle); if (!find_label(&off, chain, *handle)) { errno = ENOENT; return (unsigned int)-1; } start = get_entry(*handle, off); end = get_entry(*handle, get_chain_end(*handle, off)); return entry2index(*handle, end) - entry2index(*handle, start); } /* Get n'th rule in this chain. */ const STRUCT_ENTRY *TC_GET_RULE(const char *chain, unsigned int n, TC_HANDLE_T *handle) { unsigned int pos = 0, chainindex; CHECK(*handle); if (!find_label(&pos, chain, *handle)) { errno = ENOENT; return NULL; } chainindex = entry2index(*handle, get_entry(*handle, pos)); return index2entry(*handle, chainindex + n); } #endif static const char * target_name(TC_HANDLE_T handle, const STRUCT_ENTRY *ce) { int spos; unsigned int labelidx; STRUCT_ENTRY *jumpto; /* To avoid const warnings */ STRUCT_ENTRY *e = (STRUCT_ENTRY *)ce; if (strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET) != 0) return GET_TARGET(e)->u.user.name; /* Standard target: evaluate */ spos = *(int *)GET_TARGET(e)->data; if (spos < 0) { if (spos == RETURN) return LABEL_RETURN; else if (spos == -NF_ACCEPT-1) return LABEL_ACCEPT; else if (spos == -NF_DROP-1) return LABEL_DROP; else if (spos == -NF_QUEUE-1) return LABEL_QUEUE; fprintf(stderr, "ERROR: off %lu/%u not a valid target (%i)\n", entry2offset(handle, e), handle->entries.size, spos); abort(); } jumpto = get_entry(handle, spos); /* Fall through rule */ if (jumpto == (void *)e + e->next_offset) return ""; /* Must point to head of a chain: ie. after error rule */ labelidx = entry2index(handle, jumpto) - 1; return get_errorlabel(handle, index2offset(handle, labelidx)); } /* Returns a pointer to the target name of this position. */ const char *TC_GET_TARGET(const STRUCT_ENTRY *e, TC_HANDLE_T *handle) { return target_name(*handle, e); } /* Is this a built-in chain? Actually returns hook + 1. */ int TC_BUILTIN(const char *chain, const TC_HANDLE_T handle) { unsigned int i; for (i = 0; i < NUMHOOKS; i++) { if ((handle->info.valid_hooks & (1 << i)) && handle->hooknames[i] && strcmp(handle->hooknames[i], chain) == 0) return i+1; } return 0; } /* Get the policy of a given built-in chain */ const char * TC_GET_POLICY(const char *chain, STRUCT_COUNTERS *counters, TC_HANDLE_T *handle) { unsigned int start; STRUCT_ENTRY *e; int hook; hook = TC_BUILTIN(chain, *handle); if (hook != 0) start = (*handle)->info.hook_entry[hook-1]; else return NULL; e = get_entry(*handle, get_chain_end(*handle, start)); *counters = e->counters; return target_name(*handle, e); } static int correct_verdict(STRUCT_ENTRY *e, char *base, unsigned int offset, int delta_offset) { STRUCT_STANDARD_TARGET *t = (void *)GET_TARGET(e); unsigned int curr = (char *)e - base; /* Trap: insert of fall-through rule. Don't change fall-through verdict to jump-over-next-rule. */ if (strcmp(t->target.u.user.name, STANDARD_TARGET) == 0 && t->verdict > (int)offset && !(curr == offset && t->verdict == curr + e->next_offset)) { t->verdict += delta_offset; } return 0; } /* Adjusts standard verdict jump positions after an insertion/deletion. */ static int set_verdict(unsigned int offset, int delta_offset, TC_HANDLE_T *handle) { ENTRY_ITERATE((*handle)->entries.entrytable, (*handle)->entries.size, correct_verdict, (char *)(*handle)->entries.entrytable, offset, delta_offset); set_changed(*handle); return 1; } /* If prepend is set, then we are prepending to a chain: if the * insertion position is an entry point, keep the entry point. */ static int insert_rules(unsigned int num_rules, unsigned int rules_size, const STRUCT_ENTRY *insert, unsigned int offset, unsigned int num_rules_offset, int prepend, TC_HANDLE_T *handle) { TC_HANDLE_T newh; STRUCT_GETINFO newinfo; unsigned int i; if (offset >= (*handle)->entries.size) { errno = EINVAL; return 0; } newinfo = (*handle)->info; /* Fix up entry points. */ for (i = 0; i < NUMHOOKS; i++) { /* Entry points to START of chain, so keep same if inserting on at that point. */ if ((*handle)->info.hook_entry[i] > offset) newinfo.hook_entry[i] += rules_size; /* Underflow always points to END of chain (policy), so if something is inserted at same point, it should be advanced. */ if ((*handle)->info.underflow[i] >= offset) newinfo.underflow[i] += rules_size; } newh = alloc_handle((*handle)->info.name, (*handle)->info.size + rules_size, (*handle)->info.num_entries + num_rules); if (!newh) return 0; newh->info = newinfo; /* Copy pre... */ memcpy(newh->entries.entrytable, (*handle)->entries.entrytable,offset); /* ... Insert new ... */ memcpy((char *)newh->entries.entrytable + offset, insert, rules_size); /* ... copy post */ memcpy((char *)newh->entries.entrytable + offset + rules_size, (char *)(*handle)->entries.entrytable + offset, (*handle)->entries.size - offset); /* Move counter map. */ /* Copy pre... */ memcpy(newh->counter_map, (*handle)->counter_map, sizeof(struct counter_map) * num_rules_offset); /* ... copy post */ memcpy(newh->counter_map + num_rules_offset + num_rules, (*handle)->counter_map + num_rules_offset, sizeof(struct counter_map) * ((*handle)->new_number - num_rules_offset)); /* Set intermediates to no counter copy */ for (i = 0; i < num_rules; i++) newh->counter_map[num_rules_offset+i] = ((struct counter_map){ COUNTER_MAP_NOMAP, 0 }); newh->new_number = (*handle)->new_number + num_rules; newh->entries.size = (*handle)->entries.size + rules_size; newh->hooknames = (*handle)->hooknames; if ((*handle)->cache_chain_heads) free((*handle)->cache_chain_heads); free(*handle); *handle = newh; return set_verdict(offset, rules_size, handle); } static int delete_rules(unsigned int num_rules, unsigned int rules_size, unsigned int offset, unsigned int num_rules_offset, TC_HANDLE_T *handle) { unsigned int i; if (offset + rules_size > (*handle)->entries.size) { errno = EINVAL; return 0; } /* Fix up entry points. */ for (i = 0; i < NUMHOOKS; i++) { /* In practice, we never delete up to a hook entry, since the built-in chains are always first, so these two are never equal */ if ((*handle)->info.hook_entry[i] >= offset + rules_size) (*handle)->info.hook_entry[i] -= rules_size; else if ((*handle)->info.hook_entry[i] > offset) { fprintf(stderr, "ERROR: Deleting entry %u %u %u\n", i, (*handle)->info.hook_entry[i], offset); abort(); } /* Underflow points to policy (terminal) rule in built-in, so sequality is valid here (when deleting the last rule). */ if ((*handle)->info.underflow[i] >= offset + rules_size) (*handle)->info.underflow[i] -= rules_size; else if ((*handle)->info.underflow[i] > offset) { fprintf(stderr, "ERROR: Deleting uflow %u %u %u\n", i, (*handle)->info.underflow[i], offset); abort(); } } /* Move the rules down. */ memmove((char *)(*handle)->entries.entrytable + offset, (char *)(*handle)->entries.entrytable + offset + rules_size, (*handle)->entries.size - (offset + rules_size)); /* Move the counter map down. */ memmove(&(*handle)->counter_map[num_rules_offset], &(*handle)->counter_map[num_rules_offset + num_rules], sizeof(struct counter_map) * ((*handle)->new_number - (num_rules + num_rules_offset))); /* Fix numbers */ (*handle)->new_number -= num_rules; (*handle)->entries.size -= rules_size; return set_verdict(offset, -(int)rules_size, handle); } static int standard_map(STRUCT_ENTRY *e, int verdict) { STRUCT_STANDARD_TARGET *t; t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); if (t->target.u.target_size != ALIGN(sizeof(STRUCT_STANDARD_TARGET))) { errno = EINVAL; return 0; } /* memset for memcmp convenience on delete/replace */ memset(t->target.u.user.name, 0, FUNCTION_MAXNAMELEN); strcpy(t->target.u.user.name, STANDARD_TARGET); t->verdict = verdict; return 1; } static int map_target(const TC_HANDLE_T handle, STRUCT_ENTRY *e, unsigned int offset, STRUCT_ENTRY_TARGET *old) { STRUCT_ENTRY_TARGET *t = GET_TARGET(e); /* Save old target (except data, which we don't change, except for standard case, where we don't care). */ *old = *t; /* Maybe it's empty (=> fall through) */ if (strcmp(t->u.user.name, "") == 0) return standard_map(e, offset + e->next_offset); /* Maybe it's a standard target name... */ else if (strcmp(t->u.user.name, LABEL_ACCEPT) == 0) return standard_map(e, -NF_ACCEPT - 1); else if (strcmp(t->u.user.name, LABEL_DROP) == 0) return standard_map(e, -NF_DROP - 1); else if (strcmp(t->u.user.name, LABEL_QUEUE) == 0) return standard_map(e, -NF_QUEUE - 1); else if (strcmp(t->u.user.name, LABEL_RETURN) == 0) return standard_map(e, RETURN); else if (TC_BUILTIN(t->u.user.name, handle)) { /* Can't jump to builtins. */ errno = EINVAL; return 0; } else { /* Maybe it's an existing chain name. */ struct chain_cache *c; c = find_label(t->u.user.name, handle); if (c) return standard_map(e, entry2offset(handle, c->start)); } /* Must be a module? If not, kernel will reject... */ /* memset to all 0 for your memcmp convenience. */ memset(t->u.user.name + strlen(t->u.user.name), 0, FUNCTION_MAXNAMELEN - strlen(t->u.user.name)); return 1; } static void unmap_target(STRUCT_ENTRY *e, STRUCT_ENTRY_TARGET *old) { STRUCT_ENTRY_TARGET *t = GET_TARGET(e); /* Save old target (except data, which we don't change, except for standard case, where we don't care). */ *t = *old; } /* Insert the entry `fw' in chain `chain' into position `rulenum'. */ int TC_INSERT_ENTRY(const IPT_CHAINLABEL chain, const STRUCT_ENTRY *e, unsigned int rulenum, TC_HANDLE_T *handle) { unsigned int chainindex, offset; STRUCT_ENTRY_TARGET old; struct chain_cache *c; STRUCT_ENTRY *tmp; int ret; iptc_fn = TC_INSERT_ENTRY; if (!(c = find_label(chain, *handle))) { errno = ENOENT; return 0; } chainindex = entry2index(*handle, c->start); tmp = index2entry(*handle, chainindex + rulenum); if (!tmp || tmp > c->end) { errno = E2BIG; return 0; } offset = index2offset(*handle, chainindex + rulenum); /* Mapping target actually alters entry, but that's transparent to the caller. */ if (!map_target(*handle, (STRUCT_ENTRY *)e, offset, &old)) return 0; ret = insert_rules(1, e->next_offset, e, offset, chainindex + rulenum, rulenum == 0, handle); unmap_target((STRUCT_ENTRY *)e, &old); return ret; } /* Atomically replace rule `rulenum' in `chain' with `fw'. */ int TC_REPLACE_ENTRY(const IPT_CHAINLABEL chain, const STRUCT_ENTRY *e, unsigned int rulenum, TC_HANDLE_T *handle) { unsigned int chainindex, offset; STRUCT_ENTRY_TARGET old; struct chain_cache *c; STRUCT_ENTRY *tmp; int ret; iptc_fn = TC_REPLACE_ENTRY; if (!(c = find_label(chain, *handle))) { errno = ENOENT; return 0; } chainindex = entry2index(*handle, c->start); tmp = index2entry(*handle, chainindex + rulenum); if (!tmp || tmp >= c->end) { errno = E2BIG; return 0; } offset = index2offset(*handle, chainindex + rulenum); /* Replace = delete and insert. */ if (!delete_rules(1, get_entry(*handle, offset)->next_offset, offset, chainindex + rulenum, handle)) return 0; if (!map_target(*handle, (STRUCT_ENTRY *)e, offset, &old)) return 0; ret = insert_rules(1, e->next_offset, e, offset, chainindex + rulenum, 1, handle); unmap_target((STRUCT_ENTRY *)e, &old); return ret; } /* Append entry `fw' to chain `chain'. Equivalent to insert with rulenum = length of chain. */ int TC_APPEND_ENTRY(const IPT_CHAINLABEL chain, const STRUCT_ENTRY *e, TC_HANDLE_T *handle) { struct chain_cache *c; STRUCT_ENTRY_TARGET old; int ret; iptc_fn = TC_APPEND_ENTRY; if (!(c = find_label(chain, *handle))) { errno = ENOENT; return 0; } if (!map_target(*handle, (STRUCT_ENTRY *)e, entry2offset(*handle, c->end), &old)) return 0; ret = insert_rules(1, e->next_offset, e, entry2offset(*handle, c->end), entry2index(*handle, c->end), 0, handle); unmap_target((STRUCT_ENTRY *)e, &old); return ret; } static inline int match_different(const STRUCT_ENTRY_MATCH *a, const unsigned char *a_elems, const unsigned char *b_elems, unsigned char **maskptr) { const STRUCT_ENTRY_MATCH *b; unsigned int i; /* Offset of b is the same as a. */ b = (void *)b_elems + ((unsigned char *)a - a_elems); if (a->u.match_size != b->u.match_size) return 1; if (strcmp(a->u.user.name, b->u.user.name) != 0) return 1; *maskptr += ALIGN(sizeof(*a)); for (i = 0; i < a->u.match_size - ALIGN(sizeof(*a)); i++) if (((a->data[i] ^ b->data[i]) & (*maskptr)[i]) != 0) return 1; *maskptr += i; return 0; } static inline int target_different(const unsigned char *a_targdata, const unsigned char *b_targdata, unsigned int tdatasize, const unsigned char *mask) { unsigned int i; for (i = 0; i < tdatasize; i++) if (((a_targdata[i] ^ b_targdata[i]) & mask[i]) != 0) return 1; return 0; } static int is_same(const STRUCT_ENTRY *a, const STRUCT_ENTRY *b, unsigned char *matchmask); /* Delete the first rule in `chain' which matches `fw'. */ int TC_DELETE_ENTRY(const IPT_CHAINLABEL chain, const STRUCT_ENTRY *origfw, unsigned char *matchmask, TC_HANDLE_T *handle) { unsigned int offset; struct chain_cache *c; STRUCT_ENTRY *e, *fw; iptc_fn = TC_DELETE_ENTRY; if (!(c = find_label(chain, *handle))) { errno = ENOENT; return 0; } fw = malloc(origfw->next_offset); if (fw == NULL) { errno = ENOMEM; return 0; } for (offset = entry2offset(*handle, c->start); offset < entry2offset(*handle, c->end); offset += e->next_offset) { STRUCT_ENTRY_TARGET discard; memcpy(fw, origfw, origfw->next_offset); /* FIXME: handle this in is_same --RR */ if (!map_target(*handle, fw, offset, &discard)) { free(fw); return 0; } e = get_entry(*handle, offset); #if 0 printf("Deleting:\n"); dump_entry(newe); #endif if (is_same(e, fw, matchmask)) { int ret; ret = delete_rules(1, e->next_offset, offset, entry2index(*handle, e), handle); free(fw); return ret; } } free(fw); errno = ENOENT; return 0; } /* Delete the rule in position `rulenum' in `chain'. */ int TC_DELETE_NUM_ENTRY(const IPT_CHAINLABEL chain, unsigned int rulenum, TC_HANDLE_T *handle) { unsigned int index; int ret; STRUCT_ENTRY *e; struct chain_cache *c; iptc_fn = TC_DELETE_NUM_ENTRY; if (!(c = find_label(chain, *handle))) { errno = ENOENT; return 0; } index = entry2index(*handle, c->start) + rulenum; if (index >= entry2index(*handle, c->end)) { errno = E2BIG; return 0; } e = index2entry(*handle, index); if (e == NULL) { errno = EINVAL; return 0; } ret = delete_rules(1, e->next_offset, entry2offset(*handle, e), index, handle); return ret; } /* Check the packet `fw' on chain `chain'. Returns the verdict, or NULL and sets errno. */ const char * TC_CHECK_PACKET(const IPT_CHAINLABEL chain, STRUCT_ENTRY *entry, TC_HANDLE_T *handle) { errno = ENOSYS; return NULL; } /* Flushes the entries in the given chain (ie. empties chain). */ int TC_FLUSH_ENTRIES(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) { unsigned int startindex, endindex; struct chain_cache *c; int ret; iptc_fn = TC_FLUSH_ENTRIES; if (!(c = find_label(chain, *handle))) { errno = ENOENT; return 0; } startindex = entry2index(*handle, c->start); endindex = entry2index(*handle, c->end); ret = delete_rules(endindex - startindex, (char *)c->end - (char *)c->start, entry2offset(*handle, c->start), startindex, handle); return ret; } /* Zeroes the counters in a chain. */ int TC_ZERO_ENTRIES(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) { unsigned int i, end; struct chain_cache *c; if (!(c = find_label(chain, *handle))) { errno = ENOENT; return 0; } i = entry2index(*handle, c->start); end = entry2index(*handle, c->end); for (; i <= end; i++) { if ((*handle)->counter_map[i].maptype ==COUNTER_MAP_NORMAL_MAP) (*handle)->counter_map[i].maptype = COUNTER_MAP_ZEROED; } set_changed(*handle); return 1; } /* Creates a new chain. */ /* To create a chain, create two rules: error node and unconditional * return. */ int TC_CREATE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) { int ret; struct { STRUCT_ENTRY head; struct ipt_error_target name; STRUCT_ENTRY ret; STRUCT_STANDARD_TARGET target; } newc; iptc_fn = TC_CREATE_CHAIN; /* find_label doesn't cover built-in targets: DROP, ACCEPT, QUEUE, RETURN. */ if (find_label(chain, *handle) || strcmp(chain, LABEL_DROP) == 0 || strcmp(chain, LABEL_ACCEPT) == 0 || strcmp(chain, LABEL_QUEUE) == 0 || strcmp(chain, LABEL_RETURN) == 0) { errno = EEXIST; return 0; } if (strlen(chain)+1 > sizeof(IPT_CHAINLABEL)) { errno = EINVAL; return 0; } memset(&newc, 0, sizeof(newc)); newc.head.target_offset = sizeof(STRUCT_ENTRY); newc.head.next_offset = sizeof(STRUCT_ENTRY) + ALIGN(sizeof(struct ipt_error_target)); strcpy(newc.name.t.u.user.name, ERROR_TARGET); newc.name.t.u.target_size = ALIGN(sizeof(struct ipt_error_target)); strcpy(newc.name.error, chain); newc.ret.target_offset = sizeof(STRUCT_ENTRY); newc.ret.next_offset = sizeof(STRUCT_ENTRY) + ALIGN(sizeof(STRUCT_STANDARD_TARGET)); strcpy(newc.target.target.u.user.name, STANDARD_TARGET); newc.target.target.u.target_size = ALIGN(sizeof(STRUCT_STANDARD_TARGET)); newc.target.verdict = RETURN; /* Add just before terminal entry */ ret = insert_rules(2, sizeof(newc), &newc.head, index2offset(*handle, (*handle)->new_number - 1), (*handle)->new_number - 1, 0, handle); return ret; } static int count_ref(STRUCT_ENTRY *e, unsigned int offset, unsigned int *ref) { STRUCT_STANDARD_TARGET *t; if (strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET) == 0) { t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); if (t->verdict == offset) (*ref)++; } return 0; } /* Get the number of references to this chain. */ int TC_GET_REFERENCES(unsigned int *ref, const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) { struct chain_cache *c; if (!(c = find_label(chain, *handle))) { errno = ENOENT; return 0; } *ref = 0; ENTRY_ITERATE((*handle)->entries.entrytable, (*handle)->entries.size, count_ref, entry2offset(*handle, c->start), ref); return 1; } /* Deletes a chain. */ int TC_DELETE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle) { unsigned int labelidx, labeloff; unsigned int references; struct chain_cache *c; int ret; if (!TC_GET_REFERENCES(&references, chain, handle)) return 0; iptc_fn = TC_DELETE_CHAIN; if (TC_BUILTIN(chain, *handle)) { errno = EINVAL; return 0; } if (references > 0) { errno = EMLINK; return 0; } if (!(c = find_label(chain, *handle))) { errno = ENOENT; return 0; } if ((void *)c->start != c->end) { errno = ENOTEMPTY; return 0; } /* Need label index: preceeds chain start */ labelidx = entry2index(*handle, c->start) - 1; labeloff = index2offset(*handle, labelidx); ret = delete_rules(2, get_entry(*handle, labeloff)->next_offset + c->start->next_offset, labeloff, labelidx, handle); return ret; } /* Renames a chain. */ int TC_RENAME_CHAIN(const IPT_CHAINLABEL oldname, const IPT_CHAINLABEL newname, TC_HANDLE_T *handle) { unsigned int labeloff, labelidx; struct chain_cache *c; struct ipt_error_target *t; iptc_fn = TC_RENAME_CHAIN; /* find_label doesn't cover built-in targets: DROP, ACCEPT RETURN. */ if (find_label(newname, *handle) || strcmp(newname, LABEL_DROP) == 0 || strcmp(newname, LABEL_ACCEPT) == 0 || strcmp(newname, LABEL_RETURN) == 0) { errno = EEXIST; return 0; } if (!(c = find_label(oldname, *handle)) || TC_BUILTIN(oldname, *handle)) { errno = ENOENT; return 0; } if (strlen(newname)+1 > sizeof(IPT_CHAINLABEL)) { errno = EINVAL; return 0; } /* Need label index: preceeds chain start */ labelidx = entry2index(*handle, c->start) - 1; labeloff = index2offset(*handle, labelidx); t = (struct ipt_error_target *) GET_TARGET(get_entry(*handle, labeloff)); memset(t->error, 0, sizeof(t->error)); strcpy(t->error, newname); set_changed(*handle); return 1; } /* Sets the policy on a built-in chain. */ int TC_SET_POLICY(const IPT_CHAINLABEL chain, const IPT_CHAINLABEL policy, TC_HANDLE_T *handle) { unsigned int hook; unsigned int policyoff; STRUCT_ENTRY *e; STRUCT_STANDARD_TARGET *t; iptc_fn = TC_SET_POLICY; /* Figure out which chain. */ hook = TC_BUILTIN(chain, *handle); if (hook == 0) { errno = ENOENT; return 0; } else hook--; policyoff = get_chain_end(*handle, (*handle)->info.hook_entry[hook]); if (policyoff != (*handle)->info.underflow[hook]) { printf("ERROR: Policy for `%s' offset %u != underflow %u\n", chain, policyoff, (*handle)->info.underflow[hook]); return 0; } e = get_entry(*handle, policyoff); t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e); if (strcmp(policy, LABEL_ACCEPT) == 0) t->verdict = -NF_ACCEPT - 1; else if (strcmp(policy, LABEL_DROP) == 0) t->verdict = -NF_DROP - 1; else { errno = EINVAL; return 0; } (*handle)->counter_map[entry2index(*handle, e)] = ((struct counter_map){ COUNTER_MAP_NOMAP, 0 }); set_changed(*handle); return 1; } /* Without this, on gcc 2.7.2.3, we get: libiptc.c: In function `TC_COMMIT': libiptc.c:833: fixed or forbidden register was spilled. This may be due to a compiler bug or to impossible asm statements or clauses. */ static void subtract_counters(STRUCT_COUNTERS *answer, const STRUCT_COUNTERS *a, const STRUCT_COUNTERS *b) { answer->pcnt = a->pcnt - b->pcnt; answer->bcnt = a->bcnt - b->bcnt; } int TC_COMMIT(TC_HANDLE_T *handle) { /* Replace, then map back the counters. */ STRUCT_REPLACE *repl; STRUCT_COUNTERS_INFO *newcounters; unsigned int i; size_t counterlen = sizeof(STRUCT_COUNTERS_INFO) + sizeof(STRUCT_COUNTERS) * (*handle)->new_number; CHECK(*handle); #if 0 TC_TC_DUMP_ENTRIES(*handle); #endif /* Don't commit if nothing changed. */ if (!(*handle)->changed) goto finished; repl = malloc(sizeof(*repl) + (*handle)->entries.size); if (!repl) { errno = ENOMEM; return 0; } /* These are the old counters we will get from kernel */ repl->counters = malloc(sizeof(STRUCT_COUNTERS) * (*handle)->info.num_entries); if (!repl->counters) { free(repl); errno = ENOMEM; return 0; } /* These are the counters we're going to put back, later. */ newcounters = malloc(counterlen); if (!newcounters) { free(repl->counters); free(repl); errno = ENOMEM; return 0; } strcpy(repl->name, (*handle)->info.name); repl->num_entries = (*handle)->new_number; repl->size = (*handle)->entries.size; memcpy(repl->hook_entry, (*handle)->info.hook_entry, sizeof(repl->hook_entry)); memcpy(repl->underflow, (*handle)->info.underflow, sizeof(repl->underflow)); repl->num_counters = (*handle)->info.num_entries; repl->valid_hooks = (*handle)->info.valid_hooks; memcpy(repl->entries, (*handle)->entries.entrytable, (*handle)->entries.size); if (setsockopt(sockfd, TC_IPPROTO, SO_SET_REPLACE, repl, sizeof(*repl) + (*handle)->entries.size) < 0) { free(repl->counters); free(repl); free(newcounters); return 0; } /* Put counters back. */ strcpy(newcounters->name, (*handle)->info.name); newcounters->num_counters = (*handle)->new_number; for (i = 0; i < (*handle)->new_number; i++) { unsigned int mappos = (*handle)->counter_map[i].mappos; switch ((*handle)->counter_map[i].maptype) { case COUNTER_MAP_NOMAP: newcounters->counters[i] = ((STRUCT_COUNTERS){ 0, 0 }); break; case COUNTER_MAP_NORMAL_MAP: /* Original read: X. * Atomic read on replacement: X + Y. * Currently in kernel: Z. * Want in kernel: X + Y + Z. * => Add in X + Y * => Add in replacement read. */ newcounters->counters[i] = repl->counters[mappos]; break; case COUNTER_MAP_ZEROED: /* Original read: X. * Atomic read on replacement: X + Y. * Currently in kernel: Z. * Want in kernel: Y + Z. * => Add in Y. * => Add in (replacement read - original read). */ subtract_counters(&newcounters->counters[i], &repl->counters[mappos], &index2entry(*handle, i)->counters); break; } } if (setsockopt(sockfd, TC_IPPROTO, SO_SET_ADD_COUNTERS, newcounters, counterlen) < 0) { free(repl->counters); free(repl); free(newcounters); return 0; } free(repl->counters); free(repl); free(newcounters); finished: if ((*handle)->cache_chain_heads) free((*handle)->cache_chain_heads); free(*handle); *handle = NULL; return 1; } /* Get raw socket. */ int TC_GET_RAW_SOCKET() { return sockfd; } /* Translates errno numbers into more human-readable form than strerror. */ const char * TC_STRERROR(int err) { unsigned int i; struct table_struct { void *fn; int err; const char *message; } table [] = { { NULL, 0, "Incompatible with this kernel" }, { NULL, ENOPROTOOPT, "iptables who? (do you need to insmod?)" }, { NULL, ENOSYS, "Will be implemented real soon. I promise." }, { NULL, ENOMEM, "Memory allocation problem" }, { TC_INIT, EPERM, "Permission denied (you must be root)" }, { TC_INIT, EINVAL, "Module is wrong version" }, { TC_INIT, ENOENT, "Table does not exist (do you need to insmod?)" }, { TC_DELETE_CHAIN, ENOTEMPTY, "Chain is not empty" }, { TC_DELETE_CHAIN, EINVAL, "Can't delete built-in chain" }, { TC_DELETE_CHAIN, EMLINK, "Can't delete chain with references left" }, { TC_CREATE_CHAIN, EEXIST, "Chain already exists" }, { TC_INSERT_ENTRY, E2BIG, "Index of insertion too big" }, { TC_REPLACE_ENTRY, E2BIG, "Index of replacement too big" }, { TC_DELETE_NUM_ENTRY, E2BIG, "Index of deletion too big" }, { TC_INSERT_ENTRY, ELOOP, "Loop found in table" }, { TC_INSERT_ENTRY, EINVAL, "Target problem" }, /* EINVAL for CHECK probably means bad interface. */ { TC_CHECK_PACKET, EINVAL, "Bad arguments (does that interface exist?)" }, /* ENOENT for DELETE probably means no matching rule */ { TC_DELETE_ENTRY, ENOENT, "Bad rule (does a matching rule exist in that chain?)" }, { TC_SET_POLICY, ENOENT, "Bad built-in chain name" }, { TC_SET_POLICY, EINVAL, "Bad policy name" }, { NULL, ENOENT, "No chain/target/match by that name" } }; for (i = 0; i < sizeof(table)/sizeof(struct table_struct); i++) { if ((!table[i].fn || table[i].fn == iptc_fn) && table[i].err == err) return table[i].message; } return strerror(err); }