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 <stdio.h>
6 #include <string.h>
7 
8 #include <device_id.h>
9 #include <inet6.h>
10 #include <netifc.h>
11 #include <xefi.h>
12 
13 #include <zircon/boot/netboot.h>
14 #include <tftp/tftp.h>
15 
16 #define TFTP_BUF_SZ 2048
17 char tftp_session_scratch[TFTP_BUF_SZ];
18 char tftp_out_scratch[TFTP_BUF_SZ];
19 
20 // item being downloaded
21 static nbfile* item;
22 
23 // TFTP file state
24 typedef struct {
25     nbfile* netboot_file_data;
26     size_t file_size;
27     unsigned int progress_reported;
28 } file_info_t;
29 
30 // TFTP transport state
31 typedef struct {
32     struct ip6_addr_t dest_addr;
33     uint16_t dest_port;
34 } transport_info_t;
35 
36 static uint32_t last_cookie = 0;
37 static uint32_t last_cmd = 0;
38 static uint32_t last_arg = 0;
39 static uint32_t last_ack_cmd = 0;
40 static uint32_t last_ack_arg = 0;
41 
42 static int nb_boot_now = 0;
43 static int nb_active = 0;
44 
45 static char advertise_nodename[64] = "";
46 static char advertise_data[256] = "nodename=zircon";
47 
send_query_ack(const ip6_addr * addr,uint16_t port,uint32_t cookie)48 static void send_query_ack(const ip6_addr* addr, uint16_t port,
49                            uint32_t cookie) {
50     uint8_t buffer[256];
51     nbmsg* msg = (void*)buffer;
52     msg->magic = NB_MAGIC;
53     msg->cookie = cookie;
54     msg->cmd = NB_ACK;
55     msg->arg = NB_VERSION_CURRENT;
56     memcpy(msg->data, advertise_nodename, sizeof(advertise_nodename));
57     udp6_send(buffer, sizeof(nbmsg) + strlen(advertise_nodename) + 1,
58               addr, port, NB_SERVER_PORT);
59 }
60 
advertise(void)61 static void advertise(void) {
62     uint8_t buffer[sizeof(nbmsg) + sizeof(advertise_data)];
63     nbmsg* msg = (void*)buffer;
64     msg->magic = NB_MAGIC;
65     msg->cookie = 0;
66     msg->cmd = NB_ADVERTISE;
67     msg->arg = NB_VERSION_CURRENT;
68     size_t data_len = strlen(advertise_data) + 1;
69     memcpy(msg->data, advertise_data, data_len);
70     udp6_send(buffer, sizeof(nbmsg) + data_len, &ip6_ll_all_nodes,
71               NB_ADVERT_PORT, NB_SERVER_PORT);
72 }
73 
netboot_recv(void * data,size_t len,const ip6_addr * saddr,uint16_t sport)74 void netboot_recv(void* data, size_t len, const ip6_addr* saddr, uint16_t sport) {
75     nbmsg* msg = data;
76     nbmsg ack;
77     int do_transmit = 1;
78 
79     if (len < sizeof(nbmsg))
80         return;
81     len -= sizeof(nbmsg);
82 
83     // printf("netboot: MSG %08x %08x %08x %08x datalen %zu\n",
84     //        msg->magic, msg->cookie, msg->cmd, msg->arg, len);
85 
86     if ((last_cookie == msg->cookie) &&
87         (last_cmd == msg->cmd) && (last_arg == msg->arg)) {
88         // host must have missed the ack. resend
89         ack.magic = NB_MAGIC;
90         ack.cookie = last_cookie;
91         ack.cmd = last_ack_cmd;
92         ack.arg = last_ack_arg;
93         goto transmit;
94     }
95 
96     ack.cmd = NB_ACK;
97     ack.arg = 0;
98 
99     switch (msg->cmd) {
100     case NB_COMMAND:
101         if (len == 0)
102             return;
103         msg->data[len - 1] = 0;
104         break;
105     case NB_SEND_FILE:
106         if (len == 0)
107             return;
108         msg->data[len - 1] = 0;
109         for (size_t i = 0; i < (len - 1); i++) {
110             if ((msg->data[i] < ' ') || (msg->data[i] > 127)) {
111                 msg->data[i] = '.';
112             }
113         }
114         item = netboot_get_buffer((const char*)msg->data, msg->arg);
115         if (item) {
116             item->offset = 0;
117             ack.arg = msg->arg;
118             size_t prefix_len = strlen(NB_FILENAME_PREFIX);
119             const char* filename;
120             if (!strncmp((char*)msg->data, NB_FILENAME_PREFIX, prefix_len)) {
121                 filename = &((const char*)msg->data)[prefix_len];
122             } else {
123                 filename = (const char*)msg->data;
124             }
125             printf("netboot: Receive File '%s'...\n", filename);
126         } else {
127             printf("netboot: Rejected File '%s'...\n", (char*) msg->data);
128             ack.cmd = NB_ERROR_BAD_FILE;
129         }
130         break;
131 
132     case NB_DATA:
133     case NB_LAST_DATA:
134         if (item == 0) {
135             printf("netboot: > received chunk before NB_FILE\n");
136             return;
137         }
138         if (msg->arg != item->offset) {
139             // printf("netboot: < received chunk at offset %d but current offset is %zu\n", msg->arg, item->offset);
140             ack.arg = item->offset;
141             ack.cmd = NB_ACK;
142         } else if ((item->offset + len) > item->size) {
143             ack.cmd = NB_ERROR_TOO_LARGE;
144             ack.arg = msg->arg;
145         } else {
146             memcpy(item->data + item->offset, msg->data, len);
147             item->offset += len;
148             ack.cmd = msg->cmd == NB_LAST_DATA ? NB_FILE_RECEIVED : NB_ACK;
149             if (msg->cmd != NB_LAST_DATA) {
150                 do_transmit = 0;
151             }
152         }
153         break;
154     case NB_BOOT:
155         nb_boot_now = 1;
156         printf("netboot: Boot Kernel...\n");
157         break;
158     case NB_QUERY:
159         // Send reply and return w/o getting the netboot state out of sync.
160         send_query_ack(saddr, sport, msg->cookie);
161         return;
162     default:
163         ack.cmd = NB_ERROR_BAD_CMD;
164         ack.arg = 0;
165     }
166 
167     last_cookie = msg->cookie;
168     last_cmd = msg->cmd;
169     last_arg = msg->arg;
170     last_ack_cmd = ack.cmd;
171     last_ack_arg = ack.arg;
172 
173     ack.cookie = msg->cookie;
174     ack.magic = NB_MAGIC;
175 transmit:
176     nb_active = 1;
177     if (do_transmit) {
178         // printf("netboot: MSG %08x %08x %08x %08x\n",
179         //   ack.magic, ack.cookie, ack.cmd, ack.arg);
180 
181         udp6_send(&ack, sizeof(ack), saddr, sport, NB_SERVER_PORT);
182     }
183 }
184 
buffer_open(const char * filename,size_t size,void * cookie)185 static tftp_status buffer_open(const char* filename, size_t size, void* cookie) {
186     file_info_t* file_info = cookie;
187     file_info->netboot_file_data = netboot_get_buffer(filename, size);
188     if (file_info->netboot_file_data == NULL) {
189         printf("netboot: unrecognized file %s - rejecting\n", filename);
190         return TFTP_ERR_INVALID_ARGS;
191     }
192     file_info->netboot_file_data->offset = 0;
193     const char* base_filename;
194     size_t prefix_len = strlen(NB_FILENAME_PREFIX);
195     if (!strncmp(filename, NB_FILENAME_PREFIX, prefix_len)) {
196         base_filename = &filename[prefix_len];
197     } else {
198         base_filename = filename;
199     }
200     printf("Receiving %s [%lu bytes]... ", base_filename, (unsigned long)size);
201     file_info->file_size = size;
202     file_info->progress_reported = 0;
203     return TFTP_NO_ERROR;
204 }
205 
buffer_write(const void * data,size_t * len,off_t offset,void * cookie)206 static tftp_status buffer_write(const void* data, size_t* len, off_t offset, void* cookie) {
207     file_info_t* file_info = cookie;
208     nbfile* nb_buf_info = file_info->netboot_file_data;
209     if (offset > nb_buf_info->size || (offset + *len) > nb_buf_info->size) {
210         printf("netboot: attempt to write past end of buffer\n");
211         return TFTP_ERR_INVALID_ARGS;
212     }
213     memcpy(&nb_buf_info->data[offset], data, *len);
214     nb_buf_info->offset = offset + *len;
215     if (file_info->file_size >= 100) {
216         unsigned int progress_pct = offset / (file_info->file_size / 100);
217         if ((progress_pct > file_info->progress_reported) &&
218             (progress_pct - file_info->progress_reported >= 5)) {
219             printf("%u%%... ", progress_pct);
220             file_info->progress_reported = progress_pct;
221         }
222     }
223     return TFTP_NO_ERROR;
224 }
225 
buffer_close(void * cookie)226 static void buffer_close(void* cookie) {
227     file_info_t* file_info = cookie;
228     file_info->netboot_file_data = NULL;
229     printf("Done\n");
230 }
231 
udp_send(void * data,size_t len,void * cookie)232 static tftp_status udp_send(void* data, size_t len, void* cookie) {
233     transport_info_t* transport_info = cookie;
234     int bytes_sent = udp6_send(data, len, &transport_info->dest_addr, transport_info->dest_port,
235                                NB_TFTP_OUTGOING_PORT);
236     return bytes_sent < 0 ? TFTP_ERR_IO : TFTP_NO_ERROR;
237 }
238 
udp_timeout_set(uint32_t timeout_ms,void * cookie)239 static int udp_timeout_set(uint32_t timeout_ms, void* cookie) {
240     // TODO
241     return 0;
242 }
243 
strcmp8to16(const char * str8,const char16_t * str16)244 static int strcmp8to16(const char* str8, const char16_t* str16) {
245     while (*str8 != '\0' && *str8 == *str16) {
246         str8++;
247         str16++;
248     }
249     return *str8 - *str16;
250 }
251 
tftp_recv(void * data,size_t len,const ip6_addr * daddr,uint16_t dport,const ip6_addr * saddr,uint16_t sport)252 void tftp_recv(void* data, size_t len, const ip6_addr* daddr, uint16_t dport,
253                const ip6_addr* saddr, uint16_t sport) {
254     static tftp_session* session = NULL;
255     static file_info_t file_info = {.netboot_file_data = NULL};
256     static transport_info_t transport_info = {};
257 
258     if (dport == NB_TFTP_INCOMING_PORT) {
259         if (session != NULL) {
260             printf("Aborting to service new connection\n");
261         }
262         // Start TFTP session
263         int ret = tftp_init(&session, tftp_session_scratch, sizeof(tftp_session_scratch));
264         if (ret != TFTP_NO_ERROR) {
265             printf("netboot: failed to initiate tftp session\n");
266             session = NULL;
267             return;
268         }
269 
270         // Override our window size on the Acer tablet
271         if (!strcmp8to16("INSYDE Corp.", gSys->FirmwareVendor)) {
272             uint16_t window_size = 8;
273             tftp_set_options(session, NULL, NULL, &window_size);
274         }
275 
276         // Initialize file interface
277         tftp_file_interface file_ifc = {NULL, buffer_open, NULL, buffer_write, buffer_close};
278         tftp_session_set_file_interface(session, &file_ifc);
279 
280         // Initialize transport interface
281         memcpy(&transport_info.dest_addr, saddr, sizeof(struct ip6_addr_t));
282         transport_info.dest_port = sport;
283         tftp_transport_interface transport_ifc = {udp_send, NULL, udp_timeout_set};
284         tftp_session_set_transport_interface(session, &transport_ifc);
285     } else if (!session) {
286         // Ignore anything sent to the outgoing port unless we've already established a connection
287         return;
288     }
289 
290     size_t outlen = sizeof(tftp_out_scratch);
291 
292     char err_msg[128];
293     tftp_handler_opts handler_opts = {.inbuf = data,
294                                       .inbuf_sz = len,
295                                       .outbuf = tftp_out_scratch,
296                                       .outbuf_sz = &outlen,
297                                       .err_msg = err_msg,
298                                       .err_msg_sz = sizeof(err_msg)};
299     tftp_status status = tftp_handle_msg(session, &transport_info, &file_info, &handler_opts);
300     if (status < 0) {
301         printf("netboot: tftp protocol error: %s\n", err_msg);
302         session = NULL;
303     } else if (status == TFTP_TRANSFER_COMPLETED) {
304         session = NULL;
305     }
306 }
307 
308 #define FAST_TICK 100
309 #define SLOW_TICK 1000
310 
netboot_init(const char * nodename)311 int netboot_init(const char* nodename) {
312     if (netifc_open()) {
313         printf("netboot: Failed to open network interface\n");
314         return -1;
315     }
316     char buf[DEVICE_ID_MAX];
317     if (!nodename || (nodename[0] == 0)) {
318         device_id(eth_addr(), buf);
319         nodename = buf;
320     }
321     if (nodename) {
322         strncpy(advertise_nodename, nodename, sizeof(advertise_nodename) - 1);
323         snprintf(advertise_data, sizeof(advertise_data),
324                  "version=%s;nodename=%s", BOOTLOADER_VERSION, nodename);
325     }
326     return 0;
327 }
328 
netboot_nodename()329 const char* netboot_nodename() {
330     return advertise_nodename;
331 }
332 
333 static int nb_fastcount = 0;
334 static int nb_online = 0;
335 
netboot_poll(void)336 int netboot_poll(void) {
337     if (netifc_active()) {
338         if (nb_online == 0) {
339             printf("netboot: interface online\n");
340             nb_online = 1;
341             nb_fastcount = 20;
342             netifc_set_timer(FAST_TICK);
343             advertise();
344         }
345     } else {
346         if (nb_online == 1) {
347             printf("netboot: interface offline\n");
348             nb_online = 0;
349         }
350         return 0;
351     }
352     if (netifc_timer_expired()) {
353         if (nb_fastcount) {
354             nb_fastcount--;
355             netifc_set_timer(FAST_TICK);
356         } else {
357             netifc_set_timer(SLOW_TICK);
358         }
359         if (nb_active) {
360             // don't advertise if we're in a transfer
361             nb_active = 0;
362         } else {
363             advertise();
364         }
365     }
366 
367     netifc_poll();
368 
369     if (nb_boot_now) {
370         nb_boot_now = 0;
371         return 1;
372     } else {
373         return 0;
374     }
375 }
376 
netboot_close(void)377 void netboot_close(void) {
378     netifc_close();
379 }
380