Codebase list tayga / fresh-snapshots/main dynamic.c
fresh-snapshots/main

Tree @fresh-snapshots/main (Download .tar.gz)

dynamic.c @fresh-snapshots/mainraw · history · blame

/*
 *  dynamic.c -- dynamic address mapper
 *
 *  part of TAYGA <http://www.litech.org/tayga/>
 *  Copyright (C) 2010  Nathan Lutchansky <lutchann@litech.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.
 *
 *  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.
 */

#include <tayga.h>

#define MAP_FILE	"dynamic.map"
#define TMP_MAP_FILE	"dynamic.map~~"

extern struct config *gcfg;
extern time_t now;

static struct map_dynamic *alloc_map_dynamic(const struct in6_addr *addr6,
		const struct in_addr *addr4, struct free_addr *f)
{
	struct map_dynamic *d;
	uint32_t a = ntohl(addr4->s_addr);

	d = (struct map_dynamic *)malloc(sizeof(struct map_dynamic));
	if (!d) {
		slog(LOG_CRIT, "Unable to allocate memory\n");
		return NULL;
	}
	memset(d, 0, sizeof(struct map_dynamic));
	INIT_LIST_HEAD(&d->list);

	d->map4.type = MAP_TYPE_DYNAMIC_HOST;
	d->map4.addr = *addr4;
	d->map4.prefix_len = 32;
	calc_ip4_mask(&d->map4.mask, NULL, 32);
	INIT_LIST_HEAD(&d->map4.list);

	d->map6.type = MAP_TYPE_DYNAMIC_HOST;
	d->map6.addr = *addr6;
	d->map6.prefix_len = 128;
	calc_ip6_mask(&d->map6.mask, NULL, 128);
	INIT_LIST_HEAD(&d->map6.list);

	d->free.addr = a;
	d->free.count = f->count - (a - f->addr);
	f->count = a - f->addr - 1;
	INIT_LIST_HEAD(&d->free.list);
	list_add(&d->free.list, &f->list);

	return d;
}

static void move_to_mapped(struct map_dynamic *d, struct dynamic_pool *pool)
{
	insert_map4(&d->map4, NULL);
	insert_map6(&d->map6, NULL);
	list_add(&d->list, &pool->mapped_list);
}

static void move_to_dormant(struct map_dynamic *d, struct dynamic_pool *pool)
{
	struct list_head *entry;
	struct map_dynamic *s;

	list_del(&d->map4.list);
	list_del(&d->map6.list);

	if (list_empty(&pool->dormant_list)) {
		list_add_tail(&d->list, &pool->dormant_list);
		return;
	}

	s = list_entry(pool->dormant_list.prev, struct map_dynamic, list);
	if (s->last_use >= d->last_use) {
		list_add_tail(&d->list, &pool->dormant_list);
		return;
	}

	list_for_each(entry, &pool->dormant_list) {
		s = list_entry(entry, struct map_dynamic, list);
		if (s->last_use < d->last_use)
			break;
	}
	list_add_tail(&d->list, entry);
}

static void print_dyn_change(char *str, struct map_dynamic *d)
{
	char addrbuf4[INET_ADDRSTRLEN];
	char addrbuf6[INET6_ADDRSTRLEN];

	inet_ntop(AF_INET, &d->map4.addr, addrbuf4, sizeof(addrbuf4));
	inet_ntop(AF_INET6, &d->map6.addr, addrbuf6, sizeof(addrbuf6));
	slog(LOG_DEBUG, "%s pool address %s (%s)\n", str, addrbuf4, addrbuf6);
}

struct map6 *assign_dynamic(const struct in6_addr *addr6)
{
	struct dynamic_pool *pool;
	struct list_head *entry;
	struct free_addr *f;
	uint32_t i, addr, base, max;
	struct in_addr addr4;
	struct map4 *m4;
	struct map_dynamic *d;

	pool = gcfg->dynamic_pool;
	if (!pool)
		return NULL;

	list_for_each(entry, &pool->dormant_list) {
		d = list_entry(entry, struct map_dynamic, list);
		if (IN6_ARE_ADDR_EQUAL(addr6, &d->map6.addr)) {
			print_dyn_change("reactivated dormant", d);
			goto activate;
		}
	}

	base = 0;
	max = (1 << (32 - pool->map4.prefix_len)) - 1;

	for (i = 0; i < 4; ++i) {
		base += addr6->s6_addr32[i];
		while (base & ~max)
			base = (base & max) +
				(base >> (32 - pool->map4.prefix_len));
	}

