--- a/etworking/tftp.c.orig 2019-05-15 14:41:32.945078419 +0200 +++ b/networking/tftp.c 2019-05-15 14:41:40.125059130 +0200 @@ -89,21 +89,31 @@ //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 of SIZE octets" +//usage: "\n -b SIZE Transfer blocks of SIZE octets" //usage: ) +//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" //usage: //usage:#define tftpd_trivial_usage //usage: "[-cr] [-u USER] [DIR]" @@ -121,6 +131,10 @@ //usage: "\n -l Log to syslog (inetd mode requires this)" #include "libbb.h" +#include +#include +#include +#include #include "common_bufsiz.h" #include @@ -133,6 +147,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 @@ -153,11 +171,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, @@ -179,10 +208,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: */ @@ -198,6 +244,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 { \ @@ -317,8 +364,9 @@ uint16_t block_nr; uint16_t recv_blk; int open_mode, local_fd; - 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. @@ -329,9 +377,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, @@ -343,8 +429,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 */ @@ -401,6 +489,7 @@ if (local_fd < 0) { G_error_pkt_reason = ERR_NOFILE; strcpy(G_error_pkt_str, "can't open file"); + extend_err = EX_NOINPUT; goto send_err_pkt; } /* gcc 4.3.1 would NOT optimize it out as it should! */ @@ -451,6 +540,7 @@ len = strlen(remote_file) + 1; if (2 + len + sizeof("octet") >= io_bufsize) { bb_error_msg("remote filename is too long"); + extend_err = ERR_WRITE; goto ret; } strcpy(cp, remote_file); @@ -466,6 +556,7 @@ /* Need to add option to pkt */ if ((&xbuf[io_bufsize - 1] - cp) < sizeof("blksize NNNNN tsize ") + sizeof(off_t)*3) { bb_error_msg("remote filename is too long"); + extend_err = ERR_WRITE; goto ret; } expect_OACK = 1; @@ -519,6 +610,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) { @@ -534,8 +626,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 @@ -567,13 +669,20 @@ if (retries == 0) { tftp_progress_done(); bb_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 */ @@ -596,6 +705,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? */ @@ -621,7 +774,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]; @@ -629,7 +794,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; } @@ -645,6 +814,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; @@ -685,6 +855,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) { @@ -730,6 +901,9 @@ free(xbuf); free(rbuf); } + if (G.tftp.extended_err) { + return extend_err; + } return finished == 0; /* returns 1 on failure */ send_read_err_pkt: @@ -740,6 +914,9 @@ G.error_pkt[1] = TFTP_ERROR; xsendto(socket_fd, G.error_pkt, 4 + 1 + strlen(G_error_pkt_str), &peer_lsa->u.sa, peer_lsa->len); + if (G.tftp.extended_err) { + return extend_err; + } return EXIT_FAILURE; #undef remote_file } @@ -753,7 +930,7 @@ 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; @@ -764,26 +941,20 @@ 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:" "\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 ); + 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, '/'); @@ -800,6 +971,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),