nf_conntrack_proto_icmp.c 9.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/* (C) 1999-2001 Paul `Rusty' Russell
 * (C) 2002-2004 Netfilter Core Team <coreteam@netfilter.org>
 *
 * 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/types.h>
#include <linux/timer.h>
#include <linux/netfilter.h>
#include <linux/in.h>
#include <linux/icmp.h>
#include <linux/seq_file.h>
#include <net/ip.h>
#include <net/checksum.h>
#include <linux/netfilter_ipv4.h>
#include <net/netfilter/nf_conntrack_tuple.h>
19
#include <net/netfilter/nf_conntrack_l4proto.h>
20
#include <net/netfilter/nf_conntrack_core.h>
21
#include <net/netfilter/nf_log.h>
22

23
static unsigned long nf_ct_icmp_timeout __read_mostly = 30*HZ;
24

25 26
static bool icmp_pkt_to_tuple(const struct sk_buff *skb, unsigned int dataoff,
			      struct nf_conntrack_tuple *tuple)
27
{
28 29
	const struct icmphdr *hp;
	struct icmphdr _hdr;
30 31 32

	hp = skb_header_pointer(skb, dataoff, sizeof(_hdr), &_hdr);
	if (hp == NULL)
33
		return false;
34 35 36 37 38

	tuple->dst.u.icmp.type = hp->type;
	tuple->src.u.icmp.id = hp->un.echo.id;
	tuple->dst.u.icmp.code = hp->code;

39
	return true;
40 41
}

42 43 44 45 46 47 48 49 50 51 52 53
/* Add 1; spaces filled with 0. */
static const u_int8_t invmap[] = {
	[ICMP_ECHO] = ICMP_ECHOREPLY + 1,
	[ICMP_ECHOREPLY] = ICMP_ECHO + 1,
	[ICMP_TIMESTAMP] = ICMP_TIMESTAMPREPLY + 1,
	[ICMP_TIMESTAMPREPLY] = ICMP_TIMESTAMP + 1,
	[ICMP_INFO_REQUEST] = ICMP_INFO_REPLY + 1,
	[ICMP_INFO_REPLY] = ICMP_INFO_REQUEST + 1,
	[ICMP_ADDRESS] = ICMP_ADDRESSREPLY + 1,
	[ICMP_ADDRESSREPLY] = ICMP_ADDRESS + 1
};

54 55
static bool icmp_invert_tuple(struct nf_conntrack_tuple *tuple,
			      const struct nf_conntrack_tuple *orig)
56 57 58
{
	if (orig->dst.u.icmp.type >= sizeof(invmap)
	    || !invmap[orig->dst.u.icmp.type])
59
		return false;
60 61 62 63

	tuple->src.u.icmp.id = orig->src.u.icmp.id;
	tuple->dst.u.icmp.type = invmap[orig->dst.u.icmp.type] - 1;
	tuple->dst.u.icmp.code = orig->dst.u.icmp.code;
64
	return true;
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
}

/* Print out the per-protocol part of the tuple. */
static int icmp_print_tuple(struct seq_file *s,
			    const struct nf_conntrack_tuple *tuple)
{
	return seq_printf(s, "type=%u code=%u id=%u ",
			  tuple->dst.u.icmp.type,
			  tuple->dst.u.icmp.code,
			  ntohs(tuple->src.u.icmp.id));
}

/* Returns verdict for packet, or -1 for invalid. */
static int icmp_packet(struct nf_conn *ct,
		       const struct sk_buff *skb,
		       unsigned int dataoff,
		       enum ip_conntrack_info ctinfo,
		       int pf,
		       unsigned int hooknum)
{
	/* Try to delete connection immediately after all replies:
86 87 88
	   won't actually vanish as we still have skb, and del_timer
	   means this will only run once even if count hits zero twice
	   (theoretically possible with SMP) */
89 90 91 92 93 94 95 96 97 98 99 100 101 102
	if (CTINFO2DIR(ctinfo) == IP_CT_DIR_REPLY) {
		if (atomic_dec_and_test(&ct->proto.icmp.count)
		    && del_timer(&ct->timeout))
			ct->timeout.function((unsigned long)ct);
	} else {
		atomic_inc(&ct->proto.icmp.count);
		nf_conntrack_event_cache(IPCT_PROTOINFO_VOLATILE, skb);
		nf_ct_refresh_acct(ct, ctinfo, skb, nf_ct_icmp_timeout);
	}

	return NF_ACCEPT;
}

/* Called when a new connection for this protocol found. */
103 104
static bool icmp_new(struct nf_conn *ct, const struct sk_buff *skb,
		     unsigned int dataoff)
