netfilter: conntrack: remove l3proto abstraction
This unifies ipv4 and ipv6 protocol trackers and removes the l3proto abstraction. This gets rid of all l3proto indirect calls and the need to do a lookup on the function to call for l3 demux. It increases module size by only a small amount (12kbyte), so this reduces size because nf_conntrack.ko is useless without either nf_conntrack_ipv4 or nf_conntrack_ipv6 module. before: text data bss dec hex filename 7357 1088 0 8445 20fd nf_conntrack_ipv4.ko 7405 1084 4 8493 212d nf_conntrack_ipv6.ko 72614 13689 236 86539 1520b nf_conntrack.ko 19K nf_conntrack_ipv4.ko 19K nf_conntrack_ipv6.ko 179K nf_conntrack.ko after: text data bss dec hex filename 79277 13937 236 93450 16d0a nf_conntrack.ko 191K nf_conntrack.ko Signed-off-by: Florian Westphal <fw@strlen.de> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
This commit is contained in:

committed by
Pablo Neira Ayuso

parent
c779e84960
commit
a0ae2562c6
@@ -9,22 +9,6 @@ config NF_DEFRAG_IPV4
|
||||
tristate
|
||||
default n
|
||||
|
||||
config NF_CONNTRACK_IPV4
|
||||
tristate "IPv4 connection tracking support (required for NAT)"
|
||||
depends on NF_CONNTRACK
|
||||
default m if NETFILTER_ADVANCED=n
|
||||
select NF_DEFRAG_IPV4
|
||||
---help---
|
||||
Connection tracking keeps a record of what packets have passed
|
||||
through your machine, in order to figure out how they are related
|
||||
into connections.
|
||||
|
||||
This is IPv4 support on Layer 3 independent connection tracking.
|
||||
Layer 3 independent connection tracking is experimental scheme
|
||||
which generalize ip_conntrack to support other layer 3 protocols.
|
||||
|
||||
To compile it as a module, choose M here. If unsure, say N.
|
||||
|
||||
config NF_SOCKET_IPV4
|
||||
tristate "IPv4 socket lookup support"
|
||||
help
|
||||
@@ -112,7 +96,7 @@ config NF_REJECT_IPV4
|
||||
|
||||
config NF_NAT_IPV4
|
||||
tristate "IPv4 NAT"
|
||||
depends on NF_CONNTRACK_IPV4
|
||||
depends on NF_CONNTRACK
|
||||
default m if NETFILTER_ADVANCED=n
|
||||
select NF_NAT
|
||||
help
|
||||
@@ -279,7 +263,7 @@ config IP_NF_TARGET_SYNPROXY
|
||||
# NAT + specific targets: nf_conntrack
|
||||
config IP_NF_NAT
|
||||
tristate "iptables NAT support"
|
||||
depends on NF_CONNTRACK_IPV4
|
||||
depends on NF_CONNTRACK
|
||||
default m if NETFILTER_ADVANCED=n
|
||||
select NF_NAT
|
||||
select NF_NAT_IPV4
|
||||
@@ -340,7 +324,7 @@ config IP_NF_MANGLE
|
||||
config IP_NF_TARGET_CLUSTERIP
|
||||
tristate "CLUSTERIP target support"
|
||||
depends on IP_NF_MANGLE
|
||||
depends on NF_CONNTRACK_IPV4
|
||||
depends on NF_CONNTRACK
|
||||
depends on NETFILTER_ADVANCED
|
||||
select NF_CONNTRACK_MARK
|
||||
select NETFILTER_FAMILY_ARP
|
||||
|
@@ -3,12 +3,6 @@
|
||||
# Makefile for the netfilter modules on top of IPv4.
|
||||
#
|
||||
|
||||
# objects for l3 independent conntrack
|
||||
nf_conntrack_ipv4-y := nf_conntrack_l3proto_ipv4.o nf_conntrack_proto_icmp.o
|
||||
|
||||
# connection tracking
|
||||
obj-$(CONFIG_NF_CONNTRACK_IPV4) += nf_conntrack_ipv4.o
|
||||
|
||||
nf_nat_ipv4-y := nf_nat_l3proto_ipv4.o nf_nat_proto_icmp.o
|
||||
nf_nat_ipv4-$(CONFIG_NF_NAT_MASQUERADE_IPV4) += nf_nat_masquerade_ipv4.o
|
||||
obj-$(CONFIG_NF_NAT_IPV4) += nf_nat_ipv4.o
|
||||
|
@@ -1,368 +0,0 @@
|
||||
|
||||
/* (C) 1999-2001 Paul `Rusty' Russell
|
||||
* (C) 2002-2004 Netfilter Core Team <coreteam@netfilter.org>
|
||||
* (C) 2006-2012 Patrick McHardy <kaber@trash.net>
|
||||
*
|
||||
* 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/ip.h>
|
||||
#include <linux/netfilter.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/icmp.h>
|
||||
#include <linux/sysctl.h>
|
||||
#include <net/route.h>
|
||||
#include <net/ip.h>
|
||||
|
||||
#include <linux/netfilter_ipv4.h>
|
||||
#include <net/netfilter/nf_conntrack.h>
|
||||
#include <net/netfilter/nf_conntrack_helper.h>
|
||||
#include <net/netfilter/nf_conntrack_l4proto.h>
|
||||
#include <net/netfilter/nf_conntrack_l3proto.h>
|
||||
#include <net/netfilter/nf_conntrack_zones.h>
|
||||
#include <net/netfilter/nf_conntrack_core.h>
|
||||
#include <net/netfilter/nf_conntrack_seqadj.h>
|
||||
#include <net/netfilter/ipv4/nf_conntrack_ipv4.h>
|
||||
#include <net/netfilter/nf_nat_helper.h>
|
||||
#include <net/netfilter/ipv4/nf_defrag_ipv4.h>
|
||||
#include <net/netfilter/nf_log.h>
|
||||
|
||||
static int conntrack4_net_id __read_mostly;
|
||||
static DEFINE_MUTEX(register_ipv4_hooks);
|
||||
|
||||
struct conntrack4_net {
|
||||
unsigned int users;
|
||||
};
|
||||
|
||||
static unsigned int ipv4_helper(void *priv,
|
||||
struct sk_buff *skb,
|
||||
const struct nf_hook_state *state)
|
||||
{
|
||||
struct nf_conn *ct;
|
||||
enum ip_conntrack_info ctinfo;
|
||||
const struct nf_conn_help *help;
|
||||
const struct nf_conntrack_helper *helper;
|
||||
|
||||
/* This is where we call the helper: as the packet goes out. */
|
||||
ct = nf_ct_get(skb, &ctinfo);
|
||||
if (!ct || ctinfo == IP_CT_RELATED_REPLY)
|
||||
return NF_ACCEPT;
|
||||
|
||||
help = nfct_help(ct);
|
||||
if (!help)
|
||||
return NF_ACCEPT;
|
||||
|
||||
/* rcu_read_lock()ed by nf_hook_thresh */
|
||||
helper = rcu_dereference(help->helper);
|
||||
if (!helper)
|
||||
return NF_ACCEPT;
|
||||
|
||||
return helper->help(skb, skb_network_offset(skb) + ip_hdrlen(skb),
|
||||
ct, ctinfo);
|
||||
}
|
||||
|
||||
static unsigned int ipv4_confirm(void *priv,
|
||||
struct sk_buff *skb,
|
||||
const struct nf_hook_state *state)
|
||||
{
|
||||
struct nf_conn *ct;
|
||||
enum ip_conntrack_info ctinfo;
|
||||
|
||||
ct = nf_ct_get(skb, &ctinfo);
|
||||
if (!ct || ctinfo == IP_CT_RELATED_REPLY)
|
||||
goto out;
|
||||
|
||||
/* adjust seqs for loopback traffic only in outgoing direction */
|
||||
if (test_bit(IPS_SEQ_ADJUST_BIT, &ct->status) &&
|
||||
!nf_is_loopback_packet(skb)) {
|
||||
if (!nf_ct_seq_adjust(skb, ct, ctinfo, ip_hdrlen(skb))) {
|
||||
NF_CT_STAT_INC_ATOMIC(nf_ct_net(ct), drop);
|
||||
return NF_DROP;
|
||||
}
|
||||
}
|
||||
out:
|
||||
/* We've seen it coming out the other side: confirm it */
|
||||
return nf_conntrack_confirm(skb);
|
||||
}
|
||||
|
||||
static unsigned int ipv4_conntrack_in(void *priv,
|
||||
struct sk_buff *skb,
|
||||
const struct nf_hook_state *state)
|
||||
{
|
||||
return nf_conntrack_in(state->net, PF_INET, state->hook, skb);
|
||||
}
|
||||
|
||||
static unsigned int ipv4_conntrack_local(void *priv,
|
||||
struct sk_buff *skb,
|
||||
const struct nf_hook_state *state)
|
||||
{
|
||||
if (ip_is_fragment(ip_hdr(skb))) { /* IP_NODEFRAG setsockopt set */
|
||||
enum ip_conntrack_info ctinfo;
|
||||
struct nf_conn *tmpl;
|
||||
|
||||
tmpl = nf_ct_get(skb, &ctinfo);
|
||||
if (tmpl && nf_ct_is_template(tmpl)) {
|
||||
/* when skipping ct, clear templates to avoid fooling
|
||||
* later targets/matches
|
||||
*/
|
||||
skb->_nfct = 0;
|
||||
nf_ct_put(tmpl);
|
||||
}
|
||||
return NF_ACCEPT;
|
||||
}
|
||||
|
||||
return nf_conntrack_in(state->net, PF_INET, state->hook, skb);
|
||||
}
|
||||
|
||||
/* Connection tracking may drop packets, but never alters them, so
|
||||
make it the first hook. */
|
||||
static const struct nf_hook_ops ipv4_conntrack_ops[] = {
|
||||
{
|
||||
.hook = ipv4_conntrack_in,
|
||||
.pf = NFPROTO_IPV4,
|
||||
.hooknum = NF_INET_PRE_ROUTING,
|
||||
.priority = NF_IP_PRI_CONNTRACK,
|
||||
},
|
||||
{
|
||||
.hook = ipv4_conntrack_local,
|
||||
.pf = NFPROTO_IPV4,
|
||||
.hooknum = NF_INET_LOCAL_OUT,
|
||||
.priority = NF_IP_PRI_CONNTRACK,
|
||||
},
|
||||
{
|
||||
.hook = ipv4_helper,
|
||||
.pf = NFPROTO_IPV4,
|
||||
.hooknum = NF_INET_POST_ROUTING,
|
||||
.priority = NF_IP_PRI_CONNTRACK_HELPER,
|
||||
},
|
||||
{
|
||||
.hook = ipv4_confirm,
|
||||
.pf = NFPROTO_IPV4,
|
||||
.hooknum = NF_INET_POST_ROUTING,
|
||||
.priority = NF_IP_PRI_CONNTRACK_CONFIRM,
|
||||
},
|
||||
{
|
||||
.hook = ipv4_helper,
|
||||
.pf = NFPROTO_IPV4,
|
||||
.hooknum = NF_INET_LOCAL_IN,
|
||||
.priority = NF_IP_PRI_CONNTRACK_HELPER,
|
||||
},
|
||||
{
|
||||
.hook = ipv4_confirm,
|
||||
.pf = NFPROTO_IPV4,
|
||||
.hooknum = NF_INET_LOCAL_IN,
|
||||
.priority = NF_IP_PRI_CONNTRACK_CONFIRM,
|
||||
},
|
||||
};
|
||||
|
||||
/* Fast function for those who don't want to parse /proc (and I don't
|
||||
blame them). */
|
||||
/* Reversing the socket's dst/src point of view gives us the reply
|
||||
mapping. */
|
||||
static int
|
||||
getorigdst(struct sock *sk, int optval, void __user *user, int *len)
|
||||
{
|
||||
const struct inet_sock *inet = inet_sk(sk);
|
||||
const struct nf_conntrack_tuple_hash *h;
|
||||
struct nf_conntrack_tuple tuple;
|
||||
|
||||
memset(&tuple, 0, sizeof(tuple));
|
||||
|
||||
lock_sock(sk);
|
||||
tuple.src.u3.ip = inet->inet_rcv_saddr;
|
||||
tuple.src.u.tcp.port = inet->inet_sport;
|
||||
tuple.dst.u3.ip = inet->inet_daddr;
|
||||
tuple.dst.u.tcp.port = inet->inet_dport;
|
||||
tuple.src.l3num = PF_INET;
|
||||
tuple.dst.protonum = sk->sk_protocol;
|
||||
release_sock(sk);
|
||||
|
||||
/* We only do TCP and SCTP at the moment: is there a better way? */
|
||||
if (tuple.dst.protonum != IPPROTO_TCP &&
|
||||
tuple.dst.protonum != IPPROTO_SCTP) {
|
||||
pr_debug("SO_ORIGINAL_DST: Not a TCP/SCTP socket\n");
|
||||
return -ENOPROTOOPT;
|
||||
}
|
||||
|
||||
if ((unsigned int) *len < sizeof(struct sockaddr_in)) {
|
||||
pr_debug("SO_ORIGINAL_DST: len %d not %zu\n",
|
||||
*len, sizeof(struct sockaddr_in));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
h = nf_conntrack_find_get(sock_net(sk), &nf_ct_zone_dflt, &tuple);
|
||||
if (h) {
|
||||
struct sockaddr_in sin;
|
||||
struct nf_conn *ct = nf_ct_tuplehash_to_ctrack(h);
|
||||
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = ct->tuplehash[IP_CT_DIR_ORIGINAL]
|
||||
.tuple.dst.u.tcp.port;
|
||||
sin.sin_addr.s_addr = ct->tuplehash[IP_CT_DIR_ORIGINAL]
|
||||
.tuple.dst.u3.ip;
|
||||
memset(sin.sin_zero, 0, sizeof(sin.sin_zero));
|
||||
|
||||
pr_debug("SO_ORIGINAL_DST: %pI4 %u\n",
|
||||
&sin.sin_addr.s_addr, ntohs(sin.sin_port));
|
||||
nf_ct_put(ct);
|
||||
if (copy_to_user(user, &sin, sizeof(sin)) != 0)
|
||||
return -EFAULT;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
pr_debug("SO_ORIGINAL_DST: Can't find %pI4/%u-%pI4/%u.\n",
|
||||
&tuple.src.u3.ip, ntohs(tuple.src.u.tcp.port),
|
||||
&tuple.dst.u3.ip, ntohs(tuple.dst.u.tcp.port));
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static struct nf_sockopt_ops so_getorigdst = {
|
||||
.pf = PF_INET,
|
||||
.get_optmin = SO_ORIGINAL_DST,
|
||||
.get_optmax = SO_ORIGINAL_DST+1,
|
||||
.get = getorigdst,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int ipv4_hooks_register(struct net *net)
|
||||
{
|
||||
struct conntrack4_net *cnet = net_generic(net, conntrack4_net_id);
|
||||
int err = 0;
|
||||
|
||||
mutex_lock(®ister_ipv4_hooks);
|
||||
|
||||
cnet->users++;
|
||||
if (cnet->users > 1)
|
||||
goto out_unlock;
|
||||
|
||||
err = nf_defrag_ipv4_enable(net);
|
||||
if (err) {
|
||||
cnet->users = 0;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
err = nf_register_net_hooks(net, ipv4_conntrack_ops,
|
||||
ARRAY_SIZE(ipv4_conntrack_ops));
|
||||
|
||||
if (err)
|
||||
cnet->users = 0;
|
||||
out_unlock:
|
||||
mutex_unlock(®ister_ipv4_hooks);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void ipv4_hooks_unregister(struct net *net)
|
||||
{
|
||||
struct conntrack4_net *cnet = net_generic(net, conntrack4_net_id);
|
||||
|
||||
mutex_lock(®ister_ipv4_hooks);
|
||||
if (cnet->users && (--cnet->users == 0))
|
||||
nf_unregister_net_hooks(net, ipv4_conntrack_ops,
|
||||
ARRAY_SIZE(ipv4_conntrack_ops));
|
||||
mutex_unlock(®ister_ipv4_hooks);
|
||||
}
|
||||
|
||||
const struct nf_conntrack_l3proto nf_conntrack_l3proto_ipv4 = {
|
||||
.l3proto = PF_INET,
|
||||
.net_ns_get = ipv4_hooks_register,
|
||||
.net_ns_put = ipv4_hooks_unregister,
|
||||
.me = THIS_MODULE,
|
||||
};
|
||||
|
||||
module_param_call(hashsize, nf_conntrack_set_hashsize, param_get_uint,
|
||||
&nf_conntrack_htable_size, 0600);
|
||||
|
||||
MODULE_ALIAS("nf_conntrack-" __stringify(AF_INET));
|
||||
MODULE_ALIAS("ip_conntrack");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static const struct nf_conntrack_l4proto * const builtin_l4proto4[] = {
|
||||
&nf_conntrack_l4proto_tcp4,
|
||||
&nf_conntrack_l4proto_udp4,
|
||||
&nf_conntrack_l4proto_icmp,
|
||||
#ifdef CONFIG_NF_CT_PROTO_DCCP
|
||||
&nf_conntrack_l4proto_dccp4,
|
||||
#endif
|
||||
#ifdef CONFIG_NF_CT_PROTO_SCTP
|
||||
&nf_conntrack_l4proto_sctp4,
|
||||
#endif
|
||||
#ifdef CONFIG_NF_CT_PROTO_UDPLITE
|
||||
&nf_conntrack_l4proto_udplite4,
|
||||
#endif
|
||||
};
|
||||
|
||||
static int ipv4_net_init(struct net *net)
|
||||
{
|
||||
return nf_ct_l4proto_pernet_register(net, builtin_l4proto4,
|
||||
ARRAY_SIZE(builtin_l4proto4));
|
||||
}
|
||||
|
||||
static void ipv4_net_exit(struct net *net)
|
||||
{
|
||||
nf_ct_l4proto_pernet_unregister(net, builtin_l4proto4,
|
||||
ARRAY_SIZE(builtin_l4proto4));
|
||||
}
|
||||
|
||||
static struct pernet_operations ipv4_net_ops = {
|
||||
.init = ipv4_net_init,
|
||||
.exit = ipv4_net_exit,
|
||||
.id = &conntrack4_net_id,
|
||||
.size = sizeof(struct conntrack4_net),
|
||||
};
|
||||
|
||||
static int __init nf_conntrack_l3proto_ipv4_init(void)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
need_conntrack();
|
||||
|
||||
ret = nf_register_sockopt(&so_getorigdst);
|
||||
if (ret < 0) {
|
||||
pr_err("Unable to register netfilter socket option\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = register_pernet_subsys(&ipv4_net_ops);
|
||||
if (ret < 0) {
|
||||
pr_err("nf_conntrack_ipv4: can't register pernet ops\n");
|
||||
goto cleanup_sockopt;
|
||||
}
|
||||
|
||||
ret = nf_ct_l4proto_register(builtin_l4proto4,
|
||||
ARRAY_SIZE(builtin_l4proto4));
|
||||
if (ret < 0)
|
||||
goto cleanup_pernet;
|
||||
|
||||
ret = nf_ct_l3proto_register(&nf_conntrack_l3proto_ipv4);
|
||||
if (ret < 0) {
|
||||
pr_err("nf_conntrack_ipv4: can't register ipv4 proto.\n");
|
||||
goto cleanup_l4proto;
|
||||
}
|
||||
|
||||
return ret;
|
||||
cleanup_l4proto:
|
||||
nf_ct_l4proto_unregister(builtin_l4proto4,
|
||||
ARRAY_SIZE(builtin_l4proto4));
|
||||
cleanup_pernet:
|
||||
unregister_pernet_subsys(&ipv4_net_ops);
|
||||
cleanup_sockopt:
|
||||
nf_unregister_sockopt(&so_getorigdst);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit nf_conntrack_l3proto_ipv4_fini(void)
|
||||
{
|
||||
synchronize_net();
|
||||
nf_ct_l3proto_unregister(&nf_conntrack_l3proto_ipv4);
|
||||
nf_ct_l4proto_unregister(builtin_l4proto4,
|
||||
ARRAY_SIZE(builtin_l4proto4));
|
||||
unregister_pernet_subsys(&ipv4_net_ops);
|
||||
nf_unregister_sockopt(&so_getorigdst);
|
||||
}
|
||||
|
||||
module_init(nf_conntrack_l3proto_ipv4_init);
|
||||
module_exit(nf_conntrack_l3proto_ipv4_fini);
|
@@ -1,388 +0,0 @@
|
||||
/* (C) 1999-2001 Paul `Rusty' Russell
|
||||
* (C) 2002-2004 Netfilter Core Team <coreteam@netfilter.org>
|
||||
* (C) 2006-2010 Patrick McHardy <kaber@trash.net>
|
||||
*
|
||||
* 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>
|
||||
#include <net/netfilter/nf_conntrack_l4proto.h>
|
||||
#include <net/netfilter/nf_conntrack_core.h>
|
||||
#include <net/netfilter/nf_conntrack_timeout.h>
|
||||
#include <net/netfilter/nf_conntrack_zones.h>
|
||||
#include <net/netfilter/nf_log.h>
|
||||
|
||||
static const unsigned int nf_ct_icmp_timeout = 30*HZ;
|
||||
|
||||
static inline struct nf_icmp_net *icmp_pernet(struct net *net)
|
||||
{
|
||||
return &net->ct.nf_ct_proto.icmp;
|
||||
}
|
||||
|
||||
static bool icmp_pkt_to_tuple(const struct sk_buff *skb, unsigned int dataoff,
|
||||
struct net *net, struct nf_conntrack_tuple *tuple)
|
||||
{
|
||||
const struct icmphdr *hp;
|
||||
struct icmphdr _hdr;
|
||||
|
||||
hp = skb_header_pointer(skb, dataoff, sizeof(_hdr), &_hdr);
|
||||
if (hp == NULL)
|
||||
return false;
|
||||
|
||||
tuple->dst.u.icmp.type = hp->type;
|
||||
tuple->src.u.icmp.id = hp->un.echo.id;
|
||||
tuple->dst.u.icmp.code = hp->code;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* 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
|
||||
};
|
||||
|
||||
static bool icmp_invert_tuple(struct nf_conntrack_tuple *tuple,
|
||||
const struct nf_conntrack_tuple *orig)
|
||||
{
|
||||
if (orig->dst.u.icmp.type >= sizeof(invmap) ||
|
||||
!invmap[orig->dst.u.icmp.type])
|
||||
return false;
|
||||
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
|
||||
static unsigned int *icmp_get_timeouts(struct net *net)
|
||||
{
|
||||
return &icmp_pernet(net)->timeout;
|
||||
}
|
||||
|
||||
/* 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)
|
||||
{
|
||||
/* Do not immediately delete the connection after the first
|
||||
successful reply to avoid excessive conntrackd traffic
|
||||
and also to handle correctly ICMP echo reply duplicates. */
|
||||
unsigned int *timeout = nf_ct_timeout_lookup(ct);
|
||||
|
||||
if (!timeout)
|
||||
timeout = icmp_get_timeouts(nf_ct_net(ct));
|
||||
|
||||
nf_ct_refresh_acct(ct, ctinfo, skb, *timeout);
|
||||
|
||||
return NF_ACCEPT;
|
||||
}
|
||||
|
||||
/* Called when a new connection for this protocol found. */
|
||||
static bool icmp_new(struct nf_conn *ct, const struct sk_buff *skb,
|
||||
unsigned int dataoff)
|
||||
{
|
||||
static const u_int8_t valid_new[] = {
|
||||
[ICMP_ECHO] = 1,
|
||||
[ICMP_TIMESTAMP] = 1,
|
||||
[ICMP_INFO_REQUEST] = 1,
|
||||
[ICMP_ADDRESS] = 1
|
||||
};
|
||||
|
||||
if (ct->tuplehash[0].tuple.dst.u.icmp.type >= sizeof(valid_new) ||
|
||||
!valid_new[ct->tuplehash[0].tuple.dst.u.icmp.type]) {
|
||||
/* Can't create a new ICMP `conn' with this. */
|
||||
pr_debug("icmp: can't create new conn with type %u\n",
|
||||
ct->tuplehash[0].tuple.dst.u.icmp.type);
|
||||
nf_ct_dump_tuple_ip(&ct->tuplehash[0].tuple);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Returns conntrack if it dealt with ICMP, and filled in skb fields */
|
||||
static int
|
||||
icmp_error_message(struct net *net, struct nf_conn *tmpl, struct sk_buff *skb,
|
||||
unsigned int hooknum)
|
||||
{
|
||||
struct nf_conntrack_tuple innertuple, origtuple;
|
||||
const struct nf_conntrack_l4proto *innerproto;
|
||||
const struct nf_conntrack_tuple_hash *h;
|
||||
const struct nf_conntrack_zone *zone;
|
||||
enum ip_conntrack_info ctinfo;
|
||||
struct nf_conntrack_zone tmp;
|
||||
|
||||
WARN_ON(skb_nfct(skb));
|
||||
zone = nf_ct_zone_tmpl(tmpl, skb, &tmp);
|
||||
|
||||
/* 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, net, &origtuple)) {
|
||||
pr_debug("icmp_error_message: failed to get tuple\n");
|
||||
return -NF_ACCEPT;
|
||||
}
|
||||
|
||||
/* rcu_read_lock()ed by nf_hook_thresh */
|
||||
innerproto = __nf_ct_l4proto_find(PF_INET, origtuple.dst.protonum);
|
||||
|
||||
/* Ordinarily, we'd expect the inverted tupleproto, but it's
|
||||
been preserved inside the ICMP. */
|
||||
if (!nf_ct_invert_tuple(&innertuple, &origtuple, innerproto)) {
|
||||
pr_debug("icmp_error_message: no match\n");
|
||||
return -NF_ACCEPT;
|
||||
}
|
||||
|
||||
ctinfo = IP_CT_RELATED;
|
||||
|
||||
h = nf_conntrack_find_get(net, zone, &innertuple);
|
||||
if (!h) {
|
||||
pr_debug("icmp_error_message: no match\n");
|
||||
return -NF_ACCEPT;
|
||||
}
|
||||
|
||||
if (NF_CT_DIRECTION(h) == IP_CT_DIR_REPLY)
|
||||
ctinfo += IP_CT_IS_REPLY;
|
||||
|
||||
/* Update skb to refer to this connection */
|
||||
nf_ct_set(skb, nf_ct_tuplehash_to_ctrack(h), ctinfo);
|
||||
return NF_ACCEPT;
|
||||
}
|
||||
|
||||
static void icmp_error_log(const struct sk_buff *skb, struct net *net,
|
||||
u8 pf, const char *msg)
|
||||
{
|
||||
nf_l4proto_log_invalid(skb, net, pf, IPPROTO_ICMP, "%s", msg);
|
||||
}
|
||||
|
||||
/* Small and modified version of icmp_rcv */
|
||||
static int
|
||||
icmp_error(struct net *net, struct nf_conn *tmpl,
|
||||
struct sk_buff *skb, unsigned int dataoff,
|
||||
u8 pf, unsigned int hooknum)
|
||||
{
|
||||
const struct icmphdr *icmph;
|
||||
struct icmphdr _ih;
|
||||
|
||||
/* Not enough header? */
|
||||
icmph = skb_header_pointer(skb, ip_hdrlen(skb), sizeof(_ih), &_ih);
|
||||
if (icmph == NULL) {
|
||||
icmp_error_log(skb, net, pf, "short packet");
|
||||
return -NF_ACCEPT;
|
||||
}
|
||||
|
||||
/* See ip_conntrack_proto_tcp.c */
|
||||
if (net->ct.sysctl_checksum && hooknum == NF_INET_PRE_ROUTING &&
|
||||
nf_ip_checksum(skb, hooknum, dataoff, 0)) {
|
||||
icmp_error_log(skb, net, pf, "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) {
|
||||
icmp_error_log(skb, net, pf, "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(net, tmpl, skb, hooknum);
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_NF_CT_NETLINK)
|
||||
|
||||
#include <linux/netfilter/nfnetlink.h>
|
||||
#include <linux/netfilter/nfnetlink_conntrack.h>
|
||||
|
||||
static int icmp_tuple_to_nlattr(struct sk_buff *skb,
|
||||
const struct nf_conntrack_tuple *t)
|
||||
{
|
||||
if (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))
|
||||
goto nla_put_failure;
|
||||
return 0;
|
||||
|
||||
nla_put_failure:
|
||||
return -1;
|
||||
}
|
||||
|
||||
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 },
|
||||
};
|
||||
|
||||
static int icmp_nlattr_to_tuple(struct nlattr *tb[],
|
||||
struct nf_conntrack_tuple *tuple)
|
||||
{
|
||||
if (!tb[CTA_PROTO_ICMP_TYPE] ||
|
||||
!tb[CTA_PROTO_ICMP_CODE] ||
|
||||
!tb[CTA_PROTO_ICMP_ID])
|
||||
return -EINVAL;
|
||||
|
||||
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]);
|
||||
|
||||
if (tuple->dst.u.icmp.type >= sizeof(invmap) ||
|
||||
!invmap[tuple->dst.u.icmp.type])
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int icmp_nlattr_tuple_size(void)
|
||||
{
|
||||
static unsigned int size __read_mostly;
|
||||
|
||||
if (!size)
|
||||
size = nla_policy_len(icmp_nla_policy, CTA_PROTO_MAX + 1);
|
||||
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_NF_CT_NETLINK_TIMEOUT)
|
||||
|
||||
#include <linux/netfilter/nfnetlink.h>
|
||||
#include <linux/netfilter/nfnetlink_cttimeout.h>
|
||||
|
||||
static int icmp_timeout_nlattr_to_obj(struct nlattr *tb[],
|
||||
struct net *net, void *data)
|
||||
{
|
||||
unsigned int *timeout = data;
|
||||
struct nf_icmp_net *in = icmp_pernet(net);
|
||||
|
||||
if (tb[CTA_TIMEOUT_ICMP_TIMEOUT]) {
|
||||
if (!timeout)
|
||||
timeout = &in->timeout;
|
||||
*timeout =
|
||||
ntohl(nla_get_be32(tb[CTA_TIMEOUT_ICMP_TIMEOUT])) * HZ;
|
||||
} else if (timeout) {
|
||||
/* Set default ICMP timeout. */
|
||||
*timeout = in->timeout;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
icmp_timeout_obj_to_nlattr(struct sk_buff *skb, const void *data)
|
||||
{
|
||||
const unsigned int *timeout = data;
|
||||
|
||||
if (nla_put_be32(skb, CTA_TIMEOUT_ICMP_TIMEOUT, htonl(*timeout / HZ)))
|
||||
goto nla_put_failure;
|
||||
return 0;
|
||||
|
||||
nla_put_failure:
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
static const struct nla_policy
|
||||
icmp_timeout_nla_policy[CTA_TIMEOUT_ICMP_MAX+1] = {
|
||||
[CTA_TIMEOUT_ICMP_TIMEOUT] = { .type = NLA_U32 },
|
||||
};
|
||||
#endif /* CONFIG_NF_CT_NETLINK_TIMEOUT */
|
||||
|
||||
#ifdef CONFIG_SYSCTL
|
||||
static struct ctl_table icmp_sysctl_table[] = {
|
||||
{
|
||||
.procname = "nf_conntrack_icmp_timeout",
|
||||
.maxlen = sizeof(unsigned int),
|
||||
.mode = 0644,
|
||||
.proc_handler = proc_dointvec_jiffies,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
#endif /* CONFIG_SYSCTL */
|
||||
|
||||
static int icmp_kmemdup_sysctl_table(struct nf_proto_net *pn,
|
||||
struct nf_icmp_net *in)
|
||||
{
|
||||
#ifdef CONFIG_SYSCTL
|
||||
pn->ctl_table = kmemdup(icmp_sysctl_table,
|
||||
sizeof(icmp_sysctl_table),
|
||||
GFP_KERNEL);
|
||||
if (!pn->ctl_table)
|
||||
return -ENOMEM;
|
||||
|
||||
pn->ctl_table[0].data = &in->timeout;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int icmp_init_net(struct net *net, u_int16_t proto)
|
||||
{
|
||||
struct nf_icmp_net *in = icmp_pernet(net);
|
||||
struct nf_proto_net *pn = &in->pn;
|
||||
|
||||
in->timeout = nf_ct_icmp_timeout;
|
||||
|
||||
return icmp_kmemdup_sysctl_table(pn, in);
|
||||
}
|
||||
|
||||
static struct nf_proto_net *icmp_get_net_proto(struct net *net)
|
||||
{
|
||||
return &net->ct.nf_ct_proto.icmp.pn;
|
||||
}
|
||||
|
||||
const struct nf_conntrack_l4proto nf_conntrack_l4proto_icmp =
|
||||
{
|
||||
.l3proto = PF_INET,
|
||||
.l4proto = IPPROTO_ICMP,
|
||||
.pkt_to_tuple = icmp_pkt_to_tuple,
|
||||
.invert_tuple = icmp_invert_tuple,
|
||||
.packet = icmp_packet,
|
||||
.new = icmp_new,
|
||||
.error = icmp_error,
|
||||
.destroy = NULL,
|
||||
.me = NULL,
|
||||
#if IS_ENABLED(CONFIG_NF_CT_NETLINK)
|
||||
.tuple_to_nlattr = icmp_tuple_to_nlattr,
|
||||
.nlattr_tuple_size = icmp_nlattr_tuple_size,
|
||||
.nlattr_to_tuple = icmp_nlattr_to_tuple,
|
||||
.nla_policy = icmp_nla_policy,
|
||||
#endif
|
||||
#if IS_ENABLED(CONFIG_NF_CT_NETLINK_TIMEOUT)
|
||||
.ctnl_timeout = {
|
||||
.nlattr_to_obj = icmp_timeout_nlattr_to_obj,
|
||||
.obj_to_nlattr = icmp_timeout_obj_to_nlattr,
|
||||
.nlattr_max = CTA_TIMEOUT_ICMP_MAX,
|
||||
.obj_size = sizeof(unsigned int),
|
||||
.nla_policy = icmp_timeout_nla_policy,
|
||||
},
|
||||
#endif /* CONFIG_NF_CT_NETLINK_TIMEOUT */
|
||||
.init_net = icmp_init_net,
|
||||
.get_net_proto = icmp_get_net_proto,
|
||||
};
|
Reference in New Issue
Block a user