* Copyright 2005, Ingo Weinhold <bonefish@cs.tu-berlin.de>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include <boot/net/ARP.h>
#include <stdio.h>
#include <KernelExport.h>
#include <boot/net/ChainBuffer.h>
#ifdef TRACE_ARP
# define TRACE(x) dprintf x
#else
# define TRACE(x) ;
#endif
ARPService::ARPService(EthernetService *ethernet)
: EthernetSubService(kARPServiceName),
fEthernet(ethernet),
fAge(0)
{
for (int i = 0; i < MAP_ENTRY_COUNT; i++)
fEntries[i].ip = INADDR_ANY;
}
ARPService::~ARPService()
{
if (fEthernet)
fEthernet->UnregisterEthernetSubService(this);
}
status_t
ARPService::Init()
{
if (!fEthernet)
return B_BAD_VALUE;
if (!fEthernet->RegisterEthernetSubService(this))
return B_NO_MEMORY;
return B_OK;
}
uint16
ARPService::EthernetProtocol() const
{
return ETHERTYPE_ARP;
}
void
ARPService::HandleEthernetPacket(EthernetService *ethernet,
const mac_addr_t &targetAddress, const void *data, size_t size)
{
TRACE(("ARPService::HandleEthernetPacket(): %lu - %lu bytes\n", size,
sizeof(ip_header)));
if (size < sizeof(arp_header))
return;
arp_header *header = (arp_header*)data;
if (header->hardware_format != htons(ARPHRD_ETHER)
|| header->protocol_format != htons(ETHERTYPE_IP)
|| header->hardware_length != sizeof(mac_addr_t)
|| header->protocol_length != sizeof(ip_addr_t)
|| header->sender_mac == kNoMACAddress
|| header->sender_mac == kBroadcastMACAddress
|| (header->opcode != htons(ARPOP_REQUEST)
&& header->opcode != htons(ARPOP_REPLY))) {
return;
}
if (header->opcode == htons(ARPOP_REQUEST)
&& (fEthernet->IPAddress() == INADDR_ANY
|| header->target_ip != htonl(fEthernet->IPAddress()))) {
return;
}
if (header->opcode == htons(ARPOP_REPLY)
&& (targetAddress != fEthernet->MACAddress()
|| header->target_mac != targetAddress)) {
return;
}
if (header->sender_ip != htonl(INADDR_ANY)
&& header->sender_ip != htonl(INADDR_BROADCAST)) {
_PutEntry(ntohl(header->sender_ip), header->sender_mac);
}
if (header->opcode == htons(ARPOP_REQUEST)) {
_SendARPPacket(ntohl(header->sender_ip), header->sender_mac,
ARPOP_REPLY);
}
}
status_t
ARPService::GetMACForIP(ip_addr_t ip, mac_addr_t &mac)
{
TRACE(("ARPService::GetMACForIP(%08lx)\n", ip));
if (ip == INADDR_ANY)
return B_BAD_VALUE;
if (ip == INADDR_BROADCAST) {
mac = kBroadcastMACAddress;
TRACE(("ARPService::GetMACForIP(%08lx) done: %012llx\n", ip,
mac.ToUInt64()));
return B_OK;
}
if (MapEntry *entry = _FindEntry(ip)) {
mac = entry->mac;
TRACE(("ARPService::GetMACForIP(%08lx) done: %012llx\n", ip,
mac.ToUInt64()));
return B_OK;
}
for (int i = 0; i < ARP_REQUEST_RETRY_COUNT; i++) {
status_t error = _SendARPPacket(ip, kBroadcastMACAddress,
ARPOP_REQUEST);
if (error != B_OK) {
TRACE(("ARPService::GetMACForIP(%08lx) failed: sending failed\n",
ip));
return error;
}
bigtime_t startTime = system_time();
do {
fEthernet->ProcessIncomingPackets();
if (MapEntry *entry = _FindEntry(ip)) {
mac = entry->mac;
TRACE(("ARPService::GetMACForIP(%08lx) done: %012llx\n", ip,
mac.ToUInt64()));
return B_OK;
}
} while (system_time() - startTime < ARP_REPLY_TIMEOUT);
}
TRACE(("ARPService::GetMACForIP(%08lx) failed: no reply\n", ip));
return EHOSTUNREACH;
}
status_t
ARPService::_SendARPPacket(ip_addr_t ip, const mac_addr_t &mac, uint16 opcode)
{
arp_header header;
ChainBuffer headerBuffer(&header, sizeof(header));
header.hardware_format = htons(ARPHRD_ETHER);
header.protocol_format = htons(ETHERTYPE_IP);
header.hardware_length = sizeof(mac_addr_t);
header.protocol_length = sizeof(ip_addr_t);
header.opcode = htons(opcode);
header.sender_mac = fEthernet->MACAddress();
header.sender_ip = htonl(fEthernet->IPAddress());
header.target_mac = (mac == kBroadcastMACAddress ? kNoMACAddress : mac);
header.target_ip = htonl(ip);
return fEthernet->Send(mac, ETHERTYPE_ARP, &headerBuffer);
}
ARPService::MapEntry *
ARPService::_FindEntry(ip_addr_t ip)
{
if (ip == INADDR_ANY)
return NULL;
for (int i = 0; i < MAP_ENTRY_COUNT; i++) {
if (ip == fEntries[i].ip)
return fEntries + i;
}
return NULL;
}
void
ARPService::_PutEntry(ip_addr_t ip, const mac_addr_t &mac)
{
MapEntry *entry = fEntries;
for (int i = 0; i < MAP_ENTRY_COUNT; i++) {
if (fEntries[i].ip == INADDR_ANY) {
entry = fEntries + i;
break;
}
if (fAge - fEntries[i].age > fAge - entry->age)
entry = fEntries + i;
}
entry->age = fAge++;
entry->ip = ip;
entry->mac = mac;
}