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