xref: /haiku/src/system/kernel/fs/vfs_net_boot.cpp (revision b30304acc8c37e678a1bf66976d15bdab103f931)
1 /*
2  * Copyright 2007, Ingo Weinhold, bonefish@cs.tu-berlin.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 #include "vfs_net_boot.h"
7 
8 #include <dirent.h>
9 #include <errno.h>
10 #include <net/if.h>
11 #include <net/if_dl.h>
12 #include <net/if_types.h>
13 #include <netinet/in.h>
14 #include <stdio.h>
15 #include <sys/socket.h>
16 #include <sys/sockio.h>
17 
18 #include <DiskDeviceTypes.h>
19 
20 #include <disk_device_manager/KDiskDevice.h>
21 
22 #include <KPath.h>
23 
24 
25 static bool
26 string_starts_with(const char* string, const char* prefix)
27 {
28 	size_t stringLen = strlen(string);
29 	size_t prefixLen = strlen(prefix);
30 	return (stringLen >= prefixLen && strncmp(string, prefix, prefixLen) == 0);
31 }
32 
33 
34 static bool
35 is_net_device(KDiskDevice* device)
36 {
37 	const char* path = device->Path();
38 	return (string_starts_with(path, "/dev/disk/virtual/nbd/")
39 		|| string_starts_with(path, "/dev/disk/virtual/remote_disk/"));
40 }
41 
42 
43 static int
44 compare_partitions_net_devices(const void *_a, const void *_b)
45 {
46 	KPartition* a = *(KPartition**)_a;
47 	KPartition* b = *(KPartition**)_b;
48 
49 	bool aIsNetDevice = is_net_device(a->Device());
50 	bool bIsNetDevice = is_net_device(b->Device());
51 
52 	int compare = (int)aIsNetDevice - (int)bIsNetDevice;
53 	if (compare != 0)
54 		return compare;
55 
56 	return compare_image_boot(_a, _b);
57 }
58 
59 
60 class NetStackInitializer {
61 public:
62 	NetStackInitializer(uint64 clientMAC, uint32 clientIP, uint32 netMask)
63 		:
64 		fSocket(-1),
65 		fLinkSocket(-1),
66 		fClientMAC(clientMAC),
67 		fClientIP(clientIP),
68 		fNetMask(netMask),
69 		fFoundInterface(false),
70 		fConfiguredInterface(false)
71 	{
72 	}
73 
74 	~NetStackInitializer()
75 	{
76 		// close control sockets
77 		if (fSocket >= 0)
78 			close(fSocket);
79 
80 		if (fLinkSocket >= 0)
81 			close(fLinkSocket);
82 	}
83 
84 	status_t Init()
85 	{
86 		// open a control socket for playing with the stack
87 		fSocket = socket(AF_INET, SOCK_DGRAM, 0);
88 		if (fSocket < 0) {
89 			dprintf("NetStackInitializer: Failed to open socket: %s\n",
90 				strerror(errno));
91 			return errno;
92 		}
93 
94 		// ... and a link level socket
95 		fLinkSocket = socket(AF_LINK, SOCK_DGRAM, 0);
96 		if (fLinkSocket < 0) {
97 			dprintf("NetStackInitializer: Failed to open link level socket:"
98 				" %s\n", strerror(errno));
99 			return errno;
100 		}
101 
102 
103 		// now iterate through the existing network devices
104 		KPath path;
105 		status_t error = path.SetTo("/dev/net");
106 		if (error != B_OK)
107 			return error;
108 
109 		_ScanDevices(path);
110 
111 		return fConfiguredInterface ? B_OK : B_ERROR;
112 	}
113 
114 private:
115 	void _ScanDevices(KPath& path)
116 	{
117 		DIR* dir = opendir(path.Path());
118 		if (!dir) {
119 			dprintf("NetStackInitializer: Failed to opendir() \"%s\": %s\n",
120 				path.Path(), strerror(errno));
121 			return;
122 		}
123 
124 		status_t error = B_OK;
125 
126 		while (dirent* entry = readdir(dir)) {
127 			// skip "." and ".."
128 			if (strcmp(entry->d_name, ".") == 0
129 				|| strcmp(entry->d_name, "..") == 0) {
130 				continue;
131 			}
132 
133 			path.Append(entry->d_name);
134 
135 			struct stat st;
136 			if (stat(path.Path(), &st) == 0) {
137 				if (S_ISDIR(st.st_mode))
138 					_ScanDevices(path);
139 				else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode))
140 					_ScanDevice(path.Path());
141 			}
142 
143 			path.RemoveLeaf();
144 
145 			if (fFoundInterface)
146 				break;
147 		}
148 
149 		closedir(dir);
150 	}
151 
152 	void _ScanDevice(const char* path)
153 	{
154 		dprintf("NetStackInitializer: scanning device %s\n", path);
155 
156 		// check if this interface is already known
157 		ifreq request;
158 		if (strlen(path) >= IF_NAMESIZE)
159 			return;
160 		strcpy(request.ifr_name, path);
161 
162 		if (ioctl(fSocket, SIOCGIFINDEX, &request, sizeof(request)) < 0) {
163 			// not known yet -- add it
164 			request.ifr_parameter.base_name[0] = '\0';
165 			request.ifr_parameter.device[0] = '\0';
166 			request.ifr_parameter.sub_type = 0;
167 
168 			if (ioctl(fSocket, SIOCAIFADDR, &request, sizeof(request)) < 0) {
169 				dprintf("NetStackInitializer: adding interface failed for "
170 					"device %s: %s\n", path, strerror(errno));
171 				return;
172 			}
173 		}
174 
175 		// bring the interface up (get flags, add IFF_UP)
176 		if (ioctl(fSocket, SIOCGIFFLAGS, &request, sizeof(request)) < 0) {
177 			dprintf("NetStackInitializer: getting flags failed for interface "
178 				"%s: %s\n", path, strerror(errno));
179 			return;
180 		}
181 
182 		int interfaceFlags = request.ifr_flags;
183 		if (!(interfaceFlags & IFF_UP)) {
184 			interfaceFlags |= IFF_UP;
185 			request.ifr_flags = interfaceFlags;
186 			if (ioctl(fSocket, SIOCSIFFLAGS, &request, sizeof(request)) < 0) {
187 				dprintf("NetStackInitializer: failed to bring interface up "
188 					"%s: %s\n", path, strerror(errno));
189 				return;
190 			}
191 		}
192 
193 		// get the MAC address
194 		if (ioctl(fLinkSocket, SIOCGIFADDR, &request, sizeof(request)) < 0) {
195 			dprintf("NetStackInitializer: Getting MAC addresss failed for "
196 				"interface %s: %s\n", path, strerror(errno));
197 			return;
198 		}
199 
200 		sockaddr_dl& link = *(sockaddr_dl*)&request.ifr_addr;
201 		if (link.sdl_type != IFT_ETHER)
202 			return;
203 
204 		if (link.sdl_alen == 0)
205 			return;
206 
207 		uint8* macBytes = (uint8 *)LLADDR(&link);
208 		uint64 macAddress = ((uint64)macBytes[0] << 40)
209 			| ((uint64)macBytes[1] << 32)
210 			| ((uint64)macBytes[2] << 24)
211 			| ((uint64)macBytes[3] << 16)
212 			| ((uint64)macBytes[4] << 8)
213 			| (uint64)macBytes[5];
214 
215 		dprintf("NetStackInitializer: found ethernet interface with MAC "
216 			"address %02x:%02x:%02x:%02x:%02x:%02x; which is%s the one we're "
217 			"looking for\n", macBytes[0], macBytes[1], macBytes[2], macBytes[3],
218 			macBytes[4], macBytes[5], (macAddress == fClientMAC ? "" : "n't"));
219 
220 		if (macAddress != fClientMAC)
221 			return;
222 
223 		fFoundInterface = true;
224 
225 		// configure the interface
226 
227 		// set IP address
228 		sockaddr_in& address = *(sockaddr_in*)&request.ifr_addr;
229 		address.sin_family = AF_INET;
230 		address.sin_len = sizeof(sockaddr_in);
231 		address.sin_port = 0;
232 		address.sin_addr.s_addr = htonl(fClientIP);
233 		memset(&address.sin_zero[0], 0, sizeof(address.sin_zero));
234 		if (ioctl(fSocket, SIOCSIFADDR, &request, sizeof(request)) < 0) {
235 			dprintf("NetStackInitializer: Setting IP addresss failed for "
236 				"interface %s: %s\n", path, strerror(errno));
237 			return;
238 		}
239 
240 		// set net mask
241 		address.sin_addr.s_addr = htonl(fNetMask);
242 		if (ioctl(fSocket, SIOCSIFNETMASK, &request, sizeof(request)) < 0) {
243 			dprintf("NetStackInitializer: Setting net mask failed for "
244 				"interface %s: %s\n", path, strerror(errno));
245 			return;
246 		}
247 
248 		// set broadcast address
249 		address.sin_addr.s_addr = htonl(fClientIP | ~fNetMask);
250 		if (ioctl(fSocket, SIOCSIFBRDADDR, &request, sizeof(request)) < 0) {
251 			dprintf("NetStackInitializer: Setting broadcast address failed for "
252 				"interface %s: %s\n", path, strerror(errno));
253 			return;
254 		}
255 
256 		// set IFF_BROADCAST
257 		if (!(interfaceFlags & IFF_BROADCAST)) {
258 			interfaceFlags |= IFF_BROADCAST;
259 			request.ifr_flags = interfaceFlags;
260 			if (ioctl(fSocket, SIOCSIFFLAGS, &request, sizeof(request)) < 0) {
261 				dprintf("NetStackInitializer: failed to set IFF_BROADCAST flag "
262 					"for interface %s: %s\n", path, strerror(errno));
263 				return;
264 			}
265 		}
266 
267 		// set default route; remove previous one, if any
268 		route_entry route;
269 		memset(&route, 0, sizeof(route_entry));
270 		route.flags = RTF_STATIC | RTF_DEFAULT;
271 
272 		request.ifr_route = route;
273 		ioctl(fSocket, SIOCDELRT, &request, sizeof(request));
274 		if (ioctl(fSocket, SIOCADDRT, &request, sizeof(request)) < 0) {
275 			dprintf("NetStackInitializer: Failed to set default route: %s\n",
276 				strerror(errno));
277 			return;
278 		}
279 
280 		fConfiguredInterface = true;
281 
282 		dprintf("NetStackInitializer: successfully configured boot network "
283 			"interface\n");
284 	}
285 
286 private:
287 	int					fSocket;
288 	int					fLinkSocket;
289 	uint64				fClientMAC;
290 	uint32				fClientIP;
291 	uint32				fNetMask;
292 	bool				fFoundInterface;
293 	bool				fConfiguredInterface;
294 };
295 
296 
297 // #pragma mark - NetBootMethod
298 
299 
300 NetBootMethod::NetBootMethod(const KMessage& bootVolume, int32 method)
301 	: BootMethod(bootVolume, method)
302 {
303 }
304 
305 
306 NetBootMethod::~NetBootMethod()
307 {
308 }
309 
310 
311 status_t
312 NetBootMethod::Init()
313 {
314 	// We need to bring up the net stack.
315 	status_t status;
316 
317 	uint64 clientMAC;
318 	uint32 clientIP;
319 	uint32 netMask;
320 	if (fBootVolume.FindInt64("client MAC", (int64*)&clientMAC) != B_OK
321 		|| fBootVolume.FindInt32("client IP", (int32*)&clientIP) != B_OK) {
322 		panic("no client MAC or IP address or net mask\n");
323 		return B_ERROR;
324 	}
325 
326 	if (fBootVolume.FindInt32("net mask", (int32*)&netMask) != B_OK) {
327 		// choose default netmask depending on the class of the address
328 		in_addr_t net = htonl(clientIP);
329 		if (IN_CLASSA(net)
330 			|| (ntohl(net) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) {
331 			// class A, or loopback
332 			netMask = ntohl(IN_CLASSA_NET);
333 		} else if (IN_CLASSB(net)) {
334 			// class B
335 			netMask = ntohl(IN_CLASSB_NET);
336 		} else {
337 			// class C and rest
338 			netMask = ntohl(IN_CLASSC_NET);
339 		}
340 	}
341 
342 	NetStackInitializer initializer(clientMAC, clientIP, netMask);
343 	status = initializer.Init();
344 	if (status != B_OK)
345 		return status;
346 
347 	// TODO: "net root path" should be used for finding the boot device/FS,
348 	// but ATM neither the remote_disk nor the nbd driver are configurable
349 	// at this point.
350 	const char* rootPath = fBootVolume.GetString("net root path", NULL);
351 	dprintf("NetBootMethod::Init(): net stack initialized; root path is: %s\n",
352 		rootPath);
353 
354 	return B_OK;
355 }
356 
357 
358 bool
359 NetBootMethod::IsBootDevice(KDiskDevice* device, bool strict)
360 {
361 	// We support only NBD and RemoteDisk at the moment, so we accept any
362 	// device under /dev/disk/virtual/{nbd,remote_disk}/.
363 	return is_net_device(device);
364 }
365 
366 
367 bool
368 NetBootMethod::IsBootPartition(KPartition* partition, bool& foundForSure)
369 {
370 	// as long as it's BFS, we're fine
371 	return (partition->ContentType()
372 		&& strcmp(partition->ContentType(), kPartitionTypeBFS) == 0);
373 }
374 
375 
376 void
377 NetBootMethod::SortPartitions(KPartition** partitions, int32 count)
378 {
379 	qsort(partitions, count, sizeof(KPartition*),
380 		compare_partitions_net_devices);
381 }
382