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
25static bool
26string_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
34static bool
35is_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
43static int
44compare_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
60class NetStackInitializer {
61public:
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
114private:
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		while (dirent* entry = readdir(dir)) {
125			// skip "." and ".."
126			if (strcmp(entry->d_name, ".") == 0
127				|| strcmp(entry->d_name, "..") == 0) {
128				continue;
129			}
130
131			path.Append(entry->d_name);
132
133			struct stat st;
134			if (stat(path.Path(), &st) == 0) {
135				if (S_ISDIR(st.st_mode))
136					_ScanDevices(path);
137				else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode))
138					_ScanDevice(path.Path());
139			}
140
141			path.RemoveLeaf();
142
143			if (fFoundInterface)
144				break;
145		}
146
147		closedir(dir);
148	}
149
150	void _ScanDevice(const char* path)
151	{
152		dprintf("NetStackInitializer: scanning device %s\n", path);
153
154		// check if this interface is already known
155		ifreq request;
156		if (strlen(path) >= IF_NAMESIZE)
157			return;
158		strcpy(request.ifr_name, path);
159
160		if (ioctl(fSocket, SIOCGIFINDEX, &request, sizeof(request)) < 0) {
161			// not known yet -- add it
162			ifaliasreq aliasRequest;
163			strcpy(aliasRequest.ifra_name, path);
164			aliasRequest.ifra_addr.ss_family = AF_UNSPEC;
165			aliasRequest.ifra_addr.ss_len = 2;
166			aliasRequest.ifra_broadaddr.ss_family = AF_UNSPEC;
167			aliasRequest.ifra_broadaddr.ss_len = 2;
168			aliasRequest.ifra_mask.ss_family = AF_UNSPEC;
169			aliasRequest.ifra_mask.ss_len = 2;
170
171			if (ioctl(fSocket, SIOCAIFADDR, &aliasRequest,
172					sizeof(aliasRequest)) < 0) {
173				dprintf("NetStackInitializer: adding interface failed for "
174					"device %s: %s\n", path, strerror(errno));
175				return;
176			}
177		}
178
179		// bring the interface up (get flags, add IFF_UP)
180		if (ioctl(fSocket, SIOCGIFFLAGS, &request, sizeof(request)) < 0) {
181			dprintf("NetStackInitializer: getting flags failed for interface "
182				"%s: %s\n", path, strerror(errno));
183			return;
184		}
185
186		int interfaceFlags = request.ifr_flags;
187		if (!(interfaceFlags & IFF_UP)) {
188			interfaceFlags |= IFF_UP;
189			request.ifr_flags = interfaceFlags;
190			if (ioctl(fSocket, SIOCSIFFLAGS, &request, sizeof(request)) < 0) {
191				dprintf("NetStackInitializer: failed to bring interface up "
192					"%s: %s\n", path, strerror(errno));
193				return;
194			}
195		}
196
197		// get the MAC address
198		if (ioctl(fLinkSocket, SIOCGIFADDR, &request, sizeof(request)) < 0) {
199			dprintf("NetStackInitializer: Getting MAC addresss failed for "
200				"interface %s: %s\n", path, strerror(errno));
201			return;
202		}
203
204		sockaddr_dl& link = *(sockaddr_dl*)&request.ifr_addr;
205		if (link.sdl_type != IFT_ETHER)
206			return;
207
208		if (link.sdl_alen == 0)
209			return;
210
211		uint8* macBytes = (uint8 *)LLADDR(&link);
212		uint64 macAddress = ((uint64)macBytes[0] << 40)
213			| ((uint64)macBytes[1] << 32)
214			| ((uint64)macBytes[2] << 24)
215			| ((uint64)macBytes[3] << 16)
216			| ((uint64)macBytes[4] << 8)
217			| (uint64)macBytes[5];
218
219		dprintf("NetStackInitializer: found ethernet interface with MAC "
220			"address %02x:%02x:%02x:%02x:%02x:%02x; which is%s the one we're "
221			"looking for\n", macBytes[0], macBytes[1], macBytes[2], macBytes[3],
222			macBytes[4], macBytes[5], (macAddress == fClientMAC ? "" : "n't"));
223
224		if (macAddress != fClientMAC)
225			return;
226
227		fFoundInterface = true;
228
229		// configure the interface
230
231		// set IP address
232		sockaddr_in& address = *(sockaddr_in*)&request.ifr_addr;
233		address.sin_family = AF_INET;
234		address.sin_len = sizeof(sockaddr_in);
235		address.sin_port = 0;
236		address.sin_addr.s_addr = htonl(fClientIP);
237		memset(&address.sin_zero[0], 0, sizeof(address.sin_zero));
238		if (ioctl(fSocket, SIOCSIFADDR, &request, sizeof(request)) < 0) {
239			dprintf("NetStackInitializer: Setting IP addresss failed for "
240				"interface %s: %s\n", path, strerror(errno));
241			return;
242		}
243
244		// set net mask
245		address.sin_addr.s_addr = htonl(fNetMask);
246		if (ioctl(fSocket, SIOCSIFNETMASK, &request, sizeof(request)) < 0) {
247			dprintf("NetStackInitializer: Setting net mask failed for "
248				"interface %s: %s\n", path, strerror(errno));
249			return;
250		}
251
252		// set broadcast address
253		address.sin_addr.s_addr = htonl(fClientIP | ~fNetMask);
254		if (ioctl(fSocket, SIOCSIFBRDADDR, &request, sizeof(request)) < 0) {
255			dprintf("NetStackInitializer: Setting broadcast address failed for "
256				"interface %s: %s\n", path, strerror(errno));
257			return;
258		}
259
260		// set IFF_BROADCAST
261		if (!(interfaceFlags & IFF_BROADCAST)) {
262			interfaceFlags |= IFF_BROADCAST;
263			request.ifr_flags = interfaceFlags;
264			if (ioctl(fSocket, SIOCSIFFLAGS, &request, sizeof(request)) < 0) {
265				dprintf("NetStackInitializer: failed to set IFF_BROADCAST flag "
266					"for interface %s: %s\n", path, strerror(errno));
267				return;
268			}
269		}
270
271		// set default route; remove previous one, if any
272		route_entry route;
273		memset(&route, 0, sizeof(route_entry));
274		route.flags = RTF_STATIC | RTF_DEFAULT;
275
276		request.ifr_route = route;
277		ioctl(fSocket, SIOCDELRT, &request, sizeof(request));
278		if (ioctl(fSocket, SIOCADDRT, &request, sizeof(request)) < 0) {
279			dprintf("NetStackInitializer: Failed to set default route: %s\n",
280				strerror(errno));
281			return;
282		}
283
284		fConfiguredInterface = true;
285
286		dprintf("NetStackInitializer: successfully configured boot network "
287			"interface\n");
288	}
289
290private:
291	int					fSocket;
292	int					fLinkSocket;
293	uint64				fClientMAC;
294	uint32				fClientIP;
295	uint32				fNetMask;
296	bool				fFoundInterface;
297	bool				fConfiguredInterface;
298};
299
300
301// #pragma mark - NetBootMethod
302
303
304NetBootMethod::NetBootMethod(const KMessage& bootVolume, int32 method)
305	: BootMethod(bootVolume, method)
306{
307}
308
309
310NetBootMethod::~NetBootMethod()
311{
312}
313
314
315status_t
316NetBootMethod::Init()
317{
318	// We need to bring up the net stack.
319	status_t status;
320
321	uint64 clientMAC;
322	uint32 clientIP = 0;
323	uint32 netMask;
324	if (fBootVolume.FindInt64("client MAC", (int64*)&clientMAC) != B_OK
325		|| fBootVolume.FindInt32("client IP", (int32*)&clientIP) != B_OK) {
326		panic("no client MAC or IP address or net mask\n");
327		return B_ERROR;
328	}
329
330	if (fBootVolume.FindInt32("net mask", (int32*)&netMask) != B_OK) {
331		// choose default netmask depending on the class of the address
332		if (IN_CLASSA(clientIP)
333			|| (clientIP >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) {
334			// class A, or loopback
335			netMask = IN_CLASSA_NET;
336		} else if (IN_CLASSB(clientIP)) {
337			// class B
338			netMask = IN_CLASSB_NET;
339		} else {
340			// class C and rest
341			netMask = IN_CLASSC_NET;
342		}
343	}
344
345	NetStackInitializer initializer(clientMAC, clientIP, netMask);
346	status = initializer.Init();
347	if (status != B_OK)
348		return status;
349
350	// TODO: "net root path" should be used for finding the boot device/FS,
351	// but ATM neither the remote_disk nor the nbd driver are configurable
352	// at this point.
353	const char* rootPath = fBootVolume.GetString("net root path", NULL);
354	dprintf("NetBootMethod::Init(): net stack initialized; root path is: %s\n",
355		rootPath);
356
357	return B_OK;
358}
359
360
361bool
362NetBootMethod::IsBootDevice(KDiskDevice* device, bool strict)
363{
364	// We support only NBD and RemoteDisk at the moment, so we accept any
365	// device under /dev/disk/virtual/{nbd,remote_disk}/.
366	return is_net_device(device);
367}
368
369
370bool
371NetBootMethod::IsBootPartition(KPartition* partition, bool& foundForSure)
372{
373	// as long as it's BFS, we're fine
374	return (partition->ContentType()
375		&& strcmp(partition->ContentType(), kPartitionTypeBFS) == 0);
376}
377
378
379void
380NetBootMethod::SortPartitions(KPartition** partitions, int32 count)
381{
382	qsort(partitions, count, sizeof(KPartition*),
383		compare_partitions_net_devices);
384}
385