/*
 * Copyright 2006-2010, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Axel Dörfler, axeld@pinc-software.de
 *		Hugo Santos, hugosantos@gmail.com
 */
 
 
//! Ethernet Address Resolution Protocol, see RFC 826.
 
 
#include <arp_control.h>
#include <net_datalink_protocol.h>
#include <net_device.h>
#include <net_datalink.h>
#include <net_stack.h>
#include <NetBufferUtilities.h>
 
#include <generic_syscall.h>
#include <util/atomic.h>
#include <util/AutoLock.h>
#include <util/DoublyLinkedList.h>
 
#include <ByteOrder.h>
#include <KernelExport.h>
 
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <new>
#include <stdio.h>
#include <string.h>
#include <sys/sockio.h>
 
 
//#define TRACE_ARP
#ifdef TRACE_ARP
#	define TRACE(x) dprintf x
#else
#	define TRACE(x) ;
#endif
 
 
struct arp_header {
	uint16		hardware_type;
	uint16		protocol_type;
	uint8		hardware_length;
	uint8		protocol_length;
	uint16		opcode;
 
	// TODO: this should be a variable length header, but for our current
	//	usage (Ethernet/IPv4), this should work fine.
	uint8		hardware_sender[6];
	in_addr_t	protocol_sender;
	uint8		hardware_target[6];
	in_addr_t	protocol_target;
} _PACKED;
 
#define ARP_OPCODE_REQUEST	1
#define ARP_OPCODE_REPLY	2
 
#define ARP_HARDWARE_TYPE_ETHER	1
 
struct arp_entry {
	arp_entry	*next;
	in_addr_t	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 arp_entry *Lookup(in_addr_t protocolAddress);
	static arp_entry *Add(in_addr_t protocolAddress,
		sockaddr_dl *hardwareAddress, uint32 flags);
 
	~arp_entry();
 
	void ClearQueue();
	void MarkFailed();
	void MarkValid();
	void ScheduleRemoval();
};
 
// see arp_control.h for more flags
#define ARP_FLAG_REMOVED			0x00010000
#define ARP_PUBLIC_FLAG_MASK		0x0000ffff
 
#define ARP_NO_STATE				0
#define ARP_STATE_REQUEST			1
#define ARP_STATE_LAST_REQUEST		5
#define ARP_STATE_REQUEST_FAILED	6
#define ARP_STATE_REMOVE_FAILED		7
#define ARP_STATE_STALE				8
 
#define ARP_STALE_TIMEOUT	30 * 60000000LL		// 30 minutes
#define ARP_REJECT_TIMEOUT	20000000LL			// 20 seconds
#define ARP_REQUEST_TIMEOUT	1000000LL			// 1 second
 
struct arp_protocol : net_datalink_protocol {
	sockaddr_dl	hardware_address;
	in_addr_t	local_address;
};
 
 
static const net_buffer* kDeletedBuffer = (net_buffer*)~0;
 
static void arp_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 mutex sCacheLock;
static bool sIgnoreReplies;
 
 
struct arpHash {
	typedef in_addr_t KeyType;
	typedef arp_entry ValueType;
 
	size_t HashKey(KeyType key) const
	{
		return key;
	}
 
	size_t Hash(ValueType* value) const
	{
		return HashKey(value->protocol_address);
	}
 
	bool Compare(KeyType key, ValueType* value) const
	{
		return value->protocol_address == key;
	}
 
