1// Copyright 2017 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 <errno.h> 6#include <fcntl.h> 7#include <stddef.h> 8#include <stdio.h> 9#include <string.h> 10#include <threads.h> 11#include <unistd.h> 12 13#include <inet6/inet6.h> 14#include <lib/fdio/spawn.h> 15#include <lib/sync/completion.h> 16#include <tftp/tftp.h> 17#include <zircon/assert.h> 18#include <zircon/boot/netboot.h> 19#include <zircon/process.h> 20#include <zircon/status.h> 21#include <zircon/syscalls.h> 22#include <zircon/time.h> 23 24#include "netsvc.h" 25 26#define SCRATCHSZ 2048 27 28#define TFTP_TIMEOUT_SECS 1 29 30#define NB_IMAGE_PREFIX_LEN (strlen(NB_IMAGE_PREFIX)) 31#define NB_FILENAME_PREFIX_LEN (strlen(NB_FILENAME_PREFIX)) 32 33// Identifies what the file being streamed over TFTP should be 34// used for. 35typedef enum netfile_type { 36 netboot, // A bootfs file 37 paver, // A disk image which should be paved to disk 38} netfile_type_t; 39 40typedef struct { 41 bool is_write; 42 char filename[PATH_MAX + 1]; 43 netfile_type_t type; 44 union { 45 nbfile* netboot_file; 46 struct { 47 int fd; // Pipe to paver process 48 size_t size; // Total size of file 49 zx_handle_t process; 50 51 // Buffer used for stashing data from tftp until it can be written out to the paver 52 zx_handle_t buffer_handle; 53 uint8_t* buffer; 54 atomic_uint buf_refcount; 55 atomic_size_t offset; // Buffer write offset (read offset is stored locally) 56 thrd_t buf_copy_thrd; 57 sync_completion_t data_ready; // Allows read thread to block on buffer writes 58 } paver; 59 }; 60} file_info_t; 61 62typedef struct { 63 ip6_addr_t dest_addr; 64 uint16_t dest_port; 65 uint32_t timeout_ms; 66} transport_info_t; 67 68static char tftp_session_scratch[SCRATCHSZ]; 69char tftp_out_scratch[SCRATCHSZ]; 70 71static size_t last_msg_size = 0; 72static tftp_session* session = NULL; 73static file_info_t file_info; 74static transport_info_t transport_info; 75 76atomic_bool paving_in_progress = false; 77zx_time_t tftp_next_timeout = ZX_TIME_INFINITE; 78 79static ssize_t file_open_read(const char* filename, void* cookie) { 80 // Make sure all in-progress paving options have completed 81 if (atomic_load(&paving_in_progress) == true) { 82 return TFTP_ERR_SHOULD_WAIT; 83 } 84 file_info_t* file_info = cookie; 85 file_info->is_write = false; 86 strncpy(file_info->filename, filename, PATH_MAX); 87 file_info->filename[PATH_MAX] = '\0'; 88 file_info->netboot_file = NULL; 89 size_t file_size; 90 if (netfile_open(filename, O_RDONLY, &file_size) == 0) { 91 return (ssize_t)file_size; 92 } 93 return TFTP_ERR_NOT_FOUND; 94} 95 96static zx_status_t alloc_paver_buffer(file_info_t* file_info, size_t size) { 97 zx_status_t status; 98 status = zx_vmo_create(size, 0, &file_info->paver.buffer_handle); 99 if (status != ZX_OK) { 100 printf("netsvc: unable to allocate buffer VMO\n"); 101 return status; 102 } 103 zx_object_set_property(file_info->paver.buffer_handle, ZX_PROP_NAME, "paver", 5); 104 uintptr_t buffer; 105 status = zx_vmar_map(zx_vmar_root_self(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 106 0, file_info->paver.buffer_handle, 0, size, &buffer); 107 if (status != ZX_OK) { 108 printf("netsvc: unable to map buffer\n"); 109 zx_handle_close(file_info->paver.buffer_handle); 110 return status; 111 } 112 file_info->paver.buffer = (uint8_t*)buffer; 113 return ZX_OK; 114} 115 116static zx_status_t dealloc_paver_buffer(file_info_t* file_info) { 117 zx_status_t status = zx_vmar_unmap(zx_vmar_root_self(), (uintptr_t)file_info->paver.buffer, 118 file_info->paver.size); 119 if (status != ZX_OK) { 120 printf("netsvc: failed to unmap paver buffer: %s\n", zx_status_get_string(status)); 121 goto done; 122 } 123 124 status = zx_handle_close(file_info->paver.buffer_handle); 125 if (status != ZX_OK) { 126 printf("netsvc: failed to close paver buffer handle: %s\n", zx_status_get_string(status)); 127 } 128 129done: 130 file_info->paver.buffer = NULL; 131 return status; 132} 133 134static int drain_pipe(void* arg) { 135 char buf[4096]; 136 int fd = (uintptr_t)arg; 137 138 ssize_t sz; 139 while ((sz = read(fd, buf, sizeof(buf) - 1)) > 0) { 140 // ensure null termination 141 buf[sz] = '\0'; 142 printf("%s", buf); 143 } 144 145 close(fd); 146 return sz; 147} 148 149// Pushes all data from the paver buffer (filled by netsvc) into the paver input pipe. When 150// there's no data to copy, blocks on data_ready until more data is written into the buffer. 151static int paver_copy_buffer(void* arg) { 152 file_info_t* file_info = arg; 153 size_t read_ndx = 0; 154 int result = 0; 155 zx_time_t last_reported = zx_clock_get_monotonic(); 156 while (read_ndx < file_info->paver.size) { 157 sync_completion_reset(&file_info->paver.data_ready); 158 size_t write_ndx = atomic_load(&file_info->paver.offset); 159 if (write_ndx == read_ndx) { 160 // Wait for more data to be written -- we are allowed up to 3 tftp timeouts before 161 // a connection is dropped, so we should wait at least that long before giving up. 162 if (sync_completion_wait(&file_info->paver.data_ready, ZX_SEC(5 * TFTP_TIMEOUT_SECS)) 163 == ZX_OK) { 164 continue; 165 } 166 printf("netsvc: timed out while waiting for data in paver-copy thread\n"); 167 result = TFTP_ERR_TIMED_OUT; 168 goto done; 169 } 170 while(read_ndx < write_ndx) { 171 int r = write(file_info->paver.fd, &file_info->paver.buffer[read_ndx], 172 write_ndx - read_ndx); 173 if (r <= 0) { 174 printf("netsvc: couldn't write to paver fd: %d\n", r); 175 result = TFTP_ERR_IO; 176 goto done; 177 } 178 read_ndx += r; 179 zx_time_t curr_time = zx_clock_get_monotonic(); 180 if (zx_time_sub_time(curr_time, last_reported) >= ZX_SEC(1)) { 181 float complete = ((float)read_ndx / (float)file_info->paver.size) * 100.0; 182 printf("netsvc: paver write progress %0.1f%%\n", complete); 183 last_reported = curr_time; 184 } 185 } 186 } 187done: 188 close(file_info->paver.fd); 189 190 unsigned int refcount = atomic_fetch_sub(&file_info->paver.buf_refcount, 1); 191 if (refcount == 1) { 192 dealloc_paver_buffer(file_info); 193 } 194 // If all of the data has been written out to the paver process wait for it to complete 195 if (result == 0) { 196 zx_signals_t signals; 197 zx_object_wait_one(file_info->paver.process, ZX_TASK_TERMINATED, 198 zx_deadline_after(ZX_SEC(10)), &signals); 199 } 200 zx_handle_close(file_info->paver.process); 201 202 // Extra protection against double-close. 203 file_info->filename[0] = '\0'; 204 atomic_store(&paving_in_progress, false); 205 return result; 206} 207 208static tftp_status paver_open_write(const char* filename, size_t size, file_info_t* file_info) { 209 // paving an image to disk 210 const char* argv[] = {"/boot/bin/install-disk-image", NULL, NULL}; 211 212 if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_FVM_HOST_FILENAME)) { 213 printf("netsvc: Running FVM Paver\n"); 214 argv[1] = "install-fvm"; 215 } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_BOOTLOADER_HOST_FILENAME)) { 216 printf("netsvc: Running BOOTLOADER Paver\n"); 217 argv[1] = "install-bootloader"; 218 } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_EFI_HOST_FILENAME)) { 219 printf("netsvc: Running EFI Paver\n"); 220 argv[1] = "install-efi"; 221 } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_KERNC_HOST_FILENAME)) { 222 printf("netsvc: Running KERN-C Paver\n"); 223 argv[1] = "install-kernc"; 224 } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_ZIRCONA_HOST_FILENAME)) { 225 printf("netsvc: Running ZIRCON-A Paver\n"); 226 argv[1] = "install-zircona"; 227 } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_ZIRCONB_HOST_FILENAME)) { 228 printf("netsvc: Running ZIRCON-B Paver\n"); 229 argv[1] = "install-zirconb"; 230 } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_ZIRCONR_HOST_FILENAME)) { 231 printf("netsvc: Running ZIRCON-R Paver\n"); 232 argv[1] = "install-zirconr"; 233 } else { 234 fprintf(stderr, "netsvc: Unknown Paver\n"); 235 return TFTP_ERR_IO; 236 } 237 238 int fds[2]; 239 if (pipe(fds)) { 240 return TFTP_ERR_IO; 241 } 242 243 int logfds[2]; 244 if (pipe(logfds)) { 245 close(fds[0]); 246 close(fds[1]); 247 return TFTP_ERR_IO; 248 } 249 250 fdio_spawn_action_t actions[] = { 251 {.action = FDIO_SPAWN_ACTION_SET_NAME, .name = {.data = "paver"}}, 252 {.action = FDIO_SPAWN_ACTION_TRANSFER_FD, 253 .fd = {.local_fd = fds[0], .target_fd = STDIN_FILENO}}, 254 {.action = FDIO_SPAWN_ACTION_TRANSFER_FD, 255 .fd = {.local_fd = logfds[1], .target_fd = STDERR_FILENO}}, 256 }; 257 258 zx_status_t status = fdio_spawn_etc(ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL, 259 argv[0], argv, NULL, countof(actions), actions, 260 &file_info->paver.process, NULL); 261 262 if (status != ZX_OK) { 263 printf("netsvc: tftp couldn't launch paver\n"); 264 goto err_close_fds; 265 } 266 267 thrd_t log_thrd; 268 if ((thrd_create(&log_thrd, drain_pipe, (void*)(uintptr_t)logfds[0])) == thrd_success) { 269 thrd_detach(log_thrd); 270 } else { 271 printf("netsvc: couldn't create paver log message redirection thread\n"); 272 goto err_close_fds; 273 } 274 275 if ((status = alloc_paver_buffer(file_info, size)) != ZX_OK) { 276 goto err_close_fds; 277 } 278 279 file_info->type = paver; 280 file_info->paver.fd = fds[1]; 281 file_info->paver.size = size; 282 // Both the netsvc thread and the paver copy thread access the buffer, and either 283 // may be done with it first so we use a refcount to decide when to deallocate it 284 atomic_store(&file_info->paver.buf_refcount, 2); 285 atomic_store(&file_info->paver.offset, 0); 286 atomic_store(&paving_in_progress, true); 287 288 if ((thrd_create(&file_info->paver.buf_copy_thrd, paver_copy_buffer, (void*)file_info)) 289 != thrd_success) { 290 printf("netsvc: unable to launch buffer copy thread\n"); 291 status = ZX_ERR_NO_RESOURCES; 292 goto dealloc_buffer; 293 } 294 thrd_detach(file_info->paver.buf_copy_thrd); 295 296 return TFTP_NO_ERROR; 297 298dealloc_buffer: 299 dealloc_paver_buffer(file_info); 300 301err_close_fds: 302 close(fds[1]); 303 close(logfds[0]); 304 return status; 305} 306 307static tftp_status file_open_write(const char* filename, size_t size, 308 void* cookie) { 309 // Make sure all in-progress paving options have completed 310 if (atomic_load(&paving_in_progress) == true) { 311 return TFTP_ERR_SHOULD_WAIT; 312 } 313 file_info_t* file_info = cookie; 314 file_info->is_write = true; 315 strncpy(file_info->filename, filename, PATH_MAX); 316 file_info->filename[PATH_MAX] = '\0'; 317 318 if (netbootloader && !strncmp(filename, NB_FILENAME_PREFIX, NB_FILENAME_PREFIX_LEN)) { 319 // netboot 320 file_info->type = netboot; 321 file_info->netboot_file = netboot_get_buffer(filename, size); 322 if (file_info->netboot_file != NULL) { 323 return TFTP_NO_ERROR; 324 } 325 } else if (netbootloader & !strncmp(filename, NB_IMAGE_PREFIX, NB_IMAGE_PREFIX_LEN)) { 326 // paver 327 tftp_status status = paver_open_write(filename, size, file_info); 328 if (status != TFTP_NO_ERROR) { 329 file_info->filename[0] = '\0'; 330 } 331 return status; 332 } else { 333 // netcp 334 if (netfile_open(filename, O_WRONLY, NULL) == 0) { 335 return TFTP_NO_ERROR; 336 } 337 } 338 return TFTP_ERR_INVALID_ARGS; 339} 340 341static tftp_status file_read(void* data, size_t* length, off_t offset, void* cookie) { 342 if (length == NULL) { 343 return TFTP_ERR_INVALID_ARGS; 344 } 345 int read_len = netfile_offset_read(data, offset, *length); 346 if (read_len < 0) { 347 return TFTP_ERR_IO; 348 } 349 *length = read_len; 350 return TFTP_NO_ERROR; 351} 352 353static tftp_status file_write(const void* data, size_t* length, off_t offset, void* cookie) { 354 if (length == NULL) { 355 return TFTP_ERR_INVALID_ARGS; 356 } 357 file_info_t* file_info = cookie; 358 if (file_info->type == netboot && file_info->netboot_file != NULL) { 359 nbfile* nb_file = file_info->netboot_file; 360 if (((size_t)offset > nb_file->size) || (offset + *length) > nb_file->size) { 361 return TFTP_ERR_INVALID_ARGS; 362 } 363 memcpy(nb_file->data + offset, data, *length); 364 nb_file->offset = offset + *length; 365 return TFTP_NO_ERROR; 366 } else if (file_info->type == paver) { 367 if (!atomic_load(&paving_in_progress)) { 368 printf("netsvc: paver exited prematurely\n"); 369 return TFTP_ERR_IO; 370 } 371 372 if (((size_t)offset > file_info->paver.size) 373 || (offset + *length) > file_info->paver.size) { 374 return TFTP_ERR_INVALID_ARGS; 375 } 376 memcpy(&file_info->paver.buffer[offset], data, *length); 377 size_t new_offset = offset + *length; 378 atomic_store(&file_info->paver.offset, new_offset); 379 // Wake the paver thread, if it is waiting for data 380 sync_completion_signal(&file_info->paver.data_ready); 381 return TFTP_NO_ERROR; 382 } else { 383 int write_result = netfile_offset_write(data, offset, *length); 384 if ((size_t)write_result == *length) { 385 return TFTP_NO_ERROR; 386 } 387 if (write_result == -EBADF) { 388 return TFTP_ERR_BAD_STATE; 389 } 390 return TFTP_ERR_IO; 391 } 392} 393 394static void file_close(void* cookie) { 395 file_info_t* file_info = cookie; 396 if (file_info->type == netboot && file_info->netboot_file == NULL) { 397 netfile_close(); 398 } else if (file_info->type == paver) { 399 unsigned int refcount = atomic_fetch_sub(&file_info->paver.buf_refcount, 1); 400 if (refcount == 1) { 401 dealloc_paver_buffer(file_info); 402 } 403 } 404} 405 406static tftp_status transport_send(void* data, size_t len, void* transport_cookie) { 407 transport_info_t* transport_info = transport_cookie; 408 zx_status_t status = udp6_send(data, len, &transport_info->dest_addr, 409 transport_info->dest_port, NB_TFTP_OUTGOING_PORT, true); 410 if (status != ZX_OK) { 411 return TFTP_ERR_IO; 412 } 413 414 // The timeout is relative to sending instead of receiving a packet, since there are some 415 // received packets we want to ignore (duplicate ACKs). 416 if (transport_info->timeout_ms != 0) { 417 tftp_next_timeout = zx_deadline_after(ZX_MSEC(transport_info->timeout_ms)); 418 update_timeouts(); 419 } 420 return TFTP_NO_ERROR; 421} 422 423static int transport_timeout_set(uint32_t timeout_ms, void* transport_cookie) { 424 transport_info_t* transport_info = transport_cookie; 425 transport_info->timeout_ms = timeout_ms; 426 return 0; 427} 428 429static void initialize_connection(const ip6_addr_t* saddr, uint16_t sport) { 430 int ret = tftp_init(&session, tftp_session_scratch, 431 sizeof(tftp_session_scratch)); 432 if (ret != TFTP_NO_ERROR) { 433 printf("netsvc: failed to initiate tftp session\n"); 434 session = NULL; 435 return; 436 } 437 438 // Initialize file interface 439 tftp_file_interface file_ifc = {file_open_read, file_open_write, 440 file_read, file_write, file_close}; 441 tftp_session_set_file_interface(session, &file_ifc); 442 443 // Initialize transport interface 444 memcpy(&transport_info.dest_addr, saddr, sizeof(ip6_addr_t)); 445 transport_info.dest_port = sport; 446 transport_info.timeout_ms = TFTP_TIMEOUT_SECS * 1000; 447 tftp_transport_interface transport_ifc = {transport_send, NULL, transport_timeout_set}; 448 tftp_session_set_transport_interface(session, &transport_ifc); 449} 450 451static void end_connection(void) { 452 session = NULL; 453 tftp_next_timeout = ZX_TIME_INFINITE; 454} 455 456void tftp_timeout_expired(void) { 457 tftp_status result = tftp_timeout(session, tftp_out_scratch, &last_msg_size, 458 sizeof(tftp_out_scratch), &transport_info.timeout_ms, 459 &file_info); 460 if (result == TFTP_ERR_TIMED_OUT) { 461 printf("netsvc: excessive timeouts, dropping tftp connection\n"); 462 file_close(&file_info); 463 end_connection(); 464 netfile_abort_write(); 465 } else if (result < 0) { 466 printf("netsvc: failed to generate timeout response, dropping tftp connection\n"); 467 file_close(&file_info); 468 end_connection(); 469 netfile_abort_write(); 470 } else { 471 if (last_msg_size > 0) { 472 tftp_status send_result = transport_send(tftp_out_scratch, last_msg_size, 473 &transport_info); 474 if (send_result != TFTP_NO_ERROR) { 475 printf("netsvc: failed to send tftp timeout response (err = %d)\n", send_result); 476 } 477 } 478 } 479} 480 481void tftp_recv(void* data, size_t len, 482 const ip6_addr_t* daddr, uint16_t dport, 483 const ip6_addr_t* saddr, uint16_t sport) { 484 if (dport == NB_TFTP_INCOMING_PORT) { 485 if (session != NULL) { 486 printf("netsvc: only one simultaneous tftp session allowed\n"); 487 // ignore attempts to connect when a session is in progress 488 return; 489 } 490 initialize_connection(saddr, sport); 491 } else if (!session) { 492 // Ignore anything sent to the outgoing port unless we've already 493 // established a connection. 494 return; 495 } 496 497 last_msg_size = sizeof(tftp_out_scratch); 498 499 char err_msg[128]; 500 tftp_handler_opts handler_opts = {.inbuf = data, 501 .inbuf_sz = len, 502 .outbuf = tftp_out_scratch, 503 .outbuf_sz = &last_msg_size, 504 .err_msg = err_msg, 505 .err_msg_sz = sizeof(err_msg)}; 506 tftp_status status = tftp_handle_msg(session, &transport_info, &file_info, 507 &handler_opts); 508 switch (status) { 509 case TFTP_NO_ERROR: 510 return; 511 case TFTP_TRANSFER_COMPLETED: 512 printf("netsvc: tftp %s of file %s completed\n", 513 file_info.is_write ? "write" : "read", 514 file_info.filename); 515 break; 516 case TFTP_ERR_SHOULD_WAIT: 517 break; 518 default: 519 printf("netsvc: %s\n", err_msg); 520 netfile_abort_write(); 521 file_close(&file_info); 522 break; 523 } 524 end_connection(); 525} 526 527bool tftp_has_pending(void) { 528 return session && tftp_session_has_pending(session); 529} 530 531void tftp_send_next(void) { 532 last_msg_size = sizeof(tftp_out_scratch); 533 tftp_prepare_data(session, tftp_out_scratch, &last_msg_size, &transport_info.timeout_ms, 534 &file_info); 535 if (last_msg_size) { 536 transport_send(tftp_out_scratch, last_msg_size, &transport_info); 537 } 538} 539