1/*
2 * Copyright 2005, Ingo Weinhold <bonefish@cs.tu-berlin.de>.
3 * All rights reserved. Distributed under the terms of the MIT License.
4 */
5
6#include <boot/net/ARP.h>
7
8#include <stdio.h>
9#include <KernelExport.h>
10
11#include <boot/net/ChainBuffer.h>
12
13
14//#define TRACE_ARP
15#ifdef TRACE_ARP
16#	define TRACE(x) dprintf x
17#else
18#	define TRACE(x) ;
19#endif
20
21
22// constructor
23ARPService::ARPService(EthernetService *ethernet)
24	: EthernetSubService(kARPServiceName),
25		fEthernet(ethernet),
26		fAge(0)
27{
28	// clear table
29	for (int i = 0; i < MAP_ENTRY_COUNT; i++)
30		fEntries[i].ip = INADDR_ANY;
31}
32
33// destructor
34ARPService::~ARPService()
35{
36	if (fEthernet)
37		fEthernet->UnregisterEthernetSubService(this);
38}
39
40// Init
41status_t
42ARPService::Init()
43{
44	if (!fEthernet)
45		return B_BAD_VALUE;
46	if (!fEthernet->RegisterEthernetSubService(this))
47		return B_NO_MEMORY;
48	return B_OK;
49}
50
51// EthernetProtocol
52uint16
53ARPService::EthernetProtocol() const
54{
55	return ETHERTYPE_ARP;
56}
57
58// HandleEthernetPacket
59void
60ARPService::HandleEthernetPacket(EthernetService *ethernet,
61	const mac_addr_t &targetAddress, const void *data, size_t size)
62{
63	TRACE(("ARPService::HandleEthernetPacket(): %lu - %lu bytes\n", size,
64		sizeof(ip_header)));
65
66	if (size < sizeof(arp_header))
67		return;
68
69	arp_header *header = (arp_header*)data;
70	// check packet validity
71	if (header->hardware_format != htons(ARPHRD_ETHER)
72		|| header->protocol_format != htons(ETHERTYPE_IP)
73		|| header->hardware_length != sizeof(mac_addr_t)
74		|| header->protocol_length != sizeof(ip_addr_t)
75		// valid sender MAC?
76		|| header->sender_mac == kNoMACAddress
77		|| header->sender_mac == kBroadcastMACAddress
78		// do we support the opcode?
79		|| (header->opcode != htons(ARPOP_REQUEST)
80			&& header->opcode != htons(ARPOP_REPLY))) {
81		return;
82	}
83
84	// if this is a request, we continue only, if we have the targeted IP
85	if (header->opcode == htons(ARPOP_REQUEST)
86		&& (fEthernet->IPAddress() == INADDR_ANY
87			|| header->target_ip != htonl(fEthernet->IPAddress()))) {
88		return;
89	}
90
91	// if this is a reqly, we accept it only, if it was directly sent to us
92	if (header->opcode == htons(ARPOP_REPLY)
93		&& (targetAddress != fEthernet->MACAddress()
94			|| header->target_mac != targetAddress)) {
95		return;
96	}
97
98	// if sender IP looks valid, enter the mapping
99	if (header->sender_ip != htonl(INADDR_ANY)
100		&& header->sender_ip != htonl(INADDR_BROADCAST)) {
101		_PutEntry(ntohl(header->sender_ip), header->sender_mac);
102	}
103
104	// if this is a request, send a reply
105	if (header->opcode == htons(ARPOP_REQUEST)) {
106		_SendARPPacket(ntohl(header->sender_ip), header->sender_mac,
107			ARPOP_REPLY);
108	}
109}
110
111// GetMACForIP
112status_t
113ARPService::GetMACForIP(ip_addr_t ip, mac_addr_t &mac)
114{
115	TRACE(("ARPService::GetMACForIP(%08lx)\n", ip));
116
117	if (ip == INADDR_ANY)
118		return B_BAD_VALUE;
119	if (ip == INADDR_BROADCAST) {
120		mac = kBroadcastMACAddress;
121		TRACE(("ARPService::GetMACForIP(%08lx) done: %012llx\n", ip,
122			mac.ToUInt64()));
123		return B_OK;
124	}
125
126	// already known?
127	if (MapEntry *entry = _FindEntry(ip)) {
128		mac = entry->mac;
129		TRACE(("ARPService::GetMACForIP(%08lx) done: %012llx\n", ip,
130			mac.ToUInt64()));
131		return B_OK;
132	}
133
134	for (int i = 0; i < ARP_REQUEST_RETRY_COUNT; i++) {
135		// send request
136		status_t error = _SendARPPacket(ip, kBroadcastMACAddress,
137			ARPOP_REQUEST);
138		if (error != B_OK) {
139			TRACE(("ARPService::GetMACForIP(%08lx) failed: sending failed\n",
140				ip));
141			return error;
142		}
143
144		bigtime_t startTime = system_time();
145		do {
146			fEthernet->ProcessIncomingPackets();
147
148			// received reply?
149			if (MapEntry *entry = _FindEntry(ip)) {
150				mac = entry->mac;
151				TRACE(("ARPService::GetMACForIP(%08lx) done: %012llx\n", ip,
152					mac.ToUInt64()));
153				return B_OK;
154			}
155		} while (system_time() - startTime < ARP_REPLY_TIMEOUT);
156	}
157
158	TRACE(("ARPService::GetMACForIP(%08lx) failed: no reply\n", ip));
159
160	return EHOSTUNREACH;
161}
162
163// _SendARPPacket
164status_t
165ARPService::_SendARPPacket(ip_addr_t ip, const mac_addr_t &mac, uint16 opcode)
166{
167	// prepare ARP header
168	arp_header header;
169	ChainBuffer headerBuffer(&header, sizeof(header));
170	header.hardware_format = htons(ARPHRD_ETHER);
171	header.protocol_format = htons(ETHERTYPE_IP);
172	header.hardware_length = sizeof(mac_addr_t);
173	header.protocol_length = sizeof(ip_addr_t);
174	header.opcode = htons(opcode);
175	header.sender_mac = fEthernet->MACAddress();
176	header.sender_ip = htonl(fEthernet->IPAddress());
177	header.target_mac = (mac == kBroadcastMACAddress ? kNoMACAddress : mac);
178	header.target_ip = htonl(ip);
179
180	return fEthernet->Send(mac, ETHERTYPE_ARP, &headerBuffer);
181}
182
183// _FindEntry
184ARPService::MapEntry *
185ARPService::_FindEntry(ip_addr_t ip)
186{
187	if (ip == INADDR_ANY)
188		return NULL;
189
190	for (int i = 0; i < MAP_ENTRY_COUNT; i++) {
191		if (ip == fEntries[i].ip)
192			return fEntries + i;
193	}
194
195	return NULL;
196}
197
198// _PutEntry
199void
200ARPService::_PutEntry(ip_addr_t ip, const mac_addr_t &mac)
201{
202	// find empty/oldest slot
203	MapEntry *entry = fEntries;
204	for (int i = 0; i < MAP_ENTRY_COUNT; i++) {
205		if (fEntries[i].ip == INADDR_ANY) {
206			entry = fEntries + i;
207			break;
208		}
209
210		if (fAge - fEntries[i].age > fAge - entry->age)
211			entry = fEntries + i;
212	}
213
214	entry->age = fAge++;
215	entry->ip = ip;
216	entry->mac = mac;
217}
218