#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/ethernet.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <netpacket/packet.h>
#include <arpa/inet.h>
#include <linux/if.h>

#include "events.h"
#include "list.h"
#include "triton.h"
#include "log.h"
#include "mempool.h"
#include "memdebug.h"
#include "ap_session.h"
#include "ipdb.h"
#include "radius.h"
#include "dhcp_attr_defs.h"

#include "dhcpv4.h"

#define BUF_SIZE 4096

#ifndef max
#define max(x,y) ((x) > (y) ? (x) : (y))
#endif

struct dhcpv4_relay_ctx {
	struct list_head entry;
	struct triton_context_t *ctx;
	triton_event_func recv;
};

static int conf_verbose;
static in_addr_t conf_dns1;
static in_addr_t conf_dns2;
static in_addr_t conf_wins1;
static in_addr_t conf_wins2;

static mempool_t pack_pool;
static mempool_t opt_pool;

static LIST_HEAD(relay_list);
static pthread_mutex_t relay_lock = PTHREAD_MUTEX_INITIALIZER;

static int raw_sock = -1;

static int dhcpv4_read(struct triton_md_handler_t *h);
int dhcpv4_packet_add_opt(struct dhcpv4_packet *pack, int type, const void *data, int len);

static void open_raw_sock(void)
{
	raw_sock = socket(AF_PACKET, SOCK_RAW, 0);
	if (raw_sock < 0) {
		log_error("dhcpv4: socket(AF_PACKET, SOCK_RAW): %s\n", strerror(errno));
		return;
	}

	fcntl(raw_sock, F_SETFL, O_NONBLOCK);
	fcntl(raw_sock, F_SETFD, FD_CLOEXEC);
}

static struct dhcpv4_iprange *parse_range(const char *str)
{
	unsigned int f1,f2,f3,f4,m,n, mask, start, end, len;
	struct dhcpv4_iprange *r;

	n = sscanf(str, "%u.%u.%u.%u/%u", &f1, &f2, &f3, &f4, &m);

	if (n != 5)
		goto parse_err;
	if (f1 > 255)
		goto parse_err;
	if (f2 > 255)
		goto parse_err;
	if (f3 > 255)
		goto parse_err;
	if (f4 > 255)
		goto parse_err;
	if (m == 0 || m > 30)
		goto parse_err;

	start = (f1 << 24) | (f2 << 16) | (f3 << 8) | f4;
	mask = ~((1 << (32 - m)) - 1);
	start = start & mask;
	end = start | ~mask;

	len = (end - start - 1) / (8 * sizeof(long)) + 1;

	r = _malloc(sizeof(*r) + len * sizeof(long));
	memset(r, 0, sizeof(*r));
	memset(r->free, 0xff, len * sizeof(long));
	r->routerip = start + 1;
	r->startip = start;
	r->mask = m;
	r->len = len;
	pthread_mutex_init(&r->lock, NULL);

	end -= start;
	r->free[(end - 1) / ( 8 * sizeof(long))] &= (1ul << ((end - 1) % (8 * sizeof(long)) + 1)) - 1;
	r->free[0] &= ~3;

	return r;

parse_err:
	log_emerg("dhcpv4: failed to parse range=%s\n", str);
	return NULL;
}

struct dhcpv4_serv *dhcpv4_create(struct triton_context_t *ctx, const char *ifname, const char *opt)
{
	struct dhcpv4_serv *serv;
	int sock;
	struct sockaddr_in addr;
	struct ifreq ifr;
	int f = 1;
	char *str0, *str, *ptr1, *ptr2;
	int end, ifindex;

	memset(&ifr, 0, sizeof(ifr));

	strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
	if (ioctl(sock_fd, SIOCGIFINDEX, &ifr)) {
		log_error("dhcpv4(%s): ioctl(SIOCGIFINDEX): %s\n", ifname, strerror(errno));
		return NULL;
	}
	ifindex = ifr.ifr_ifindex;

	memset(&addr, 0, sizeof(addr));

	addr.sin_family = AF_INET;
	addr.sin_port = htons(DHCP_SERV_PORT);
	addr.sin_addr.s_addr = htonl(INADDR_ANY);

	sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &f, sizeof(f)))
		log_error("setsockopt(SO_REUSEADDR): %s\n", strerror(errno));


	if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &f, sizeof(f))) {
		log_error("setsockopt(SO_BROADCAST): %s\n", strerror(errno));
		goto out_err;
	}

	if (setsockopt(sock, SOL_SOCKET, SO_NO_CHECK, &f, sizeof(f))) {
		log_error("setsockopt(SO_NO_CHECK): %s\n", strerror(errno));
		goto out_err;
	}

	if (setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &f, sizeof(f))) {
		log_error("setsockopt(IP_PKTINFO): %s\n", strerror(errno));
		goto out_err;
	}

	if (bind(sock, &addr, sizeof(addr))) {
		log_error("bind: %s\n", strerror(errno));
		goto out_err;
	}

	if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname))) {
		log_error("setsockopt(SO_BINDTODEVICE): %s\n", strerror(errno));
		goto out_err;
	}

	if (ioctl(sock, SIOCGIFHWADDR, &ifr)) {
		log_error("dhcpv4(%s): ioctl(SIOCGIFHWADDR): %s\n", ifname, strerror(errno));
		goto out_err;
	}

	fcntl(sock, F_SETFL, O_NONBLOCK);
	fcntl(sock, F_SETFD, fcntl(sock, F_GETFD) | FD_CLOEXEC);

	serv = _malloc(sizeof(*serv));
	memset(serv, 0, sizeof(*serv));

	memcpy(serv->hwaddr, ifr.ifr_hwaddr.sa_data, ETH_ALEN);

	serv->ctx = ctx;
	serv->hnd.fd = sock;
	serv->hnd.read = dhcpv4_read;
	serv->ifindex = ifindex;

	if (opt && *opt) {
		str0 = _strdup(opt);
		str = str0;

		while (1) {
			for (ptr1 = str + 1; *ptr1 && *ptr1 != '='; ptr1++);

			if (!*ptr1)
				break;

			*ptr1 = 0;

			for (ptr2 = ++ptr1; *ptr2 && *ptr2 != ','; ptr2++);

			end = *ptr2 == 0;

			if (!end)
				*ptr2 = 0;

			if (ptr2 == ptr1)
				break;

			if (strcmp(str, "range") == 0)
				serv->range = parse_range(ptr1);

			if (end)
				break;

			str = ptr2 + 1;
		}

		_free(str0);
	}

	triton_md_register_handler(ctx, &serv->hnd);
	triton_md_enable_handler(&serv->hnd, MD_MODE_READ);

	return serv;

out_err:
	close(sock);
	return NULL;
}

void dhcpv4_free(struct dhcpv4_serv *serv)
{
	triton_md_unregister_handler(&serv->hnd, 1);
	if (serv->range)
		_free(serv->range);
	_free(serv);
}

void dhcpv4_print_packet(struct dhcpv4_packet *pack, int relay, void (*print)(const char *fmt, ...))
{
	const char *msg_name[] = {"Discover", "Offer", "Request", "Decline", "Ack", "Nak", "Release", "Inform"};

	print("[DHCPv4 %s%s xid=%x ", relay ? "relay " : "", msg_name[pack->msg_type - 1], pack->hdr->xid);

	if (pack->hdr->ciaddr) {
		in_addr_t addr = ntohl(pack->hdr->ciaddr);
		print("ciaddr=%i.%i.%i.%i ",
			(addr >> 24) & 0xff,
			(addr >> 16) & 0xff,
			(addr >> 8) & 0xff,
			addr & 0xff);
	}

	if (pack->hdr->yiaddr) {
		in_addr_t addr = ntohl(pack->hdr->yiaddr);
		print("yiaddr=%i.%i.%i.%i ",
			(addr >> 24) & 0xff,
			(addr >> 16) & 0xff,
			(addr >> 8) & 0xff,
			addr & 0xff);
	}

	if (pack->hdr->siaddr) {
		in_addr_t addr = ntohl(pack->hdr->siaddr);
		print("siaddr=%i.%i.%i.%i ",
			(addr >> 24) & 0xff,
			(addr >> 16) & 0xff,
			(addr >> 8) & 0xff,
			addr & 0xff);
	}

	if (pack->hdr->giaddr) {
		in_addr_t addr = ntohl(pack->hdr->giaddr);
		print("giaddr=%i.%i.%i.%i ",
			(addr >> 24) & 0xff,
			(addr >> 16) & 0xff,
			(addr >> 8) & 0xff,
			addr & 0xff);
	}

	print("chaddr=%02x:%02x:%02x:%02x:%02x:%02x ",
		pack->hdr->chaddr[0],
		pack->hdr->chaddr[1],
		pack->hdr->chaddr[2],
		pack->hdr->chaddr[3],
		pack->hdr->chaddr[4],
		pack->hdr->chaddr[5],
		pack->hdr->chaddr[6]);

	dhcpv4_print_options(pack, print);

	print("]\n");
}

static int dhcpv4_parse_packet(struct dhcpv4_packet *pack, int len)
{
	struct dhcpv4_option *opt;
	uint8_t *ptr, *endptr = pack->data + len;

	if (len < sizeof(struct dhcpv4_hdr)) {
		if (conf_verbose)
			log_warn("dhcpv4: short packet received\n");
		return -1;
	}

	if (pack->hdr->htype != 1)
		return -1;

	if (pack->hdr->hlen != 6)
		return -1;

	if (memcmp(pack->hdr->magic, DHCP_MAGIC, 4))
		return -1;

	ptr = pack->data + sizeof(struct dhcpv4_hdr);

	while (ptr < endptr) {
		if (*ptr == 0) {
			ptr++;
			continue;
		}

		if (*ptr == 0xff) {
			ptr++;
			break;
		}

		if (ptr + 2 > endptr ||
		    ptr + 2 + ptr[1] > endptr) {
			log_warn("dhcpv4: invalid packet received\n");
			return -1;
		}

		opt = mempool_alloc(opt_pool);
		if (!opt) {
			log_emerg("out of memory\n");
			return -1;
		}
		memset(opt, 0, sizeof(*opt));
		opt->type = *ptr++;
		opt->len = *ptr++;
		opt->data = ptr;
		ptr += opt->len;

		list_add_tail(&opt->entry, &pack->options);

		if (opt->type == 53)
			pack->msg_type = opt->data[0];
		else if (opt->type == 82)
			pack->relay_agent = opt;
		else if (opt->type == 62)
			pack->client_id = opt;
		else if (opt->type == 50)
			pack->request_ip = *(uint32_t *)opt->data;
		else if (opt->type == 54)
			pack->server_id = *(uint32_t *)opt->data;
	}

	if (pack->msg_type == 0 || pack->msg_type > 8)
		return -1;

	if (dhcpv4_check_options(pack))
		return -1;

	pack->ptr = ptr;

	/*if (conf_verbose) {
		log_info2("recv ");
		print_packet(pack, log_info2);
	}*/

	return 0;
}

