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