blob: acbfa7da5ebe667eee216c6b8116e70244822e81 [file] [log] [blame]
/*
* (C) 2009 by Pablo Neira Ayuso <pablo@netfilter.org>
*
* 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 <stdlib.h>
#include <unistd.h>
#include <net/if.h>
#include <string.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <errno.h>
#include "conntrackd.h"
#include "channel.h"
#include "network.h"
#include "queue.h"
static struct channel_ops *ops[CHANNEL_MAX];
extern struct channel_ops channel_mcast;
extern struct channel_ops channel_udp;
extern struct channel_ops channel_tcp;
static struct queue *errorq;
int channel_init(void)
{
ops[CHANNEL_MCAST] = &channel_mcast;
ops[CHANNEL_UDP] = &channel_udp;
ops[CHANNEL_TCP] = &channel_tcp;
errorq = queue_create("errorq", CONFIG(channelc).error_queue_length, 0);
if (errorq == NULL) {
return -1;
}
return 0;
}
void channel_end(void)
{
queue_destroy(errorq);
}
struct channel_buffer {
char *data;
int size;
int len;
};
static struct channel_buffer *
channel_buffer_open(int mtu, int headersiz)
{
struct channel_buffer *b;
b = calloc(sizeof(struct channel_buffer), 1);
if (b == NULL)
return NULL;
b->size = mtu - headersiz;
b->data = malloc(b->size);
if (b->data == NULL) {
free(b);
return NULL;
}
return b;
}
static void
channel_buffer_close(struct channel_buffer *b)
{
if (b == NULL)
return;
free(b->data);
free(b);
}
struct channel *
channel_open(struct channel_conf *cfg)
{
struct channel *c;
struct ifreq ifr;
int fd;
if (cfg->channel_type >= CHANNEL_MAX)
return NULL;
if (!cfg->channel_ifname[0])
return NULL;
if (cfg->channel_flags >= CHANNEL_F_MAX)
return NULL;
c = calloc(sizeof(struct channel), 1);
if (c == NULL)
return NULL;
c->channel_type = cfg->channel_type;
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd == -1) {
free(c);
return NULL;
}
strncpy(ifr.ifr_name, cfg->channel_ifname, sizeof(ifr.ifr_name));
if (ioctl(fd, SIOCGIFMTU, &ifr) == -1) {
free(c);
close(fd);
return NULL;
}
close(fd);
c->channel_ifmtu = ifr.ifr_mtu;
c->channel_ifindex = if_nametoindex(cfg->channel_ifname);
if (c->channel_ifindex == 0) {
free(c);
return NULL;
}
c->ops = ops[cfg->channel_type];
if (cfg->channel_flags & CHANNEL_F_BUFFERED) {
c->buffer = channel_buffer_open(c->channel_ifmtu,
c->ops->headersiz);
if (c->buffer == NULL) {
free(c);
return NULL;
}
}
c->channel_flags = cfg->channel_flags;
c->data = c->ops->open(&cfg->u);
if (c->data == NULL) {
channel_buffer_close(c->buffer);
free(c);
return NULL;
}
return c;
}
void
channel_close(struct channel *c)
{
c->ops->close(c->data);
if (c->channel_flags & CHANNEL_F_BUFFERED)
channel_buffer_close(c->buffer);
free(c);
}
struct channel_error {
char *data;
int len;
};
static void channel_enqueue_errors(struct channel *c)
{
struct queue_object *qobj;
struct channel_error *error;
qobj = queue_object_new(Q_ELEM_ERR, sizeof(struct channel_error));
if (qobj == NULL)
return;
error = (struct channel_error *)qobj->data;
error->len = c->buffer->len;
error->data = malloc(c->buffer->len);
if (error->data == NULL) {
queue_object_free(qobj);
return;
}
memcpy(error->data, c->buffer->data, c->buffer->len);
if (queue_add(errorq, &qobj->qnode) < 0) {
if (errno == ENOSPC) {
struct queue_node *tail;
struct channel_error *tmp;
tail = queue_del_head(errorq);
tmp = queue_node_data(tail);
free(tmp->data);
queue_object_free((struct queue_object *)tail);
queue_add(errorq, &qobj->qnode);
}
}
}
static int channel_handle_error_step(struct queue_node *n, const void *data2)
{
struct channel_error *error;
const struct channel *c = data2;
int ret;
error = queue_node_data(n);
ret = c->ops->send(c->data, error->data, error->len);
if (ret != -1) {
/* Success. Delete it from the error queue. */
queue_del(n);
free(error->data);
queue_object_free((struct queue_object *)n);
} else {
/* We failed to deliver, give up now, try later. */
return 1;
}
return 0;
}
static int channel_handle_errors(struct channel *c)
{
/* there are pending errors that we have to handle. */
if (c->channel_flags & CHANNEL_F_ERRORS && queue_len(errorq) > 0) {
queue_iterate(errorq, c, channel_handle_error_step);
return queue_len(errorq) > 0;
}
return 0;
}
int channel_send(struct channel *c, const struct nethdr *net)
{
int ret = 0, len = ntohs(net->len), pending_errors;
pending_errors = channel_handle_errors(c);
if (!(c->channel_flags & CHANNEL_F_BUFFERED)) {
c->ops->send(c->data, net, len);
return 1;
}
retry:
if (c->buffer->len + len < c->buffer->size) {
memcpy(c->buffer->data + c->buffer->len, net, len);
c->buffer->len += len;
} else {
/* We've got pending packets to deliver, enqueue this
* packet to avoid possible re-ordering. */
if (pending_errors) {
channel_enqueue_errors(c);
} else {
ret = c->ops->send(c->data, c->buffer->data,
c->buffer->len);
if (ret == -1 &&
(c->channel_flags & CHANNEL_F_ERRORS)) {
/* Give it another chance to deliver. */
channel_enqueue_errors(c);
}
}
ret = 1;
c->buffer->len = 0;
goto retry;
}
return ret;
}
int channel_send_flush(struct channel *c)
{
int ret, pending_errors;
pending_errors = channel_handle_errors(c);
if (!(c->channel_flags & CHANNEL_F_BUFFERED) || c->buffer->len == 0)
return 0;
/* We still have pending errors to deliver, avoid any re-ordering. */
if (pending_errors) {
channel_enqueue_errors(c);
} else {
ret = c->ops->send(c->data, c->buffer->data, c->buffer->len);
if (ret == -1 && (c->channel_flags & CHANNEL_F_ERRORS)) {
/* Give it another chance to deliver it. */
channel_enqueue_errors(c);
}
}
c->buffer->len = 0;
return 1;
}
int channel_recv(struct channel *c, char *buf, int size)
{
return c->ops->recv(c->data, buf, size);
}
int channel_get_fd(struct channel *c)
{
return c->ops->get_fd(c->data);
}
void channel_stats(struct channel *c, int fd)
{
return c->ops->stats(c, fd);
}
void channel_stats_extended(struct channel *c, int active,
struct nlif_handle *h, int fd)
{
return c->ops->stats_extended(c, active, h, fd);
}
int channel_accept_isset(struct channel *c, fd_set *readfds)
{
return c->ops->accept_isset(c, readfds);
}
int channel_isset(struct channel *c, fd_set *readfds)
{
return c->ops->isset(c, readfds);
}
int channel_accept(struct channel *c)
{
return c->ops->accept(c);
}
int channel_type(struct channel *c)
{
return c->ops->type;
}