| /* |
| * Shared library add-on to iptables to add IPVS matching. |
| * |
| * Detailed doc is in the kernel module source net/netfilter/xt_ipvs.c |
| * |
| * Author: Hannes Eder <heder@google.com> |
| */ |
| #include <sys/types.h> |
| #include <assert.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <getopt.h> |
| #include <netdb.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <xtables.h> |
| #include <linux/ip_vs.h> |
| #include <linux/netfilter/xt_ipvs.h> |
| |
| static const struct option ipvs_mt_opts[] = { |
| { .name = "ipvs", .has_arg = false, .val = '0' }, |
| { .name = "vproto", .has_arg = true, .val = '1' }, |
| { .name = "vaddr", .has_arg = true, .val = '2' }, |
| { .name = "vport", .has_arg = true, .val = '3' }, |
| { .name = "vdir", .has_arg = true, .val = '4' }, |
| { .name = "vmethod", .has_arg = true, .val = '5' }, |
| { .name = "vportctl", .has_arg = true, .val = '6' }, |
| XT_GETOPT_TABLEEND, |
| }; |
| |
| static void ipvs_mt_help(void) |
| { |
| printf( |
| "IPVS match options:\n" |
| "[!] --ipvs packet belongs to an IPVS connection\n" |
| "\n" |
| "Any of the following options implies --ipvs (even negated)\n" |
| "[!] --vproto protocol VIP protocol to match; by number or name,\n" |
| " e.g. \"tcp\"\n" |
| "[!] --vaddr address[/mask] VIP address to match\n" |
| "[!] --vport port VIP port to match; by number or name,\n" |
| " e.g. \"http\"\n" |
| " --vdir {ORIGINAL|REPLY} flow direction of packet\n" |
| "[!] --vmethod {GATE|IPIP|MASQ} IPVS forwarding method used\n" |
| "[!] --vportctl port VIP port of the controlling connection to\n" |
| " match, e.g. 21 for FTP\n" |
| ); |
| } |
| |
| static void ipvs_mt_parse_addr_and_mask(const char *arg, |
| union nf_inet_addr *address, |
| union nf_inet_addr *mask, |
| unsigned int family) |
| { |
| struct in_addr *addr = NULL; |
| struct in6_addr *addr6 = NULL; |
| unsigned int naddrs = 0; |
| |
| if (family == NFPROTO_IPV4) { |
| xtables_ipparse_any(arg, &addr, &mask->in, &naddrs); |
| if (naddrs > 1) |
| xtables_error(PARAMETER_PROBLEM, |
| "multiple IP addresses not allowed"); |
| if (naddrs == 1) |
| memcpy(&address->in, addr, sizeof(*addr)); |
| } else if (family == NFPROTO_IPV6) { |
| xtables_ip6parse_any(arg, &addr6, &mask->in6, &naddrs); |
| if (naddrs > 1) |
| xtables_error(PARAMETER_PROBLEM, |
| "multiple IP addresses not allowed"); |
| if (naddrs == 1) |
| memcpy(&address->in6, addr6, sizeof(*addr6)); |
| } else { |
| /* Hu? */ |
| assert(false); |
| } |
| } |
| |
| /* Function which parses command options; returns true if it ate an option */ |
| static int ipvs_mt_parse(int c, char **argv, int invert, unsigned int *flags, |
| const void *entry, struct xt_entry_match **match, |
| unsigned int family) |
| { |
| struct xt_ipvs_mtinfo *data = (void *)(*match)->data; |
| char *p = NULL; |
| uint8_t op = 0; |
| |
| if ('0' <= c && c <= '6') { |
| static const int ops[] = { |
| XT_IPVS_IPVS_PROPERTY, |
| XT_IPVS_PROTO, |
| XT_IPVS_VADDR, |
| XT_IPVS_VPORT, |
| XT_IPVS_DIR, |
| XT_IPVS_METHOD, |
| XT_IPVS_VPORTCTL |
| }; |
| op = ops[c - '0']; |
| } else |
| return 0; |
| |
| if (*flags & op & XT_IPVS_ONCE_MASK) |
| goto multiple_use; |
| |
| switch (c) { |
| case '0': /* --ipvs */ |
| /* Nothing to do here. */ |
| break; |
| |
| case '1': /* --vproto */ |
| /* Canonicalize into lower case */ |
| for (p = optarg; *p != '\0'; ++p) |
| *p = tolower(*p); |
| |
| data->l4proto = xtables_parse_protocol(optarg); |
| break; |
| |
| case '2': /* --vaddr */ |
| ipvs_mt_parse_addr_and_mask(optarg, &data->vaddr, |
| &data->vmask, family); |
| break; |
| |
| case '3': /* --vport */ |
| data->vport = htons(xtables_parse_port(optarg, "tcp")); |
| break; |
| |
| case '4': /* --vdir */ |
| xtables_param_act(XTF_NO_INVERT, "ipvs", "--vdir", invert); |
| if (strcasecmp(optarg, "ORIGINAL") == 0) { |
| data->bitmask |= XT_IPVS_DIR; |
| data->invert &= ~XT_IPVS_DIR; |
| } else if (strcasecmp(optarg, "REPLY") == 0) { |
| data->bitmask |= XT_IPVS_DIR; |
| data->invert |= XT_IPVS_DIR; |
| } else { |
| xtables_param_act(XTF_BAD_VALUE, |
| "ipvs", "--vdir", optarg); |
| } |
| break; |
| |
| case '5': /* --vmethod */ |
| if (strcasecmp(optarg, "GATE") == 0) |
| data->fwd_method = IP_VS_CONN_F_DROUTE; |
| else if (strcasecmp(optarg, "IPIP") == 0) |
| data->fwd_method = IP_VS_CONN_F_TUNNEL; |
| else if (strcasecmp(optarg, "MASQ") == 0) |
| data->fwd_method = IP_VS_CONN_F_MASQ; |
| else |
| xtables_param_act(XTF_BAD_VALUE, |
| "ipvs", "--vmethod", optarg); |
| break; |
| |
| case '6': /* --vportctl */ |
| data->vportctl = htons(xtables_parse_port(optarg, "tcp")); |
| break; |
| } |
| |
| if (op & XT_IPVS_ONCE_MASK) { |
| if (data->invert & XT_IPVS_IPVS_PROPERTY) |
| xtables_error(PARAMETER_PROBLEM, |
| "! --ipvs cannot be together with" |
| " other options"); |
| data->bitmask |= XT_IPVS_IPVS_PROPERTY; |
| } |
| |
| data->bitmask |= op; |
| if (invert) |
| data->invert |= op; |
| *flags |= op; |
| return 1; |
| |
| multiple_use: |
| xtables_error(PARAMETER_PROBLEM, |
| "multiple use of the same IPVS option is not allowed"); |
| } |
| |
| static int ipvs_mt4_parse(int c, char **argv, int invert, unsigned int *flags, |
| const void *entry, struct xt_entry_match **match) |
| { |
| return ipvs_mt_parse(c, argv, invert, flags, entry, match, |
| NFPROTO_IPV4); |
| } |
| |
| static int ipvs_mt6_parse(int c, char **argv, int invert, unsigned int *flags, |
| const void *entry, struct xt_entry_match **match) |
| { |
| return ipvs_mt_parse(c, argv, invert, flags, entry, match, |
| NFPROTO_IPV6); |
| } |
| |
| static void ipvs_mt_check(unsigned int flags) |
| { |
| if (flags == 0) |
| xtables_error(PARAMETER_PROBLEM, |
| "IPVS: At least one option is required"); |
| } |
| |
| /* Shamelessly copied from libxt_conntrack.c */ |
| static void ipvs_mt_dump_addr(const union nf_inet_addr *addr, |
| const union nf_inet_addr *mask, |
| unsigned int family, bool numeric) |
| { |
| char buf[BUFSIZ]; |
| |
| if (family == NFPROTO_IPV4) { |
| if (!numeric && addr->ip == 0) { |
| printf(" anywhere"); |
| return; |
| } |
| if (numeric) |
| strcpy(buf, xtables_ipaddr_to_numeric(&addr->in)); |
| else |
| strcpy(buf, xtables_ipaddr_to_anyname(&addr->in)); |
| strcat(buf, xtables_ipmask_to_numeric(&mask->in)); |
| printf(" %s", buf); |
| } else if (family == NFPROTO_IPV6) { |
| if (!numeric && addr->ip6[0] == 0 && addr->ip6[1] == 0 && |
| addr->ip6[2] == 0 && addr->ip6[3] == 0) { |
| printf(" anywhere"); |
| return; |
| } |
| if (numeric) |
| strcpy(buf, xtables_ip6addr_to_numeric(&addr->in6)); |
| else |
| strcpy(buf, xtables_ip6addr_to_anyname(&addr->in6)); |
| strcat(buf, xtables_ip6mask_to_numeric(&mask->in6)); |
| printf(" %s", buf); |
| } |
| } |
| |
| static void ipvs_mt_dump(const void *ip, const struct xt_ipvs_mtinfo *data, |
| unsigned int family, bool numeric, const char *prefix) |
| { |
| if (data->bitmask == XT_IPVS_IPVS_PROPERTY) { |
| if (data->invert & XT_IPVS_IPVS_PROPERTY) |
| printf(" !"); |
| printf(" %sipvs", prefix); |
| } |
| |
| if (data->bitmask & XT_IPVS_PROTO) { |
| if (data->invert & XT_IPVS_PROTO) |
| printf(" !"); |
| printf(" %sproto %u", prefix, data->l4proto); |
| } |
| |
| if (data->bitmask & XT_IPVS_VADDR) { |
| if (data->invert & XT_IPVS_VADDR) |
| printf(" !"); |
| |
| printf(" %svaddr", prefix); |
| ipvs_mt_dump_addr(&data->vaddr, &data->vmask, family, numeric); |
| } |
| |
| if (data->bitmask & XT_IPVS_VPORT) { |
| if (data->invert & XT_IPVS_VPORT) |
| printf(" !"); |
| |
| printf(" %svport %u", prefix, ntohs(data->vport)); |
| } |
| |
| if (data->bitmask & XT_IPVS_DIR) { |
| if (data->invert & XT_IPVS_DIR) |
| printf(" %svdir REPLY", prefix); |
| else |
| printf(" %svdir ORIGINAL", prefix); |
| } |
| |
| if (data->bitmask & XT_IPVS_METHOD) { |
| if (data->invert & XT_IPVS_METHOD) |
| printf(" !"); |
| |
| printf(" %svmethod", prefix); |
| switch (data->fwd_method) { |
| case IP_VS_CONN_F_DROUTE: |
| printf(" GATE"); |
| break; |
| case IP_VS_CONN_F_TUNNEL: |
| printf(" IPIP"); |
| break; |
| case IP_VS_CONN_F_MASQ: |
| printf(" MASQ"); |
| break; |
| default: |
| /* Hu? */ |
| printf(" UNKNOWN"); |
| break; |
| } |
| } |
| |
| if (data->bitmask & XT_IPVS_VPORTCTL) { |
| if (data->invert & XT_IPVS_VPORTCTL) |
| printf(" !"); |
| |
| printf(" %svportctl %u", prefix, ntohs(data->vportctl)); |
| } |
| } |
| |
| static void ipvs_mt4_print(const void *ip, const struct xt_entry_match *match, |
| int numeric) |
| { |
| const struct xt_ipvs_mtinfo *data = (const void *)match->data; |
| ipvs_mt_dump(ip, data, NFPROTO_IPV4, numeric, ""); |
| } |
| |
| static void ipvs_mt6_print(const void *ip, const struct xt_entry_match *match, |
| int numeric) |
| { |
| const struct xt_ipvs_mtinfo *data = (const void *)match->data; |
| ipvs_mt_dump(ip, data, NFPROTO_IPV6, numeric, ""); |
| } |
| |
| static void ipvs_mt4_save(const void *ip, const struct xt_entry_match *match) |
| { |
| const struct xt_ipvs_mtinfo *data = (const void *)match->data; |
| ipvs_mt_dump(ip, data, NFPROTO_IPV4, true, "--"); |
| } |
| |
| static void ipvs_mt6_save(const void *ip, const struct xt_entry_match *match) |
| { |
| const struct xt_ipvs_mtinfo *data = (const void *)match->data; |
| ipvs_mt_dump(ip, data, NFPROTO_IPV6, true, "--"); |
| } |
| |
| static struct xtables_match ipvs_matches_reg[] = { |
| { |
| .version = XTABLES_VERSION, |
| .name = "ipvs", |
| .revision = 0, |
| .family = NFPROTO_IPV4, |
| .size = XT_ALIGN(sizeof(struct xt_ipvs_mtinfo)), |
| .userspacesize = XT_ALIGN(sizeof(struct xt_ipvs_mtinfo)), |
| .help = ipvs_mt_help, |
| .parse = ipvs_mt4_parse, |
| .final_check = ipvs_mt_check, |
| .print = ipvs_mt4_print, |
| .save = ipvs_mt4_save, |
| .extra_opts = ipvs_mt_opts, |
| }, |
| { |
| .version = XTABLES_VERSION, |
| .name = "ipvs", |
| .revision = 0, |
| .family = NFPROTO_IPV6, |
| .size = XT_ALIGN(sizeof(struct xt_ipvs_mtinfo)), |
| .userspacesize = XT_ALIGN(sizeof(struct xt_ipvs_mtinfo)), |
| .help = ipvs_mt_help, |
| .parse = ipvs_mt6_parse, |
| .final_check = ipvs_mt_check, |
| .print = ipvs_mt6_print, |
| .save = ipvs_mt6_save, |
| .extra_opts = ipvs_mt_opts, |
| }, |
| }; |
| |
| void _init(void) |
| { |
| xtables_register_matches(ipvs_matches_reg, |
| ARRAY_SIZE(ipvs_matches_reg)); |
| } |