	for (i = 0, entry = NULL; i <= max; ++i) {
		addr = pool->free_head.addr | ((base + i) & max);
		if (!entry || addr == pool->free_head.addr)
			entry = pool->free_list.next;
		for (;;) {
			f = list_entry(entry, struct free_addr, list);
			if (f->addr + f->count >= addr)
				break;
			entry = entry->next;
		}
		if (f->addr >= addr)
			continue;
		addr4.s_addr = htonl(addr);
		m4 = find_map4(&addr4);
		if (m4 == &pool->map4) {
			d = alloc_map_dynamic(addr6, &addr4, f);
			if (!d)
				return NULL;
			print_dyn_change("assigned new", d);
			gcfg->map_write_pending = 1;
			goto activate;
		}
	}

	if (list_empty(&pool->dormant_list))
		return NULL;

	d = list_entry(pool->dormant_list.prev, struct map_dynamic, list);
	d->map6.addr = *addr6;
	print_dyn_change("reassigned dormant", d);
	gcfg->map_write_pending = 1;

activate:
	move_to_mapped(d, pool);
	return &d->map6;
}

static void load_map(struct dynamic_pool *pool, const struct in6_addr *addr6,
		const struct in_addr *addr4, long int last_use)
{
	struct list_head *entry;
	struct free_addr *f;
	uint32_t addr;
	struct map4 *m4;
	struct map_dynamic *d;
	char addrbuf4[INET_ADDRSTRLEN];
	char addrbuf6[INET6_ADDRSTRLEN];

	if (pool->map4.addr.s_addr != (addr4->s_addr &
					pool->map4.mask.s_addr)) {
		inet_ntop(AF_INET, addr4, addrbuf4, sizeof(addrbuf4));
		slog(LOG_NOTICE, "Ignoring map for %s from %s/%s that lies "
				"outside dynamic pool prefix\n", addrbuf4,
				gcfg->data_dir, MAP_FILE);
		return;
	}
	m4 = find_map4(addr4);
	if (m4 != &pool->map4) {
		inet_ntop(AF_INET, addr4, addrbuf4, sizeof(addrbuf4));
		slog(LOG_NOTICE, "Ignoring map for %s from %s/%s that "
				"conflicts with statically-configured map\n",
				addrbuf4, gcfg->data_dir, MAP_FILE);
		return;
	}
	if (validate_ip6_addr(addr6) < 0) {
		inet_ntop(AF_INET, addr4, addrbuf4, sizeof(addrbuf4));
		inet_ntop(AF_INET6, addr6, addrbuf6, sizeof(addrbuf6));
		slog(LOG_NOTICE, "Ignoring map for %s from %s/%s with "
				"invalid IPv6 address %s\n", addrbuf4,
				gcfg->data_dir, MAP_FILE, addrbuf6);
		return;
	}
	if (find_map6(addr6)) {
		inet_ntop(AF_INET6, addr6, addrbuf6, sizeof(addrbuf6));
		slog(LOG_NOTICE, "Ignoring map for %s from %s/%s that "
				"conflicts with statically-configured map\n",
				addrbuf6, gcfg->data_dir, MAP_FILE);
		return;
	}
	addr = ntohl(addr4->s_addr);
	list_for_each(entry, &pool->free_list) {
		f = list_entry(entry, struct free_addr, list);
		if (f->addr + f->count >= addr)
			break;
	}
	if (entry == &pool->free_list || f->addr >= addr) {
		inet_ntop(AF_INET, addr4, addrbuf4, sizeof(addrbuf4));
		slog(LOG_NOTICE, "Ignoring duplicate map for %s from %s/%s\n",
				addrbuf4, gcfg->data_dir, MAP_FILE);
		return;
	}
	d = alloc_map_dynamic(addr6, addr4, f);
	if (!d)
		return;
	d->last_use = last_use;
	move_to_dormant(d, pool);
}