105
{
106 107 108 109 110 111
	static const u_int8_t valid_new[] = {
		[ICMP_ECHO] = 1,
		[ICMP_TIMESTAMP] = 1,
		[ICMP_INFO_REQUEST] = 1,
		[ICMP_ADDRESS] = 1
	};
112

113 114
	if (ct->tuplehash[0].tuple.dst.u.icmp.type >= sizeof(valid_new)
	    || !valid_new[ct->tuplehash[0].tuple.dst.u.icmp.type]) {
115
		/* Can't create a new ICMP `conn' with this. */
116
		pr_debug("icmp: can't create new conn with type %u\n",
117 118
			 ct->tuplehash[0].tuple.dst.u.icmp.type);
		NF_CT_DUMP_TUPLE(&ct->tuplehash[0].tuple);
119
		return false;
120
	}
121
	atomic_set(&ct->proto.icmp.count, 0);
122
	return true;
123 124 125 126 127
}

/* Returns conntrack if it dealt with ICMP, and filled in skb fields */
static int
icmp_error_message(struct sk_buff *skb,
128 129
		 enum ip_conntrack_info *ctinfo,
		 unsigned int hooknum)
130 131
{
	struct nf_conntrack_tuple innertuple, origtuple;
132 133
	const struct nf_conntrack_l4proto *innerproto;
	const struct nf_conntrack_tuple_hash *h;
134 135 136

	NF_CT_ASSERT(skb->nfct == NULL);

137 138 139 140 141 142
	/* Are they talking about one of our connections? */
	if (!nf_ct_get_tuplepr(skb,
			       skb_network_offset(skb) + ip_hdrlen(skb)
						       + sizeof(struct icmphdr),
			       PF_INET, &origtuple)) {
		pr_debug("icmp_error_message: failed to get tuple\n");
143 144 145
		return -NF_ACCEPT;
	}

146
	/* rcu_read_lock()ed by nf_hook_slow */
147
	innerproto = __nf_ct_l4proto_find(PF_INET, origtuple.dst.protonum);
148

149 150 151
	/* Ordinarily, we'd expect the inverted tupleproto, but it's
	   been preserved inside the ICMP. */
	if (!nf_ct_invert_tuple(&innertuple, &origtuple,
152
				&nf_conntrack_l3proto_ipv4, innerproto)) {
153
		pr_debug("icmp_error_message: no match\n");
154 155 156 157 158
		return -NF_ACCEPT;
	}

	*ctinfo = IP_CT_RELATED;

159
	h = nf_conntrack_find_get(&innertuple);
160
	if (!h) {
161 162
		pr_debug("icmp_error_message: no match\n");
		return -NF_ACCEPT;
163 164
	}

165 166 167
	if (NF_CT_DIRECTION(h) == IP_CT_DIR_REPLY)
		*ctinfo += IP_CT_IS_REPLY;

168 169 170 171
	/* Update skb to refer to this connection */
	skb->nfct = &nf_ct_tuplehash_to_ctrack(h)->ct_general;
	skb->nfctinfo = *ctinfo;
	return -NF_ACCEPT;
172 173 174 175 176 177 178
}

/* Small and modified version of icmp_rcv */
static int
icmp_error(struct sk_buff *skb, unsigned int dataoff,
	   enum ip_conntrack_info *ctinfo, int pf, unsigned int hooknum)
{
179 180
	const struct icmphdr *icmph;
	struct icmphdr _ih;
181 182

	/* Not enough header? */
183
	icmph = skb_header_pointer(skb, ip_hdrlen(skb), sizeof(_ih), &_ih);
184 185 186 187 188 189 190 191
	if (icmph == NULL) {
		if (LOG_INVALID(IPPROTO_ICMP))
			nf_log_packet(PF_INET, 0, skb, NULL, NULL, NULL,
				      "nf_ct_icmp: short packet ");
		return -NF_ACCEPT;
	}

	/* See ip_conntrack_proto_tcp.c */
192
	if (nf_conntrack_checksum && hooknum == NF_INET_PRE_ROUTING &&
193
	    nf_ip_checksum(skb, hooknum, dataoff, 0)) {
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
		if (LOG_INVALID(IPPROTO_ICMP))
			nf_log_packet(PF_INET, 0, skb, NULL, NULL, NULL,
				      "nf_ct_icmp: bad HW ICMP checksum ");
		return -NF_ACCEPT;
	}

	/*
	 *	18 is the highest 'known' ICMP type. Anything else is a mystery
	 *
	 *	RFC 1122: 3.2.2  Unknown ICMP messages types MUST be silently
	 *		  discarded.
	 */
	if (icmph->type > NR_ICMP_TYPES) {
		if (LOG_INVALID(IPPROTO_ICMP))
			nf_log_packet(PF_INET, 0, skb, NULL, NULL, NULL,
				      "nf_ct_icmp: invalid ICMP type ");
		return -NF_ACCEPT;
	}

	/* Need to track icmp error message? */
	if (icmph->type != ICMP_DEST_UNREACH
	    && icmph->type != ICMP_SOURCE_QUENCH
	    && icmph->type != ICMP_TIME_EXCEEDED
	    && icmph->type != ICMP_PARAMETERPROB
	    && icmph->type != ICMP_REDIRECT)
		return NF_ACCEPT;

