1// SPDX-License-Identifier: GPL-2.0 2/* 3 * WGET/HTTP support driver based on U-BOOT's nfs.c 4 * Copyright Duncan Hare <dh@synoia.com> 2017 5 */ 6 7#include <asm/global_data.h> 8#include <command.h> 9#include <common.h> 10#include <display_options.h> 11#include <env.h> 12#include <image.h> 13#include <lmb.h> 14#include <mapmem.h> 15#include <net.h> 16#include <net/tcp.h> 17#include <net/wget.h> 18#include <stdlib.h> 19 20DECLARE_GLOBAL_DATA_PTR; 21 22/* The default, change with environment variable 'httpdstp' */ 23#define SERVER_PORT 80 24 25static const char bootfile1[] = "GET "; 26static const char bootfile3[] = " HTTP/1.0\r\n\r\n"; 27static const char http_eom[] = "\r\n\r\n"; 28static const char http_ok[] = "200"; 29static const char content_len[] = "Content-Length"; 30static const char linefeed[] = "\r\n"; 31static struct in_addr web_server_ip; 32static int our_port; 33static int wget_timeout_count; 34 35struct pkt_qd { 36 uchar *pkt; 37 unsigned int tcp_seq_num; 38 unsigned int len; 39}; 40 41/* 42 * This is a control structure for out of order packets received. 43 * The actual packet bufers are in the kernel space, and are 44 * expected to be overwritten by the downloaded image. 45 */ 46#define PKTQ_SZ (PKTBUFSRX / 4) 47static struct pkt_qd pkt_q[PKTQ_SZ]; 48static int pkt_q_idx; 49static unsigned long content_length; 50static unsigned int packets; 51 52static unsigned int initial_data_seq_num; 53static unsigned int next_data_seq_num; 54 55static enum wget_state current_wget_state; 56 57static char *image_url; 58static unsigned int wget_timeout = WGET_TIMEOUT; 59 60static enum net_loop_state wget_loop_state; 61 62/* Timeout retry parameters */ 63static u8 retry_action; /* actions for TCP retry */ 64static unsigned int retry_tcp_ack_num; /* TCP retry acknowledge number*/ 65static unsigned int retry_tcp_seq_num; /* TCP retry sequence number */ 66static int retry_len; /* TCP retry length */ 67 68static ulong wget_load_size; 69 70/** 71 * wget_init_max_size() - initialize maximum load size 72 * 73 * Return: 0 if success, -1 if fails 74 */ 75static int wget_init_load_size(void) 76{ 77 struct lmb lmb; 78 phys_size_t max_size; 79 80 lmb_init_and_reserve(&lmb, gd->bd, (void *)gd->fdt_blob); 81 82 max_size = lmb_get_free_size(&lmb, image_load_addr); 83 if (!max_size) 84 return -1; 85 86 wget_load_size = max_size; 87 88 return 0; 89} 90 91/** 92 * store_block() - store block in memory 93 * @src: source of data 94 * @offset: offset 95 * @len: length 96 */ 97static inline int store_block(uchar *src, unsigned int offset, unsigned int len) 98{ 99 ulong store_addr = image_load_addr + offset; 100 ulong newsize = offset + len; 101 uchar *ptr; 102 103 if (IS_ENABLED(CONFIG_LMB)) { 104 ulong end_addr = image_load_addr + wget_load_size; 105 106 if (!end_addr) 107 end_addr = ULONG_MAX; 108 109 if (store_addr < image_load_addr || 110 store_addr + len > end_addr) { 111 printf("\nwget error: "); 112 printf("trying to overwrite reserved memory...\n"); 113 return -1; 114 } 115 } 116 117 ptr = map_sysmem(store_addr, len); 118 memcpy(ptr, src, len); 119 unmap_sysmem(ptr); 120 121 if (net_boot_file_size < (offset + len)) 122 net_boot_file_size = newsize; 123 124 return 0; 125} 126 127/** 128 * wget_send_stored() - wget response dispatcher 129 * 130 * WARNING, This, and only this, is the place in wget.c where 131 * SEQUENCE NUMBERS are swapped between incoming (RX) 132 * and outgoing (TX). 133 * Procedure wget_handler() is correct for RX traffic. 134 */ 135static void wget_send_stored(void) 136{ 137 u8 action = retry_action; 138 int len = retry_len; 139 unsigned int tcp_ack_num = retry_tcp_seq_num + (len == 0 ? 1 : len); 140 unsigned int tcp_seq_num = retry_tcp_ack_num; 141 unsigned int server_port; 142 uchar *ptr, *offset; 143 144 server_port = env_get_ulong("httpdstp", 10, SERVER_PORT) & 0xffff; 145 146 switch (current_wget_state) { 147 case WGET_CLOSED: 148 debug_cond(DEBUG_WGET, "wget: send SYN\n"); 149 current_wget_state = WGET_CONNECTING; 150 net_send_tcp_packet(0, server_port, our_port, action, 151 tcp_seq_num, tcp_ack_num); 152 packets = 0; 153 break; 154 case WGET_CONNECTING: 155 pkt_q_idx = 0; 156 net_send_tcp_packet(0, server_port, our_port, action, 157 tcp_seq_num, tcp_ack_num); 158 159 ptr = net_tx_packet + net_eth_hdr_size() + 160 IP_TCP_HDR_SIZE + TCP_TSOPT_SIZE + 2; 161 offset = ptr; 162 163 memcpy(offset, &bootfile1, strlen(bootfile1)); 164 offset += strlen(bootfile1); 165 166 memcpy(offset, image_url, strlen(image_url)); 167 offset += strlen(image_url); 168 169 memcpy(offset, &bootfile3, strlen(bootfile3)); 170 offset += strlen(bootfile3); 171 net_send_tcp_packet((offset - ptr), server_port, our_port, 172 TCP_PUSH, tcp_seq_num, tcp_ack_num); 173 current_wget_state = WGET_CONNECTED; 174 break; 175 case WGET_CONNECTED: 176 case WGET_TRANSFERRING: 177 case WGET_TRANSFERRED: 178 net_send_tcp_packet(0, server_port, our_port, action, 179 tcp_seq_num, tcp_ack_num); 180 break; 181 } 182} 183 184static void wget_send(u8 action, unsigned int tcp_seq_num, 185 unsigned int tcp_ack_num, int len) 186{ 187 retry_action = action; 188 retry_tcp_ack_num = tcp_ack_num; 189 retry_tcp_seq_num = tcp_seq_num; 190 retry_len = len; 191 192 wget_send_stored(); 193} 194 195void wget_fail(char *error_message, unsigned int tcp_seq_num, 196 unsigned int tcp_ack_num, u8 action) 197{ 198 printf("wget: Transfer Fail - %s\n", error_message); 199 net_set_timeout_handler(0, NULL); 200 wget_send(action, tcp_seq_num, tcp_ack_num, 0); 201} 202 203void wget_success(u8 action, unsigned int tcp_seq_num, 204 unsigned int tcp_ack_num, int len, int packets) 205{ 206 printf("Packets received %d, Transfer Successful\n", packets); 207 wget_send(action, tcp_seq_num, tcp_ack_num, len); 208} 209 210/* 211 * Interfaces of U-BOOT 212 */ 213static void wget_timeout_handler(void) 214{ 215 if (++wget_timeout_count > WGET_RETRY_COUNT) { 216 puts("\nRetry count exceeded; starting again\n"); 217 wget_send(TCP_RST, 0, 0, 0); 218 net_start_again(); 219 } else { 220 puts("T "); 221 net_set_timeout_handler(wget_timeout + 222 WGET_TIMEOUT * wget_timeout_count, 223 wget_timeout_handler); 224 wget_send_stored(); 225 } 226} 227 228#define PKT_QUEUE_OFFSET 0x20000 229#define PKT_QUEUE_PACKET_SIZE 0x800 230 231static void wget_connected(uchar *pkt, unsigned int tcp_seq_num, 232 u8 action, unsigned int tcp_ack_num, unsigned int len) 233{ 234 uchar *pkt_in_q; 235 char *pos; 236 int hlen, i; 237 uchar *ptr1; 238 239 pkt[len] = '\0'; 240 pos = strstr((char *)pkt, http_eom); 241 242 if (!pos) { 243 debug_cond(DEBUG_WGET, 244 "wget: Connected, data before Header %p\n", pkt); 245 pkt_in_q = (void *)image_load_addr + PKT_QUEUE_OFFSET + 246 (pkt_q_idx * PKT_QUEUE_PACKET_SIZE); 247 248 ptr1 = map_sysmem((phys_addr_t)pkt_in_q, len); 249 memcpy(ptr1, pkt, len); 250 unmap_sysmem(ptr1); 251 252 pkt_q[pkt_q_idx].pkt = pkt_in_q; 253 pkt_q[pkt_q_idx].tcp_seq_num = tcp_seq_num; 254 pkt_q[pkt_q_idx].len = len; 255 pkt_q_idx++; 256 257 if (pkt_q_idx >= PKTQ_SZ) { 258 printf("wget: Fatal error, queue overrun!\n"); 259 net_set_state(NETLOOP_FAIL); 260 261 return; 262 } 263 } else { 264 debug_cond(DEBUG_WGET, "wget: Connected HTTP Header %p\n", pkt); 265 /* sizeof(http_eom) - 1 is the string length of (http_eom) */ 266 hlen = pos - (char *)pkt + sizeof(http_eom) - 1; 267 pos = strstr((char *)pkt, linefeed); 268 if (pos > 0) 269 i = pos - (char *)pkt; 270 else 271 i = hlen; 272 printf("%.*s", i, pkt); 273 274 current_wget_state = WGET_TRANSFERRING; 275 276 initial_data_seq_num = tcp_seq_num + hlen; 277 next_data_seq_num = tcp_seq_num + len; 278 279 if (strstr((char *)pkt, http_ok) == 0) { 280 debug_cond(DEBUG_WGET, 281 "wget: Connected Bad Xfer\n"); 282 wget_loop_state = NETLOOP_FAIL; 283 wget_send(action, tcp_seq_num, tcp_ack_num, len); 284 } else { 285 debug_cond(DEBUG_WGET, 286 "wget: Connctd pkt %p hlen %x\n", 287 pkt, hlen); 288 289 pos = strstr((char *)pkt, content_len); 290 if (!pos) { 291 content_length = -1; 292 } else { 293 pos += sizeof(content_len) + 2; 294 strict_strtoul(pos, 10, &content_length); 295 debug_cond(DEBUG_WGET, 296 "wget: Connected Len %lu\n", 297 content_length); 298 } 299 300 net_boot_file_size = 0; 301 302 if (len > hlen) { 303 if (store_block(pkt + hlen, 0, len - hlen) != 0) { 304 wget_loop_state = NETLOOP_FAIL; 305 wget_fail("wget: store error\n", tcp_seq_num, tcp_ack_num, action); 306 net_set_state(NETLOOP_FAIL); 307 return; 308 } 309 } 310 311 debug_cond(DEBUG_WGET, 312 "wget: Connected Pkt %p hlen %x\n", 313 pkt, hlen); 314 315 for (i = 0; i < pkt_q_idx; i++) { 316 int err; 317 318 ptr1 = map_sysmem( 319 (phys_addr_t)(pkt_q[i].pkt), 320 pkt_q[i].len); 321 err = store_block(ptr1, 322 pkt_q[i].tcp_seq_num - 323 initial_data_seq_num, 324 pkt_q[i].len); 325 unmap_sysmem(ptr1); 326 debug_cond(DEBUG_WGET, 327 "wget: Connctd pkt Q %p len %x\n", 328 pkt_q[i].pkt, pkt_q[i].len); 329 if (err) { 330 wget_loop_state = NETLOOP_FAIL; 331 wget_fail("wget: store error\n", tcp_seq_num, tcp_ack_num, action); 332 net_set_state(NETLOOP_FAIL); 333 return; 334 } 335 } 336 } 337 } 338 wget_send(action, tcp_seq_num, tcp_ack_num, len); 339} 340 341/** 342 * wget_handler() - TCP handler of wget 343 * @pkt: pointer to the application packet 344 * @dport: destination TCP port 345 * @sip: source IP address 346 * @sport: source TCP port 347 * @tcp_seq_num: TCP sequential number 348 * @tcp_ack_num: TCP acknowledgment number 349 * @action: TCP action (SYN, ACK, FIN, etc) 350 * @len: packet length 351 * 352 * In the "application push" invocation, the TCP header with all 353 * its information is pointed to by the packet pointer. 354 */ 355static void wget_handler(uchar *pkt, u16 dport, 356 struct in_addr sip, u16 sport, 357 u32 tcp_seq_num, u32 tcp_ack_num, 358 u8 action, unsigned int len) 359{ 360 enum tcp_state wget_tcp_state = tcp_get_tcp_state(); 361 362 net_set_timeout_handler(wget_timeout, wget_timeout_handler); 363 packets++; 364 365 switch (current_wget_state) { 366 case WGET_CLOSED: 367 debug_cond(DEBUG_WGET, "wget: Handler: Error!, State wrong\n"); 368 break; 369 case WGET_CONNECTING: 370 debug_cond(DEBUG_WGET, 371 "wget: Connecting In len=%x, Seq=%u, Ack=%u\n", 372 len, tcp_seq_num, tcp_ack_num); 373 if (!len) { 374 if (wget_tcp_state == TCP_ESTABLISHED) { 375 debug_cond(DEBUG_WGET, 376 "wget: Cting, send, len=%x\n", len); 377 wget_send(action, tcp_seq_num, tcp_ack_num, 378 len); 379 } else { 380 printf("%.*s", len, pkt); 381 wget_fail("wget: Handler Connected Fail\n", 382 tcp_seq_num, tcp_ack_num, action); 383 } 384 } 385 break; 386 case WGET_CONNECTED: 387 debug_cond(DEBUG_WGET, "wget: Connected seq=%u, len=%x\n", 388 tcp_seq_num, len); 389 if (!len) { 390 wget_fail("Image not found, no data returned\n", 391 tcp_seq_num, tcp_ack_num, action); 392 } else { 393 wget_connected(pkt, tcp_seq_num, action, tcp_ack_num, len); 394 } 395 break; 396 case WGET_TRANSFERRING: 397 debug_cond(DEBUG_WGET, 398 "wget: Transferring, seq=%x, ack=%x,len=%x\n", 399 tcp_seq_num, tcp_ack_num, len); 400 401 if (next_data_seq_num != tcp_seq_num) { 402 debug_cond(DEBUG_WGET, "wget: seq=%x packet was lost\n", next_data_seq_num); 403 return; 404 } 405 next_data_seq_num = tcp_seq_num + len; 406 407 if (store_block(pkt, tcp_seq_num - initial_data_seq_num, len) != 0) { 408 wget_fail("wget: store error\n", 409 tcp_seq_num, tcp_ack_num, action); 410 net_set_state(NETLOOP_FAIL); 411 return; 412 } 413 414 switch (wget_tcp_state) { 415 case TCP_FIN_WAIT_2: 416 wget_send(TCP_ACK, tcp_seq_num, tcp_ack_num, len); 417 fallthrough; 418 case TCP_SYN_SENT: 419 case TCP_SYN_RECEIVED: 420 case TCP_CLOSING: 421 case TCP_FIN_WAIT_1: 422 case TCP_CLOSED: 423 net_set_state(NETLOOP_FAIL); 424 break; 425 case TCP_ESTABLISHED: 426 wget_send(TCP_ACK, tcp_seq_num, tcp_ack_num, 427 len); 428 wget_loop_state = NETLOOP_SUCCESS; 429 break; 430 case TCP_CLOSE_WAIT: /* End of transfer */ 431 current_wget_state = WGET_TRANSFERRED; 432 wget_send(action | TCP_ACK | TCP_FIN, 433 tcp_seq_num, tcp_ack_num, len); 434 break; 435 } 436 break; 437 case WGET_TRANSFERRED: 438 printf("Packets received %d, Transfer Successful\n", packets); 439 net_set_state(wget_loop_state); 440 break; 441 } 442} 443 444#define RANDOM_PORT_START 1024 445#define RANDOM_PORT_RANGE 0x4000 446 447/** 448 * random_port() - make port a little random (1024-17407) 449 * 450 * Return: random port number from 1024 to 17407 451 * 452 * This keeps the math somewhat trivial to compute, and seems to work with 453 * all supported protocols/clients/servers 454 */ 455static unsigned int random_port(void) 456{ 457 return RANDOM_PORT_START + (get_timer(0) % RANDOM_PORT_RANGE); 458} 459 460#define BLOCKSIZE 512 461 462void wget_start(void) 463{ 464 image_url = strchr(net_boot_file_name, ':'); 465 if (image_url > 0) { 466 web_server_ip = string_to_ip(net_boot_file_name); 467 ++image_url; 468 net_server_ip = web_server_ip; 469 } else { 470 web_server_ip = net_server_ip; 471 image_url = net_boot_file_name; 472 } 473 474 debug_cond(DEBUG_WGET, 475 "wget: Transfer HTTP Server %pI4; our IP %pI4\n", 476 &web_server_ip, &net_ip); 477 478 /* Check if we need to send across this subnet */ 479 if (net_gateway.s_addr && net_netmask.s_addr) { 480 struct in_addr our_net; 481 struct in_addr server_net; 482 483 our_net.s_addr = net_ip.s_addr & net_netmask.s_addr; 484 server_net.s_addr = net_server_ip.s_addr & net_netmask.s_addr; 485 if (our_net.s_addr != server_net.s_addr) 486 debug_cond(DEBUG_WGET, 487 "wget: sending through gateway %pI4", 488 &net_gateway); 489 } 490 debug_cond(DEBUG_WGET, "URL '%s'\n", image_url); 491 492 if (net_boot_file_expected_size_in_blocks) { 493 debug_cond(DEBUG_WGET, "wget: Size is 0x%x Bytes = ", 494 net_boot_file_expected_size_in_blocks * BLOCKSIZE); 495 print_size(net_boot_file_expected_size_in_blocks * BLOCKSIZE, 496 ""); 497 } 498 debug_cond(DEBUG_WGET, 499 "\nwget:Load address: 0x%lx\nLoading: *\b", image_load_addr); 500 501 if (IS_ENABLED(CONFIG_LMB)) { 502 if (wget_init_load_size()) { 503 printf("\nwget error: "); 504 printf("trying to overwrite reserved memory...\n"); 505 net_set_state(NETLOOP_FAIL); 506 return; 507 } 508 } 509 510 net_set_timeout_handler(wget_timeout, wget_timeout_handler); 511 tcp_set_tcp_handler(wget_handler); 512 513 wget_timeout_count = 0; 514 current_wget_state = WGET_CLOSED; 515 516 our_port = random_port(); 517 518 /* 519 * Zero out server ether to force arp resolution in case 520 * the server ip for the previous u-boot command, for example dns 521 * is not the same as the web server ip. 522 */ 523 524 memset(net_server_ethaddr, 0, 6); 525 526 wget_send(TCP_SYN, 0, 0, 0); 527} 528 529#if (IS_ENABLED(CONFIG_CMD_DNS)) 530int wget_with_dns(ulong dst_addr, char *uri) 531{ 532 int ret; 533 char *s, *host_name, *file_name, *str_copy; 534 535 /* 536 * Download file using wget. 537 * 538 * U-Boot wget takes the target uri in this format. 539 * "<http server ip>:<file path>" e.g.) 192.168.1.1:/sample/test.iso 540 * Need to resolve the http server ip address before starting wget. 541 */ 542 str_copy = strdup(uri); 543 if (!str_copy) 544 return -ENOMEM; 545 546 s = str_copy + strlen("http://"); 547 host_name = strsep(&s, "/"); 548 if (!s) { 549 log_err("Error: invalied uri, no file path\n"); 550 ret = -EINVAL; 551 goto out; 552 } 553 file_name = s; 554 555 /* TODO: If the given uri has ip address for the http server, skip dns */ 556 net_dns_resolve = host_name; 557 net_dns_env_var = "httpserverip"; 558 if (net_loop(DNS) < 0) { 559 log_err("Error: dns lookup of %s failed, check setup\n", net_dns_resolve); 560 ret = -EINVAL; 561 goto out; 562 } 563 s = env_get("httpserverip"); 564 if (!s) { 565 ret = -EINVAL; 566 goto out; 567 } 568 569 strlcpy(net_boot_file_name, s, sizeof(net_boot_file_name)); 570 strlcat(net_boot_file_name, ":/", sizeof(net_boot_file_name)); /* append '/' which is removed by strsep() */ 571 strlcat(net_boot_file_name, file_name, sizeof(net_boot_file_name)); 572 image_load_addr = dst_addr; 573 ret = net_loop(WGET); 574 575out: 576 free(str_copy); 577 578 return ret; 579} 580#endif 581 582/** 583 * wget_validate_uri() - validate the uri for wget 584 * 585 * @uri: uri string 586 * 587 * This function follows the current U-Boot wget implementation. 588 * scheme: only "http:" is supported 589 * authority: 590 * - user information: not supported 591 * - host: supported 592 * - port: not supported(always use the default port) 593 * 594 * Uri is expected to be correctly percent encoded. 595 * This is the minimum check, control codes(0x1-0x19, 0x7F, except '\0') 596 * and space character(0x20) are not allowed. 597 * 598 * TODO: stricter uri conformance check 599 * 600 * Return: true on success, false on failure 601 */ 602bool wget_validate_uri(char *uri) 603{ 604 char c; 605 bool ret = true; 606 char *str_copy, *s, *authority; 607 608 for (c = 0x1; c < 0x21; c++) { 609 if (strchr(uri, c)) { 610 log_err("invalid character is used\n"); 611 return false; 612 } 613 } 614 if (strchr(uri, 0x7f)) { 615 log_err("invalid character is used\n"); 616 return false; 617 } 618 619 if (strncmp(uri, "http://", 7)) { 620 log_err("only http:// is supported\n"); 621 return false; 622 } 623 str_copy = strdup(uri); 624 if (!str_copy) 625 return false; 626 627 s = str_copy + strlen("http://"); 628 authority = strsep(&s, "/"); 629 if (!s) { 630 log_err("invalid uri, no file path\n"); 631 ret = false; 632 goto out; 633 } 634 s = strchr(authority, '@'); 635 if (s) { 636 log_err("user information is not supported\n"); 637 ret = false; 638 goto out; 639 } 640 s = strchr(authority, ':'); 641 if (s) { 642 log_err("user defined port is not supported\n"); 643 ret = false; 644 goto out; 645 } 646 647out: 648 free(str_copy); 649 650 return ret; 651} 652