/* 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 #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; /* This was taking us ~50 seconds to list 300 rules. */ /* Cached: last find_label result */ char cache_label_name[IPT_TABLE_MAXNAMELEN]; int cache_label_return; unsigned int cache_label_offset; /* Number in here reflects current state. */ unsigned int new_number; struct ipt_get_entries entries; }; static void set_changed(iptc_handle_t h) { h->cache_label_name[0] = '\0'; h->changed = 1; } 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->cache_label_name[0] = '\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; /* Cached? */ if (handle->cache_label_name[0] && strcmp(name, handle->cache_label_name) == 0) { *off = handle->cache_label_offset; return handle->cache_label_return; } /* 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) { strcpy(handle->cache_label_name, name); handle->cache_label_offset = *off; handle->cache_label_return = 1; return 1; } } strcpy(handle->cache_label_name, name); handle->cache_label_return = 0; 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_QUEUE-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); 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 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 + ((char *)a-a_elems); if (a->match_size != b->match_size) return 1; if (strcmp(a->u.name, b->u.name) != 0) return 1; /* FIXME: This is really, really gross --RR */ if (strcmp(a->u.name,"limit") == 0) { /* Special case, the kernel writes in this data, so we * need to make sure that we only check the parts * that are user specified */ if (((struct ipt_rateinfo *)a->data)->avg != ((struct ipt_rateinfo *)b->data)->avg || ((struct ipt_rateinfo *)a->data)->burst != ((struct ipt_rateinfo *)b->data)->burst) return 1; } else 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; } set_changed(*handle); 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); set_changed(*handle); 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 }); set_changed(*handle); 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); }