]> git.ipfire.org Git - thirdparty/xtables-addons.git/commitdiff
DHCP address match and mangler
authorJan Engelhardt <jengelh@medozas.de>
Mon, 1 Sep 2008 18:26:21 +0000 (14:26 -0400)
committerJan Engelhardt <jengelh@medozas.de>
Mon, 1 Sep 2008 19:27:43 +0000 (15:27 -0400)
12 files changed:
extensions/Kbuild
extensions/Mbuild
extensions/compat_xtables.c
extensions/compat_xtables.h
extensions/libxt_DHCPADDR.c [new file with mode: 0644]
extensions/libxt_DHCPADDR.man [new file with mode: 0644]
extensions/libxt_dhcpaddr.c [new file with mode: 0644]
extensions/libxt_dhcpaddr.man [new file with mode: 0644]
extensions/mac.c [new file with mode: 0644]
extensions/xt_DHCPADDR.c [new file with mode: 0644]
extensions/xt_DHCPADDR.h [new file with mode: 0644]
mconfig

index 54c04ef2a8d9c65e26f2da399297f300cdb402dd..2717b31398bf8aad2c7b2daef5dc55a5fda8d63c 100644 (file)
@@ -7,6 +7,7 @@ obj-m                    += compat_xtables.o
 
 obj-${build_CHAOS}       += xt_CHAOS.o
 obj-${build_DELUDE}      += xt_DELUDE.o
+obj-${build_DHCPADDR}    += xt_DHCPADDR.o
 obj-${build_ECHO}        += xt_ECHO.o
 obj-${build_IPMARK}      += xt_IPMARK.o
 obj-${build_LOGMARK}     += xt_LOGMARK.o
index cf9e51c6401f3fc9d1006f6337656f5bb81361ed..d19da8e97033eb6e462859cf11185af3cb052c67 100644 (file)
@@ -1,5 +1,6 @@
 obj-${build_CHAOS}       += libxt_CHAOS.so
 obj-${build_DELUDE}      += libxt_DELUDE.so
+obj-${build_DHCPADDR}    += libxt_DHCPADDR.so libxt_dhcpaddr.so
 obj-${build_ECHO}        += libxt_ECHO.so
 obj-${build_IPMARK}      += libxt_IPMARK.so
 obj-${build_LOGMARK}     += libxt_LOGMARK.so
index 3c40863994406f54e82b74f8b46349f64eb7c6b5..8dc9f0034a8a66e8f1024565cc42a7692aea22f1 100644 (file)
@@ -383,6 +383,19 @@ int xtnu_neigh_hh_output(struct hh_cache *hh, struct sk_buff *skb)
        return hh->hh_output(skb);
 }
 EXPORT_SYMBOL_GPL(xtnu_neigh_hh_output);
+
+static inline void csum_replace4(__sum16 *sum, __be32 from, __be32 to)
+{
+       __be32 diff[] = {~from, to};
+       *sum = csum_fold(csum_partial((char *)diff, sizeof(diff),
+              ~csum_unfold(*sum)));
+}
+
+void xtnu_csum_replace2(__sum16 *sum, __be16 from, __be16 to)
+{
+       csum_replace4(sum, (__force __be32)from, (__force __be32)to);
+}
+EXPORT_SYMBOL_GPL(xtnu_csum_replace2);
 #endif
 
 MODULE_LICENSE("GPL");
index 9d2d2d275d620d15de9f93138d8a5858a3b4d35e..86f573e7c640a0de59c83107cdbdeadd4e4a9b7b 100644 (file)
 #      define xt_unregister_matches xtnu_unregister_matches
 #endif
 
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 19)
+#      define csum_replace2 xtnu_csum_replace2
+#elif LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 24)
+#      define csum_replace2 nf_csum_replace2
+#endif
+
 #define ip_route_me_harder    xtnu_ip_route_me_harder
 #define skb_make_writable     xtnu_skb_make_writable
 #define xt_target             xtnu_target
