/*
 * Copyright 2010, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Atis Elsts, the.kfx@gmail.com
 */
 
 
#include <net_datalink.h>
#include <net_datalink_protocol.h>
#include <net_device.h>
#include <net_stack.h>
#include <net_protocol.h>
#include <NetBufferUtilities.h>
 
#include <generic_syscall.h>
#include <util/atomic.h>
#include <util/AutoLock.h>
#include <util/DoublyLinkedList.h>
#include <KernelExport.h>
 
#include <netinet/in.h>
#include <netinet6/in6.h>
#include <netinet/icmp6.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_types.h>
#include <net/if_dl.h>
#include <sys/sockio.h>
#include <new>
 
#include <ipv6/jenkins.h>
#include <ipv6/ipv6_address.h>
#include "ndp.h"
 
 
#define TRACE_NDP
#ifdef TRACE_NDP
#	define TRACE(x) dprintf x
#else
#	define TRACE(x) ;
#endif
 
 
struct ipv6_datalink_protocol : net_datalink_protocol {
	sockaddr_dl	hardware_address;
	in6_addr	local_address;
};
 
 
static void ndp_timer(struct net_timer* timer, void* data);
 
 
net_buffer_module_info* gBufferModule;
static net_stack_module_info* sStackModule;
static net_datalink_module_info* sDatalinkModule;
static net_protocol_module_info* sIPv6Module;
static net_protocol* sIPv6Protocol;
static mutex sCacheLock;
static const net_buffer* kDeletedBuffer = (net_buffer*)~0;
 
// needed for IN6_IS_ADDR_UNSPECIFIED() macro
const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;
 
//	#pragma mark -
 
 
struct neighbor_discovery_header {
	uint8		icmp6_type;
	uint8		icmp6_code;
	uint16		icmp6_checksum;
	uint32		flags;
	in6_addr	target_address;
 
	// This part is specific for Ethernet;
	// also, theoretically there could be more than one option.
	uint8		option_type;
	uint8		option_length;
	uint8		link_address[ETHER_ADDRESS_LENGTH];
} _PACKED;
 
struct router_advertisement_header {
	uint8		icmp6_type;
	uint8		icmp6_code;
	uint16		icmp6_checksum;
	uint8		hop_limit;
	uint8		flags;
	uint16		router_lifetime;
	uint32		reachable_time;
	uint32		retransmit_timer;
	uint8		options[0];
} _PACKED;
 
 
struct ndp_entry {
	ndp_entry*	next;
	in6_addr	protocol_address;
	sockaddr_dl	hardware_address;
	uint32		flags;
	net_buffer*	request_buffer;
	net_timer	timer;
	uint32		timer_state;
	bigtime_t	timestamp;
	net_datalink_protocol* protocol;
 
	typedef DoublyLinkedListCLink<net_buffer> NetBufferLink;
	typedef DoublyLinkedList<net_buffer, NetBufferLink> BufferList;
 
	BufferList  queue;
 
	static ndp_entry* Lookup(const in6_addr& protocolAddress);
	static ndp_entry* Add(const in6_addr& protocolAddress,
		sockaddr_dl* hardwareAddress, uint32 flags);
 
	~ndp_entry();
 
	void ClearQueue();
	void MarkFailed();
	void MarkValid();
	void ScheduleRemoval();
};
 
 
struct ndpHash {
	typedef in6_addr KeyType;
	typedef ndp_entry ValueType;
 
	size_t HashKey(in6_addr key) const
	{
		return jenkins_hashword((const uint32*)&(key),
			sizeof(in6_addr) / sizeof(uint32), 0);
	}
 
	size_t Hash(ndp_entry* value) const
	{
		return HashKey(value->protocol_address);
	}
 
	bool Compare(in6_addr key, ndp_entry* value) const
	{
		return value->protocol_address == key;
	}
 
	ndp_entry*& GetLink(ndp_entry* value) const
	{
		return value->next;
	}
};
 
 
typedef BOpenHashTable<ndpHash> AddressCache;
static AddressCache* sCache;
 
 
#define NDP_FLAG_LOCAL		0x01
#define NDP_FLAG_REJECT		0x02
#define NDP_FLAG_PERMANENT	0x04
#define NDP_FLAG_PUBLISH	0x08
#define NDP_FLAG_VALID		0x10
 
#define NDP_FLAG_REMOVED			0x00010000
#define NDP_PUBLIC_FLAG_MASK		0x0000ffff
 
#define NDP_NO_STATE				0
#define NDP_STATE_REQUEST			1
#define NDP_STATE_LAST_REQUEST		5
#define NDP_STATE_REQUEST_FAILED	6
#define NDP_STATE_REMOVE_FAILED		7
#define NDP_STATE_STALE				8
 
