| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // This is an example binary to exercise ulib/tftp. It runs on Linux or macOS. |
| |
| #define _POSIX_C_SOURCE 200809L |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <netdb.h> |
| #include <netinet/in.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/socket.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include "tftp/tftp.h" |
| |
| #define BLOCKSZ 1024 |
| #define WINSZ 64 |
| |
| #define DROPRATE 100 |
| |
| #define SCRATCHSZ 2048 |
| static char scratch[SCRATCHSZ]; |
| static char out_scratch[SCRATCHSZ]; |
| static char in_scratch[SCRATCHSZ]; |
| |
| typedef struct connection connection_t; |
| |
| struct tftp_file { |
| int fd; |
| size_t size; |
| }; |
| |
| struct connection { |
| int socket; |
| struct sockaddr_in out_addr; |
| struct sockaddr_in in_addr; |
| uint32_t previous_timeout_ms; |
| }; |
| |
| tftp_status connection_send(void* data, size_t len, void* transport_cookie) { |
| connection_t* connection = (connection_t*)transport_cookie; |
| #if DROPRATE != 0 |
| if (rand() % DROPRATE == 0) { |
| fprintf(stderr, "DROP\n"); |
| return len; |
| } |
| #endif |
| uint8_t* msg = data; |
| uint16_t opcode = ntohs(*(uint16_t*)msg); |
| fprintf(stderr, "sending opcode=%u\n", opcode); |
| ssize_t send_result = sendto(connection->socket, data, len, 0, |
| (struct sockaddr*)&connection->out_addr, sizeof(struct sockaddr_in)); |
| if (send_result != len) { |
| return TFTP_ERR_IO; |
| } |
| return TFTP_NO_ERROR; |
| } |
| |
| int connection_receive(void* data, size_t len, bool block, void* transport_cookie) { |
| connection_t* connection = (connection_t*)transport_cookie; |
| socklen_t server_len; |
| int fl = fcntl(connection->socket, F_GETFL, 0); |
| if (fl < 0) { |
| int e = errno; |
| fprintf(stderr, "could not get socket flags: %d\n", errno); |
| errno = e; |
| return -1; |
| } |
| if (block) { |
| fl &= ~O_NONBLOCK; |
| } else { |
| fl |= O_NONBLOCK; |
| } |
| int ret = fcntl(connection->socket, F_SETFL, fl); |
| if (ret < 0) { |
| int e = errno; |
| fprintf(stderr, "could not set socket flags: %d\n", errno); |
| errno = e; |
| return -1; |
| } |
| ssize_t recv_result = recvfrom(connection->socket, data, len, 0, |
| (struct sockaddr*)&connection->in_addr, &server_len); |
| if (recv_result < 0) { |
| if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) |
| return TFTP_ERR_TIMED_OUT; |
| fprintf(stderr, "failed during recvfrom: errno=%d\n", (int)errno); |
| return TFTP_ERR_INTERNAL; |
| } |
| return recv_result; |
| } |
| |
| int connection_set_timeout(uint32_t timeout_ms, void* transport_cookie) { |
| connection_t* connection = (connection_t*)transport_cookie; |
| if (connection->previous_timeout_ms != timeout_ms && timeout_ms > 0) { |
| fprintf(stdout, "Setting timeout to %dms\n", timeout_ms); |
| connection->previous_timeout_ms = timeout_ms; |
| struct timeval tv; |
| tv.tv_sec = timeout_ms / 1000; |
| tv.tv_usec = 1000 * (timeout_ms - 1000 * tv.tv_sec); |
| return setsockopt(connection->socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); |
| } |
| return 0; |
| } |
| |
| connection_t* create_connection(const char* hostname, int incoming_port, int outgoing_port) { |
| connection_t* connection = (connection_t*)malloc(sizeof(connection_t)); |
| memset(connection, 0, sizeof(connection_t)); |
| |
| struct hostent* server; |
| |
| if ((connection->socket = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { |
| fprintf(stderr, "Cannot create socket\n"); |
| goto err; |
| } |
| |
| if (!(server = gethostbyname(hostname))) { |
| fprintf(stderr, "Could not resolve host '%s'\n", hostname); |
| goto err; |
| } |
| |
| memset(&connection->out_addr, 0, sizeof(struct sockaddr_in)); |
| connection->out_addr.sin_family = AF_INET; |
| connection->out_addr.sin_port = htons(outgoing_port); |
| void* server_addr = server->h_addr_list[0]; |
| memcpy(&connection->out_addr.sin_addr.s_addr, server_addr, server->h_length); |
| |
| memset(&connection->in_addr, 0, sizeof(struct sockaddr_in)); |
| connection->in_addr.sin_family = AF_INET; |
| connection->in_addr.sin_port = htons(incoming_port); |
| memcpy(&connection->in_addr.sin_addr.s_addr, server_addr, server->h_length); |
| if (bind(connection->socket, (struct sockaddr*)&connection->in_addr, |
| sizeof(struct sockaddr_in)) == -1) { |
| fprintf(stderr, "Could not bind\n"); |
| goto err; |
| } |
| |
| connection->previous_timeout_ms = 0; |
| return connection; |
| |
| err: |
| if (connection->socket) |
| close(connection->socket); |
| free(connection); |
| return NULL; |
| } |
| |
| void print_usage(void) { |
| fprintf(stdout, "tftp (-s filename|-r)\n"); |
| fprintf(stdout, "\t -s filename to send the provided file\n"); |
| fprintf(stdout, "\t -r to receive a file\n"); |
| } |
| |
| ssize_t open_read_file(const char* filename, void* file_cookie) { |
| fprintf(stdout, "Opening %s for reading\n", filename); |
| int fd = open(filename, O_RDONLY); |
| if (fd < 0) { |
| fprintf(stderr, "could not open file: err=%d\n", errno); |
| return TFTP_ERR_IO; |
| } |
| struct tftp_file* f = (struct tftp_file*)file_cookie; |
| f->fd = fd; |
| struct stat st; |
| if (fstat(f->fd, &st) < 0) { |
| fprintf(stderr, "could not get file size: err=%d\n", errno); |
| return TFTP_ERR_IO; |
| } |
| return st.st_size; |
| } |
| |
| tftp_status open_write_file(const char* filename, size_t size, void* file_cookie) { |
| fprintf(stdout, "Opening %s for writing\n", filename); |
| int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| if (fd < 0) { |
| fprintf(stderr, "could not open file: err=%d\n", errno); |
| return TFTP_ERR_IO; |
| } |
| struct tftp_file* f = (struct tftp_file*)file_cookie; |
| f->fd = fd; |
| return TFTP_NO_ERROR; |
| } |
| |
| tftp_status read_file(void* data, size_t* length, off_t offset, void* file_cookie) { |
| int fd = ((struct tftp_file*)file_cookie)->fd; |
| ssize_t n = pread(fd, data, *length, offset); |
| if (n < 0) { |
| fprintf(stderr, "could not read file: offset %jd, err=%d\n", (intmax_t)offset, errno); |
| return n; |
| } |
| *length = n; |
| return TFTP_NO_ERROR; |
| } |
| |
| tftp_status write_file(const void* data, size_t* length, off_t offset, void* file_cookie) { |
| struct tftp_file* f = file_cookie; |
| int fd = f->fd; |
| ssize_t n = pwrite(fd, data, *length, offset); |
| if (n < 0) { |
| fprintf(stderr, "could not write file: offset %jd, err=%d\n", (intmax_t)offset, errno); |
| return n; |
| } |
| *length = n; |
| f->size = offset + *length; |
| return TFTP_NO_ERROR; |
| } |
| |
| void close_file(void* file_cookie) { |
| struct tftp_file* f = file_cookie; |
| close(f->fd); |
| } |
| |
| int tftp_send_file_wrapper(tftp_session* session, connection_t* connection, const char* filename) { |
| uint16_t block_size = BLOCKSZ; |
| uint16_t window_size = WINSZ; |
| tftp_set_options(session, &block_size, NULL, &window_size); |
| |
| struct tftp_file file_cookie; |
| char err_msg[128]; |
| tftp_request_opts options = {0}; |
| options.inbuf = in_scratch; |
| options.inbuf_sz = sizeof(in_scratch); |
| options.outbuf = out_scratch; |
| options.outbuf_sz = sizeof(out_scratch); |
| options.err_msg = err_msg; |
| options.err_msg_sz = sizeof(err_msg); |
| tftp_status send_result = |
| tftp_push_file(session, connection, &file_cookie, filename, "zircon.bin", &options); |
| if (send_result == TFTP_NO_ERROR) { |
| return 0; |
| } |
| fprintf(stderr, "%s\n", err_msg); |
| return -1; |
| } |
| |
| int tftp_receive_file_wrapper(tftp_session* session, connection_t* connection) { |
| struct tftp_file file_cookie; |
| char err_msg[128]; |
| tftp_handler_opts options = {0}; |
| options.inbuf = in_scratch; |
| options.inbuf_sz = sizeof(in_scratch); |
| options.outbuf = out_scratch; |
| size_t outbuf_sz = sizeof(out_scratch); |
| options.outbuf_sz = &outbuf_sz; |
| options.err_msg = err_msg; |
| options.err_msg_sz = sizeof(err_msg); |
| |
| tftp_status status; |
| do { |
| status = tftp_service_request(session, connection, &file_cookie, &options); |
| } while (status == TFTP_NO_ERROR || status == TFTP_ERR_TIMED_OUT); |
| if (status == TFTP_TRANSFER_COMPLETED) |
| return 0; |
| fprintf(stderr, "%s\n", err_msg); |
| return 1; |
| } |
| |
| int main(int argc, char* argv[]) { |
| const char* hostname = "127.0.0.1"; |
| int port = 2343; |
| |
| srand(time(NULL)); |
| |
| if (argc < 2) { |
| print_usage(); |
| return 1; |
| } |
| |
| tftp_session* session = NULL; |
| if (SCRATCHSZ < tftp_sizeof_session()) { |
| fprintf(stderr, "Need more space for tftp session: %d < %zu\n", SCRATCHSZ, |
| tftp_sizeof_session()); |
| return -1; |
| } |
| if (tftp_init(&session, scratch, SCRATCHSZ)) { |
| fprintf(stderr, "Failed to initialize TFTP Session\n"); |
| return -1; |
| } |
| |
| tftp_file_interface file_interface = {open_read_file, open_write_file, read_file, write_file, |
| close_file}; |
| tftp_session_set_file_interface(session, &file_interface); |
| tftp_transport_interface transport_interface = {connection_send, connection_receive, |
| connection_set_timeout}; |
| tftp_session_set_transport_interface(session, &transport_interface); |
| |
| connection_t* connection; |
| if (!strncmp(argv[1], "-s", 2)) { |
| if (argc < 3) { |
| print_usage(); |
| return 1; |
| } |
| connection = create_connection(hostname, port, port + 1); |
| if (!connection) { |
| return -1; |
| } |
| return tftp_send_file_wrapper(session, connection, argv[2]); |
| } else if (!strncmp(argv[1], "-r", 2)) { |
| connection = create_connection(hostname, port + 1, port); |
| if (!connection) { |
| return -1; |
| } |
| return tftp_receive_file_wrapper(session, connection); |
| } else { |
| print_usage(); |
| return 2; |
| } |
| return 0; |
| } |