	ValueType*& GetLink(ValueType* value) const
	{
		return value->next;
	}
};
 
 
typedef BOpenHashTable<arpHash> AddressCache;
static AddressCache* sCache;
 
 
#ifdef TRACE_ARP
 
 
const char*
mac_to_string(uint8* address)
{
	static char buffer[20];
	snprintf(buffer, sizeof(buffer), "%02x:%02x:%02x:%02x:%02x:%02x",
		address[0], address[1], address[2], address[3], address[4], address[5]);
	return buffer;
}
 
 
const char*
inet_to_string(in_addr_t address)
{
	static char buffer[20];
 
	unsigned int hostAddress = ntohl(address);
	snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d",
		hostAddress >> 24, (hostAddress >> 16) & 0xff,
		(hostAddress >> 8) & 0xff, hostAddress & 0xff);
	return buffer;
}
 
 
#endif	// TRACE_ARP
 
 
static net_buffer*
get_request_buffer(arp_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(arp_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(arp_entry* entry)
{
	net_buffer* buffer = atomic_pointer_get_and_set(&entry->request_buffer,
		kDeletedBuffer);
	if (buffer != NULL && buffer != kDeletedBuffer)
		gBufferModule->free(buffer);
}
 
 
static void
ipv4_to_ether_multicast(sockaddr_dl *destination, const sockaddr_in *source)
{
	// RFC 1112 - Host extensions for IP multicasting
	//
	//   ``An IP host group address is mapped to an Ethernet multicast
	//   address by placing the low-order 23-bits of the IP address into
	//   the low-order 23 bits of the Ethernet multicast address
	//   01-00-5E-00-00-00 (hex).''
 
	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_IP);
	destination->sdl_nlen = destination->sdl_slen = 0;
	destination->sdl_alen = ETHER_ADDRESS_LENGTH;
 
	memcpy(LLADDR(destination) + 2, &source->sin_addr, sizeof(in_addr));
	uint32 *data = (uint32 *)LLADDR(destination);
	data[0] = (data[0] & htonl(0x7f)) | htonl(0x01005e00);
}
 
 
// #pragma mark -
 
 
/*static*/ arp_entry *
arp_entry::Lookup(in_addr_t address)
{
	return sCache->Lookup(address);
}
 
 
/*static*/ arp_entry *
arp_entry::Add(in_addr_t protocolAddress, sockaddr_dl *hardwareAddress,
	uint32 flags)
{
	ASSERT_LOCKED_MUTEX(&sCacheLock);
 
	arp_entry *entry = new (std::nothrow) arp_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 = ARP_NO_STATE;
	sStackModule->init_timer(&entry->timer, arp_timer, entry);
 
	if (hardwareAddress != NULL) {
		// this entry is already resolved
		entry->hardware_address = *hardwareAddress;
		entry->hardware_address.sdl_e_type = htons(ETHER_TYPE_IP);
	} 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;
}
 
 
arp_entry::~arp_entry()
{
	// make sure there is no active timer left for us
	sStackModule->cancel_timer(&timer);
	sStackModule->wait_for_timer(&timer);
 
	ClearQueue();
}
 
 
void
arp_entry::ClearQueue()
{
	BufferList::Iterator iterator = queue.GetIterator();
	while (iterator.HasNext()) {
		net_buffer *buffer = iterator.Next();
		iterator.Remove();
		gBufferModule->free(buffer);
	}
}
 
 
void
arp_entry::MarkFailed()
{
	TRACE(("ARP entry %p Marked as FAILED\n", this));
 
	flags = (flags & ~ARP_FLAG_VALID) | ARP_FLAG_REJECT;
	ClearQueue();
}
 
 
void
arp_entry::MarkValid()
{
	TRACE(("ARP entry %p Marked as VALID, have %li packets queued.\n", this,
		queue.Count()));
 
	flags = (flags & ~ARP_FLAG_REJECT) | ARP_FLAG_VALID;
 
	BufferList::Iterator iterator = queue.GetIterator();
	while (iterator.HasNext()) {
		net_buffer *buffer = iterator.Next();
		iterator.Remove();
 
		TRACE(("  ARP Dequeing packet %p...\n", buffer));
 
		memcpy(buffer->destination, &hardware_address,
			hardware_address.sdl_len);
		protocol->next->module->send_data(protocol->next, buffer);
	}
}
 
 
void
arp_entry::ScheduleRemoval()
{
	// schedule a timer to remove this entry
	timer_state = ARP_STATE_REMOVE_FAILED;
	sStackModule->set_timer(&timer, 0);
}
 
 
//	#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 ARP_FLAG_LOCAL), an error is returned.
 
	This function does not lock the cache - you have to do it yourself
	before calling it.
*/
static status_t
arp_update_entry(in_addr_t protocolAddress, sockaddr_dl *hardwareAddress,
	uint32 flags, arp_entry **_entry = NULL)
{
	ASSERT_LOCKED_MUTEX(&sCacheLock);
	TRACE(("%s(%s, %s, flags 0x%" B_PRIx32 ")\n", __FUNCTION__,
		inet_to_string(protocolAddress), mac_to_string(LLADDR(hardwareAddress)),
		flags));
 
	arp_entry *entry = arp_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 ARP entries (or wait some
		// time) to let us switch to the new address.
		if (protocolAddress != INADDR_ANY
			&& entry->hardware_address.sdl_alen != 0
			&& memcmp(LLADDR(&entry->hardware_address),
				LLADDR(hardwareAddress), ETHER_ADDRESS_LENGTH)) {
			uint8* data = LLADDR(hardwareAddress);
			dprintf("ARP host %08x updated with different hardware address "
				"%02x:%02x:%02x:%02x:%02x:%02x.\n", protocolAddress,
				data[0], data[1], data[2], data[3], data[4], data[5]);
			return B_ERROR;
		}
 
		entry->hardware_address = *hardwareAddress;
		entry->timestamp = system_time();
	} else {
		entry = arp_entry::Add(protocolAddress, hardwareAddress, flags);
		if (entry == NULL)
			return B_NO_MEMORY;
	}
 
	delete_request_buffer(entry);
 
	if ((entry->flags & ARP_FLAG_PERMANENT) == 0) {
		// (re)start the stale timer
		entry->timer_state = ARP_STATE_STALE;
		sStackModule->set_timer(&entry->timer, ARP_STALE_TIMEOUT);
	}
 
	if ((entry->flags & ARP_FLAG_REJECT) != 0)
		entry->MarkFailed();
	else
		entry->MarkValid();
 
	if (_entry)
		*_entry = entry;
 
	return B_OK;
}
 
 
static status_t
arp_set_local_entry(arp_protocol* protocol, const sockaddr* local)
{
	MutexLocker locker(sCacheLock);
 
	net_interface* interface = protocol->interface;
	in_addr_t inetAddress;
 
	if (local == NULL) {
		// interface has not yet been set
		inetAddress = INADDR_ANY;
	} else
		inetAddress = ((sockaddr_in*)local)->sin_addr.s_addr;
 
	TRACE(("%s(): address %s\n", __FUNCTION__, inet_to_string(inetAddress)));
 
	if (protocol->local_address == 0)
		protocol->local_address = inetAddress;
 
	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_IP);
	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
 
	arp_entry* entry;
	status_t status = arp_update_entry(inetAddress, &address,
		ARP_FLAG_LOCAL | ARP_FLAG_PERMANENT, &entry);
	if (status == B_OK)
		entry->protocol = protocol;
 
	return status;
}
 
 
static void
arp_remove_local_entry(arp_protocol* protocol, const sockaddr* local,
	net_interface_address* updateLocalAddress = NULL)
{
	in_addr_t inetAddress;
 
	if (local == NULL) {
		// interface has not yet been set
		inetAddress = INADDR_ANY;
	} else
		inetAddress = ((sockaddr_in*)local)->sin_addr.s_addr;
 
	TRACE(("%s(): address %s\n", __FUNCTION__, inet_to_string(inetAddress)));
 
	MutexLocker locker(sCacheLock);
 
	arp_entry* entry = arp_entry::Lookup(inetAddress);
	if (entry != NULL) {
		sCache->Remove(entry);
		entry->flags |= ARP_FLAG_REMOVED;
	}
 
	if (updateLocalAddress != NULL && protocol->local_address == inetAddress) {
		// find new local sender address
		protocol->local_address = 0;
 
		net_interface_address* address = NULL;
		while (sDatalinkModule->get_next_interface_address(protocol->interface,
				&address)) {
			if (address == updateLocalAddress || address->local == NULL
				|| address->local->sa_family != AF_INET)
				continue;
 
			protocol->local_address
				= ((sockaddr_in*)address->local)->sin_addr.s_addr;
		}
	}
 
	locker.Unlock();
	delete entry;
 
	if (protocol->local_address == 0 && updateLocalAddress) {
		// Try to keep the interface operational
		arp_set_local_entry(protocol, NULL);
	}
}
 
 
/*!	Removes all entries belonging to the local interface of the \a procotol
	given.
*/
static void
arp_remove_local(arp_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_INET)
			continue;
 
		arp_remove_local_entry(protocol, address->local);
	}
}
 
 
/*!	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
arp_update_local(arp_protocol* protocol)
{
	protocol->local_address = 0;
		// TODO: test if this actually works - maybe we should use
		// INADDR_BROADCAST instead
 
	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_INET)
			continue;
 
		if (arp_set_local_entry(protocol, address->local) == B_OK) {
			count++;
		}
	}
 
	if (count == 0)
		return arp_set_local_entry(protocol, NULL);
 
	return B_OK;
}
 
 
static status_t
handle_arp_request(net_buffer *buffer, arp_header &header)
{
	MutexLocker locker(sCacheLock);
 
	if (!sIgnoreReplies) {
		arp_update_entry(header.protocol_sender,
			(sockaddr_dl *)buffer->source, 0);
			// remember the address of the sender as we might need it later
	}
 
	// check if this request is for us
 
	arp_entry *entry = arp_entry::Lookup(header.protocol_target);
	if (entry == NULL || entry->protocol == NULL
		|| (entry->flags & (ARP_FLAG_LOCAL | ARP_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)
 
	TRACE(("  send reply!\n"));
	header.opcode = htons(ARP_OPCODE_REPLY);
 
	memcpy(header.hardware_target, header.hardware_sender, ETHER_ADDRESS_LENGTH);
	header.protocol_target = header.protocol_sender;
	memcpy(header.hardware_sender, LLADDR(&entry->hardware_address),
		ETHER_ADDRESS_LENGTH);
	header.protocol_sender = entry->protocol_address;
 
	// exchange source and destination address
	memcpy(LLADDR((sockaddr_dl *)buffer->source), header.hardware_sender,
		ETHER_ADDRESS_LENGTH);
	memcpy(LLADDR((sockaddr_dl *)buffer->destination), header.hardware_target,
		ETHER_ADDRESS_LENGTH);
 
	buffer->flags = 0;
		// make sure this won't be a broadcast message
 
	gBufferModule->trim(buffer, sizeof(arp_header));
	return entry->protocol->next->module->send_data(entry->protocol->next,
		buffer);
}
 
 
static void
handle_arp_reply(net_buffer *buffer, arp_header &header)
{
	if (sIgnoreReplies)
		return;
 
	MutexLocker locker(sCacheLock);
	arp_update_entry(header.protocol_sender, (sockaddr_dl *)buffer->source, 0);
}
 
 
static status_t
arp_receive(void *cookie, net_device *device, net_buffer *buffer)
{
	TRACE(("ARP receive\n"));
 
	NetBufferHeaderReader<arp_header> bufferHeader(buffer);
	if (bufferHeader.Status() < B_OK)
		return bufferHeader.Status();
 
	arp_header &header = bufferHeader.Data();
	uint16 opcode = ntohs(header.opcode);
 
#ifdef TRACE_ARP
	dprintf("  hw sender: %s\n", mac_to_string(header.hardware_sender));
	dprintf("  proto sender: %s\n", inet_to_string(header.protocol_sender));
	dprintf("  hw target: %s\n", mac_to_string(header.hardware_target));;
	dprintf("  proto target: %s\n", inet_to_string(header.protocol_target));
#endif	// TRACE_ARP
 
	if (ntohs(header.protocol_type) != ETHER_TYPE_IP
		|| ntohs(header.hardware_type) != ARP_HARDWARE_TYPE_ETHER)
		return B_BAD_TYPE;
 
	// check if the packet is okay
 
	if (header.hardware_length != ETHER_ADDRESS_LENGTH
		|| header.protocol_length != sizeof(in_addr_t))
		return B_BAD_DATA;
 
	// handle packet
 
	switch (opcode) {
		case ARP_OPCODE_REQUEST:
			TRACE(("  got ARP request\n"));
			if (handle_arp_request(buffer, header) == B_OK) {
				// the function will take care of the buffer if everything
				// went well
				return B_OK;
			}
			break;
		case ARP_OPCODE_REPLY:
			TRACE(("  got ARP reply\n"));
			handle_arp_reply(buffer, header);
			break;
 
		default:
			dprintf("unknown ARP opcode %d\n", opcode);
			return B_ERROR;
	}
 
	gBufferModule->free(buffer);
	return B_OK;
}
 
 
static void
arp_timer(struct net_timer *timer, void *data)
{
	arp_entry *entry = (arp_entry *)data;
	TRACE(("ARP timer %ld, entry %p!\n", entry->timer_state, entry));
 
	switch (entry->timer_state) {
		case ARP_NO_STATE:
			// who are you kidding?
			break;
 
		case ARP_STATE_REQUEST_FAILED:
			// Requesting the ARP 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 ARP entry %p failed!\n", entry));
			entry->timer_state = ARP_STATE_REMOVE_FAILED;
			entry->MarkFailed();
			sStackModule->set_timer(&entry->timer, ARP_REJECT_TIMEOUT);
			break;
 
		case ARP_STATE_REMOVE_FAILED:
		case ARP_STATE_STALE:
		{
			// the entry has aged so much that we're going to remove it
			TRACE(("  remove ARP entry %p!\n", entry));
 
			MutexLocker locker(sCacheLock);
			if ((entry->flags & ARP_FLAG_REMOVED) != 0) {
				// The entry has already been removed, and is about to be
				// deleted
				break;
			}
 
			sCache->Remove(entry);
			locker.Unlock();
 
			delete entry;
			break;
		}
 
		default:
		{
			if (entry->timer_state > ARP_STATE_LAST_REQUEST
				|| entry->protocol == NULL)
				break;
 
			TRACE(("  send request for ARP entry %p!\n", entry));
 
			net_buffer *request = get_request_buffer(entry);
			if (request == NULL)
				break;
 
			if (entry->timer_state < ARP_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 = ARP_STATE_LAST_REQUEST;
				} else {
					put_request_buffer(entry, request);
					request = clone;
				}
			}
 
			// we're trying to resolve the address, so keep sending requests
			status_t status = entry->protocol->next->module->send_data(
				entry->protocol->next, request);
			if (status < B_OK)
				gBufferModule->free(request);
 
			entry->timer_state++;
			sStackModule->set_timer(&entry->timer, ARP_REQUEST_TIMEOUT);
			break;
		}
	}
}
 
 
/*!	Address resolver function: prepares and triggers the ARP request necessary
	to retrieve the hardware address for \a address.
 
	You need to have the sCacheLock held when calling this function.
*/
static status_t
arp_start_resolve(arp_protocol* protocol, in_addr_t address, arp_entry** _entry)
{
	ASSERT_LOCKED_MUTEX(&sCacheLock);
 
	// create an unresolved ARP entry as a placeholder
	arp_entry *entry = arp_entry::Add(address, NULL, 0);
	if (entry == NULL)
		return B_NO_MEMORY;
 
	// prepare ARP request
 
	entry->request_buffer = gBufferModule->create(256);
	if (entry->request_buffer == NULL) {
		entry->ScheduleRemoval();
		return B_NO_MEMORY;
	}
 
	NetBufferPrepend<arp_header> bufferHeader(entry->request_buffer);
	status_t status = bufferHeader.Status();
	if (status < B_OK) {
		entry->ScheduleRemoval();
		return status;
	}
 
	// prepare ARP header
 
	net_device *device = protocol->interface->device;
	arp_header &header = bufferHeader.Data();
 
	header.hardware_type = htons(ARP_HARDWARE_TYPE_ETHER);
	header.protocol_type = htons(ETHER_TYPE_IP);
	header.hardware_length = ETHER_ADDRESS_LENGTH;
	header.protocol_length = sizeof(in_addr_t);
	header.opcode = htons(ARP_OPCODE_REQUEST);
 
	memcpy(header.hardware_sender, device->address.data, ETHER_ADDRESS_LENGTH);
	memset(header.hardware_target, 0, ETHER_ADDRESS_LENGTH);
	header.protocol_sender = protocol->local_address;
	header.protocol_target = address;
 
	// prepare source and target addresses
 
	struct sockaddr_dl &source = *(struct sockaddr_dl *)
		entry->request_buffer->source;
	source.sdl_len = sizeof(sockaddr_dl);
	source.sdl_family = AF_LINK;
	source.sdl_index = device->index;
	source.sdl_type = IFT_ETHER;
	source.sdl_e_type = htons(ETHER_TYPE_ARP);
	source.sdl_nlen = source.sdl_slen = 0;
	source.sdl_alen = ETHER_ADDRESS_LENGTH;
	memcpy(source.sdl_data, device->address.data, ETHER_ADDRESS_LENGTH);
 
	entry->request_buffer->flags = MSG_BCAST;
		// this is a broadcast packet, we don't need to fill in the destination
 
	entry->protocol = protocol;
	entry->timer_state = ARP_STATE_REQUEST;
	sStackModule->set_timer(&entry->timer, 0);
		// start request timer
 
	*_entry = entry;
	return B_OK;
}
 
 
static status_t
arp_control(const char *subsystem, uint32 function, void *buffer,
	size_t bufferSize)
{
	struct arp_control control;
	if (bufferSize != sizeof(struct arp_control))
		return B_BAD_VALUE;
	if (user_memcpy(&control, buffer, sizeof(struct arp_control)) < B_OK)
		return B_BAD_ADDRESS;
 
	MutexLocker locker(sCacheLock);
 
	switch (function) {
		case ARP_SET_ENTRY:
		{
			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_IP);
			hardwareAddress.sdl_nlen = hardwareAddress.sdl_slen = 0;
			hardwareAddress.sdl_alen = ETHER_ADDRESS_LENGTH;
			memcpy(hardwareAddress.sdl_data, control.ethernet_address,
				ETHER_ADDRESS_LENGTH);
 
			return arp_update_entry(control.address, &hardwareAddress,
				control.flags & (ARP_FLAG_PUBLISH | ARP_FLAG_PERMANENT
					| ARP_FLAG_REJECT));
		}
 
		case ARP_GET_ENTRY:
		{
			arp_entry *entry = arp_entry::Lookup(control.address);
			if (entry == NULL || !(entry->flags & ARP_FLAG_VALID))
				return B_ENTRY_NOT_FOUND;
 
			if (entry->hardware_address.sdl_alen == ETHER_ADDRESS_LENGTH) {
				memcpy(control.ethernet_address,
					entry->hardware_address.sdl_data, ETHER_ADDRESS_LENGTH);
			} else
				memset(control.ethernet_address, 0, ETHER_ADDRESS_LENGTH);
 
			control.flags = entry->flags & ARP_PUBLIC_FLAG_MASK;
			return user_memcpy(buffer, &control, sizeof(struct arp_control));
		}
 
		case ARP_GET_ENTRIES:
		{
			AddressCache::Iterator iterator(sCache);
 
			arp_entry *entry = NULL;
			for (uint32 i = 0; i <= control.cookie; i++) {
				if (!iterator.HasNext())
					return B_ENTRY_NOT_FOUND;
				entry = iterator.Next();
			}
 
			control.cookie++;
			control.address = entry->protocol_address;
			if (entry->hardware_address.sdl_alen == ETHER_ADDRESS_LENGTH) {
				memcpy(control.ethernet_address,
					entry->hardware_address.sdl_data, ETHER_ADDRESS_LENGTH);
			} else
				memset(control.ethernet_address, 0, ETHER_ADDRESS_LENGTH);
			control.flags = entry->flags & ARP_PUBLIC_FLAG_MASK;
 
			return user_memcpy(buffer, &control, sizeof(struct arp_control));
		}
 
		case ARP_DELETE_ENTRY:
		{
			arp_entry *entry = arp_entry::Lookup(control.address);
			if (entry == NULL)
				return B_ENTRY_NOT_FOUND;
			if ((entry->flags & ARP_FLAG_LOCAL) != 0)
				return B_BAD_VALUE;
 
			entry->ScheduleRemoval();
			return B_OK;
		}
 
		case ARP_FLUSH_ENTRIES:
		{
			AddressCache::Iterator iterator(sCache);
 
			arp_entry *entry;
			while (iterator.HasNext()) {
				entry = iterator.Next();
				// we never flush local ARP entries
				if ((entry->flags & ARP_FLAG_LOCAL) != 0)
					continue;
 
				entry->ScheduleRemoval();
			}
			return B_OK;
		}
 
		case ARP_IGNORE_REPLIES:
			sIgnoreReplies = control.flags != 0;
			return B_OK;
	}
 
	return B_BAD_VALUE;
}
 
 
static status_t
arp_init()
{
	mutex_init(&sCacheLock, "arp cache");
 
	sCache = new(std::nothrow) AddressCache();
	if (sCache == NULL || sCache->Init(64) != B_OK) {
		mutex_destroy(&sCacheLock);
		return B_NO_MEMORY;
	}
 
	register_generic_syscall(ARP_SYSCALLS, arp_control, 1, 0);
	return B_OK;
}
 
 
static status_t
arp_uninit()
{
	unregister_generic_syscall(ARP_SYSCALLS, 1);
	return B_OK;
}
 
 
//	#pragma mark - net_datalink_protocol
 
 
status_t
arp_init_protocol(net_interface* interface, net_domain* domain,
	net_datalink_protocol** _protocol)
{
	// We currently only support a single family and type!
	if (interface->device->type != IFT_ETHER
		|| domain->family != AF_INET)
		return B_BAD_TYPE;
 
	status_t status = sStackModule->register_device_handler(interface->device,
		B_NET_FRAME_TYPE(IFT_ETHER, ETHER_TYPE_ARP), &arp_receive, NULL);
	if (status != B_OK)
		return status;
 
	status = sStackModule->register_domain_device_handler(
		interface->device, B_NET_FRAME_TYPE(IFT_ETHER, ETHER_TYPE_IP), domain);
	if (status != B_OK)
		return status;
 
	arp_protocol* protocol = new(std::nothrow) arp_protocol;
	if (protocol == NULL)
		return B_NO_MEMORY;
 
	memset(&protocol->hardware_address, 0, sizeof(sockaddr_dl));
	protocol->local_address = 0;
 
	*_protocol = protocol;
	return B_OK;
}
 
 
status_t
arp_uninit_protocol(net_datalink_protocol *protocol)
{
	sStackModule->unregister_device_handler(protocol->interface->device,
		B_NET_FRAME_TYPE(IFT_ETHER, ETHER_TYPE_ARP));
	sStackModule->unregister_device_handler(protocol->interface->device,
		B_NET_FRAME_TYPE(IFT_ETHER, ETHER_TYPE_IP));
 
	delete protocol;
	return B_OK;
}
 
 
status_t
arp_send_data(net_datalink_protocol *_protocol, net_buffer *buffer)
{
	arp_protocol *protocol = (arp_protocol *)_protocol;
	{
		MutexLocker locker(sCacheLock);
 
		// Set buffer target and destination address
 
		memcpy(buffer->source, &protocol->hardware_address,
			protocol->hardware_address.sdl_len);
 
		if ((buffer->flags & MSG_MCAST) != 0) {
			sockaddr_dl multicastDestination;
			ipv4_to_ether_multicast(&multicastDestination,
				(sockaddr_in *)buffer->destination);
			memcpy(buffer->destination, &multicastDestination,
				sizeof(multicastDestination));
		} else if ((buffer->flags & MSG_BCAST) == 0) {
			// Lookup destination (we may need to wait for this)
			arp_entry *entry = arp_entry::Lookup(
				((struct sockaddr_in *)buffer->destination)->sin_addr.s_addr);
			if (entry == NULL) {
				status_t status = arp_start_resolve(protocol,
					((struct sockaddr_in*)buffer->destination)->sin_addr.s_addr,
					&entry);
				if (status != B_OK)
					return status;
			}
 
			if ((entry->flags & ARP_FLAG_REJECT) != 0)
				return EHOSTUNREACH;
 
			if ((entry->flags & ARP_FLAG_VALID) == 0) {
				// entry is still being resolved.
				TRACE(("ARP 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);
		}
		// the broadcast address is set in the ethernet frame module
	}
	TRACE(("%s(%p): from %s\n", __FUNCTION__, buffer,
		mac_to_string(LLADDR((sockaddr_dl*)buffer->source))));
	TRACE(("  to %s\n",
		mac_to_string(LLADDR((sockaddr_dl*)buffer->destination))));
 
	return protocol->next->module->send_data(protocol->next, buffer);
}
 
 
status_t
arp_up(net_datalink_protocol* _protocol)
{
	arp_protocol* protocol = (arp_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 = arp_update_local(protocol);
	if (status != B_OK) {
		protocol->next->module->interface_down(protocol->next);
		return status;
	}
 
	return B_OK;
}
 
 
void
arp_down(net_datalink_protocol *protocol)
{
	// remove local ARP entries from the cache
	arp_remove_local((arp_protocol*)protocol);
 
	protocol->next->module->interface_down(protocol->next);
}
 
 
status_t
arp_change_address(net_datalink_protocol* _protocol,
	net_interface_address* address, int32 option,
	const struct sockaddr* oldAddress, const struct sockaddr* newAddress)
{
	arp_protocol* protocol = (arp_protocol*)_protocol;
	TRACE(("%s(option %" B_PRId32 ")\n", __FUNCTION__, option));
 
	switch (option) {
		case SIOCSIFADDR:
		case SIOCAIFADDR:
		case SIOCDIFADDR:
			// Those are the options we handle
			if ((protocol->interface->flags & IFF_UP) != 0) {
				// Update ARP entry for the local address
 
				if (newAddress != NULL && newAddress->sa_family == AF_INET) {
					status_t status = arp_set_local_entry(protocol, newAddress);
					if (status != B_OK)
						return status;
				}
 
				if (option != SIOCAIFADDR
					&& (oldAddress == NULL || oldAddress->sa_family == AF_INET))
					arp_remove_local_entry(protocol, oldAddress, address);
			}
			break;
 
		default:
			break;
	}
 
	return protocol->next->module->change_address(protocol->next, address,
		option, oldAddress, newAddress);
}
 
 
status_t
arp_control(net_datalink_protocol *_protocol, int32 op, void *argument,
	size_t length)
{
	arp_protocol* protocol = (arp_protocol*)_protocol;
	return protocol->next->module->control(protocol->next, op, argument,
		length);
}
 
 
static status_t
arp_join_multicast(net_datalink_protocol *protocol, const sockaddr *address)
{
	if (address->sa_family != AF_INET)
		return EINVAL;
 
	sockaddr_dl multicastAddress;
	ipv4_to_ether_multicast(&multicastAddress, (const sockaddr_in *)address);
 
	return protocol->next->module->join_multicast(protocol->next,
		(sockaddr *)&multicastAddress);
}
 
 
static status_t
arp_leave_multicast(net_datalink_protocol *protocol, const sockaddr *address)
{
	if (address->sa_family != AF_INET)
		return EINVAL;
 
	sockaddr_dl multicastAddress;
	ipv4_to_ether_multicast(&multicastAddress, (const sockaddr_in *)address);
 
	return protocol->next->module->leave_multicast(protocol->next,
		(sockaddr *)&multicastAddress);
}
 
 
static status_t
arp_std_ops(int32 op, ...)
{
	switch (op) {
		case B_MODULE_INIT:
			return arp_init();
		case B_MODULE_UNINIT:
			return arp_uninit();
 
		default:
			return B_ERROR;
	}
}
 
 
static net_datalink_protocol_module_info sARPModule = {
	{
		"network/datalink_protocols/arp/v1",
		0,
		arp_std_ops
	},
	arp_init_protocol,
	arp_uninit_protocol,
	arp_send_data,
	arp_up,
	arp_down,
	arp_change_address,
	arp_control,
	arp_join_multicast,
	arp_leave_multicast,
};
 
 
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},
	{}
};
 
module_info* modules[] = {
	(module_info*)&sARPModule,
	NULL
};

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