	return icmp_error_message(skb, ctinfo, hooknum);
}

224
#if defined(CONFIG_NF_CT_NETLINK) || defined(CONFIG_NF_CT_NETLINK_MODULE)
225 226 227 228

#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nfnetlink_conntrack.h>

229
static int icmp_tuple_to_nlattr(struct sk_buff *skb,
230 231
				const struct nf_conntrack_tuple *t)
{
232 233 234
	NLA_PUT_BE16(skb, CTA_PROTO_ICMP_ID, t->src.u.icmp.id);
	NLA_PUT_U8(skb, CTA_PROTO_ICMP_TYPE, t->dst.u.icmp.type);
	NLA_PUT_U8(skb, CTA_PROTO_ICMP_CODE, t->dst.u.icmp.code);
235 236 237

	return 0;

238
nla_put_failure:
239 240 241
	return -1;
}

242 243 244 245
static const struct nla_policy icmp_nla_policy[CTA_PROTO_MAX+1] = {
	[CTA_PROTO_ICMP_TYPE]	= { .type = NLA_U8 },
	[CTA_PROTO_ICMP_CODE]	= { .type = NLA_U8 },
	[CTA_PROTO_ICMP_ID]	= { .type = NLA_U16 },
246 247
};

248
static int icmp_nlattr_to_tuple(struct nlattr *tb[],
249 250
				struct nf_conntrack_tuple *tuple)
{
251 252 253
	if (!tb[CTA_PROTO_ICMP_TYPE]
	    || !tb[CTA_PROTO_ICMP_CODE]
	    || !tb[CTA_PROTO_ICMP_ID])
254 255
		return -EINVAL;

256 257 258
	tuple->dst.u.icmp.type = nla_get_u8(tb[CTA_PROTO_ICMP_TYPE]);
	tuple->dst.u.icmp.code = nla_get_u8(tb[CTA_PROTO_ICMP_CODE]);
	tuple->src.u.icmp.id = nla_get_be16(tb[CTA_PROTO_ICMP_ID]);
259 260 261 262 263 264 265 266 267

	if (tuple->dst.u.icmp.type >= sizeof(invmap)
	    || !invmap[tuple->dst.u.icmp.type])
		return -EINVAL;

	return 0;
}
#endif

268 269 270 271 272 273 274 275 276 277
#ifdef CONFIG_SYSCTL
static struct ctl_table_header *icmp_sysctl_header;
static struct ctl_table icmp_sysctl_table[] = {
	{
		.procname	= "nf_conntrack_icmp_timeout",
		.data		= &nf_ct_icmp_timeout,
		.maxlen		= sizeof(unsigned int),
		.mode		= 0644,
		.proc_handler	= &proc_dointvec_jiffies,
	},
278
	{
279 280 281
		.ctl_name = 0
	}
};
282 283 284 285 286 287 288 289 290
#ifdef CONFIG_NF_CONNTRACK_PROC_COMPAT
static struct ctl_table icmp_compat_sysctl_table[] = {
	{
		.procname	= "ip_conntrack_icmp_timeout",
		.data		= &nf_ct_icmp_timeout,
		.maxlen		= sizeof(unsigned int),
		.mode		= 0644,
		.proc_handler	= &proc_dointvec_jiffies,
	},
291
	{
292 293 294 295
		.ctl_name = 0
	}
};
#endif /* CONFIG_NF_CONNTRACK_PROC_COMPAT */
296 297
#endif /* CONFIG_SYSCTL */

298
struct nf_conntrack_l4proto nf_conntrack_l4proto_icmp __read_mostly =
299 300
{
	.l3proto		= PF_INET,
301
	.l4proto		= IPPROTO_ICMP,
302 303 304 305 306 307 308 309
	.name			= "icmp",
	.pkt_to_tuple		= icmp_pkt_to_tuple,
	.invert_tuple		= icmp_invert_tuple,
	.print_tuple		= icmp_print_tuple,
	.packet			= icmp_packet,
	.new			= icmp_new,
	.error			= icmp_error,
	.destroy		= NULL,
310
	.me			= NULL,
311
#if defined(CONFIG_NF_CT_NETLINK) || defined(CONFIG_NF_CT_NETLINK_MODULE)
312 313
	.tuple_to_nlattr	= icmp_tuple_to_nlattr,
	.nlattr_to_tuple	= icmp_nlattr_to_tuple,
314
	.nla_policy		= icmp_nla_policy,
315
#endif
316 317 318
#ifdef CONFIG_SYSCTL
	.ctl_table_header	= &icmp_sysctl_header,
	.ctl_table		= icmp_sysctl_table,
319 320 321
#ifdef CONFIG_NF_CONNTRACK_PROC_COMPAT
	.ctl_compat_table	= icmp_compat_sysctl_table,
#endif
322
#endif
323
};