blob: 8599d4853ef6d51732abf23259c0b0f21e7f9823 [file] [log] [blame]
/*
* communication.c, v2.0 April 2002
*
* Author: Bart De Schuymer
*
*/
// All the userspace/kernel communication is in this file.
// The other code should not have to know anything about the way the
// kernel likes the structure of the table data.
// The other code works with linked lists, lots of linked lists.
// So, the translation is done here.
#include <getopt.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <linux/netfilter_bridge/ebtables.h>
#include <linux/br_db.h> // the database
#include <netinet/in.h> // IPPROTO_IP
#include <asm/types.h>
#include "include/ebtables_u.h"
extern char* hooknames[NF_BR_NUMHOOKS];
int sockfd = -1;
void get_sockfd()
{
if (sockfd == -1) {
sockfd = socket(AF_INET, SOCK_RAW, PF_INET);
if (sockfd < 0)
print_error("Problem getting a socket");
}
}
static struct ebt_replace * translate_user2kernel(struct ebt_u_replace *u_repl)
{
struct ebt_replace *new;
struct ebt_u_entry *e;
struct ebt_u_match_list *m_l;
struct ebt_u_watcher_list *w_l;
struct ebt_u_chain_list *cl;
struct ebt_u_entries *entries;
char *p, *base;
int i, j;
unsigned int entries_size = 0, *chain_offsets;
new = (struct ebt_replace *)malloc(sizeof(struct ebt_replace));
if (!new)
print_memory();
new->valid_hooks = u_repl->valid_hooks;
memcpy(new->name, u_repl->name, sizeof(new->name));
new->nentries = u_repl->nentries;
new->num_counters = u_repl->num_counters;
new->counters = u_repl->counters;
// determine nr of udc
i = 0;
cl = u_repl->udc;
while (cl) {
i++;
cl = cl->next;
}
i += NF_BR_NUMHOOKS;
chain_offsets = (unsigned int *)malloc(i * sizeof(unsigned int));
// determine size
i = 0;
cl = u_repl->udc;
while (1) {
if (i < NF_BR_NUMHOOKS) {
if (!(new->valid_hooks & (1 << i))) {
i++;
continue;
}
entries = u_repl->hook_entry[i];
} else {
if (!cl)
break;
entries = cl->udc;
}
chain_offsets[i] = entries_size;
entries_size += sizeof(struct ebt_entries);
j = 0;
e = entries->entries;
while (e) {
j++;
entries_size += sizeof(struct ebt_entry);
m_l = e->m_list;
while (m_l) {
entries_size += m_l->m->match_size +
sizeof(struct ebt_entry_match);
m_l = m_l->next;
}
w_l = e->w_list;
while (w_l) {
entries_size += w_l->w->watcher_size +
sizeof(struct ebt_entry_watcher);
w_l = w_l->next;
}
entries_size += e->t->target_size +
sizeof(struct ebt_entry_target);
e = e->next;
}
// a little sanity check
if (j != entries->nentries)
print_bug("Wrong nentries: %d != %d, hook = %s", j,
entries->nentries, entries->name);
if (i >= NF_BR_NUMHOOKS)
cl = cl->next;
i++;
}
new->entries_size = entries_size;
new->entries = (char *)malloc(entries_size);
if (!new->entries)
print_memory();
// put everything in one block
p = new->entries;
i = 0;
cl = u_repl->udc;
while (1) {
struct ebt_entries *hlp;
hlp = (struct ebt_entries *)p;
if (i < NF_BR_NUMHOOKS) {
if (!(new->valid_hooks & (1 << i))) {
i++;
continue;
}
entries = u_repl->hook_entry[i];
new->hook_entry[i] = hlp;
} else {
if (!cl)
break;
entries = cl->udc;
}
hlp->nentries = entries->nentries;
hlp->policy = entries->policy;
strcpy(hlp->name, entries->name);
hlp->counter_offset = entries->counter_offset;
hlp->distinguisher = 0; // make the kernel see the light
p += sizeof(struct ebt_entries);
e = entries->entries;
while (e) {
struct ebt_entry *tmp = (struct ebt_entry *)p;
tmp->bitmask = e->bitmask | EBT_ENTRY_OR_ENTRIES;
tmp->invflags = e->invflags;
tmp->ethproto = e->ethproto;
memcpy(tmp->in, e->in, sizeof(tmp->in));
memcpy(tmp->out, e->out, sizeof(tmp->out));
memcpy(tmp->logical_in, e->logical_in,
sizeof(tmp->logical_in));
memcpy(tmp->logical_out, e->logical_out,
sizeof(tmp->logical_out));
memcpy(tmp->sourcemac, e->sourcemac,
sizeof(tmp->sourcemac));
memcpy(tmp->sourcemsk, e->sourcemsk,
sizeof(tmp->sourcemsk));
memcpy(tmp->destmac, e->destmac, sizeof(tmp->destmac));
memcpy(tmp->destmsk, e->destmsk, sizeof(tmp->destmsk));
base = p;
p += sizeof(struct ebt_entry);
m_l = e->m_list;
while (m_l) {
memcpy(p, m_l->m, m_l->m->match_size +
sizeof(struct ebt_entry_match));
p += m_l->m->match_size +
sizeof(struct ebt_entry_match);
m_l = m_l->next;
}
tmp->watchers_offset = p - base;
w_l = e->w_list;
while (w_l) {
memcpy(p, w_l->w, w_l->w->watcher_size +
sizeof(struct ebt_entry_watcher));
p += w_l->w->watcher_size +
sizeof(struct ebt_entry_watcher);
w_l = w_l->next;
}
tmp->target_offset = p - base;
memcpy(p, e->t, e->t->target_size +
sizeof(struct ebt_entry_target));
if (!strcmp(e->t->u.name, EBT_STANDARD_TARGET)) {
struct ebt_standard_target *st =
(struct ebt_standard_target *)p;
// translate the jump to a udc
if (st->verdict >= 0)
st->verdict = chain_offsets[st->verdict + NF_BR_NUMHOOKS];
}
p += e->t->target_size +
sizeof(struct ebt_entry_target);
tmp->next_offset = p - base;
e = e->next;
}
if (i >= NF_BR_NUMHOOKS)
cl = cl->next;
i++;
}
// sanity check
if (p - new->entries != new->entries_size)
print_bug("Entries_size bug");
free(chain_offsets);
return new;
}
void deliver_table(struct ebt_u_replace *u_repl)
{
socklen_t optlen;
struct ebt_replace *repl;
// translate the struct ebt_u_replace to a struct ebt_replace
repl = translate_user2kernel(u_repl);
get_sockfd();
// give the data to the kernel
optlen = sizeof(struct ebt_replace) + repl->entries_size;
if (setsockopt(sockfd, IPPROTO_IP, EBT_SO_SET_ENTRIES, repl, optlen))
print_error("The kernel doesn't support a certain ebtables"
" extension, consider recompiling your kernel or insmod"
" the extension");
}
// gets executed after deliver_table
void
deliver_counters(struct ebt_u_replace *u_repl, unsigned short *counterchanges)
{
unsigned short *point;
struct ebt_counter *old, *new, *newcounters;
socklen_t optlen;
struct ebt_replace repl;
if (u_repl->nentries == 0)
return;
newcounters = (struct ebt_counter *)
malloc(u_repl->nentries * sizeof(struct ebt_counter));
if (!newcounters)
print_memory();
memset(newcounters, 0, u_repl->nentries * sizeof(struct ebt_counter));
old = u_repl->counters;
new = newcounters;
point = counterchanges;
while (*point != CNT_END) {
if (*point == CNT_NORM) {
// 'normal' rule, meaning we didn't do anything to it
// So, we just copy
new->pcnt = old->pcnt;
// we've used an old counter
old++;
// we've set a new counter
new++;
} else
if (*point == CNT_DEL) {
// don't use this old counter
old++;
} else if (*point == CNT_ADD) {
// new counter, let it stay 0
new++;
} else {
// zero it
new->pcnt = 0;
old++;
new++;
}
point++;
}
free(u_repl->counters);
u_repl->counters = newcounters;
u_repl->num_counters = u_repl->nentries;
optlen = u_repl->nentries * sizeof(struct ebt_counter) +
sizeof(struct ebt_replace);
// now put the stuff in the kernel's struct ebt_replace
repl.counters = u_repl->counters;
repl.num_counters = u_repl->num_counters;
memcpy(repl.name, u_repl->name, sizeof(repl.name));
get_sockfd();
if (setsockopt(sockfd, IPPROTO_IP, EBT_SO_SET_COUNTERS, &repl, optlen))
print_bug("couldn't update kernel counters");
}
static int
ebt_translate_match(struct ebt_entry_match *m, struct ebt_u_match_list ***l)
{
struct ebt_u_match_list *new;
new = (struct ebt_u_match_list *)
malloc(sizeof(struct ebt_u_match_list));
if (!new)
print_memory();
new->m = (struct ebt_entry_match *)
malloc(m->match_size + sizeof(struct ebt_entry_match));
if (!new->m)
print_memory();
memcpy(new->m, m, m->match_size + sizeof(struct ebt_entry_match));
new->next = NULL;
**l = new;
*l = &new->next;
if (find_match(new->m->u.name) == NULL)
print_error("Kernel match %s unsupported by userspace tool",
new->m->u.name);
return 0;
}
static int
ebt_translate_watcher(struct ebt_entry_watcher *w,
struct ebt_u_watcher_list ***l)
{
struct ebt_u_watcher_list *new;
new = (struct ebt_u_watcher_list *)
malloc(sizeof(struct ebt_u_watcher_list));
if (!new)
print_memory();
new->w = (struct ebt_entry_watcher *)
malloc(w->watcher_size + sizeof(struct ebt_entry_watcher));
if (!new->w)
print_memory();
memcpy(new->w, w, w->watcher_size + sizeof(struct ebt_entry_watcher));
new->next = NULL;
**l = new;
*l = &new->next;
if (find_watcher(new->w->u.name) == NULL)
print_error("Kernel watcher %s unsupported by userspace tool",
new->w->u.name);
return 0;
}
static int
ebt_translate_entry(struct ebt_entry *e, unsigned int *hook, int *n, int *cnt,
int *totalcnt, struct ebt_u_entry ***u_e, struct ebt_u_replace *u_repl,
unsigned int valid_hooks, char *base)
{
// an entry
if (e->bitmask & EBT_ENTRY_OR_ENTRIES) {
struct ebt_u_entry *new;
struct ebt_u_match_list **m_l;
struct ebt_u_watcher_list **w_l;
struct ebt_entry_target *t;
new = (struct ebt_u_entry *)malloc(sizeof(struct ebt_u_entry));
if (!new)
print_memory();
new->bitmask = e->bitmask;
// plain userspace code doesn't know about EBT_ENTRY_OR_ENTRIES
new->bitmask &= ~EBT_ENTRY_OR_ENTRIES;
new->invflags = e->invflags;
new->ethproto = e->ethproto;
memcpy(new->in, e->in, sizeof(new->in));
memcpy(new->out, e->out, sizeof(new->out));
memcpy(new->logical_in, e->logical_in,
sizeof(new->logical_in));
memcpy(new->logical_out, e->logical_out,
sizeof(new->logical_out));
memcpy(new->sourcemac, e->sourcemac, sizeof(new->sourcemac));
memcpy(new->sourcemsk, e->sourcemsk, sizeof(new->sourcemsk));
memcpy(new->destmac, e->destmac, sizeof(new->destmac));
memcpy(new->destmsk, e->destmsk, sizeof(new->destmsk));
new->m_list = NULL;
new->w_list = NULL;
new->next = NULL;
m_l = &new->m_list;
EBT_MATCH_ITERATE(e, ebt_translate_match, &m_l);
w_l = &new->w_list;
EBT_WATCHER_ITERATE(e, ebt_translate_watcher, &w_l);
t = (struct ebt_entry_target *)(((char *)e) + e->target_offset);
new->t = (struct ebt_entry_target *)
malloc(t->target_size + sizeof(struct ebt_entry_target));
if (!new->t)
print_memory();
if (find_target(t->u.name) == NULL)
print_error("Kernel target %s unsupported by "
"userspace tool", t->u.name);
memcpy(new->t, t, t->target_size +
sizeof(struct ebt_entry_target));
// deal with jumps to udc
if (!strcmp(t->u.name, EBT_STANDARD_TARGET)) {
char *tmp = base;
int verdict = ((struct ebt_standard_target *)t)->verdict;
int i;
struct ebt_u_chain_list *cl;
if (verdict >= 0) {
tmp += verdict;
cl = u_repl->udc;
i = 0;
while (cl && cl->kernel_start != tmp) {
i++;
cl = cl->next;
}
if (!cl)
print_bug("can't find udc for jump");
((struct ebt_standard_target *)new->t)->verdict = i;
}
}
// I love pointers
**u_e = new;
*u_e = &new->next;
(*cnt)++;
(*totalcnt)++;
return 0;
} else { // a new chain
int i;
struct ebt_entries *entries = (struct ebt_entries *)e;
struct ebt_u_chain_list *cl;
if (*n != *cnt)
print_bug("Nr of entries in the chain is wrong");
*n = entries->nentries;
*cnt = 0;
for (i = *hook + 1; i < NF_BR_NUMHOOKS; i++)
if (valid_hooks & (1 << i))
break;
*hook = i;
// makes use of fact that standard chains come before udc
if (i >= NF_BR_NUMHOOKS) { // udc
i -= NF_BR_NUMHOOKS;
cl = u_repl->udc;
while (i-- > 0)
cl = cl->next;
*u_e = &(cl->udc->entries);
} else {
*u_e = &(u_repl->hook_entry[*hook]->entries);
}
return 0;
}
}
// initialize all chain headers
static int
ebt_translate_chains(struct ebt_entry *e, unsigned int *hook,
struct ebt_u_replace *u_repl, unsigned int valid_hooks)
{
int i;
struct ebt_entries *entries = (struct ebt_entries *)e;
struct ebt_u_entries *new;
struct ebt_u_chain_list **chain_list;
if (!(e->bitmask & EBT_ENTRY_OR_ENTRIES)) {
for (i = *hook + 1; i < NF_BR_NUMHOOKS; i++)
if (valid_hooks & (1 << i))
break;
// makes use of fact that standard chains come before udc
if (i >= NF_BR_NUMHOOKS) { // udc
chain_list = &u_repl->udc;
// add in the back
while (*chain_list)
chain_list = &((*chain_list)->next);
*chain_list = (struct ebt_u_chain_list *)
malloc(sizeof(struct ebt_u_chain_list));
if (!(*chain_list))
print_memory();
(*chain_list)->next = NULL;
(*chain_list)->udc = (struct ebt_u_entries *)
malloc(sizeof(struct ebt_u_entries));
if (!((*chain_list)->udc))
print_memory();
new = (*chain_list)->udc;
// ebt_translate_entry depends on this for knowing
// to which chain is being jumped
(*chain_list)->kernel_start = (char *)e;
} else {
*hook = i;
new = (struct ebt_u_entries *)
malloc(sizeof(struct ebt_u_entries));
if (!new)
print_memory();
u_repl->hook_entry[*hook] = new;
}
new->nentries = entries->nentries;
new->policy = entries->policy;
new->entries = NULL;
new->counter_offset = entries->counter_offset;
strcpy(new->name, entries->name);
}
return 0;
}
// talk with kernel to receive the kernel's table
int get_table(struct ebt_u_replace *u_repl)
{
int i, j, k, hook;
socklen_t optlen;
struct ebt_replace repl;
struct ebt_u_entry **u_e;
get_sockfd();
optlen = sizeof(struct ebt_replace);
strcpy(repl.name, u_repl->name);
if (getsockopt(sockfd, IPPROTO_IP, EBT_SO_GET_INFO, &repl, &optlen))
return -1;
if ( !(repl.entries = (char *) malloc(repl.entries_size)) )
print_memory();
if (repl.nentries) {
if (!(repl.counters = (struct ebt_counter *)
malloc(repl.nentries * sizeof(struct ebt_counter))) )
print_memory();
}
else
repl.counters = NULL;
// we want to receive the counters
repl.num_counters = repl.nentries;
optlen += repl.entries_size + repl.num_counters *
sizeof(struct ebt_counter);
if (getsockopt(sockfd, IPPROTO_IP, EBT_SO_GET_ENTRIES, &repl, &optlen))
print_bug("hmm, what is wrong??? bug#1");
// translate the struct ebt_replace to a struct ebt_u_replace
memcpy(u_repl->name, repl.name, sizeof(u_repl->name));
u_repl->valid_hooks = repl.valid_hooks;
u_repl->nentries = repl.nentries;
u_repl->num_counters = repl.num_counters;
u_repl->counters = repl.counters;
u_repl->udc = NULL;
hook = -1;
EBT_ENTRY_ITERATE(repl.entries, repl.entries_size, ebt_translate_chains,
&hook, u_repl, u_repl->valid_hooks);
i = 0; // holds the expected nr. of entries for the chain
j = 0; // holds the up to now counted entries for the chain
k = 0; // holds the total nr. of entries,
// should equal u_repl->nentries afterwards
hook = -1;
EBT_ENTRY_ITERATE(repl.entries, repl.entries_size, ebt_translate_entry,
&hook, &i, &j, &k, &u_e, u_repl, u_repl->valid_hooks, repl.entries);
if (k != u_repl->nentries)
print_bug("Wrong total nentries");
return 0;
}
void get_dbinfo(struct brdb_dbinfo *nr)
{
socklen_t optlen = sizeof(struct brdb_dbinfo);
get_sockfd();
if (getsockopt(sockfd, IPPROTO_IP, BRDB_SO_GET_DBINFO, nr, &optlen))
print_error("Sorry, br_db code probably not in kernel, "
"try insmod br_db");
}
void get_db(int len, struct brdb_dbentry *db)
{
socklen_t optlen = len;
get_sockfd();
if ( getsockopt(sockfd, IPPROTO_IP, BRDB_SO_GET_DB, db, &optlen) ) {
print_bug("hmm, what is wrong??? bug#2");
}
}
void deliver_allowdb(__u16 *decision)
{
socklen_t optlen = sizeof(__u16);
get_sockfd();
if (setsockopt(sockfd, IPPROTO_IP, BRDB_SO_SET_ALLOWDB,
decision, optlen))
print_error("Sorry, br_db code probably not in kernel, "
"try insmod br_db");
}