diff --git a/extensions/libxt_DHCPADDR.c b/extensions/libxt_DHCPADDR.c
new file mode 100644 (file)
index 0000000..4171e5a
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ *     "DHCPADDR" target extension for iptables
+ *     Copyright © Jan Engelhardt <jengelh [at] medozas de>, 2008
+ *
+ *     This program is free software; you can redistribute it and/or
+ *     modify it under the terms of the GNU General Public License; either
+ *     version 2 of the License, or any later version, as published by the
+ *     Free Software Foundation.
+ */
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/ether.h>
+#include <xtables.h>
+#include "xt_DHCPADDR.h"
+#include "mac.c"
+
+enum {
+       F_MAC = 1 << 0,
+};
+
+static const struct option dhcpaddr_tg_opts[] = {
+       {.name = "set-mac", .has_arg = true, .val = 'M'},
+       {NULL},
+};
+
+static void dhcpaddr_tg_help(void)
+{
+       printf(
+"DHCPADDDR target options:\n"
+"  --set-mac lladdr[/mask]    Set MAC address in DHCP Client Host field\n"
+       );
+}
+
+static int dhcpaddr_tg_parse(int c, char **argv, int invert,
+    unsigned int *flags, const void *entry, struct xt_entry_target **target)
+{
+       struct dhcpaddr_info *info = (void *)(*target)->data;
+
+       switch (c) {
+       case 'M':
+               param_act(P_ONLY_ONCE, "DHCPADDR", "--set-mac", *flags & F_MAC);
+               param_act(P_NO_INVERT, "DHCPADDR", "--set-mac", invert);
+               if (!mac_parse(optarg, info->addr, &info->mask))
+                       param_act(P_BAD_VALUE, "DHCPADDR", "--set-mac", optarg);
+               *flags |= F_MAC;
+               return true;
+       }
+
+       return false;
+}
+
+static void dhcpaddr_tg_check(unsigned int flags)
+{
+       if (flags == 0)
+               exit_error(PARAMETER_PROBLEM, "DHCPADDR target: "
+                          "--set-mac parameter required");
+}
+
+static void dhcpaddr_tg_print(const void *ip,
+    const struct xt_entry_target *target, int numeric)
+{
+       const struct dhcpaddr_info *info = (void *)target->data;
+
+       printf("DHCPADDR %s" DH_MAC_FMT "/%u ",
+              info->invert ? "!" : "", DH_MAC_HEX(info->addr), info->mask);
+}
+
+static void dhcpaddr_tg_save(const void *ip,
+    const struct xt_entry_target *target)
+{
+       const struct dhcpaddr_info *info = (const void *)target->data;
+
+       if (info->invert)
+               printf("! ");
+       printf("--set-mac " DH_MAC_FMT "/%u ",
+              DH_MAC_HEX(info->addr), info->mask);
+}
+
+static struct xtables_target dhcpaddr_tg_reg = {
+       .version       = XTABLES_VERSION,
+       .name          = "DHCPADDR",
+       .revision      = 0,
+       .family        = PF_INET,
+       .size          = XT_ALIGN(sizeof(struct dhcpaddr_info)),
+       .userspacesize = XT_ALIGN(sizeof(struct dhcpaddr_info)),
+       .help          = dhcpaddr_tg_help,
+       .parse         = dhcpaddr_tg_parse,
+       .final_check   = dhcpaddr_tg_check,
+       .print         = dhcpaddr_tg_print,
+       .save          = dhcpaddr_tg_save,
+       .extra_opts    = dhcpaddr_tg_opts,
+};
+
+static void _init(void)
+{
+       xtables_register_target(&dhcpaddr_tg_reg);
+}
diff --git a/extensions/libxt_DHCPADDR.man b/extensions/libxt_DHCPADDR.man
new file mode 100644 (file)
index 0000000..09f545d
--- /dev/null
@@ -0,0 +1,25 @@
+In conjunction with ebtables, DHCPADDR can be used to completely change all MAC
+addresses from and to a VMware-based virtual machine. This is needed because
+VMware does not allow to set a non-VMware MAC address before an operating
+system is booted (and the MAC be changed with `ip link set eth0 address
+aa:bb..`).
+.TP
+\fB--set-mac\fP \fIaa:bb:cc:dd:ee:ff\fP[\fB/\fP\fImask\fP]
+Replace the client host MAC address field in the DHCP message with the given
+MAC address. This option is mandatory. The \fImask\fP parameter specifies the
+prefix length of bits to change.
+.PP
+EXAMPLE, replacing all addresses from one of VMware's assigned vendor IDs
+(00:50:56) addresses with something else:
+.PP
+iptables -t mangle -A FORWARD -p udp --dport 67 -m physdev --physdev-in vmnet1
+-m dhcpaddr --mac 00:50:56:00:00:00/24 -j DHCPADDR --set-mac
+ab:cd:ef:00:00:00/24
+.PP
+iptables -t mangle -A FORWARD -p udp --dport 68 -m physdev --physdev-out vmnet1
+-m dhcpaddr --mac ab:cd:ef:00:00:00/24 -j DHCPADDR --set-mac
+00:50:56:00:00:00/24
+.PP
+(This assumes there is a bridge interface that has vmnet1 as a port. You will
+also need to add appropriate ebtables rules to change the MAC address of the
+Ethernet headers.)
diff --git a/extensions/libxt_dhcpaddr.c b/extensions/libxt_dhcpaddr.c
new file mode 100644 (file)
index 0000000..ac4da32
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ *     "dhcpaddr" match extension for iptables
+ *     Copyright © Jan Engelhardt <jengelh [at] medozas de>, 2008
+ *
+ *     This program is free software; you can redistribute it and/or
+ *     modify it under the terms of the GNU General Public License; either
+ *     version 2 of the License, or any later version, as published by the
+ *     Free Software Foundation.
+ */
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netdb.h>
+#include <net/ethernet.h>
+#include <xtables.h>
+#include "xt_DHCPADDR.h"
+#include "mac.c"
+
+enum {
+       F_MAC = 1 << 0,
+};
+
+static const struct option dhcpaddr_mt_opts[] = {
+       {.name = "mac", .has_arg = true, .val = 'M'},
+       {NULL},
+};
+
+static void dhcpaddr_mt_help(void)
+{
+       printf(
+"dhcpaddr match options:\n"
+"[!] --mac lladdr[/mask]    Match on MAC address in DHCP Client Host field\n"
+       );
+}
+
+static int dhcpaddr_mt_parse(int c, char **argv, int invert,
+    unsigned int *flags, const void *entry, struct xt_entry_match **match)
+{
+       struct dhcpaddr_info *info = (void *)(*match)->data;
+
+       switch (c) {
+       case 'M':
+               param_act(P_ONLY_ONCE, "dhcpaddr", "--mac", *flags & F_MAC);
+               param_act(P_NO_INVERT, "dhcpaddr", "--mac", invert);
+               if (!mac_parse(optarg, info->addr, &info->mask))
+                       param_act(P_BAD_VALUE, "dhcpaddr", "--mac", optarg);
+               if (invert)
+                       info->invert = true;
+               *flags |= F_MAC;
+               return true;
+       }
+
+       return false;
+}
+
+static void dhcpaddr_mt_check(unsigned int flags)
+{
+       if (flags == 0)
+               exit_error(PARAMETER_PROBLEM, "dhcpaddr match: "
+                          "--mac parameter required");
+}
+
+static void dhcpaddr_mt_print(const void *ip,
+    const struct xt_entry_match *match, int numeric)
+{
+       const struct dhcpaddr_info *info = (void *)match->data;
+
+       printf("dhcpaddr %s" DH_MAC_FMT "/%u ",
+              info->invert ? "!" : "", DH_MAC_HEX(info->addr), info->mask);
+}
+
+static void dhcpaddr_mt_save(const void *ip,
+    const struct xt_entry_match *match)
+{
+       const struct dhcpaddr_info *info = (void *)match->data;
+
+       if (info->invert)
+               printf("! ");
+       printf("--mac " DH_MAC_FMT "/%u ",
+              DH_MAC_HEX(info->addr), info->mask);
+}
+
+static struct xtables_match dhcpaddr_mt_reg = {
+       .version       = XTABLES_VERSION,
+       .name          = "dhcpaddr",
+       .revision      = 0,
+       .family        = PF_INET,
+       .size          = XT_ALIGN(sizeof(struct dhcpaddr_info)),
+       .userspacesize = XT_ALIGN(sizeof(struct dhcpaddr_info)),
+       .help          = dhcpaddr_mt_help,
+       .parse         = dhcpaddr_mt_parse,
+       .final_check   = dhcpaddr_mt_check,
+       .print         = dhcpaddr_mt_print,
+       .save          = dhcpaddr_mt_save,
+       .extra_opts    = dhcpaddr_mt_opts,
+};
+
+static void _init(void)
+{
+       xtables_register_match(&dhcpaddr_mt_reg);
+}
diff --git a/extensions/libxt_dhcpaddr.man b/extensions/libxt_dhcpaddr.man
new file mode 100644 (file)
index 0000000..af678d1
--- /dev/null
@@ -0,0 +1,4 @@
+.TP
+\fB--mac\fP \fIaa:bb:cc:dd:ee:ff\fP[\fB/\fP\fImask\fP]
+Matches the DHCP Client Host address in a DHCP message. \fImask\fP specifies
+the prefix length of the initial portion to match.
diff --git a/extensions/mac.c b/extensions/mac.c
new file mode 100644 (file)
index 0000000..1149791
--- /dev/null
@@ -0,0 +1,29 @@
+static bool mac_parse(const char *addr, unsigned char *dest, uint8_t *mask)
+{
+       unsigned int i = 0, value;
+       char *end;
+
+       for (i = 0; i < ETH_ALEN; ++i) {
+               value = strtoul(addr, &end, 16);
+               if (addr == end || value > 0xFF)
+                       return false;
+               if (i == ETH_ALEN - 1) {
+                       if (*end != '\0' && *end != '/')
+                               return false;
+               } else if (*end != ':') {
+                       return false;
+               }
+               dest[i] = value;
+               addr = end + 1;
+       }
+
+       *mask = 48;
+       if (*end == '/') {
+               if (!strtonum(end + 1, &end, &value, 0, 48))
+                       return false;
+               if (*end != '\0')
+                       return false;
+       }
+
+       return true;
+}
diff --git a/extensions/xt_DHCPADDR.c b/extensions/xt_DHCPADDR.c
new file mode 100644 (file)
index 0000000..f57089d
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ *     "DHCPADDR" extensions for Xtables
+ *     Copyright © Jan Engelhardt <jengelh [at] medozas de>, 2008
+ *
+ *     This program is free software; you can redistribute it and/or
+ *     modify it under the terms of the GNU General Public License; either
+ *     version 2 of the License, or any later version, as published by the
+ *     Free Software Foundation.
+ */
+#include <linux/ip.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/types.h>
+#include <linux/udp.h>
+#include <net/ip.h>
+#include <linux/netfilter/x_tables.h>
+#include "xt_DHCPADDR.h"
+#include "compat_xtables.h"
+
+struct dhcp_message {
+       uint8_t op, htype, hlen, hops;
+       __be32 xid;
+       __be16 secs, flags;
+       __be32 ciaddr, yiaddr, siaddr, giaddr;
+       char chaddr[16];
+       /* Omitting all unneeded fields saves runtime memory */
+       /* char sname[64], file[128]; */
+};
+
+static void ether_set(unsigned char *addr, const unsigned char *op,
+    uint8_t mask)
+{
+       uint8_t lo_mask;
+       unsigned int i;
+
+       for (i = 0; i < ETH_ALEN && mask > 0; ++i) {
+               lo_mask = mask % 8;
+               /* FF << 4 >> 4 = 0F */
+               lo_mask = ~(uint8_t)0U << lo_mask >> lo_mask;
+               addr[i] &= lo_mask;
+               addr[i] |= op[i] & ~lo_mask;
+               if (mask >= 8)
+                       mask -= 8;
+               else
+                       mask = 0;
+       }
+}
+
+static bool ether_cmp(const unsigned char *lh, const unsigned char *rh,
+    uint8_t mask)
+{
+       uint8_t lo_mask;
+       unsigned int i;
+#define ZMAC_FMT "%02X:%02X:%02X:%02X:%02X:%02X"
+#define ZMACHEX(s) s[0], s[1], s[2], s[3], s[4], s[5]
+
+       for (i = 0; i < ETH_ALEN && mask > 0; ++i) {
+               lo_mask = mask % 8;
+               /* ~(0xFF << 4 >> 4) = ~0x0F = 0xF0 */
+               lo_mask = ~(~(uint8_t)0U << lo_mask >> lo_mask);
+               if ((lh[i] ^ rh[i]) & lo_mask)
+                       return false;
+               if (mask >= 8)
+                       mask -= 8;
+               else
+                       mask = 0;
+       }
+       return true;
+}
+
+static bool dhcpaddr_mt(const struct sk_buff *skb, const struct net_device *in,
+    const struct net_device *out, const struct xt_match *match,
+    const void *matchinfo, int offset, unsigned int protoff, bool *hotdrop)
+{
+       const struct dhcpaddr_info *info = matchinfo;
+       const struct dhcp_message *dh;
+       struct dhcp_message dhcpbuf;
+
+       dh = skb_header_pointer(skb, protoff + sizeof(struct udphdr),
+            sizeof(dhcpbuf), &dhcpbuf);
+       if (dh == NULL)
+               /*
+                * No hotdrop. This packet does not look like DHCP, but other
+                * matches may still have a valid reason to get their chance
+                * to match on this.
+                */
+               return false;
+
+       return ether_cmp((const void *)dh->chaddr, info->addr, info->mask);
+}
+
+static unsigned int dhcpaddr_tg(struct sk_buff **pskb,
+    const struct net_device *in, const struct net_device *out,
+    unsigned int hooknum, const struct xt_target *target, const void *targinfo)
+{
+       const struct dhcpaddr_info *info = targinfo;
+       struct dhcp_message dhcpbuf, *dh;
+       struct udphdr udpbuf, *udph;
+       struct sk_buff *skb = *pskb;
+       unsigned int i;
+
+       if (!skb_make_writable(pskb, 0))
+               return NF_DROP;
+
+       udph = skb_header_pointer(skb, ip_hdrlen(skb),
+              sizeof(udpbuf), &udpbuf);
+       if (udph == NULL)
+               return NF_DROP;
+
+       dh = skb_header_pointer(skb, ip_hdrlen(skb) + sizeof(udpbuf),
+            sizeof(dhcpbuf), &dhcpbuf);
+       if (dh == NULL)
+               return NF_DROP;
+
+       for (i = 0; i < sizeof(dh->chaddr); i += 2)
+               csum_replace2(&udph->check, *(const __be16 *)dh->chaddr, 0);
+
+       memset(dh->chaddr, 0, sizeof(dh->chaddr));
+       ether_set(dh->chaddr, info->addr, info->mask);
+
+       for (i = 0; i < sizeof(dh->chaddr); i += 2)
+               csum_replace2(&udph->check, 0, *(const __be16 *)dh->chaddr);
+
+       return XT_CONTINUE;
+}
+
+static struct xt_target dhcpaddr_tg_reg __read_mostly = {
+       .name       = "DHCPADDR",
+       .revision   = 0,
+       .family     = PF_INET,
+       .proto      = IPPROTO_UDP,
+       .table      = "mangle",
+       .target     = dhcpaddr_tg,
+       .targetsize = XT_ALIGN(sizeof(struct dhcpaddr_info)),
+       .me         = THIS_MODULE,
+};
+
+static struct xt_match dhcpaddr_mt_reg __read_mostly = {
+       .name       = "dhcpaddr",
+       .revision   = 0,
+       .family     = PF_INET,
+       .proto      = IPPROTO_UDP,
+       .match      = dhcpaddr_mt,
+       .matchsize  = XT_ALIGN(sizeof(struct dhcpaddr_info)),
+       .me         = THIS_MODULE,
+};
+
+static int __init dhcpaddr_init(void)
+{
+       int ret;
+
+       ret = xt_register_target(&dhcpaddr_tg_reg);
+       if (ret != 0)
+               return ret;
+       ret = xt_register_match(&dhcpaddr_mt_reg);
+       if (ret != 0) {
+               xt_unregister_target(&dhcpaddr_tg_reg);
+               return ret;
+       }
+       return 0;
+}
+
+static void __exit dhcpaddr_exit(void)
+{
+       xt_unregister_target(&dhcpaddr_tg_reg);
+       xt_unregister_match(&dhcpaddr_mt_reg);
+}
+
+module_init(dhcpaddr_init);
+module_exit(dhcpaddr_exit);
+MODULE_DESCRIPTION("Xtables: Clamp DHCP MAC to packet MAC addresses");
+MODULE_AUTHOR("Jan Engelhardt <jengelh@medozas.de>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("ipt_DHCPADDR");
+MODULE_ALIAS("ipt_dhcpaddr");
diff --git a/extensions/xt_DHCPADDR.h b/extensions/xt_DHCPADDR.h
new file mode 100644 (file)
index 0000000..8f926cf
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef _LINUX_NETFILTER_XT_DHCPADDR_H
+#define _LINUX_NETFILTER_XT_DHCPADDR_H 1
+
+#define DH_MAC_FMT "%02X:%02X:%02X:%02X:%02X:%02X"
+#define DH_MAC_HEX(z) z[0], z[1], z[2], z[3], z[4], z[5]
+
+struct dhcpaddr_info {
+       unsigned char addr[ETH_ALEN];
+       uint8_t mask, invert;
+};
+
+#endif /* _LINUX_NETFILTER_XT_DHCPADDR_H */
diff --git a/mconfig b/mconfig
index a86028433217a7799cdd978c7b7db8cbe98998a3..5fe5828db8a6a0da2bee290255efc89894409f2f 100644 (file)
--- a/mconfig
+++ b/mconfig
@@ -2,6 +2,7 @@
 #
 build_CHAOS=m
 build_DELUDE=m
+build_DHCPADDR=m
 build_ECHO=
 build_IPMARK=m
 build_LOGMARK=m