1 // Copyright 2016 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "netsvc.h"
6 #include "zbi.h"
7
8 #include <assert.h>
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <sys/stat.h>
15 #include <threads.h>
16 #include <unistd.h>
17
18 #include <fuchsia/device/manager/c/fidl.h>
19 #include <inet6/inet6.h>
20 #include <inet6/netifc.h>
21 #include <lib/fdio/util.h>
22 #include <zircon/boot/netboot.h>
23 #include <zircon/processargs.h>
24 #include <zircon/process.h>
25 #include <zircon/syscalls.h>
26
27 static uint32_t last_cookie = 0;
28 static uint32_t last_cmd = 0;
29 static uint32_t last_arg = 0;
30 static uint32_t last_ack_cmd = 0;
31 static uint32_t last_ack_arg = 0;
32
33 #define PAGE_ROUNDUP(x) ((x + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1))
34 #define MAX_ADVERTISE_DATA_LEN 256
35
36 static bool xfer_active = false;
37
38 typedef struct nbfilecontainer {
39 nbfile file;
40 zx_handle_t data; // handle to vmo that backs netbootfile.
41 } nbfilecontainer_t;
42
43 static nbfilecontainer_t nbkernel;
44 static nbfilecontainer_t nbbootdata;
45 static nbfilecontainer_t nbcmdline;
46
47 // Pointer to the currently active transfer.
48 static nbfile* active;
49
nbfilecontainer_init(size_t size,nbfilecontainer_t * target)50 static zx_status_t nbfilecontainer_init(size_t size,
51 nbfilecontainer_t* target) {
52 zx_status_t st = ZX_OK;
53
54 assert(target);
55
56 // De-init the container if it's already initialized.
57 if (target->file.data) {
58 // For now, I can't see a valid reason that a client would send the same
59 // filename twice.
60 // We'll handle this case gracefully, but we'll print a warning to the
61 // console just in case it was a mistake.
62 printf("netbootloader: warning, reusing a previously initialized container\n");
63
64 // Unmap the vmo from the address space.
65 st = zx_vmar_unmap(zx_vmar_root_self(), (uintptr_t)target->file.data, target->file.size);
66 if (st != ZX_OK) {
67 printf("netbootloader: failed to unmap existing vmo, st = %d\n", st);
68 return st;
69 }
70
71 zx_handle_close(target->data);
72
73 target->file.offset = 0;
74 target->file.size = 0;
75 target->file.data = 0;
76 }
77
78 st = zx_vmo_create(size, 0, &target->data);
79 if (st != ZX_OK) {
80 printf("netbootloader: Could not create a netboot vmo of size = %lu "
81 "retcode = %d\n", size, st);
82 return st;
83 }
84 zx_object_set_property(target->data, ZX_PROP_NAME, "netboot", 7);
85
86 uintptr_t buffer;
87 st = zx_vmar_map(zx_vmar_root_self(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
88 0, target->data, 0, size, &buffer);
89 if (st != ZX_OK) {
90 printf("netbootloader: failed to map data vmo for buffer, st = %d\n", st);
91 zx_handle_close(target->data);
92 return st;
93 }
94
95 target->file.offset = 0;
96 target->file.size = size;
97 target->file.data = (uint8_t*)buffer;
98
99 return ZX_OK;
100 }
101
netboot_get_buffer(const char * name,size_t size)102 nbfile* netboot_get_buffer(const char* name, size_t size) {
103 zx_status_t st = ZX_OK;
104 nbfilecontainer_t* result;
105
106 if (!strcmp(name, NB_KERNEL_FILENAME)) {
107 result = &nbkernel;
108 } else if (!strcmp(name, NB_RAMDISK_FILENAME)) {
109 result = &nbbootdata;
110 } else if (!strcmp(name, NB_CMDLINE_FILENAME)) {
111 result = &nbcmdline;
112 } else {
113 return NULL;
114 }
115
116 st = nbfilecontainer_init(size, result);
117 if (st != ZX_OK) {
118 printf("netbootloader: failed to initialize file container for "
119 "file = '%s', retcode = %d\n", name, st);
120 return NULL;
121 }
122
123 return &result->file;
124 }
125
netboot_advertise(const char * nodename)126 void netboot_advertise(const char* nodename) {
127 // Don't advertise if a transfer is active.
128 if (xfer_active) return;
129
130 uint8_t buffer[sizeof(nbmsg) + MAX_ADVERTISE_DATA_LEN];
131 nbmsg* msg = (void*)buffer;
132 msg->magic = NB_MAGIC;
133 msg->cookie = 0;
134 msg->cmd = NB_ADVERTISE;
135 msg->arg = NB_VERSION_CURRENT;
136
137 snprintf((char*)msg->data, MAX_ADVERTISE_DATA_LEN, "version=%s;nodename=%s",
138 BOOTLOADER_VERSION, nodename);
139 const size_t data_len = strlen((char*)msg->data) + 1;
140 udp6_send(buffer, sizeof(nbmsg) + data_len, &ip6_ll_all_nodes,
141 NB_ADVERT_PORT, NB_SERVER_PORT, false);
142 }
143
nb_open(const char * filename,uint32_t cookie,uint32_t arg,const ip6_addr_t * saddr,uint16_t sport,uint16_t dport)144 static void nb_open(const char* filename, uint32_t cookie, uint32_t arg,
145 const ip6_addr_t* saddr, uint16_t sport, uint16_t dport) {
146 nbmsg m;
147 m.magic = NB_MAGIC;
148 m.cookie = cookie;
149 m.cmd = NB_ACK;
150 m.arg = netfile_open(filename, arg, NULL);
151 udp6_send(&m, sizeof(m), saddr, sport, dport, false);
152 }
153
nb_read(uint32_t cookie,uint32_t arg,const ip6_addr_t * saddr,uint16_t sport,uint16_t dport)154 static void nb_read(uint32_t cookie, uint32_t arg,
155 const ip6_addr_t* saddr, uint16_t sport, uint16_t dport) {
156 static netfilemsg m = { .hdr.magic = NB_MAGIC, .hdr.cmd = NB_ACK};
157 static size_t msg_size = 0;
158 static uint32_t blocknum = (uint32_t) -1;
159 if (arg == blocknum) {
160 // Request to resend last message, verify that the cookie is unchanged
161 if (cookie != m.hdr.cookie) {
162 m.hdr.arg = -EIO;
163 m.hdr.cookie = cookie;
164 msg_size = sizeof(m.hdr);
165 }
166 } else if (arg == 0 || arg == blocknum + 1) {
167 int result = netfile_read(&m.data, sizeof(m.data));
168 if (result < 0) {
169 m.hdr.arg = result;
170 msg_size = sizeof(m.hdr);
171 } else {
172 // Note that the response does not use actual size as the argument,
173 // it matches the *requested* size. Actual size can be determined by
174 // the packet size.
175 m.hdr.arg = arg;
176 msg_size = sizeof(m.hdr) + result;
177 }
178 m.hdr.cookie = cookie;
179 blocknum = arg;
180 } else {
181 // Ignore bogus read requests -- host will timeout if they're confused
182 return;
183 }
184 udp6_send(&m, msg_size, saddr, sport, dport, false);
185 }
186
nb_write(const char * data,size_t len,uint32_t cookie,uint32_t arg,const ip6_addr_t * saddr,uint16_t sport,uint16_t dport)187 static void nb_write(const char* data, size_t len, uint32_t cookie, uint32_t arg,
188 const ip6_addr_t* saddr, uint16_t sport, uint16_t dport) {
189 static nbmsg m = {.magic = NB_MAGIC, .cmd = NB_ACK};
190 static uint32_t blocknum = (uint32_t) -1;
191 if (arg == blocknum) {
192 // Request to repeat last write, verify that cookie is unchanged
193 if (cookie != m.cookie) {
194 m.arg = -EIO;
195 }
196 } else if (arg == 0 || arg == blocknum + 1) {
197 int result = netfile_write(data, len);
198 m.arg = result > 0 ? 0 : result;
199 blocknum = arg;
200 }
201 m.cookie = cookie;
202 udp6_send(&m, sizeof(m), saddr, sport, dport, false);
203 }
204
nb_close(uint32_t cookie,const ip6_addr_t * saddr,uint16_t sport,uint16_t dport)205 static void nb_close(uint32_t cookie,
206 const ip6_addr_t* saddr, uint16_t sport, uint16_t dport) {
207 nbmsg m;
208 m.magic = NB_MAGIC;
209 m.cookie = cookie;
210 m.cmd = NB_ACK;
211 m.arg = netfile_close();
212 udp6_send(&m, sizeof(m), saddr, sport, dport, false);
213 }
214
do_dmctl_mexec(void)215 static zx_status_t do_dmctl_mexec(void) {
216 zx_handle_t kernel, bootdata;
217 zx_status_t status =
218 netboot_prepare_zbi(nbkernel.data, nbbootdata.data,
219 nbcmdline.file.data, nbcmdline.file.size,
220 &kernel, &bootdata);
221 if (status != ZX_OK) {
222 return status;
223 }
224
225 zx_handle_t wait_handle;
226 status = zx_handle_duplicate(kernel, ZX_RIGHT_SAME_RIGHTS, &wait_handle);
227 if (status != ZX_OK) {
228 return status;
229 }
230
231 int fd = open("/dev/misc/dmctl", O_WRONLY);
232 if (fd < 0) {
233 return ZX_ERR_INTERNAL;
234 }
235
236 zx_handle_t dmctl;
237 status = fdio_get_service_handle(fd, &dmctl);
238 if (status != ZX_OK) {
239 return status;
240 }
241 status = fuchsia_device_manager_ExternalControllerPerformMexec(dmctl, kernel,
242 bootdata);
243 zx_handle_close(dmctl);
244 if (status != ZX_OK) {
245 return status;
246 }
247
248 status = zx_object_wait_one(wait_handle, ZX_USER_SIGNAL_0,
249 ZX_TIME_INFINITE, NULL);
250 zx_handle_close(wait_handle);
251 if (status != ZX_OK) {
252 return status;
253 }
254 // if we get here, mexec failed
255 return ZX_ERR_INTERNAL;
256 }
257
bootloader_recv(void * data,size_t len,const ip6_addr_t * daddr,uint16_t dport,const ip6_addr_t * saddr,uint16_t sport)258 static void bootloader_recv(void* data, size_t len,
259 const ip6_addr_t* daddr, uint16_t dport,
260 const ip6_addr_t* saddr, uint16_t sport) {
261 nbmsg* msg = data;
262 nbmsg ack;
263
264 bool do_transmit = true;
265 bool do_boot = false;
266 bool do_reboot = false;
267
268 if (dport != NB_SERVER_PORT)
269 return;
270
271 if (len < sizeof(nbmsg))
272 return;
273 len -= sizeof(nbmsg);
274
275 if ((last_cookie == msg->cookie) &&
276 (last_cmd == msg->cmd) && (last_arg == msg->arg)) {
277 // host must have missed the ack. resend
278 ack.magic = NB_MAGIC;
279 ack.cookie = last_cookie;
280 ack.cmd = last_ack_cmd;
281 ack.arg = last_ack_arg;
282 goto transmit;
283 }
284
285 ack.cmd = NB_ACK;
286 ack.arg = 0;
287
288 switch (msg->cmd) {
289 case NB_COMMAND:
290 if (len == 0)
291 return;
292 msg->data[len - 1] = 0;
293 break;
294 case NB_SEND_FILE:
295 xfer_active = true;
296 if (len == 0)
297 return;
298 msg->data[len - 1] = 0;
299 for (size_t i = 0; i < (len - 1); i++) {
300 if ((msg->data[i] < ' ') || (msg->data[i] > 127)) {
301 msg->data[i] = '.';
302 }
303 }
304 active = netboot_get_buffer((const char*)msg->data, msg->arg);
305 if (active) {
306 active->offset = 0;
307 ack.arg = msg->arg;
308 size_t prefix_len = strlen(NB_FILENAME_PREFIX);
309 const char* filename;
310 if (!strncmp((char*)msg->data, NB_FILENAME_PREFIX, prefix_len)) {
311 filename = &((const char*)msg->data)[prefix_len];
312 } else {
313 filename = (const char*)msg->data;
314 }
315 printf("netboot: Receive File '%s'...\n", filename);
316 } else {
317 printf("netboot: Rejected File '%s'...\n", (char*) msg->data);
318 ack.cmd = NB_ERROR_BAD_FILE;
319 }
320 break;
321
322 case NB_DATA:
323 case NB_LAST_DATA:
324 xfer_active = true;
325 if (active == 0) {
326 printf("netboot: > received chunk before NB_FILE\n");
327 return;
328 }
329 if (msg->arg != active->offset) {
330 // printf("netboot: < received chunk at offset %d but current offset is %zu\n", msg->arg, active->offset);
331 ack.arg = active->offset;
332 ack.cmd = NB_ACK;
333 } else if ((active->offset + len) > active->size) {
334 ack.cmd = NB_ERROR_TOO_LARGE;
335 ack.arg = msg->arg;
336 } else {
337 memcpy(active->data + active->offset, msg->data, len);
338 active->offset += len;
339 ack.cmd = msg->cmd == NB_LAST_DATA ? NB_FILE_RECEIVED : NB_ACK;
340 if (msg->cmd != NB_LAST_DATA) {
341 do_transmit = false;
342 } else {
343 xfer_active = false;
344 }
345 }
346 break;
347 case NB_BOOT:
348 // Wait for the paver to complete
349 while (atomic_load(&paving_in_progress)) {
350 thrd_yield();
351 }
352 if (atomic_load(&paver_exit_code) != 0) {
353 printf("netboot: detected paver error: %d\n", atomic_load(&paver_exit_code));
354 atomic_store(&paver_exit_code, 0);
355 break;
356 }
357 do_boot = true;
358 printf("netboot: Boot Kernel...\n");
359 break;
360 case NB_REBOOT:
361 // Wait for the paver to complete
362 while (atomic_load(&paving_in_progress)) {
363 thrd_yield();
364 }
365 if (atomic_load(&paver_exit_code) != 0) {
366 printf("netboot: detected paver error: %d\n", atomic_load(&paver_exit_code));
367 atomic_store(&paver_exit_code, 0);
368 break;
369 }
370 do_reboot = true;
371 printf("netboot: Reboot ...\n");
372 break;
373 default:
374 // We don't have a handler for this command, let netsvc handle it.
375 do_transmit = false;
376 }
377
378 last_cookie = msg->cookie;
379 last_cmd = msg->cmd;
380 last_arg = msg->arg;
381 last_ack_cmd = ack.cmd;
382 last_ack_arg = ack.arg;
383
384 ack.cookie = msg->cookie;
385 ack.magic = NB_MAGIC;
386 transmit:
387 if (do_transmit) {
388 udp6_send(&ack, sizeof(ack), saddr, sport, NB_SERVER_PORT, false);
389 }
390
391 if (do_boot) {
392 if (do_dmctl_mexec() != ZX_OK) {
393 // TODO: This will return before the system actually mexecs.
394 // We can't pass an event to wait on here because fdio
395 // has a limit of 3 handles, and we're already using
396 // all 3 to pass boot parameters.
397 printf("netboot: Boot failed\n");
398 }
399 }
400
401 if (do_reboot) {
402 int fd = open("/dev/misc/dmctl", O_WRONLY);
403 if (fd < 0) {
404 printf("netboot: Reboot failed: %s\n", strerror(errno));
405 } else {
406 dprintf(fd, "reboot");
407 close(fd);
408 }
409 }
410 }
411
netboot_recv(void * data,size_t len,bool is_mcast,const ip6_addr_t * daddr,uint16_t dport,const ip6_addr_t * saddr,uint16_t sport)412 void netboot_recv(void *data, size_t len, bool is_mcast,
413 const ip6_addr_t* daddr, uint16_t dport,
414 const ip6_addr_t* saddr, uint16_t sport) {
415 nbmsg* msg = data;
416 // Not enough bytes to be a message
417 if ((len < sizeof(*msg)) ||
418 (msg->magic != NB_MAGIC)) {
419 return;
420 }
421 len -= sizeof(*msg);
422
423 if (len && msg->cmd != NB_DATA && msg->cmd != NB_LAST_DATA) {
424 msg->data[len - 1] = '\0';
425 }
426
427 switch (msg->cmd) {
428 case NB_QUERY:
429 if (strcmp((char*)msg->data, "*") &&
430 strcmp((char*)msg->data, nodename)) {
431 break;
432 }
433 size_t dlen = strlen(nodename) + 1;
434 char buf[1024 + sizeof(nbmsg)];
435 if ((dlen + sizeof(nbmsg)) > sizeof(buf)) {
436 return;
437 }
438 msg->cmd = NB_ACK;
439 memcpy(buf, msg, sizeof(nbmsg));
440 memcpy(buf + sizeof(nbmsg), nodename, dlen);
441 udp6_send(buf, sizeof(nbmsg) + dlen, saddr, sport, dport, false);
442 break;
443 case NB_SHELL_CMD:
444 if (!is_mcast) {
445 netboot_run_cmd((char*) msg->data);
446 return;
447 }
448 break;
449 case NB_OPEN:
450 nb_open((char*)msg->data, msg->cookie, msg->arg, saddr, sport, dport);
451 break;
452 case NB_READ:
453 nb_read(msg->cookie, msg->arg, saddr, sport, dport);
454 break;
455 case NB_WRITE:
456 len--; // NB NUL-terminator is not part of the data
457 nb_write((char*)msg->data, len, msg->cookie, msg->arg, saddr, sport, dport);
458 break;
459 case NB_CLOSE:
460 nb_close(msg->cookie, saddr, sport, dport);
461 break;
462 default:
463 // If the bootloader is enabled, then let it have a crack at the
464 // incoming packets as well.
465 if (netbootloader) {
466 bootloader_recv(data, len + sizeof(nbmsg), daddr, dport, saddr, sport);
467 }
468 }
469 }
470