#define NDP_STALE_TIMEOUT	30 * 60000000LL		// 30 minutes
#define NDP_REJECT_TIMEOUT	20000000LL			// 20 seconds
#define NDP_REQUEST_TIMEOUT	1000000LL			// 1 second
 
 
//	#pragma mark -
 
 
static void
ipv6_to_ether_multicast(sockaddr_dl* destination, const sockaddr_in6* source)
{
	// To send an IPv6 multicast packet over Ethernet,
	// take the last 32 bits of the destination IPv6 address,
	// prepend 33-33- and use that as the destination Ethernet address.
 
	destination->sdl_len = sizeof(sockaddr_dl);
	destination->sdl_family = AF_LINK;
	destination->sdl_index = 0;
	destination->sdl_type = IFT_ETHER;
	destination->sdl_e_type = htons(ETHER_TYPE_IPV6);
	destination->sdl_nlen = destination->sdl_slen = 0;
	destination->sdl_alen = ETHER_ADDRESS_LENGTH;
 
	destination->sdl_data[0] = 0x33;
	destination->sdl_data[1] = 0x33;
	memcpy(&destination->sdl_data[2], &source->sin6_addr.s6_addr[12], 4);
}
 
 
static inline sockaddr*
ipv6_to_sockaddr(sockaddr_in6* target, const in6_addr& address)
{
	target->sin6_family = AF_INET6;
	target->sin6_len = sizeof(sockaddr_in6);
	target->sin6_port = 0;
	target->sin6_flowinfo = 0;
	target->sin6_scope_id = 0;
	memcpy(target->sin6_addr.s6_addr, address.s6_addr, sizeof(in6_addr));
	return (sockaddr*)target;
}
 
 
static inline sockaddr*
ipv6_to_solicited_multicast(sockaddr_in6* target, const in6_addr& address)
{
	// The solicited-node multicast address for a given unicast address
	// is constructed by taking the last three octets of the unicast address
	// and prepending FF02::1:FF00:0000/104.
 
	target->sin6_family = AF_INET6;
	target->sin6_len = sizeof(sockaddr_in6);
	target->sin6_port = 0;
	target->sin6_flowinfo = 0;
	target->sin6_scope_id = 0;
 
	uint8* targetIPv6 = target->sin6_addr.s6_addr;
	memset(targetIPv6, 0, sizeof(in6_addr));
	targetIPv6[0] = 0xff;
	targetIPv6[1] = 0x02;
	targetIPv6[11] = 0x01;
	targetIPv6[12] = 0xff;
 	memcpy(&targetIPv6[13], &address.s6_addr[13], 3);
 
	return (sockaddr*)target;
}
 
 
//	#pragma mark -
 
 
static net_buffer*
get_request_buffer(ndp_entry* entry)
{
	net_buffer* buffer = entry->request_buffer;
	if (buffer == NULL || buffer == kDeletedBuffer)
		return NULL;
 
	buffer = atomic_pointer_test_and_set(&entry->request_buffer,
		(net_buffer*)NULL, buffer);
	if (buffer == kDeletedBuffer)
		return NULL;
 
	return buffer;
}
 
 
static void
put_request_buffer(ndp_entry* entry, net_buffer* buffer)
{
	net_buffer* requestBuffer = atomic_pointer_test_and_set(
		&entry->request_buffer, buffer, (net_buffer*)NULL);
	if (requestBuffer != NULL) {
		// someone else took over ownership of the request buffer
		gBufferModule->free(buffer);
	}
}
 
 
static void
delete_request_buffer(ndp_entry* entry)
{
	net_buffer* buffer = atomic_pointer_get_and_set(&entry->request_buffer,
		kDeletedBuffer);
	if (buffer != NULL && buffer != kDeletedBuffer)
		gBufferModule->free(buffer);
}
 
 
ndp_entry*
ndp_entry::Lookup(const in6_addr& address)
{
	return sCache->Lookup(address);
}
 
 
ndp_entry*
ndp_entry::Add(const in6_addr& protocolAddress, sockaddr_dl* hardwareAddress,
	uint32 flags)
{
	ASSERT_LOCKED_MUTEX(&sCacheLock);
 
	ndp_entry* entry = new (std::nothrow) ndp_entry;
	if (entry == NULL)
		return NULL;
 
	entry->protocol_address = protocolAddress;
	entry->flags = flags;
	entry->timestamp = system_time();
	entry->protocol = NULL;
	entry->request_buffer = NULL;
	entry->timer_state = NDP_NO_STATE;
	sStackModule->init_timer(&entry->timer, ndp_timer, entry);
 
	if (hardwareAddress != NULL) {
		// this entry is already resolved
		entry->hardware_address = *hardwareAddress;
		entry->hardware_address.sdl_e_type = htons(ETHER_TYPE_IPV6);
	} else {
		// this entry still needs to be resolved
		entry->hardware_address.sdl_alen = 0;
	}
	if (entry->hardware_address.sdl_len != sizeof(sockaddr_dl)) {
		// explicitly set correct length in case our caller hasn't...
		entry->hardware_address.sdl_len = sizeof(sockaddr_dl);
	}
 
	if (sCache->Insert(entry) != B_OK) {
		// We can delete the entry here with the sCacheLock held, since it's
		// guaranteed there are no timers pending.
		delete entry;
		return NULL;
	}
 
	return entry;
}
 
 
ndp_entry::~ndp_entry()
{
	// make sure there is no active timer left for us
	sStackModule->cancel_timer(&timer);
	sStackModule->wait_for_timer(&timer);
 
	ClearQueue();
}
 
 
void
ndp_entry::ClearQueue()
{
	BufferList::Iterator iterator = queue.GetIterator();
	while (iterator.HasNext()) {
		net_buffer* buffer = iterator.Next();
		iterator.Remove();
		gBufferModule->free(buffer);
	}
}
 
 
void
ndp_entry::MarkFailed()
{
	TRACE(("NDP entry %p Marked as FAILED\n", this));
 
	flags = (flags & ~NDP_FLAG_VALID) | NDP_FLAG_REJECT;
	ClearQueue();
}
 
 
void
ndp_entry::MarkValid()
{
	TRACE(("NDP entry %p Marked as VALID\n", this));
 
	flags = (flags & ~NDP_FLAG_REJECT) | NDP_FLAG_VALID;
 
	BufferList::Iterator iterator = queue.GetIterator();
	while (iterator.HasNext()) {
		net_buffer* buffer = iterator.Next();
		iterator.Remove();
 
		TRACE(("  NDP Dequeing packet %p...\n", buffer));
 
		memcpy(buffer->destination, &hardware_address,
			hardware_address.sdl_len);
		protocol->next->module->send_data(protocol->next, buffer);
	}
}
 
 
void
ndp_entry::ScheduleRemoval()
{
	// schedule a timer to remove this entry
	timer_state = NDP_STATE_REMOVE_FAILED;
	sStackModule->set_timer(&timer, 0);
}
 
 
//	#pragma mark -
 
 
static status_t
ndp_init()
{
	sIPv6Protocol = sIPv6Module->init_protocol(NULL);
	if (sIPv6Protocol == NULL)
		return B_NO_MEMORY;
	sIPv6Protocol->module = sIPv6Module;
	sIPv6Protocol->socket = NULL;
	sIPv6Protocol->next = NULL;
 
	int value = 255;
	sIPv6Module->setsockopt(sIPv6Protocol, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
		&value, sizeof(value));
 
	mutex_init(&sCacheLock, "ndp cache");
 
	sCache = new(std::nothrow) AddressCache();
	if (sCache == NULL || sCache->Init(64) != B_OK) {
		mutex_destroy(&sCacheLock);
		return B_NO_MEMORY;
	}
 
	return B_OK;
}
 
 
static status_t
ndp_uninit()
{
	if (sIPv6Protocol)
		sIPv6Module->uninit_protocol(sIPv6Protocol);
 
	return B_OK;
}
 
 
//	#pragma mark -
 
 
/*!	Updates the entry determined by \a protocolAddress with the specified
	\a hardwareAddress.
	If such an entry does not exist yet, a new entry is added. If you try
	to update a local existing entry but didn't ask for it (by setting
	\a flags to NDP_FLAG_LOCAL), an error is returned.
 
	This function does not lock the cache - you have to do it yourself
	before calling it.
*/
status_t
ndp_update_entry(const in6_addr& protocolAddress, sockaddr_dl* hardwareAddress,
	uint32 flags, ndp_entry** _entry = NULL)
{
	ASSERT_LOCKED_MUTEX(&sCacheLock);
 
	ndp_entry* entry = ndp_entry::Lookup(protocolAddress);
	if (entry != NULL) {
		// We disallow updating of entries that had been resolved before,
		// but to a different address (only for those that belong to a
		// specific address - redefining INADDR_ANY is always allowed).
		// Right now, you have to manually purge the NDP entries (or wait some
		// time) to let us switch to the new address.
		if (!IN6_IS_ADDR_UNSPECIFIED(&protocolAddress)
			&& entry->hardware_address.sdl_alen != 0
			&& memcmp(LLADDR(&entry->hardware_address),
				LLADDR(hardwareAddress), ETHER_ADDRESS_LENGTH)) {
			// TODO: also printf the address
			dprintf("NDP host updated with different hardware address "
				"%02x:%02x:%02x:%02x:%02x:%02x.\n",
				hardwareAddress->sdl_data[0], hardwareAddress->sdl_data[1],
				hardwareAddress->sdl_data[2], hardwareAddress->sdl_data[3],
				hardwareAddress->sdl_data[4], hardwareAddress->sdl_data[5]);
			return B_ERROR;
		}
 
		entry->hardware_address = *hardwareAddress;
		entry->timestamp = system_time();
	} else {
		entry = ndp_entry::Add(protocolAddress, hardwareAddress, flags);
		if (entry == NULL)
			return B_NO_MEMORY;
	}
 
	delete_request_buffer(entry);
 
	if ((entry->flags & NDP_FLAG_PERMANENT) == 0) {
		// (re)start the stale timer
		entry->timer_state = NDP_STATE_STALE;
		sStackModule->set_timer(&entry->timer, NDP_STALE_TIMEOUT);
	}
 
	if ((entry->flags & NDP_FLAG_REJECT) != 0)
		entry->MarkFailed();
	else
		entry->MarkValid();
 
	if (_entry)
		*_entry = entry;
 
	return B_OK;
}
 
 
static void
ndp_remove_local_entry(ipv6_datalink_protocol* protocol, const sockaddr* local,
	bool updateLocalAddress)
{
	in6_addr inetAddress;
 
	if (local == NULL) {
		// interface has not yet been set
		memset(&inetAddress, 0, sizeof(in6_addr));
	} else {
		memcpy(&inetAddress, &((sockaddr_in6*)local)->sin6_addr,
			sizeof(in6_addr));
 
		// leave the NS multicast address
		sockaddr_in6 multicast;
		ipv6_to_solicited_multicast(&multicast, inetAddress);
 
		struct ipv6_mreq mreq;
		memcpy(&mreq.ipv6mr_multiaddr, &multicast.sin6_addr, sizeof(in6_addr));
		mreq.ipv6mr_interface = protocol->interface->index;
 
		if (sIPv6Protocol != NULL) {
			sIPv6Module->setsockopt(sIPv6Protocol, IPPROTO_IPV6,
				IPV6_LEAVE_GROUP, &mreq, sizeof(mreq));
		}
	}
 
	// TRACE(("%s(): address %s\n", __FUNCTION__, inet6_to_string(inetAddress)));
 
	MutexLocker locker(sCacheLock);
 
	ndp_entry* entry = ndp_entry::Lookup(inetAddress);
	if (entry != NULL) {
		sCache->Remove(entry);
		entry->flags |= NDP_FLAG_REMOVED;
	}
 
	if (updateLocalAddress && protocol->local_address == inetAddress) {
		// find new local sender address
		memset(&protocol->local_address, 0, sizeof(in6_addr));
 
		net_interface_address* address = NULL;
		while (sDatalinkModule->get_next_interface_address(protocol->interface,
				&address)) {
			if (address->local == NULL || address->local->sa_family != AF_INET6)
				continue;
 
			memcpy(&protocol->local_address,
				&((sockaddr_in6*)address->local)->sin6_addr, sizeof(in6_addr));
		}
	}
 
	locker.Unlock();
	delete entry;
}
 
 
/*!	Removes all entries belonging to the local interface of the \a procotol
	given.
*/
static void
ndp_remove_local(ipv6_datalink_protocol* protocol)
{
	net_interface_address* address = NULL;
	while (sDatalinkModule->get_next_interface_address(protocol->interface,
			&address)) {
		if (address->local == NULL || address->local->sa_family != AF_INET6)
			continue;
 
		ndp_remove_local_entry(protocol, address->local, false);
	}
}
 
 
static status_t
ndp_set_local_entry(ipv6_datalink_protocol* protocol, const sockaddr* local)
{
	MutexLocker locker(sCacheLock);
 
	net_interface* interface = protocol->interface;
	in6_addr inetAddress;
 
	if (local == NULL) {
		// interface has not yet been set
		memset(&inetAddress, 0, sizeof(in6_addr));
	} else {
		memcpy(&inetAddress,
			&((sockaddr_in6*)local)->sin6_addr,
			sizeof(in6_addr));
 
		// join multicast address for listening to NS packets
		sockaddr_in6 multicast;
		ipv6_to_solicited_multicast(&multicast, inetAddress);
 
		struct ipv6_mreq mreq;
		memcpy(&mreq.ipv6mr_multiaddr, &multicast.sin6_addr, sizeof(in6_addr));
		mreq.ipv6mr_interface = protocol->interface->index;
 
		if (sIPv6Protocol != NULL) {
			sIPv6Module->setsockopt(sIPv6Protocol, IPPROTO_IPV6,
				IPV6_JOIN_GROUP, &mreq, sizeof(mreq));
		}
	}
 
	// TRACE(("%s(): address %s\n", __FUNCTION__, inet6_to_string(inetAddress)));
 
	if (IN6_IS_ADDR_UNSPECIFIED(&protocol->local_address))
		memcpy(&protocol->local_address, &inetAddress, sizeof(in6_addr));
 
	sockaddr_dl address;
	address.sdl_len = sizeof(sockaddr_dl);
	address.sdl_family = AF_LINK;
	address.sdl_type = IFT_ETHER;
	address.sdl_e_type = htons(ETHER_TYPE_IPV6);
	address.sdl_nlen = 0;
	address.sdl_slen = 0;
	address.sdl_alen = interface->device->address.length;
	memcpy(LLADDR(&address), interface->device->address.data, address.sdl_alen);
 
	memcpy(&protocol->hardware_address, &address, sizeof(sockaddr_dl));
		// cache the address in our protocol
 
	ndp_entry* entry;
	status_t status = ndp_update_entry(inetAddress, &address,
		NDP_FLAG_LOCAL | NDP_FLAG_PERMANENT, &entry);
	if (status == B_OK)
		entry->protocol = protocol;
 
	return status;
}
 
 
/*!	Creates permanent local entries for all addresses of the interface belonging
	to this protocol.
	Returns an error if no entry could be added.
*/
static status_t
ndp_update_local(ipv6_datalink_protocol* protocol)
{
	memset(&protocol->local_address, 0, sizeof(in6_addr));
 
	ssize_t count = 0;
 
	net_interface_address* address = NULL;
	while (sDatalinkModule->get_next_interface_address(protocol->interface,
			&address)) {
		if (address->local == NULL || address->local->sa_family != AF_INET6)
			continue;
 
		if (ndp_set_local_entry(protocol, address->local) == B_OK) {
			count++;
		}
	}
 
	if (count == 0)
		return ndp_set_local_entry(protocol, NULL);
 
	return B_OK;
}
 
 
static status_t
ndp_receive_solicitation(net_buffer* buffer, bool* reuseBuffer)
{
	*reuseBuffer = false;
 
	NetBufferHeaderReader<neighbor_discovery_header> bufferHeader(buffer);
	if (bufferHeader.Status() < B_OK)
		return bufferHeader.Status();
 
	neighbor_discovery_header& header = bufferHeader.Data();
	if (header.option_type != ND_OPT_SOURCE_LINKADDR
		|| header.option_length != 1)
		return B_OK;
 
	{
		MutexLocker locker(sCacheLock);
 
		// remember the address of the sender as we might need it later
		sockaddr_dl hardwareAddress;
		hardwareAddress.sdl_len = sizeof(sockaddr_dl);
		hardwareAddress.sdl_family = AF_LINK;
		hardwareAddress.sdl_index = 0;
		hardwareAddress.sdl_type = IFT_ETHER;
		hardwareAddress.sdl_e_type = htons(ETHER_TYPE_IPV6);
		hardwareAddress.sdl_nlen = hardwareAddress.sdl_slen = 0;
		hardwareAddress.sdl_alen = ETHER_ADDRESS_LENGTH;
		memcpy(LLADDR(&hardwareAddress), header.link_address,
			ETHER_ADDRESS_LENGTH);
 
		ndp_update_entry(header.target_address, &hardwareAddress, 0);
 
		// check if this request is for us
 
		ndp_entry* entry = ndp_entry::Lookup(header.target_address);
		if (entry == NULL
			|| (entry->flags & (NDP_FLAG_LOCAL | NDP_FLAG_PUBLISH)) == 0) {
			// We're not the one to answer this request
			// TODO: instead of letting the other's request time-out, can we
			//	reply failure somehow?
			TRACE(("  not for us\n"));
			return B_ERROR;
		}
 
		// send a reply (by reusing the buffer we got)
		gBufferModule->trim(buffer, sizeof(neighbor_discovery_header));
 
		header.icmp6_type = ND_NEIGHBOR_SOLICIT;
		header.icmp6_code = 0;
		header.icmp6_checksum = 0;
		header.flags = ND_NA_FLAG_SOLICITED;
		header.option_type = ND_OPT_TARGET_LINKADDR;
		memcpy(&header.link_address, LLADDR(&entry->hardware_address),
			ETHER_ADDRESS_LENGTH);
		bufferHeader.Sync();
	}
 
	// fix source and destination address
	sockaddr_in6* source = (sockaddr_in6*)buffer->source;
	sockaddr_in6* destination = (sockaddr_in6*)buffer->destination;
	memcpy(&destination->sin6_addr, &source->sin6_addr, sizeof(in6_addr));
	memcpy(&source->sin6_addr, &header.target_address, sizeof(in6_addr));
 
	buffer->flags = 0;
		// make sure this won't be a broadcast message
 
	if (sIPv6Protocol == NULL)
		return B_ERROR;
 
	*reuseBuffer = true;
 
	// send the ICMPv6 packet out
	TRACE(("Sending Neighbor Advertisement\n"));
	return sIPv6Module->send_data(sIPv6Protocol, buffer);
}
 
 
static void
ndp_receive_advertisement(net_buffer* buffer)
{
	// TODO: also process unsolicited advertisments?
	if ((buffer->flags & MSG_MCAST) != 0)
		return;
 
	NetBufferHeaderReader<neighbor_discovery_header> bufferHeader(buffer);
	if (bufferHeader.Status() < B_OK)
		return;
 
	neighbor_discovery_header& header = bufferHeader.Data();
	if (header.option_type != ND_OPT_TARGET_LINKADDR
		|| header.option_length != 1) {
		return;
	}
 
	sockaddr_dl hardwareAddress;
	hardwareAddress.sdl_len = sizeof(sockaddr_dl);
	hardwareAddress.sdl_family = AF_LINK;
	hardwareAddress.sdl_index = 0;
	hardwareAddress.sdl_type = IFT_ETHER;
	hardwareAddress.sdl_e_type = htons(ETHER_TYPE_IPV6);
	hardwareAddress.sdl_nlen = hardwareAddress.sdl_slen = 0;
	hardwareAddress.sdl_alen = ETHER_ADDRESS_LENGTH;
	memcpy(LLADDR(&hardwareAddress), header.link_address, ETHER_ADDRESS_LENGTH);
 
	MutexLocker locker(sCacheLock);
	// TODO: take in account ND_NA_FLAGs
	ndp_update_entry(header.target_address, &hardwareAddress, 0);
}
 
 
static void
ndp_receive_router_advertisement(net_buffer* buffer)
{
	NetBufferHeaderReader<router_advertisement_header> bufferHeader(buffer);
	if (bufferHeader.Status() < B_OK)
		return;
 
	// TODO: check for validity
 
	// TODO: parse the options
}
 
 
static status_t
ndp_receive_data(net_buffer* buffer)
{
	dprintf("ndp_receive_data\n");
 
	NetBufferHeaderReader<icmp6_hdr> icmp6Header(buffer);
	if (icmp6Header.Status() < B_OK)
		return icmp6Header.Status();
 
	bool reuseBuffer = false;
 
	switch (icmp6Header->icmp6_type) {
		case ND_NEIGHBOR_SOLICIT:
			TRACE(("  received Neighbor Solicitation\n"));
			ndp_receive_solicitation(buffer, &reuseBuffer);
			break;
 
		case ND_NEIGHBOR_ADVERT:
			TRACE(("  received Neighbor Advertisement\n"));
			ndp_receive_advertisement(buffer);
			break;
 
		case ND_ROUTER_ADVERT:
			TRACE(("  received Router Advertisement\n"));
			ndp_receive_router_advertisement(buffer);
			break;
	}
 
	if (reuseBuffer == false)
		gBufferModule->free(buffer);
	return B_OK;
}
 
 
static void
ndp_timer(struct net_timer* timer, void* data)
{
	ndp_entry* entry = (ndp_entry*)data;
	TRACE(("NDP timer %" B_PRId32 ", entry %p!\n", entry->timer_state, entry));
 
	switch (entry->timer_state) {
		case NDP_NO_STATE:
			// who are you kidding?
			break;
 
		case NDP_STATE_REQUEST_FAILED:
			// Requesting the NDP entry failed, we keep it around for a while,
			// though, so that we won't try to request the same address again
			// too soon.
			TRACE(("  requesting NDP entry %p failed!\n", entry));
			entry->timer_state = NDP_STATE_REMOVE_FAILED;
			entry->MarkFailed();
			sStackModule->set_timer(&entry->timer, NDP_REJECT_TIMEOUT);
			break;
 
		case NDP_STATE_REMOVE_FAILED:
		case NDP_STATE_STALE:
			// the entry has aged so much that we're going to remove it
			TRACE(("  remove NDP entry %p!\n", entry));
 
			mutex_lock(&sCacheLock);
			if ((entry->flags & NDP_FLAG_REMOVED) != 0) {
				// The entry has already been removed, and is about to be deleted
				mutex_unlock(&sCacheLock);
				break;
			}
 
			sCache->Remove(entry);
			mutex_unlock(&sCacheLock);
 
			delete entry;
			break;
 
		default:
		{
			if (entry->timer_state > NDP_STATE_LAST_REQUEST)
				break;
 
			TRACE(("  send request for NDP entry %p!\n", entry));
 
			net_buffer* request = get_request_buffer(entry);
			if (request == NULL)
				break;
 
			if (entry->timer_state < NDP_STATE_LAST_REQUEST) {
				// we'll still need our buffer, so in order to prevent it being
				// freed by a successful send, we need to clone it
				net_buffer* clone = gBufferModule->clone(request, true);
				if (clone == NULL) {
					// cloning failed - that means we won't be able to send as
					// many requests as originally planned
					entry->timer_state = NDP_STATE_LAST_REQUEST;
				} else {
					put_request_buffer(entry, request);
					request = clone;
				}
			}
 
			if (sIPv6Protocol == NULL)
				break;
 
			// we're trying to resolve the address, so keep sending requests
			status_t status = sIPv6Module->send_data(sIPv6Protocol, request);
			if (status < B_OK)
				gBufferModule->free(request);
 
			entry->timer_state++;
			sStackModule->set_timer(&entry->timer, NDP_REQUEST_TIMEOUT);
			break;
		}
	}
}
 
 
static status_t
ndp_start_resolve(ipv6_datalink_protocol* protocol, const in6_addr& address,
	ndp_entry** _entry)
{
	ASSERT_LOCKED_MUTEX(&sCacheLock);
 
	// create an unresolved entry as a placeholder
	ndp_entry* entry = ndp_entry::Add(address, NULL, 0);
	if (entry == NULL)
		return B_NO_MEMORY;
 
	// prepare NDP request
 
	net_buffer* buffer = entry->request_buffer = gBufferModule->create(256);
	if (entry->request_buffer == NULL) {
		entry->ScheduleRemoval();
		return B_NO_MEMORY;
	}
 
	NetBufferPrepend<neighbor_discovery_header> header(buffer);
	status_t status = header.Status();
	if (status < B_OK) {
		entry->ScheduleRemoval();
		return status;
	}
 
	net_interface* interface = protocol->interface;
	net_device* device = interface->device;
 
	// prepare source and target addresses
 
	sockaddr_in6* source = (sockaddr_in6*)buffer->source;
	ipv6_to_sockaddr(source, protocol->local_address);
	// protocol->local_address
 
	sockaddr_in6* destination = (sockaddr_in6*)buffer->destination;
	ipv6_to_solicited_multicast(destination, address);
 
	buffer->protocol = IPPROTO_ICMPV6;
 
	// prepare Neighbor Solicitation header
 
	header->icmp6_type = ND_NEIGHBOR_SOLICIT;
	header->icmp6_code = 0;
	header->icmp6_checksum = 0;
	header->flags = 0;
	memcpy(&header->target_address, &address, sizeof(in6_addr));
	header->option_type = ND_OPT_SOURCE_LINKADDR;
	header->option_length = (sizeof(nd_opt_hdr) + ETHER_ADDRESS_LENGTH) >> 3;
	memcpy(&header->link_address, device->address.data, ETHER_ADDRESS_LENGTH);
	header.Sync();
 
	if (sIPv6Protocol == NULL) {
		entry->ScheduleRemoval();
		return B_NO_MEMORY;
	}
 
	// this does not work, because multicast for now is only looped back!
#if FIXME
	// hack: set to use the correct interface by setting socket option
	sIPv6Module->setsockopt(sIPv6Protocol, IPPROTO_IPV6, IPV6_MULTICAST_IF,
		&source->sin6_addr, sizeof(in6_addr));
#endif
 
	net_buffer* clone = gBufferModule->clone(buffer, true);
	if (clone == NULL) {
		entry->ScheduleRemoval();
		return B_NO_MEMORY;
	}
 
	// send the ICMPv6 packet out
	TRACE(("Sending Neighbor Solicitation\n"));
	status = sIPv6Module->send_data(sIPv6Protocol, clone);
	if (status < B_OK) {
		entry->ScheduleRemoval();
		return status;
	}
 
	entry->protocol = protocol;
	entry->timer_state = NDP_STATE_REQUEST;
	sStackModule->set_timer(&entry->timer, 0);
		// start request timer
 
	*_entry = entry;
	return B_OK;
}
 
 
//	#pragma mark -
 
 
static status_t
ipv6_datalink_init(net_interface* interface, net_domain* domain,
	net_datalink_protocol** _protocol)
{
	if (domain->family != AF_INET6)
		return B_BAD_TYPE;
 
	status_t status = sStackModule->register_domain_device_handler(
		interface->device, B_NET_FRAME_TYPE(IFT_ETHER, ETHER_TYPE_IPV6), domain);
	if (status != B_OK)
		return status;
 
	ipv6_datalink_protocol* protocol = new(std::nothrow) ipv6_datalink_protocol;
	if (protocol == NULL)
		return B_NO_MEMORY;
 
	memset(&protocol->hardware_address, 0, sizeof(sockaddr_dl));
	memset(&protocol->local_address, 0, sizeof(in6_addr));
	*_protocol = protocol;
	return B_OK;
}
 
 
static status_t
ipv6_datalink_uninit(net_datalink_protocol* protocol)
{
	sStackModule->unregister_device_handler(protocol->interface->device,
		B_NET_FRAME_TYPE(IFT_ETHER, ETHER_TYPE_IPV6));
 
	delete protocol;
 	return B_OK;
}
 
 
static status_t
ipv6_datalink_send_data(net_datalink_protocol* _protocol, net_buffer* buffer)
{
	ipv6_datalink_protocol* protocol = (ipv6_datalink_protocol*)_protocol;
 
	memcpy(buffer->source, &protocol->hardware_address,
		protocol->hardware_address.sdl_len);
 
	if ((buffer->flags & MSG_MCAST) != 0) {
		sockaddr_dl multicastDestination;
		ipv6_to_ether_multicast(&multicastDestination,
			(sockaddr_in6*)buffer->destination);
		memcpy(buffer->destination, &multicastDestination,
			sizeof(sockaddr_dl));
	} else {
		MutexLocker locker(sCacheLock);
 
		// Lookup destination (we may need to wait for this)
		ndp_entry* entry = ndp_entry::Lookup(
			((struct sockaddr_in6*)buffer->destination)->sin6_addr);
		if (entry == NULL) {
			status_t status = ndp_start_resolve(protocol,
				((struct sockaddr_in6*)buffer->destination)->sin6_addr, &entry);
			if (status < B_OK)
				return status;
		}
 
		if ((entry->flags & NDP_FLAG_REJECT) != 0)
			return EHOSTUNREACH;
		if (!(entry->flags & NDP_FLAG_VALID)) {
			// entry is still being resolved.
			TRACE(("NDP Queuing packet %p, entry still being resolved.\n",
					buffer));
			entry->queue.Add(buffer);
			return B_OK;
		}
 
		memcpy(buffer->destination, &entry->hardware_address,
			entry->hardware_address.sdl_len);
	}
 
	return protocol->next->module->send_data(protocol->next, buffer);
}
 
 
 
