/* * ebtables.c, v2.0 July 2002 * * Author: Bart De Schuymer * * This code is stongly inspired on the iptables code which is * Copyright (C) 1999 Paul `Rusty' Russell & Michael J. Neuling * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include // the database #include #include #include "include/ebtables_u.h" #include #include #include // here are the number-name correspondences kept for the Ethernet // frame type field #define PROTOCOLFILE "/etc/ethertypes" #ifndef PROC_SYS_MODPROBE #define PROC_SYS_MODPROBE "/proc/sys/kernel/modprobe" #endif #define DATABASEHOOKNR -2 #define DATABASEHOOKNAME "DB" static char *prog_name = PROGNAME; static char *prog_version = PROGVERSION; char *hooknames[NF_BR_NUMHOOKS] = { [NF_BR_PRE_ROUTING]"PREROUTING", [NF_BR_LOCAL_IN]"INPUT", [NF_BR_FORWARD]"FORWARD", [NF_BR_LOCAL_OUT]"OUTPUT", [NF_BR_POST_ROUTING]"POSTROUTING", [NF_BR_BROUTING]"BROUTING" }; // default command line options // do not mess around with the already assigned numbers unless // you know what you are doing static struct option ebt_original_options[] = { { "append" , required_argument, 0, 'A' }, { "insert" , required_argument, 0, 'I' }, { "delete" , required_argument, 0, 'D' }, { "list" , optional_argument, 0, 'L' }, { "Lc" , no_argument , 0, 4 }, { "Ln" , no_argument , 0, 5 }, { "Lx" , no_argument , 0, 6 }, { "zero" , optional_argument, 0, 'Z' }, { "flush" , optional_argument, 0, 'F' }, { "policy" , required_argument, 0, 'P' }, { "in-interface" , required_argument, 0, 'i' }, { "in-if" , required_argument, 0, 'i' }, { "logical-in" , required_argument, 0, 2 }, { "logical-out" , required_argument, 0, 3 }, { "out-interface" , required_argument, 0, 'o' }, { "out-if" , required_argument, 0, 'o' }, { "version" , no_argument , 0, 'V' }, { "help" , no_argument , 0, 'h' }, { "jump" , required_argument, 0, 'j' }, { "proto" , required_argument, 0, 'p' }, { "protocol" , required_argument, 0, 'p' }, { "db" , required_argument, 0, 'b' }, { "source" , required_argument, 0, 's' }, { "src" , required_argument, 0, 's' }, { "destination" , required_argument, 0, 'd' }, { "dst" , required_argument, 0, 'd' }, { "table" , required_argument, 0, 't' }, { "modprobe" , required_argument, 0, 'M' }, { "new-chain" , required_argument, 0, 'N' }, { "rename-chain" , required_argument, 0, 'E' }, { "delete-chain" , required_argument, 0, 'X' }, { "atomic-init" , required_argument, 0, 7 }, { "atomic-commit" , required_argument, 0, 8 }, { "atomic" , required_argument, 0, 9 }, { "atomic-save" , required_argument, 0, 10 }, { "init-table" , no_argument , 0, 11 }, { 0 } }; static struct option *ebt_options = ebt_original_options; // yup, all the possible target names char* standard_targets[NUM_STANDARD_TARGETS] = { "ACCEPT", "DROP", "CONTINUE", "RETURN", }; unsigned char mac_type_unicast[ETH_ALEN] = {0,0,0,0,0,0}; unsigned char msk_type_unicast[ETH_ALEN] = {1,0,0,0,0,0}; unsigned char mac_type_multicast[ETH_ALEN] = {1,0,0,0,0,0}; unsigned char msk_type_multicast[ETH_ALEN] = {1,0,0,0,0,0}; unsigned char mac_type_broadcast[ETH_ALEN] = {255,255,255,255,255,255}; unsigned char msk_type_broadcast[ETH_ALEN] = {255,255,255,255,255,255}; // holds all the data static struct ebt_u_replace replace; // the chosen table static struct ebt_u_table *table = NULL; // the lists of supported tables, matches, watchers and targets static struct ebt_u_table *tables = NULL; static struct ebt_u_match *matches = NULL; static struct ebt_u_watcher *watchers = NULL; static struct ebt_u_target *targets = NULL; struct ebt_u_target *find_target(const char *name) { struct ebt_u_target *t = targets; while(t && strcmp(t->name, name)) t = t->next; return t; } struct ebt_u_match *find_match(const char *name) { struct ebt_u_match *m = matches; while(m && strcmp(m->name, name)) m = m->next; return m; } struct ebt_u_watcher *find_watcher(const char *name) { struct ebt_u_watcher *w = watchers; while(w && strcmp(w->name, name)) w = w->next; return w; } struct ebt_u_table *find_table(char *name) { struct ebt_u_table *t = tables; while (t && strcmp(t->name, name)) t = t->next; return t; } // The pointers in here are special: // The struct ebt_target * pointer is actually a struct ebt_u_target * pointer. // instead of making yet a few other structs, we just do a cast. // We need a struct ebt_u_target pointer because we know the address of the data // they point to won't change. We want to allow that the struct ebt_u_target.t // member can change. // Same holds for the struct ebt_match and struct ebt_watcher pointers struct ebt_u_entry *new_entry; static void initialize_entry(struct ebt_u_entry *e) { e->bitmask = EBT_NOPROTO; e->invflags = 0; e->ethproto = 0; strcpy(e->in, ""); strcpy(e->out, ""); strcpy(e->logical_in, ""); strcpy(e->logical_out, ""); e->m_list = NULL; e->w_list = NULL; // the init function of the standard target should have put the verdict // on CONTINUE e->t = (struct ebt_entry_target *)find_target(EBT_STANDARD_TARGET); if (!e->t) print_bug("Couldn't load standard target\n"); } // this doesn't free e, becoz the calling function might need e->next static void free_u_entry(struct ebt_u_entry *e) { struct ebt_u_match_list *m_l, *m_l2; struct ebt_u_watcher_list *w_l, *w_l2; m_l = e->m_list; while (m_l) { m_l2 = m_l->next; free(m_l->m); free(m_l); m_l = m_l2; } w_l = e->w_list; while (w_l) { w_l2 = w_l->next; free(w_l->w); free(w_l); w_l = w_l2; } free(e->t); } // the user will use the match, so put it in new_entry static void add_match(struct ebt_u_match *m) { struct ebt_u_match_list **m_list, *new; m->used = 1; for (m_list = &new_entry->m_list; *m_list; m_list = &(*m_list)->next); new = (struct ebt_u_match_list *) malloc(sizeof(struct ebt_u_match_list)); if (!new) print_memory(); *m_list = new; new->next = NULL; new->m = (struct ebt_entry_match *)m; } static void add_watcher(struct ebt_u_watcher *w) { struct ebt_u_watcher_list **w_list; struct ebt_u_watcher_list *new; w->used = 1; for (w_list = &new_entry->w_list; *w_list; w_list = &(*w_list)->next); new = (struct ebt_u_watcher_list *) malloc(sizeof(struct ebt_u_watcher_list)); if (!new) print_memory(); *w_list = new; new->next = NULL; new->w = (struct ebt_entry_watcher *)w; } static int global_option_offset = 0; #define OPTION_OFFSET 256 static struct option * merge_options(struct option *oldopts, const struct option *newopts, unsigned int *options_offset) { unsigned int num_old, num_new, i; struct option *merge; if (!newopts || !oldopts || !options_offset) print_bug("merge wrong"); for (num_old = 0; oldopts[num_old].name; num_old++); for (num_new = 0; newopts[num_new].name; num_new++); global_option_offset += OPTION_OFFSET; *options_offset = global_option_offset; merge = malloc(sizeof(struct option) * (num_new + num_old + 1)); if (!merge) print_memory(); memcpy(merge, oldopts, num_old * sizeof(struct option)); for (i = 0; i < num_new; i++) { merge[num_old + i] = newopts[i]; merge[num_old + i].val += *options_offset; } memset(merge + num_old + num_new, 0, sizeof(struct option)); // only free dynamically allocated stuff if (oldopts != ebt_original_options) free(oldopts); return merge; } void register_match(struct ebt_u_match *m) { int size = m->size + sizeof(struct ebt_entry_match); struct ebt_u_match **i; m->m = (struct ebt_entry_match *)malloc(size); if (!m->m) print_memory(); strcpy(m->m->u.name, m->name); m->m->match_size = m->size; ebt_options = merge_options (ebt_options, m->extra_ops, &(m->option_offset)); m->init(m->m); for (i = &matches; *i; i = &((*i)->next)); m->next = NULL; *i = m; } void register_watcher(struct ebt_u_watcher *w) { int size = w->size + sizeof(struct ebt_entry_watcher); struct ebt_u_watcher **i; w->w = (struct ebt_entry_watcher *)malloc(size); if (!w->w) print_memory(); strcpy(w->w->u.name, w->name); w->w->watcher_size = w->size; ebt_options = merge_options (ebt_options, w->extra_ops, &(w->option_offset)); w->init(w->w); for (i = &watchers; *i; i = &((*i)->next)); w->next = NULL; *i = w; } void register_target(struct ebt_u_target *t) { int size = t->size + sizeof(struct ebt_entry_target); struct ebt_u_target **i; t->t = (struct ebt_entry_target *)malloc(size); if (!t->t) print_memory(); strcpy(t->t->u.name, t->name); t->t->target_size = t->size; ebt_options = merge_options (ebt_options, t->extra_ops, &(t->option_offset)); t->init(t->t); for (i = &targets; *i; i = &((*i)->next)); t->next = NULL; *i = t; } void register_table(struct ebt_u_table *t) { t->next = tables; tables = t; } // blatently stolen (again) from iptables.c userspace program // find out where the modprobe utility is located static char *get_modprobe(void) { int procfile; char *ret; procfile = open(PROC_SYS_MODPROBE, O_RDONLY); if (procfile < 0) return NULL; ret = malloc(1024); if (ret) { switch (read(procfile, ret, 1024)) { case -1: goto fail; case 1024: goto fail; /* Partial read. Wierd */ } if (ret[strlen(ret)-1]=='\n') ret[strlen(ret)-1]=0; close(procfile); return ret; } fail: free(ret); close(procfile); return NULL; } int ebtables_insmod(const char *modname, const char *modprobe) { char *buf = NULL; char *argv[3]; // If they don't explicitly set it, read out of kernel if (!modprobe) { buf = get_modprobe(); if (!buf) return -1; modprobe = buf; } switch (fork()) { case 0: argv[0] = (char *)modprobe; argv[1] = (char *)modname; argv[2] = NULL; execv(argv[0], argv); /* not usually reached */ exit(0); case -1: return -1; default: /* parent */ wait(NULL); } free(buf); return 0; } // helper function: processes a line of data from the file /etc/ethertypes static int get_a_line(char *buffer, char *value, FILE *ifp) { char line[80], *p; const char delim[] = " \t\n"; while (fgets(line, sizeof(line), ifp)) { p = strtok(line, delim); if (!p || p[0] == '#') continue; if (strlen(p) > 20) continue; strcpy(buffer, p); p = strtok(NULL, delim); if (!p || strlen(p) > 10) continue; strcpy(value, p); return 0; } return -1; } // translate a hexadecimal number to a protocol name, parsing /etc/ethertypes // returns 0 on success int number_to_name(unsigned short proto, char *name) { FILE *ifp; char buffer[21], value[11], *bfr; unsigned short i; if ( !(ifp = fopen(PROTOCOLFILE, "r")) ) return -1; while (1) { if (get_a_line(buffer, value, ifp)) { fclose(ifp); return -1; } i = (unsigned short) strtol(value, &bfr, 16); if (*bfr != '\0' || i != proto) continue; strcpy(name, buffer); fclose(ifp); return 0; } } // we use replace.flags, so we can't use the following values: // 0x01 == OPT_COMMAND, 0x02 == OPT_TABLE, 0x100 == OPT_ZERO #define LIST_N 0x04 #define LIST_C 0x08 #define LIST_X 0x10 // helper function for list_rules() static void list_em(struct ebt_u_entries *entries) { int i, j, space = 0, digits; struct ebt_u_entry *hlp; struct ebt_u_match_list *m_l; struct ebt_u_watcher_list *w_l; struct ebt_u_match *m; struct ebt_u_watcher *w; struct ebt_u_target *t; char name[21]; hlp = entries->entries; if (replace.flags & LIST_X && entries->policy != EBT_ACCEPT) { printf("ebtables -t %s -P %s %s\n", replace.name, entries->name, standard_targets[-entries->policy - 1]); } else if (!(replace.flags & LIST_X)) { printf("\nBridge chain: %s\nPolicy: %s\n", entries->name, standard_targets[-entries->policy - 1]); printf("nr. of entries: %d \n", entries->nentries); } i = entries->nentries; while (i > 9) { space++; i /= 10; } for (i = 0; i < entries->nentries; i++) { if (replace.flags & LIST_N) { digits = 0; // A little work to get nice rule numbers. j = i + 1; while (j > 9) { digits++; j /= 10; } for (j = 0; j < space - digits; j++) printf(" "); printf("%d. ", i + 1); } if (replace.flags & LIST_X) printf("ebtables -t %s -A %s ", replace.name, entries->name); // Don't print anything about the protocol if no protocol was // specified, obviously this means any protocol will do. if (!(hlp->bitmask & EBT_NOPROTO)) { printf("-p "); if (hlp->invflags & EBT_IPROTO) printf("! "); if (hlp->bitmask & EBT_802_3) printf("Length "); else { if (number_to_name(ntohs(hlp->ethproto), name)) printf("0x%x ", ntohs(hlp->ethproto)); else printf("%s ", name); } } if (hlp->bitmask & EBT_SOURCEMAC) { char hlpmsk[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; printf("-s "); if (hlp->invflags & EBT_ISOURCE) printf("! "); if (!memcmp(hlp->sourcemac, mac_type_unicast, 6) && !memcmp(hlp->sourcemsk, msk_type_unicast, 6)) { printf("Unicast"); goto endsrc; } if (!memcmp(hlp->sourcemac, mac_type_multicast, 6) && !memcmp(hlp->sourcemsk, msk_type_multicast, 6)) { printf("Multicast"); goto endsrc; } if (!memcmp(hlp->sourcemac, mac_type_broadcast, 6) && !memcmp(hlp->sourcemsk, msk_type_broadcast, 6)) { printf("Broadcast"); goto endsrc; } printf("%s", ether_ntoa((struct ether_addr *) hlp->sourcemac)); if (memcmp(hlp->sourcemsk, hlpmsk, 6)) { printf("/"); printf("%s", ether_ntoa((struct ether_addr *) hlp->sourcemsk)); } endsrc: printf(" "); } if (hlp->bitmask & EBT_DESTMAC) { char hlpmsk[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; printf("-d "); if (hlp->invflags & EBT_IDEST) printf("! "); if (!memcmp(hlp->destmac, mac_type_unicast, 6) && !memcmp(hlp->destmsk, msk_type_unicast, 6)) { printf("Unicast"); goto enddst; } if (!memcmp(hlp->destmac, mac_type_multicast, 6) && !memcmp(hlp->destmsk, msk_type_multicast, 6)) { printf("Multicast"); goto enddst; } if (!memcmp(hlp->destmac, mac_type_broadcast, 6) && !memcmp(hlp->destmsk, msk_type_broadcast, 6)) { printf("Broadcast"); goto enddst; } printf("%s", ether_ntoa((struct ether_addr *) hlp->destmac)); if (memcmp(hlp->destmsk, hlpmsk, 6)) { printf("/"); printf("%s", ether_ntoa((struct ether_addr *) hlp->destmsk)); } enddst: printf(" "); } if (hlp->in[0] != '\0') { printf("-i "); if (hlp->invflags & EBT_IIN) printf("! "); printf("%s ", hlp->in); } if (hlp->logical_in[0] != '\0') { printf("--logical-in "); if (hlp->invflags & EBT_ILOGICALIN) printf("! "); printf("%s ", hlp->logical_in); } if (hlp->logical_out[0] != '\0') { printf("--logical-out "); if (hlp->invflags & EBT_ILOGICALOUT) printf("! "); printf("%s ", hlp->logical_out); } if (hlp->out[0] != '\0') { printf("-o "); if (hlp->invflags & EBT_IOUT) printf("! "); printf("%s ", hlp->out); } m_l = hlp->m_list; while (m_l) { m = find_match(m_l->m->u.name); if (!m) print_bug("Match not found"); m->print(hlp, m_l->m); m_l = m_l->next; } w_l = hlp->w_list; while (w_l) { w = find_watcher(w_l->w->u.name); if (!w) print_bug("Watcher not found"); w->print(hlp, w_l->w); w_l = w_l->next; } printf("-j "); if (strcmp(hlp->t->u.name, EBT_STANDARD_TARGET)) printf("%s ", hlp->t->u.name); t = find_target(hlp->t->u.name); if (!t) print_bug("Target not found"); t->print(hlp, hlp->t); if (replace.flags & LIST_C) printf(", count = %llu", replace.counters[entries->counter_offset + i].pcnt); printf("\n"); hlp = hlp->next; } } struct ebt_u_entries *nr_to_chain(int nr) { if (nr == -1) return NULL; if (nr < NF_BR_NUMHOOKS) return replace.hook_entry[nr]; else { int i; struct ebt_u_chain_list *cl = replace.udc; i = nr - NF_BR_NUMHOOKS; while (i > 0 && cl) { cl = cl->next; i--; } if (cl) return cl->udc; else return NULL; } } static struct ebt_u_entries *to_chain() { return nr_to_chain(replace.selected_hook); } struct ebt_u_stack { int chain_nr; int n; struct ebt_u_entry *e; struct ebt_u_entries *entries; }; static void check_for_loops() { int chain_nr , i, j , k, sp = 0, verdict; struct ebt_u_entries *entries, *entries2; struct ebt_u_stack *stack = NULL; struct ebt_u_entry *e; i = -1; // initialize hook_mask to 0 while (1) { i++; if (i < NF_BR_NUMHOOKS && !(replace.valid_hooks & (1 << i))) continue; entries = nr_to_chain(i); if (!entries) break; entries->hook_mask = 0; } if (i > NF_BR_NUMHOOKS) { stack = (struct ebt_u_stack *)malloc((i - NF_BR_NUMHOOKS) * sizeof(struct ebt_u_stack)); if (!stack) print_memory(); } // check for loops, starting from every base chain for (i = 0; i < NF_BR_NUMHOOKS; i++) { if (!(replace.valid_hooks & (1 << i))) continue; entries = nr_to_chain(i); entries->hook_mask = (1 << i); chain_nr = i; e = entries->entries; for (j = 0; j < entries->nentries; j++) { if (strcmp(e->t->u.name, EBT_STANDARD_TARGET)) goto letscontinue; verdict = ((struct ebt_standard_target *)(e->t))->verdict; if (verdict < 0) goto letscontinue; entries2 = nr_to_chain(verdict + NF_BR_NUMHOOKS); entries2->hook_mask |= entries->hook_mask; // now see if we've been here before for (k = 0; k < sp; k++) if (stack[k].chain_nr == verdict + NF_BR_NUMHOOKS) print_error("Loop from chain %s to chain %s", nr_to_chain(chain_nr)->name, nr_to_chain(stack[k].chain_nr)->name); // jump to the chain, make sure we know how to get back stack[sp].chain_nr = chain_nr; stack[sp].n = j; stack[sp].entries = entries; stack[sp].e = e; sp++; j = -1; e = entries2->entries; chain_nr = verdict + NF_BR_NUMHOOKS; entries = entries2; continue; letscontinue: e = e->next; } // we are at the end of a standard chain if (sp == 0) continue; // go back to the chain one level higher sp--; j = stack[sp].n; chain_nr = stack[sp].chain_nr; e = stack[sp].e; entries = stack[sp].entries; goto letscontinue; } free(stack); return; } // parse the chain name and return the corresponding nr // returns -1 on failure int get_hooknr(char* arg) { int i; struct ebt_u_chain_list *cl = replace.udc; // database is special case (not really a chain) if (!strcmp(arg, DATABASEHOOKNAME)) return DATABASEHOOKNR; for (i = 0; i < NF_BR_NUMHOOKS; i++) { if (!(replace.valid_hooks & (1 << i))) continue; if (!strcmp(arg, replace.hook_entry[i]->name)) return i; } while(cl) { if (!strcmp(arg, cl->udc->name)) return i; i++; cl = cl->next; } return -1; } // yup, print out help static void print_help() { struct ebt_u_match_list *m_l; struct ebt_u_watcher_list *w_l; printf( "%s v%s\n" "Usage:\n" "ebtables -[ADI] chain rule-specification [options]\n" "ebtables -P chain target\n" "ebtables -[LFZ] [chain]\n" "ebtables -[b] [y,n]\n" "Commands:\n" "--append -A chain : Append to chain\n" "--delete -D chain : Delete matching rule from chain\n" "--delete -D chain rulenum : Delete rule at position rulenum from chain\n" "--insert -I chain rulenum : insert rule at position rulenum in chain\n" "--list -L [chain] : List the rules in a chain or in all chains\n" "--list -L "DATABASEHOOKNAME" : List the database (if present)\n" "--flush -F [chain] : Delete all rules in chain or in all chains\n" "--init-table : Replace the kernel table with the initial table\n" "--zero -Z [chain] : Put counters on zero in chain or in all chains\n" "--policy -P chain target : Change policy on chain to target\n" "--new-chain -N chain : Create a user defined chain\n" "--rename-chain -E old new : Rename a chain\n" "--delete-chain -X chain : Delete a user defined chain\n" "--atomic-commit file : update the kernel w/ the table contained in file\n" "--atomic-init file : put the initial kernel table into file\n" "--atomic-save file : put the current kernel table into file\n" "--atomic file : write changes to file instead of kernel\n" "Options:\n" "--proto -p [!] proto : protocol hexadecimal, by name or LENGTH\n" "--src -s [!] address[/mask]: source mac address\n" "--dst -d [!] address[/mask]: destination mac address\n" "--in-if -i [!] name : network input interface name\n" "--out-if -o [!] name : network output interface name\n" "--logical-in [!] name : logical bridge input interface name\n" "--logical-out [!] name : logical bridge output interface name\n" "--modprobe -M program : try to insert modules using this program\n" "--version -V : print package version\n" "\n" , prog_name, prog_version); m_l = new_entry->m_list; while (m_l) { ((struct ebt_u_match *)m_l->m)->help(); printf("\n"); m_l = m_l->next; } w_l = new_entry->w_list; while (w_l) { ((struct ebt_u_watcher *)w_l->w)->help(); printf("\n"); w_l = w_l->next; } ((struct ebt_u_target *)new_entry->t)->help(); printf("\n"); if (table->help) table->help(hooknames); exit(0); } // execute command L static void list_rules() { int i; if (!(replace.flags & LIST_X)) printf("Bridge table: %s\n", table->name); if (replace.selected_hook != -1) { list_em(to_chain()); } else { struct ebt_u_chain_list *cl = replace.udc; // create new chains and rename standard chains when necessary if (replace.flags & LIST_X) { while (cl) { printf("ebtables -t %s -N %s\n", replace.name, cl->udc->name); cl = cl->next; } cl = replace.udc; for (i = 0; i < NF_BR_NUMHOOKS; i++) if (replace.valid_hooks & (1 << i) && strcmp(replace.hook_entry[i]->name, hooknames[i])) printf("ebtables -t %s -E %s %s\n", replace.name, hooknames[i], replace.hook_entry[i]->name); } i = 0; while (1) { if (i < NF_BR_NUMHOOKS) { if (replace.valid_hooks & (1 << i)) list_em(replace.hook_entry[i]); i++; continue; } else { if (!cl) break; list_em(cl->udc); cl = cl->next; } } } } // execute command P static void change_policy(int policy) { int i; struct ebt_u_entries *entries = to_chain(); // don't do anything if the policy is the same if (entries->policy != policy) { entries->policy = policy; replace.num_counters = replace.nentries; if (replace.nentries) { // '+ 1' for the CNT_END if (!(replace.counterchanges = (unsigned short *) malloc( (replace.nentries + 1) * sizeof(unsigned short)))) print_memory(); // done nothing special to the rules for (i = 0; i < replace.nentries; i++) replace.counterchanges[i] = CNT_NORM; replace.counterchanges[replace.nentries] = CNT_END; } else replace.counterchanges = NULL; } else exit(0); } // flush one chain or the complete table // -1 == nothing to do // 0 == give back to kernel static int flush_chains() { int i, j, oldnentries, numdel; unsigned short *cnt; struct ebt_u_entry *u_e, *tmp; struct ebt_u_entries *entries = to_chain(); // flush whole table if (!entries) { if (replace.nentries == 0) return -1; replace.nentries = 0; // no need for the kernel to give us counters back replace.num_counters = 0; // free everything and zero (n)entries i = -1; while (1) { i++; entries = nr_to_chain(i); if (!entries) { if (i < NF_BR_NUMHOOKS) continue; else break; } entries->nentries = 0; entries->counter_offset = 0; u_e = entries->entries; entries->entries = NULL; while (u_e) { free_u_entry(u_e); tmp = u_e->next; free(u_e); u_e = tmp; } } return 0; } if (entries->nentries == 0) return -1; oldnentries = replace.nentries; replace.nentries -= entries->nentries; numdel = entries->nentries; if (replace.nentries) { // +1 for CNT_END if ( !(replace.counterchanges = (unsigned short *) malloc((oldnentries + 1) * sizeof(unsigned short))) ) print_memory(); } // delete the counters belonging to the specified chain, // update counter_offset i = -1; cnt = replace.counterchanges; while (1) { i++; entries = nr_to_chain(i); if (!entries) { if (i < NF_BR_NUMHOOKS) continue; else break; } if (i > replace.selected_hook) entries->counter_offset -= numdel; if (replace.nentries) { for (j = 0; j < entries->nentries; j++) { if (i == replace.selected_hook) *cnt = CNT_DEL; else *cnt = CNT_NORM; cnt++; } } } if (replace.nentries) { *cnt = CNT_END; replace.num_counters = oldnentries; } else replace.num_counters = 0; entries = to_chain(); entries->nentries = 0; u_e = entries->entries; while (u_e) { free_u_entry(u_e); tmp = u_e->next; free(u_e); u_e = tmp; } entries->entries = NULL; return 0; } // -1 == no match static int check_rule_exists(int rule_nr) { struct ebt_u_entry *u_e; struct ebt_u_match_list *m_l, *m_l2; struct ebt_u_match *m; struct ebt_u_watcher_list *w_l, *w_l2; struct ebt_u_watcher *w; struct ebt_u_target *t = (struct ebt_u_target *)new_entry->t; struct ebt_u_entries *entries = to_chain(); int i, j, k; // handle '-D chain rulenr' command if (rule_nr != -1) { if (rule_nr > entries->nentries) return -1; // user starts counting from 1 return rule_nr - 1; } u_e = entries->entries; // check for an existing rule (if there are duplicate rules, // take the first occurance) for (i = 0; i < entries->nentries; i++, u_e = u_e->next) { if (!u_e) print_bug("Hmm, trouble"); if (u_e->ethproto != new_entry->ethproto) continue; if (strcmp(u_e->in, new_entry->in)) continue; if (strcmp(u_e->out, new_entry->out)) continue; if (strcmp(u_e->logical_in, new_entry->logical_in)) continue; if (strcmp(u_e->logical_out, new_entry->logical_out)) continue; if (new_entry->bitmask & EBT_SOURCEMAC && memcmp(u_e->sourcemac, new_entry->sourcemac, ETH_ALEN)) continue; if (new_entry->bitmask & EBT_DESTMAC && memcmp(u_e->destmac, new_entry->destmac, ETH_ALEN)) continue; if (new_entry->bitmask != u_e->bitmask || new_entry->invflags != u_e->invflags) continue; // compare all matches m_l = new_entry->m_list; j = 0; while (m_l) { m = (struct ebt_u_match *)(m_l->m); m_l2 = u_e->m_list; while (m_l2 && strcmp(m_l2->m->u.name, m->m->u.name)) m_l2 = m_l2->next; if (!m_l2 || !m->compare(m->m, m_l2->m)) goto letscontinue; j++; m_l = m_l->next; } // now be sure they have the same nr of matches k = 0; m_l = u_e->m_list; while (m_l) { k++; m_l = m_l->next; } if (j != k) continue; // compare all watchers w_l = new_entry->w_list; j = 0; while (w_l) { w = (struct ebt_u_watcher *)(w_l->w); w_l2 = u_e->w_list; while (w_l2 && strcmp(w_l2->w->u.name, w->w->u.name)) w_l2 = w_l2->next; if (!w_l2 || !w->compare(w->w, w_l2->w)) goto letscontinue; j++; w_l = w_l->next; } k = 0; w_l = u_e->w_list; while (w_l) { k++; w_l = w_l->next; } if (j != k) continue; if (strcmp(t->t->u.name, u_e->t->u.name)) continue; if (!t->compare(t->t, u_e->t)) continue; return i; letscontinue: } return -1; } // execute command A or I static void add_rule(int rule_nr) { int i, j; struct ebt_u_entry **u_e; unsigned short *cnt; struct ebt_u_match_list *m_l; struct ebt_u_watcher_list *w_l; struct ebt_u_entries *entries = to_chain(), *entries2; if (rule_nr != -1) { // command -I if (--rule_nr > entries->nentries) print_error("rule nr too high: %d > %d", rule_nr + 1, entries->nentries + 1); } else rule_nr = entries->nentries; // we're adding one rule replace.num_counters = replace.nentries; replace.nentries++; entries->nentries++; // handle counter stuff // +1 for CNT_END if ( !(replace.counterchanges = (unsigned short *) malloc((replace.nentries + 1) * sizeof(unsigned short))) ) print_memory(); cnt = replace.counterchanges; for (i = 0; i < replace.selected_hook; i++) { if (i < NF_BR_NUMHOOKS && !(replace.valid_hooks & (1 << i))) continue; entries2 = nr_to_chain(i); for (j = 0; j < entries2->nentries; j++) { *cnt = CNT_NORM; cnt++; } } for (i = 0; i < rule_nr; i++) { *cnt = CNT_NORM; cnt++; } *cnt = CNT_ADD; cnt++; while (cnt != replace.counterchanges + replace.nentries) { *cnt = CNT_NORM; cnt++; } *cnt = CNT_END; // go to the right position in the chain u_e = &entries->entries; for (i = 0; i < rule_nr; i++) u_e = &(*u_e)->next; // insert the rule new_entry->next = *u_e; *u_e = new_entry; // put the ebt_[match, watcher, target] pointers in place m_l = new_entry->m_list; while (m_l) { m_l->m = ((struct ebt_u_match *)m_l->m)->m; m_l = m_l->next; } w_l = new_entry->w_list; while (w_l) { w_l->w = ((struct ebt_u_watcher *)w_l->w)->w; w_l = w_l->next; } new_entry->t = ((struct ebt_u_target *)new_entry->t)->t; // update the counter_offset of chains behind this one i = replace.selected_hook; while (1) { i++; entries = nr_to_chain(i); if (!entries) { if (i < NF_BR_NUMHOOKS) continue; else break; } else entries->counter_offset++; } } // execute command D static void delete_rule(int rule_nr) { int i, j, lentmp = 0; unsigned short *cnt; struct ebt_u_entry **u_e, *u_e2; struct ebt_u_entries *entries = to_chain(), *entries2; if ( (i = check_rule_exists(rule_nr)) == -1 ) print_error("Sorry, rule does not exist"); // we're deleting a rule replace.num_counters = replace.nentries; replace.nentries--; entries->nentries--; if (replace.nentries) { for (j = 0; j < replace.selected_hook; j++) { if (j < NF_BR_NUMHOOKS && !(replace.valid_hooks & (1 << j))) continue; entries2 = nr_to_chain(j); lentmp += entries2->nentries; } lentmp += i; // +1 for CNT_END if ( !(replace.counterchanges = (unsigned short *)malloc( (replace.num_counters + 1) * sizeof(unsigned short))) ) print_memory(); cnt = replace.counterchanges; for (j = 0; j < lentmp; j++) { *cnt = CNT_NORM; cnt++; } *cnt = CNT_DEL; cnt++; for (j = 0; j < replace.num_counters - lentmp; j++) { *cnt = CNT_NORM; cnt++; } *cnt = CNT_END; } else replace.num_counters = 0; // go to the right position in the chain u_e = &entries->entries; for (j = 0; j < i; j++) u_e = &(*u_e)->next; // remove the rule u_e2 = *u_e; *u_e = (*u_e)->next; // free everything free_u_entry(u_e2); free(u_e2); // update the counter_offset of chains behind this one i = replace.selected_hook; while (1) { i++; entries = nr_to_chain(i); if (!entries) { if (i < NF_BR_NUMHOOKS) continue; else break; } else entries->counter_offset--; } } // execute command Z static void zero_counters(int zerochain) { if (zerochain == -1) { // tell main() we don't update the counters // this results in tricking the kernel to zero its counters, // naively expecting userspace to update its counters. Muahahaha replace.counterchanges = NULL; replace.num_counters = 0; } else { int i, j; unsigned short *cnt; struct ebt_u_entries *entries = nr_to_chain(zerochain), *e2; if (entries->nentries == 0) exit(0); replace.counterchanges = (unsigned short *) malloc((replace.nentries + 1) * sizeof(unsigned short)); if (!replace.counterchanges) print_memory(); cnt = replace.counterchanges; for (i = 0; i < zerochain; i++) { if (i < NF_BR_NUMHOOKS && !(replace.valid_hooks & (1 << i))) continue; e2 = nr_to_chain(i); for (j = 0; j < e2->nentries; j++) { *cnt = CNT_NORM; cnt++; } } for (i = 0; i < entries->nentries; i++) { *cnt = CNT_ZERO; cnt++; } while (cnt != replace.counterchanges + replace.nentries) { *cnt = CNT_NORM; cnt++; } *cnt = CNT_END; } } // list the database (optionally compiled into the kernel) static void list_db() { struct brdb_dbinfo nr; struct brdb_dbentry *db; char name[21]; int i; get_dbinfo(&nr); // 0 : database disabled (-db n) if (!(nr.nentries)) print_error("Database not present" " (disabled), try ebtables --db y"); nr.nentries--; if (!nr.nentries) print_error("Database empty"); if ( !(db = (struct brdb_dbentry *) malloc(nr.nentries * sizeof(struct brdb_dbentry))) ) print_memory(); get_db(nr.nentries, db); printf("number of entries: %d\n", nr.nentries); for (i = 0; i < nr.nentries; i++) { printf( "%d:\n" "hook : %s\n" "in-if : %s\n" "out-if : %s\n" "protocol: ", i + 1, hooknames[db->hook], db->in, db->out); if (db->ethproto == IDENTIFY802_3) printf("802.2/802.3 STYLE LENGTH FIELD\n"); else { if (number_to_name(ntohs(db->ethproto), name)) printf("%x\n",ntohs(db->ethproto)); else printf("%s\n", name); } db++; } exit(0); } // handle db [dis,en]abling static void allowdb(char yorn) { __u16 decision; if (yorn != 'y' && yorn != 'n') print_error("Option [y] or [n] needed"); if (yorn == 'y') decision = BRDB_DB; else decision = BRDB_NODB; deliver_allowdb(&decision); exit(0); } // 0 == success // 1 == success, but for the special 'protocol' LENGTH // -1 == failure int name_to_number(char *name, __u16 *proto) { FILE *ifp; char buffer[21], value[11], *bfr; unsigned short i; if (!strcasecmp("LENGTH", name)) { *proto = 0; new_entry->bitmask |= EBT_802_3; return 1; } if ( !(ifp = fopen(PROTOCOLFILE, "r")) ) return -1; while (1) { if (get_a_line(buffer, value, ifp)) return -1; if (strcasecmp(buffer, name)) continue; fclose(ifp); i = (unsigned short) strtol(value, &bfr, 16); if (*bfr != '\0') return -1; *proto = i; return 0; } return -1; } // put the mac address into 6 (ETH_ALEN) bytes int getmac_and_mask(char *from, char *to, char *mask) { char *p; int i; struct ether_addr *addr; if (strcasecmp(from, "Unicast") == 0) { memcpy(to, mac_type_unicast, ETH_ALEN); memcpy(mask, msk_type_unicast, ETH_ALEN); return 0; } if (strcasecmp(from, "Multicast") == 0) { memcpy(to, mac_type_multicast, ETH_ALEN); memcpy(mask, msk_type_multicast, ETH_ALEN); return 0; } if (strcasecmp(from, "Broadcast") == 0) { memcpy(to, mac_type_broadcast, ETH_ALEN); memcpy(mask, msk_type_broadcast, ETH_ALEN); return 0; } if ( (p = strrchr(from, '/')) != NULL) { *p = '\0'; if (!(addr = ether_aton(p + 1))) return -1; memcpy(mask, addr, ETH_ALEN); } else memset(mask, 0xff, ETH_ALEN); if (!(addr = ether_aton(from))) return -1; memcpy(to, addr, ETH_ALEN); for (i = 0; i < ETH_ALEN; i++) to[i] &= mask[i]; return 0; } // executes the final_check() function for all extensions used by the rule static void do_final_checks(struct ebt_u_entry *e, struct ebt_u_entries *entries) { struct ebt_u_match_list *m_l; struct ebt_u_watcher_list *w_l; struct ebt_u_target *t; struct ebt_u_match *m; struct ebt_u_watcher *w; m_l = e->m_list; w_l = e->w_list; while (m_l) { m = find_match(m_l->m->u.name); m->final_check(e, m_l->m, replace.name, entries->hook_mask, 1); m_l = m_l->next; } while (w_l) { w = find_watcher(w_l->w->u.name); w->final_check(e, w_l->w, replace.name, entries->hook_mask, 1); w_l = w_l->next; } t = find_target(e->t->u.name); t->final_check(e, e->t, replace.name, entries->hook_mask, 1); } // used for the -X command static void check_for_references(int chain_nr) { int i = -1, j; struct ebt_u_entries *entries; struct ebt_u_entry *e; while (1) { i++; entries = nr_to_chain(i); if (!entries) { if (i < NF_BR_NUMHOOKS) continue; else break; } e = entries->entries; j = 0; while (e) { j++; if (strcmp(e->t->u.name, EBT_STANDARD_TARGET)) { e = e->next; continue; } if (((struct ebt_standard_target *)e->t)->verdict == chain_nr) print_error("Can't delete the chain, it's referenced " "in chain %s, rule %d", entries->name, j); e = e->next; } } } int check_inverse(const char option[]) { if (strcmp(option, "!") == 0) { optind++; return 1; } return 0; } void check_option(unsigned int *flags, unsigned int mask) { if (*flags & mask) print_error("Multiple use of same option not allowed"); *flags |= mask; } static void get_kernel_table(const char *modprobe) { if ( !(table = find_table(replace.name)) ) print_error("Bad table name"); // get the kernel's information if (get_table(&replace)) { ebtables_insmod("ebtables", modprobe); if (get_table(&replace)) print_error("The kernel doesn't support the ebtables " "%s table", replace.name); } } #define OPT_COMMAND 0x01 #define OPT_TABLE 0x02 #define OPT_IN 0x04 #define OPT_OUT 0x08 #define OPT_JUMP 0x10 #define OPT_PROTOCOL 0x20 #define OPT_SOURCE 0x40 #define OPT_DEST 0x80 #define OPT_ZERO 0x100 #define OPT_LOGICALIN 0x200 #define OPT_LOGICALOUT 0x400 // the main thing int main(int argc, char *argv[]) { char *buffer, allowbc = 'n'; int c, i; // this special one for the -Z option (we can have -Z -L ) int zerochain = -1; int policy = 0; int rule_nr = -1;// used for -[D,I] chain number struct ebt_u_target *t; struct ebt_u_match *m; struct ebt_u_watcher *w; struct ebt_u_match_list *m_l; struct ebt_u_watcher_list *w_l; struct ebt_u_entries *entries; const char *modprobe = NULL; // initialize the table name, OPT_ flags, selected hook and command strcpy(replace.name, "filter"); replace.flags = 0; replace.selected_hook = -1; replace.command = 'h'; replace.filename = NULL; replace.counterchanges = NULL; new_entry = (struct ebt_u_entry *)malloc(sizeof(struct ebt_u_entry)); if (!new_entry) print_memory(); // put some sane values in our new entry initialize_entry(new_entry); // The scenario induced by this loop makes that: // '-t' ,'-M' and --atomic (if specified) have to come // before '-A' and the like // getopt saves the day while ((c = getopt_long(argc, argv, "-A:D:I:N:E:X:L::Z::F::P:Vhi:o:j:p:b:s:d:t:M:", ebt_options, NULL)) != -1) { switch (c) { case 'A': // add a rule case 'D': // delete a rule case 'P': // define policy case 'I': // insert a rule case 'N': // make a user defined chain case 'E': // rename chain case 'X': // delete chain replace.command = c; if (replace.flags & OPT_COMMAND) print_error("Multiple commands not allowed"); replace.flags |= OPT_COMMAND; get_kernel_table(modprobe); if (optarg[0] == '-' || !strcmp(optarg, "!")) print_error("No chain name specified"); if (c == 'N') { struct ebt_u_chain_list *cl, **cl2; if (get_hooknr(optarg) != -1) print_error("Chain %s already exists", optarg); if (find_target(optarg)) print_error("Target with name %s exists" , optarg); if (strlen(optarg) >= EBT_CHAIN_MAXNAMELEN) print_error("Chain name length can't exceed %d", EBT_CHAIN_MAXNAMELEN - 1); cl = (struct ebt_u_chain_list *) malloc(sizeof(struct ebt_u_chain_list)); if (!cl) print_memory(); cl->next = NULL; cl->udc = (struct ebt_u_entries *) malloc(sizeof(struct ebt_u_entries)); if (!cl->udc) print_memory(); cl->udc->nentries = 0; cl->udc->policy = EBT_ACCEPT; cl->udc->counter_offset = replace.nentries; cl->udc->hook_mask = 0; strcpy(cl->udc->name, optarg); cl->udc->entries = NULL; cl->kernel_start = NULL; // put the new chain at the end cl2 = &replace.udc; while (*cl2) cl2 = &((*cl2)->next); *cl2 = cl; break; } if ((replace.selected_hook = get_hooknr(optarg)) == -1) print_error("Chain %s doesn't exist", optarg); if (c == 'E') { if (optind >= argc || argv[optind][0] == '-' || !strcmp(argv[optind], "!")) print_error("No new chain name specified"); if (strlen(argv[optind]) >= EBT_CHAIN_MAXNAMELEN) print_error("Chain name len can't exceed %d", EBT_CHAIN_MAXNAMELEN - 1); if (get_hooknr(argv[optind]) != -1) print_error("Chain %s already exists", argv[optind]); if (find_target(argv[optind])) print_error("Target with name %s exists" , argv[optind]); entries = to_chain(); strcpy(entries->name, argv[optind]); optind++; break; } if (c == 'X') { struct ebt_u_chain_list *cl, **cl2; if (replace.selected_hook < NF_BR_NUMHOOKS) print_error("You can't remove a standard chain"); // if the chain is referenced, don't delete it check_for_references(replace.selected_hook - NF_BR_NUMHOOKS); flush_chains(); entries = to_chain(); cl2 = &(replace.udc); while ((*cl2)->udc != entries) cl2 = &((*cl2)->next); cl = (*cl2); (*cl2) = (*cl2)->next; free(cl->udc); free(cl); break; } if ( (c == 'D' && optind < argc && argv[optind][0] != '-') || c == 'I') { if (optind >= argc || argv[optind][0] == '-') print_error("No rulenr for -I" " specified"); rule_nr = strtol(argv[optind], &buffer, 10); if (*buffer != '\0' || rule_nr < 0) print_error("Problem with the " "specified rule number"); optind++; } if (c == 'P') { if (optind >= argc) print_error("No policy specified"); policy = 0; for (i = 0; i < NUM_STANDARD_TARGETS; i++) if (!strcmp(argv[optind], standard_targets[i])) { policy = -i -1; if (policy == EBT_CONTINUE) policy = 0; break; } if (policy == 0) print_error("Wrong policy"); optind++; } break; case 'L': // list case 'F': // flush case 'Z': // zero counters if (c == 'Z') { if (replace.flags & OPT_ZERO) print_error("Multiple commands" " not allowed"); if ( (replace.flags & OPT_COMMAND && replace.command != 'L')) print_error("command -Z only allowed " "together with command -L"); replace.flags |= OPT_ZERO; } else { replace.command = c; if (replace.flags & OPT_COMMAND) print_error("Multiple commands" " not allowed"); replace.flags |= OPT_COMMAND; } get_kernel_table(modprobe); i = -1; if (optarg) { if ( (i = get_hooknr(optarg)) == -1 ) print_error("Bad chain"); } else if (optind < argc && argv[optind][0] != '-') { if ((i = get_hooknr(argv[optind])) == -1) print_error("Bad chain"); optind++; } if (i != -1) { if (c == 'Z') zerochain = i; else replace.selected_hook = i; } break; case 'V': // version replace.command = 'V'; if (replace.flags & OPT_COMMAND) print_error("Multiple commands not allowed"); printf("%s, %s\n", prog_name, prog_version); exit(0); case 'M': // modprobe if (replace.command != 'h') print_error("Please put the -M option earlier"); modprobe = optarg; break; case 'h': // help if (replace.flags & OPT_COMMAND) print_error("Multiple commands not allowed"); replace.command = 'h'; // All other arguments should be extension names while (optind < argc) { struct ebt_u_match *m; struct ebt_u_watcher *w; if ((m = find_match(argv[optind]))) add_match(m); else if ((w = find_watcher(argv[optind]))) add_watcher(w); else { if (!(t = find_target(argv[optind]))) print_error("Extension %s " "not found", argv[optind]); if (replace.flags & OPT_JUMP) print_error("Sorry, you can " "only see help for one " "target extension each time"); replace.flags |= OPT_JUMP; new_entry->t = (struct ebt_entry_target *)t; } optind++; } break; case 't': // table if (replace.command != 'h') print_error("Please put the -t option first"); check_option(&replace.flags, OPT_TABLE); if (strlen(optarg) > EBT_TABLE_MAXNAMELEN - 1) print_error("Table name too long"); strcpy(replace.name, optarg); break; case 'i': // input interface case 2 : // logical input interface case 'o': // output interface case 3 : // logical output interface case 'j': // target case 'p': // net family protocol case 's': // source mac case 'd': // destination mac if ((replace.flags & OPT_COMMAND) == 0) print_error("No command specified"); if ( replace.command != 'A' && replace.command != 'D' && replace.command != 'I') print_error("Command and option do not match"); if (c == 'i') { check_option(&replace.flags, OPT_IN); if (replace.selected_hook > 2 && replace.selected_hook < NF_BR_BROUTING) print_error("Use in-interface only in " "INPUT, FORWARD, PREROUTING and" "BROUTING chains"); if (check_inverse(optarg)) new_entry->invflags |= EBT_IIN; if (optind > argc) print_error("No in-interface " "specified"); if (strlen(argv[optind - 1]) >= IFNAMSIZ) print_error("Illegal interface length"); strcpy(new_entry->in, argv[optind - 1]); break; } if (c == 2) { check_option(&replace.flags, OPT_LOGICALIN); if (replace.selected_hook > 2 && replace.selected_hook < NF_BR_BROUTING) print_error("Use logical in-interface " "only in INPUT, FORWARD, " "PREROUTING and BROUTING chains"); if (check_inverse(optarg)) new_entry->invflags |= EBT_ILOGICALIN; if (optind > argc) print_error("No logical in-interface " "specified"); if (strlen(argv[optind - 1]) >= IFNAMSIZ) print_error("Illegal interface length"); strcpy(new_entry->logical_in, argv[optind - 1]); break; } if (c == 'o') { check_option(&replace.flags, OPT_OUT); if (replace.selected_hook < 2) print_error("Use out-interface only" " in OUTPUT, FORWARD and " "POSTROUTING chains"); if (check_inverse(optarg)) new_entry->invflags |= EBT_IOUT; if (optind > argc) print_error("No out-interface " "specified"); if (strlen(argv[optind - 1]) >= IFNAMSIZ) print_error("Illegal interface " "length"); strcpy(new_entry->out, argv[optind - 1]); break; } if (c == 3) { check_option(&replace.flags, OPT_LOGICALOUT); if (replace.selected_hook < 2) print_error("Use logical out-interface " "only in OUTPUT, FORWARD and " "POSTROUTING chains"); if (check_inverse(optarg)) new_entry->invflags |= EBT_ILOGICALOUT; if (optind > argc) print_error("No logical out-interface " "specified"); if (strlen(argv[optind - 1]) >= IFNAMSIZ) print_error("Illegal interface " "length"); strcpy(new_entry->logical_out, argv[optind - 1]); break; } if (c == 'j') { check_option(&replace.flags, OPT_JUMP); for (i = 0; i < NUM_STANDARD_TARGETS; i++) if (!strcmp(optarg, standard_targets[i])) { t = find_target( EBT_STANDARD_TARGET); ((struct ebt_standard_target *) t->t)->verdict = -i - 1; break; } if (-i - 1 == EBT_RETURN) { if (replace.selected_hook < NF_BR_NUMHOOKS) print_error("Return target" " only for user defined chains"); } if (i != NUM_STANDARD_TARGETS) break; if ((i = get_hooknr(optarg)) != -1) { if (i < NF_BR_NUMHOOKS) print_error("don't jump" " to a standard chain"); t = find_target( EBT_STANDARD_TARGET); ((struct ebt_standard_target *) t->t)->verdict = i - NF_BR_NUMHOOKS; break; } else { // must be an extension then struct ebt_u_target *t; t = find_target(optarg); // -j standard not allowed either if (!t || t == (struct ebt_u_target *)new_entry->t) print_error("Illegal target " "name"); new_entry->t = (struct ebt_entry_target *)t; } break; } if (c == 's') { check_option(&replace.flags, OPT_SOURCE); if (check_inverse(optarg)) new_entry->invflags |= EBT_ISOURCE; if (optind > argc) print_error("No source mac " "specified"); if (getmac_and_mask(argv[optind - 1], new_entry->sourcemac, new_entry->sourcemsk)) print_error("Problem with specified " "source mac"); new_entry->bitmask |= EBT_SOURCEMAC; break; } if (c == 'd') { check_option(&replace.flags, OPT_DEST); if (check_inverse(optarg)) new_entry->invflags |= EBT_IDEST; if (optind > argc) print_error("No destination mac " "specified"); if (getmac_and_mask(argv[optind - 1], new_entry->destmac, new_entry->destmsk)) print_error("Problem with specified " "destination mac"); new_entry->bitmask |= EBT_DESTMAC; break; } check_option(&replace.flags, OPT_PROTOCOL); if (check_inverse(optarg)) new_entry->invflags |= EBT_IPROTO; if (optind > argc) print_error("No protocol specified"); new_entry->bitmask &= ~((unsigned int)EBT_NOPROTO); i = strtol(argv[optind - 1], &buffer, 16); if (*buffer == '\0' && (i < 0 || i > 0xFFFF)) print_error("Problem with the specified " "protocol"); new_entry->ethproto = i; if (*buffer != '\0') { if ((i = name_to_number(argv[optind - 1], &new_entry->ethproto)) == -1) print_error("Problem with the specified" " protocol"); if (i == 1) new_entry->bitmask |= EBT_802_3; } if (new_entry->ethproto < 1536 && !(new_entry->bitmask & EBT_802_3)) print_error("Sorry, protocols have values above" " or equal to 0x0600"); break; case 'b': // allow database? if (replace.flags & OPT_COMMAND) print_error("Multiple commands not allowed"); replace.command = c; allowbc = *optarg; break; case 4 : // Lc check_option(&replace.flags, LIST_C); if (replace.selected_hook == DATABASEHOOKNR) print_error("--Lc not valid for listing" " the database"); if (replace.command != 'L') print_error("Use --Lc with -L"); if (replace.flags & LIST_X) print_error("--Lx not compatible with --Lc"); replace.flags |= LIST_C; break; case 5 : // Ln check_option(&replace.flags, LIST_N); if (replace.selected_hook == DATABASEHOOKNR) print_error("--Ln not valid for listing" " the database"); if (replace.command != 'L') print_error("Use --Ln with -L"); if (replace.flags & LIST_X) print_error("--Lx not compatible with --Ln"); replace.flags |= LIST_N; break; case 6 : // Lx check_option(&replace.flags, LIST_X); if (replace.selected_hook == DATABASEHOOKNR) print_error("--Lx not valid for listing" " the database"); if (replace.command != 'L') print_error("Use --Lx with -L"); if (replace.flags & LIST_C) print_error("--Lx not compatible with --Lc"); if (replace.flags & LIST_N) print_error("--Lx not compatible with --Ln"); replace.flags |= LIST_X; break; case 8 : // atomic-commit replace.command = c; if (replace.flags & OPT_COMMAND) print_error("Multiple commands not allowed"); replace.flags |= OPT_COMMAND; if (replace.filename) print_error("--atomic incompatible with " "command"); replace.filename = (char *)malloc(strlen(optarg) + 1); strcpy(replace.filename, optarg); // get the information from the file get_table(&replace); if (replace.nentries) { replace.counterchanges = (unsigned short *) malloc(sizeof(unsigned short) * (replace.nentries + 1)); for (i = 0; i < replace.nentries; i++) replace.counterchanges[i] = CNT_NORM; replace.counterchanges[i] = CNT_END; } // we don't want the kernel giving us its counters, they would // overwrite the counters extracted from the file replace.num_counters = 0; // make sure the table will be written to the kernel free(replace.filename); replace.filename = NULL; break; case 7 : // atomic-init case 10: // atomic-save case 11: // init-table replace.command = c; if (replace.flags & OPT_COMMAND) print_error("Multiple commands not allowed"); replace.flags |= OPT_COMMAND; if (replace.filename) print_error("--atomic incompatible with " "command"); get_kernel_table(modprobe); if (replace.nentries) { replace.counterchanges = (unsigned short *) malloc(sizeof(unsigned short) * (replace.nentries + 1)); for (i = 0; i < replace.nentries; i++) replace.counterchanges[i] = CNT_NORM; replace.counterchanges[i] = CNT_END; } if (c == 11) break; case 9 : // atomic if (c == 9 && (replace.flags & OPT_COMMAND)) print_error("--atomic has to come before" " the command"); replace.filename = (char *)malloc(strlen(optarg) + 1); strcpy(replace.filename, optarg); break; default: // is it a target option? t = (struct ebt_u_target *)new_entry->t; if ((t->parse(c - t->option_offset, argv, argc, new_entry, &t->flags, &t->t))) goto check_extension; // is it a match_option? for (m = matches; m; m = m->next) if (m->parse(c - m->option_offset, argv, argc, new_entry, &m->flags, &m->m)) break; if (m != NULL) { if (m->used == 0) add_match(m); goto check_extension; } // is it a watcher option? for (w = watchers; w; w = w->next) if (w->parse(c-w->option_offset, argv, argc, new_entry, &w->flags, &w->w)) break; if (w == NULL) print_error("Unknown argument"); if (w->used == 0) add_watcher(w); check_extension: if (replace.command != 'A' && replace.command != 'I' && replace.command != 'D') print_error("Extensions only for -A, -I and -D"); } } if ( !table && !(table = find_table(replace.name)) ) print_error("Bad table name"); // database stuff before ebtables stuff if (replace.command == 'b') allowdb(allowbc); if (replace.command == 'L' && replace.selected_hook == DATABASEHOOKNR) list_db(); if ( (replace.flags & OPT_COMMAND) && replace.command != 'L' && replace.flags & OPT_ZERO ) print_error("Command -Z only allowed together with command -L"); // do this after parsing everything, so we can print specific info if (replace.command == 'h' && !(replace.flags & OPT_ZERO)) print_help(); // do the final checks if (replace.command == 'A' || replace.command == 'I' || replace.command == 'D') { // this will put the hook_mask right for the chains check_for_loops(); entries = to_chain(); m_l = new_entry->m_list; w_l = new_entry->w_list; t = (struct ebt_u_target *)new_entry->t; while (m_l) { m = (struct ebt_u_match *)(m_l->m); m->final_check(new_entry, m->m, replace.name, entries->hook_mask, 0); m_l = m_l->next; } while (w_l) { w = (struct ebt_u_watcher *)(w_l->w); w->final_check(new_entry, w->w, replace.name, entries->hook_mask, 0); w_l = w_l->next; } t->final_check(new_entry, t->t, replace.name, entries->hook_mask, 0); } // so, the extensions can work with the host endian // the kernel does not have to do this ofcourse new_entry->ethproto = htons(new_entry->ethproto); if (replace.command == 'P') change_policy(policy); else if (replace.command == 'L') { list_rules(); if (replace.flags & OPT_ZERO) zero_counters(zerochain); else exit(0); } if (replace.flags & OPT_ZERO) zero_counters(zerochain); else if (replace.command == 'F') { if (flush_chains() == -1) exit(0); } else if (replace.command == 'A' || replace.command == 'I') { add_rule(rule_nr); check_for_loops(); // do the final_check(), for all entries // needed when adding a rule that has a chain target i = -1; while (1) { struct ebt_u_entry *e; i++; entries = nr_to_chain(i); if (!entries) { if (i < NF_BR_NUMHOOKS) continue; else break; } e = entries->entries; while (e) { // userspace extensions use host endian e->ethproto = ntohs(e->ethproto); do_final_checks(e, entries); e->ethproto = htons(e->ethproto); e = e->next; } } } else if (replace.command == 'D') delete_rule(rule_nr); // commands -N, -E, -X, --atomic-commit, --atomic-commit, --atomic-save, // --init-table fall through if (table->check) table->check(&replace); deliver_table(&replace); if (replace.counterchanges) deliver_counters(&replace); return 0; }