Introduce xt_length match revision 1. It adds support for layer-4, layer-5 and layer-7 length matching. It is much easier than writing up the according xt_u32 magic. This can be used for packet scheduling; specific example are online games where all data is transferred over the same port, but the regular gameplay has a characteristically lower packet size than bulk downloads of game maps. (Tested with Unreal Tournament 99.) Signed-off-by: Jan Engelhardt <jengelh@xxxxxxxxxxxxxxx> --- Documentation/feature-removal-schedule.txt | 3 + include/linux/netfilter/xt_length.h | 21 ++ net/netfilter/xt_length.c | 267 ++++++++++++++++++-- 3 files changed, 273 insertions(+), 18 deletions(-) diff --git a/Documentation/feature-removal-schedule.txt b/Documentation/feature-removal-schedule.txt index ee3cc8b..0209a5a 100644 --- a/Documentation/feature-removal-schedule.txt +++ b/Documentation/feature-removal-schedule.txt @@ -243,6 +243,9 @@ What (Why): include/linux/netfilter_ipv4/ipt_iprange.h (superseded by xt_iprange match revision 1) + - xt_length match revision 0 + (superseded by xt_length match revision 1) + - xt_mark match revision 0 (superseded by xt_mark match revision 1) diff --git a/include/linux/netfilter/xt_length.h b/include/linux/netfilter/xt_length.h index 7c2b439..4e70268 100644 --- a/include/linux/netfilter/xt_length.h +++ b/include/linux/netfilter/xt_length.h @@ -6,4 +6,25 @@ struct xt_length_info { u_int8_t invert; }; +enum { + XT_LENGTH_INVERT = 1 << 0, + + /* IP header plus payload */ + XT_LENGTH_LAYER3 = 1 << 1, + + /* TCP/UDP/etc. header plus payload */ + XT_LENGTH_LAYER4 = 1 << 2, + + /* TCP/UDP/etc. payload */ + XT_LENGTH_LAYER5 = 1 << 3, + + /* SCTP payload */ + XT_LENGTH_LAYER7 = 1 << 4, +}; + +struct xt_length_mtinfo1 { + __u32 min, max; + __u16 flags; +}; + #endif /*_XT_LENGTH_H*/ diff --git a/net/netfilter/xt_length.c b/net/netfilter/xt_length.c index b8640f9..d874fa2 100644 --- a/net/netfilter/xt_length.c +++ b/net/netfilter/xt_length.c @@ -1,30 +1,40 @@ -/* Kernel module to match packet length. */ -/* (C) 1999-2001 James Morris <jmorros@xxxxxxxxxxxxxxxx> +/* + * xt_length - Netfilter module to match packet length * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. + * (C) 1999-2001 James Morris <jmorros@xxxxxxxxxxxxxxxx> + * Copyright © CC Computer Consultants GmbH, 2007-2008 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. */ - +#include <linux/dccp.h> #include <linux/module.h> +#include <linux/sctp.h> #include <linux/skbuff.h> +#include <linux/icmp.h> +#include <linux/ip.h> #include <linux/ipv6.h> +#include <linux/tcp.h> +#include <linux/udp.h> #include <net/ip.h> - -#include <linux/netfilter/xt_length.h> +#include <net/ipv6.h> #include <linux/netfilter/x_tables.h> +#include <linux/netfilter/xt_length.h> +#include <linux/netfilter_ipv6/ip6_tables.h> MODULE_AUTHOR("James Morris <jmorris@xxxxxxxxxxxxxxxx>"); +MODULE_AUTHOR("Jan Engelhardt <jengelh@xxxxxxxxxxxxxxx>"); MODULE_DESCRIPTION("Xtables: Packet length (Layer3,4,5) match"); MODULE_LICENSE("GPL"); MODULE_ALIAS("ipt_length"); MODULE_ALIAS("ip6t_length"); static bool -length_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) +length_mt_v0(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 xt_length_info *info = matchinfo; u_int16_t pktlen = ntohs(ip_hdr(skb)->tot_len); @@ -33,10 +43,10 @@ length_mt(const struct sk_buff *skb, const struct net_device *in, } static bool -length_mt6(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) +length_mt6_v0(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 xt_length_info *info = matchinfo; const u_int16_t pktlen = ntohs(ipv6_hdr(skb)->payload_len) + @@ -45,21 +55,242 @@ length_mt6(const struct sk_buff *skb, const struct net_device *in, return (pktlen >= info->min && pktlen <= info->max) ^ info->invert; } +/* + * GCC will decide if these functions (xtlength_layer?_*) are + * good enough for inlining, we should not act overly smart about + * these decisions. + */ + +static bool xtlength_layer5_tcp(unsigned int *length, const struct sk_buff *skb, + unsigned int offset) +{ + const struct tcphdr *tcph; + struct tcphdr buf; + + tcph = skb_header_pointer(skb, offset, sizeof(buf), &buf); + if (tcph == NULL) + return false; + + *length = skb->len - offset; + if (*length >= 4 * tcph->doff) + *length -= 4 * tcph->doff; + return true; +} + +static bool +xtlength_layer5_dccp(unsigned int *length, const struct sk_buff *skb, + unsigned int offset) +{ + const struct dccp_hdr *dh; + struct dccp_hdr dhbuf; + + dh = skb_header_pointer(skb, offset, sizeof(dhbuf), &dhbuf); + if (dh == NULL) + return false; + + *length = skb->len - offset; + if (*length >= 4 * dh->dccph_doff) + *length -= 4 * dh->dccph_doff; + return true; +} + +static bool xtlength_layer5(unsigned int *length, const struct sk_buff *skb, + unsigned int proto, unsigned int offset) +{ + switch (proto) { + case IPPROTO_TCP: + return xtlength_layer5_tcp(length, skb, offset); + case IPPROTO_UDP: + case IPPROTO_UDPLITE: + *length = skb->len - offset - sizeof(struct udphdr); + return true; + case IPPROTO_SCTP: + *length = skb->len - offset - sizeof(struct sctphdr); + return true; + case IPPROTO_DCCP: + return xtlength_layer5_dccp(length, skb, offset); + case IPPROTO_ICMP: + *length = skb->len - offset - sizeof(struct icmphdr); + return true; + case IPPROTO_ICMPV6: + *length = skb->len - offset - + offsetof(struct icmp6hdr, icmp6_dataun); + return true; + case IPPROTO_AH: + *length = skb->len - offset - sizeof(struct ip_auth_hdr); + return true; + case IPPROTO_ESP: + *length = skb->len - offset - sizeof(struct ip_esp_hdr); + return true; + default: + return false; + } +} + +static bool +xtlength_layer7_sctp(unsigned int *length, const struct sk_buff *skb, + unsigned int offset) +{ + const struct sctp_chunkhdr *ch; + struct sctp_chunkhdr chbuf; + unsigned int pos; + + *length = 0; + for (pos = sizeof(struct sctphdr); pos < skb->len; + pos += ntohs(ch->length)) { + ch = skb_header_pointer(skb, offset + pos, + sizeof(chbuf), &chbuf); + if (ch == NULL) + return false; + if (ch->type != SCTP_CID_DATA) + continue; + *length += ntohs(ch->length); + } + return true; +} + +static bool xtlength_layer7(unsigned int *length, const struct sk_buff *skb, + unsigned int proto, unsigned int offset) +{ + switch (proto) { + case IPPROTO_SCTP: + return xtlength_layer7_sctp(length, skb, offset); + default: + return xtlength_layer5(length, skb, proto, offset); + } +} + +/* + * llayer4_proto - figure out the L4 protocol in an IPv6 packet + * @skb: skb pointer + * @offset: position at which L4 starts (equal to 'protoff' in IPv4 code) + * @hotdrop: hotdrop pointer + * + * Searches for a recognized L4 header. On success, fills in @offset and + * returns the protocol number. If not found, %NEXTHDR_MAX is returned. + * On error, @hotdrop is set. + */ +static unsigned int +llayer4_proto(const struct sk_buff *skb, unsigned int *offset, bool *hotdrop) +{ + /* + * Do encapsulation first so that %IPPROTO_TCP does not hit the TCP + * part in an IPv6-in-IPv6 encapsulation, for example. + */ + static const unsigned int types[] = + {IPPROTO_IPV6, IPPROTO_IPIP, IPPROTO_ESP, IPPROTO_AH, + IPPROTO_ICMP, IPPROTO_TCP, IPPROTO_UDP, IPPROTO_UDPLITE, + IPPROTO_SCTP, IPPROTO_DCCP}; + unsigned int i; + int err; + + for (i = 0; i < ARRAY_SIZE(types); ++i) { + err = ipv6_find_hdr(skb, offset, types[i], NULL); + if (err >= 0) + return types[i]; + if (err != -ENOENT) { + *hotdrop = true; + break; + } + } + + return NEXTHDR_MAX; +} + +static bool +length_mt4(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 xt_length_mtinfo1 *info = matchinfo; + const struct iphdr *iph = ip_hdr(skb); + unsigned int len = 0; + bool hit = true; + + if (info->flags & XT_LENGTH_LAYER3) + len = ntohs(iph->tot_len); + else if (info->flags & XT_LENGTH_LAYER4) + len = ntohs(iph->tot_len) - protoff; + else if (info->flags & XT_LENGTH_LAYER5) + hit = xtlength_layer5(&len, skb, iph->protocol, protoff); + else if (info->flags & XT_LENGTH_LAYER7) + hit = xtlength_layer7(&len, skb, iph->protocol, protoff); + if (!hit) + return false; + + return (len >= info->min && len <= info->max) ^ + !!(info->flags & XT_LENGTH_INVERT); +} + +#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE) +static bool +length_mt6(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 xt_length_mtinfo1 *info = matchinfo; + const struct ipv6hdr *iph = ipv6_hdr(skb); + unsigned int len = 0, l4proto; + bool hit = true; + + if (info->flags & XT_LENGTH_LAYER3) { + len = sizeof(struct ipv6hdr) + ntohs(iph->payload_len); + } else { + l4proto = llayer4_proto(skb, &protoff, hotdrop); + if (l4proto == NEXTHDR_MAX) + return false; + if (info->flags & XT_LENGTH_LAYER4) + len = skb->len - protoff; + else if (info->flags & XT_LENGTH_LAYER5) + hit = xtlength_layer5(&len, skb, l4proto, protoff); + else if (info->flags & XT_LENGTH_LAYER7) + hit = xtlength_layer7(&len, skb, l4proto, protoff); + } + if (!hit) + return false; + + return (len >= info->min && len <= info->max) ^ + !!(info->flags & XT_LENGTH_INVERT); +} +#endif + static struct xt_match length_mt_reg[] __read_mostly = { { .name = "length", + .revision = 0, .family = AF_INET, - .match = length_mt, + .match = length_mt_v0, .matchsize = sizeof(struct xt_length_info), .me = THIS_MODULE, }, { .name = "length", + .revision = 0, .family = AF_INET6, - .match = length_mt6, + .match = length_mt6_v0, .matchsize = sizeof(struct xt_length_info), .me = THIS_MODULE, }, + { + .name = "length", + .revision = 1, + .family = AF_INET, + .match = length_mt4, + .matchsize = sizeof(struct xt_length_mtinfo1), + .me = THIS_MODULE, + }, +#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE) + { + .name = "length", + .revision = 1, + .family = AF_INET6, + .match = length_mt6, + .matchsize = sizeof(struct xt_length_mtinfo1), + .me = THIS_MODULE, + }, +#endif }; static int __init length_mt_init(void) -- 1.5.5.rc3 -- To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html