File: tcpecho.c 1 /* 2 The MIT License (MIT) 3 4 Copyright © 2020-2025 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the “Software”), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 /* 26 You can build this command-line app by running 27 28 cc -Wall -s -O3 -flto -o ./tcpecho ./tcpecho.c 29 */ 30 31 #include <arpa/inet.h> 32 #include <fcntl.h> 33 #include <netinet/in.h> 34 #include <stdbool.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <sys/socket.h> 39 #include <sys/types.h> 40 #include <unistd.h> 41 42 #ifdef _WIN32 43 #include <windows.h> 44 #endif 45 46 #ifdef RED_ERRORS 47 #define ERROR_STYLE "\x1b[38;2;204;0;0m" 48 #ifdef __APPLE__ 49 #define ERROR_STYLE "\x1b[31m" 50 #endif 51 #define RESET_STYLE "\x1b[0m" 52 #else 53 #define ERROR_STYLE 54 #define RESET_STYLE 55 #endif 56 57 #define ERROR_LINE(MSG) (ERROR_STYLE MSG RESET_STYLE "\n") 58 59 #define BAD_ALLOC 2 60 61 const char* info = "" 62 "tcpecho [port] [words...]\n" 63 "\n" 64 "\n" 65 "Start a TCP server which always replies with a plain-text line made from\n" 66 "the arguments given after the port number, joined by single spaces.\n" 67 "\n" 68 "\n" 69 "Options, all of which can start with either 1 or 2 dashes:\n" 70 "\n" 71 "\n" 72 " -h show this help message\n" 73 " -help show this help message\n" 74 ""; 75 76 // is_help_option simplifies control-flow for func main 77 bool is_help_option(const char* s) { 78 return (s[0] == '-') && ( 79 strcmp(s, "-h") == 0 || 80 strcmp(s, "-help") == 0 || 81 strcmp(s, "--h") == 0 || 82 strcmp(s, "--help") == 0 83 ); 84 } 85 86 int main(int argc, char** argv) { 87 #ifdef _WIN32 88 setmode(fileno(stdin), O_BINARY); 89 // ensure output lines end in LF instead of CRLF on windows 90 setmode(fileno(stdout), O_BINARY); 91 setmode(fileno(stderr), O_BINARY); 92 #endif 93 94 // handle any of the help options, if given 95 if (argc > 1 && is_help_option(argv[1])) { 96 printf("%s", info); 97 return 0; 98 } 99 100 if (argc < 2) { 101 const char* m = "you forgot the port number as the leading argument"; 102 fprintf(stderr, ERROR_LINE("%s"), m); 103 return 1; 104 } 105 106 char* end; 107 int port = strtol(argv[1], &end, 10); 108 if (*end != 0 || port < 1 || port > 65535) { 109 const char* msg; 110 msg = "isn't a valid port number"; 111 fprintf(stderr, ERROR_LINE("%s %s"), argv[1], msg); 112 msg = "maybe you forgot the port number as the leading argument"; 113 fprintf(stderr, ERROR_LINE("%s"), msg); 114 return 1; 115 } 116 117 // count spaces between words 118 size_t msglen = argc - 2; 119 // count word lengths 120 for (size_t i = 2; i < argc; i++) { 121 msglen += strlen(argv[i]); 122 } 123 // remember the final line-feed 124 msglen++; 125 126 // also remember that strings are ended with an extra null byte 127 char* msg = malloc(msglen + 1); 128 if (msg == NULL) { 129 fprintf(stderr, ERROR_LINE("out of memory")); 130 exit(BAD_ALLOC); 131 } 132 133 for (size_t i = 2, pos = 0; i < argc; i++) { 134 if (i > 2) { 135 msg[pos] = ' '; 136 pos++; 137 } 138 139 for (size_t j = 0; argv[i][j] != 0; j++) { 140 msg[pos] = argv[i][j]; 141 pos++; 142 } 143 } 144 145 msg[msglen - 1] = '\n'; 146 msg[msglen] = 0; 147 148 int fd = socket(AF_INET, SOCK_STREAM, 0); 149 if (fd < 0 || fcntl(fd, F_GETFD) == -1) { 150 free(msg); 151 fprintf(stderr, ERROR_LINE("can't open socket connection")); 152 return 1; 153 } 154 155 struct sockaddr_in srv; 156 memset(&srv, 0, sizeof(srv)); 157 srv.sin_family = AF_INET; 158 srv.sin_addr.s_addr = htonl(INADDR_ANY); 159 srv.sin_port = htons(port); 160 161 if (bind(fd, (struct sockaddr*)&srv, sizeof(srv)) != 0) { 162 free(msg); 163 fprintf(stderr, ERROR_LINE("can't open socket connection")); 164 return 1; 165 } 166 167 if (listen(fd, 1) != 0) { 168 free(msg); 169 fprintf(stderr, ERROR_LINE("can't listen on open connection")); 170 return 1; 171 } 172 173 while (true) { 174 int conn = accept(fd, NULL, NULL); 175 send(conn, msg, msglen, 0); 176 close(conn); 177 } 178 179 free(msg); 180 return 0; 181 }