├── .gitignore ├── LICENSE ├── Makefile ├── README.md └── src ├── net.c ├── net.h ├── recv.c └── send.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Libraries 8 | *.lib 9 | *.a 10 | 11 | # Shared objects (inc. Windows DLLs) 12 | *.dll 13 | *.so 14 | *.so.* 15 | *.dylib 16 | 17 | # Executables 18 | *.exe 19 | *.out 20 | *.app 21 | *.i*86 22 | *.x86_64 23 | *.hex 24 | 25 | a.out 26 | hellsend 27 | hellrecv 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Kia <> 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=clang 2 | CFLAGS=-Wall -ggdb -Weverything # -fsanitize=integer -fsanitize=address -fsanitize=undefined 3 | 4 | all: hellrecv hellsend 5 | 6 | %.o : %.c 7 | $(CC) -c $(CFLAGS) $< -o $@ 8 | 9 | hellrecv: src/recv.o src/net.o 10 | $(CC) $(CFLAGS) -o $@ $^ 11 | 12 | hellsend: src/send.o src/net.o 13 | $(CC) $(CFLAGS) -o $@ $^ 14 | 15 | clean: 16 | rm -f hellsend hellrecv src/*.o 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | hellcat 2 | ======= 3 | 4 | netcat that takes unfair advantage of traffic shaping systems that apply a 5 | higher rate limit (more bandwidth) for the first k bytes (or t seconds, configs 6 | vary) of a TCP connection than for the rest of the connection. This is usually 7 | done to make web browsing feel faster while still throttling big downloads. In 8 | order to exploit this behavior, we send k bytes over a TCP connection, then we 9 | close it and then create another one. That way, data transfer always happens in 10 | the initial regime (the one with a higher rate limit), increasing the average 11 | rate of data transfer. 12 | 13 | You *need* to give the same chunksize value to the receiving end and the 14 | transmitting end otherwise you will have incomplete transfer. 15 | 16 | TODO: transfer the chunksize over the control connection from the sender to the 17 | receiver so you don't have to specify it once on each end. 18 | 19 | -------------------------------------------------------------------------------- /src/net.c: -------------------------------------------------------------------------------- 1 | /* copyright (c) 2014 Kia <> */ 2 | /* this file contains functions that make dealing with the network a bit 3 | easier */ 4 | #include "net.h" 5 | 6 | /* this function just runs send(2) in a loop until the entire buffer is sent or 7 | an error happens */ 8 | 9 | ssize_t send_all(int socket, const uint8_t *buffer, size_t length, int flag) 10 | { 11 | size_t bytes_sent = 0; 12 | size_t bytes_unsent = length; 13 | 14 | ssize_t sent; 15 | 16 | while (bytes_sent < length) 17 | { 18 | sent = send(socket, buffer + bytes_sent, bytes_unsent, flag); 19 | if (sent == -1) 20 | return -1; 21 | bytes_sent += (size_t)sent; 22 | bytes_unsent -= (size_t)sent; 23 | } 24 | 25 | return (ssize_t)bytes_sent; 26 | } 27 | 28 | /* just runs recv(2) in a loop until an error happens or our peer shuts down 29 | the connection */ 30 | 31 | ssize_t recv_all(int socket, uint8_t *buffer, size_t length, int flag) 32 | { 33 | size_t bytes_received = 0; 34 | size_t bytes_unreceived = length; 35 | 36 | ssize_t received; 37 | 38 | while (bytes_received < length) 39 | { 40 | received = recv(socket, buffer + bytes_received, bytes_unreceived, flag); 41 | if (received == -1) 42 | return -1; 43 | if (received == 0) 44 | return 0; 45 | bytes_received += (size_t)received; 46 | bytes_unreceived -= (size_t)received; 47 | } 48 | 49 | return (ssize_t)bytes_received; 50 | } 51 | 52 | 53 | 54 | ssize_t read_all(int fd, uint8_t *buffer, size_t length) 55 | { 56 | size_t bytes_received = 0; 57 | size_t bytes_unreceived = length; 58 | 59 | ssize_t received; 60 | 61 | while (bytes_received < length) 62 | { 63 | received = read(fd, buffer + bytes_received, bytes_unreceived); 64 | if (received == -1) 65 | return -1; 66 | if (received == 0) 67 | return (ssize_t) bytes_received; 68 | bytes_received += (size_t)received; 69 | bytes_unreceived -= (size_t)received; 70 | } 71 | 72 | return (ssize_t)bytes_received; 73 | } 74 | 75 | 76 | ssize_t write_all(int fd, const uint8_t *buffer, size_t count) 77 | { 78 | size_t bytes_sent = 0; 79 | size_t bytes_unsent = count; 80 | 81 | ssize_t sent; 82 | 83 | while (bytes_sent < count) 84 | { 85 | sent = write(fd, buffer + bytes_sent, bytes_unsent); 86 | if (sent == -1) 87 | return -1; 88 | bytes_sent += (size_t)sent; 89 | bytes_unsent -= (size_t)sent; 90 | } 91 | 92 | return (ssize_t)bytes_sent; 93 | } 94 | 95 | 96 | int make_socket(char *node, char *port) { 97 | int rval, fd = -1; 98 | struct addrinfo hints; 99 | struct addrinfo *result, *rp; 100 | 101 | /* let's do fun stuff with getaddrinfo now */ 102 | 103 | memset(&hints, 0, sizeof(struct addrinfo)); 104 | hints.ai_family = AF_UNSPEC; /* either ipv4 or ipv6, we don't care */ 105 | hints.ai_socktype = SOCK_STREAM; /* TCP */ 106 | hints.ai_flags = 0; 107 | hints.ai_protocol = 0; 108 | 109 | rval = getaddrinfo(node, port, &hints, &result); 110 | if (rval != 0) { 111 | fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rval)); 112 | exit(1); 113 | } 114 | /* now we iterate over the lists of results that getaddrinfo returned 115 | until we can successfully make a socket and connect with it */ 116 | for (rp = result; rp != NULL; rp = rp->ai_next) { 117 | fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); 118 | if (fd == -1) { 119 | /* the socket making failed, so we need to do a different 120 | address */ 121 | continue; 122 | } 123 | 124 | /* we made a socket, now we try to connect */ 125 | if (connect(fd, rp->ai_addr, rp->ai_addrlen) != -1) { 126 | break; /* we successfully connected, let's exit this loop */ 127 | } 128 | 129 | close(fd); /* making the socket worked but connect() failed so we 130 | close this socket */ 131 | } 132 | if (rp == NULL) { /* no address worked */ 133 | fprintf(stderr, "Could not connect to %s:%s\n",node, port); 134 | exit(1); 135 | } 136 | freeaddrinfo(result); 137 | return fd; 138 | } 139 | 140 | 141 | 142 | int make_listener(char *port) 143 | { 144 | int rval, fd = -1; 145 | struct addrinfo hints; 146 | struct addrinfo *result, *rp; 147 | int optval = 1; 148 | 149 | /* let's do fun stuff with getaddrinfo now */ 150 | 151 | memset(&hints, 0, sizeof(struct addrinfo)); 152 | hints.ai_family = AF_UNSPEC; /* either ipv4 or ipv6, we don't care */ 153 | hints.ai_socktype = SOCK_STREAM; /* TCP */ 154 | hints.ai_flags = AI_PASSIVE; 155 | hints.ai_protocol = 0; 156 | 157 | 158 | rval = getaddrinfo(NULL, port, &hints, &result); 159 | if (rval != 0) 160 | { 161 | fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rval)); 162 | exit(1); 163 | } 164 | /* now we iterate over the lists of results that getaddrinfo returned 165 | until we can successfully make a socket and connect with it */ 166 | for (rp = result; rp != NULL; rp = rp->ai_next) 167 | { 168 | fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); 169 | setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval); 170 | 171 | if (fd == -1) 172 | { 173 | /* the socket making failed, so we need to do a different 174 | address */ 175 | continue; 176 | } 177 | 178 | /* we made a socket, now we try to bind */ 179 | if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) 180 | { 181 | break; /* we successfully bound, let's exit this loop */ 182 | } 183 | perror("bind bind bind"); 184 | 185 | close(fd); /* making the socket worked but bind() failed so we 186 | close this socket */ 187 | } 188 | if (rp == NULL) /* no address worked */ 189 | { 190 | perror("bind"); 191 | fprintf(stderr, "Could not bind to %s\n", port); 192 | exit(1); 193 | } 194 | freeaddrinfo(result); 195 | listen(fd, 1); 196 | return fd; 197 | } 198 | -------------------------------------------------------------------------------- /src/net.h: -------------------------------------------------------------------------------- 1 | /* copyright (c) 2013 Kia <> */ 2 | 3 | #define _GNU_SOURCE 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | ssize_t send_all(int socket, const uint8_t *buffer, size_t length, int flag); 18 | ssize_t recv_all(int socket, uint8_t *buffer, size_t length, int flag); 19 | ssize_t read_all(int fd, uint8_t *buffer, size_t length); 20 | ssize_t write_all(int fd, const uint8_t *buffer, size_t count); 21 | int make_socket(char *node, char *port); 22 | int make_listener(char *port); 23 | 24 | #define CONT 0xcc 25 | #define DONE 0xdd 26 | -------------------------------------------------------------------------------- /src/recv.c: -------------------------------------------------------------------------------- 1 | /* copyright (c) 2014 Kia <> */ 2 | 3 | /* the functions in this file handle the whole "receiving" thing */ 4 | 5 | #include "net.h" 6 | #include 7 | 8 | 9 | 10 | int main(int argc, char* argv[]) 11 | { 12 | int datafd, controlfd, control_listener, data_listener, listening; 13 | ssize_t rval, rval_send; 14 | uint8_t statusbyte, cont = CONT; 15 | uint8_t *buf; 16 | size_t bufsize, totalsize = 0; 17 | struct timeval start, end; 18 | float elapsed; 19 | 20 | if ((argc != 5) && (argc != 4)) { 21 | fprintf(stderr, "usage: %s chunksize dataport controlport [host]\nchunksize is in bytes\n", argv[0]); 22 | exit(10); 23 | } 24 | 25 | if (argc == 4) { 26 | listening = 1; 27 | } else { 28 | listening = 0; 29 | } 30 | 31 | bufsize = (size_t) atol(argv[1]); 32 | buf = malloc(bufsize); 33 | assert(buf != NULL); 34 | 35 | if (listening == 0) { 36 | controlfd = make_socket(argv[4], argv[3]); 37 | } else { 38 | control_listener = make_listener(argv[3]); 39 | data_listener = make_listener(argv[2]); 40 | controlfd = accept(control_listener, NULL, NULL); 41 | if (controlfd == -1) { 42 | perror("accept on control port"); 43 | exit(1); 44 | } 45 | } 46 | 47 | sleep(1); 48 | gettimeofday(&start, NULL); 49 | 50 | while (1) { 51 | rval = read(controlfd, &statusbyte, 1); 52 | 53 | if (rval != 1) { 54 | perror("read on controlfd"); 55 | exit(1); 56 | } 57 | 58 | if (statusbyte == DONE) { 59 | fprintf(stderr, "received DONE\n"); 60 | gettimeofday(&end, NULL); 61 | elapsed = (end.tv_sec - start.tv_sec) + (end.tv_usec - start.tv_usec) / 1000000; 62 | fprintf(stderr, "transferred %zu bytes in %.3f seconds\n", totalsize, elapsed); 63 | 64 | break; 65 | } 66 | 67 | assert (statusbyte == CONT); 68 | 69 | if (listening == 0) { 70 | datafd = make_socket(argv[4], argv[2]); 71 | } else { 72 | datafd = accept(data_listener, NULL, NULL); 73 | if (datafd == -1) { 74 | perror("accept on data port"); 75 | exit(1); 76 | } 77 | } 78 | 79 | rval = read_all(datafd, buf, bufsize); 80 | 81 | if (rval == -1) { 82 | perror("read on datafd"); 83 | exit(1); 84 | } 85 | 86 | if (rval == 0) { /* eof on datafd */ 87 | fprintf(stderr, "unexpected EOF on datafd\n"); 88 | exit(1); 89 | } 90 | 91 | rval_send = write_all(1, buf, (size_t)rval); 92 | 93 | if (rval_send != rval) { 94 | perror("write on stdout"); 95 | exit(1); 96 | } 97 | 98 | if (listening == 1) { 99 | rval = write(controlfd, &cont, 1); 100 | if (rval != 1) { 101 | perror("write on controlfd"); 102 | exit(1); 103 | } 104 | 105 | } 106 | 107 | totalsize += (size_t) rval; 108 | close(datafd); 109 | } 110 | 111 | close(controlfd); 112 | 113 | return 0; 114 | } 115 | 116 | -------------------------------------------------------------------------------- /src/send.c: -------------------------------------------------------------------------------- 1 | /* copyright (c) 2014 Kia <> */ 2 | 3 | /* the functions in this file handle the whole "sending data out" thing */ 4 | 5 | #include "net.h" 6 | 7 | 8 | 9 | 10 | int main(int argc, char* argv[]) 11 | { 12 | int data_listener, controlfd, control_listener, listening, datafd = -1; 13 | ssize_t rval, numbytes, rval_send; 14 | uint8_t *buf; 15 | size_t bufsize; 16 | uint8_t cont = CONT; 17 | uint8_t done = DONE; 18 | uint8_t statusbyte; 19 | 20 | if ((argc != 4) && (argc != 5)) { 21 | fprintf(stderr, "usage: %s chunksize dataport controlport [host]\nchunksize is in bytes\n", argv[0]); 22 | exit(10); 23 | } 24 | 25 | if (argc == 4) { 26 | listening = 1; 27 | } else { 28 | listening = 0; 29 | } 30 | 31 | 32 | bufsize = (size_t) atol(argv[1]); 33 | buf = malloc(bufsize); 34 | assert(buf != NULL); 35 | 36 | if (listening == 1) { 37 | control_listener = make_listener(argv[3]); 38 | data_listener = make_listener(argv[2]); 39 | controlfd = accept(control_listener, NULL, NULL); 40 | if (controlfd == -1) { 41 | perror("accept on control port"); 42 | exit(1); 43 | } 44 | 45 | } else { 46 | controlfd = make_socket(argv[4], argv[3]); 47 | } 48 | 49 | while (1) { 50 | 51 | numbytes = read_all(0, buf, bufsize); 52 | if (numbytes == -1) { 53 | perror("read on stdin"); 54 | exit(1); 55 | } 56 | if (numbytes == 0) { /* eof on stdin */ 57 | rval = write(controlfd, &done, 1); 58 | if (rval != 1) { 59 | perror("write on controlfd (while exiting, which makes this even more humiliating)"); 60 | exit(1); 61 | } 62 | 63 | break; 64 | } 65 | rval = write(controlfd, &cont, 1); 66 | 67 | if (rval != 1) { 68 | perror("write on controlfd"); 69 | exit(1); 70 | } 71 | 72 | if (listening == 1) { 73 | datafd = accept(data_listener, NULL, NULL); 74 | } else { 75 | datafd = make_socket(argv[4], argv[2]); 76 | } 77 | 78 | 79 | if (datafd == -1) { 80 | perror("data port connect / accept failure?"); 81 | exit(1); 82 | } 83 | rval_send = write_all(datafd, buf, (size_t) numbytes); 84 | 85 | if (rval_send != numbytes) { 86 | perror("incomplete write on datafd"); 87 | exit(1); 88 | } 89 | 90 | rval = close(datafd); 91 | 92 | if (rval != 0) { 93 | perror("close"); 94 | fprintf(stderr,"close returned %d\n", rval); 95 | exit(1); 96 | } 97 | 98 | if (listening == 0) { 99 | rval = read(controlfd, &statusbyte, 1); 100 | if (rval != 1) { 101 | perror("read on controlfd"); 102 | fprintf(stderr, "%d\n", rval); 103 | exit(1); 104 | } 105 | assert(statusbyte == CONT); 106 | } 107 | 108 | 109 | } 110 | 111 | 112 | free(buf); 113 | if (datafd != -1) { 114 | close(datafd); 115 | } 116 | 117 | close(controlfd); 118 | 119 | return 0; 120 | } 121 | 122 | --------------------------------------------------------------------------------