static struct dhcpv4_packet *dhcpv4_packet_alloc()
{
	struct dhcpv4_packet *pack = mempool_alloc(pack_pool);

	if (!pack)
		return NULL;

	memset(pack, 0, sizeof(*pack));

	INIT_LIST_HEAD(&pack->options);

	pack->hdr = (struct dhcpv4_hdr *)pack->data;
	pack->ptr = (uint8_t *)(pack->hdr + 1);
	pack->refs = 1;

	memcpy(pack->hdr->magic, DHCP_MAGIC, 4);

	return pack;
}

void dhcpv4_packet_ref(struct dhcpv4_packet *pack)
{
	__sync_add_and_fetch(&pack->refs, 1);
}

struct dhcpv4_option *dhcpv4_packet_find_opt(struct dhcpv4_packet *pack, int type)
{
	struct dhcpv4_option *opt;

	list_for_each_entry(opt, &pack->options, entry) {
		if (opt->type == type)
			return opt;
	}

	return NULL;
}

void dhcpv4_packet_free(struct dhcpv4_packet *pack)
{
	struct dhcpv4_option *opt;

	if (__sync_sub_and_fetch(&pack->refs, 1))
		return;

	while (!list_empty(&pack->options)) {
		opt = list_entry(pack->options.next, typeof(*opt), entry);
		list_del(&opt->entry);
		mempool_free(opt);
	}

	mempool_free(pack);
}

int dhcpv4_parse_opt82(struct dhcpv4_option *opt, uint8_t **agent_circuit_id, uint8_t **agent_remote_id)
{
	uint8_t *ptr = opt->data;
	uint8_t *endptr = ptr + opt->len;
	int type, len;

	while (ptr < endptr) {
		if (ptr + 2 > endptr ||
		    ptr + 2 + ptr[1] > endptr) {
			log_warn("dhcpv4: invalid packet received\n");
			return -1;
		}

		type = *ptr++;
		len = *ptr++;

		if (type == 1)
			*agent_circuit_id = ptr - 1;
		else if (type == 2)
			*agent_remote_id = ptr - 1;

		ptr += len;
	}

	return 0;
}

int dhcpv4_packet_insert_opt82(struct dhcpv4_packet *pack, const char *agent_circuit_id, const char *agent_remote_id)
{
	int len1 = strlen(agent_circuit_id);
	int len2 = strlen(agent_remote_id);
	uint8_t *data = _malloc(4 + len1 + len2);
	uint8_t *ptr = data;
	int r;

	pack->ptr--;

	*ptr++ = 1;
	*ptr++ = len1;
	memcpy(ptr, agent_circuit_id, len1); ptr += len1;

	*ptr++ = 2;
	*ptr++ = len2;
	memcpy(ptr, agent_remote_id, len2); ptr += len2;

	r = dhcpv4_packet_add_opt(pack, 82, data, 4 + len1 + len2);
	_free(data);

	*pack->ptr++ = 255;

	return r;
}

static int dhcpv4_read(struct triton_md_handler_t *h)
{
	struct dhcpv4_packet *pack;
	struct dhcpv4_serv *serv = container_of(h, typeof(*serv), hnd);
	struct sockaddr_in addr;
	int n;
	struct msghdr msg;
	struct cmsghdr *cmsg;
	char msg_control[128];
	struct iovec iov;

	memset(&msg, 0, sizeof(msg));
	msg.msg_name = &addr;
	msg.msg_namelen = sizeof(addr);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	msg.msg_control = msg_control;
	msg.msg_controllen = sizeof(msg_control);

	while (1) {
		pack = dhcpv4_packet_alloc();
		if (!pack) {
			log_emerg("out of memory\n");
			return 1;
		}

		iov.iov_base = pack->data;
		iov.iov_len = BUF_SIZE;

		n = recvmsg(h->fd, &msg, 0);
		if (n == -1) {
			mempool_free(pack);
			if (errno == EAGAIN)
				return 0;
			log_error("dhcpv4: recv: %s\n", strerror(errno));
			continue;
		}

		if (dhcpv4_parse_packet(pack, n)) {
			dhcpv4_packet_free(pack);
			continue;
		}

		if (pack->hdr->op != DHCP_OP_REQUEST) {
			dhcpv4_packet_free(pack);
			continue;
		}

		pack->src_addr = addr.sin_addr.s_addr;

		for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
			if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {
				struct in_pktinfo *pkt_info = (struct in_pktinfo *)CMSG_DATA(cmsg);
				pack->dst_addr = pkt_info->ipi_addr.s_addr;
				break;
			}
		}

		if (serv->recv)
			serv->recv(serv, pack);

		dhcpv4_packet_free(pack);
	}
}

