Example #1
0
static int
tfw_bmb_connect(int tn, int cn)
{
	int ret;
	struct sock *sk;
	TfwBmbConn *conn;

	conn = &bmb_task[tn].conn[cn];

	ret = ss_sock_create(bmb_server_address.sa.sa_family, SOCK_STREAM,
			     IPPROTO_TCP, &sk);
	if (ret) {
		TFW_ERR("Unable to create kernel socket (%d)\n", ret);
		return ret;
	}

	ss_proto_init(&conn->proto, &bmb_hooks, tn * nconns + cn);
	rcu_assign_sk_user_data(sk, &conn->proto);
	ss_set_callbacks(sk);

	ret = ss_connect(sk, &bmb_server_address.sa,
			 tfw_addr_sa_len(&bmb_server_address), 0);
	if (ret) {
		TFW_ERR("Connect error on server socket sk %p (%d)\n", sk, ret);
		tfw_connection_unlink_from_sk(sk);
		ss_close(sk);
		return ret;
        }

	conn->sk = sk;
	bmb_task[tn].conn_attempt++;

	return 0;
}
Example #2
0
/**
 * Check name of an attribute or name
 *
 * Much like C identifiers, names must start with a letter and consist
 * only of alphanumeric and underscore characters. Currently this is
 * only a sanity check and the parser code would work without it, but in
 * future it may help to preserve compatibility if we decide to change
 * the parser.
 */
