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
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
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");
# 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
--- /dev/null
+/*
+ * "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);
+}
--- /dev/null
+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.)
--- /dev/null
+/*
+ * "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);
+}
--- /dev/null
+.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.
--- /dev/null
+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;
+}
--- /dev/null
+/*
+ * "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");
--- /dev/null
+#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 */
#
build_CHAOS=m
build_DELUDE=m
+build_DHCPADDR=m
build_ECHO=
build_IPMARK=m
build_LOGMARK=m