mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-12 05:48:40 +00:00
![Pablo Neira Ayuso](/assets/img/avatar_default.png)
This patch adds flow-based timestamping for conntracks. This conntrack extension is disabled by default. Basically, we use two 64-bits variables to store the creation timestamp once the conntrack has been confirmed and the other to store the deletion time. This extension is disabled by default, to enable it, you have to: echo 1 > /proc/sys/net/netfilter/nf_conntrack_timestamp This patch allows to save memory for user-space flow-based loogers such as ulogd2. In short, ulogd2 does not need to keep a hashtable with the conntrack in user-space to know when they were created and destroyed, instead we use the kernel timestamp. If we want to have a sane IPFIX implementation in user-space, this nanosecs resolution timestamps are also useful. Other custom user-space applications can benefit from this via libnetfilter_conntrack. This patch modifies the /proc output to display the delta time in seconds since the flow start. You can also obtain the flow-start date by means of the conntrack-tools. Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> Signed-off-by: Patrick McHardy <kaber@trash.net>
595 lines
14 KiB
C
595 lines
14 KiB
C
/* (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/netfilter.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/module.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/percpu.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/security.h>
|
|
#include <net/net_namespace.h>
|
|
#ifdef CONFIG_SYSCTL
|
|
#include <linux/sysctl.h>
|
|
#endif
|
|
|
|
#include <net/netfilter/nf_conntrack.h>
|
|
#include <net/netfilter/nf_conntrack_core.h>
|
|
#include <net/netfilter/nf_conntrack_l3proto.h>
|
|
#include <net/netfilter/nf_conntrack_l4proto.h>
|
|
#include <net/netfilter/nf_conntrack_expect.h>
|
|
#include <net/netfilter/nf_conntrack_helper.h>
|
|
#include <net/netfilter/nf_conntrack_acct.h>
|
|
#include <net/netfilter/nf_conntrack_zones.h>
|
|
#include <net/netfilter/nf_conntrack_timestamp.h>
|
|
#include <linux/rculist_nulls.h>
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
int
|
|
print_tuple(struct seq_file *s, const struct nf_conntrack_tuple *tuple,
|
|
const struct nf_conntrack_l3proto *l3proto,
|
|
const struct nf_conntrack_l4proto *l4proto)
|
|
{
|
|
return l3proto->print_tuple(s, tuple) || l4proto->print_tuple(s, tuple);
|
|
}
|
|
EXPORT_SYMBOL_GPL(print_tuple);
|
|
|
|
struct ct_iter_state {
|
|
struct seq_net_private p;
|
|
unsigned int bucket;
|
|
u_int64_t time_now;
|
|
};
|
|
|
|
static struct hlist_nulls_node *ct_get_first(struct seq_file *seq)
|
|
{
|
|
struct net *net = seq_file_net(seq);
|
|
struct ct_iter_state *st = seq->private;
|
|
struct hlist_nulls_node *n;
|
|
|
|
for (st->bucket = 0;
|
|
st->bucket < net->ct.htable_size;
|
|
st->bucket++) {
|
|
n = rcu_dereference(hlist_nulls_first_rcu(&net->ct.hash[st->bucket]));
|
|
if (!is_a_nulls(n))
|
|
return n;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static struct hlist_nulls_node *ct_get_next(struct seq_file *seq,
|
|
struct hlist_nulls_node *head)
|
|
{
|
|
struct net *net = seq_file_net(seq);
|
|
struct ct_iter_state *st = seq->private;
|
|
|
|
head = rcu_dereference(hlist_nulls_next_rcu(head));
|
|
while (is_a_nulls(head)) {
|
|
if (likely(get_nulls_value(head) == st->bucket)) {
|
|
if (++st->bucket >= net->ct.htable_size)
|
|
return NULL;
|
|
}
|
|
head = rcu_dereference(
|
|
hlist_nulls_first_rcu(
|
|
&net->ct.hash[st->bucket]));
|
|
}
|
|
return head;
|
|
}
|
|
|
|
static struct hlist_nulls_node *ct_get_idx(struct seq_file *seq, loff_t pos)
|
|
{
|
|
struct hlist_nulls_node *head = ct_get_first(seq);
|
|
|
|
if (head)
|
|
while (pos && (head = ct_get_next(seq, head)))
|
|
pos--;
|
|
return pos ? NULL : head;
|
|
}
|
|
|
|
static void *ct_seq_start(struct seq_file *seq, loff_t *pos)
|
|
__acquires(RCU)
|
|
{
|
|
struct ct_iter_state *st = seq->private;
|
|
|
|
st->time_now = ktime_to_ns(ktime_get_real());
|
|
rcu_read_lock();
|
|
return ct_get_idx(seq, *pos);
|
|
}
|
|
|
|
static void *ct_seq_next(struct seq_file *s, void *v, loff_t *pos)
|
|
{
|
|
(*pos)++;
|
|
return ct_get_next(s, v);
|
|
}
|
|
|
|
static void ct_seq_stop(struct seq_file *s, void *v)
|
|
__releases(RCU)
|
|
{
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
#ifdef CONFIG_NF_CONNTRACK_SECMARK
|
|
static int ct_show_secctx(struct seq_file *s, const struct nf_conn *ct)
|
|
{
|
|
int ret;
|
|
u32 len;
|
|
char *secctx;
|
|
|
|
ret = security_secid_to_secctx(ct->secmark, &secctx, &len);
|
|
if (ret)
|
|
return 0;
|
|
|
|
ret = seq_printf(s, "secctx=%s ", secctx);
|
|
|
|
security_release_secctx(secctx, len);
|
|
return ret;
|
|
}
|
|
#else
|
|
static inline int ct_show_secctx(struct seq_file *s, const struct nf_conn *ct)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_NF_CONNTRACK_TIMESTAMP
|
|
static u_int64_t ct_delta_time(u_int64_t time_now, const struct nf_conn *ct)
|
|
{
|
|
struct nf_conn_tstamp *tstamp;
|
|
|
|
tstamp = nf_conn_tstamp_find(ct);
|
|
if (tstamp) {
|
|
u_int64_t delta_time = time_now - tstamp->start;
|
|
return delta_time > 0 ? div_s64(delta_time, NSEC_PER_SEC) : 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int ct_show_delta_time(struct seq_file *s, const struct nf_conn *ct)
|
|
{
|
|
struct ct_iter_state *st = s->private;
|
|
u_int64_t delta_time;
|
|
|
|
delta_time = ct_delta_time(st->time_now, ct);
|
|
if (delta_time < 0)
|
|
return 0;
|
|
|
|
return seq_printf(s, "delta-time=%llu ",
|
|
(unsigned long long)delta_time);
|
|
}
|
|
#else
|
|
static inline int
|
|
ct_show_delta_time(struct seq_file *s, const struct nf_conn *ct)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/* return 0 on success, 1 in case of error */
|
|
static int ct_seq_show(struct seq_file *s, void *v)
|
|
{
|
|
struct nf_conntrack_tuple_hash *hash = v;
|
|
struct nf_conn *ct = nf_ct_tuplehash_to_ctrack(hash);
|
|
const struct nf_conntrack_l3proto *l3proto;
|
|
const struct nf_conntrack_l4proto *l4proto;
|
|
int ret = 0;
|
|
|
|
NF_CT_ASSERT(ct);
|
|
if (unlikely(!atomic_inc_not_zero(&ct->ct_general.use)))
|
|
return 0;
|
|
|
|
/* we only want to print DIR_ORIGINAL */
|
|
if (NF_CT_DIRECTION(hash))
|
|
goto release;
|
|
|
|
l3proto = __nf_ct_l3proto_find(nf_ct_l3num(ct));
|
|
NF_CT_ASSERT(l3proto);
|
|
l4proto = __nf_ct_l4proto_find(nf_ct_l3num(ct), nf_ct_protonum(ct));
|
|
NF_CT_ASSERT(l4proto);
|
|
|
|
ret = -ENOSPC;
|
|
if (seq_printf(s, "%-8s %u %-8s %u %ld ",
|
|
l3proto->name, nf_ct_l3num(ct),
|
|
l4proto->name, nf_ct_protonum(ct),
|
|
timer_pending(&ct->timeout)
|
|
? (long)(ct->timeout.expires - jiffies)/HZ : 0) != 0)
|
|
goto release;
|
|
|
|
if (l4proto->print_conntrack && l4proto->print_conntrack(s, ct))
|
|
goto release;
|
|
|
|
if (print_tuple(s, &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple,
|
|
l3proto, l4proto))
|
|
goto release;
|
|
|
|
if (seq_print_acct(s, ct, IP_CT_DIR_ORIGINAL))
|
|
goto release;
|
|
|
|
if (!(test_bit(IPS_SEEN_REPLY_BIT, &ct->status)))
|
|
if (seq_printf(s, "[UNREPLIED] "))
|
|
goto release;
|
|
|
|
if (print_tuple(s, &ct->tuplehash[IP_CT_DIR_REPLY].tuple,
|
|
l3proto, l4proto))
|
|
goto release;
|
|
|
|
if (seq_print_acct(s, ct, IP_CT_DIR_REPLY))
|
|
goto release;
|
|
|
|
if (test_bit(IPS_ASSURED_BIT, &ct->status))
|
|
if (seq_printf(s, "[ASSURED] "))
|
|
goto release;
|
|
|
|
#if defined(CONFIG_NF_CONNTRACK_MARK)
|
|
if (seq_printf(s, "mark=%u ", ct->mark))
|
|
goto release;
|
|
#endif
|
|
|
|
if (ct_show_secctx(s, ct))
|
|
goto release;
|
|
|
|
#ifdef CONFIG_NF_CONNTRACK_ZONES
|
|
if (seq_printf(s, "zone=%u ", nf_ct_zone(ct)))
|
|
goto release;
|
|
#endif
|
|
|
|
if (ct_show_delta_time(s, ct))
|
|
goto release;
|
|
|
|
if (seq_printf(s, "use=%u\n", atomic_read(&ct->ct_general.use)))
|
|
goto release;
|
|
|
|
ret = 0;
|
|
release:
|
|
nf_ct_put(ct);
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations ct_seq_ops = {
|
|
.start = ct_seq_start,
|
|
.next = ct_seq_next,
|
|
.stop = ct_seq_stop,
|
|
.show = ct_seq_show
|
|
};
|
|
|
|
static int ct_open(struct inode *inode, struct file *file)
|
|
{
|
|
return seq_open_net(inode, file, &ct_seq_ops,
|
|
sizeof(struct ct_iter_state));
|
|
}
|
|
|
|
static const struct file_operations ct_file_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = ct_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release_net,
|
|
};
|
|
|
|
static void *ct_cpu_seq_start(struct seq_file *seq, loff_t *pos)
|
|
{
|
|
struct net *net = seq_file_net(seq);
|
|
int cpu;
|
|
|
|
if (*pos == 0)
|
|
return SEQ_START_TOKEN;
|
|
|
|
for (cpu = *pos-1; cpu < nr_cpu_ids; ++cpu) {
|
|
if (!cpu_possible(cpu))
|
|
continue;
|
|
*pos = cpu + 1;
|
|
return per_cpu_ptr(net->ct.stat, cpu);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void *ct_cpu_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
|
{
|
|
struct net *net = seq_file_net(seq);
|
|
int cpu;
|
|
|
|
for (cpu = *pos; cpu < nr_cpu_ids; ++cpu) {
|
|
if (!cpu_possible(cpu))
|
|
continue;
|
|
*pos = cpu + 1;
|
|
return per_cpu_ptr(net->ct.stat, cpu);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void ct_cpu_seq_stop(struct seq_file *seq, void *v)
|
|
{
|
|
}
|
|
|
|
static int ct_cpu_seq_show(struct seq_file *seq, void *v)
|
|
{
|
|
struct net *net = seq_file_net(seq);
|
|
unsigned int nr_conntracks = atomic_read(&net->ct.count);
|
|
const struct ip_conntrack_stat *st = v;
|
|
|
|
if (v == SEQ_START_TOKEN) {
|
|
seq_printf(seq, "entries searched found new invalid ignore delete delete_list insert insert_failed drop early_drop icmp_error expect_new expect_create expect_delete search_restart\n");
|
|
return 0;
|
|
}
|
|
|
|
seq_printf(seq, "%08x %08x %08x %08x %08x %08x %08x %08x "
|
|
"%08x %08x %08x %08x %08x %08x %08x %08x %08x\n",
|
|
nr_conntracks,
|
|
st->searched,
|
|
st->found,
|
|
st->new,
|
|
st->invalid,
|
|
st->ignore,
|
|
st->delete,
|
|
st->delete_list,
|
|
st->insert,
|
|
st->insert_failed,
|
|
st->drop,
|
|
st->early_drop,
|
|
st->error,
|
|
|
|
st->expect_new,
|
|
st->expect_create,
|
|
st->expect_delete,
|
|
st->search_restart
|
|
);
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations ct_cpu_seq_ops = {
|
|
.start = ct_cpu_seq_start,
|
|
.next = ct_cpu_seq_next,
|
|
.stop = ct_cpu_seq_stop,
|
|
.show = ct_cpu_seq_show,
|
|
};
|
|
|
|
static int ct_cpu_seq_open(struct inode *inode, struct file *file)
|
|
{
|
|
return seq_open_net(inode, file, &ct_cpu_seq_ops,
|
|
sizeof(struct seq_net_private));
|
|
}
|
|
|
|
static const struct file_operations ct_cpu_seq_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = ct_cpu_seq_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release_net,
|
|
};
|
|
|
|
static int nf_conntrack_standalone_init_proc(struct net *net)
|
|
{
|
|
struct proc_dir_entry *pde;
|
|
|
|
pde = proc_net_fops_create(net, "nf_conntrack", 0440, &ct_file_ops);
|
|
if (!pde)
|
|
goto out_nf_conntrack;
|
|
|
|
pde = proc_create("nf_conntrack", S_IRUGO, net->proc_net_stat,
|
|
&ct_cpu_seq_fops);
|
|
if (!pde)
|
|
goto out_stat_nf_conntrack;
|
|
return 0;
|
|
|
|
out_stat_nf_conntrack:
|
|
proc_net_remove(net, "nf_conntrack");
|
|
out_nf_conntrack:
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static void nf_conntrack_standalone_fini_proc(struct net *net)
|
|
{
|
|
remove_proc_entry("nf_conntrack", net->proc_net_stat);
|
|
proc_net_remove(net, "nf_conntrack");
|
|
}
|
|
#else
|
|
static int nf_conntrack_standalone_init_proc(struct net *net)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void nf_conntrack_standalone_fini_proc(struct net *net)
|
|
{
|
|
}
|
|
#endif /* CONFIG_PROC_FS */
|
|
|
|
/* Sysctl support */
|
|
|
|
#ifdef CONFIG_SYSCTL
|
|
/* Log invalid packets of a given protocol */
|
|
static int log_invalid_proto_min = 0;
|
|
static int log_invalid_proto_max = 255;
|
|
|
|
static struct ctl_table_header *nf_ct_netfilter_header;
|
|
|
|
static ctl_table nf_ct_sysctl_table[] = {
|
|
{
|
|
.procname = "nf_conntrack_max",
|
|
.data = &nf_conntrack_max,
|
|
.maxlen = sizeof(int),
|
|
.mode = 0644,
|
|
.proc_handler = proc_dointvec,
|
|
},
|
|
{
|
|
.procname = "nf_conntrack_count",
|
|
.data = &init_net.ct.count,
|
|
.maxlen = sizeof(int),
|
|
.mode = 0444,
|
|
.proc_handler = proc_dointvec,
|
|
},
|
|
{
|
|
.procname = "nf_conntrack_buckets",
|
|
.data = &init_net.ct.htable_size,
|
|
.maxlen = sizeof(unsigned int),
|
|
.mode = 0444,
|
|
.proc_handler = proc_dointvec,
|
|
},
|
|
{
|
|
.procname = "nf_conntrack_checksum",
|
|
.data = &init_net.ct.sysctl_checksum,
|
|
.maxlen = sizeof(unsigned int),
|
|
.mode = 0644,
|
|
.proc_handler = proc_dointvec,
|
|
},
|
|
{
|
|
.procname = "nf_conntrack_log_invalid",
|
|
.data = &init_net.ct.sysctl_log_invalid,
|
|
.maxlen = sizeof(unsigned int),
|
|
.mode = 0644,
|
|
.proc_handler = proc_dointvec_minmax,
|
|
.extra1 = &log_invalid_proto_min,
|
|
.extra2 = &log_invalid_proto_max,
|
|
},
|
|
{
|
|
.procname = "nf_conntrack_expect_max",
|
|
.data = &nf_ct_expect_max,
|
|
.maxlen = sizeof(int),
|
|
.mode = 0644,
|
|
.proc_handler = proc_dointvec,
|
|
},
|
|
{ }
|
|
};
|
|
|
|
#define NET_NF_CONNTRACK_MAX 2089
|
|
|
|
static ctl_table nf_ct_netfilter_table[] = {
|
|
{
|
|
.procname = "nf_conntrack_max",
|
|
.data = &nf_conntrack_max,
|
|
.maxlen = sizeof(int),
|
|
.mode = 0644,
|
|
.proc_handler = proc_dointvec,
|
|
},
|
|
{ }
|
|
};
|
|
|
|
static struct ctl_path nf_ct_path[] = {
|
|
{ .procname = "net", },
|
|
{ }
|
|
};
|
|
|
|
static int nf_conntrack_standalone_init_sysctl(struct net *net)
|
|
{
|
|
struct ctl_table *table;
|
|
|
|
if (net_eq(net, &init_net)) {
|
|
nf_ct_netfilter_header =
|
|
register_sysctl_paths(nf_ct_path, nf_ct_netfilter_table);
|
|
if (!nf_ct_netfilter_header)
|
|
goto out;
|
|
}
|
|
|
|
table = kmemdup(nf_ct_sysctl_table, sizeof(nf_ct_sysctl_table),
|
|
GFP_KERNEL);
|
|
if (!table)
|
|
goto out_kmemdup;
|
|
|
|
table[1].data = &net->ct.count;
|
|
table[2].data = &net->ct.htable_size;
|
|
table[3].data = &net->ct.sysctl_checksum;
|
|
table[4].data = &net->ct.sysctl_log_invalid;
|
|
|
|
net->ct.sysctl_header = register_net_sysctl_table(net,
|
|
nf_net_netfilter_sysctl_path, table);
|
|
if (!net->ct.sysctl_header)
|
|
goto out_unregister_netfilter;
|
|
|
|
return 0;
|
|
|
|
out_unregister_netfilter:
|
|
kfree(table);
|
|
out_kmemdup:
|
|
if (net_eq(net, &init_net))
|
|
unregister_sysctl_table(nf_ct_netfilter_header);
|
|
out:
|
|
printk(KERN_ERR "nf_conntrack: can't register to sysctl.\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static void nf_conntrack_standalone_fini_sysctl(struct net *net)
|
|
{
|
|
struct ctl_table *table;
|
|
|
|
if (net_eq(net, &init_net))
|
|
unregister_sysctl_table(nf_ct_netfilter_header);
|
|
table = net->ct.sysctl_header->ctl_table_arg;
|
|
unregister_net_sysctl_table(net->ct.sysctl_header);
|
|
kfree(table);
|
|
}
|
|
#else
|
|
static int nf_conntrack_standalone_init_sysctl(struct net *net)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void nf_conntrack_standalone_fini_sysctl(struct net *net)
|
|
{
|
|
}
|
|
#endif /* CONFIG_SYSCTL */
|
|
|
|
static int nf_conntrack_net_init(struct net *net)
|
|
{
|
|
int ret;
|
|
|
|
ret = nf_conntrack_init(net);
|
|
if (ret < 0)
|
|
goto out_init;
|
|
ret = nf_conntrack_standalone_init_proc(net);
|
|
if (ret < 0)
|
|
goto out_proc;
|
|
net->ct.sysctl_checksum = 1;
|
|
net->ct.sysctl_log_invalid = 0;
|
|
ret = nf_conntrack_standalone_init_sysctl(net);
|
|
if (ret < 0)
|
|
goto out_sysctl;
|
|
return 0;
|
|
|
|
out_sysctl:
|
|
nf_conntrack_standalone_fini_proc(net);
|
|
out_proc:
|
|
nf_conntrack_cleanup(net);
|
|
out_init:
|
|
return ret;
|
|
}
|
|
|
|
static void nf_conntrack_net_exit(struct net *net)
|
|
{
|
|
nf_conntrack_standalone_fini_sysctl(net);
|
|
nf_conntrack_standalone_fini_proc(net);
|
|
nf_conntrack_cleanup(net);
|
|
}
|
|
|
|
static struct pernet_operations nf_conntrack_net_ops = {
|
|
.init = nf_conntrack_net_init,
|
|
.exit = nf_conntrack_net_exit,
|
|
};
|
|
|
|
static int __init nf_conntrack_standalone_init(void)
|
|
{
|
|
return register_pernet_subsys(&nf_conntrack_net_ops);
|
|
}
|
|
|
|
static void __exit nf_conntrack_standalone_fini(void)
|
|
{
|
|
unregister_pernet_subsys(&nf_conntrack_net_ops);
|
|
}
|
|
|
|
module_init(nf_conntrack_standalone_init);
|
|
module_exit(nf_conntrack_standalone_fini);
|
|
|
|
/* Some modules need us, but don't depend directly on any symbol.
|
|
They should call this. */
|
|
void need_conntrack(void)
|
|
{
|
|
}
|
|
EXPORT_SYMBOL_GPL(need_conntrack);
|