diff --git a/extensions/Kbuild b/extensions/Kbuild index 2717b31..25fa72f 100644 --- a/extensions/Kbuild +++ b/extensions/Kbuild @@ -19,6 +19,7 @@ obj-${build_fuzzy} += xt_fuzzy.o obj-${build_geoip} += xt_geoip.o obj-${build_ipp2p} += xt_ipp2p.o obj-${build_ipset} += ipset/ +obj-${build_length2} += xt_length2.o obj-${build_portscan} += xt_portscan.o obj-${build_quota2} += xt_quota2.o diff --git a/extensions/Mbuild b/extensions/Mbuild index 6fbda26..05f6485 100644 --- a/extensions/Mbuild +++ b/extensions/Mbuild @@ -12,5 +12,6 @@ obj-${build_fuzzy} += libxt_fuzzy.so obj-${build_geoip} += libxt_geoip.so obj-${build_ipp2p} += libxt_ipp2p.so obj-${build_ipset} += ipset/ +obj-${build_length2} += libxt_length2.so obj-${build_portscan} += libxt_portscan.so obj-${build_quota2} += libxt_quota2.so diff --git a/extensions/libxt_length.man b/extensions/libxt_length.man new file mode 100644 index 0000000..235504e --- /dev/null +++ b/extensions/libxt_length.man @@ -0,0 +1,18 @@ +This module matches the length of a packet against a specific value or range of +values. +.TP +[\fB!\fR] \fB--length\fR \fIlength\fR[\fB:\fR\fIlength\fR] +Match exact length or length range. +.TP +\fB--layer3\fR +Match the layer3 frame size (e.g. IPv4/v6 header plus payload). +.TP +\fB--layer4\fR +Match the layer4 frame size (e.g. TCP/UDP header plus payload). +.TP +\fB--layer5\fR +Match the layer5 frame size (e.g. TCP/UDP payload, often called layer7). +.PP +If no --layer* option is given, --layer3 is assumed by default. Note that using +--layer5 may not match a packet if it is not one of the recognized types +(currently TCP, UDP, UDPLite, ICMP, AH and ESP) or which has no 5th layer. diff --git a/extensions/libxt_length2.c b/extensions/libxt_length2.c new file mode 100644 index 0000000..130a321 --- /dev/null +++ b/extensions/libxt_length2.c @@ -0,0 +1,173 @@ +#include +#include +#include +#include +#include +#include +#include "xt_length2.h" + +enum { + F_LAYER = 1 << 0, + F_LENGTH = 1 << 1, + + XT_LENGTH_LAYER_MASK = XT_LENGTH_LAYER3 | XT_LENGTH_LAYER4 | + XT_LENGTH_LAYER5 | XT_LENGTH_LAYER7, +}; + +static void length_mt_help(void) +{ + printf( +"length match options:\n" +" --layer3 Match against layer3 size (e.g. L4 + IPv6 header)\n" +" --layer4 Match against layer4 size (e.g. L5 + SCTP header)\n" +" --layer5 Match against layer5 size (e.g. L7 + chunk headers)\n" +" --layer7 Match against layer7 payload (e.g. SCTP payload)\n" +"[!] --length n[:n] Match packet length against value or range\n" +" of values (inclusive)\n" +); +} + +static const struct option length_mt_opts[] = { + {.name = "layer3", .has_arg = false, .val = '3'}, + {.name = "layer4", .has_arg = false, .val = '4'}, + {.name = "layer5", .has_arg = false, .val = '5'}, + {.name = "layer7", .has_arg = false, .val = '7'}, + {.name = "length", .has_arg = true, .val = '='}, + {NULL}, +}; + +static void length_mt_init(struct xt_entry_match *match) +{ + struct xt_length_mtinfo2 *info = (void *)match->data; + + info->flags = XT_LENGTH_LAYER3; +} + +static int length_mt_parse(int c, char **argv, int invert, unsigned int *flags, + const void *entry, struct xt_entry_match **match) +{ + struct xt_length_mtinfo2 *info = (void *)(*match)->data; + unsigned int from, to; + char *end; + + switch (c) { + case '3': /* --layer3 */ + param_act(P_ONLY_ONCE, "length", "--layer*", *flags & F_LAYER); + info->flags &= ~XT_LENGTH_LAYER_MASK; + info->flags |= XT_LENGTH_LAYER3; + *flags |= F_LAYER; + return true; + case '4': /* --layer4 */ + param_act(P_ONLY_ONCE, "length", "--layer*", *flags & F_LAYER); + info->flags &= ~XT_LENGTH_LAYER_MASK; + info->flags |= XT_LENGTH_LAYER4; + *flags |= F_LAYER; + return true; + case '5': /* --layer5 */ + param_act(P_ONLY_ONCE, "length", "--layer*", *flags & F_LAYER); + info->flags &= ~XT_LENGTH_LAYER_MASK; + info->flags |= XT_LENGTH_LAYER5; + *flags |= F_LAYER; + return true; + case '7': /* --layer7 */ + param_act(P_ONLY_ONCE, "length", "--layer*", *flags & F_LAYER); + info->flags &= ~XT_LENGTH_LAYER_MASK; + info->flags |= XT_LENGTH_LAYER7; + *flags |= F_LAYER; + return true; + case '=': /* --length */ + param_act(P_ONLY_ONCE, "length", "--length", *flags & F_LENGTH); + if (invert) + info->flags |= XT_LENGTH_INVERT; + if (!strtonum(optarg, &end, &from, 0, ~0U)) + param_act(P_BAD_VALUE, "length", "--length", optarg); + to = from; + if (*end == ':') + if (!strtonum(end + 1, &end, &to, 0, ~0U)) + param_act(P_BAD_VALUE, "length", + "--length", optarg); + if (*end != '\0') + param_act(P_BAD_VALUE, "length", "--length", optarg); + info->min = from; + info->max = to; + *flags |= F_LENGTH; + return true; + } + return false; +} + +static void length_mt_check(unsigned int flags) +{ + if (!(flags & F_LENGTH)) + exit_error(PARAMETER_PROBLEM, + "length: You must specify \"--length\""); + if (!(flags & F_LAYER)) + fprintf(stderr, "iptables: length match: Defaulting to " + "--layer3. Consider specifying it explicitly.\n"); +} + +static void length_mt_print(const void *ip, const struct xt_entry_match *match, + int numeric) +{ + const struct xt_length_mtinfo2 *info = (const void *)match->data; + + if (info->flags & XT_LENGTH_LAYER3) + printf("layer3 "); + else if (info->flags & XT_LENGTH_LAYER4) + printf("layer4 "); + else if (info->flags & XT_LENGTH_LAYER5) + printf("layer5 "); + else if (info->flags & XT_LENGTH_LAYER7) + printf("layer7 "); + printf("length "); + if (info->flags & XT_LENGTH_INVERT) + printf("! "); + if (info->min == info->max) + printf("%u ", (unsigned int)info->min); + else + printf("%u-%u ", (unsigned int)info->min, + (unsigned int)info->max); +} + +static void length_mt_save(const void *ip, const struct xt_entry_match *match) +{ + const struct xt_length_mtinfo2 *info = (const void *)match->data; + + if (info->flags & XT_LENGTH_LAYER3) + printf("--layer3 "); + else if (info->flags & XT_LENGTH_LAYER4) + printf("--layer4 "); + else if (info->flags & XT_LENGTH_LAYER5) + printf("--layer5 "); + else if (info->flags & XT_LENGTH_LAYER7) + printf("--layer7 "); + if (info->flags & XT_LENGTH_INVERT) + printf("! "); + printf("--length "); + if (info->min == info->max) + printf("%u ", (unsigned int)info->min); + else + printf("%u:%u ", (unsigned int)info->min, + (unsigned int)info->max); +} + +static struct xtables_match length2_mt_reg = { + .version = XTABLES_VERSION, + .name = "length2", + .revision = 2, + .family = PF_UNSPEC, + .size = XT_ALIGN(sizeof(struct xt_length_mtinfo2)), + .userspacesize = XT_ALIGN(sizeof(struct xt_length_mtinfo2)), + .init = length_mt_init, + .help = length_mt_help, + .parse = length_mt_parse, + .final_check = length_mt_check, + .print = length_mt_print, + .save = length_mt_save, + .extra_opts = length_mt_opts, +}; + +static void _init(void) +{ + xtables_register_match(&length2_mt_reg); +} diff --git a/extensions/xt_length2.c b/extensions/xt_length2.c new file mode 100644 index 0000000..a329cbd --- /dev/null +++ b/extensions/xt_length2.c @@ -0,0 +1,262 @@ +/* + * xt_length - Netfilter module to match packet length + * Copyright © Jan Engelhardt , 2007 - 2009 + * + * 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 +#include +#include +#include +#include +#include "xt_length2.h" +#include "compat_xtables.h" +#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE) +# define WITH_IPV6 1 +#endif +#ifndef NEXTHDR_IPV4 +# define NEXTHDR_IPV4 4 +#endif + +MODULE_AUTHOR("Jan Engelhardt "); +MODULE_DESCRIPTION("Xtables: Packet length (Layer3,4,5) match"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ipt_length2"); +MODULE_ALIAS("ip6t_length2"); + +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 inline bool +xtlength_layer5(unsigned int *length, const struct sk_buff *skb, + unsigned int prot, unsigned int offset) +{ + switch (prot) { + 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; + } + 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 %NEXTHDR_TCP does not hit the TCP + * part in an IPv6-in-IPv6 encapsulation. + */ + 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 +length2_mt(const struct sk_buff *skb, const struct xt_match_param *par) +{ + const struct xt_length_mtinfo2 *info = par->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) - par->thoff; + else if (info->flags & XT_LENGTH_LAYER5) + hit = xtlength_layer5(&len, skb, iph->protocol, par->thoff); + else if (info->flags & XT_LENGTH_LAYER7) + hit = xtlength_layer7(&len, skb, iph->protocol, par->thoff); + if (!hit) + return false; + + return (len >= info->min && len <= info->max) ^ + !!(info->flags & XT_LENGTH_INVERT); +} + +#ifdef WITH_IPV6 +static bool +length2_mt6(const struct sk_buff *skb, const struct xt_match_param *par) +{ + const struct xt_length_mtinfo2 *info = par->matchinfo; + const struct ipv6hdr *iph = ipv6_hdr(skb); + unsigned int len = 0, l4proto; + unsigned int thoff = par->thoff; + bool hit = true; + + if (info->flags & XT_LENGTH_LAYER3) { + len = sizeof(struct ipv6hdr) + ntohs(iph->payload_len); + } else { + l4proto = llayer4_proto(skb, &thoff, par->hotdrop); + if (l4proto == NEXTHDR_MAX) + return false; + if (info->flags & XT_LENGTH_LAYER4) + len = skb->len - thoff; + else if (info->flags & XT_LENGTH_LAYER5) + hit = xtlength_layer5(&len, skb, l4proto, thoff); + else if (info->flags & XT_LENGTH_LAYER7) + hit = xtlength_layer7(&len, skb, l4proto, thoff); + } + if (!hit) + return false; + + return (len >= info->min && len <= info->max) ^ + !!(info->flags & XT_LENGTH_INVERT); +} +#endif + +static struct xt_match length2_mt_reg[] __read_mostly = { + { + .name = "length2", + .revision = 2, + .family = NFPROTO_IPV4, + .match = length2_mt, + .matchsize = sizeof(struct xt_length_mtinfo2), + .me = THIS_MODULE, + }, +#ifdef WITH_IPV6 + { + .name = "length2", + .revision = 2, + .family = NFPROTO_IPV6, + .match = length2_mt6, + .matchsize = sizeof(struct xt_length_mtinfo2), + .me = THIS_MODULE, + }, +#endif +}; + +static int __init length2_mt_init(void) +{ + return xt_register_matches(length2_mt_reg, ARRAY_SIZE(length2_mt_reg)); +} + +static void __exit length2_mt_exit(void) +{ + xt_unregister_matches(length2_mt_reg, ARRAY_SIZE(length2_mt_reg)); +} + +module_init(length2_mt_init); +module_exit(length2_mt_exit); diff --git a/extensions/xt_length2.h b/extensions/xt_length2.h new file mode 100644 index 0000000..c708476 --- /dev/null +++ b/extensions/xt_length2.h @@ -0,0 +1,22 @@ +#ifndef _LINUX_NETFILTER_XT_LENGTH2_H +#define _LINUX_NETFILTER_XT_LENGTH2_H + +enum { + XT_LENGTH_INVERT = 1 << 0, + + /* IP header plus payload */ + XT_LENGTH_LAYER3 = 1 << 1, + /* Strip IP header: */ + XT_LENGTH_LAYER4 = 1 << 2, + /* Strip TCP/UDP/etc. header */ + XT_LENGTH_LAYER5 = 1 << 3, + /* TCP/UDP/SCTP payload */ + XT_LENGTH_LAYER7 = 1 << 4, +}; + +struct xt_length_mtinfo2 { + u_int32_t min, max; + u_int16_t flags; +}; + +#endif /* _LINUX_NETFILTER_XT_LENGTH2_H */ diff --git a/mconfig b/mconfig index 5fe5828..7dab1a4 100644 --- a/mconfig +++ b/mconfig @@ -14,5 +14,6 @@ build_fuzzy=m build_geoip=m build_ipp2p=m build_ipset=m +build_length2=m build_portscan=m build_quota2=m