xref: /haiku/src/servers/net/AutoconfigLooper.cpp (revision 3dfd9cb95ce45f59160d50975210bc55e3fc0709)
1 /*
2  * Copyright 2006-2010, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Axel Dörfler, axeld@pinc-software.de
7  */
8 
9 
10 #include "AutoconfigLooper.h"
11 
12 #include <errno.h>
13 #include <net/if_dl.h>
14 #include <net/if_media.h>
15 #include <net/if_types.h>
16 #include <stdio.h>
17 #include <sys/socket.h>
18 #include <sys/sockio.h>
19 
20 #include <NetworkInterface.h>
21 #include <NetworkNotifications.h>
22 
23 #include "DHCPClient.h"
24 #include "NetServer.h"
25 
26 
27 static const uint32 kMsgReadyToRun = 'rdyr';
28 
29 
30 AutoconfigLooper::AutoconfigLooper(BMessenger target, const char* device)
31 	: BLooper(device),
32 	fTarget(target),
33 	fDevice(device),
34 	fCurrentClient(NULL)
35 {
36 	memset(fCurrentMac, 0, sizeof(fCurrentMac));
37 	BMessage ready(kMsgReadyToRun);
38 	PostMessage(&ready);
39 }
40 
41 
42 AutoconfigLooper::~AutoconfigLooper()
43 {
44 }
45 
46 
47 void
48 AutoconfigLooper::_RemoveClient()
49 {
50 	if (fCurrentClient == NULL)
51 		return;
52 
53 	RemoveHandler(fCurrentClient);
54 	delete fCurrentClient;
55 	fCurrentClient = NULL;
56 }
57 
58 
59 void
60 AutoconfigLooper::_ConfigureIPv4()
61 {
62 	// start with DHCP
63 
64 	if (fCurrentClient == NULL) {
65 		fCurrentClient = new DHCPClient(fTarget, fDevice.String());
66 		AddHandler(fCurrentClient);
67 	}
68 
69 	// set IFF_CONFIGURING flag on interface
70 
71 	BNetworkInterface interface(fDevice.String());
72 	int32 flags = interface.Flags() & ~IFF_AUTO_CONFIGURED;
73 	interface.SetFlags(flags | IFF_CONFIGURING);
74 
75 	if (fCurrentClient->Initialize() == B_OK)
76 		return;
77 
78 	_RemoveClient();
79 
80 	puts("DHCP failed miserably!");
81 
82 	// DHCP obviously didn't work out, take some default values for now
83 	// TODO: have a look at zeroconf
84 	// TODO: this could also be done add-on based
85 
86 	if ((interface.Flags() & IFF_CONFIGURING) == 0) {
87 		// Someone else configured the interface in the mean time
88 		return;
89 	}
90 
91 	BMessage message(kMsgConfigureInterface);
92 	message.AddString("device", fDevice.String());
93 	message.AddBool("auto_configured", true);
94 
95 	BNetworkAddress link;
96 	uint8 last = 56;
97 	if (interface.GetHardwareAddress(link) == B_OK) {
98 		// choose IP address depending on the MAC address, if available
99 		uint8* mac = link.LinkLevelAddress();
100 		last = mac[0] ^ mac[1] ^ mac[2] ^ mac[3] ^ mac[4] ^ mac[5];
101 		if (last > 253)
102 			last = 253;
103 		else if (last == 0)
104 			last = 1;
105 	}
106 
107 	// IANA defined the default autoconfig network (for when a DHCP request
108 	// fails for some reason) as being 169.254.0.0/255.255.0.0. We are only
109 	// generating the last octet but we could also use the 2 last octets if
110 	// wanted.
111 	char string[64];
112 	snprintf(string, sizeof(string), "169.254.0.%u", last);
113 
114 	BMessage address;
115 	address.AddInt32("family", AF_INET);
116 	address.AddString("address", string);
117 	message.AddMessage("address", &address);
118 
119 	fTarget.SendMessage(&message);
120 }
121 
122 
123 #ifdef INET6
124 static in6_addr
125 BuildIPv6LinkLocalAddress(uint8 mac[6])
126 {
127 	in6_addr result;
128 
129 	result.s6_addr[0] = 0xfe;
130 	result.s6_addr[1] = 0x80;
131 	result.s6_addr[2] = 0;
132 	result.s6_addr[3] = 0;
133 	result.s6_addr[4] = 0;
134 	result.s6_addr[5] = 0;
135 	result.s6_addr[6] = 0;
136 	result.s6_addr[7] = 0;
137 
138 	result.s6_addr[8] = mac[0] ^ 0x02;
139 	result.s6_addr[9] = mac[1];
140 	result.s6_addr[10] = mac[2];
141 	result.s6_addr[11] = 0xff;
142 	result.s6_addr[12] = 0xfe;
143 	result.s6_addr[13] = mac[3];
144 	result.s6_addr[14] = mac[4];
145 	result.s6_addr[15] = mac[5];
146 
147 	return result;
148 }
149 
150 
151 void
152 AutoconfigLooper::_ConfigureIPv6LinkLocal(bool add)
153 {
154 	// do not touch the loopback device
155 	if (!strncmp(fDevice.String(), "loop", 4))
156 		return;
157 
158 	ifreq request;
159 	if (!prepare_request(request, fDevice.String()))
160 		return;
161 
162 	int socket = ::socket(AF_INET6, SOCK_DGRAM, 0);
163 	if (socket < 0)
164 		return;
165 
166 	// set IFF_CONFIGURING flag on interface
167 	if (ioctl(socket, SIOCGIFFLAGS, &request, sizeof(struct ifreq)) == 0) {
168 		request.ifr_flags |= IFF_CONFIGURING;
169 		ioctl(socket, SIOCSIFFLAGS, &request, sizeof(struct ifreq));
170 	}
171 
172 	uint8 mac[6];
173 	memcpy(mac, fCurrentMac, 6);
174 	if (add == true) {
175 		if (get_mac_address(fDevice.String(), mac) != B_OK)
176 			add = false;
177 	}
178 
179 	if (add == true) {
180 		in6_addr inetAddress = BuildIPv6LinkLocalAddress(mac);
181 		if (_AddIPv6LinkLocal(socket, inetAddress) != true)
182 			add = false;
183 
184 		// save the MAC address for later usage
185 		memcpy(fCurrentMac, mac, 6);
186 	}
187 
188 	if (add == false) {
189 		static const uint8 zeroMac[6] = {0};
190 		if (memcmp(fCurrentMac, zeroMac, 6)) {
191 			in6_addr inetAddress = BuildIPv6LinkLocalAddress(fCurrentMac);
192 			_RemoveIPv6LinkLocal(socket, inetAddress);
193 			// reset the stored MAC address
194 			memcpy(fCurrentMac, zeroMac, 6);
195 		}
196 	}
197 
198 	if (ioctl(socket, SIOCGIFFLAGS, &request, sizeof(struct ifreq)) == 0
199 		&& (request.ifr_flags & IFF_CONFIGURING) == 0) {
200 		// Someone else configured the interface in the mean time
201 		close(socket);
202 		return;
203 	}
204 
205 	close(socket);
206 }
207 
208 
209 bool
210 AutoconfigLooper::_AddIPv6LinkLocal(int socket, const in6_addr &address)
211 {
212 	struct ifreq request;
213 	if (!prepare_request(request, fDevice.String()))
214 		return false;
215 
216 	ifaliasreq aliasRequest;
217 	memset(&aliasRequest, 0, sizeof(ifaliasreq));
218 	strlcpy(aliasRequest.ifra_name, fDevice.String(), IF_NAMESIZE);
219 	aliasRequest.ifra_addr.ss_len = sizeof(sockaddr_in6);
220 	aliasRequest.ifra_addr.ss_family = AF_INET6;
221 
222 	if (ioctl(socket, SIOCAIFADDR, &aliasRequest, sizeof(ifaliasreq)) < 0) {
223 		if (errno != B_NAME_IN_USE)
224 			return false;
225 	}
226 
227 	sockaddr_in6* socketAddress = (sockaddr_in6*)&request.ifr_addr;
228 	socketAddress->sin6_len = sizeof(sockaddr_in6);
229 	socketAddress->sin6_family = AF_INET6;
230 
231 	// address
232 	memcpy(&socketAddress->sin6_addr, &address, sizeof(in6_addr));
233 	if (ioctl(socket, SIOCSIFADDR, &request, sizeof(struct ifreq)) < 0)
234 		return false;
235 
236 	// mask (/64)
237 	memset(socketAddress->sin6_addr.s6_addr, 0xff, 8);
238 	memset(socketAddress->sin6_addr.s6_addr + 8, 0, 8);
239 
240 	if (ioctl(socket, SIOCSIFNETMASK, &request, sizeof(struct ifreq)) < 0)
241 		return false;
242 
243 	return true;
244 }
245 
246 
247 void
248 AutoconfigLooper::_RemoveIPv6LinkLocal(int socket, const in6_addr &address)
249 {
250 	struct ifreq request;
251 	if (!prepare_request(request, fDevice.String()))
252 		return;
253 
254 	sockaddr_in6* socketAddress = (sockaddr_in6*)&request.ifr_addr;
255 	socketAddress->sin6_len = sizeof(sockaddr_in6);
256 	socketAddress->sin6_family = AF_INET6;
257 
258 	// address
259 	memcpy(&socketAddress->sin6_addr, &address, sizeof(in6_addr));
260 	if (ioctl(socket, SIOCDIFADDR, &request, sizeof(struct ifreq)) < 0)
261 		return;
262 }
263 #endif // INET6
264 
265 
266 void
267 AutoconfigLooper::_ReadyToRun()
268 {
269 	start_watching_network(B_WATCH_NETWORK_LINK_CHANGES, this);
270 	_ConfigureIPv4();
271 #ifdef INET6
272 	_ConfigureIPv6LinkLocal(true);
273 #endif
274 }
275 
276 
277 void
278 AutoconfigLooper::MessageReceived(BMessage* message)
279 {
280 	switch (message->what) {
281 		case kMsgReadyToRun:
282 			_ReadyToRun();
283 			break;
284 
285 		case B_NETWORK_MONITOR:
286 			const char* device;
287 			int32 opcode;
288 			int32 media;
289 			if (message->FindInt32("opcode", &opcode) != B_OK
290 				|| opcode != B_NETWORK_DEVICE_LINK_CHANGED
291 				|| message->FindString("device", &device) != B_OK
292 				|| fDevice != device
293 				|| message->FindInt32("media", &media) != B_OK)
294 				break;
295 
296 			if ((media & IFM_ACTIVE) != 0) {
297 				// Reconfigure the interface when we have a link again
298 				_ConfigureIPv4();
299 			}
300 #ifdef INET6
301 			_ConfigureIPv6LinkLocal((media & IFM_ACTIVE) != 0);
302 #endif
303 			break;
304 
305 		default:
306 			BLooper::MessageReceived(message);
307 			break;
308 	}
309 }
310 
311