From 5c306289b8a78a060a12eba5cd86e0efc3533da0 Mon Sep 17 00:00:00 2001 From: marc Date: Mon, 20 Mar 2000 06:03:29 +0000 Subject: reorganized tree after kernel merge --- libiptc/Makefile | 16 + libiptc/libiptc.c | 1828 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1844 insertions(+) create mode 100644 libiptc/Makefile create mode 100644 libiptc/libiptc.c (limited to 'libiptc') diff --git a/libiptc/Makefile b/libiptc/Makefile new file mode 100644 index 0000000..e1bc7be --- /dev/null +++ b/libiptc/Makefile @@ -0,0 +1,16 @@ +#! /usr/bin/make + +EXTRAS+=libiptc/libiptc.a + +ifndef TOPLEVEL_INCLUDED +local: + cd .. && $(MAKE) $(KERN_TARGETS) $(SHARED_LIBS) $(EXTRAS) + +else +EXTRA_DEPENDS+=libiptc/libiptc.d + +libiptc/libiptc.a: libiptc/libiptc.a(libiptc/libiptc.o) + +libiptc/libiptc.d: %.d: %.c + @-$(CC) -M -MG $(CFLAGS) $< | sed -e 's@^.*\.o:@$*.d $*.a:@' > $@ +endif diff --git a/libiptc/libiptc.c b/libiptc/libiptc.c new file mode 100644 index 0000000..9109703 --- /dev/null +++ b/libiptc/libiptc.c @@ -0,0 +1,1828 @@ +/* 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). */ + +#include +#include +#include +#include +#include + +#if !defined(__GLIBC__) || (__GLIBC__ < 2) +typedef unsigned int socklen_t; +#endif + +#include + +#define IP_VERSION 4 +#define IP_OFFSET 0x1FFF + +#ifndef IPT_LIB_DIR +#define IPT_LIB_DIR "/usr/local/lib/iptables" +#endif + +static int sockfd = -1; +static void *iptc_fn = NULL; + +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" +}; + +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 ipt_entry_target t; + char error[IPT_TABLE_MAXNAMELEN]; +}; + +struct iptc_handle +{ + /* Have changes been made? */ + int changed; + /* Size in here reflects original state. */ + struct ipt_getinfo info; + + struct counter_map *counter_map; + /* Array of hook names */ + const char **hooknames; + + /* Number in here reflects current state. */ + unsigned int new_number; + struct ipt_get_entries entries; +}; + +static void do_check(iptc_handle_t h, unsigned int line); +#define CHECK(h) do_check((h), __LINE__) + +static inline int +get_number(const struct ipt_entry *i, + const struct ipt_entry *seek, + unsigned int *pos) +{ + if (i == seek) + return 1; + (*pos)++; + return 0; +} + +static unsigned int +entry2index(const iptc_handle_t h, const struct ipt_entry *seek) +{ + unsigned int pos = 0; + + if (IPT_ENTRY_ITERATE(h->entries.entries, h->entries.size, + get_number, seek, &pos) == 0) { + fprintf(stderr, "ERROR: offset %i not an entry!\n", + (unsigned char *)seek - h->entries.entries); + abort(); + } + return pos; +} + +static inline int +get_entry_n(struct ipt_entry *i, + unsigned int number, + unsigned int *pos, + struct ipt_entry **pe) +{ + if (*pos == number) { + *pe = i; + return 1; + } + (*pos)++; + return 0; +} + +static struct ipt_entry * +index2entry(iptc_handle_t h, unsigned int index) +{ + unsigned int pos = 0; + struct ipt_entry *ret = NULL; + + IPT_ENTRY_ITERATE(h->entries.entries, h->entries.size, + get_entry_n, index, &pos, &ret); + + return ret; +} + +static inline struct ipt_entry * +get_entry(iptc_handle_t h, unsigned int offset) +{ + return (struct ipt_entry *)(h->entries.entries + offset); +} + +static inline unsigned long +entry2offset(const iptc_handle_t h, const struct ipt_entry *e) +{ + return (unsigned char *)e - h->entries.entries; +} + +static unsigned long +index2offset(iptc_handle_t h, unsigned int index) +{ + return entry2offset(h, index2entry(h, index)); +} + +static const char * +get_errorlabel(iptc_handle_t h, unsigned int offset) +{ + struct ipt_entry *e; + + e = get_entry(h, offset); + if (strcmp(ipt_get_target(e)->u.name, IPT_ERROR_TARGET) != 0) { + fprintf(stderr, "ERROR: offset %u not an error node!\n", + offset); + abort(); + } + + return (const char *)ipt_get_target(e)->data; +} + +/* Allocate handle of given size */ +static iptc_handle_t +alloc_handle(const char *tablename, unsigned int size, unsigned int num_rules) +{ + size_t len; + iptc_handle_t h; + + len = sizeof(struct iptc_handle) + + size + + num_rules * sizeof(struct counter_map); + + if ((h = malloc(len)) == NULL) { + errno = ENOMEM; + return NULL; + } + + h->changed = 0; + h->counter_map = (void *)h + + sizeof(struct iptc_handle) + + size; + strcpy(h->info.name, tablename); + strcpy(h->entries.name, tablename); + + return h; +} + +iptc_handle_t +iptc_init(const char *tablename) +{ + iptc_handle_t h; + struct ipt_getinfo info; + unsigned int i; + int tmp; + socklen_t s; + + iptc_fn = iptc_init; + + sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (sockfd < 0) + return NULL; + + s = sizeof(info); + if (strlen(tablename) >= IPT_TABLE_MAXNAMELEN) { + errno = EINVAL; + return NULL; + } + strcpy(info.name, tablename); + if (getsockopt(sockfd, IPPROTO_IP, IPT_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 ipt_get_entries) + h->info.size; + + if (getsockopt(sockfd, IPPROTO_IP, IPT_SO_GET_ENTRIES, &h->entries, + &tmp) < 0) { + free(h); + return NULL; + } + + CHECK(h); + return h; +} + +#define IP_PARTS_NATIVE(n) \ +(unsigned int)((n)>>24)&0xFF, \ +(unsigned int)((n)>>16)&0xFF, \ +(unsigned int)((n)>>8)&0xFF, \ +(unsigned int)((n)&0xFF) + +#define IP_PARTS(n) IP_PARTS_NATIVE(ntohl(n)) + +static inline int +print_match(const struct ipt_entry_match *m) +{ + printf("Match name: `%s'\n", m->u.name); + return 0; +} + +int +dump_entry(struct ipt_entry *e, const iptc_handle_t handle) +{ + size_t i; + struct ipt_entry_target *t; + + printf("Entry %u (%lu):\n", entry2index(handle, e), + entry2offset(handle, e)); + printf("SRC IP: %u.%u.%u.%u/%u.%u.%u.%u\n", + IP_PARTS(e->ip.src.s_addr),IP_PARTS(e->ip.smsk.s_addr)); + printf("DST IP: %u.%u.%u.%u/%u.%u.%u.%u\n", + IP_PARTS(e->ip.dst.s_addr),IP_PARTS(e->ip.dmsk.s_addr)); + printf("Interface: `%s'/", e->ip.iniface); + for (i = 0; i < IFNAMSIZ; i++) + printf("%c", e->ip.iniface_mask[i] ? 'X' : '.'); + printf("to `%s'/", e->ip.outiface); + for (i = 0; i < IFNAMSIZ; i++) + printf("%c", e->ip.outiface_mask[i] ? 'X' : '.'); + printf("\nProtocol: %u\n", e->ip.proto); + printf("Flags: %02X\n", e->ip.flags); + printf("Invflags: %02X\n", e->ip.invflags); + printf("Counters: %llu packets, %llu bytes\n", + e->counters.pcnt, e->counters.bcnt); + printf("Cache: %08X ", e->nfcache); + if (e->nfcache & NFC_ALTERED) printf("ALTERED "); + if (e->nfcache & NFC_UNKNOWN) printf("UNKNOWN "); + if (e->nfcache & NFC_IP_SRC) printf("IP_SRC "); + if (e->nfcache & NFC_IP_DST) printf("IP_DST "); + if (e->nfcache & NFC_IP_IF_IN) printf("IP_IF_IN "); + if (e->nfcache & NFC_IP_IF_OUT) printf("IP_IF_OUT "); + if (e->nfcache & NFC_IP_TOS) printf("IP_TOS "); + if (e->nfcache & NFC_IP_PROTO) printf("IP_PROTO "); + if (e->nfcache & NFC_IP_OPTIONS) printf("IP_OPTIONS "); + if (e->nfcache & NFC_IP_TCPFLAGS) printf("IP_TCPFLAGS "); + if (e->nfcache & NFC_IP_SRC_PT) printf("IP_SRC_PT "); + if (e->nfcache & NFC_IP_DST_PT) printf("IP_DST_PT "); + if (e->nfcache & NFC_IP_PROTO_UNKNOWN) printf("IP_PROTO_UNKNOWN "); + printf("\n"); + + IPT_MATCH_ITERATE(e, print_match); + + t = ipt_get_target(e); + printf("Target name: `%s' [%u]\n", t->u.name, t->target_size); + if (strcmp(t->u.name, IPT_STANDARD_TARGET) == 0) { + int pos = *(int *)t->data; + if (pos < 0) + printf("verdict=%s\n", + pos == -NF_ACCEPT-1 ? "NF_ACCEPT" + : pos == -NF_DROP-1 ? "NF_DROP" + : pos == -NF_QUEUE-1 ? "NF_QUEUE" + : pos == IPT_RETURN ? "RETURN" + : "UNKNOWN"); + else + printf("verdict=%u\n", pos); + } else if (strcmp(t->u.name, IPT_ERROR_TARGET) == 0) + printf("error=`%s'\n", t->data); + + printf("\n"); + return 0; +} + +void +dump_entries(const iptc_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[NF_IP_PRE_ROUTING], + handle->info.hook_entry[NF_IP_LOCAL_IN], + handle->info.hook_entry[NF_IP_FORWARD], + handle->info.hook_entry[NF_IP_LOCAL_OUT], + handle->info.hook_entry[NF_IP_POST_ROUTING]); + printf("Underflows: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n", + handle->info.underflow[NF_IP_PRE_ROUTING], + handle->info.underflow[NF_IP_LOCAL_IN], + handle->info.underflow[NF_IP_FORWARD], + handle->info.underflow[NF_IP_LOCAL_OUT], + handle->info.underflow[NF_IP_POST_ROUTING]); + + IPT_ENTRY_ITERATE(handle->entries.entries, handle->entries.size, + dump_entry, handle); +} + +static inline int +find_user_label(struct ipt_entry *e, unsigned int *off, const char *name) +{ + /* Increment first: they want offset of entry AFTER label */ + (*off) += e->next_offset; + + if (strcmp(ipt_get_target(e)->u.name, IPT_ERROR_TARGET) == 0 + && strcmp(ipt_get_target(e)->data, name) == 0) + return 1; + + return 0; +} + +/* Returns offset of label. */ +static int +find_label(unsigned int *off, + const char *name, + const iptc_handle_t handle) +{ + unsigned int i; + + /* Builtin chain name? */ + i = iptc_builtin(name, handle); + if (i != 0) { + *off = handle->info.hook_entry[i-1]; + return 1; + } + + /* User chain name? */ + *off = 0; + if (IPT_ENTRY_ITERATE(handle->entries.entries, handle->entries.size, + find_user_label, off, name) != 0) { + /* last error node doesn't count */ + if (*off != handle->entries.size) + return 1; + } + + return 0; +} + +/* Does this chain exist? */ +int iptc_is_chain(const char *chain, const iptc_handle_t handle) +{ + unsigned int dummy; + + /* avoid infinite recursion */ +#if 0 + CHECK(handle); +#endif + + return find_label(&dummy, chain, handle); +} + +/* Returns the position of the final (ie. unconditional) element. */ +static unsigned int +get_chain_end(const iptc_handle_t handle, unsigned int start) +{ + unsigned int last_off, off; + struct ipt_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 ipt_entry_target *t; + unsigned int i; + + e = get_entry(handle, off); + + /* We hit an entry point. */ + for (i = 0; i < NF_IP_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 = ipt_get_target(e); + if (strcmp(t->u.name, IPT_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; prev = NULL means + first chain. Returns NULL at end. */ +const char * +iptc_next_chain(const char *prev, iptc_handle_t *handle) +{ + unsigned int pos; + unsigned int i; + struct ipt_entry *e; + + CHECK(*handle); + if (!prev) + pos = 0; + else { + if (!find_label(&pos, prev, *handle)) { + errno = ENOENT; + return NULL; + } + pos = get_chain_end(*handle, pos); + /* Next entry. */ + e = get_entry(*handle, pos); + pos += e->next_offset; + } + e = get_entry(*handle, pos); + + /* Return names of entry points if it is one. */ + for (i = 0; i < NF_IP_NUMHOOKS; i++) { + if (((*handle)->info.valid_hooks & (1 << i)) + && pos == (*handle)->info.hook_entry[i]) + return (*handle)->hooknames[i]; + } + /* If this is the last element, iteration finished */ + if (pos + e->next_offset == (*handle)->entries.size) + return NULL; + + if (strcmp(ipt_get_target(e)->u.name, IPT_ERROR_TARGET) != 0) { + /* SHOULD NEVER HAPPEN */ + fprintf(stderr, "ERROR: position %u/%u not an error label\n", + pos, (*handle)->entries.size); + abort(); + } + + return (const char *)ipt_get_target(e)->data; +} + +/* How many rules in this chain? */ +unsigned int +iptc_num_rules(const char *chain, iptc_handle_t *handle) +{ + unsigned int off = 0; + struct ipt_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 ipt_entry *iptc_get_rule(const char *chain, + unsigned int n, + iptc_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); +} + +static const char *target_name(iptc_handle_t handle, struct ipt_entry *e) +{ + int spos; + unsigned int labelidx; + struct ipt_entry *jumpto; + + if (strcmp(ipt_get_target(e)->u.name, IPT_STANDARD_TARGET) != 0) + return ipt_get_target(e)->u.name; + + /* Standard target: evaluate */ + spos = *(int *)ipt_get_target(e)->data; + if (spos < 0) { + if (spos == IPT_RETURN) + return IPTC_LABEL_RETURN; + else if (spos == -NF_ACCEPT-1) + return IPTC_LABEL_ACCEPT; + else if (spos == -NF_DROP-1) + return IPTC_LABEL_DROP; + else if (spos == -NF_ACCEPT-1) + return IPTC_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 *iptc_get_target(const char *chain, + unsigned int n, + iptc_handle_t *handle) +{ + unsigned int pos = 0, chainindex; + struct ipt_entry *e; + + CHECK(*handle); + if (!find_label(&pos, chain, *handle)) { + errno = ENOENT; + return NULL; + } + + chainindex = entry2index(*handle, get_entry(*handle, pos)); + e = index2entry(*handle, chainindex + n); + + return target_name(*handle, e); +} + +/* Is this a built-in chain? Actually returns hook + 1. */ +int +iptc_builtin(const char *chain, const iptc_handle_t handle) +{ + unsigned int i; + + for (i = 0; i < NF_IP_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 * +iptc_get_policy(const char *chain, + struct ipt_counters *counters, + iptc_handle_t *handle) +{ + unsigned int start; + struct ipt_entry *e; + int hook; + + CHECK(*handle); + hook = iptc_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 ipt_entry *e, + unsigned char *base, + unsigned int offset, int delta_offset) +{ + struct ipt_standard_target *t = (void *)ipt_get_target(e); + unsigned int curr = (unsigned 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.name, IPT_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, iptc_handle_t *handle) +{ + IPT_ENTRY_ITERATE((*handle)->entries.entries, + (*handle)->entries.size, + correct_verdict, (*handle)->entries.entries, + offset, delta_offset); + + (*handle)->changed = 1; + 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 ipt_entry *insert, + unsigned int offset, unsigned int num_rules_offset, + int prepend, + iptc_handle_t *handle) +{ + iptc_handle_t newh; + struct ipt_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 < NF_IP_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.entries, (*handle)->entries.entries, offset); + /* ... Insert new ... */ + memcpy(newh->entries.entries + offset, insert, rules_size); + /* ... copy post */ + memcpy(newh->entries.entries + offset + rules_size, + (*handle)->entries.entries + 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; + + 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, + iptc_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 < NF_IP_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((*handle)->entries.entries + offset, + (*handle)->entries.entries + 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 ipt_entry *e, int verdict) +{ + struct ipt_standard_target *t; + + t = (struct ipt_standard_target *)ipt_get_target(e); + + if (t->target.target_size != IPT_ALIGN(sizeof(struct ipt_standard_target))) { + errno = EINVAL; + return 0; + } + /* memset for memcmp convenience on delete/replace */ + memset(t->target.u.name, 0, IPT_FUNCTION_MAXNAMELEN); + strcpy(t->target.u.name, IPT_STANDARD_TARGET); + t->verdict = verdict; + + return 1; +} + +static int +map_target(const iptc_handle_t handle, + struct ipt_entry *e, + unsigned int offset, + struct ipt_entry_target *old) +{ + struct ipt_entry_target *t = ipt_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.name, "") == 0) + return standard_map(e, offset + e->next_offset); + /* Maybe it's a standard target name... */ + else if (strcmp(t->u.name, IPTC_LABEL_ACCEPT) == 0) + return standard_map(e, -NF_ACCEPT - 1); + else if (strcmp(t->u.name, IPTC_LABEL_DROP) == 0) + return standard_map(e, -NF_DROP - 1); + else if (strcmp(t->u.name, IPTC_LABEL_QUEUE) == 0) + return standard_map(e, -NF_QUEUE - 1); + else if (strcmp(t->u.name, IPTC_LABEL_RETURN) == 0) + return standard_map(e, IPT_RETURN); + else if (iptc_builtin(t->u.name, handle)) { + /* Can't jump to builtins. */ + errno = EINVAL; + return 0; + } else { + /* Maybe it's an existing chain name. */ + unsigned int exists; + + if (find_label(&exists, t->u.name, handle)) + return standard_map(e, exists); + } + + /* Must be a module? If not, kernel will reject... */ + /* memset to all 0 for your memcmp convenience. */ + memset(t->u.name + strlen(t->u.name), + 0, + IPT_FUNCTION_MAXNAMELEN - strlen(t->u.name)); + return 1; +} + +static void +unmap_target(struct ipt_entry *e, struct ipt_entry_target *old) +{ + struct ipt_entry_target *t = ipt_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 +iptc_insert_entry(const ipt_chainlabel chain, + const struct ipt_entry *e, + unsigned int rulenum, + iptc_handle_t *handle) +{ + unsigned int chainoff, chainindex, offset; + struct ipt_entry_target old; + int ret; + + CHECK(*handle); + iptc_fn = iptc_insert_entry; + if (!find_label(&chainoff, chain, *handle)) { + errno = ENOENT; + return 0; + } + + chainindex = entry2index(*handle, get_entry(*handle, chainoff)); + + if (index2entry(*handle, chainindex + rulenum) + > get_entry(*handle, get_chain_end(*handle, chainoff))) { + 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 ipt_entry *)e, offset, &old)) + return 0; + + ret = insert_rules(1, e->next_offset, e, offset, + chainindex + rulenum, rulenum == 0, handle); + unmap_target((struct ipt_entry *)e, &old); + CHECK(*handle); + return ret; +} + +/* Atomically replace rule `rulenum' in `chain' with `fw'. */ +int +iptc_replace_entry(const ipt_chainlabel chain, + const struct ipt_entry *e, + unsigned int rulenum, + iptc_handle_t *handle) +{ + unsigned int chainoff, chainindex, offset; + struct ipt_entry_target old; + int ret; + + CHECK(*handle); + iptc_fn = iptc_replace_entry; + + if (!find_label(&chainoff, chain, *handle)) { + errno = ENOENT; + return 0; + } + + chainindex = entry2index(*handle, get_entry(*handle, chainoff)); + + if (index2entry(*handle, chainindex + rulenum) + >= get_entry(*handle, get_chain_end(*handle, chainoff))) { + 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 ipt_entry *)e, offset, &old)) + return 0; + CHECK(*handle); + + ret = insert_rules(1, e->next_offset, e, offset, + chainindex + rulenum, 1, handle); + unmap_target((struct ipt_entry *)e, &old); + CHECK(*handle); + return ret; +} + +/* Append entry `fw' to chain `chain'. Equivalent to insert with + rulenum = length of chain. */ +int +iptc_append_entry(const ipt_chainlabel chain, + const struct ipt_entry *e, + iptc_handle_t *handle) +{ + unsigned int startoff, endoff; + struct ipt_entry_target old; + int ret; + + CHECK(*handle); + iptc_fn = iptc_append_entry; + if (!find_label(&startoff, chain, *handle)) { + errno = ENOENT; + return 0; + } + + endoff = get_chain_end(*handle, startoff); + if (!map_target(*handle, (struct ipt_entry *)e, endoff, &old)) + return 0; + + ret = insert_rules(1, e->next_offset, e, endoff, + entry2index(*handle, get_entry(*handle, endoff)), + 0, handle); + unmap_target((struct ipt_entry *)e, &old); + CHECK(*handle); + return ret; +} + +static inline int +match_different(const struct ipt_entry_match *a, + const char *a_elems, + const char *b_elems) +{ + const struct ipt_entry_match *b; + + /* Offset of b is the same as a. */ + b = (void *)b_elems + (a_elems - (char *)a); + + if (a->match_size != b->match_size) + return 1; + + if (strcmp(a->u.name, b->u.name) != 0) + return 1; + + /* FIXME: If kernel modifies these (eg. RATE), then we'll + never match --RR */ + if (memcmp(a->data, b->data, a->match_size - sizeof(*a)) != 0) + return 1; + + return 0; +} + +static inline int +is_same(const struct ipt_entry *a, const struct ipt_entry *b) +{ + unsigned int i; + struct ipt_entry_target *ta, *tb; + + if (a->ip.src.s_addr != b->ip.src.s_addr + || a->ip.dst.s_addr != b->ip.dst.s_addr + || a->ip.smsk.s_addr != b->ip.smsk.s_addr + || a->ip.smsk.s_addr != b->ip.smsk.s_addr + || a->ip.proto != b->ip.proto + || a->ip.flags != b->ip.flags + || a->ip.invflags != b->ip.invflags) + return 0; + + for (i = 0; i < IFNAMSIZ; i++) { + if (a->ip.iniface_mask[i] != b->ip.iniface_mask[i]) + return 0; + if ((a->ip.iniface[i] & a->ip.iniface_mask[i]) + != (b->ip.iniface[i] & b->ip.iniface_mask[i])) + return 0; + if (a->ip.outiface_mask[i] != b->ip.outiface_mask[i]) + return 0; + if ((a->ip.outiface[i] & a->ip.outiface_mask[i]) + != (b->ip.outiface[i] & b->ip.outiface_mask[i])) + return 0; + } + + if (a->nfcache != b->nfcache + || a->target_offset != b->target_offset + || a->next_offset != b->next_offset) + return 0; + + if (IPT_MATCH_ITERATE(a, match_different, a->elems, b->elems)) + return 0; + + ta = ipt_get_target((struct ipt_entry *)a); + tb = ipt_get_target((struct ipt_entry *)b); + if (ta->target_size != tb->target_size) + return 0; + if (strcmp(ta->u.name, tb->u.name) != 0) + return 0; + /* FIXME: If kernel modifies these, then we never match --RR */ + if (memcmp(ta->data, tb->data, ta->target_size - sizeof(*ta)) != 0) + return 0; + + return 1; +} + +/* Delete the first rule in `chain' which matches `fw'. */ +int +iptc_delete_entry(const ipt_chainlabel chain, + const struct ipt_entry *origfw, + iptc_handle_t *handle) +{ + unsigned int offset, lastoff; + struct ipt_entry *e, *fw; + + CHECK(*handle); + iptc_fn = iptc_delete_entry; + if (!find_label(&offset, chain, *handle)) { + errno = ENOENT; + return 0; + } + + fw = malloc(origfw->next_offset); + if (fw == NULL) { + errno = ENOMEM; + return 0; + } + lastoff = get_chain_end(*handle, offset); + + for (; offset < lastoff; offset += e->next_offset) { + struct ipt_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)) { + int ret; + ret = delete_rules(1, e->next_offset, + offset, entry2index(*handle, e), + handle); + free(fw); + CHECK(*handle); + return ret; + } + } + + free(fw); + errno = ENOENT; + return 0; +} + +/* Delete the rule in position `rulenum' in `chain'. */ +int +iptc_delete_num_entry(const ipt_chainlabel chain, + unsigned int rulenum, + iptc_handle_t *handle) +{ + unsigned int chainstart; + unsigned int index; + int ret; + struct ipt_entry *e; + + CHECK(*handle); + iptc_fn = iptc_delete_num_entry; + if (!find_label(&chainstart, chain, *handle)) { + errno = ENOENT; + return 0; + } + + index = entry2index(*handle, get_entry(*handle, chainstart)) + + rulenum; + + if (index + >= entry2index(*handle, + get_entry(*handle, + get_chain_end(*handle, chainstart)))) { + 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); + CHECK(*handle); + return ret; +} + +/* Check the packet `fw' on chain `chain'. Returns the verdict, or + NULL and sets errno. */ +const char * +iptc_check_packet(const ipt_chainlabel chain, + struct ipt_entry *entry, + iptc_handle_t *handle) +{ + errno = ENOSYS; + return NULL; +} + +/* Flushes the entries in the given chain (ie. empties chain). */ +int +iptc_flush_entries(const ipt_chainlabel chain, iptc_handle_t *handle) +{ + unsigned int startoff, endoff, startindex, endindex; + int ret; + + CHECK(*handle); + iptc_fn = iptc_flush_entries; + if (!find_label(&startoff, chain, *handle)) { + errno = ENOENT; + return 0; + } + endoff = get_chain_end(*handle, startoff); + startindex = entry2index(*handle, get_entry(*handle, startoff)); + endindex = entry2index(*handle, get_entry(*handle, endoff)); + + ret = delete_rules(endindex - startindex, + endoff - startoff, startoff, startindex, + handle); + CHECK(*handle); + return ret; +} + +/* Zeroes the counters in a chain. */ +int +iptc_zero_entries(const ipt_chainlabel chain, iptc_handle_t *handle) +{ + unsigned int i, end; + + CHECK(*handle); + if (!find_label(&i, chain, *handle)) { + errno = ENOENT; + return 0; + } + end = get_chain_end(*handle, i); + + i = entry2index(*handle, get_entry(*handle, i)); + end = entry2index(*handle, get_entry(*handle, end)); + + for (; i <= end; i++) { + if ((*handle)->counter_map[i].maptype ==COUNTER_MAP_NORMAL_MAP) + (*handle)->counter_map[i].maptype = COUNTER_MAP_ZEROED; + } + (*handle)->changed = 1; + + CHECK(*handle); + return 1; +} + +/* Creates a new chain. */ +/* To create a chain, create two rules: error node and unconditional + * return. */ +int +iptc_create_chain(const ipt_chainlabel chain, iptc_handle_t *handle) +{ + unsigned int pos; + int ret; + struct { + struct ipt_entry head; + struct ipt_error_target name; + struct ipt_entry ret; + struct ipt_standard_target target; + } newc; + + CHECK(*handle); + iptc_fn = iptc_create_chain; + + /* find_label doesn't cover built-in targets: DROP, ACCEPT, + QUEUE, RETURN. */ + if (find_label(&pos, chain, *handle) + || strcmp(chain, IPTC_LABEL_DROP) == 0 + || strcmp(chain, IPTC_LABEL_ACCEPT) == 0 + || strcmp(chain, IPTC_LABEL_QUEUE) == 0 + || strcmp(chain, IPTC_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 ipt_entry); + newc.head.next_offset + = sizeof(struct ipt_entry) + sizeof(struct ipt_error_target); + strcpy(newc.name.t.u.name, IPT_ERROR_TARGET); + newc.name.t.target_size = sizeof(struct ipt_error_target); + strcpy(newc.name.error, chain); + + newc.ret.target_offset = sizeof(struct ipt_entry); + newc.ret.next_offset + = sizeof(struct ipt_entry)+sizeof(struct ipt_standard_target); + strcpy(newc.target.target.u.name, IPT_STANDARD_TARGET); + newc.target.target.target_size = sizeof(struct ipt_standard_target); + newc.target.verdict = IPT_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); + CHECK(*handle); + return ret; +} + +static int +count_ref(struct ipt_entry *e, unsigned int offset, unsigned int *ref) +{ + struct ipt_standard_target *t; + + if (strcmp(ipt_get_target(e)->u.name, IPT_STANDARD_TARGET) == 0) { + t = (struct ipt_standard_target *)ipt_get_target(e); + + if (t->verdict == offset) + (*ref)++; + } + + return 0; +} + +/* Get the number of references to this chain. */ +int +iptc_get_references(unsigned int *ref, const ipt_chainlabel chain, + iptc_handle_t *handle) +{ + unsigned int offset; + + CHECK(*handle); + if (!find_label(&offset, chain, *handle)) { + errno = ENOENT; + return 0; + } + + *ref = 0; + IPT_ENTRY_ITERATE((*handle)->entries.entries, + (*handle)->entries.size, + count_ref, offset, ref); + return 1; +} + +/* Deletes a chain. */ +int +iptc_delete_chain(const ipt_chainlabel chain, iptc_handle_t *handle) +{ + unsigned int chainoff, labelidx, labeloff; + unsigned int references; + struct ipt_entry *e; + int ret; + + CHECK(*handle); + if (!iptc_get_references(&references, chain, handle)) + return 0; + + iptc_fn = iptc_delete_chain; + + if (iptc_builtin(chain, *handle)) { + errno = EINVAL; + return 0; + } + + if (references > 0) { + errno = EMLINK; + return 0; + } + + if (!find_label(&chainoff, chain, *handle)) { + errno = ENOENT; + return 0; + } + + e = get_entry(*handle, chainoff); + if (get_chain_end(*handle, chainoff) != chainoff) { + errno = ENOTEMPTY; + return 0; + } + + /* Need label index: preceeds chain start */ + labelidx = entry2index(*handle, e) - 1; + labeloff = index2offset(*handle, labelidx); + + ret = delete_rules(2, + get_entry(*handle, labeloff)->next_offset + + e->next_offset, + labeloff, labelidx, handle); + CHECK(*handle); + return ret; +} + +/* Renames a chain. */ +int iptc_rename_chain(const ipt_chainlabel oldname, + const ipt_chainlabel newname, + iptc_handle_t *handle) +{ + unsigned int chainoff, labeloff, labelidx; + struct ipt_error_target *t; + + CHECK(*handle); + iptc_fn = iptc_rename_chain; + + /* find_label doesn't cover built-in targets: DROP, ACCEPT + RETURN. */ + if (find_label(&chainoff, newname, *handle) + || strcmp(newname, IPTC_LABEL_DROP) == 0 + || strcmp(newname, IPTC_LABEL_ACCEPT) == 0 + || strcmp(newname, IPTC_LABEL_RETURN) == 0) { + errno = EEXIST; + return 0; + } + + if (!find_label(&chainoff, oldname, *handle) + || iptc_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, get_entry(*handle, chainoff)) - 1; + labeloff = index2offset(*handle, labelidx); + + t = (struct ipt_error_target *) + ipt_get_target(get_entry(*handle, labeloff)); + + memset(t->error, 0, sizeof(t->error)); + strcpy(t->error, newname); + (*handle)->changed = 1; + + CHECK(*handle); + return 1; +} + +/* Sets the policy on a built-in chain. */ +int +iptc_set_policy(const ipt_chainlabel chain, + const ipt_chainlabel policy, + iptc_handle_t *handle) +{ + unsigned int hook; + unsigned int policyoff; + struct ipt_entry *e; + struct ipt_standard_target *t; + + CHECK(*handle); + /* Figure out which chain. */ + hook = iptc_builtin(chain, *handle); + if (hook == 0) { + errno = EINVAL; + 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 ipt_standard_target *)ipt_get_target(e); + + if (strcmp(policy, IPTC_LABEL_ACCEPT) == 0) + t->verdict = -NF_ACCEPT - 1; + else if (strcmp(policy, IPTC_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 }); + (*handle)->changed = 1; + + CHECK(*handle); + return 1; +} + +/* Without this, on gcc 2.7.2.3, we get: + libiptc.c: In function `iptc_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 ipt_counters *answer, + const struct ipt_counters *a, + const struct ipt_counters *b) +{ + answer->pcnt = a->pcnt - b->pcnt; + answer->bcnt = a->bcnt - b->bcnt; +} + +int +iptc_commit(iptc_handle_t *handle) +{ + /* Replace, then map back the counters. */ + struct ipt_replace *repl; + struct ipt_counters_info *newcounters; + unsigned int i; + size_t counterlen + = sizeof(struct ipt_counters_info) + + sizeof(struct ipt_counters) * (*handle)->new_number; + + CHECK(*handle); +#if 0 + 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 ipt_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.entries, + (*handle)->entries.size); + + if (setsockopt(sockfd, IPPROTO_IP, IPT_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 ipt_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, IPPROTO_IP, IPT_SO_SET_ADD_COUNTERS, + newcounters, counterlen) < 0) { + free(repl->counters); + free(repl); + free(newcounters); + return 0; + } + + free(repl->counters); + free(repl); + free(newcounters); + + finished: + free(*handle); + *handle = NULL; + return 1; +} + +/* Get raw socket. */ +int +iptc_get_raw_socket() +{ + return sockfd; +} + +/* Translates errno numbers into more human-readable form than strerror. */ +const char * +iptc_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" }, + { iptc_init, EPERM, "Permission denied (you must be root)" }, + { iptc_init, EINVAL, "Module is wrong version" }, + { iptc_delete_chain, ENOTEMPTY, "Chain is not empty" }, + { iptc_delete_chain, EINVAL, "Can't delete built-in chain" }, + { iptc_delete_chain, EMLINK, + "Can't delete chain with references left" }, + { iptc_create_chain, EEXIST, "Chain already exists" }, + { iptc_insert_entry, E2BIG, "Index of insertion too big" }, + { iptc_replace_entry, E2BIG, "Index of replacement too big" }, + { iptc_delete_num_entry, E2BIG, "Index of deletion too big" }, + { iptc_insert_entry, ELOOP, "Loop found in table" }, + { iptc_insert_entry, EINVAL, "Target problem" }, + /* EINVAL for CHECK probably means bad interface. */ + { iptc_check_packet, EINVAL, + "bad arguments (does that interface exist?)" }, + /* ENOENT for DELETE probably means no matching rule */ + { iptc_delete_entry, ENOENT, + "bad rule (does a matching rule exist in that chain?)" }, + { NULL, ENOENT, "No extended 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); +} + +/***************************** DEBUGGING ********************************/ +static inline int +unconditional(const struct ipt_ip *ip) +{ + unsigned int i; + + for (i = 0; i < sizeof(*ip)/sizeof(u_int32_t); i++) + if (((u_int32_t *)ip)[i]) + return 0; + + return 1; +} + +static inline int +check_match(const struct ipt_entry_match *m, unsigned int *off) +{ + assert(m->match_size >= sizeof(struct ipt_entry_match)); + + (*off) += m->match_size; + return 0; +} + +static inline int +check_entry(const struct ipt_entry *e, unsigned int *i, unsigned int *off, + unsigned int user_offset, int *was_return, + iptc_handle_t h) +{ + unsigned int toff; + struct ipt_standard_target *t; + + assert(e->target_offset >= sizeof(struct ipt_entry)); + assert(e->next_offset >= e->target_offset + + sizeof(struct ipt_entry_target)); + toff = sizeof(struct ipt_entry); + IPT_MATCH_ITERATE(e, check_match, &toff); + + assert(toff == e->target_offset); + + t = (struct ipt_standard_target *) + ipt_get_target((struct ipt_entry *)e); + assert(t->target.target_size == e->next_offset - e->target_offset); + assert(!iptc_is_chain(t->target.u.name, h)); + + if (strcmp(t->target.u.name, IPT_STANDARD_TARGET) == 0) { + assert(t->target.target_size + == IPT_ALIGN(sizeof(struct ipt_standard_target))); + + assert(t->verdict == -NF_DROP-1 + || t->verdict == -NF_ACCEPT-1 + || t->verdict == IPT_RETURN + || t->verdict < (int)h->entries.size); + + if (t->verdict >= 0) { + struct ipt_entry *te = get_entry(h, t->verdict); + int idx; + + idx = entry2index(h, te); + assert(strcmp(ipt_get_target(te)->u.name, + IPT_ERROR_TARGET) + != 0); + assert(te != e); + + /* Prior node must be error node, or this node. */ + assert(t->verdict == entry2offset(h, e)+e->next_offset + || strcmp(ipt_get_target(index2entry(h, idx-1)) + ->u.name, IPT_ERROR_TARGET) + == 0); + } + + if (t->verdict == IPT_RETURN + && unconditional(&e->ip) + && e->target_offset == sizeof(*e)) + *was_return = 1; + else + *was_return = 0; + } else if (strcmp(t->target.u.name, IPT_ERROR_TARGET) == 0) { + assert(t->target.target_size + == IPT_ALIGN(sizeof(struct ipt_error_target))); + + /* If this is in user area, previous must have been return */ + if (*off > user_offset) + assert(*was_return); + + *was_return = 0; + } + else *was_return = 0; + + if (*off == user_offset) + assert(strcmp(t->target.u.name, IPT_ERROR_TARGET) == 0); + + (*off) += e->next_offset; + (*i)++; + return 0; +} + +/* Do every conceivable sanity check on the handle */ +static void +do_check(iptc_handle_t h, unsigned int line) +{ + unsigned int i, n; + unsigned int user_offset; /* Offset of first user chain */ + int was_return; + + assert(h->changed == 0 || h->changed == 1); + if (strcmp(h->info.name, "filter") == 0) { + assert(h->info.valid_hooks + == (1 << NF_IP_LOCAL_IN + | 1 << NF_IP_FORWARD + | 1 << NF_IP_LOCAL_OUT)); + + /* Hooks should be first three */ + assert(h->info.hook_entry[NF_IP_LOCAL_IN] == 0); + + n = get_chain_end(h, 0); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_FORWARD] == n); + + n = get_chain_end(h, n); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_LOCAL_OUT] == n); + + user_offset = h->info.hook_entry[NF_IP_LOCAL_OUT]; + } else if (strcmp(h->info.name, "nat") == 0) { + assert(h->info.valid_hooks + == (1 << NF_IP_PRE_ROUTING + | 1 << NF_IP_POST_ROUTING + | 1 << NF_IP_LOCAL_OUT)); + + assert(h->info.hook_entry[NF_IP_PRE_ROUTING] == 0); + + n = get_chain_end(h, 0); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_POST_ROUTING] == n); + + n = get_chain_end(h, n); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_LOCAL_OUT] == n); + + user_offset = h->info.hook_entry[NF_IP_LOCAL_OUT]; + } else if (strcmp(h->info.name, "mangle") == 0) { + assert(h->info.valid_hooks + == (1 << NF_IP_PRE_ROUTING + | 1 << NF_IP_LOCAL_OUT)); + + /* Hooks should be first three */ + assert(h->info.hook_entry[NF_IP_PRE_ROUTING] == 0); + + n = get_chain_end(h, 0); + n += get_entry(h, n)->next_offset; + assert(h->info.hook_entry[NF_IP_LOCAL_OUT] == n); + + user_offset = h->info.hook_entry[NF_IP_LOCAL_OUT]; + } else + abort(); + + /* User chain == end of last builtin + policy entry */ + user_offset = get_chain_end(h, user_offset); + user_offset += get_entry(h, user_offset)->next_offset; + + /* Overflows should be end of entry chains, and unconditional + policy nodes. */ + for (i = 0; i < NF_IP_NUMHOOKS; i++) { + struct ipt_entry *e; + struct ipt_standard_target *t; + + if (!(h->info.valid_hooks & (1 << i))) + continue; + assert(h->info.underflow[i] + == get_chain_end(h, h->info.hook_entry[i])); + + e = get_entry(h, get_chain_end(h, h->info.hook_entry[i])); + assert(unconditional(&e->ip)); + assert(e->target_offset == sizeof(*e)); + assert(e->next_offset == sizeof(*e) + sizeof(*t)); + t = (struct ipt_standard_target *)ipt_get_target(e); + + assert(strcmp(t->target.u.name, IPT_STANDARD_TARGET) == 0); + assert(t->verdict == -NF_DROP-1 || t->verdict == -NF_ACCEPT-1); + + /* Hooks and underflows must be valid entries */ + entry2index(h, get_entry(h, h->info.hook_entry[i])); + entry2index(h, get_entry(h, h->info.underflow[i])); + } + + assert(h->info.size + >= h->info.num_entries * (sizeof(struct ipt_entry) + +sizeof(struct ipt_standard_target))); + + assert(h->entries.size + >= (h->new_number + * (sizeof(struct ipt_entry) + + sizeof(struct ipt_standard_target)))); + assert(strcmp(h->info.name, h->entries.name) == 0); + + i = 0; n = 0; + was_return = 0; + /* Check all the entries. */ + IPT_ENTRY_ITERATE(h->entries.entries, h->entries.size, + check_entry, &i, &n, user_offset, &was_return, h); + + assert(i == h->new_number); + assert(n == h->entries.size); + + /* Final entry must be error node */ + assert(strcmp(ipt_get_target(index2entry(h, h->new_number-1))->u.name, + IPT_ERROR_TARGET) == 0); +} -- cgit v1.2.3