static int dhcpv4_relay_read(struct triton_md_handler_t *h)
{
	struct dhcpv4_packet *pack;
	struct dhcpv4_relay *r = container_of(h, typeof(*r), hnd);
	int n;
	struct dhcpv4_relay_ctx *c;

	while (1) {
		pack = dhcpv4_packet_alloc();
		if (!pack) {
			log_emerg("out of memory\n");
			return 1;
		}

		n = read(h->fd, pack->data, BUF_SIZE);
		if (n == -1) {
			mempool_free(pack);
			if (errno == EAGAIN)
				return 0;
			log_error("dhcpv4: recv: %s\n", strerror(errno));
			continue;
		}

		if (dhcpv4_parse_packet(pack, n)) {
			dhcpv4_packet_free(pack);
			continue;
		}

		if (pack->hdr->op != DHCP_OP_REPLY) {
			dhcpv4_packet_free(pack);
			continue;
		}

		pthread_mutex_lock(&relay_lock);
		list_for_each_entry(c, &r->ctx_list, entry) {
			dhcpv4_packet_ref(pack);
			triton_context_call(c->ctx, c->recv, pack);
		}
		pthread_mutex_unlock(&relay_lock);

		dhcpv4_packet_free(pack);
	}
}

static uint16_t ip_csum(uint16_t *buf, int len)
{
	uint32_t sum = 0;

	for (; len > 1; len -= 2)
		sum += *buf++;

	if (len & 1) {
#if __BYTE_ORDER == __LITTLE_ENDIAN
		sum += *(uint8_t *)buf;
#else
		sum += *(uint8_t *)buf << 8;
#endif
 	}

	// take only 16 bits out of the 32 bit sum and add up the carries
	while (sum >> 16)
		sum = (sum & 0xffff) + (sum >> 16);

	// one's complement the result
	sum = ~sum;

	return sum & 0xffff;
}

