Files
xtables-addons/extensions/xt_length2.c
2010-02-24 00:29:55 +01:00

263 lines
6.9 KiB
C

/*
* xt_length - Netfilter module to match packet length
* Copyright © Jan Engelhardt <jengelh@medozas.de>, 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 <linux/dccp.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/icmp.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/sctp.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <net/ip.h>
#include <net/ipv6.h>
#include <linux/netfilter/x_tables.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#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 <jengelh@medozas.de>");
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);
}
}
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
/**
* 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_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);