Merge commit 'origin/trunk@12184' into fedora
[iptables.git] / trunk / extensions / libipt_DNAT.c
diff --git a/trunk/extensions/libipt_DNAT.c b/trunk/extensions/libipt_DNAT.c
new file mode 100644 (file)
index 0000000..fed7611
--- /dev/null
@@ -0,0 +1,261 @@
+/* Shared library add-on to iptables to add destination-NAT support. */
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <iptables.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter/nf_nat.h>
+
+#define IPT_DNAT_OPT_DEST 0x1
+#define IPT_DNAT_OPT_RANDOM 0x2
+
+/* Dest NAT data consists of a multi-range, indicating where to map
+   to. */
+struct ipt_natinfo
+{
+       struct xt_entry_target t;
+       struct ip_nat_multi_range mr;
+};
+
+/* Function which prints out usage message. */
+static void DNAT_help(void)
+{
+       printf(
+"DNAT target options:\n"
+" --to-destination <ipaddr>[-<ipaddr>][:port-port]\n"
+"                              Address to map destination to.\n"
+"[--random]\n");
+}
+
+static const struct option DNAT_opts[] = {
+       { "to-destination", 1, NULL, '1' },
+       { "random", 0, NULL, '2' },
+       { .name = NULL }
+};
+
+static struct ipt_natinfo *
+append_range(struct ipt_natinfo *info, const struct ip_nat_range *range)
+{
+       unsigned int size;
+
+       /* One rangesize already in struct ipt_natinfo */
+       size = XT_ALIGN(sizeof(*info) + info->mr.rangesize * sizeof(*range));
+
+       info = realloc(info, size);
+       if (!info)
+               exit_error(OTHER_PROBLEM, "Out of memory\n");
+
+       info->t.u.target_size = size;
+       info->mr.range[info->mr.rangesize] = *range;
+       info->mr.rangesize++;
+
+       return info;
+}
+
+/* Ranges expected in network order. */
+static struct xt_entry_target *
+parse_to(char *arg, int portok, struct ipt_natinfo *info)
+{
+       struct ip_nat_range range;
+       char *colon, *dash, *error;
+       const struct in_addr *ip;
+
+       memset(&range, 0, sizeof(range));
+       colon = strchr(arg, ':');
+
+       if (colon) {
+               int port;
+
+               if (!portok)
+                       exit_error(PARAMETER_PROBLEM,
+                                  "Need TCP or UDP with port specification");
+
+               range.flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
+
+               port = atoi(colon+1);
+               if (port <= 0 || port > 65535)
+                       exit_error(PARAMETER_PROBLEM,
+                                  "Port `%s' not valid\n", colon+1);
+
+               error = strchr(colon+1, ':');
+               if (error)
+                       exit_error(PARAMETER_PROBLEM,
+                                  "Invalid port:port syntax - use dash\n");
+
+               dash = strchr(colon, '-');
+               if (!dash) {
+                       range.min.tcp.port
+                               = range.max.tcp.port
+                               = htons(port);
+               } else {
+                       int maxport;
+
+                       maxport = atoi(dash + 1);
+                       if (maxport <= 0 || maxport > 65535)
+                               exit_error(PARAMETER_PROBLEM,
+                                          "Port `%s' not valid\n", dash+1);
+                       if (maxport < port)
+                               /* People are stupid. */
+                               exit_error(PARAMETER_PROBLEM,
+                                          "Port range `%s' funky\n", colon+1);
+                       range.min.tcp.port = htons(port);
+                       range.max.tcp.port = htons(maxport);
+               }
+               /* Starts with a colon? No IP info...*/
+               if (colon == arg)
+                       return &(append_range(info, &range)->t);
+               *colon = '\0';
+       }
+
+       range.flags |= IP_NAT_RANGE_MAP_IPS;
+       dash = strchr(arg, '-');
+       if (colon && dash && dash > colon)
+               dash = NULL;
+
+       if (dash)
+               *dash = '\0';
+
+       ip = numeric_to_ipaddr(arg);
+       if (!ip)
+               exit_error(PARAMETER_PROBLEM, "Bad IP address `%s'\n",
+                          arg);
+       range.min_ip = ip->s_addr;
+       if (dash) {
+               ip = numeric_to_ipaddr(dash+1);
+               if (!ip)
+                       exit_error(PARAMETER_PROBLEM, "Bad IP address `%s'\n",
+                                  dash+1);
+               range.max_ip = ip->s_addr;
+       } else
+               range.max_ip = range.min_ip;
+
+       return &(append_range(info, &range)->t);
+}
+
+/* Function which parses command options; returns true if it
+   ate an option */
+static int DNAT_parse(int c, char **argv, int invert, unsigned int *flags,
+                      const void *e, struct xt_entry_target **target)
+{
+       const struct ipt_entry *entry = e;
+       struct ipt_natinfo *info = (void *)*target;
+       int portok;
+
+       if (entry->ip.proto == IPPROTO_TCP
+           || entry->ip.proto == IPPROTO_UDP
+           || entry->ip.proto == IPPROTO_ICMP)
+               portok = 1;
+       else
+               portok = 0;
+
+       switch (c) {
+       case '1':
+               if (check_inverse(optarg, &invert, NULL, 0))
+                       exit_error(PARAMETER_PROBLEM,
+                                  "Unexpected `!' after --to-destination");
+
+               if (*flags) {
+                       if (!kernel_version)
+                               get_kernel_version();
+                       if (kernel_version > LINUX_VERSION(2, 6, 10))
+                               exit_error(PARAMETER_PROBLEM,
+                                          "Multiple --to-destination not supported");
+               }
+               *target = parse_to(optarg, portok, info);
+               /* WTF do we need this for?? */
+               if (*flags & IPT_DNAT_OPT_RANDOM)
+                       info->mr.range[0].flags |= IP_NAT_RANGE_PROTO_RANDOM;
+               *flags |= IPT_DNAT_OPT_DEST;
+               return 1;
+
+       case '2':
+               if (*flags & IPT_DNAT_OPT_DEST) {
+                       info->mr.range[0].flags |= IP_NAT_RANGE_PROTO_RANDOM;
+                       *flags |= IPT_DNAT_OPT_RANDOM;
+               } else
+                       *flags |= IPT_DNAT_OPT_RANDOM;
+               return 1;
+       default:
+               return 0;
+       }
+}
+
+/* Final check; must have specfied --to-source. */
+static void DNAT_check(unsigned int flags)
+{
+       if (!flags)
+               exit_error(PARAMETER_PROBLEM,
+                          "You must specify --to-destination");
+}
+
+static void print_range(const struct ip_nat_range *r)
+{
+       if (r->flags & IP_NAT_RANGE_MAP_IPS) {
+               struct in_addr a;
+
+               a.s_addr = r->min_ip;
+               printf("%s", ipaddr_to_numeric(&a));
+               if (r->max_ip != r->min_ip) {
+                       a.s_addr = r->max_ip;
+                       printf("-%s", ipaddr_to_numeric(&a));
+               }
+       }
+       if (r->flags & IP_NAT_RANGE_PROTO_SPECIFIED) {
+               printf(":");
+               printf("%hu", ntohs(r->min.tcp.port));
+               if (r->max.tcp.port != r->min.tcp.port)
+                       printf("-%hu", ntohs(r->max.tcp.port));
+       }
+}
+
+/* Prints out the targinfo. */
+static void DNAT_print(const void *ip, const struct xt_entry_target *target,
+                       int numeric)
+{
+       struct ipt_natinfo *info = (void *)target;
+       unsigned int i = 0;
+
+       printf("to:");
+       for (i = 0; i < info->mr.rangesize; i++) {
+               print_range(&info->mr.range[i]);
+               printf(" ");
+               if (info->mr.range[i].flags & IP_NAT_RANGE_PROTO_RANDOM)
+                       printf("random ");
+       }
+}
+
+/* Saves the union ipt_targinfo in parsable form to stdout. */
+static void DNAT_save(const void *ip, const struct xt_entry_target *target)
+{
+       struct ipt_natinfo *info = (void *)target;
+       unsigned int i = 0;
+
+       for (i = 0; i < info->mr.rangesize; i++) {
+               printf("--to-destination ");
+               print_range(&info->mr.range[i]);
+               printf(" ");
+               if (info->mr.range[i].flags & IP_NAT_RANGE_PROTO_RANDOM)
+                       printf("--random ");
+       }
+}
+
+static struct xtables_target dnat_tg_reg = {
+       .name           = "DNAT",
+       .version        = XTABLES_VERSION,
+       .family         = PF_INET,
+       .size           = XT_ALIGN(sizeof(struct ip_nat_multi_range)),
+       .userspacesize  = XT_ALIGN(sizeof(struct ip_nat_multi_range)),
+       .help           = DNAT_help,
+       .parse          = DNAT_parse,
+       .final_check    = DNAT_check,
+       .print          = DNAT_print,
+       .save           = DNAT_save,
+       .extra_opts     = DNAT_opts,
+};
+
+void _init(void)
+{
+       xtables_register_target(&dnat_tg_reg);
+}