xref: /haiku/src/system/boot/loader/net/ARP.cpp (revision 03c2b46cdf16f7e84fc001ecfc770f47d53399c0)
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
ARPService(EthernetService * ethernet)23 ARPService::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
~ARPService()34 ARPService::~ARPService()
35 {
36 	if (fEthernet)
37 		fEthernet->UnregisterEthernetSubService(this);
38 }
39 
40 // Init
41 status_t
Init()42 ARPService::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
52 uint16
EthernetProtocol() const53 ARPService::EthernetProtocol() const
54 {
55 	return ETHERTYPE_ARP;
56 }
57 
58 // HandleEthernetPacket
59 void
HandleEthernetPacket(EthernetService * ethernet,const mac_addr_t & targetAddress,const void * data,size_t size)60 ARPService::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
112 status_t
GetMACForIP(ip_addr_t ip,mac_addr_t & mac)113 ARPService::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
164 status_t
_SendARPPacket(ip_addr_t ip,const mac_addr_t & mac,uint16 opcode)165 ARPService::_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
184 ARPService::MapEntry *
_FindEntry(ip_addr_t ip)185 ARPService::_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
199 void
_PutEntry(ip_addr_t ip,const mac_addr_t & mac)200 ARPService::_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