X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=ip6tables.c;h=f316c7a99cfef6232e9009ccd4d26b67e9ebcdbe;hb=refs%2Fheads%2Fnetfilter;hp=6afe68fd841c06b5a1bde7473ec359718154228e;hpb=34d283f916eeef73728a99668e40754f3961f580;p=iptables.git diff --git a/ip6tables.c b/ip6tables.c index 6afe68f..f316c7a 100644 --- a/ip6tables.c +++ b/ip6tables.c @@ -81,8 +81,7 @@ #define CMD_NEW_CHAIN 0x0100U #define CMD_DELETE_CHAIN 0x0200U #define CMD_SET_POLICY 0x0400U -#define CMD_CHECK 0x0800U -#define CMD_RENAME_CHAIN 0x1000U +#define CMD_RENAME_CHAIN 0x0800U #define NUMBER_OF_CMD 13 static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z', 'N', 'X', 'P', 'E' }; @@ -159,20 +158,19 @@ static unsigned int global_option_offset = 0; static char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT] = /* Well, it's better than "Re: Linux vs FreeBSD" */ { - /* -n -s -d -p -j -v -x -i -o --line */ -/*INSERT*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x'}, -/*DELETE*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x'}, -/*DELETE_NUM*/{'x','x','x','x','x',' ','x','x','x','x'}, -/*REPLACE*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x'}, -/*APPEND*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x'}, -/*LIST*/ {' ','x','x','x','x',' ',' ','x','x',' '}, -/*FLUSH*/ {'x','x','x','x','x',' ','x','x','x','x'}, -/*ZERO*/ {'x','x','x','x','x',' ','x','x','x','x'}, -/*NEW_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x'}, -/*DEL_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x'}, -/*SET_POLICY*/{'x','x','x','x','x',' ','x','x','x','x'}, -/*CHECK*/ {'x','+','+','+','x',' ','x',' ',' ','x'}, -/*RENAME*/ {'x','x','x','x','x',' ','x','x','x','x'} + /* -n -s -d -p -j -v -x -i -o --line -c */ +/*INSERT*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' '}, +/*DELETE*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x','x'}, +/*DELETE_NUM*/{'x','x','x','x','x',' ','x','x','x','x','x'}, +/*REPLACE*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' '}, +/*APPEND*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' '}, +/*LIST*/ {' ','x','x','x','x',' ',' ','x','x',' ','x'}, +/*FLUSH*/ {'x','x','x','x','x',' ','x','x','x','x','x'}, +/*ZERO*/ {'x','x','x','x','x',' ','x','x','x','x','x'}, +/*NEW_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x'}, +/*DEL_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x'}, +/*SET_POLICY*/{'x','x','x','x','x',' ','x','x','x','x','x'}, +/*RENAME*/ {'x','x','x','x','x',' ','x','x','x','x','x'} }; static int inverse_for_options[NUMBER_OF_OPT] = @@ -186,13 +184,17 @@ static int inverse_for_options[NUMBER_OF_OPT] = /* -x */ 0, /* -i */ IP6T_INV_VIA_IN, /* -o */ IP6T_INV_VIA_OUT, -/*--line*/ 0 +/*--line*/ 0, +/* -c */ 0, }; const char *program_version; const char *program_name; char *lib_dir; +/* the path to command to load kernel module */ +const char *modprobe = NULL; + /* Keeping track of external matches and targets: linked lists. */ struct ip6tables_match *ip6tables_matches = NULL; struct ip6tables_target *ip6tables_targets = NULL; @@ -217,13 +219,20 @@ struct pprot { #define IPPROTO_AH 51 #endif #endif +#ifndef IPPROTO_MH +#define IPPROTO_MH 135 +#endif static const struct pprot chain_protos[] = { { "tcp", IPPROTO_TCP }, { "udp", IPPROTO_UDP }, + { "udplite", IPPROTO_UDPLITE }, { "icmpv6", IPPROTO_ICMPV6 }, + { "ipv6-icmp", IPPROTO_ICMPV6 }, { "esp", IPPROTO_ESP }, { "ah", IPPROTO_AH }, + { "ipv6-mh", IPPROTO_MH }, + { "mh", IPPROTO_MH }, { "all", 0 }, }; @@ -245,6 +254,30 @@ proto_to_name(u_int8_t proto, int nolookup) return NULL; } +int +service_to_port(const char *name, const char *proto) +{ + struct servent *service; + + if ((service = getservbyname(name, proto)) != NULL) + return ntohs((unsigned short) service->s_port); + + return -1; +} + +u_int16_t +parse_port(const char *port, const char *proto) +{ + unsigned int portnum; + + if ((string_to_number(port, 0, 65535, &portnum)) != -1 || + (portnum = service_to_port(port, proto)) != -1) + return (u_int16_t)portnum; + + exit_error(PARAMETER_PROBLEM, + "invalid port/service `%s' specified", port); +} + static void in6addrcpy(struct in6_addr *dst, struct in6_addr *src) { @@ -710,37 +743,46 @@ parse_hostnetworkmask(const char *name, struct in6_addr **addrpp, } struct ip6tables_match * -find_match(const char *name, enum ip6t_tryload tryload, struct ip6tables_rule_match **matches) +find_match(const char *match_name, enum ip6t_tryload tryload, struct ip6tables_rule_match **matches) { struct ip6tables_match *ptr; - int icmphack = 0; + const char *icmp6 = "icmp6"; + const char *name; /* This is ugly as hell. Nonetheless, there is no way of changing * this without hurting backwards compatibility */ - if ( (strcmp(name,"icmpv6") == 0) || - (strcmp(name,"ipv6-icmp") == 0) || - (strcmp(name,"icmp6") == 0) ) icmphack = 1; + if ( (strcmp(match_name,"icmpv6") == 0) || + (strcmp(match_name,"ipv6-icmp") == 0) || + (strcmp(match_name,"icmp6") == 0) ) + name = icmp6; + else + name = match_name; - if (!icmphack) { - for (ptr = ip6tables_matches; ptr; ptr = ptr->next) { - if (strcmp(name, ptr->name) == 0) - break; - } - } else { - for (ptr = ip6tables_matches; ptr; ptr = ptr->next) { - if (strcmp("icmp6", ptr->name) == 0) - break; - } - } + for (ptr = ip6tables_matches; ptr; ptr = ptr->next) { + if (strcmp(name, ptr->name) == 0) { + struct ip6tables_match *clone; + + /* First match of this type: */ + if (ptr->m == NULL) + break; + + /* Second and subsequent clones */ + clone = fw_malloc(sizeof(struct ip6tables_match)); + memcpy(clone, ptr, sizeof(struct ip6tables_match)); + clone->mflags = 0; + /* This is a clone: */ + clone->next = clone; + + ptr = clone; + break; + } + } #ifndef NO_SHARED_LIBS if (!ptr && tryload != DONT_LOAD && tryload != DURING_LOAD) { char path[strlen(lib_dir) + sizeof("/libip6t_.so") + strlen(name)]; - if (!icmphack) - sprintf(path, "%s/libip6t_%s.so", lib_dir, name); - else - sprintf(path, "%s/libip6t_%s.so", lib_dir, "icmpv6"); + sprintf(path, "%s/libip6t_%s.so", lib_dir, name); if (dlopen(path, RTLD_NOW)) { /* Found library. If it didn't register itself, maybe they specified target as match. */ @@ -774,8 +816,12 @@ find_match(const char *name, enum ip6t_tryload tryload, struct ip6tables_rule_ma newentry = fw_malloc(sizeof(struct ip6tables_rule_match)); - for (i = matches; *i; i = &(*i)->next); + for (i = matches; *i; i = &(*i)->next) { + if (strcmp(name, (*i)->match->name) == 0) + (*i)->completed = 1; + } newentry->match = ptr; + newentry->completed = 0; newentry->next = NULL; *i = newentry; } @@ -808,6 +854,13 @@ parse_protocol(const char *s) if (string_to_number(s, 0, 255, &proto) == -1) { struct protoent *pent; + /* first deal with the special case of 'all' to prevent + * people from being able to redefine 'all' in nsswitch + * and/or provoke expensive [not working] ldap/nis/... + * lookups */ + if (!strcmp(s, "all")) + return 0; + if ((pent = getprotobyname(s))) proto = pent->p_proto; else { @@ -830,6 +883,15 @@ parse_protocol(const char *s) return (u_int16_t)proto; } +/* These are invalid numbers as upper layer protocol */ +static int is_exthdr(u_int16_t proto) +{ + return (proto == IPPROTO_ROUTING || + proto == IPPROTO_FRAGMENT || + proto == IPPROTO_AH || + proto == IPPROTO_DSTOPTS); +} + void parse_interface(const char *arg, char *vianame, unsigned char *mask) { int vialen = strlen(arg); @@ -855,10 +917,10 @@ void parse_interface(const char *arg, char *vianame, unsigned char *mask) memset(mask, 0xFF, vialen + 1); memset(mask + vialen + 1, 0, IFNAMSIZ - vialen - 1); for (i = 0; vianame[i]; i++) { - if (!isalnum(vianame[i]) - && vianame[i] != '_' - && vianame[i] != '.') { - printf("Warning: wierd character in interface" + if (vianame[i] == ':' || + vianame[i] == '!' || + vianame[i] == '*') { + printf("Warning: weird character in interface" " `%s' (No aliases, :, ! or *).\n", vianame); break; @@ -1047,10 +1109,53 @@ merge_options(struct option *oldopts, const struct option *newopts, return merge; } +static int compatible_revision(const char *name, u_int8_t revision, int opt) +{ + struct ip6t_get_revision rev; + socklen_t s = sizeof(rev); + int max_rev, sockfd; + + sockfd = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW); + if (sockfd < 0) { + fprintf(stderr, "Could not open socket to kernel: %s\n", + strerror(errno)); + exit(1); + } + + strcpy(rev.name, name); + rev.revision = revision; + + load_ip6tables_ko(modprobe, 1); + + max_rev = getsockopt(sockfd, IPPROTO_IPV6, opt, &rev, &s); + if (max_rev < 0) { + /* Definitely don't support this? */ + if (errno == EPROTONOSUPPORT) { + close(sockfd); + return 0; + } else if (errno == ENOPROTOOPT) { + close(sockfd); + /* Assume only revision 0 support (old kernel) */ + return (revision == 0); + } else { + fprintf(stderr, "getsockopt failed strangely: %s\n", + strerror(errno)); + exit(1); + } + } + close(sockfd); + return 1; +} + +static int compatible_match_revision(const char *name, u_int8_t revision) +{ + return compatible_revision(name, revision, IP6T_SO_GET_REVISION_MATCH); +} + void register_match6(struct ip6tables_match *me) { - struct ip6tables_match **i; + struct ip6tables_match **i, *old; if (strcmp(me->version, program_version) != 0) { fprintf(stderr, "%s: match `%s' v%s (I'm v%s).\n", @@ -1058,12 +1163,36 @@ register_match6(struct ip6tables_match *me) exit(1); } - if (find_match(me->name, DURING_LOAD, NULL)) { - fprintf(stderr, "%s: match `%s' already registered.\n", + /* Revision field stole a char from name. */ + if (strlen(me->name) >= IP6T_FUNCTION_MAXNAMELEN-1) { + fprintf(stderr, "%s: target `%s' has invalid name\n", program_name, me->name); exit(1); } + old = find_match(me->name, DURING_LOAD, NULL); + if (old) { + if (old->revision == me->revision) { + fprintf(stderr, + "%s: match `%s' already registered.\n", + program_name, me->name); + exit(1); + } + + /* Now we have two (or more) options, check compatibility. */ + if (compatible_match_revision(old->name, old->revision) + && old->revision > me->revision) + return; + + /* Replace if compatible. */ + if (!compatible_match_revision(me->name, me->revision)) + return; + + /* Delete old one. */ + for (i = &ip6tables_matches; *i!=old; i = &(*i)->next); + *i = old->next; + } + if (me->size != IP6T_ALIGN(me->size)) { fprintf(stderr, "%s: match `%s' has invalid size %u.\n", program_name, me->name, (unsigned int)me->size); @@ -1621,10 +1750,10 @@ static char *get_modprobe(void) return NULL; } -int ip6tables_insmod(const char *modname, const char *modprobe) +int ip6tables_insmod(const char *modname, const char *modprobe, int quiet) { char *buf = NULL; - char *argv[3]; + char *argv[4]; int status; /* If they don't explicitly set it, read out of kernel */ @@ -1639,7 +1768,13 @@ int ip6tables_insmod(const char *modname, const char *modprobe) case 0: argv[0] = (char *)modprobe; argv[1] = (char *)modname; - argv[2] = NULL; + if (quiet) { + argv[2] = "-q"; + argv[3] = NULL; + } else { + argv[2] = NULL; + argv[3] = NULL; + } execv(argv[0], argv); /* not usually reached */ @@ -1657,6 +1792,19 @@ int ip6tables_insmod(const char *modname, const char *modprobe) return -1; } +int load_ip6tables_ko(const char *modprobe, int quiet) +{ + static int loaded = 0; + static int ret = -1; + + if (!loaded) { + ret = ip6tables_insmod("ip6_tables", modprobe, quiet); + loaded = (ret == 0); + } + + return ret; +} + static struct ip6t_entry * generate_entry(const struct ip6t_entry *fw, struct ip6tables_rule_match *matches, @@ -1691,8 +1839,14 @@ void clear_rule_matches(struct ip6tables_rule_match **matches) for (matchp = *matches; matchp;) { tmp = matchp->next; - if (matchp->match->m) + if (matchp->match->m) { free(matchp->match->m); + matchp->match->m = NULL; + } + if (matchp->match == matchp->match->next) { + free(matchp->match); + matchp->match = NULL; + } free(matchp); matchp = tmp; } @@ -1700,6 +1854,14 @@ void clear_rule_matches(struct ip6tables_rule_match **matches) *matches = NULL; } +static void set_revision(char *name, u_int8_t revision) +{ + /* Old kernel sources don't have ".revision" field, + but we stole a byte from name. */ + name[IP6T_FUNCTION_MAXNAMELEN - 2] = '\0'; + name[IP6T_FUNCTION_MAXNAMELEN - 1] = revision; +} + int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle) { struct ip6t_entry fw, *e = NULL; @@ -1721,9 +1883,7 @@ int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle) struct ip6tables_target *t; const char *jumpto = ""; char *protocol = NULL; - const char *modprobe = NULL; int proto_used = 0; - char icmp6p[] = "icmpv6"; memset(&fw, 0, sizeof(fw)); @@ -1851,7 +2011,7 @@ int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle) newname = argv[optind++]; else exit_error(PARAMETER_PROBLEM, - "-%c requires old-chain-name and " + "-%c requires old-chain-name and " "new-chain-name", cmd2char(CMD_RENAME_CHAIN)); break; @@ -1892,8 +2052,6 @@ int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle) *protocol = tolower(*protocol); protocol = argv[optind-1]; - if ( strcmp(protocol,"ipv6-icmp") == 0) - protocol = icmp6p; fw.ipv6.proto = parse_protocol(protocol); fw.ipv6.flags |= IP6T_F_PROTO; @@ -1901,6 +2059,12 @@ int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle) && (fw.ipv6.invflags & IP6T_INV_PROTO)) exit_error(PARAMETER_PROBLEM, "rule would never match protocol"); + + if (is_exthdr(fw.ipv6.proto) + && (fw.ipv6.invflags & IP6T_INV_PROTO) == 0) + printf("Warning: never matched protocol: %s. " + "use extension match instead.\n", + protocol); break; case 's': @@ -1978,9 +2142,12 @@ int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle) m->m = fw_calloc(1, size); m->m->u.match_size = size; strcpy(m->m->u.user.name, m->name); + set_revision(m->m->u.user.name, m->revision); if (m->init != NULL) m->init(m->m, &fw.nfcache); - opts = merge_options(opts, m->extra_opts, &m->option_offset); + if (m != m->next) + /* Merge options for non-cloned matches */ + opts = merge_options(opts, m->extra_opts, &m->option_offset); } break; @@ -2058,14 +2225,14 @@ int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle) exit_tryhelp(2); default: - /* FIXME: This scheme doesn't allow two of the same - matches --RR */ if (!target || !(target->parse(c - target->option_offset, argv, invert, &target->tflags, &fw, &target->t))) { for (matchp = matches; matchp; matchp = matchp->next) { + if (matchp->completed) + continue; if (matchp->match->parse(c - matchp->match->option_offset, argv, invert, &matchp->match->mflags, @@ -2080,7 +2247,7 @@ int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle) actually hear this code suck. */ /* some explanations (after four different bugs - * in 3 different releases): If we encountere a + * in 3 different releases): If we encounter a * parameter, that has not been parsed yet, * it's not an option of an explicitly loaded * match or a target. However, we support @@ -2120,6 +2287,8 @@ int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle) m->m = fw_calloc(1, size); m->m->u.match_size = size; strcpy(m->m->u.user.name, m->name); + set_revision(m->m->u.user.name, + m->revision); if (m->init != NULL) m->init(m->m, &fw.nfcache); @@ -2192,7 +2361,7 @@ int do_command6(int argc, char *argv[], char **table, ip6tc_handle_t *handle) *handle = ip6tc_init(*table); /* try to insmod the module if iptc_init failed */ - if (!*handle && ip6tables_insmod("ip6_tables", modprobe) != -1) + if (!*handle && load_ip6tables_ko(modprobe, 0) != -1) *handle = ip6tc_init(*table); if (!*handle)