static bool
check_identifier(const char *buf, size_t len)
{
	size_t i;

	if (!len) {
		TFW_ERR("the string is empty\n");
		return false;
	}

	if (!isalpha(buf[0])) {
		TFW_ERR("the first character is not a letter: '%c'\n", buf[0]);
		return false;
	}

	for (i = 0; i < len; ++i) {
		if (!isalnum(buf[i]) && buf[i] != '_') {
			TFW_ERR("invalid character: '%c' in '%.*s'\n",
				buf[i], (int)len, buf);
			return false;
		}
	}

	return true;
}
Example #3
0
static void
tfw_bmb_msg_send(int tn, int cn)
{
	TfwBmbTask *task = &bmb_task[tn];
	int fz_tries = 0, r;
	TfwStr msg;
	TfwHttpMsg req;
	TfwMsgIter it;

	BUG_ON(!task->conn[cn].sk);

	do {
		if (++fz_tries > 10) {
			TFW_ERR("Too many fuzzer tries to generate request\n");
			return;
		}

		r = fuzz_gen(&task->ctx, task->buf, task->buf + BUF_SIZE, 0, 1,
			     FUZZ_REQ);
		if (r < 0) {
			TFW_ERR("Cannot generate HTTP request, r=%d\n", r);
			return;
		}
		if (r == FUZZ_END)
			fuzz_init(&task->ctx, true);
	} while (r != FUZZ_VALID);

	msg.ptr = task->buf;
	msg.skb = NULL;
	msg.len = strlen(msg.ptr);
	msg.flags = 0;

	if (!tfw_http_msg_create(&req, &it, Conn_Clnt, msg.len)) {
		TFW_WARN("Cannot create HTTP request.\n");
		return;
	}

	if (verbose)
		TFW_LOG("Send request:\n"
			"------------------------------\n"
			"%s\n"
			"------------------------------\n",
			task->buf);

	tfw_http_msg_write(&it, &req, &msg);
	ss_send(task->conn[cn].sk, &req.msg.skb_list, true);

	atomic_inc(&bmb_request_send);
}
Example #4
0
static int
fop_open(struct inode *inode, struct file *file)
{
	fmode_t mode = file->f_mode;
	tfw_debugfs_handler_t fn = file->f_inode->i_private;
	TfwDebugfsIoState *state;

	if ((mode & FMODE_READ) && (mode & FMODE_WRITE)) {
		TFW_ERR("This debugfs file can't be opened in read-write mode");
		return -EPERM;
	}

	state = kzalloc(sizeof(*state), GFP_KERNEL);
	BUG_ON(!state);

	/* Simply allocate a fixed-size buffer. The buffer doesn't expand, the
	 * data is cropped if it doesn't fit to it. Later on we may change it */
	state->buf_size = PAGE_SIZE;
	state->buf = kmalloc(state->buf_size, GFP_KERNEL);
	BUG_ON(!state->buf);

	if (mode & FMODE_WRITE)
		state->is_input = true;
	else
		state->len = fn(state->is_input, state->buf, state->buf_size);

	file->private_data = state;

	return 0;
}
Example #5
0
static int
tfw_bmb_worker(void *data)
{
	int tn = (int)(long)data;
	TfwBmbTask *task = &bmb_task[tn];
	int attempt, send, k, i;
	unsigned long time_max;

	fuzz_init(&task->ctx, true);

	for (k = 0; k < niters; k++) {
		task->conn_attempt = 0;
		atomic_set(&task->conn_compl, 0);
		atomic_set(&task->conn_error, 0);
		atomic_set(&task->conn_rd_tail, 0);
		init_waitqueue_head(&task->conn_wq);

		for (i = 0; i < nconns; i++)
			tfw_bmb_connect(tn, i);

		set_freezable();
		time_max = jiffies + 60 * HZ;
		attempt = task->conn_attempt;
		do {
#define COND()	(atomic_read(&task->conn_compl) > 0 || \
		 atomic_read(&task->conn_error) == attempt)
			wait_event_freezable_timeout(task->conn_wq, COND(), HZ);
#undef COND
			if (atomic_read(&task->conn_compl) > 0)
				break;
			if (atomic_read(&task->conn_error) == attempt)
				goto release_sockets;
			if (jiffies > time_max) {
				TFW_ERR("worker exceeded maximum wait time\n");
				goto release_sockets;
			}
		} while (!kthread_should_stop());

		for (send = 0; send < nconns * nmessages; ) {
			int tail = atomic_read(&task->conn_rd_tail);
			for (i = 0; i < tail; i++){
				tfw_bmb_msg_send(tn, task->conn_rd[i]);
				send++;
			}
		}

release_sockets:
		atomic_add(attempt, &bmb_conn_attempt);
		atomic_add(atomic_read(&task->conn_compl), &bmb_conn_compl);
		atomic_add(atomic_read(&task->conn_error), &bmb_conn_error);

		tfw_bmb_release_sockets(tn);
	}

	task->task_struct = NULL;
	atomic_dec(&bmb_threads);
	wake_up(&bmb_task_wq);

	return 0;
}
Example #6
0
static ssize_t
fop_write(struct file *file, const char __user *user_buf,
	  size_t count,
	  loff_t *ppos)
{
	int len;
	TfwDebugfsIoState *state = file->private_data;

	if (!state->is_input) {
		TFW_ERR("Can't write to this debugfs file: "
			"it was open()'ed only for reading\n");
		return -EPERM;
	}

	/* copy data from user-space */
	len = simple_write_to_buffer(state->buf, (state->buf_size - 1),
				     ppos,
				     user_buf, count);
	if (len > 0) {
		state->len += len;
		state->buf[state->len] = '\0';
	}

	return len;
}
Example #7
0
static int __init
tfw_bmb_init(void)
{
	long i;
	volatile unsigned long ts_start;
	struct task_struct *task;
	int r = 0;

	if (tfw_addr_pton(&TFW_STR_FROM(server), &bmb_server_address)) {
		TFW_ERR("Unable to parse server's address: %s", server);
		return -EINVAL;
	}
	TFW_LOG("Started bomber module, server's address is %s\n", server);

	if (tfw_bmb_alloc())
		return -ENOMEM;

	ts_start = jiffies;

	for (i = 0; i < nthreads; i++) {
		task = kthread_create(tfw_bmb_worker, (void *)i, "worker");
		if (IS_ERR_OR_NULL(task)) {
			TFW_ERR("Unable to create worker\n");
			r = -EINVAL;
			goto stop_threads;
		}
		bmb_task[i].task_struct = task;
	}

	atomic_set(&bmb_threads, nthreads);
	for (i = 0; i < nthreads; i++)
		wake_up_process(bmb_task[i].task_struct);

	wait_event_interruptible(bmb_task_wq, !atomic_read(&bmb_threads));

	tfw_bmb_report(ts_start);

stop_threads:
	tfw_bmb_stop_threads();
	tfw_bmb_free();

	return r;
}
Example #8
0
/**
 * Register a hook which will be called with priority @prio when FSM @fsm_id
 * reaches state @state. The hooks switches calling FSM to FSM represented by
 * @hndl_fsm_id at state @st0.
 * @return resulting priority at which the hook was registered or
 * negative value of failure.
 */
