blob: bad31f33b66c44920236f0fd9490fa94fa9f6694 [file] [log] [blame]
/*
* (C) 2006-2011 by Pablo Neira Ayuso <pablo@netfilter.org>
* (C) 2011 by Vyatta Inc. <http://www.vyatta.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include "conntrackd.h"
#include "sync.h"
#include "log.h"
#include "cache.h"
#include "netlink.h"
#include "network.h"
#include "origin.h"
static inline void sync_send(struct cache_object *obj, int query)
{
STATE_SYNC(sync)->enqueue(obj, query);
}
static int internal_cache_init(void)
{
STATE(mode)->internal->ct.data =
cache_create("internal", CACHE_T_CT,
STATE_SYNC(sync)->internal_cache_flags,
STATE_SYNC(sync)->internal_cache_extra,
&cache_sync_internal_ct_ops);
if (!STATE(mode)->internal->ct.data) {
dlog(LOG_ERR, "can't allocate memory for the internal cache");
return -1;
}
STATE(mode)->internal->exp.data =
cache_create("internal", CACHE_T_EXP,
STATE_SYNC(sync)->internal_cache_flags,
STATE_SYNC(sync)->internal_cache_extra,
&cache_sync_internal_exp_ops);
if (!STATE(mode)->internal->exp.data) {
dlog(LOG_ERR, "can't allocate memory for the internal cache");
return -1;
}
return 0;
}
static void internal_cache_close(void)
{
cache_destroy(STATE(mode)->internal->ct.data);
cache_destroy(STATE(mode)->internal->exp.data);
}
static void internal_cache_ct_dump(int fd, int type)
{
cache_dump(STATE(mode)->internal->ct.data, fd, type);
}
static void internal_cache_ct_flush(void)
{
cache_flush(STATE(mode)->internal->ct.data);
}
static void internal_cache_ct_stats(int fd)
{
cache_stats(STATE(mode)->internal->ct.data, fd);
}
static void internal_cache_ct_stats_ext(int fd)
{
cache_stats_extended(STATE(mode)->internal->ct.data, fd);
}
static void internal_cache_ct_populate(struct nf_conntrack *ct)
{
/* This is required by kernels < 2.6.20 */
nfct_attr_unset(ct, ATTR_ORIG_COUNTER_BYTES);
nfct_attr_unset(ct, ATTR_ORIG_COUNTER_PACKETS);
nfct_attr_unset(ct, ATTR_REPL_COUNTER_BYTES);
nfct_attr_unset(ct, ATTR_REPL_COUNTER_PACKETS);
nfct_attr_unset(ct, ATTR_USE);
cache_update_force(STATE(mode)->internal->ct.data, ct);
}
static int internal_cache_ct_purge_step(void *data1, void *data2)
{
struct cache_object *obj = data2;
STATE(get_retval) = 0;
nl_get_conntrack(STATE(get), obj->ptr); /* modifies STATE(get_reval) */
if (!STATE(get_retval)) {
if (obj->status != C_OBJ_DEAD) {
cache_object_set_status(obj, C_OBJ_DEAD);
sync_send(obj, NET_T_STATE_CT_DEL);
cache_object_put(obj);
}
}
return 0;
}
static void internal_cache_ct_purge(void)
{
cache_iterate(STATE(mode)->internal->ct.data, NULL,
internal_cache_ct_purge_step);
}
static int
internal_cache_ct_resync(enum nf_conntrack_msg_type type,
struct nf_conntrack *ct, void *data)
{
struct cache_object *obj;
if (ct_filter_conntrack(ct, 1))
return NFCT_CB_CONTINUE;
/* This is required by kernels < 2.6.20 */
nfct_attr_unset(ct, ATTR_ORIG_COUNTER_BYTES);
nfct_attr_unset(ct, ATTR_ORIG_COUNTER_PACKETS);
nfct_attr_unset(ct, ATTR_REPL_COUNTER_BYTES);
nfct_attr_unset(ct, ATTR_REPL_COUNTER_PACKETS);
nfct_attr_unset(ct, ATTR_USE);
obj = cache_update_force(STATE(mode)->internal->ct.data, ct);
if (obj == NULL)
return NFCT_CB_CONTINUE;
switch (obj->status) {
case C_OBJ_NEW:
sync_send(obj, NET_T_STATE_CT_NEW);
break;
case C_OBJ_ALIVE:
sync_send(obj, NET_T_STATE_CT_UPD);
break;
}
return NFCT_CB_CONTINUE;
}
static void internal_cache_ct_event_new(struct nf_conntrack *ct, int origin)
{
struct cache_object *obj;
int id;
/* this event has been triggered by a direct inject, skip */
if (origin == CTD_ORIGIN_INJECT)
return;
/* required by linux kernel <= 2.6.20 */
nfct_attr_unset(ct, ATTR_ORIG_COUNTER_BYTES);
nfct_attr_unset(ct, ATTR_ORIG_COUNTER_PACKETS);
nfct_attr_unset(ct, ATTR_REPL_COUNTER_BYTES);
nfct_attr_unset(ct, ATTR_REPL_COUNTER_PACKETS);
obj = cache_find(STATE(mode)->internal->ct.data, ct, &id);
if (obj == NULL) {
retry:
obj = cache_object_new(STATE(mode)->internal->ct.data, ct);
if (obj == NULL)
return;
if (cache_add(STATE(mode)->internal->ct.data, obj, id) == -1) {
cache_object_free(obj);
return;
}
/* only synchronize events that have been triggered by other
* processes or the kernel, but don't propagate events that
* have been triggered by conntrackd itself, eg. commits. */
if (origin == CTD_ORIGIN_NOT_ME)
sync_send(obj, NET_T_STATE_CT_NEW);
} else {
cache_del(STATE(mode)->internal->ct.data, obj);
cache_object_free(obj);
goto retry;
}
}
static void internal_cache_ct_event_upd(struct nf_conntrack *ct, int origin)
{
struct cache_object *obj;
/* this event has been triggered by a direct inject, skip */
if (origin == CTD_ORIGIN_INJECT)
return;
obj = cache_update_force(STATE(mode)->internal->ct.data, ct);
if (obj == NULL)
return;
if (origin == CTD_ORIGIN_NOT_ME)
sync_send(obj, NET_T_STATE_CT_UPD);
}
static int internal_cache_ct_event_del(struct nf_conntrack *ct, int origin)
{
struct cache_object *obj;
int id;
/* this event has been triggered by a direct inject, skip */
if (origin == CTD_ORIGIN_INJECT)
return 0;
/* we don't synchronize events for objects that are not in the cache */
obj = cache_find(STATE(mode)->internal->ct.data, ct, &id);
if (obj == NULL)
return 0;
if (obj->status != C_OBJ_DEAD) {
cache_object_set_status(obj, C_OBJ_DEAD);
if (origin == CTD_ORIGIN_NOT_ME) {
sync_send(obj, NET_T_STATE_CT_DEL);
}
cache_object_put(obj);
}
return 1;
}
static void internal_cache_exp_dump(int fd, int type)
{
cache_dump(STATE(mode)->internal->exp.data, fd, type);
}
static void internal_cache_exp_flush(void)
{
cache_flush(STATE(mode)->internal->exp.data);
}
static void internal_cache_exp_stats(int fd)
{
cache_stats(STATE(mode)->internal->exp.data, fd);
}
static void internal_cache_exp_stats_ext(int fd)
{
cache_stats_extended(STATE(mode)->internal->exp.data, fd);
}
static void internal_cache_exp_populate(struct nf_expect *exp)
{
cache_update_force(STATE(mode)->internal->exp.data, exp);
}
static int internal_cache_exp_purge_step(void *data1, void *data2)
{
struct cache_object *obj = data2;
STATE(get_retval) = 0;
nl_get_expect(STATE(get), obj->ptr); /* modifies STATE(get_reval) */
if (!STATE(get_retval)) {
if (obj->status != C_OBJ_DEAD) {
cache_object_set_status(obj, C_OBJ_DEAD);
sync_send(obj, NET_T_STATE_EXP_DEL);
cache_object_put(obj);
}
}
return 0;
}
static void internal_cache_exp_purge(void)
{
cache_iterate(STATE(mode)->internal->exp.data, NULL,
internal_cache_exp_purge_step);
}
static int
internal_cache_exp_resync(enum nf_conntrack_msg_type type,
struct nf_expect *exp, void *data)
{
struct cache_object *obj;
const struct nf_conntrack *master =
nfexp_get_attr(exp, ATTR_EXP_MASTER);
if (!exp_filter_find(STATE(exp_filter), exp))
return NFCT_CB_CONTINUE;
if (ct_filter_conntrack(master, 1))
return NFCT_CB_CONTINUE;
obj = cache_update_force(STATE(mode)->internal->exp.data, exp);
if (obj == NULL)
return NFCT_CB_CONTINUE;
switch (obj->status) {
case C_OBJ_NEW:
sync_send(obj, NET_T_STATE_EXP_NEW);
break;
case C_OBJ_ALIVE:
sync_send(obj, NET_T_STATE_EXP_UPD);
break;
}
return NFCT_CB_CONTINUE;
}
static void internal_cache_exp_event_new(struct nf_expect *exp, int origin)
{
struct cache_object *obj;
int id;
/* this event has been triggered by a direct inject, skip */
if (origin == CTD_ORIGIN_INJECT)
return;
obj = cache_find(STATE(mode)->internal->exp.data, exp, &id);
if (obj == NULL) {
retry:
obj = cache_object_new(STATE(mode)->internal->exp.data, exp);
if (obj == NULL)
return;
if (cache_add(STATE(mode)->internal->exp.data, obj, id) == -1) {
cache_object_free(obj);
return;
}
/* only synchronize events that have been triggered by other
* processes or the kernel, but don't propagate events that
* have been triggered by conntrackd itself, eg. commits. */
if (origin == CTD_ORIGIN_NOT_ME)
sync_send(obj, NET_T_STATE_EXP_NEW);
} else {
cache_del(STATE(mode)->internal->exp.data, obj);
cache_object_free(obj);
goto retry;
}
}
static void internal_cache_exp_event_upd(struct nf_expect *exp, int origin)
{
struct cache_object *obj;
/* this event has been triggered by a direct inject, skip */
if (origin == CTD_ORIGIN_INJECT)
return;
obj = cache_update_force(STATE(mode)->internal->exp.data, exp);
if (obj == NULL)
return;
if (origin == CTD_ORIGIN_NOT_ME)
sync_send(obj, NET_T_STATE_EXP_UPD);
}
static int internal_cache_exp_event_del(struct nf_expect *exp, int origin)
{
struct cache_object *obj;
int id;
/* this event has been triggered by a direct inject, skip */
if (origin == CTD_ORIGIN_INJECT)
return 0;
/* we don't synchronize events for objects that are not in the cache */
obj = cache_find(STATE(mode)->internal->exp.data, exp, &id);
if (obj == NULL)
return 0;
if (obj->status != C_OBJ_DEAD) {
cache_object_set_status(obj, C_OBJ_DEAD);
if (origin == CTD_ORIGIN_NOT_ME) {
sync_send(obj, NET_T_STATE_EXP_DEL);
}
cache_object_put(obj);
}
return 1;
}
static int internal_cache_exp_master_find(const struct nf_conntrack *master)
{
struct cache_object *obj;
int id;
obj = cache_find(STATE(mode)->internal->ct.data,
(struct nf_conntrack *)master, &id);
return obj ? 1 : 0;
}
struct internal_handler internal_cache = {
.flags = INTERNAL_F_POPULATE | INTERNAL_F_RESYNC,
.init = internal_cache_init,
.close = internal_cache_close,
.ct = {
.dump = internal_cache_ct_dump,
.flush = internal_cache_ct_flush,
.stats = internal_cache_ct_stats,
.stats_ext = internal_cache_ct_stats_ext,
.populate = internal_cache_ct_populate,
.purge = internal_cache_ct_purge,
.resync = internal_cache_ct_resync,
.new = internal_cache_ct_event_new,
.upd = internal_cache_ct_event_upd,
.del = internal_cache_ct_event_del,
},
.exp = {
.dump = internal_cache_exp_dump,
.flush = internal_cache_exp_flush,
.stats = internal_cache_exp_stats,
.stats_ext = internal_cache_exp_stats_ext,
.populate = internal_cache_exp_populate,
.purge = internal_cache_exp_purge,
.resync = internal_cache_exp_resync,
.new = internal_cache_exp_event_new,
.upd = internal_cache_exp_event_upd,
.del = internal_cache_exp_event_del,
.find = internal_cache_exp_master_find,
},
};