static int dhcpv4_send_raw(struct dhcpv4_serv *serv, struct dhcpv4_packet *pack, in_addr_t saddr, in_addr_t daddr, int dport)
{
	static const uint8_t bc_addr[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
	struct {
		struct ether_header eth;
		struct iphdr ip;
		struct udphdr udp;
		uint8_t data[0];
	} __packed *hdr;
	struct sockaddr_ll ll_addr;
	int n, len = pack->ptr - pack->data;

	memset(&ll_addr, 0, sizeof(ll_addr));
	ll_addr.sll_family = AF_PACKET;
	ll_addr.sll_ifindex = serv->ifindex;
	ll_addr.sll_protocol = ntohs(ETH_P_IP);

	hdr = alloca(sizeof(*hdr) + max(len, 300));
	memset(hdr, 0, sizeof(*hdr));
	memcpy(hdr->data, pack->data, len);

	// pad packet to minimal bootp length
	if (len < 300) {
		memset(hdr->data + len, 0, 300 - len);
		len = 300;
	}

	memcpy(hdr->eth.ether_dhost, (pack->hdr->flags & DHCP_F_BROADCAST) ? bc_addr : pack->hdr->chaddr, ETH_ALEN);
	memcpy(hdr->eth.ether_shost, serv->hwaddr, ETH_ALEN);
	hdr->eth.ether_type = htons(ETH_P_IP);

	hdr->ip.protocol = IPPROTO_UDP;
	hdr->ip.saddr = saddr;
	hdr->ip.daddr = (pack->hdr->flags & DHCP_F_BROADCAST) ? INADDR_BROADCAST : daddr;
	hdr->udp.source = ntohs(DHCP_SERV_PORT);
	hdr->udp.dest = ntohs(dport);
	hdr->udp.len = hdr->ip.tot_len = htons(sizeof(hdr->udp) + len);
	hdr->udp.check = ip_csum((uint16_t *)&hdr->ip, sizeof(hdr->ip) + sizeof(hdr->udp) + len);

	hdr->ip.ihl = sizeof(hdr->ip) >> 2;
	hdr->ip.version = 4;
	hdr->ip.tos = 0x10;
	hdr->ip.ttl = 128;
	hdr->ip.tot_len = ntohs(sizeof(hdr->ip) + sizeof(hdr->udp) + len);
	hdr->ip.check = ip_csum((uint16_t *)&hdr->ip, sizeof(hdr->ip));

	n = sendto(raw_sock, hdr, sizeof(*hdr) + len, 0, (struct sockaddr *)&ll_addr, sizeof(ll_addr));
	if (n != len)
		return -1;

	return 0;
}

static int dhcpv4_send_udp(struct dhcpv4_serv *serv, struct dhcpv4_packet *pack, in_addr_t ip, int port)
{
	struct sockaddr_in addr;
	int n, len = pack->ptr - pack->data;

	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = ip;

	// pad packet to minimal bootp length
	if (len < 300) {
		memset(pack->data + len, 0, 300 - len);
		len = 300;
	}

	n = sendto(serv->hnd.fd, pack->data, len, 0, (struct sockaddr *)&addr, sizeof(addr));
	if (n != len)
		return -1;

	return 0;
}

int dhcpv4_packet_add_opt(struct dhcpv4_packet *pack, int type, const void *data, int len)
{
	struct dhcpv4_option *opt;

	if (pack->data + BUF_SIZE - pack->ptr < 2 + len)
		return -1;

	opt = mempool_alloc(opt_pool);
	if (!opt) {
		log_emerg("out of memory\n");
		return -1;
	}

	*pack->ptr++ = type;
	*pack->ptr++ = len;

	opt->type = type;
	opt->len = len;
	opt->data = pack->ptr;
	pack->ptr += len;

	memcpy(opt->data, data, len);

	list_add_tail(&opt->entry, &pack->options);

	if (type == 82)
		pack->relay_agent = opt;

	return 0;
}

static inline int dhcpv4_packet_add_opt_u8(struct dhcpv4_packet *pack, int type, uint8_t val)
{
	return dhcpv4_packet_add_opt(pack, type, &val, 1);
}

static inline int dhcpv4_packet_add_opt_u32(struct dhcpv4_packet *pack, int type, uint32_t val)
{
	val = htonl(val);
	return dhcpv4_packet_add_opt(pack, type, &val, 4);
}

int dhcpv4_send_reply(int msg_type, struct dhcpv4_serv *serv, struct dhcpv4_packet *req,
	uint32_t yiaddr, uint32_t siaddr, uint32_t router, uint32_t mask,
	int lease_time, int renew_time, int rebind_time, struct dhcpv4_packet *relay)
{
	struct dhcpv4_packet *pack;
	struct dhcpv4_option *opt;
	in_addr_t addr[2];
	int dns_avail = 0;
	int wins_avail = 0;
	int val, r;

	pack = dhcpv4_packet_alloc();
	if (!pack) {
		log_emerg("out of memory\n");
		return -1;
	}

	memcpy(pack->hdr, req->hdr, sizeof(*req->hdr));

	pack->hdr->op = DHCP_OP_REPLY;
	pack->hdr->yiaddr = yiaddr;
	if (msg_type == DHCPACK)
		pack->hdr->ciaddr = req->hdr->ciaddr;
	else
		pack->hdr->ciaddr = 0;
	pack->hdr->siaddr = 0;
	pack->hdr->giaddr = req->hdr->giaddr;

	if (dhcpv4_packet_add_opt_u8(pack, 53, msg_type))
		goto out_err;

	if (dhcpv4_packet_add_opt(pack, 54, &siaddr, 4))
		goto out_err;

	if (dhcpv4_packet_add_opt_u32(pack, 51, lease_time))
		goto out_err;

	if (renew_time && dhcpv4_packet_add_opt_u32(pack, 58, renew_time))
		goto out_err;

	if (rebind_time && dhcpv4_packet_add_opt_u32(pack, 59, rebind_time))
		goto out_err;

	if (router && dhcpv4_packet_add_opt(pack, 3, &router, 4))
		goto out_err;

	val = htonl(~((1 << (32 - mask)) - 1));
	if (dhcpv4_packet_add_opt(pack, 1, &val, 4))
		goto out_err;

	if (relay) {
		list_for_each_entry(opt, &relay->options, entry) {
			if (opt->type == 53 || opt->type == 54 || opt->type == 51 || opt->type == 58 ||
			    opt->type == 1 || (opt->type == 3 && router) || opt->type == 82)
				continue;
			else if (opt->type == 6)
				dns_avail = 1;
			else if (opt->type == 44)
				wins_avail = 1;
			if (dhcpv4_packet_add_opt(pack, opt->type, opt->data, opt->len))
				goto out_err;
		}
	}

	if (!dns_avail) {
		if (conf_dns1)
			addr[dns_avail++] = conf_dns1;
		if (conf_dns2)
			addr[dns_avail++] = conf_dns2;
		if (dns_avail && dhcpv4_packet_add_opt(pack, 6, addr, dns_avail * sizeof(addr[0])))
			goto out_err;
	}

	if (!wins_avail) {
		if (conf_wins1)
			addr[wins_avail++] = conf_wins1;
		if (conf_wins2)
			addr[wins_avail++] = conf_wins2;
		if (wins_avail && dhcpv4_packet_add_opt(pack, 44, addr, wins_avail * sizeof(addr[0])))
			goto out_err;
	}

	if (req->relay_agent && dhcpv4_packet_add_opt(pack, 82, req->relay_agent->data, req->relay_agent->len))
		goto out_err;

	*pack->ptr++ = 255;

	if (conf_verbose) {
		pack->msg_type = msg_type;
		log_ppp_info2("send ");
		dhcpv4_print_packet(pack, 0, log_ppp_info2);
	}

	if (req->hdr->giaddr)
		r = dhcpv4_send_udp(serv, pack, req->hdr->giaddr, DHCP_SERV_PORT);
	else if (req->hdr->ciaddr && !(pack->hdr->flags & DHCP_F_BROADCAST))
		r = dhcpv4_send_udp(serv, pack, req->hdr->ciaddr, DHCP_CLIENT_PORT);
	else
		r = dhcpv4_send_raw(serv, pack, siaddr, yiaddr, DHCP_CLIENT_PORT);

	dhcpv4_packet_free(pack);

	return r;

out_err:
	dhcpv4_packet_free(pack);
	return -1;
}

int dhcpv4_send_nak(struct dhcpv4_serv *serv, struct dhcpv4_packet *req, const char *err)
{
	struct dhcpv4_packet *pack;
	int val, r;
	uint32_t server_id = req->server_id ?: req->hdr->siaddr ?: req->dst_addr;

	pack = dhcpv4_packet_alloc();
	if (!pack) {
		log_emerg("out of memory\n");
		return -1;
	}

	memcpy(pack->hdr, req->hdr, sizeof(*req->hdr));

	pack->hdr->op = DHCP_OP_REPLY;
	pack->hdr->ciaddr = 0;
	pack->hdr->yiaddr = 0;
	pack->hdr->siaddr = 0;
	pack->hdr->giaddr = req->hdr->giaddr;

	val = DHCPNAK;
	if (dhcpv4_packet_add_opt(pack, 53, &val, 1))
		goto out_err;

	if (server_id && dhcpv4_packet_add_opt(pack, 54, &server_id, 4))
		goto out_err;

	if (req->relay_agent && dhcpv4_packet_add_opt(pack, 82, req->relay_agent->data, req->relay_agent->len))
		goto out_err;

	if (err && dhcpv4_packet_add_opt(pack, 56, err, strlen(err)))
		goto out_err;

	*pack->ptr++ = 255;

	if (conf_verbose) {
		pack->msg_type = DHCPNAK;
		log_info2("send ");
		dhcpv4_print_packet(pack, 0, log_info2);
	}

	if (req->hdr->giaddr)
		r = dhcpv4_send_udp(serv, pack, req->hdr->giaddr, DHCP_SERV_PORT);
	else
		r = dhcpv4_send_raw(serv, pack, 0, 0xffffffff, DHCP_CLIENT_PORT);

	dhcpv4_packet_free(pack);

	return r;

out_err:
	dhcpv4_packet_free(pack);
	return -1;

	return 0;
}

void dhcpv4_send_notify(struct dhcpv4_serv *serv, struct dhcpv4_packet *req, unsigned int weight)
{
	struct dhcpv4_packet *pack = dhcpv4_packet_alloc();
	uint8_t opt[8 + ETH_ALEN];

	if (!pack) {
		log_emerg("out of memory\n");
		return;
	}

	memcpy(pack->hdr, req->hdr, sizeof(*req->hdr));
	pack->hdr->flags = DHCP_F_BROADCAST;
	pack->hdr->ciaddr = 0;
	pack->hdr->yiaddr = 0;
	pack->hdr->siaddr = 0;
	pack->hdr->giaddr = 0;

	*(uint32_t *)opt = htonl(ACCEL_PPP_MAGIC);
	*(uint32_t *)(opt + 4) = htonl(weight);
	memcpy(opt + 8, serv->hwaddr, ETH_ALEN);

	dhcpv4_packet_add_opt_u8(pack, 53, DHCPDISCOVER);
	dhcpv4_packet_add_opt(pack, 43, opt, sizeof(opt));

	dhcpv4_send_raw(serv, pack, 0, INADDR_BROADCAST, DHCP_SERV_PORT);

	dhcpv4_packet_free(pack);
}

struct dhcpv4_relay *dhcpv4_relay_create(const char *_addr, in_addr_t giaddr, struct triton_context_t *ctx, triton_event_func recv)
{
	char str[17], *ptr;
	struct dhcpv4_relay *r;
	in_addr_t addr;// = inet_addr(_addr);
	int port = DHCP_SERV_PORT;
	struct sockaddr_in raddr;
	struct sockaddr_in laddr;
	int sock = -1;
	int f = 1;
	struct dhcpv4_relay_ctx *c;

	ptr = strchr(_addr, ':');
	if (ptr) {
		memcpy(str, _addr, ptr - _addr);
		str[ptr - _addr] = 0;
		addr = inet_addr(str);
		port = atoi(ptr + 1);
	} else
		addr = inet_addr(_addr);

	memset(&raddr, 0, sizeof(raddr));
	raddr.sin_family = AF_INET;
	raddr.sin_addr.s_addr = addr;
	raddr.sin_port = htons(port);

	memset(&laddr, 0, sizeof(laddr));
	laddr.sin_family = AF_INET;
	laddr.sin_addr.s_addr = giaddr;
	laddr.sin_port = htons(DHCP_SERV_PORT);

	pthread_mutex_lock(&relay_lock);
	list_for_each_entry(r, &relay_list, entry) {
		if (r->addr == addr && r->giaddr == giaddr)
			goto found;
	}

	r = _malloc(sizeof(*r));
	memset(r, 0, sizeof(*r));
	INIT_LIST_HEAD(&r->ctx_list);
	r->addr = addr;
	r->giaddr = giaddr;

	sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (!sock) {
		log_error("socket: %s\n", strerror(errno));
		goto out_err_unlock;
	}

	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &f, sizeof(f)))
		log_error("dhcpv4: setsockopt(SO_REUSEADDR): %s\n", strerror(errno));

	if (bind(sock, &laddr, sizeof(laddr))) {
		log_error("dhcpv4: relay: %s: bind: %s\n", _addr, strerror(errno));
		goto out_err_unlock;
	}

	if (connect(sock, &raddr, sizeof(raddr))) {
		log_error("dhcpv4: relay: %s: connect: %s\n", _addr, strerror(errno));
		goto out_err_unlock;
	}

	fcntl(sock, F_SETFL, O_NONBLOCK);
	fcntl(sock, F_SETFD, fcntl(sock, F_GETFD) | FD_CLOEXEC);

	r->hnd.fd = sock;
	r->hnd.read = dhcpv4_relay_read;

	r->ctx.before_switch = log_switch;

	triton_context_register(&r->ctx, NULL);
	triton_md_register_handler(&r->ctx, &r->hnd);
	triton_md_enable_handler(&r->hnd, MD_MODE_READ);
	triton_context_wakeup(&r->ctx);

	list_add_tail(&r->entry, &relay_list);

