brarpwatch/datastore.c

287 lines
6.4 KiB
C

#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);
}