diff --git a/extensions/Kbuild b/extensions/Kbuild index 54c04ef..2717b31 100644 --- a/extensions/Kbuild +++ b/extensions/Kbuild @@ -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 diff --git a/extensions/Mbuild b/extensions/Mbuild index cf9e51c..d19da8e 100644 --- a/extensions/Mbuild +++ b/extensions/Mbuild @@ -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 diff --git a/extensions/compat_xtables.c b/extensions/compat_xtables.c index 3c40863..8dc9f00 100644 --- a/extensions/compat_xtables.c +++ b/extensions/compat_xtables.c @@ -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"); diff --git a/extensions/compat_xtables.h b/extensions/compat_xtables.h index 9d2d2d2..86f573e 100644 --- a/extensions/compat_xtables.h +++ b/extensions/compat_xtables.h @@ -59,6 +59,12 @@ # 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 index 0000000..4171e5a --- /dev/null +++ b/extensions/libxt_DHCPADDR.c @@ -0,0 +1,101 @@ +/* + * "DHCPADDR" target extension for iptables + * Copyright © Jan Engelhardt , 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 +#include +#include +#include +#include +#include +#include +#include +#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 index 0000000..09f545d --- /dev/null +++ b/extensions/libxt_DHCPADDR.man @@ -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 index 0000000..ac4da32 --- /dev/null +++ b/extensions/libxt_dhcpaddr.c @@ -0,0 +1,102 @@ +/* + * "dhcpaddr" match extension for iptables + * Copyright © Jan Engelhardt , 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 +#include +#include +#include +#include +#include +#include +#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 index 0000000..af678d1 --- /dev/null +++ b/extensions/libxt_dhcpaddr.man @@ -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 index 0000000..1149791 --- /dev/null +++ b/extensions/mac.c @@ -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 index 0000000..f57089d --- /dev/null +++ b/extensions/xt_DHCPADDR.c @@ -0,0 +1,175 @@ +/* + * "DHCPADDR" extensions for Xtables + * Copyright © Jan Engelhardt , 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 +#include +#include +#include +#include +#include +#include +#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 "); +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 index 0000000..8f926cf --- /dev/null +++ b/extensions/xt_DHCPADDR.h @@ -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 a860284..5fe5828 100644 --- 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