int
tfw_gfsm_register_hook(int fsm_id, int prio, int state,
		       unsigned short hndl_fsm_id, int st0)
{
	int shift, st = state & TFW_GFSM_STATE_MASK;
	unsigned int st_bit = 1 << st;

	/* Initial FSM state isn't hookable. */
	BUG_ON(!st);

	if (prio == TFW_GFSM_HOOK_PRIORITY_ANY) {
		/*
		 * Try to register the hook with higest priority.
		 * If the state slot for the priority is already acquired,
		 * then try lower priority.
		 */
		for (prio = TFW_GFSM_HOOK_PRIORITY_HIGH;
		     prio < TFW_GFSM_HOOK_PRIORITY_NUM; ++prio)
			if (!(fsm_hooks_bm[fsm_id][prio] & st_bit))
				break;
		if (prio == TFW_GFSM_HOOK_PRIORITY_NUM) {
			TFW_ERR("All hook slots for FSM %d are acquired\n",
				fsm_id);
			return -EBUSY;
		}
	}
	shift = prio * TFW_GFSM_PRIO_N + st;

	if (fsm_hooks[fsm_id][shift].fsm_id)
		return -EBUSY;
	if (!fsm_htbl[fsm_id]) {
		TFW_ERR("gfsm: fsm %d is not registered\n", fsm_id);
		return -ENOENT;
	}

	fsm_hooks[fsm_id][shift].st0 = st0;
	fsm_hooks[fsm_id][shift].fsm_id = hndl_fsm_id;
	fsm_hooks_bm[fsm_id][prio] |= st_bit;

	return prio;
}
Example #9
0
/**
 * Parse IP address, create a socket and bind it with the address,
 * but not yet start listening.
 */
static int
add_listen_sock(TfwAddr *addr, int type)
{
	int r;
	SsProto *proto;
	struct socket *s;

	if (listen_socks_n == ARRAY_SIZE(listen_socks)) {
		TFW_ERR("maximum number of listen sockets (%d) is reached\n",
			listen_socks_n);
		return -ENOBUFS;
	}

	r = sock_create_kern(addr->sa.sa_family, SOCK_STREAM, IPPROTO_TCP, &s);
	if (r) {
		TFW_ERR("can't create socket (err: %d)\n", r);
		return r;
	}

	inet_sk(s->sk)->freebind = 1;
	s->sk->sk_reuse = 1;
	r = s->ops->bind(s, &addr->sa, tfw_addr_sa_len(addr));
	if (r) {
		TFW_ERR_ADDR("can't bind to", addr);
		sock_release(s);
		return r;
	}

	proto = &protos[listen_socks_n];
	proto->type = type;
	BUG_ON(proto->listener);
	ss_tcp_set_listen(s, proto);
	TFW_DBG("created front-end socket: sk=%p\n", s->sk);

	BUG_ON(listen_socks[listen_socks_n]);
	listen_socks[listen_socks_n] = s;
	++listen_socks_n;

	return 0;
}
Example #10
0
/**
 * Create a listening front-end socket.
 */