void load_dynamic(struct dynamic_pool *pool)
{
	FILE *in;
	char line[512];
	char *s4, *s6, *stime, *end, *tokptr;
	struct in_addr addr4;
	struct in6_addr addr6;
	long int last_use;
	struct list_head *entry;
	struct map_dynamic *d;
	int count = 0;

	in = fopen(MAP_FILE, "r");
	if (!in) {
		if (errno != ENOENT)
			slog(LOG_ERR, "Unable to open %s/%s, ignoring: %s\n",
					gcfg->data_dir, MAP_FILE,
					strerror(errno));
		return;
	}
	while (fgets(line, sizeof(line), in)) {
		if (strlen(line) + 1 == sizeof(line)) {
			slog(LOG_ERR, "Ignoring oversized line in %s/%s\n",
					gcfg->data_dir, MAP_FILE);
			continue;
		}
		s4 = strtok_r(line, DELIM, &tokptr);
		if (!s4 || *s4 == '#')
			continue;
		s6 = strtok_r(NULL, DELIM, &tokptr);
		if (!s6)
			goto malformed;
		stime = strtok_r(NULL, DELIM, &tokptr);
		if (!stime)
			goto malformed;
		end = strtok_r(NULL, DELIM, &tokptr);
		if (end)
			goto malformed;
		if (!inet_pton(AF_INET, s4, &addr4) ||
				!inet_pton(AF_INET6, s6, &addr6))
			goto malformed;
		last_use = strtol(stime, &end, 10);
		if (last_use <= 0 || *end)
			goto malformed;
		load_map(pool, &addr6, &addr4, last_use);
		continue;
malformed:
		slog(LOG_ERR, "Ignoring malformed line in %s/%s\n",
				gcfg->data_dir, MAP_FILE);
	}
	fclose(in);

	time(&now);
	last_use = 0;
	list_for_each(entry, &pool->dormant_list) {
		d = list_entry(entry, struct map_dynamic, list);
		if (d->last_use > last_use)
			last_use = d->last_use;
		++count;
	}
	slog(LOG_INFO, "Loaded %d dynamic %s from %s/%s\n", count,
			count == 1 ? "map" : "maps",
			gcfg->data_dir, MAP_FILE);
	if (last_use > now) {
		slog(LOG_DEBUG, "Note: maps in %s/%s are dated in the future\n",
				gcfg->data_dir, MAP_FILE);
		list_for_each(entry, &pool->dormant_list) {
			d = list_entry(entry, struct map_dynamic, list);
			d->last_use = now - gcfg->dyn_min_lease;
		}
	} else {
		while (!list_empty(&pool->dormant_list)) {
			d = list_entry(pool->dormant_list.next,
					struct map_dynamic, list);
			if (d->last_use + gcfg->dyn_min_lease < now)
				break;
			move_to_mapped(d, pool);
		}
	}
}

static void write_to_file(struct dynamic_pool *pool)
{
	FILE *out;
	struct list_head *entry;
	struct map_dynamic *d;
	char addrbuf4[INET_ADDRSTRLEN];
	char addrbuf6[INET6_ADDRSTRLEN];

	out = fopen(TMP_MAP_FILE, "w");
	if (!out) {
		slog(LOG_ERR, "Unable to open %s/%s for writing: %s\n",
				gcfg->data_dir, TMP_MAP_FILE,
				strerror(errno));
		return;
	}
	fprintf(out, "###\n###\n### TAYGA dynamic map database\n###\n"
			"### You can edit this (carefully!) as long as "
			"you shut down TAYGA first\n###\n###\n"
			"### Last written: %s###\n###\n\n",
			asctime(gmtime(&now)));
	entry = pool->mapped_list.next;
	while (entry != &pool->dormant_list) {
		if (entry == &pool->mapped_list) {
			entry = pool->dormant_list.next;
			continue;
		}
		d = list_entry(entry, struct map_dynamic, list);
		inet_ntop(AF_INET, &d->map4.addr, addrbuf4, sizeof(addrbuf4));
		inet_ntop(AF_INET6, &d->map6.addr, addrbuf6, sizeof(addrbuf6));
		fprintf(out, "%s\t%s\t%ld\n", addrbuf4, addrbuf6,
				d->cache_entry ?
					d->cache_entry->last_use : d->last_use);
		entry = entry->next;
	}
	fclose(out);
	if (rename(TMP_MAP_FILE, MAP_FILE) < 0) {
		slog(LOG_ERR, "Unable to rename %s/%s to %s/%s: %s\n",
				gcfg->data_dir, TMP_MAP_FILE,
				gcfg->data_dir, MAP_FILE,
				strerror(errno));
		unlink(TMP_MAP_FILE);
	}
}

void dynamic_maint(struct dynamic_pool *pool, int shutdown)
{
	struct list_head *entry, *next;
	struct map_dynamic *d;
	struct free_addr *f;

	list_for_each_safe(entry, next, &pool->mapped_list) {
		d = list_entry(entry, struct map_dynamic, list);
		if (d->cache_entry)
			continue;
		if (d->last_use + gcfg->dyn_min_lease < now) {
			print_dyn_change("unmapped dormant", d);
			move_to_dormant(d, pool);
		}
	}
	while (!list_empty(&pool->dormant_list)) {
		d = list_entry(pool->dormant_list.prev,
				struct map_dynamic, list);
		if (d->last_use + gcfg->dyn_max_lease >= now)
			break;
		f = list_entry(d->free.list.prev, struct free_addr, list);
		f->count += d->free.count + 1;
		list_del(&d->free.list);
		list_del(&d->list);
		free(d);
	}
	if (gcfg->data_dir[0]) {
		if (shutdown || gcfg->map_write_pending ||
				gcfg->last_map_write +
					gcfg->max_commit_delay < now ||
				gcfg->last_map_write > now) {
			write_to_file(pool);
			gcfg->last_map_write = now;
			gcfg->map_write_pending = 0;
		}
	}
}