From 3aef54dce4f9bbe0b466478fd33a1d3131efbbb8 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 3 Jan 2005 03:48:40 +0000 Subject: Extension revision number support (if kernel supports the getsockopts). Enhance MARK match with second revision. Committed in anticipation of the kernel patch being applied. --- extensions/libipt_MARK.c | 140 ++++++++++++++++++++++++++++---- include/iptables.h | 18 ++++ include/linux/netfilter_ipv4/ipt_MARK.h | 15 ++++ iptables.c | 125 ++++++++++++++++++++++++++-- libiptc/libiptc.c | 4 +- 5 files changed, 281 insertions(+), 21 deletions(-) diff --git a/extensions/libipt_MARK.c b/extensions/libipt_MARK.c index bc3dd42e..457f6ad3 100644 --- a/extensions/libipt_MARK.c +++ b/extensions/libipt_MARK.c @@ -16,12 +16,16 @@ help(void) printf( "MARK target v%s options:\n" " --set-mark value Set nfmark value\n" +" --and-mark value Binary AND the nfmark with value\n" +" --or-mark value Binary OR the nfmark with value\n" "\n", IPTABLES_VERSION); } static struct option opts[] = { { "set-mark", 1, 0, '1' }, + { "and-mark", 1, 0, '2' }, + { "or-mark", 1, 0, '3' }, { 0 } }; @@ -34,9 +38,9 @@ init(struct ipt_entry_target *t, unsigned int *nfcache) /* Function which parses command options; returns true if it ate an option */ static int -parse(int c, char **argv, int invert, unsigned int *flags, - const struct ipt_entry *entry, - struct ipt_entry_target **target) +parse_v0(int c, char **argv, int invert, unsigned int *flags, + const struct ipt_entry *entry, + struct ipt_entry_target **target) { struct ipt_mark_target_info *markinfo = (struct ipt_mark_target_info *)(*target)->data; @@ -56,7 +60,12 @@ parse(int c, char **argv, int invert, unsigned int *flags, "MARK target: Can't specify --set-mark twice"); *flags = 1; break; - + case '2': + exit_error(PARAMETER_PROBLEM, + "MARK target: kernel too old for --and-mark"); + case '3': + exit_error(PARAMETER_PROBLEM, + "MARK target: kernel too old for --or-mark"); default: return 0; } @@ -69,7 +78,47 @@ final_check(unsigned int flags) { if (!flags) exit_error(PARAMETER_PROBLEM, - "MARK target: Parameter --set-mark is required"); + "MARK target: Parameter --set/and/or-mark" + " is required"); +} + +/* Function which parses command options; returns true if it + ate an option */ +static int +parse_v1(int c, char **argv, int invert, unsigned int *flags, + const struct ipt_entry *entry, + struct ipt_entry_target **target) +{ + struct ipt_mark_target_info_v1 *markinfo + = (struct ipt_mark_target_info_v1 *)(*target)->data; + + switch (c) { + case '1': + markinfo->mode = IPT_MARK_SET; + break; + case '2': + markinfo->mode = IPT_MARK_AND; + break; + case '3': + markinfo->mode = IPT_MARK_OR; + break; + default: + return 0; + } + +#ifdef KERNEL_64_USERSPACE_32 + if (string_to_number_ll(optarg, 0, 0, &markinfo->mark)) +#else + if (string_to_number_l(optarg, 0, 0, &markinfo->mark)) +#endif + exit_error(PARAMETER_PROBLEM, "Bad MARK value `%s'", optarg); + + if (*flags) + exit_error(PARAMETER_PROBLEM, + "MARK target: Can't specify --set-mark twice"); + + *flags = 1; + return 1; } #ifdef KERNEL_64_USERSPACE_32 @@ -88,9 +137,9 @@ print_mark(unsigned long mark) /* Prints out the targinfo. */ static void -print(const struct ipt_ip *ip, - const struct ipt_entry_target *target, - int numeric) +print_v0(const struct ipt_ip *ip, + const struct ipt_entry_target *target, + int numeric) { const struct ipt_mark_target_info *markinfo = (const struct ipt_mark_target_info *)target->data; @@ -100,7 +149,7 @@ print(const struct ipt_ip *ip, /* Saves the union ipt_targinfo in parsable form to stdout. */ static void -save(const struct ipt_ip *ip, const struct ipt_entry_target *target) +save_v0(const struct ipt_ip *ip, const struct ipt_entry_target *target) { const struct ipt_mark_target_info *markinfo = (const struct ipt_mark_target_info *)target->data; @@ -109,23 +158,86 @@ save(const struct ipt_ip *ip, const struct ipt_entry_target *target) print_mark(markinfo->mark); } +/* Prints out the targinfo. */ +static void +print_v1(const struct ipt_ip *ip, + const struct ipt_entry_target *target, + int numeric) +{ + const struct ipt_mark_target_info_v1 *markinfo = + (const struct ipt_mark_target_info_v1 *)target->data; + + switch (markinfo->mode) { + case IPT_MARK_SET: + printf("MARK set "); + break; + case IPT_MARK_AND: + printf("MARK and "); + break; + case IPT_MARK_OR: + printf("MARK or "); + break; + } + print_mark(markinfo->mark); +} + +/* Saves the union ipt_targinfo in parsable form to stdout. */ +static void +save_v1(const struct ipt_ip *ip, const struct ipt_entry_target *target) +{ + const struct ipt_mark_target_info_v1 *markinfo = + (const struct ipt_mark_target_info_v1 *)target->data; + + switch (markinfo->mode) { + case IPT_MARK_SET: + printf("--set-mark "); + break; + case IPT_MARK_AND: + printf("--and-mark "); + break; + case IPT_MARK_OR: + printf("--or-mark "); + break; + } + print_mark(markinfo->mark); +} + static -struct iptables_target mark = { +struct iptables_target mark_v0 = { .next = NULL, .name = "MARK", .version = IPTABLES_VERSION, + .revision = 0, .size = IPT_ALIGN(sizeof(struct ipt_mark_target_info)), .userspacesize = IPT_ALIGN(sizeof(struct ipt_mark_target_info)), .help = &help, .init = &init, - .parse = &parse, + .parse = &parse_v0, + .final_check = &final_check, + .print = &print_v0, + .save = &save_v0, + .extra_opts = opts +}; + +static +struct iptables_target mark_v1 = { + .next = NULL, + .name = "MARK", + .version = IPTABLES_VERSION, + .revision = 1, + .size = IPT_ALIGN(sizeof(struct ipt_mark_target_info_v1)), + .userspacesize = IPT_ALIGN(sizeof(struct ipt_mark_target_info_v1)), + .help = &help, + .init = &init, + .parse = &parse_v1, .final_check = &final_check, - .print = &print, - .save = &save, + .print = &print_v1, + .save = &save_v1, .extra_opts = opts }; void _init(void) { - register_target(&mark); + register_target(&mark_v0); + register_target(&mark_v1); } diff --git a/include/iptables.h b/include/iptables.h index 6d997f7c..25f36aeb 100644 --- a/include/iptables.h +++ b/include/iptables.h @@ -12,6 +12,18 @@ #define IPPROTO_SCTP 132 #endif +#ifndef IPT_SO_GET_REVISION_MATCH /* Old kernel source. */ +#define IPT_SO_GET_REVISION_MATCH (IPT_BASE_CTL + 2) +#define IPT_SO_GET_REVISION_TARGET (IPT_BASE_CTL + 3) + +struct ipt_get_revision +{ + char name[IPT_FUNCTION_MAXNAMELEN-1]; + + u_int8_t revision; +}; +#endif /* IPT_SO_GET_REVISION_MATCH Old kernel source */ + struct iptables_rule_match { struct iptables_rule_match *next; @@ -26,6 +38,9 @@ struct iptables_match ipt_chainlabel name; + /* Revision of match (0 by default). */ + u_int8_t revision; + const char *version; /* Size of match data. */ @@ -76,6 +91,9 @@ struct iptables_target ipt_chainlabel name; + /* Revision of target (0 by default). */ + u_int8_t revision; + const char *version; /* Size of target data. */ diff --git a/include/linux/netfilter_ipv4/ipt_MARK.h b/include/linux/netfilter_ipv4/ipt_MARK.h index 6febfe64..3694e488 100644 --- a/include/linux/netfilter_ipv4/ipt_MARK.h +++ b/include/linux/netfilter_ipv4/ipt_MARK.h @@ -9,4 +9,19 @@ struct ipt_mark_target_info { #endif }; +enum { + IPT_MARK_SET=0, + IPT_MARK_AND, + IPT_MARK_OR +}; + +struct ipt_mark_target_info_v1 { +#ifdef KERNEL_64_USERSPACE_32 + unsigned long long mark; +#else + unsigned long mark; +#endif + u_int8_t mode; +}; + #endif /*_IPT_MARK_H_target*/ diff --git a/iptables.c b/iptables.c index ca0a66fa..3cedaccf 100644 --- a/iptables.c +++ b/iptables.c @@ -1033,10 +1033,56 @@ 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 ipt_get_revision rev; + socklen_t s = sizeof(rev); + int max_rev, sockfd; + + sockfd = socket(AF_INET, 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; + + max_rev = getsockopt(sockfd, IPPROTO_IP, 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, IPT_SO_GET_REVISION_MATCH); +} + +static int compatible_target_revision(const char *name, u_int8_t revision) +{ + return compatible_revision(name, revision, IPT_SO_GET_REVISION_TARGET); +} + void register_match(struct iptables_match *me) { - struct iptables_match **i; + struct iptables_match **i, *old; if (strcmp(me->version, program_version) != 0) { fprintf(stderr, "%s: match `%s' v%s (I'm v%s).\n", @@ -1044,12 +1090,36 @@ register_match(struct iptables_match *me) exit(1); } - if (find_match(me->name, DONT_LOAD, NULL)) { - fprintf(stderr, "%s: match `%s' already registered.\n", + /* Revision field stole a char: check for 30 char names. */ + if (!memchr(me->name, 0, IPT_FUNCTION_MAXNAMELEN-1)) { + fprintf(stderr, "%s: target `%s' has invalid name\n", program_name, me->name); exit(1); } + old = find_match(me->name, DONT_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 = &iptables_matches; *i!=old; i = &(*i)->next); + *i = old->next; + } + if (me->size != IPT_ALIGN(me->size)) { fprintf(stderr, "%s: match `%s' has invalid size %u.\n", program_name, me->name, (unsigned int)me->size); @@ -1068,18 +1138,49 @@ register_match(struct iptables_match *me) void register_target(struct iptables_target *me) { + struct iptables_target *old; + if (strcmp(me->version, program_version) != 0) { fprintf(stderr, "%s: target `%s' v%s (I'm v%s).\n", program_name, me->name, me->version, program_version); exit(1); } - if (find_target(me->name, DONT_LOAD)) { - fprintf(stderr, "%s: target `%s' already registered.\n", + /* Revision field stole a char: check for 30 char names. */ + if (!memchr(me->name, 0, IPT_FUNCTION_MAXNAMELEN)) { + fprintf(stderr, "%s: target `%s' has invalid name\n", program_name, me->name); exit(1); } + old = find_target(me->name, DONT_LOAD); + if (old) { + struct iptables_target **i; + + if (old->revision == me->revision) { + fprintf(stderr, + "%s: target `%s' already registered.\n", + program_name, me->name); + exit(1); + } + + fprintf(stderr, "%s v%i vs v%i\n", + me->name, me->revision, old->revision); + + /* Now we have two (or more) options, check compatibility. */ + if (compatible_target_revision(old->name, old->revision) + && old->revision > me->revision) + return; + + /* Replace if compatible. */ + if (!compatible_target_revision(me->name, me->revision)) + return; + + /* Delete old one. */ + for (i = &iptables_targets; *i!=old; i = &(*i)->next); + *i = old->next; + } + if (me->size != IPT_ALIGN(me->size)) { fprintf(stderr, "%s: target `%s' has invalid size %u.\n", program_name, me->name, (unsigned int)me->size); @@ -1684,6 +1785,14 @@ void clear_rule_matches(struct iptables_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[IPT_FUNCTION_MAXNAMELEN - 2] = '\0'; + name[IPT_FUNCTION_MAXNAMELEN - 1] = revision; +} + int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle) { struct ipt_entry fw, *e = NULL; @@ -1916,6 +2025,8 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle) target->t = fw_calloc(1, size); target->t->u.target_size = size; strcpy(target->t->u.user.name, jumpto); + set_revision(target->t->u.user.name, + target->revision); target->init(target->t, &fw.nfcache); opts = merge_options(opts, target->extra_opts, &target->option_offset); } @@ -1969,6 +2080,7 @@ int do_command(int argc, char *argv[], char **table, iptc_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); m->init(m->m, &fw.nfcache); opts = merge_options(opts, m->extra_opts, &m->option_offset); } @@ -2110,6 +2222,8 @@ int do_command(int argc, char *argv[], char **table, iptc_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); m->init(m->m, &fw.nfcache); opts = merge_options(opts, @@ -2237,6 +2351,7 @@ int do_command(int argc, char *argv[], char **table, iptc_handle_t *handle) target->t = fw_calloc(1, size); target->t->u.target_size = size; strcpy(target->t->u.user.name, jumpto); + set_revision(target->t->u.user.name, target->revision); target->init(target->t, &fw.nfcache); } diff --git a/libiptc/libiptc.c b/libiptc/libiptc.c index 22b80a6b..ad0b57c4 100644 --- a/libiptc/libiptc.c +++ b/libiptc/libiptc.c @@ -1234,10 +1234,10 @@ iptcc_map_target(const TC_HANDLE_T handle, } /* Must be a module? If not, kernel will reject... */ - /* memset to all 0 for your memcmp convenience. */ + /* memset to all 0 for your memcmp convenience: don't clear version */ memset(t->u.user.name + strlen(t->u.user.name), 0, - FUNCTION_MAXNAMELEN - strlen(t->u.user.name)); + FUNCTION_MAXNAMELEN - 1 - strlen(t->u.user.name)); r->type = IPTCC_R_MODULE; set_changed(handle); return 1; -- cgit v1.2.3