found:
	c = _malloc(sizeof(*c));
	c->ctx = ctx;
	c->recv = recv;
	list_add_tail(&c->entry, &r->ctx_list);

	pthread_mutex_unlock(&relay_lock);

	return r;

out_err_unlock:
	pthread_mutex_unlock(&relay_lock);

	if (sock != -1)
		close(sock);
	_free(r);
	return NULL;
}

static void __dhcpv4_relay_free(struct dhcpv4_relay *r)
{
	triton_md_unregister_handler(&r->hnd, 1);
	triton_context_unregister(&r->ctx);
	_free(r);
}

void dhcpv4_relay_free(struct dhcpv4_relay *r, struct triton_context_t *ctx)
{
	struct dhcpv4_relay_ctx *c;

	pthread_mutex_lock(&relay_lock);
	list_for_each_entry(c, &r->ctx_list, entry) {
		if (c->ctx == ctx) {
			list_del(&c->entry);
			_free(c);
			break;
		}
	}

	if (list_empty(&r->ctx_list)) {
		list_del(&r->entry);
		triton_context_call(&r->ctx, (triton_event_func)__dhcpv4_relay_free, r);
	}
	pthread_mutex_unlock(&relay_lock);
}

int dhcpv4_relay_send(struct dhcpv4_relay *relay, struct dhcpv4_packet *request, uint32_t server_id, const char *agent_circuit_id, const char *agent_remote_id)
{
	int n;
	int len = request->ptr - request->data;
	uint32_t giaddr = request->hdr->giaddr;
	struct dhcpv4_option *opt = NULL;
	uint32_t _server_id;

	if (!request->relay_agent && agent_remote_id && dhcpv4_packet_insert_opt82(request, agent_circuit_id, agent_remote_id))
		return -1;

	request->hdr->giaddr = relay->giaddr;

	if (server_id) {
		opt = dhcpv4_packet_find_opt(request, 54);
		if (opt) {
			_server_id = *(uint32_t *)opt->data;
			*(uint32_t *)opt->data = server_id;
		}
	}

	len = request->ptr - request->data;

	// pad packet to minimal bootp length
	if (len < 300) {
		memset(request->ptr, 0, 300 - len);
		len = 300;
	}

	if (conf_verbose) {
		log_ppp_info2("send ");
		dhcpv4_print_packet(request, 1, log_ppp_info2);
	}

	n = write(relay->hnd.fd, request->data, len);

	request->hdr->giaddr = giaddr;

	if (opt)
		*(uint32_t *)opt->data = _server_id;

	if (n != len)
		return -1;

	return 0;
}

