2007-07-14 15:34:29 +02:00
|
|
|
/***************************************************************************
|
|
|
|
* Copyright (C) 07/2007 by Olaf Rempel *
|
|
|
|
* razzor@kopf-tisch.de *
|
|
|
|
* *
|
|
|
|
* 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; version 2 of the License *
|
|
|
|
* *
|
|
|
|
* This program is distributed in the hope that it will be useful, *
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
|
|
* GNU General Public License for more details. *
|
|
|
|
* *
|
|
|
|
* You should have received a copy of the GNU General Public License *
|
|
|
|
* along with this program; if not, write to the *
|
|
|
|
* Free Software Foundation, Inc., *
|
|
|
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
|
|
***************************************************************************/
|
2007-04-11 19:37:40 +02:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <net/if.h>
|
|
|
|
#include <time.h>
|
|
|
|
|
|
|
|
#include "configfile.h"
|
|
|
|
#include "datastore.h"
|
|
|
|
#include "event.h"
|
|
|
|
#include "list.h"
|
|
|
|
#include "logging.h"
|
|
|
|
|
|
|
|
struct subnet_entry {
|
|
|
|
struct list_head list;
|
|
|
|
|
|
|
|
uint32_t ip;
|
|
|
|
uint32_t mask;
|
|
|
|
|
|
|
|
char dev[IFNAMSIZ];
|
|
|
|
};
|
|
|
|
|
|
|
|
struct hash_table {
|
|
|
|
struct list_head bucket[0];
|
|
|
|
};
|
|
|
|
|
|
|
|
struct ip_entry {
|
|
|
|
struct list_head list;
|
|
|
|
|
|
|
|
uint32_t ip;
|
|
|
|
struct subnet_entry *subnet;
|
|
|
|
|
|
|
|
uint8_t mac[6];
|
|
|
|
uint32_t timestamp;
|
|
|
|
};
|
|
|
|
|
|
|
|
LIST_HEAD(subnet_list);
|
|
|
|
|
|
|
|
static int hashsize;
|
|
|
|
static struct hash_table *hashtable;
|
|
|
|
static struct event_timeout *gc_event;
|
|
|
|
|
|
|
|
static int ds_add_subnet(const char *dev, uint32_t ip, uint32_t mask)
|
|
|
|
{
|
|
|
|
struct subnet_entry *entry = malloc(sizeof(struct subnet_entry));
|
|
|
|
if (entry == NULL) {
|
|
|
|
log_print(LOG_ERROR, "ds_add_subnet(): out of memory");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
entry->ip = ip;
|
|
|
|
entry->mask = mask;
|
|
|
|
strncpy(entry->dev, dev, sizeof(entry->dev));
|
|
|
|
|
|
|
|
list_add_tail(&entry->list, &subnet_list);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct subnet_entry * ds_find_subnet(uint32_t ip)
|
|
|
|
{
|
|
|
|
struct subnet_entry *entry;
|
|
|
|
list_for_each_entry(entry, &subnet_list, list)
|
|
|
|
if ((ip & entry->mask) == entry->ip)
|
|
|
|
return entry;
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ds_calc_hash(uint32_t ip)
|
|
|
|
{
|
|
|
|
/* TODO: real hash */
|
|
|
|
return (ip * 16777219) % hashsize;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct ip_entry * ds_find_ip(uint32_t ip)
|
|
|
|
{
|
|
|
|
struct ip_entry *entry;
|
|
|
|
list_for_each_entry(entry, &(hashtable->bucket[ds_calc_hash(ip)]), list)
|
|
|
|
if (entry->ip == ip)
|
|
|
|
return entry;
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int is_same_mac(uint8_t *a, uint8_t *b)
|
|
|
|
{
|
|
|
|
return ((a[0] ^ b[0]) | (a[1] ^ b[1]) | (a[2] ^ b[2]) |
|
|
|
|
(a[3] ^ b[3]) | (a[4] ^ b[4]) | (a[5] ^ b[5])) == 0x00;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ds_error(struct ip_entry *entry, const char *msg, const char *dev)
|
|
|
|
{
|
|
|
|
char *subnetdev = '\0';
|
|
|
|
char ip[16], subnet[16] = {'\0'}, mask[16] = {'\0'};
|
|
|
|
|
|
|
|
inet_ntop(AF_INET, &entry->ip, ip, sizeof(ip));
|
|
|
|
|
|
|
|
if (entry->subnet != NULL) {
|
|
|
|
subnetdev = entry->subnet->dev;
|
|
|
|
inet_ntop(AF_INET, &entry->subnet->ip, subnet, sizeof(subnet));
|
|
|
|
inet_ntop(AF_INET, &entry->subnet->mask, mask, sizeof(mask));
|
|
|
|
}
|
|
|
|
|
|
|
|
log_print(LOG_DEBUG, "%s: paket %s: %s[%02x:%02x:%02x:%02x:%02x:%02x] subnet %s: %s/%s",
|
|
|
|
msg, dev, ip,
|
|
|
|
entry->mac[0], entry->mac[1], entry->mac[2],
|
|
|
|
entry->mac[3], entry->mac[4], entry->mac[5],
|
|
|
|
subnetdev, subnet, mask);
|
|
|
|
}
|
|
|
|
|
|
|
|
int ds_check_update_ip(const char *dev, uint32_t ip, uint8_t *mac, uint32_t timestamp)
|
|
|
|
{
|
|
|
|
struct ip_entry *entry = ds_find_ip(ip);
|
|
|
|
if (entry == NULL) {
|
|
|
|
entry = malloc(sizeof(struct ip_entry));
|
|
|
|
if (entry == NULL) {
|
|
|
|
log_print(LOG_ERROR, "ds_check_update_ip(): out of memory");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
entry->ip = ip;
|
|
|
|
memcpy(entry->mac, mac, 6);
|
|
|
|
entry->timestamp = timestamp;
|
|
|
|
|
|
|
|
entry->subnet = ds_find_subnet(ip);
|
|
|
|
if (entry->subnet == NULL) {
|
|
|
|
ds_error(entry, "NEW: subnet not found", dev);
|
|
|
|
free(entry);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (strncmp(entry->subnet->dev, dev, sizeof(entry->subnet->dev)) != 0) {
|
|
|
|
ds_error(entry, "NEW: invalid device", dev);
|
|
|
|
free(entry);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
list_add_tail(&entry->list, &(hashtable->bucket[ds_calc_hash(ip)]));
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
} else if (strncmp(entry->subnet->dev, dev, sizeof(entry->subnet->dev)) != 0) {
|
|
|
|
ds_error(entry, "UPDATE: invalid device", dev);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_same_mac(entry->mac, mac)) {
|
|
|
|
entry->timestamp = timestamp;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
char ip[16];
|
|
|
|
inet_ntop(AF_INET, &entry->ip, ip, sizeof(ip));
|
|
|
|
|
|
|
|
log_print(LOG_DEBUG, "UPDATE: %s: %s [%02x:%02x:%02x:%02x:%02x:%02x] -> [%02x:%02x:%02x:%02x:%02x:%02x]",
|
|
|
|
dev, ip,
|
|
|
|
entry->mac[0], entry->mac[1], entry->mac[2],
|
|
|
|
entry->mac[3], entry->mac[4], entry->mac[5],
|
|
|
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
|
|
|
|
|
|
memcpy(entry->mac, mac, 6);
|
|
|
|
entry->timestamp = timestamp;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int parse_netmask(char *buf, uint32_t *ip, uint32_t *mask)
|
|
|
|
{
|
|
|
|
char *maskstr = strchr(buf, '/');
|
|
|
|
if (maskstr == NULL)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
*maskstr++ = '\0';
|
|
|
|
|
|
|
|
if (inet_pton(AF_INET, buf, ip) <= 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (inet_pton(AF_INET, maskstr, mask) <= 0) {
|
|
|
|
*mask = atoi(maskstr);
|
|
|
|
if (*mask < 0 || *mask > 32)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
*mask = htonl(0xFFFFFFFF << (32 - *mask));
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int add_subnet_cb(const char *parameter, void *privdata)
|
|
|
|
{
|
|
|
|
char *dev = strdup(parameter);
|
|
|
|
|
|
|
|
char *ipmask = strchr(dev, ':');
|
|
|
|
if (ipmask == NULL) {
|
|
|
|
log_print(LOG_WARN, "add_subnet_cb(): no ip found");
|
|
|
|
free(dev);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*ipmask++ = '\0';
|
|
|
|
|
|
|
|
uint32_t ip, mask;
|
|
|
|
if (parse_netmask(ipmask, &ip, &mask) != 0) {
|
|
|
|
log_print(LOG_WARN, "add_subnet_cb(): invalid ip/mask");
|
|
|
|
free(dev);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
char ipstr[16], maskstr[16];
|
|
|
|
inet_ntop(AF_INET, &ip, ipstr, sizeof(ipstr));
|
|
|
|
inet_ntop(AF_INET, &mask, maskstr, sizeof(maskstr));
|
|
|
|
log_print(LOG_INFO, "datastore: adding device %s with subnet %s/%s", dev, ipstr, maskstr);
|
|
|
|
|
|
|
|
ds_add_subnet(dev, ip, mask);
|
|
|
|
free(dev);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hash_gc(void *privdata)
|
|
|
|
{
|
|
|
|
uint32_t check = time(NULL) - (int)privdata;
|
|
|
|
|
|
|
|
uint32_t i, total = 0, removed = 0;
|
|
|
|
for (i = 0; i < hashsize; i++) {
|
|
|
|
struct ip_entry *entry, *tmp;
|
|
|
|
list_for_each_entry_safe(entry, tmp, &(hashtable->bucket[i]), list) {
|
|
|
|
total++;
|
|
|
|
if (entry->timestamp < check) {
|
|
|
|
list_del(&entry->list);
|
|
|
|
free(entry);
|
|
|
|
removed++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log_print(LOG_DEBUG, "garbage collector: %d/%d", removed, total);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int datastore_init(void)
|
|
|
|
{
|
|
|
|
hashsize = config_get_int("global", "hashsize", 1021);
|
|
|
|
hashtable = malloc(sizeof(struct hash_table) + sizeof(struct list_head) * hashsize);
|
|
|
|
if (hashtable == NULL) {
|
|
|
|
log_print(LOG_ERROR, "ds_init(): out of memory");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < hashsize; i++)
|
|
|
|
INIT_LIST_HEAD(&(hashtable->bucket[i]));
|
|
|
|
|
|
|
|
int cnt = config_get_strings("global", "subnet", add_subnet_cb, NULL);
|
|
|
|
if (cnt <= 0) {
|
|
|
|
log_print(LOG_ERROR, "ds_init(): no subnets defined");
|
|
|
|
free(hashtable);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int timeout = config_get_int("global", "hashgc", 300);
|
|
|
|
struct timeval tv = { .tv_sec = timeout, .tv_usec = 0 };
|
|
|
|
gc_event = event_add_timeout(&tv, hash_gc, (void *)timeout);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void datastore_close(void)
|
|
|
|
{
|
|
|
|
struct subnet_entry *entry, *tmp;
|
|
|
|
list_for_each_entry_safe(entry, tmp, &subnet_list, list) {
|
|
|
|
list_del(&entry->list);
|
|
|
|
free(entry);
|
|
|
|
}
|
|
|
|
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < hashsize; i++) {
|
|
|
|
struct ip_entry *entry, *tmp;
|
|
|
|
list_for_each_entry_safe(entry, tmp, &(hashtable->bucket[i]), list) {
|
|
|
|
list_del(&entry->list);
|
|
|
|
free(entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
event_remove_timeout(gc_event);
|
|
|
|
free(hashtable);
|
|
|
|
}
|