├── payload.txt ├── main.c ├── tests └── test.c ├── tftp_server.h ├── README.md └── tftp_server.c /payload.txt: -------------------------------------------------------------------------------- 1 | hello world! 2 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "tftp_server.h" 6 | 7 | int main(int argc, char *argv[]) { 8 | /* char *file; */ 9 | /* printf("Argument %d: %s\n", 1, argv[1]); */ 10 | 11 | struct tftp_server s = {.port = 69, .file = "payload.txt"}; 12 | int ret = start_server(&s); 13 | exit(ret); 14 | } 15 | -------------------------------------------------------------------------------- /tests/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../tftp_server.c" 5 | 6 | #if _WIN32 7 | #define C_RED(s) s 8 | #define C_GREEN(s) s 9 | #define C_YELLOW(s) s 10 | #define C_MAGENTA(s) s 11 | #else 12 | #define C_RED(s) "\033[31;1m" s "\033[0m" 13 | #define C_GREEN(s) "\033[32;1m" s "\033[0m" 14 | #define C_YELLOW(s) "\033[33;1m" s "\033[0m" 15 | #define C_MAGENTA(s) "\033[35;1m" s "\033[0m" 16 | #endif 17 | 18 | #ifndef _TEST_ 19 | 20 | int main(void) { 21 | int fails = 0; 22 | struct tftp_server s = {.port = 8082, .file = "file.jpg"}; 23 | 24 | // start_server tests 25 | { 26 | if (start_server(&s) != EXIT_SUCCESS) { 27 | printf(C_RED("FAILED: ") "start_server expected to be 0 but was 1\n"); 28 | } else { 29 | printf(C_GREEN("PASS: ") "start_server started successfully\n"); 30 | } 31 | } 32 | 33 | // start_server validation 34 | { 35 | s.file = ""; 36 | if (start_server(&s) == EXIT_FAILURE) { 37 | printf(C_GREEN("PASS: ") "start_server handles empty file argumemt\n"); 38 | } else { 39 | printf(C_RED("FAILED: ") "start_server expected to handle empty file but did not\n"); 40 | } 41 | } 42 | 43 | return fails ? EXIT_FAILURE : EXIT_SUCCESS; 44 | } 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /tftp_server.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /** 5 | * Set flag to 1 to enable debugging log messages. 6 | */ 7 | #define DEBUG 0 8 | 9 | struct tftp_server { 10 | int port; 11 | int retries; 12 | char *file; 13 | }; 14 | 15 | /* typedef union { */ 16 | 17 | /* uint16_t opcode; */ 18 | 19 | /* struct { */ 20 | /* uint16_t opcode; /1* RRQ or WRQ *1/ */ 21 | /* uint8_t filename_and_mode[514]; */ 22 | /* } request; */ 23 | 24 | /* struct { */ 25 | /* uint16_t opcode; /1* DATA *1/ */ 26 | /* uint16_t block_number; */ 27 | /* uint8_t data[512]; */ 28 | /* } data; */ 29 | 30 | /* struct { */ 31 | /* uint16_t opcode; /1* ACK *1/ */ 32 | /* uint16_t block_number; */ 33 | /* } ack; */ 34 | 35 | /* struct { */ 36 | /* uint16_t opcode; /1* ERROR *1/ */ 37 | /* uint16_t error_code; */ 38 | /* uint8_t error_string[512]; */ 39 | /* } error; */ 40 | 41 | /* } tftp_message; */ 42 | 43 | typedef struct { 44 | 45 | uint16_t opcode; 46 | 47 | union { 48 | 49 | struct { 50 | char filename_and_mode[514]; 51 | char mode[8]; 52 | } request; 53 | 54 | struct { 55 | uint16_t block_number; 56 | uint8_t data[512]; 57 | } data; 58 | 59 | struct { 60 | uint16_t block_number; 61 | } ack; 62 | 63 | struct { 64 | uint16_t error_code; 65 | uint8_t error_string[512]; 66 | } error; 67 | 68 | }; 69 | 70 | } tftp_message; 71 | 72 | int start_server(struct tftp_server *s); 73 | 74 | /** 75 | * Transfers the specified source file to the addressed client using the given 76 | * client in BINARY mode transfer. 77 | * 78 | * @param src_file source file to be transferred; 79 | * @param socket socket to be used to transfer the file to the recipient 80 | * client; 81 | * @param cli_addr address of the client requesting the file transfer. 82 | */ 83 | void transfer_binary_mode(FILE *src_file, int socket, struct sockaddr_in *cli_addr); 84 | 85 | int parse_message(int socket_desc, tftp_message *m,struct sockaddr_in *client_addr); 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Trivial File Transfer Protocol Server (TFTP) implementation in C 2 | 3 | TFTP is a (trivial) File Transfer Protocol that ensures reliable data transfer over UDP. It does this by using a subset of mechanics that make TCP reliable. 4 | The full RFC can be found below, with the packet structure highlighted in this read me. 5 | 6 | You can find the full specification here: https://datatracker.ietf.org/doc/html/rfc1350 7 | 8 | ### Usage 9 | 10 | **Lauch server:** 11 | 12 | ```shell 13 | $ clang -pedantic main.c tftp_server.c && ./a.out 14 | ``` 15 | 16 | **Read file via tftp** 17 | 18 | ```shell 19 | $ tftp -e 127.0.0.1 20 | get payload.txt 21 | ``` 22 | 23 | Use the following tcpdump command to view network traffic: 24 | 25 | ``` 26 | tcpdump -i lo0 -n udp port 69 27 | ``` 28 | 29 | 30 | ### Packet structure 31 | 32 | The TFTP header of a packet contains one of the following opcodes to indicate the intended operation, these are: 33 | 34 | ``` 35 | opcode operation 36 | 1 Read request (RRQ) 37 | 2 Write request (WRQ) 38 | 3 Data (DATA) 39 | 4 Acknowledgment (ACK) 40 | 5 Error (ERROR) 41 | ``` 42 | 43 | #### Initial Read Request 44 | 45 | ReadReq acts as the initial read request packet (RRQ) informing the server which file it would like to read 46 | 47 | ``` 48 | 2 bytes string 1 byte string 1 byte 49 | ------------------------------------------------ 50 | | Opcode | Filename | 0 | Mode | 0 | 51 | ------------------------------------------------ 52 | ``` 53 | 54 | ### Data Packet 55 | 56 | The data packet contains an opcode, the block number (a monotonically incrementing number used by the server to ensure reliable transfer by waiting for the client to ack the packet by sending the block number back to the server) 57 | 58 | ``` 59 | 2 bytes 2 bytes n bytes 60 | ---------------------------------- 61 | | Opcode | Block # | Data | 62 | ---------------------------------- 63 | ``` 64 | 65 | ### ACK Packet 66 | 67 | Ack responds to the server with a block number to inform the server which packet it just received 68 | 69 | ``` 70 | 2 bytes 2 bytes 71 | --------------------- 72 | | Opcode | Block # | 73 | --------------------- 74 | ``` 75 | -------------------------------------------------------------------------------- /tftp_server.c: -------------------------------------------------------------------------------- 1 | #include "tftp_server.h" 2 | 3 | #include <_string.h> 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define DATAGRAM_SIZE 516 11 | #define BLOCKSIZE = DATAGRAM_SIZE - 4; 12 | 13 | #define OPCODE_RRQ 1 14 | #define OPCODE_WRQ 2 15 | #define OPCODE_DATA 3 16 | #define OPCODE_ACK 4 17 | #define OPCODE_ERR 5 18 | 19 | #undef DEBUG 20 | #define DEBUG 1 21 | 22 | FILE *src_file; 23 | 24 | // buffer used for both reading and writing data to packets 25 | char buf[DATAGRAM_SIZE]; 26 | 27 | int start_server(struct tftp_server *s) { 28 | if (s->file == NULL || strlen(s->file) < 1) { 29 | perror("file to transfer not specified"); 30 | return EXIT_FAILURE; 31 | } 32 | 33 | if (s->port == 0) { 34 | perror("port not specified"); 35 | return EXIT_FAILURE; 36 | } 37 | 38 | int socket_desc = socket(AF_INET, SOCK_DGRAM, 0); 39 | if (socket_desc < 0) { 40 | printf("Error while creating socket\n"); 41 | return -1; 42 | } 43 | 44 | printf("Socket created successfully\n"); 45 | 46 | // configure socket to allow it to be reused, avoiding 'Address already in use' errors 47 | int reuse = 1; 48 | if (setsockopt(socket_desc, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)) < 0) { 49 | printf("SO_REUSEPORT failed\n"); 50 | return -1; 51 | } 52 | 53 | // Set port and IP: 54 | 55 | /* server_sock.sin_family = AF_INET; */ 56 | struct sockaddr_in serv_addr = { 57 | .sin_family = AF_INET, // we are using Ipv4 protocol 58 | .sin_port = htons(s->port), // port 2053 59 | .sin_addr = {htonl(INADDR_ANY)}, // 0.0.0.0 60 | }; 61 | 62 | // Bind to the set port and IP so we don't receive everything 63 | if (bind(socket_desc, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) { 64 | printf("Couldn't bind to the port %d: %d\n", s->port, errno); 65 | return -1; 66 | } 67 | 68 | printf("Listening for incoming tftp messages on port %d...\n\n", s->port); 69 | 70 | struct sockaddr_in client_addr; 71 | /* socklen_t clientAddrLen = sizeof(client_addr); */ 72 | 73 | // ensure buffer is set 74 | memset(buf, 0, sizeof buf); 75 | 76 | // mode is either "netascii", "octet" or "mail" 77 | char mode[8]; 78 | tftp_message m; 79 | 80 | while (1) { 81 | // read contents of dataframe 82 | printf("waiting for packet...\n"); 83 | if (parse_message(socket_desc, &m, &client_addr) != 0) { 84 | printf("failed to parse tftp message\n", errno); 85 | return -1; 86 | } 87 | 88 | if (strcmp(m.request.mode, "octet") == 0) { 89 | FILE *file; 90 | file = fopen(m.request.filename_and_mode, "r"); 91 | if (file == NULL) { 92 | printf("Unable to open the file: %s\n", strerror(errno)); 93 | continue; 94 | } 95 | 96 | transfer_binary_mode(file, socket_desc, &client_addr); 97 | } 98 | } 99 | 100 | return EXIT_SUCCESS; 101 | } 102 | 103 | int parse_message(int socket_desc, tftp_message *m, struct sockaddr_in *client_addr) { 104 | socklen_t clientAddrLen = sizeof(&client_addr); 105 | int bytes_read = recvfrom(socket_desc, m, sizeof(*m), 0, (struct sockaddr *)client_addr, &clientAddrLen); 106 | if (bytes_read < 0) { 107 | printf("Error whilst listening for tftp message: %d", errno); 108 | return -1; 109 | } 110 | 111 | printf("reading message...\n"); 112 | 113 | m->opcode = ntohs(m->opcode); 114 | 115 | switch (m->opcode) { 116 | case OPCODE_RRQ: 117 | /* 2 bytes string 1 byte string 1 byte */ 118 | /* ------------------------------------------------ */ 119 | /* | Opcode | Filename | 0 | Mode | 0 | */ 120 | /* ------------------------------------------------ */ 121 | 122 | // parse mode (positioned after filename, which could be variable length) by using 123 | // pointer arithmatic to move pointer beyond "filename\n" so we can read mode string. 124 | strcpy(m->request.mode, m->request.filename_and_mode + 1 + strlen(m->request.filename_and_mode)); 125 | 126 | if (DEBUG) { 127 | printf("read: root opcode received: %d\n", m->opcode); 128 | printf("read: request filename: %s\n", m->request.filename_and_mode); 129 | printf("read: mode: %s\n", m->request.mode); 130 | } 131 | break; 132 | case OPCODE_ACK: 133 | /* 2 bytes 2 bytes */ 134 | /* --------------------- */ 135 | /* | Opcode | Block # | */ 136 | /* --------------------- */ 137 | // statements 138 | break; 139 | } 140 | 141 | return 0; 142 | } 143 | 144 | void transfer_binary_mode(FILE *src_file, int socket_desc, struct sockaddr_in *client_addr) { 145 | // prepare response message 146 | // Data acts as the data packet that will transfer the files payload 147 | // 2 bytes 2 bytes n bytes 148 | // ---------------------------------- 149 | // | Opcode | Block # | Data | 150 | // ---------------------------------- 151 | 152 | /* uint16_t block_num = htons(1); */ 153 | uint16_t block_num = 0; 154 | 155 | uint16_t stop_sending = 0; 156 | ssize_t contents_len; 157 | uint8_t file_content[512]; 158 | 159 | tftp_message m; 160 | 161 | printf("\nsending file...\n"); 162 | 163 | while (stop_sending == 0) { 164 | block_num++; 165 | 166 | // parse response 167 | if (block_num > 1) { 168 | if (parse_message(socket_desc, &m, client_addr) != 0) { 169 | printf("failed to parse tftp message\n", errno); 170 | return; 171 | } 172 | 173 | if (m.opcode == OPCODE_ACK) { 174 | printf("ack received for block %d, exiting...\n\n", m.ack.block_number); 175 | return; 176 | } 177 | } 178 | 179 | contents_len = fread(file_content, 1, sizeof(file_content), src_file); 180 | // can we send entire file in single data frame? 181 | if (contents_len < 512) { 182 | /* stop_sending = 1; */ 183 | } 184 | 185 | tftp_message m; 186 | m.opcode = htons(OPCODE_DATA); 187 | m.data.block_number = htons(block_num); 188 | 189 | memcpy(m.data.data, file_content, contents_len); 190 | printf("setting block number %d\n", m.data.block_number); 191 | 192 | ssize_t len = sizeof(m.opcode) + sizeof(m.data.block_number) + contents_len; 193 | int bytes_sent = sendto(socket_desc, &m, len, 0, (struct sockaddr *)client_addr, sizeof(*client_addr)); 194 | if (bytes_sent < 0) { 195 | printf("failed to send to client: %s\n", strerror(errno)); 196 | return; 197 | } 198 | 199 | printf("%d bytes sent successfully\n\n", bytes_sent); 200 | } 201 | 202 | return; 203 | } 204 | --------------------------------------------------------------------------------