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 17char tftp_session_scratch[TFTP_BUF_SZ]; 18char tftp_out_scratch[TFTP_BUF_SZ]; 19 20// item being downloaded 21static nbfile* item; 22 23// TFTP file state 24typedef 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 31typedef struct { 32 struct ip6_addr_t dest_addr; 33 uint16_t dest_port; 34} transport_info_t; 35 36static uint32_t last_cookie = 0; 37static uint32_t last_cmd = 0; 38static uint32_t last_arg = 0; 39static uint32_t last_ack_cmd = 0; 40static uint32_t last_ack_arg = 0; 41 42static int nb_boot_now = 0; 43static int nb_active = 0; 44 45static char advertise_nodename[64] = ""; 46static char advertise_data[256] = "nodename=zircon"; 47 48static 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 61static 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 74void 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 (int 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; 175transmit: 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 185static 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 206static 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 226static 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 232static 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 239static int udp_timeout_set(uint32_t timeout_ms, void* cookie) { 240 // TODO 241 return 0; 242} 243 244static 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 252void 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 311int 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 329const char* netboot_nodename() { 330 return advertise_nodename; 331} 332 333static int nb_fastcount = 0; 334static int nb_online = 0; 335 336int 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 377void netboot_close(void) { 378 netifc_close(); 379} 380