diff -ruN a/networking/tftp.c b/networking/tftp.c --- a/networking/tftp.c 2021-12-26 17:53:21.000000000 +0100 +++ b/networking/tftp.c 2022-10-17 17:45:15.794447921 +0200 @@ -91,22 +91,32 @@ //kbuild:lib-$(CONFIG_TFTP) += tftp.o //kbuild:lib-$(CONFIG_TFTPD) += tftp.o +/* + * Includes Intel Corporation's changes/modifications dated: 2019. + * Changed/modified portions - Copyright 2019, Intel Corporation. + */ + //usage:#define tftp_trivial_usage //usage: "[OPTIONS] HOST [PORT]" //usage:#define tftp_full_usage "\n\n" //usage: "Transfer a file from/to tftp server\n" -//usage: "\n -l FILE Local FILE" -//usage: "\n -r FILE Remote FILE" +//usage: "\n -l FILE Local FILE" +//usage: "\n -r FILE Remote FILE" //usage: IF_FEATURE_TFTP_GET( -//usage: "\n -g Get file" +//usage: "\n -g Get file" //usage: ) //usage: IF_FEATURE_TFTP_PUT( -//usage: "\n -p Put file" +//usage: "\n -p Put file" //usage: ) //usage: IF_FEATURE_TFTP_BLOCKSIZE( -//usage: "\n -b SIZE Transfer blocks in bytes" +//usage: "\n -b SIZE Transfer blocks in bytes" //usage: ) -///////: "\n -m STR Accepted and ignored ('-m binary' compat with tftp-hpa 5.2)" +//usage: "\n -t RETRIES retries attempts. default 12" +//usage: "\n -i INTERFACE interface name" +//usage: "\n -w WORKDIR working directory" +//usage: "\n -d docsis standard" +//usage: "\n -e extended error code return\n" +///////: "\n -m STR Accepted and ignored ('-m binary' compat with tftp-hpa 5.2)" //usage: //usage:#define tftpd_trivial_usage //usage: "[-crl] [-u USER] [DIR]" @@ -123,6 +133,10 @@ //usage: "\n -l Log to syslog (inetd mode requires this)" #include "libbb.h" +#include +#include +#include +#include #include "common_bufsiz.h" #include @@ -135,6 +149,10 @@ #define TFTP_MAXTIMEOUT_MS 2000 #define TFTP_NUM_RETRIES 12 /* number of backed-off retries */ +/* Following defines are according to Docsis spesification */ +#define TFTP_DOCSIS_TIMEOUT_MS 1000 /* Initial value for TFTP backoff: 1 sec */ +#define TFTP_DOCSIS_MAXTIMEOUT_MS 16000 /* Last value for TFTP backoff: 16 sec */ + /* opcodes we support */ #define TFTP_RRQ 1 #define TFTP_WRQ 2 @@ -155,11 +173,22 @@ #define ERR_EXIST 6 #define ERR_BAD_USER 7 #define ERR_BAD_OPT 8 +#define ERR_DST_UNREACH 9 /* masks coming from getopt32 */ enum { TFTP_OPT_GET = (1 << 0), TFTP_OPT_PUT = (1 << 1), +#if ENABLE_FEATURE_TFTP_GET && !ENABLE_FEATURE_TFTP_PUT + TFTP_OPT_DOCSIS = (1 << 1), + TFTP_OPT_EXTND_ERR = (1 << 2), +#elif !ENABLE_FEATURE_TFTP_GET && ENABLE_FEATURE_TFTP_PUT + TFTP_OPT_DOCSIS = (1 << 1), + TFTP_OPT_EXTND_ERR = (1 << 2), +#else + TFTP_OPT_DOCSIS = (1 << 2), + TFTP_OPT_EXTND_ERR = (1 << 3), +#endif /* pseudo option: if set, it's tftpd */ TFTPD_OPT = (1 << 7) * ENABLE_TFTPD, TFTPD_OPT_r = (1 << 8) * ENABLE_TFTPD, @@ -181,10 +210,27 @@ #define CMD_GET(cmd) ((cmd) & TFTP_OPT_GET) #define CMD_PUT(cmd) ((cmd) & TFTP_OPT_PUT) #endif +#define CMD_DOCSIS(cmd) ((cmd) & TFTP_OPT_DOCSIS) +#define CMD_EXTND_ERR(cmd) ((cmd) & TFTP_OPT_EXTND_ERR) + /* NB: in the code below * CMD_GET(cmd) and CMD_PUT(cmd) are mutually exclusive */ +/* Control ancillary data buffer to read ICMP packets */ +union ctrl_un { + struct cmsghdr cmsg_hdr; + char control[CMSG_SPACE(200)]; +}; + +/* TFTP parameters */ +struct tftp_params { + const char *retries_str; + const char *if_name; + const char *work_dir; + uint32_t docsis; + uint32_t extended_err; +}; struct globals { /* u16 TFTP_ERROR; u16 reason; both network-endian, then error text: */ @@ -200,6 +246,7 @@ const char *file; bb_progress_t pmt; #endif + struct tftp_params tftp; } FIX_ALIASING; #define G (*(struct globals*)bb_common_bufsiz1) #define INIT_G() do { \ @@ -319,8 +366,9 @@ uint16_t block_nr; uint16_t recv_blk; int local_fd = -1; - int retries, waittime_ms; + int retries, waittime_ms, max_timeout_ms; int io_bufsize = blksize + 4; + int extend_err = EX_OK; char *cp; /* Can't use RESERVE_CONFIG_BUFFER here since the allocation * size varies meaning BUFFERS_GO_ON_STACK would fail. @@ -331,9 +379,47 @@ char *xbuf = xmalloc(io_bufsize); char *rbuf = xmalloc(io_bufsize); + if (G.tftp.work_dir) { + /* Change workin directory */ + chdir(G.tftp.work_dir); + } + + if (G.tftp.docsis) { + max_timeout_ms = TFTP_DOCSIS_MAXTIMEOUT_MS; + } else { + max_timeout_ms = TFTP_MAXTIMEOUT_MS; + } + socket_fd = xsocket(peer_lsa->u.sa.sa_family, SOCK_DGRAM, 0); setsockopt_reuseaddr(socket_fd); + if (G.tftp.if_name) { + /* Set interface name to bind */ + if (setsockopt(socket_fd, SOL_SOCKET, SO_BINDTODEVICE, G.tftp.if_name, + strlen(G.tftp.if_name)+1) < 0) { + perror("setsockopt: BINDTODEVICE"); + exit(EX_SOFTWARE); + } + } + + if (G.tftp.docsis) { + /* Add socket option for reading ICMP errors to detect "Destination unreachable" reply */ + int optname, proto; + + if (peer_lsa->u.sa.sa_family == AF_INET) { + optname = IP_RECVERR; + proto = SOL_IP; + } + else { + optname = IPV6_RECVERR; + proto = SOL_IPV6; + } + if (setsockopt_1(socket_fd, proto, optname) < 0) { + perror("setsockopt: IP_RECVERR"); + exit(EX_SOFTWARE); + } + } + if (!ENABLE_TFTP || our_lsa) { /* tftpd */ /* Create a socket which is: * 1. bound to IP:port peer sent 1st datagram to, @@ -345,8 +431,10 @@ xconnect(socket_fd, &peer_lsa->u.sa, peer_lsa->len); /* Is there an error already? Send pkt and bail out */ - if (G_error_pkt_reason || G_error_pkt_str[0]) + if (G_error_pkt_reason || G_error_pkt_str[0]) { + extend_err = EX_UNAVAILABLE; goto send_err_pkt; + } if (G.pw) { change_identity(G.pw); /* initgroups, setgid, setuid */ @@ -406,6 +494,7 @@ bb_perror_msg("can't open '%s'", local_file); G_error_pkt_reason = ERR_NOFILE; strcpy(G_error_pkt_str, "can't open file"); + extend_err = EX_NOINPUT; goto send_err_pkt_nomsg; } @@ -458,6 +547,7 @@ len = strlen(remote_file); if (len + 3 + sizeof("octet") >= io_bufsize) { bb_simple_error_msg("remote filename is too long"); + extend_err = ERR_WRITE; goto ret; } cp = stpcpy(cp, remote_file) + 1; @@ -471,6 +561,7 @@ /* Need to add option to pkt */ if ((&xbuf[io_bufsize - 1] - cp) < sizeof("blksize NNNNN tsize ") + sizeof(off_t)*3) { bb_simple_error_msg("remote filename is too long"); + extend_err = ERR_WRITE; goto ret; } expect_OACK = 1; @@ -525,6 +616,7 @@ opcode = TFTP_DATA; len = full_read(local_fd, cp, blksize); if (len < 0) { + extend_err = EX_DATAERR; goto send_read_err_pkt; } if (len != blksize) { @@ -540,8 +632,18 @@ /* NB: send_len value is preserved in code below * for potential resend */ - retries = TFTP_NUM_RETRIES; /* re-initialize */ - waittime_ms = TFTP_TIMEOUT_MS; + if (G.tftp.retries_str) { + /* Total retries plus initial try */ + retries = bb_strtou(G.tftp.retries_str, NULL, 10) + 1; + } else { + retries = TFTP_NUM_RETRIES; /* re-initialize */ + } + if (G.tftp.docsis) { + /* Set initial TFTP backoff value according to Docsis spec */ + waittime_ms = TFTP_DOCSIS_TIMEOUT_MS; + } else { + waittime_ms = TFTP_TIMEOUT_MS; + } send_again: #if ENABLE_TFTP_DEBUG @@ -573,13 +675,20 @@ if (retries == 0) { tftp_progress_done(); bb_simple_error_msg("timeout"); + extend_err = EX_UNAVAILABLE; goto ret; /* no err packet sent */ } - /* exponential backoff with limit */ - waittime_ms += waittime_ms/2; - if (waittime_ms > TFTP_MAXTIMEOUT_MS) { - waittime_ms = TFTP_MAXTIMEOUT_MS; + if (G.tftp.docsis) { + /* the retries interval should be x^2 (1,2,4...) with limit */ + waittime_ms *= 2; + } + else { + /* exponential backoff with limit */ + waittime_ms += waittime_ms/2; + } + if (waittime_ms > max_timeout_ms) { + waittime_ms = max_timeout_ms; } goto send_again; /* resend last sent pkt */ @@ -602,6 +711,50 @@ len = safe_read(socket_fd, rbuf, io_bufsize); } if (len < 0) { + if (G.tftp.docsis) { + ssize_t recv_size; + struct msghdr msg_hdr = {0}; + struct iovec iov = {0}; + struct cmsghdr *cmsg_hdr = NULL; + struct sock_extended_err *ext_err; + union ctrl_un ctrl_buff; + + /* Check if ICMP packet with "Destination unreachable" type was received */ + msg_hdr.msg_name = &peer_lsa->u.sa; + msg_hdr.msg_namelen = peer_lsa->len; + iov.iov_base = rbuf; + iov.iov_len = io_bufsize; + msg_hdr.msg_iov = &iov; + msg_hdr.msg_iovlen = 1; + msg_hdr.msg_control = (void*)&ctrl_buff; + msg_hdr.msg_controllen = sizeof(ctrl_buff); + + recv_size = recvmsg(socket_fd, &msg_hdr, MSG_ERRQUEUE); + if ((recv_size == -1) && (errno != EAGAIN)) { + fprintf(stderr, "Error in recvmsg. errno = %d (%s)\n", errno, strerror(errno)); + } + if (recv_size >= 0) { + for (cmsg_hdr = CMSG_FIRSTHDR(&msg_hdr); cmsg_hdr != NULL; cmsg_hdr = CMSG_NXTHDR(&msg_hdr, cmsg_hdr)) { + if (((cmsg_hdr->cmsg_level == SOL_IP) && (cmsg_hdr->cmsg_type == IP_RECVERR)) || + ((cmsg_hdr->cmsg_level == SOL_IPV6) && (cmsg_hdr->cmsg_type == IPV6_RECVERR))) { + ext_err = (struct sock_extended_err*)CMSG_DATA(cmsg_hdr); + if ((ext_err->ee_origin == SO_EE_ORIGIN_ICMP) && (ext_err->ee_type == ICMP_DEST_UNREACH)) { + if ((ext_err->ee_code >= ICMP_NET_UNREACH) && (ext_err->ee_code <= ICMP_PREC_CUTOFF)) { + extend_err = ERR_DST_UNREACH; + goto ret; + } + } + else if ((ext_err->ee_origin == SO_EE_ORIGIN_ICMP6) && (ext_err->ee_type == ICMPV6_DEST_UNREACH)) { + if ((ext_err->ee_code >= ICMPV6_NOROUTE) && (ext_err->ee_code <= ICMPV6_PORT_UNREACH)) { + extend_err = ERR_DST_UNREACH; + goto ret; + } + } + } + } + } + } + extend_err = EX_DATAERR; goto send_read_err_pkt; } if (len < 4) { /* too small? */ @@ -627,7 +780,19 @@ "no such user\0" "bad option"; - const char *msg = ""; + static int errcode_flag[] ALIGN1 = { + EX_DATAERR, + ERR_NOFILE, + ERR_ACCESS, + ERR_WRITE, + ERR_OP, + ERR_BAD_ID, + ERR_EXIST, + ERR_BAD_USER, + EX_DATAERR + }; + const char *msg = "Read error"; + int flag = EX_DATAERR; if (len > 4 && rbuf[4] != '\0') { msg = &rbuf[4]; @@ -635,7 +800,11 @@ } else if (recv_blk <= 8) { msg = nth_string(errcode_str, recv_blk); } + if (recv_blk <= 8){ + flag = errcode_flag[recv_blk]; + } bb_error_msg("server error: (%u) %s", recv_blk, msg); + extend_err = flag; goto ret; } @@ -651,6 +820,7 @@ blksize = tftp_blksize_check(res, blksize); if (blksize < 0) { G_error_pkt_reason = ERR_BAD_OPT; + extend_err = EX_USAGE; goto send_err_pkt; } io_bufsize = blksize + 4; @@ -697,6 +867,7 @@ if (sz != len - 4) { strcpy(G_error_pkt_str, bb_msg_write_error); G_error_pkt_reason = ERR_WRITE; + extend_err = EX_UNAVAILABLE; goto send_err_pkt; } if (sz != blksize) { @@ -742,6 +913,9 @@ free(xbuf); free(rbuf); } + if (G.tftp.extended_err) { + return extend_err; + } if (!finished) goto err; return EXIT_SUCCESS; @@ -758,6 +932,9 @@ err: if (local_fd >= 0 && CMD_GET(option_mask32) && local_file) unlink(local_file); + if (G.tftp.extended_err) { + return extend_err; + } return EXIT_FAILURE; #undef remote_file } @@ -770,11 +947,12 @@ const char *local_file = NULL; const char *remote_file = NULL; # if ENABLE_FEATURE_TFTP_BLOCKSIZE - const char *blksize_str = TFTP_BLKSIZE_DEFAULT_STR; + const char *blksize_str = NULL; int blksize; # endif int result; int port; + IF_GETPUT(int opt;) INIT_G(); @@ -815,30 +993,24 @@ } } - getopt32(argv, "^" + IF_GETPUT(opt =) getopt32(argv, "^" IF_FEATURE_TFTP_GET("g") IF_FEATURE_TFTP_PUT("p") - "l:r:" IF_FEATURE_TFTP_BLOCKSIZE("b:") + "del:r:" IF_FEATURE_TFTP_BLOCKSIZE("b:") + "t:i:w:" IF_FEATURE_TFTP_HPA_COMPAT("m:") "\0" /* -p or -g is mandatory, and they are mutually exclusive */ IF_FEATURE_TFTP_GET("g:") IF_FEATURE_TFTP_PUT("p:") IF_GETPUT("g--p:p--g:"), &local_file, &remote_file - IF_FEATURE_TFTP_BLOCKSIZE(, &blksize_str) + IF_FEATURE_TFTP_BLOCKSIZE(, &blksize_str), + &G.tftp.retries_str, &G.tftp.if_name, &G.tftp.work_dir IF_FEATURE_TFTP_HPA_COMPAT(, NULL) ); + G.tftp.docsis = CMD_DOCSIS(opt); + G.tftp.extended_err = CMD_EXTND_ERR(opt); argv += optind; -# if ENABLE_FEATURE_TFTP_BLOCKSIZE - /* Check if the blksize is valid: - * RFC2348 says between 8 and 65464 */ - blksize = tftp_blksize_check(blksize_str, 65564); - if (blksize < 0) { - //bb_error_msg("bad block size"); - return EXIT_FAILURE; - } -# endif - if (remote_file) { if (!local_file) { const char *slash = strrchr(remote_file, '/'); @@ -855,6 +1027,26 @@ port = bb_lookup_port(argv[1], "udp", 69); peer_lsa = xhost2sockaddr(argv[0], port); +# if ENABLE_FEATURE_TFTP_BLOCKSIZE + if (!blksize_str) { + if (G.tftp.docsis) { + /* The CM MUST request a block size of 1448 octets if using TFTP over IPv4. + * The CM MUST request a block size of 1428 octets if using TFTP over IPv6. */ + blksize_str = (peer_lsa->u.sa.sa_family==AF_INET?"1448":"1428"); + } else { + blksize_str = TFTP_BLKSIZE_DEFAULT_STR; + } + } + + /* Check if the blksize is valid: + * RFC2348 says between 8 and 65464 */ + blksize = tftp_blksize_check(blksize_str, 65564); + if (blksize < 0) { + //bb_error_msg("bad block size"); + return EXIT_FAILURE; + } +# endif + # if ENABLE_TFTP_DEBUG fprintf(stderr, "using server '%s', remote_file '%s', local_file '%s'\n", xmalloc_sockaddr2dotted(&peer_lsa->u.sa),