static status_t
ipv6_datalink_up(net_datalink_protocol* _protocol)
{
	ipv6_datalink_protocol* protocol = (ipv6_datalink_protocol*)_protocol;
	status_t status = protocol->next->module->interface_up(protocol->next);
	if (status != B_OK)
		return status;
 
	// cache this device's address for later use
 
	status = ndp_update_local(protocol);
	if (status != B_OK) {
		protocol->next->module->interface_down(protocol->next);
		return status;
	}
 
	return B_OK;
}
 
 
static void
ipv6_datalink_down(net_datalink_protocol *protocol)
{
	// remove local NDP entries from the cache
	ndp_remove_local((ipv6_datalink_protocol*)protocol);
 
	protocol->next->module->interface_down(protocol->next);
}
 
 
status_t
ipv6_datalink_change_address(net_datalink_protocol* _protocol,
	net_interface_address* address, int32 option,
	const struct sockaddr* oldAddress, const struct sockaddr* newAddress)
{
	ipv6_datalink_protocol* protocol = (ipv6_datalink_protocol*)_protocol;
	switch (option) {
		case SIOCSIFADDR:
		case SIOCAIFADDR:
		case SIOCDIFADDR:
			// Those are the options we handle
			if ((protocol->interface->flags & IFF_UP) != 0) {
				// Update NDP entry for the local address
 
				if (newAddress != NULL && newAddress->sa_family == AF_INET6) {
					status_t status = ndp_set_local_entry(protocol, newAddress);
					if (status != B_OK)
						return status;
 
					// add IPv6 multicast route (ff00::/8)
					sockaddr_in6 socketAddress;
					memset(&socketAddress, 0, sizeof(sockaddr_in6));
					socketAddress.sin6_family = AF_INET6;
					socketAddress.sin6_len = sizeof(sockaddr_in6);
					socketAddress.sin6_addr.s6_addr[0] = 0xff;
 
					net_route route;
					memset(&route, 0, sizeof(net_route));
					route.destination = (sockaddr*)&socketAddress;
					route.mask = (sockaddr*)&socketAddress;
					route.flags = 0;
					sDatalinkModule->add_route(address->domain, &route);
				}
 
				if (oldAddress != NULL && oldAddress->sa_family == AF_INET6) {
					ndp_remove_local_entry(protocol, oldAddress, true);
 
					// remove IPv6 multicast route (ff00::/8)
					sockaddr_in6 socketAddress;
					memset(&socketAddress, 0, sizeof(sockaddr_in6));
					socketAddress.sin6_family = AF_INET6;
					socketAddress.sin6_len = sizeof(sockaddr_in6);
					socketAddress.sin6_addr.s6_addr[0] = 0xff;
 
					net_route route;
					memset(&route, 0, sizeof(net_route));
					route.destination = (sockaddr*)&socketAddress;
					route.mask = (sockaddr*)&socketAddress;
					route.flags = 0;
					sDatalinkModule->remove_route(address->domain, &route);
				}
			}
			break;
 
		default:
			break;
	}
 
	return protocol->next->module->change_address(protocol->next, address,
		option, oldAddress, newAddress);
}
 
 
static status_t
ipv6_datalink_control(net_datalink_protocol* protocol, int32 op, void* argument,
	size_t length)
{
	return protocol->next->module->control(protocol->next, op, argument,
		length);
}
 
 
static status_t
ipv6_datalink_join_multicast(net_datalink_protocol* protocol,
	const sockaddr* address)
{
	if (address->sa_family != AF_INET6)
		return EINVAL;
 
	sockaddr_dl multicastAddress;
	ipv6_to_ether_multicast(&multicastAddress, (const sockaddr_in6*)address);
 
	return protocol->next->module->join_multicast(protocol->next,
		(sockaddr*)&multicastAddress);
}
 
 
static status_t
ipv6_datalink_leave_multicast(net_datalink_protocol* protocol,
	const sockaddr* address)
{
	if (address->sa_family != AF_INET6)
		return EINVAL;
 
	sockaddr_dl multicastAddress;
	ipv6_to_ether_multicast(&multicastAddress, (const sockaddr_in6*)address);
 
	return protocol->next->module->leave_multicast(protocol->next,
		(sockaddr*)&multicastAddress);
}
 
 
static status_t
ipv6_datalink_std_ops(int32 op, ...)
{
	switch (op) {
	case B_MODULE_INIT:
		return ndp_init();
 
	case B_MODULE_UNINIT:
		return ndp_uninit();
	}
 
	return B_ERROR;
}
 
 
net_datalink_protocol_module_info gIPv6DataLinkModule = {
	{
		"network/datalink_protocols/ipv6_datagram/v1",
		0,
		ipv6_datalink_std_ops
	},
	ipv6_datalink_init,
	ipv6_datalink_uninit,
	ipv6_datalink_send_data,
	ipv6_datalink_up,
	ipv6_datalink_down,
	ipv6_datalink_change_address,
	ipv6_datalink_control,
	ipv6_datalink_join_multicast,
	ipv6_datalink_leave_multicast,
};
 
net_ndp_module_info gIPv6NDPModule = {
	{
		"network/datalink_protocols/ipv6_datagram/ndp/v1",
		0,
		NULL
	},
	ndp_receive_data
};
 
module_dependency module_dependencies[] = {
	{NET_STACK_MODULE_NAME, (module_info**)&sStackModule},
	{NET_DATALINK_MODULE_NAME, (module_info**)&sDatalinkModule},
	{NET_BUFFER_MODULE_NAME, (module_info**)&gBufferModule},
	{"network/protocols/ipv6/v1", (module_info**)&sIPv6Module},
	{}
};
 
module_info* modules[] = {
	(module_info*)&gIPv6DataLinkModule,
	(module_info*)&gIPv6NDPModule,
	NULL
};

V512 A call of the 'memcpy' function will lead to overflow of the buffer 'buffer->destination'.

V512 A call of the 'memcpy' function will lead to underflow of the buffer 'device->address.data'.