int dhcpv4_relay_send_release(struct dhcpv4_relay *relay, uint8_t *chaddr, uint32_t xid, uint32_t ciaddr,
	struct dhcpv4_option *client_id, struct dhcpv4_option *relay_agent,
	const char *agent_circuit_id, const char *agent_remote_id)
{
	struct dhcpv4_packet *pack;
	int n, len;

	pack = dhcpv4_packet_alloc();
	if (!pack) {
		log_emerg("out of memory\n");
		return -1;
	}

	memset(pack->hdr, 0, sizeof(*pack->hdr));

	pack->msg_type = DHCPRELEASE;
	pack->hdr->op = DHCP_OP_REQUEST;
	pack->hdr->htype = 1;
	pack->hdr->hlen = 6;
	pack->hdr->ciaddr = ciaddr;
	pack->hdr->giaddr = relay->giaddr;
	pack->hdr->xid = xid;
	memcpy(pack->hdr->magic, DHCP_MAGIC, 4);
	memcpy(pack->hdr->chaddr, chaddr, 6);

	if (dhcpv4_packet_add_opt(pack, 53, &pack->msg_type, 1))
		goto out_err;

	if (client_id && dhcpv4_packet_add_opt(pack, 61, client_id->data, client_id->len))
		goto out_err;

	if (relay_agent && dhcpv4_packet_add_opt(pack, 82, relay_agent->data, relay_agent->len))
		goto out_err;
	else if (!relay_agent && agent_remote_id) {
		pack->ptr++;
		if (dhcpv4_packet_insert_opt82(pack, agent_circuit_id, agent_remote_id))
			goto out_err;
		pack->ptr--;
	}

	*pack->ptr++ = 255;

	len = pack->ptr - pack->data;

	// pad packet to minimal bootp length
	if (len < 300) {
		memset(pack->ptr, 0, 300 - len);
		len = 300;
	}

	if (conf_verbose) {
		log_ppp_info2("send ");
		dhcpv4_print_packet(pack, 1, log_ppp_info2);
	}

	n = write(relay->hnd.fd, pack->data, len);

	dhcpv4_packet_free(pack);

	return n == len ? 0 : -1;

out_err:
	dhcpv4_packet_free(pack);
	return -1;
}

