Apply netretry and interface rotation after TFTP exhausts its packet retries. Like the legacy stack, also restart after a peer error other than file-not-found or access-denied. Distinguish peer errors from local option and memory-write failures so invalid input is not retried.
Retire the failed transfer before restarting the shared runtime. Rebuild each attempt from the original address and size, recalculate automatic filenames for a replacement interface, and leave another client's runtime intact if exclusive restart is refused. Keep Ctrl-C, initialization and metadata publication failures terminal. Retry initial route and device-send failures with the request timer. Invalid requests and setup allocation failures remain terminal. Apply the same policy to receive-server listening and transfer timeouts, retaining its compiled timeout and retry defaults. A partial upload must not advance the next attempt's cursor or byte count. Cover bounded retry policies, peer errors, packet retries, partial transfers, interface rotation and shared-runtime refusal in sandbox. Signed-off-by: James Hilliard <[email protected]> --- doc/usage/cmd/tftpsrv.rst | 4 +- lib/lwip/lwip/src/apps/tftp/tftp.c | 21 +- lib/lwip/lwip/src/include/lwip/apps/tftp_common.h | 16 +- net/lwip/tftp.c | 75 ++-- test/dm/lwip_tftp.c | 73 +++- test/dm/tftp.c | 403 ++++++++++++++++++++-- 6 files changed, 517 insertions(+), 75 deletions(-) diff --git a/doc/usage/cmd/tftpsrv.rst b/doc/usage/cmd/tftpsrv.rst index 2c6d08cdc80..c71702953ca 100644 --- a/doc/usage/cmd/tftpsrv.rst +++ b/doc/usage/cmd/tftpsrv.rst @@ -30,8 +30,8 @@ file is passed to bootm after the transfer completes. Otherwise, the command returns without booting the file; boot scripts can use commands such as bootm, booti or bootefi to boot from the load address. -The transfer is aborted if no transfer has started after about 50 seconds or -if Ctrl-C is pressed. +If no transfer starts after about 50 seconds, *netretry* controls whether +the command retries or aborts. Ctrl-C always aborts the command. Example ------- diff --git a/lib/lwip/lwip/src/apps/tftp/tftp.c b/lib/lwip/lwip/src/apps/tftp/tftp.c index a2c82ababd9..e742273bee1 100644 --- a/lib/lwip/lwip/src/apps/tftp/tftp.c +++ b/lib/lwip/lwip/src/apps/tftp/tftp.c @@ -67,17 +67,6 @@ #define TFTP_ERROR 5 #define TFTP_OACK 6 -enum tftp_error { - TFTP_ERROR_FILE_NOT_FOUND = 1, - TFTP_ERROR_ACCESS_VIOLATION = 2, - TFTP_ERROR_DISK_FULL = 3, - TFTP_ERROR_ILLEGAL_OPERATION = 4, - TFTP_ERROR_UNKNOWN_TRFR_ID = 5, - TFTP_ERROR_FILE_EXISTS = 6, - TFTP_ERROR_NO_SUCH_USER = 7, - TFTP_ERROR_OPTION_NEGOTIATION = 8 -}; - #include <string.h> struct tftp_req { @@ -181,7 +170,7 @@ static void transfer_timed_out(void) { LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: timeout\n")); - tftp_state.ctx->error(tftp_state.handle, -1, tftp_timeout_msg, + tftp_state.ctx->error(tftp_state.handle, TFTP_ERROR_TIMEOUT, tftp_timeout_msg, sizeof(tftp_timeout_msg) - 1); close_handle(); } @@ -295,7 +284,7 @@ send_error_and_close(const ip_addr_t *addr, u16_t port, { send_error(addr, port, code, str); if (tftp_state.handle != NULL) - tftp_state.ctx->error(tftp_state.handle, code, str, strlen(str)); + tftp_state.ctx->error(tftp_state.handle, TFTP_ERROR_LOCAL, str, strlen(str)); close_handle(); } @@ -1087,7 +1076,11 @@ start_send_requests(const ip_addr_t *addr, u16_t port, u16_t opcode, const char* tftp_state.port = 0; tftp_state.retries = 0; ret = resend_request(); - if (ret != ERR_OK) { + /* A route or device send failure must not bypass the request retry timer. + * Keep allocation and invalid-request errors terminal at setup time. + */ + if ((ret != ERR_OK) && (ret != ERR_RTE) && + (ret != ERR_IF) && (ret != ERR_ABRT)) { clear_req(); ip_addr_set_any(0, &tftp_state.addr); tftp_state.handle = NULL; diff --git a/lib/lwip/lwip/src/include/lwip/apps/tftp_common.h b/lib/lwip/lwip/src/include/lwip/apps/tftp_common.h index 092e7d4059a..4445f551166 100644 --- a/lib/lwip/lwip/src/include/lwip/apps/tftp_common.h +++ b/lib/lwip/lwip/src/include/lwip/apps/tftp_common.h @@ -50,6 +50,20 @@ extern "C" { #endif +/* Negative callback errors are not TFTP wire error codes. */ +enum tftp_error { + TFTP_ERROR_LOCAL = -2, + TFTP_ERROR_TIMEOUT = -1, + TFTP_ERROR_FILE_NOT_FOUND = 1, + TFTP_ERROR_ACCESS_VIOLATION = 2, + TFTP_ERROR_DISK_FULL = 3, + TFTP_ERROR_ILLEGAL_OPERATION = 4, + TFTP_ERROR_UNKNOWN_TRFR_ID = 5, + TFTP_ERROR_FILE_EXISTS = 6, + TFTP_ERROR_NO_SUCH_USER = 7, + TFTP_ERROR_OPTION_NEGOTIATION = 8 +}; + /** @ingroup tftp * TFTP context containing callback functions for TFTP transfers */ @@ -87,7 +101,7 @@ struct tftp_context { /** * Error indication from client or response from server * @param handle File handle set by open()/tftp_get()/tftp_put() - * @param err error code from client or server + * @param err A negative tftp_error value, or the peer's wire error code * @param msg error message from client or server * @param size size of msg */ diff --git a/net/lwip/tftp.c b/net/lwip/tftp.c index 41ea12ed486..9e19857d19b 100644 --- a/net/lwip/tftp.c +++ b/net/lwip/tftp.c @@ -46,6 +46,7 @@ struct tftp_ctx { bool upload; bool wrq_accepted; u32 listen_retries; + bool retry; char fname[TFTP_MAX_FILENAME_LEN + 1]; }; @@ -188,7 +189,7 @@ static void tftp_close(void *handle) static int tftp_complete(struct tftp_ctx *ctx, ulong addr, const char *fname) { if (ctx->done != SUCCESS) - return -EIO; + return ctx->done == FAILURE && ctx->retry ? -EAGAIN : -EIO; if (!ctx->upload && env_set_hex("fileaddr", addr)) { log_err("fileaddr not updated\n"); return -EIO; @@ -252,6 +253,12 @@ static void tftp_error(void *handle, int err, const char *msg, int size) char message[100]; ctx->done = FAILURE; + /* Match the legacy client's server-error and retry-exhaustion policy. + * Local validation and memory-write failures must not be retried. + */ + ctx->retry = err == TFTP_ERROR_TIMEOUT || + (err >= 0 && err != TFTP_ERROR_FILE_NOT_FOUND && + err != TFTP_ERROR_ACCESS_VIOLATION); memset(message, 0, sizeof(message)); memcpy(message, msg, LWIP_MIN(sizeof(message) - 1, (size_t)size)); @@ -480,6 +487,7 @@ static void no_request(void *arg) printf("Timeout!\n"); ctx->done = FAILURE; + ctx->retry = true; } static int tftpsrv_loop(struct net_lwip_ctx *net, ulong addr) @@ -572,10 +580,20 @@ int do_tftpsrv(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) goto out; } - if (tftpsrv_loop(&net, laddr) < 0) - ret = CMD_RET_FAILURE; - else + net_try_count = 1; + net_restart_wrap = 0; + for (;;) { + ret = tftpsrv_loop(&net, laddr); + if (ret != -EAGAIN) + break; + puts("Starting again\n\n"); + ret = net_lwip_restart(&net); + if (ret) + break; + } + if (!ret) image_load_addr = laddr; + ret = ret ? CMD_RET_FAILURE : CMD_RET_SUCCESS; out: net_lwip_stop(&net); @@ -594,10 +612,10 @@ static void tftp_default_filename(struct net_lwip_ctx *net, char *fname, printf("*** Warning: no boot file name; using '%s'\n", fname); } -static int do_tftp(struct cmd_tbl *cmdtp, int flag, int argc, - char *const argv[], enum tftp_operation operation) +static int do_tftp(int argc, char *const argv[], enum tftp_operation operation) { - struct net_lwip_ctx net = {}; + struct net_lwip_ctx local = {}; + struct net_lwip_ctx *net = &local; u32 timeout_msecs = UBOOT_TFTP_TIMEOUT_MS; u32 max_retries = UBOOT_TFTP_MAX_RETRIES; int ret = CMD_RET_SUCCESS; @@ -767,27 +785,38 @@ static int do_tftp(struct cmd_tbl *cmdtp, int flag, int argc, goto out; } - if (net_lwip_start(&net, NET_LWIP_ADDR_ENV_STRICT)) { + if (net_lwip_start(net, NET_LWIP_ADDR_ENV_STRICT)) { ret = CMD_RET_FAILURE; goto out; } + net_try_count = 1; + net_restart_wrap = 0; - if (tftp_check_ip(&net)) { - ret = CMD_RET_FAILURE; - goto out; - } - if (!fname || !*fname) { - tftp_default_filename(&net, default_fname, sizeof(default_fname)); - fname = default_fname; + for (;;) { + ret = tftp_check_ip(net); + if (ret) + break; + /* Rotation may select an interface with a different IP address. */ + if (!fname || !*fname) + tftp_default_filename(net, default_fname, sizeof(default_fname)); + ret = tftp_loop(net, operation, laddr, size, + fname && *fname ? fname : default_fname, srvip, + port, srcport, timeout_msecs, max_retries); + if (ret != -EAGAIN) + break; + /* The failed transfer's PCB and timers are already gone. Never + * change interfaces underneath another shared-runtime client. + */ + puts("Starting again\n\n"); + ret = net_lwip_restart(net); + if (ret) + break; } - - if (tftp_loop(&net, operation, laddr, size, fname, srvip, - port, srcport, timeout_msecs, max_retries) < 0) - ret = CMD_RET_FAILURE; - else if (operation == TFTP_DOWNLOAD) + if (!ret && operation == TFTP_DOWNLOAD) image_load_addr = laddr; + ret = ret ? CMD_RET_FAILURE : CMD_RET_SUCCESS; out: - net_lwip_stop(&net); + net_lwip_stop(net); free(arg_copy); return ret; } @@ -796,7 +825,7 @@ int do_tftpb(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { int ret; - ret = do_tftp(cmdtp, flag, argc, argv, TFTP_DOWNLOAD); + ret = do_tftp(argc, argv, TFTP_DOWNLOAD); if (ret || !net_boot_file_size) return ret; /* The transfer's callbacks and runtime attachment are already gone. */ @@ -805,5 +834,5 @@ int do_tftpb(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) int do_tftpput(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { - return do_tftp(cmdtp, flag, argc, argv, TFTP_UPLOAD); + return do_tftp(argc, argv, TFTP_UPLOAD); } diff --git a/test/dm/lwip_tftp.c b/test/dm/lwip_tftp.c index 7d84120ab4c..66f43e9c553 100644 --- a/test/dm/lwip_tftp.c +++ b/test/dm/lwip_tftp.c @@ -30,6 +30,7 @@ struct tftp_protocol_test { unsigned int server_phase; int error; int read_error; + err_t send_error; bool initialized; bool fail_data_alloc; bool window_requested; @@ -73,7 +74,7 @@ static err_t tftp_test_output(struct netif *netif, struct pbuf *p, offset + sizeof("timeout")); } test->sent++; - return ERR_OK; + return test->send_error; } static void tftp_test_close(void *handle) @@ -200,7 +201,7 @@ static int tftp_test_oack(struct unit_test_state *uts, ut_assertok(tftp_test_start(test, false)); memcpy(packet + 2, invalid[i], lengths[i]); ut_assertok(tftp_test_input(test, packet, lengths[i] + 2, 0)); - ut_asserteq(8, test->error); + ut_asserteq(TFTP_ERROR_LOCAL, test->error); ut_asserteq(1, test->closed); ut_asserteq(5, test->opcode); } @@ -218,7 +219,7 @@ static int tftp_test_oack(struct unit_test_state *uts, /* A write request without tsize must reject an unsolicited tsize. */ ut_assertok(tftp_test_start(test, true)); ut_assertok(tftp_test_input(test, valid, sizeof(valid), 0)); - ut_asserteq(8, test->error); + ut_asserteq(TFTP_ERROR_LOCAL, test->error); return 0; } @@ -314,7 +315,7 @@ static int tftp_test_data_alloc_retry(struct unit_test_state *uts, test->read_error = -EIO; timer_test_add_offset(40); sys_check_timeouts(); - ut_asserteq(2, test->error); + ut_asserteq(TFTP_ERROR_LOCAL, test->error); ut_asserteq(1, test->closed); ut_asserteq(1, test->reads); sent = test->sent; @@ -474,12 +475,66 @@ static int tftp_test_stale_control(struct unit_test_state *uts, ut_assertok(tftp_test_input(test, invalid, sizeof(invalid), 0)); ut_asserteq(5, test->opcode); ut_asserteq(8, test->block); - ut_asserteq(8, test->error); + ut_asserteq(TFTP_ERROR_LOCAL, test->error); ut_asserteq(1, test->closed); } return 0; } +static int tftp_test_request_send_errors(struct unit_test_state *uts, + struct tftp_protocol_test *test) +{ + static const err_t errors[] = { ERR_RTE, ERR_IF, ERR_ABRT, ERR_MEM, ERR_ARG }; + static const u8 data[] = { 0, 3, 0, 1, 'x' }; + static const u8 ack[] = { 0, 4, 0, 0 }; + int i, put, exhaust; + err_t ret; + + for (put = 0; put < 2; put++) { + for (exhaust = 0; exhaust < 2; exhaust++) { + for (i = 0; i < ARRAY_SIZE(errors); i++) { + if (test->initialized) + tftp_cleanup(); + memset(test, 0, sizeof(*test)); + ipaddr_aton("1.1.2.2", &test->server); + ut_assertok(tftp_init_client(&tftp_test_callbacks)); + test->initialized = true; + test->send_error = errors[i]; + ut_assertok(tftp_set_timeout(30, 1)); + if (put) + ret = tftp_put(test, &test->server, 69, "file", + TFTP_MODE_OCTET); + else + ret = tftp_get(test, &test->server, 69, "file", + TFTP_MODE_OCTET); + ut_asserteq(i < 3 ? ERR_OK : errors[i], ret); + ut_asserteq(1, test->sent); + if (!exhaust) + test->send_error = ERR_OK; + timer_test_add_offset(31); + sys_check_timeouts(); + ut_asserteq(i < 3 ? 2 : 1, test->sent); + ut_asserteq(0, test->error); + if (i >= 3) + continue; + if (exhaust) { + timer_test_add_offset(31); + sys_check_timeouts(); + ut_asserteq(TFTP_ERROR_TIMEOUT, test->error); + ut_asserteq(1, test->closed); + } else { + ret = tftp_test_input(test, put ? ack : data, + put ? sizeof(ack) : sizeof(data), 0); + ut_assertok(ret); + ut_asserteq(0, test->error); + ut_asserteq(put ? 3 : 4, test->opcode); + } + } + } + } + return 0; +} + static int tftp_test_timeout_range(struct unit_test_state *uts, struct tftp_protocol_test *test) { @@ -549,7 +604,7 @@ static int tftp_test_timeout_option(struct unit_test_state *uts, /* Reject unrequested or non-echoed timeout values. */ ut_assertok(tftp_test_input(test, bad, sizeof(bad), 0)); } - ut_asserteq(8, test->error); + ut_asserteq(TFTP_ERROR_LOCAL, test->error); ut_asserteq(1, test->closed); } } @@ -641,7 +696,7 @@ static int tftp_test_windows(struct unit_test_state *uts, ut_assertok(tftp_test_start_window(test, false, 4)); ut_assert(test->window_requested); ut_assertok(tftp_test_input(test, bad[i], bad_len[i], 0)); - ut_asserteq(8, test->error); + ut_asserteq(TFTP_ERROR_LOCAL, test->error); ut_asserteq(1, test->closed); } ut_assertok(tftp_test_start_window(test, false, 4)); @@ -706,7 +761,7 @@ static int tftp_test_windows(struct unit_test_state *uts, ut_assertok(tftp_test_start_window(test, false, 1)); ut_assert(!test->window_requested); ut_assertok(tftp_test_input(test, one, sizeof(one), 0)); - ut_asserteq(8, test->error); /* Unrequested option. */ + ut_asserteq(TFTP_ERROR_LOCAL, test->error); /* Unrequested option. */ ut_assertok(tftp_test_start_window(test, false, 65535)); ut_assertok(tftp_test_input(test, rollover, sizeof(rollover), 0)); @@ -872,6 +927,8 @@ static int dm_test_lwip_tftp_protocol(struct unit_test_state *uts) ret = tftp_test_duplicate_oack(uts, &test); if (!ret) ret = tftp_test_stale_control(uts, &test); + if (!ret) + ret = tftp_test_request_send_errors(uts, &test); if (!ret) ret = tftp_test_timeout_range(uts, &test); if (!ret) diff --git a/test/dm/tftp.c b/test/dm/tftp.c index 62a0a887150..e5589b19f27 100644 --- a/test/dm/tftp.c +++ b/test/dm/tftp.c @@ -46,11 +46,28 @@ struct tftp_test_context { ulong ack_start; ulong ack_delay; unsigned int advertised_timeout; + char filename[64]; + char first_filename[64]; struct in_addr local_ip; + bool default_filename; u16 blocksize; u16 windowsize; u16 port; + unsigned int drop_requests; + unsigned int send_errors; + unsigned int error_requests; + u16 error_code; + bool invalid_oack; + bool partial; + unsigned int server_ticks; + unsigned int server_attempt; + unsigned int server_phase; + unsigned int server_acks; + unsigned int failed_attempts; + unsigned int server_case; + int server_error; + unsigned int timeout_ticks; }; static ulong tftp_test_option(const char *packet, size_t len, const char *option) @@ -96,7 +113,8 @@ static int tftp_test_tx_handler(struct udevice *dev, void *packet, struct tftp_test_context *ctx = priv->priv; struct ethernet_hdr *eth = packet, *reply_eth; struct ip_udp_hdr *ip, *reply_ip; - const char *data = ctx->fail ? "not found" : tftp_test_data; + bool fail = ctx->fail || ctx->error_requests; + const char *data = fail ? "error" : tftp_test_data; unsigned int data_len = ctx->empty ? 0 : strlen(data) + 1; unsigned int reply_len, header_len = 4; u16 opcode, reply_opcode = 3, block = 1; @@ -133,6 +151,10 @@ static int tftp_test_tx_handler(struct udevice *dev, void *packet, if (ip->ip_p != IPPROTO_UDP) return 0; opcode = get_unaligned_be16(ip + 1); + if (opcode == 4 && ctx->probe == TFTP_PROBE_NONE) { + ctx->server_acks++; + return 0; + } if (opcode == 4 && ctx->probe >= TFTP_PROBE_DATA) { ctx->probe_acks++; if (ctx->probe_acks == 1) { @@ -148,7 +170,13 @@ static int tftp_test_tx_handler(struct udevice *dev, void *packet, if (!ctx->requests++) ctx->request_start = get_timer(0); + if (ctx->send_errors) { + ctx->send_errors--; + return -EIO; + } strlcpy(ctx->filename, (char *)(ip + 1) + 2, sizeof(ctx->filename)); + if (ctx->requests == 1) + strlcpy(ctx->first_filename, ctx->filename, sizeof(ctx->first_filename)); ctx->local_ip = ip->ip_src; ctx->blocksize = tftp_test_option((char *)(ip + 1), request_len, "blksize"); ctx->windowsize = tftp_test_option((char *)(ip + 1), request_len, "windowsize"); @@ -169,19 +197,32 @@ static int tftp_test_tx_handler(struct udevice *dev, void *packet, "timeout\0" "1" : "timeout\0" "5"; data_len = 10; } + if (ctx->requests <= ctx->drop_requests) + return 0; + if (ctx->error_requests) + ctx->error_requests--; if (opcode == 2) { reply_opcode = 4; block = 0; data_len = 0; + } else if (ctx->partial && ctx->requests == 1) { + partial = true; + data_len = 512; } } else if (ntohs(ip->udp_dst) == 1234 && opcode == 3 && len >= ETHER_HDR_SIZE + IP_UDP_HDR_SIZE + 4) { reply_opcode = 4; block = get_unaligned_be16((u8 *)(ip + 1) + 2); data_len = 0; + if (ctx->partial && ctx->requests == 1) + return 0; } else { return 0; } + if (fail) + block = ctx->fail ? 1 : ctx->error_code; + if (ctx->invalid_oack) + data_len = 0; reply_len = ETHER_HDR_SIZE + IP_UDP_HDR_SIZE + header_len + data_len; if (priv->recv_packets >= PKTBUFSRX) return -ENOSPC; @@ -205,7 +246,7 @@ static int tftp_test_tx_handler(struct udevice *dev, void *packet, reply_ip->udp_len = htons(UDP_HDR_SIZE + header_len + data_len); reply_ip->ip_sum = compute_ip_checksum(reply_ip, IP_HDR_SIZE); payload = (void *)(reply_ip + 1); - put_unaligned_be16(ctx->fail ? 5 : reply_opcode, payload); + put_unaligned_be16(ctx->invalid_oack ? 6 : fail ? 5 : reply_opcode, payload); if (header_len == 4) put_unaligned_be16(block, payload + 2); if (partial) @@ -409,39 +450,281 @@ static int tftp_test_options(struct unit_test_state *uts, } #endif -static int tftp_test_protected_autostart(struct unit_test_state *uts, - struct tftp_test_context *ctx) +#if IS_ENABLED(CONFIG_NET_LWIP) +static void tftp_test_tick(void *arg) +{ + struct tftp_test_context *ctx = arg; + + ctx->timeout_ticks++; + timer_test_add_offset(5001); + sys_timeout(1, tftp_test_tick, arg); +} + +static int tftp_test_retry_command(struct tftp_test_context *ctx, bool put) +{ + int ret; + + sys_timeout(1, tftp_test_tick, ctx); + if (ctx->default_filename) + ret = run_command(put ? "tftpput 2000000 20" : "tftpboot 2000000", 0); + else + ret = run_command(put ? "tftpput 2000000 20 update.bin" : + "tftpboot 2000000 update.bin", 0); + sys_untimeout(tftp_test_tick, ctx); + return ret; +} + +static int tftp_test_restarts(struct unit_test_state *uts, + struct tftp_test_context *ctx) { + static const char * const policies[] = { NULL, "no", "once", "2", "yes" }; + struct net_lwip_ctx shared = {}; char * const argv[] = { "tftpboot", "2000000", "update.bin" }; - int enabled, helper, ret; + int i, put, ret; - for (enabled = 0; enabled < 2; enabled++) { - ut_assertok(env_set("autostart", enabled ? "yes" : "no")); - ut_assertok(env_set(".flags", "autostart:br")); - for (helper = 0; helper < 3; helper++) { + ut_assertok(env_set("autostart", "no")); + ut_assertok(env_set("ethrotate", "no")); + ut_assertok(env_set("tftptimeout", NULL)); + ut_assertok(env_set("tftptimeoutcountmax", NULL)); + ut_assertok(env_set("netretry", "no")); + memset(ctx, 0, sizeof(*ctx)); + ctx->drop_requests = UINT_MAX; + ut_asserteq(CMD_RET_FAILURE, tftp_test_retry_command(ctx, false)); + ut_asserteq(CONFIG_NET_RETRY_COUNT * 2 + 1, ctx->requests); + ut_asserteq(0, net_boot_file_size); + + ut_assertok(env_set("tftptimeout", "1000")); + ut_assertok(env_set("tftptimeoutcountmax", "0")); + for (put = 0; put <= IS_ENABLED(CONFIG_CMD_TFTPPUT); put++) { + for (i = 0; i < ARRAY_SIZE(policies); i++) { memset(ctx, 0, sizeof(*ctx)); - switch (helper) { - case 0: - ret = tftpb_run(TFTP_TEST_ADDR, "update.bin"); - break; - case 1: - ret = tftpb_run_timeout(TFTP_TEST_ADDR, "update.bin", 100, 0); - break; - default: - ret = tftpb_run_argv(3, argv); - } - /* Do not override protection when suppression is required. */ - ut_asserteq(enabled, !!ret); - ut_asserteq(!enabled, ctx->requests); - ut_assert(!ctx->autostart); - ut_asserteq_str(enabled ? "yes" : "no", env_get("autostart")); + ctx->drop_requests = 2; + ut_assertok(env_set("netretry", policies[i])); + ret = tftp_test_retry_command(ctx, put); + ut_asserteq(i < 3 ? CMD_RET_FAILURE : CMD_RET_SUCCESS, ret); + ut_asserteq(i < 2 ? 1 : i == 2 ? 2 : 3, ctx->requests); + ut_asserteq(-ENODEV, net_lwip_poll()); } - ut_assertok(env_set(".flags", NULL)); + for (i = 0; i <= 8; i++) { + memset(ctx, 0, sizeof(*ctx)); + ctx->error_requests = 1; + ctx->error_code = i; + ut_assertok(env_set("netretry", "once")); + ret = tftp_test_retry_command(ctx, put); + ut_asserteq(i == 1 || i == 2 ? CMD_RET_FAILURE : CMD_RET_SUCCESS, ret); + ut_asserteq(i == 1 || i == 2 ? 1 : 2, ctx->requests); + } + /* Exhaust packet retries after a device send failure, then restart. */ + for (i = 0; i < 2; i++) { + memset(ctx, 0, sizeof(*ctx)); + ctx->send_errors = 1; + ut_assertok(env_set("netretry", i ? "once" : "no")); + ret = tftp_test_retry_command(ctx, put); + ut_asserteq(i ? CMD_RET_SUCCESS : CMD_RET_FAILURE, ret); + ut_asserteq(i + 1, ctx->requests); + } + /* A failed transfer with progress must start from the beginning. */ + memset(ctx, 0, sizeof(*ctx)); + ctx->partial = true; + ut_assertok(tftp_test_retry_command(ctx, put)); + ut_asserteq(2, ctx->requests); + ut_asserteq(put ? 32 : sizeof(tftp_test_data), net_boot_file_size); + if (!put) + ut_asserteq_mem(tftp_test_data, map_sysmem(TFTP_TEST_ADDR, 0), + sizeof(tftp_test_data)); + } + /* Invalid options and metadata publication failures are not peer errors. */ + memset(ctx, 0, sizeof(*ctx)); + ctx->invalid_oack = true; + ut_asserteq(CMD_RET_FAILURE, tftp_test_retry_command(ctx, false)); + ut_asserteq(1, ctx->requests); + memset(ctx, 0, sizeof(*ctx)); + ut_assertok(env_set(".flags", "filesize:xr")); + ret = do_tftpb(NULL, 0, ARRAY_SIZE(argv), argv); + ut_assertok(env_set(".flags", NULL)); + ut_asserteq(CMD_RET_FAILURE, ret); + ut_asserteq(1, ctx->requests); + + /* A second runtime user must survive a request to rotate interfaces. */ + memset(ctx, 0, sizeof(*ctx)); + ctx->error_requests = 1; + ctx->error_code = 3; + ut_assertok(net_lwip_start(&shared, NET_LWIP_ADDR_ENV_STRICT)); + ret = do_tftpb(NULL, 0, ARRAY_SIZE(argv), argv); + i = net_lwip_poll(); + net_lwip_stop(&shared); + ut_asserteq(CMD_RET_FAILURE, ret); + ut_assert(i >= 0); + ut_asserteq(1, ctx->requests); + + /* Retry on the next sandbox interface, not on the failed one. */ + memset(ctx, 0, sizeof(*ctx)); + ctx->drop_requests = 1; + ut_assertok(env_set("ethrotate", NULL)); + ut_assertok(env_set("ipaddr5", "1.1.2.4")); + ut_assertok(env_set("netmask5", "255.255.255.0")); + ut_assertok(tftp_test_retry_command(ctx, false)); + ut_asserteq(2, ctx->requests); + ut_asserteq_str("eth@10003000", env_get("ethact")); + ut_asserteq_str("update.bin", ctx->filename); + ut_asserteq(string_to_ip("1.1.2.4").s_addr, ctx->local_ip.s_addr); + ut_assertok(env_set("bootfile", NULL)); + for (put = 0; put <= IS_ENABLED(CONFIG_CMD_TFTPPUT); put++) { + ut_assertok(env_set("ethact", "eth@10002000")); + memset(ctx, 0, sizeof(*ctx)); + ctx->default_filename = true; + ctx->drop_requests = 1; + ut_assertok(tftp_test_retry_command(ctx, put)); + ut_asserteq(2, ctx->requests); + ut_asserteq_str("01010203.img", ctx->first_filename); + ut_asserteq_str("01010204.img", ctx->filename); } + ut_assertok(env_set("ethact", "eth@10002000")); + ut_assertok(env_set("netretry", "no")); + ut_assertok(env_set("tftptimeout", NULL)); + ut_assertok(env_set("tftptimeoutcountmax", NULL)); return 0; } -#if IS_ENABLED(CONFIG_NET_LWIP) +#if IS_ENABLED(CONFIG_CMD_TFTPSRV) +static int tftp_test_server_packet(const void *data, size_t len) +{ + struct udp_pcb *pcb; + struct pbuf *p; + ip_addr_t peer; + + for (pcb = udp_pcbs; pcb; pcb = pcb->next) + if (pcb->local_port == 69 && pcb->recv) + break; + if (!pcb) + return -ENOENT; + p = pbuf_alloc(PBUF_RAW, len, PBUF_RAM); + if (!p) + return -ENOMEM; + pbuf_take(p, data, len); + ipaddr_aton("1.1.2.2", &peer); + pcb->recv(pcb->recv_arg, pcb, p, &peer, 1234); + return 0; +} + +static void tftp_test_server_tick(void *arg) +{ + static const char wrq[] = "\000\002file\000octet"; + u8 data[4 + 512] = { 0, 3, 0, 1 }; + struct tftp_test_context *ctx = arg; + bool fail = net_try_count <= ctx->failed_attempts; + + ctx->server_ticks++; + if (ctx->server_attempt != net_try_count) { + ctx->server_attempt = net_try_count; + ctx->server_phase = 0; + } + if (!ctx->server_phase && (!fail || ctx->server_case)) { + ctx->server_error = tftp_test_server_packet(wrq, sizeof(wrq)); + ctx->server_phase++; + } else if (ctx->server_phase == 1 && (!fail || ctx->server_case > 1)) { + if (!fail) { + memcpy(data + 4, tftp_test_data, sizeof(tftp_test_data)); + ctx->server_error = tftp_test_server_packet(data, + 4 + sizeof(tftp_test_data)); + } else if (ctx->server_case == 2) { + /* Retain a full block before forcing a restart. */ + memset(data + 4, 0xee, 512); + ctx->server_error = tftp_test_server_packet(data, sizeof(data)); + } else { + put_unaligned_be16(5, data); + put_unaligned_be16(ctx->error_code, data + 2); + ctx->server_error = tftp_test_server_packet(data, 5); + } + ctx->server_phase++; + } + timer_test_add_offset(5001); + sys_timeout(1, tftp_test_server_tick, ctx); +} + +static int tftp_test_server_run(struct tftp_test_context *ctx) +{ + int ret; + + sys_timeout(1, tftp_test_server_tick, ctx); + ret = run_command("tftpsrv 2000000", 0); + sys_untimeout(tftp_test_server_tick, ctx); + return ret; +} + +static int tftp_test_server_policy(struct unit_test_state *uts, + struct tftp_test_context *ctx) +{ + static const char * const policies[] = { "no", "once", "2", "yes" }; + struct net_lwip_ctx shared = {}; + unsigned int retries = CONFIG_NET_RETRY_COUNT * 2; + int scenario, i, ret; + + ut_assertok(env_set("autostart", "no")); + ut_assertok(env_set("ethrotate", "no")); + /* These client settings must not change the server's compiled defaults. */ + ut_assertok(env_set("tftptimeout", "1000")); + ut_assertok(env_set("tftptimeoutcountmax", "0")); + ut_assertok(env_set("netretry", "no")); + for (scenario = 0; scenario < 2; scenario++) { + memset(ctx, 0, sizeof(*ctx)); + ctx->server_case = scenario; + ctx->failed_attempts = UINT_MAX; + ut_asserteq(CMD_RET_FAILURE, tftp_test_server_run(ctx)); + ut_assertok(ctx->server_error); + ut_asserteq(1, ctx->server_attempt); + ut_asserteq(retries + 1, ctx->server_ticks); + ut_asserteq(scenario ? retries + 1 : 0, ctx->server_acks); + ut_asserteq(0, net_boot_file_size); + } + /* Exhaust listening, accepted-WRQ and partial-DATA retries in turn. */ + for (scenario = 0; scenario < 4; scenario++) { + for (i = 0; i < ARRAY_SIZE(policies); i++) { + memset(ctx, 0, sizeof(*ctx)); + ctx->server_case = scenario; + ctx->failed_attempts = 2; + ctx->error_code = 3; + ut_assertok(env_set("netretry", policies[i])); + ret = tftp_test_server_run(ctx); + ut_assertok(ctx->server_error); + ut_asserteq(i < 2 ? CMD_RET_FAILURE : CMD_RET_SUCCESS, ret); + ut_asserteq(i < 2 ? i + 1 : 3, ctx->server_attempt); + ut_asserteq(i < 2 ? 0 : sizeof(tftp_test_data), net_boot_file_size); + if (i >= 2) + ut_asserteq_mem(tftp_test_data, map_sysmem(TFTP_TEST_ADDR, 0), + sizeof(tftp_test_data)); + } + } + ut_assertok(env_set("netretry", "once")); + for (i = 1; i <= 2; i++) { + memset(ctx, 0, sizeof(*ctx)); + ctx->server_case = 3; + ctx->failed_attempts = 1; + ctx->error_code = i; + ut_asserteq(CMD_RET_FAILURE, tftp_test_server_run(ctx)); + ut_asserteq(1, ctx->server_attempt); + } + /* Do not rotate a runtime still held by another client. */ + memset(ctx, 0, sizeof(*ctx)); + ctx->server_case = 3; + ctx->failed_attempts = 1; + ctx->error_code = 3; + ut_assertok(net_lwip_start(&shared, NET_LWIP_ADDR_ENV_STRICT)); + ret = tftp_test_server_run(ctx); + i = net_lwip_poll(); + net_lwip_stop(&shared); + ut_asserteq(CMD_RET_FAILURE, ret); + ut_assert(i >= 0); + ut_asserteq(1, ctx->server_attempt); + + ut_assertok(env_set("tftptimeout", NULL)); + ut_assertok(env_set("tftptimeoutcountmax", NULL)); + ut_assertok(env_set("netretry", "no")); + ut_assertok(env_set("ethrotate", NULL)); + return 0; +} +#endif + static int tftp_test_addresses(struct unit_test_state *uts, struct tftp_test_context *ctx) { @@ -461,6 +744,12 @@ static int tftp_test_addresses(struct unit_test_state *uts, ut_asserteq_str("01010204.img", ctx->filename); ut_asserteq(string_to_ip("1.1.2.4").s_addr, ctx->local_ip.s_addr); } +#if IS_ENABLED(CONFIG_CMD_TFTPSRV) + memset(ctx, 0, sizeof(*ctx)); + ut_assertok(tftp_test_server_run(ctx)); + ut_assertok(ctx->server_error); + ut_asserteq(sizeof(tftp_test_data), net_boot_file_size); +#endif /* A valid global address must not hide an unset/invalid indexed one. */ ut_assertok(env_set("ipaddr", "1.1.2.3")); for (i = 0; i < 2; i++) { @@ -470,6 +759,9 @@ static int tftp_test_addresses(struct unit_test_state *uts, ut_asserteq(CMD_RET_FAILURE, ret); ut_asserteq(0, ctx->requests); ut_asserteq(0, net_boot_file_size); +#if IS_ENABLED(CONFIG_CMD_TFTPSRV) + ut_asserteq(CMD_RET_FAILURE, run_command("tftpsrv 2000000", 0)); +#endif } ut_assertok(env_set("ethact", "eth@10002000")); ut_assertok(env_set("ethrotate", NULL)); @@ -508,12 +800,57 @@ static int tftp_test_zero_address(struct unit_test_state *uts, ut_asserteq(1, ctx->requests); ut_asserteq(32, net_boot_file_size); } +#if IS_ENABLED(CONFIG_NET_LWIP) && IS_ENABLED(CONFIG_CMD_TFTPSRV) + memcpy(saved, buffer, sizeof(saved)); + memset(ctx, 0, sizeof(*ctx)); + sys_timeout(1, tftp_test_server_tick, ctx); + ret = run_command("tftpsrv 0", 0); + sys_untimeout(tftp_test_server_tick, ctx); + memcpy(received, buffer, sizeof(received)); + memcpy(buffer, saved, sizeof(saved)); + ut_assertok(ret); + ut_assertok(ctx->server_error); + ut_asserteq(0, env_get_hex("fileaddr", 1)); + ut_asserteq_mem(tftp_test_data, received, sizeof(received)); +#endif unmap_sysmem(buffer); ut_assertok(env_set("loadaddr", "2000000")); image_load_addr = TFTP_TEST_ADDR; return 0; } +static int tftp_test_protected_autostart(struct unit_test_state *uts, + struct tftp_test_context *ctx) +{ + char * const argv[] = { "tftpboot", "2000000", "update.bin" }; + int enabled, helper, ret; + + for (enabled = 0; enabled < 2; enabled++) { + ut_assertok(env_set("autostart", enabled ? "yes" : "no")); + ut_assertok(env_set(".flags", "autostart:br")); + for (helper = 0; helper < 3; helper++) { + memset(ctx, 0, sizeof(*ctx)); + switch (helper) { + case 0: + ret = tftpb_run(TFTP_TEST_ADDR, "update.bin"); + break; + case 1: + ret = tftpb_run_timeout(TFTP_TEST_ADDR, "update.bin", 100, 0); + break; + default: + ret = tftpb_run_argv(3, argv); + } + /* Do not override protection when suppression is required. */ + ut_asserteq(enabled, !!ret); + ut_asserteq(!enabled, ctx->requests); + ut_assert(!ctx->autostart); + ut_asserteq_str(enabled ? "yes" : "no", env_get("autostart")); + } + ut_assertok(env_set(".flags", NULL)); + } + return 0; +} + static int tftp_test_download(struct unit_test_state *uts, struct tftp_test_context *ctx) { @@ -538,8 +875,13 @@ static int tftp_test_download(struct unit_test_state *uts, ut_assertok(tftp_test_protected_autostart(uts, ctx)); #if IS_ENABLED(CONFIG_NET_LWIP) - ut_assertok(tftp_test_addresses(uts, ctx)); + if (IS_ENABLED(CONFIG_NET_TFTP_VARS)) + ut_assertok(tftp_test_restarts(uts, ctx)); ut_assertok(tftp_test_options(uts, ctx)); +#if IS_ENABLED(CONFIG_CMD_TFTPSRV) + ut_assertok(tftp_test_server_policy(uts, ctx)); +#endif + ut_assertok(tftp_test_addresses(uts, ctx)); #endif ut_assertok(tftp_test_filenames(uts, ctx)); ut_assertok(tftp_test_zero_address(uts, ctx)); @@ -721,6 +1063,13 @@ static int dm_test_tftp_download_only(struct unit_test_state *uts) sandbox_eth_set_priv(0, NULL); sandbox_eth_set_tx_handler(1, NULL); sandbox_eth_set_priv(1, NULL); +#if IS_ENABLED(CONFIG_NET_LWIP) + sys_untimeout(tftp_test_tick, &ctx); +#if IS_ENABLED(CONFIG_CMD_TFTPSRV) + sys_untimeout(tftp_test_server_tick, &ctx); +#endif +#endif + env_set(".flags", NULL); image_load_addr = saved_load_addr; memcpy(net_boot_file_name, saved_bootfile, sizeof(saved_bootfile)); for (i = 0; i < ARRAY_SIZE(vars); i++) { -- 2.53.0