static int
__open_listen_socket(SsProto *proto, void *addr)
{
	struct socket *s;
	unsigned short family = *(unsigned short *)addr;
	unsigned short sza = family == AF_INET
			     ? sizeof(struct sockaddr_in)
			     : sizeof(struct sockaddr_in6);
	int r;

	r = sock_create_kern(family, SOCK_STREAM, IPPROTO_TCP, &s);
	if (r) {
		TFW_ERR("Can't create front-end listening socket (%d)\n", r);
		return r;
	}

	inet_sk(s->sk)->freebind = 1;
	s->sk->sk_reuse = 1;
	r = s->ops->bind(s, (struct sockaddr *)addr, sza);
	if (r) {
		TFW_ERR("Can't bind front-end listening socket (%d)\n", r);
		goto err;
	}

	ss_tcp_set_listen(s, proto);
	TFW_DBG("Created listening socket %p\n", s->sk);

	/* TODO adjust /proc/sys/net/core/somaxconn */
	r = s->ops->listen(s, 1024);
	if (r) {
		TFW_ERR("Can't listen on front-end socket (%d)\n", r);
		goto err;
	}

	return r;
err:
	sock_release(s);
	return r;
}
Example #11
0
int
tfw_sched_http_init(void)
{
	int ret;

	TFW_DBG("sched_http: init\n");

	ret = tfw_cfg_mod_register(&tfw_sched_http_cfg_mod);
	if (ret) {
		TFW_ERR("sched_http: can't register configuration module\n");
		return ret;
	}

	ret = tfw_sched_register(&tfw_sched_http);
	if (ret) {
		TFW_ERR("sched_http: can't register scheduler module\n");
		tfw_cfg_mod_unregister(&tfw_sched_http_cfg_mod);
		return ret;
	}

	return 0;
}
Example #12
0
int
tfw_open_listen_sockets(void)
{
	struct sockaddr_in6 *addr;
	int r = -ENOMEM;
	__be16 ports[DEF_MAX_PORTS];

	down_read(&tfw_cfg.mtx);

	TFW_LOG("Open %u listening sockets\n", tfw_cfg.listen->count);

	protos = kzalloc(sizeof(void *) * tfw_cfg.listen->count, GFP_KERNEL);
	if (!protos)
		goto out;

	for (listen_socks_n = 0; listen_socks_n < tfw_cfg.listen->count;
	     ++listen_socks_n)
	{
		SsProto *proto;

		if (listen_socks_n > DEF_MAX_PORTS) {
			TFW_ERR("Too many listening sockets\n");
			tfw_close_listen_sockets();
			goto out;
		}

		/*
		 * TODO If multiprotocol support is required, then here we must
		 * have information for which protocol we're establishing
		 * the new listener. So TfwAddrCfg must be extended with
		 * protocol information (e.g. HTTP enum value).
		 */
		proto = protos + listen_socks_n;
		proto->type = TFW_FSM_HTTP;
		addr = (struct sockaddr_in6 *)(tfw_cfg.listen->addr
					       + listen_socks_n);
		r = __open_listen_socket(proto, addr);
		if (r) {
			tfw_close_listen_sockets();
			goto out;
		}
		ports[listen_socks_n] = addr->sin6_port;
	}

	tfw_filter_set_inports(ports, listen_socks_n);

	r = 0;
out:
	up_read(&tfw_cfg.mtx);
	return r;
}
Example #13
0
int
tfw_classifier_register(TfwClassifier *mod)
{
	write_lock(&tfw_class_lock);
	if (classifier) {
		write_unlock(&tfw_class_lock);
		TFW_ERR("can't register a classifier - there is already one"
		        " registered\n");
		return -1;
	}
	classifier = mod;
	write_unlock(&tfw_class_lock);

	return 0;
}
Example #14
0
static int
start_listen_socks(void)
{
	struct socket *sock;
	int i, r;

	FOR_EACH_SOCK(sock, i) {
		/* TODO adjust /proc/sys/net/core/somaxconn */
		TFW_DBG("start listening on socket: sk=%p\n", sock->sk);
		r = sock->ops->listen(sock, LISTEN_SOCK_BACKLOG_LEN);
		if (r) {
			TFW_ERR("can't listen on front-end socket sk=%p (%d)\n",
				sock->sk, r);
			return r;
		}
	}
Example #15
0
TfwServer *
tfw_create_server(struct sock *s)
{
	TfwServer *srv = kmem_cache_alloc(srv_cache, GFP_ATOMIC);
	if (!srv)
		return NULL;

	srv->sock = s;

	if (tfw_sched_add_srv(srv)) {
		TFW_ERR("Can't add a server to requests scheduler\n");
		kmem_cache_free(srv_cache, srv);
		return NULL;
	}

	return srv;
}
Example #16
0
static ssize_t
fop_read(struct file *file, char __user *user_buf, size_t count,
	 loff_t *ppos)
{
	TfwDebugfsIoState *state = file->private_data;

	if (state->is_input) {
		TFW_ERR("Can't read this debugfs file: "
			"it was open()'ed only for writing\n");
		return -EPERM;
	}

	if (state->len < 0)
		return state->len;

	return simple_read_from_buffer(user_buf, count, ppos,
				       state->buf,
				       state->len);
}
Example #17
0
static const char *
alloc_and_copy_literal(const char *src, size_t len)
{
	const char *src_pos, *src_end;
	char *dst, *dst_pos;
	bool is_escaped;

	BUG_ON(!src);

	dst = kmalloc(len + 1, GFP_KERNEL);
	if (!dst) {
		TFW_ERR("can't allocate memory\n");
		return NULL;
	}

	/* Copy the string eating escaping backslashes. */
	/* FIXME: the logic looks like a tiny FSM,
	 *        so perhaps it should be included to the TFSM. */
	src_end = src + len;
	src_pos = src;
	dst_pos = dst;
	is_escaped = false;
	while (src_pos < src_end) {
		if (*src_pos != '\\' || is_escaped) {
			is_escaped = false;
			*dst_pos = *src_pos;
			++dst_pos;
		}
		else if (*src_pos == '\\') {
			is_escaped = true;
		}
		++src_pos;
	}
	*dst_pos = '\0';

	return dst;
}
Example #18
0
int __init
tfw_cache_init(void)
{
	int r = 0;

	if (!tfw_cfg.cache)
		return 0;

	db = tdb_open(tfw_cfg.c_path, tfw_cfg.c_size, 0);
	if (!db)
		return 1;

	cache_mgr_thr = kthread_run(tfw_cache_mgr, NULL, "tfw_cache_mgr");
	if (IS_ERR(cache_mgr_thr)) {
		r = PTR_ERR(cache_mgr_thr);
		TFW_ERR("Can't start cache manager, %d\n", r);
		goto err_thr;
	}

	c_cache = KMEM_CACHE(tfw_cache_work_t, 0);
	if (!c_cache)
		goto err_cache;

	cache_wq = alloc_workqueue("tfw_cache_wq", WQ_MEM_RECLAIM, 0);
	if (!cache_wq)
		goto err_wq;

	return 0;
err_wq:
	kmem_cache_destroy(c_cache);
err_cache:
	kthread_stop(cache_mgr_thr);
err_thr:
	tdb_close(db);
	return r;
}
Example #19
0
/**
 * Handle a "match" entry within "sched_http_rules" section, e.g.:
 *   sched_http_rules {
 *       match group1 uri prefix "/foo";
 *       match group2 host eq "example.com";
 *   }
 *
 * This callback is invoked for every such "match" entry.
 * It resolves name of the group, parses the rule and adds the entry to the
 * tfw_sched_http_rules list.
 *
 * Syntax:
 *            +------------------------ a reference to "srv_group";
 *            |     +------------------ HTTP request field
 *            |     |     +------------ operator (eq, prefix, substr, etc)
 *            |     |     |       +---- argument for the operator (any string)
 *            |     |     |       |
 *            V     V     V       V
 *    match group3 uri  prefix "/foo/bar/baz.html" backup=group4
 *                                                    ^
 *                                                    |
 *                 a backup "srv_group" (optional)----+
 *
 */
static int
tfw_sched_http_cfg_handle_match(TfwCfgSpec *cs, TfwCfgEntry *e)
{
	int r;
	size_t arg_size;
	TfwSchedHttpRule *rule;
	tfw_http_match_op_t op;
	tfw_http_match_fld_t field;
	tfw_http_match_arg_t type;
	TfwSrvGroup *main_sg, *backup_sg;
	const char *in_main_sg, *in_field, *in_op, *in_arg, *in_backup_sg;

	r = tfw_cfg_check_val_n(e, 4);
	if (r)
		return r;

	in_main_sg = e->vals[0];
	in_field = e->vals[1];
	in_op = e->vals[2];
	in_arg = e->vals[3];
	in_backup_sg = tfw_cfg_get_attr(e, "backup", NULL);

	main_sg = tfw_sg_lookup(in_main_sg);
	if (!main_sg) {
		TFW_ERR("sched_http: srv_group is not found: '%s'\n",
			in_main_sg);
		return -EINVAL;
	}

	if (!in_backup_sg) {
		backup_sg = NULL;
	} else {
		backup_sg = tfw_sg_lookup(in_backup_sg);
		if (!backup_sg) {
			TFW_ERR("sched_http: backup srv_group is not found:"
				" '%s'\n", in_backup_sg);
			return -EINVAL;
		}
	}

	r = tfw_cfg_map_enum(tfw_sched_http_cfg_field_enum, in_field, &field);
	if (r) {
		TFW_ERR("sched_http: invalid HTTP request field: '%s'\n",
			in_field);
		return -EINVAL;
	}

	r = tfw_cfg_map_enum(tfw_sched_http_cfg_op_enum, in_op, &op);
	if (r) {
		TFW_ERR("sched_http: invalid matching operator: '%s'\n",
			in_op);
		return -EINVAL;
	}

	arg_size = strlen(in_arg) + 1;
	type = tfw_sched_http_cfg_arg_tbl[field];

	rule = tfw_http_match_entry_new(tfw_sched_http_rules,
					TfwSchedHttpRule, rule, arg_size);
	if (!rule) {
		TFW_ERR("sched_http: can't allocate memory for parsed rule\n");
		return -ENOMEM;
	}

	TFW_DBG("sched_http: parsed rule: match"
		" '%s'=%p '%s'=%d '%s'=%d '%s'\n",
		in_main_sg, main_sg, in_field, field, in_op, op, in_arg);

	if (type == TFW_HTTP_MATCH_A_STR || type == TFW_HTTP_MATCH_A_WILDCARD) {
		tfw_http_match_rule_init(&rule->rule, field, op, type, in_arg);
	} else {
		BUG();
		// TODO: parsing not string matching rules
	}

	rule->main_sg = main_sg;
	rule->backup_sg = backup_sg;

	return 0;
}
Example #20
0
static unsigned int
add_body(TfwFuzzContext *ctx, char **p, char *end, int type)
{
	size_t len = 0, i, j;
	char *len_str;
	int err, ret = FUZZ_VALID;

	i = ctx->i[CONTENT_LENGTH];
	len_str = (i < gen_vector[CONTENT_LENGTH].size)
		  ? (content_len[i].flags & FUZZ_MSG_F_INVAL)
		    ? "500" /* Generate content of arbitrary size for invalid
			     * Content-Length value. */
		    : content_len[i].sval
		  : ctx->content_length;

	err = kstrtoul(len_str, 10, &len);
	if (err) {
		TFW_ERR("error %d on getting content length from \"%s\""
			"(%lu)\n", err, len_str, i);
		return FUZZ_INVALID;
	}

	if (!(ctx->fld_flags[TRANSFER_ENCODING] & FUZZ_FLD_F_CHUNKED)) {
		if (!ctx->is_only_valid && len && !(i % INVALID_BODY_PERIOD)) {
			len /= 2;
			ret = FUZZ_INVALID;
			TFW_DBG("1/2 invalid body %lu\n", len);
		}

		add_rand_string(p, end, len, A_BODY);
	}
	else {
		int chunks = ctx->i[BODY_CHUNKS_NUM] + 1;
		size_t chlen, rem, step;

		BUG_ON(chunks <= 0);

		if (len > 0) {
			chlen = len / chunks;
			rem = len % chunks;
			for (j = 0; j < chunks; j++) {
				char buf[256];

				step = chlen;
				if (rem) {
					step += rem;
					rem = 0;
				}

				snprintf(buf, sizeof(buf), "%zx", step);

				add_string(p, end, buf);
				add_string(p, end, "\r\n");

				if (!ctx->is_only_valid && step
				    && !(i % INVALID_BODY_PERIOD))
				{
					step /= 2;
					ret = FUZZ_INVALID;
					TFW_DBG("1/2 invalid chunked body %lu,"
						" chunks %d\n", len, chunks);
				}

				add_rand_string(p, end, step, A_BODY);
				add_string(p, end, "\r\n");
			}
		}

		add_string(p, end, "0\r\n\r\n");
	}

	return ret;
}
Example #21
0
/**
 * Work to copy response skbs to database mapped area.
 *
 * It's nasty to copy data on CPU, but we can't use DMA for mmaped file
 * as well as for unaligned memory areas.
 */
static void
tfw_cache_copy_resp(struct work_struct *work)
{
	int i;
	size_t hlens, tot_len;
	long n;
	char *p;
	TfwCWork *cw = (TfwCWork *)work;
	TfwCacheEntry *ce = cw->cw_ce;
	TdbVRec *trec;
	TfwHttpHdrTbl *htbl;
	TfwHttpHdr *hdr;

	BUG_ON(!ce->resp);

	/* Write HTTP headers. */
	htbl = ce->resp->h_tbl;
	ce->hdr_num = htbl->size;

	hlens = sizeof(ce->hdr_lens[0]) * ce->hdr_num;
	tot_len = hlens + ce->resp->msg.len;

	/*
	 * Try to place the cached response in single memory chunk.
	 *
	 * Number of HTTP headers is limited by TFW_HTTP_HDR_NUM_MAX while TDB
	 * should be able to allocate an empty page if we issued a large
	 * request. So HTTP header lengths must fit the first allocated data
	 * chunk, also there must be some space for headers and message bodies.
	 */
	trec = tdb_entry_add(db, (TdbVRec *)ce, tot_len);
	if (!trec || trec->len <= hlens) {
		TFW_WARN("Cannot allocate memory to cache HTTP headers."
			 " Probably TDB cache is exhausted.\n");
		goto err;
	}
	p = (char *)(trec + 1) + hlens;
	tot_len -= hlens;

	/*
	 * Set start of headers pointer just after array of
	 * header length.
	 */
	ce->hdrs = p;
	hdr = htbl->tbl;
	for (i = 0; i < hlens / sizeof(ce->hdr_lens[0]); ++i, ++hdr) {
		n = tfw_cache_copy_str_compound(&p, &trec, &hdr->field,
						tot_len);
		if (n < 0) {
			TFW_ERR("Cache: cannot copy HTTP header\n");
			goto err;
		}
		BUG_ON(n > tot_len);
		tot_len -= n;
	}

	/* Write HTTP response body. */
	ce->body = p;
	n = tfw_cache_copy_str_compound(&p, &trec, &ce->resp->body, tot_len);
	if (n < 0) {
		TFW_ERR("Cache: cannot copy HTTP body\n");
		goto err;
	}
	ce->body_len = n;

err:
	/* FIXME all allocated TDB blocks are leaked here. */
	kmem_cache_free(c_cache, cw);
}