int dhcpv4_get_ip(struct dhcpv4_serv *serv, uint32_t *yiaddr, uint32_t *siaddr, int *mask)
{
	int i, k;

	if (!serv->range)
		return 0;

	pthread_mutex_lock(&serv->range->lock);

	while (1) {
		for (i = serv->range->pos; i < serv->range->len; i++) {
			k = ffsl(serv->range->free[i]);
			if (k) {
				serv->range->free[i] &= ~(1lu << (k - 1));
				serv->range->pos = i;
				pthread_mutex_unlock(&serv->range->lock);
				*yiaddr = htonl(serv->range->startip + i * 8 * sizeof(long) + k - 1);
				*siaddr = htonl(serv->range->routerip);
				*mask = serv->range->mask;
				return 1;
			}
		}

		if (serv->range->pos == 0)
			break;

		serv->range->pos = 0;
	}

	pthread_mutex_unlock(&serv->range->lock);
	return 0;
}

void dhcpv4_put_ip(struct dhcpv4_serv *serv, uint32_t ip)
{
	int n = ntohl(ip) - serv->range->startip;

	if (n <= 0 || n / (8 * sizeof(long)) >= serv->range->len)
		return;

	pthread_mutex_lock(&serv->range->lock);
	serv->range->free[n / (8 * sizeof(long))] |= 1lu << (n % (8 * sizeof(long)));
	pthread_mutex_unlock(&serv->range->lock);
}

void dhcpv4_reserve_ip(struct dhcpv4_serv *serv, uint32_t ip)
{
	int n = ntohl(ip) - serv->range->startip;

	if (n <= 0 || n / (8 * sizeof(long)) >= serv->range->len)
		return;

	pthread_mutex_lock(&serv->range->lock);
	serv->range->free[n / (8 * sizeof(long))] |= 1 << (n % (8 * sizeof(long)));
	pthread_mutex_unlock(&serv->range->lock);
}

struct dhcpv4_packet *dhcpv4_clone_radius(struct rad_packet_t *rad)
{
	struct dhcpv4_packet *pkt = dhcpv4_packet_alloc();
	uint8_t *ptr, *endptr;
	struct dhcpv4_option *opt, *next;
	struct rad_attr_t *attr;
	struct list_head *list;

	if (!pkt)
		return NULL;

	pkt->refs = 1;
	ptr = pkt->data;
	endptr = ptr + BUF_SIZE;

	list_for_each_entry(attr, &rad->attrs, entry) {
		if (attr->vendor && attr->vendor->id == VENDOR_DHCP && attr->attr->id < 256) {
			if (ptr + attr->len >= endptr)
				goto out;

			opt = mempool_alloc(opt_pool);
			if (!opt) {
				log_emerg("out of memory\n");
				goto out;
			}

			memset(opt, 0, sizeof(*opt));
			INIT_LIST_HEAD(&opt->list);
			opt->type = attr->attr->id;
			opt->len = attr->len;
			opt->data = attr->raw;
			ptr += attr->len;

			list = &pkt->options;
			if (attr->attr->array) {
				list_for_each_entry(next, &pkt->options, entry) {
					if (next->type == opt->type) {
						list = &next->list;
						break;
					}
				}
			}

			list_add_tail(&opt->entry, list);
		}
	}

	ptr = pkt->data;

	list_for_each_entry(opt, &pkt->options, entry) {
		memcpy(ptr, opt->data, opt->len);
		opt->data = ptr;
		ptr += opt->len;

		while (!list_empty(&opt->list)) {
			next = list_entry(opt->list.next, typeof(*next), entry);
			memcpy(ptr, next->data, next->len);
			opt->len += next->len;
			ptr += next->len;

			list_del(&next->entry);
			mempool_free(next);
		}
	}

	return pkt;

out:
	list_for_each_entry(opt, &pkt->options, entry) {
		while (!list_empty(&opt->list)) {
			next = list_entry(opt->list.next, typeof(*next), entry);
			list_del(&next->entry);
			mempool_free(next);
		}
	}

	dhcpv4_packet_free(pkt);
	return NULL;
}

static void load_config()
{
	const char *opt;

	opt = conf_get_opt("ipoe", "verbose");
	if (opt)
		conf_verbose = atoi(opt);

	opt = conf_get_opt("dns", "dns1");
	if (opt)
		conf_dns1 = inet_addr(opt);

	opt = conf_get_opt("dns", "dns2");
	if (opt)
		conf_dns2 = inet_addr(opt);

	opt = conf_get_opt("wins", "wins1");
	if (opt)
		conf_wins1 = inet_addr(opt);

	opt = conf_get_opt("wins", "wins2");
	if (opt)
		conf_wins2 = inet_addr(opt);
}

static void init()
{
	pack_pool = mempool_create(BUF_SIZE + sizeof(struct dhcpv4_packet));
	opt_pool = mempool_create(sizeof(struct dhcpv4_option));

	open_raw_sock();

	load_config();

	triton_event_register_handler(EV_CONFIG_RELOAD, (triton_event_func)load_config);
}

DEFINE_INIT(100, init);
