/* * 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 <stdbool.h> #include <stdio.h> #include <string.h> #include <xtables.h> #include <linux/ip_vs.h> #include <linux/netfilter/xt_ipvs.h> enum { /* For xt_ipvs: make sure this matches up with %XT_IPVS_*'s order */ O_IPVS = 0, O_VPROTO, O_VADDR, O_VPORT, O_VDIR, O_VMETHOD, O_VPORTCTL, }; #define s struct xt_ipvs_mtinfo static const struct xt_option_entry ipvs_mt_opts[] = { {.name = "ipvs", .id = O_IPVS, .type = XTTYPE_NONE, .flags = XTOPT_INVERT}, {.name = "vproto", .id = O_VPROTO, .type = XTTYPE_STRING, .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, l4proto)}, {.name = "vaddr", .id = O_VADDR, .type = XTTYPE_HOSTMASK, .flags = XTOPT_INVERT}, {.name = "vport", .id = O_VPORT, .type = XTTYPE_PORT, .flags = XTOPT_NBO | XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, vport)}, {.name = "vdir", .id = O_VDIR, .type = XTTYPE_STRING}, {.name = "vmethod", .id = O_VMETHOD, .type = XTTYPE_STRING, .flags = XTOPT_INVERT}, {.name = "vportctl", .id = O_VPORTCTL, .type = XTTYPE_PORT, .flags = XTOPT_NBO | XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, vportctl)}, XTOPT_TABLEEND, }; #undef s 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(struct xt_option_call *cb) { struct xt_ipvs_mtinfo *data = cb->data; xtables_option_parse(cb); switch (cb->entry->id) { case O_VPROTO: data->l4proto = cb->val.protocol; break; case O_VADDR: memcpy(&data->vaddr, &cb->val.haddr, sizeof(cb->val.haddr)); memcpy(&data->vmask, &cb->val.hmask, sizeof(cb->val.hmask)); break; case O_VDIR: if (strcasecmp(cb->arg, "ORIGINAL") == 0) { data->bitmask |= XT_IPVS_DIR; data->invert &= ~XT_IPVS_DIR; } else if (strcasecmp(cb->arg, "REPLY") == 0) { data->bitmask |= XT_IPVS_DIR; data->invert |= XT_IPVS_DIR; } else { xtables_param_act(XTF_BAD_VALUE, "ipvs", "--vdir", cb->arg); } break; case O_VMETHOD: if (strcasecmp(cb->arg, "GATE") == 0) data->fwd_method = IP_VS_CONN_F_DROUTE; else if (strcasecmp(cb->arg, "IPIP") == 0) data->fwd_method = IP_VS_CONN_F_TUNNEL; else if (strcasecmp(cb->arg, "MASQ") == 0) data->fwd_method = IP_VS_CONN_F_MASQ; else xtables_param_act(XTF_BAD_VALUE, "ipvs", "--vmethod", cb->arg); break; } data->bitmask |= 1 << cb->entry->id; if (cb->invert) data->invert |= 1 << cb->entry->id; } static void ipvs_mt_check(struct xt_fcheck_call *cb) { struct xt_ipvs_mtinfo *info = cb->data; if (cb->xflags == 0) xtables_error(PARAMETER_PROBLEM, "IPVS: At least one option is required"); if (info->bitmask & XT_IPVS_ONCE_MASK) { if (info->invert & XT_IPVS_IPVS_PROPERTY) xtables_error(PARAMETER_PROBLEM, "! --ipvs cannot be together with" " other options"); info->bitmask |= XT_IPVS_IPVS_PROPERTY; } } /* 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, .x6_parse = ipvs_mt_parse, .x6_fcheck = ipvs_mt_check, .print = ipvs_mt4_print, .save = ipvs_mt4_save, .x6_options = 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, .x6_parse = ipvs_mt_parse, .x6_fcheck = ipvs_mt_check, .print = ipvs_mt6_print, .save = ipvs_mt6_save, .x6_options = ipvs_mt_opts, }, }; void _init(void) { xtables_register_matches(ipvs_matches_reg, ARRAY_SIZE(ipvs_matches_reg)); }