├── .gitignore ├── Makefile ├── README.md ├── client.c ├── common.c ├── common.h ├── config_def.h └── server.c /.gitignore: -------------------------------------------------------------------------------- 1 | /client 2 | /server 3 | /config.h 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -O2 2 | 3 | all: client server 4 | 5 | client: client.c common.c config.h common.h 6 | $(CC) $(CFLAGS) $< common.c -o $@ 7 | 8 | server: server.c common.c config.h common.h 9 | $(CC) $(CFLAGS) common.c $< -o $@ 10 | 11 | config.h: config_def.h 12 | cp $< $@ 13 | 14 | .PHONY: all 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TCP over HTTP 2 | 3 | This is TCP over HTTP 4 | 5 | ## Why? 6 | 7 | My school filters the internet quite a bit, but they're weird about it. 8 | 9 | * HTTPS is completely filtered. The network has a custom signing authority that 10 | allows them to view and filter any TLS data they see. 11 | * Many other ports are completely banned, most notably port 22 (SSH). 12 | * The school can detect if you're using a VPN, and will disable your WiFi for 5 13 | minutes if you do. Tor does work with bridges, although it's quite slow and 14 | probably not very good for the network. 15 | * The school will also block protocols, not just ports, so moving SSH to some 16 | other port won't work. 17 | * Despite this, however, many protocols remain unblocked. Most notably, VNC, 18 | which would allow you to connect directly to a home desktop as long as you're 19 | fine with the school district being able to see your screen and monitor your 20 | keystrokes at all times. 21 | * In addition, many other ports are completely unfiltered. Port 25 (can be used 22 | for email spam) is allowed, as is port 70 (Gopher), port 1965 (Gemini), and most 23 | importantly, port 80 (HTTP). 24 | 25 | This means that if we can encode a VPN into one of these unblocked protocols, we 26 | can bypass all the filters with a fast internet connection. This repo obviously 27 | encodes TCP into HTTP. 28 | 29 | ## How does it work? 30 | 31 | From RFC 9110: 32 | 33 | > All responses, regardless of the status code (including interim responses) can 34 | > be sent at any time after a request is received, even if the request is not 35 | > yet complete. A response can complete before its corresponding request is 36 | > complete (Section 6.1). Likewise, clients are not expected to wait any 37 | > specific amount of time for a response. Clients (including intermediaries) 38 | > might abandon a request if the response is not received within a reasonable 39 | > period of time. 40 | 41 | This means HTTP servers can send a response before the client even finishes 42 | sending the request, so this interaction is perfectly valid: 43 | 44 | > GET / HTTP/1.1 45 | < HTTP/1.1 500 Internal Server Error 46 | < Content-Length: 13 47 | < 48 | < We screwed up 49 | > Host: example.com 50 | > 51 | 52 | As is this one: 53 | 54 | > POST / HTTP/1.1 55 | > Host: example.com 56 | > Content-Length: 9223372036854775807 57 | > 58 | < HTTP/1.1 500 Internal Server Error 59 | < Content-Length: 9223372036854775807 60 | < 61 | 62 | Note that after this interaction, the client is both sending and receiving a 63 | message body. Any new data sent over this TCP stream can be interpreted as 64 | perfectly valid HTTP. 65 | 66 | Here's a diagram explaining how this works: 67 | 68 | ------------ ------------ 69 | | | | | 70 | | SSH Client | | SSH Daemon | 71 | | | | | 72 | ------------ ------------ 73 | ^ ^ 74 | | | 75 | TCP TCP 76 | | | 77 | v v 78 | ------------ ------------ 79 | | | | | 80 | | netcat | | netcat | 81 | | | | | 82 | ------------ ------------ 83 | ^ ^ 84 | | | 85 | Double ended pipe Double ended pipe 86 | | | 87 | v v 88 | ------------ ------------ 89 | | | --POST body-> | | 90 | | Client | | Server | 91 | | | <-500 body--- | | 92 | ------------ ------------ 93 | 94 | ## How do I configure this 95 | 96 | Change `config.h`. I blame [suckless](https://suckless.org) for this. 97 | -------------------------------------------------------------------------------- /client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "config.h" 13 | #include "common.h" 14 | 15 | static int create_proxy(char *ip_text, int *fd_ret); 16 | static int copy(int src, int dst); 17 | 18 | int main(int argc, char **argv) { 19 | char ip_text[30]; 20 | int serverfd; 21 | struct sockaddr_in addr; 22 | 23 | if ((serverfd = socket(PF_INET, SOCK_STREAM, 0)) < 0) { 24 | perror("Failed to create socket"); 25 | return 1; 26 | } 27 | addr.sin_family = AF_INET; 28 | addr.sin_port = htons(CONFIG_CLIENT_PORT); 29 | addr.sin_addr.s_addr = INADDR_ANY; 30 | if (bind(serverfd, (struct sockaddr *) &addr, sizeof addr) < 0) { 31 | perror("Failed to bind socket"); 32 | } 33 | if (listen(serverfd, 5) == -1) { 34 | perror("Failed to listen on socket"); 35 | return 1; 36 | } 37 | 38 | for (;;) { 39 | int clientfd; 40 | struct sockaddr_in addr_buff; 41 | socklen_t len_buff; 42 | pid_t pid; 43 | int forwardfd; 44 | clientfd = accept(serverfd, 45 | (struct sockaddr *) &addr_buff, &len_buff); 46 | if (clientfd < 0) { 47 | perror("Failed to accept connection"); 48 | continue; 49 | } 50 | 51 | pid = fork(); 52 | switch (pid) { 53 | case -1: 54 | perror("Failed to fork"); 55 | close(clientfd); 56 | break; 57 | case 0: 58 | if (create_proxy(CONFIG_SERVER_HOST, &forwardfd)) { 59 | fputs("Failed to create proxy\n", stderr); 60 | return 1; 61 | } 62 | pid = fork(); 63 | switch (pid) { 64 | case -1: 65 | perror("Failed to fork proxies"); 66 | return 1; 67 | case 0: 68 | dup2(clientfd, 0); 69 | dup2(forwardfd, 1); 70 | execlp("cat", "cat", NULL); 71 | return 1; 72 | default: 73 | dup2(forwardfd, 0); 74 | dup2(clientfd, 1); 75 | execlp("cat", "cat", NULL); 76 | return 1; 77 | } 78 | break; 79 | default: 80 | close(clientfd); 81 | break; 82 | } 83 | } 84 | return 1; 85 | } 86 | 87 | static int create_proxy(char *ip_text, int *fd_ret) { 88 | struct sockaddr_in addr; 89 | int fd; 90 | char buff[1000]; 91 | 92 | addr.sin_family = AF_INET; 93 | if (inet_aton(ip_text, &addr.sin_addr) == 0) { 94 | puts("Invalid address"); 95 | return 0; 96 | } 97 | addr.sin_port = htons(CONFIG_SERVER_PORT); 98 | 99 | fd = socket(PF_INET, SOCK_STREAM, 0); 100 | if (fd < 0) { 101 | perror("Failed to create socket"); 102 | return 1; 103 | } 104 | if (connect(fd, (struct sockaddr *) &addr, sizeof addr) == -1) { 105 | perror("Failed to connect send socket"); 106 | return 1; 107 | } 108 | 109 | if (dputs(fd, "POST / HTTP/1.1\r\n" 110 | "Host: " CONFIG_HTTP_HOST "\r\n" 111 | "Content-Length: 9223372036854775807\r\n" 112 | "\r\n")) { 113 | fputs("Failed to send HTTP header\n", stderr); 114 | return 1; 115 | } 116 | 117 | if (read(fd, buff, sizeof buff) < 0) { 118 | perror("Failed to receive header"); 119 | return 1; 120 | } 121 | 122 | *fd_ret = fd; 123 | return 0; 124 | } 125 | -------------------------------------------------------------------------------- /common.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "common.h" 7 | 8 | void waste_data(int fd) { 9 | char buff[65536]; 10 | if (read(fd, buff, sizeof buff) < 0) { 11 | fputs("Warning: waste read failed\n", stderr); 12 | } 13 | } 14 | 15 | int write_resilient(int fd, void *buff, size_t len) { 16 | char *start; 17 | start = buff; 18 | while (len > 0) { 19 | ssize_t written_len; 20 | written_len = write(fd, start, len); 21 | if (written_len < 0) { 22 | switch (errno) { 23 | case EBADF: case EWOULDBLOCK: case EDQUOT: case EFAULT: 24 | case EFBIG: case EINVAL: case EIO: case ENOSPC: 25 | case EPERM: case EPIPE: 26 | return 1; 27 | default: 28 | continue; 29 | } 30 | } 31 | else { 32 | len -= written_len; 33 | start += written_len; 34 | } 35 | } 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /common.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* This function reads and discards data from fd. It's used to ignore the HTTP 4 | * response header. */ 5 | void waste_data(int fd); 6 | int write_resilient(int fd, void *buff, size_t len); 7 | 8 | static inline int dputs(int fd, char *str) { 9 | return write_resilient(fd, str, strlen(str)); 10 | } 11 | 12 | #define STR_LITERAL(data) #data 13 | #define STR(macro) STR_LITERAL(macro) 14 | -------------------------------------------------------------------------------- /config_def.h: -------------------------------------------------------------------------------- 1 | /* The host that gets sent in HTTP requests */ 2 | #define CONFIG_HTTP_HOST "proxy.natechoe.dev" 3 | 4 | #define CONFIG_CLIENT_PORT 9000 5 | 6 | #define CONFIG_SERVER_HOST "127.0.0.1" 7 | #define CONFIG_SERVER_PORT 9001 8 | 9 | /* Change these to the destination of the proxy */ 10 | #define CONFIG_DEST_HOST "127.0.0.1" 11 | #define CONFIG_DEST_PORT 9002 12 | 13 | /* Ideally these are absolute paths, but not every system is the same. */ 14 | #define CONFIG_CLIENT_NC "nc" 15 | #define CONFIG_SERVER_NC "nc" 16 | -------------------------------------------------------------------------------- /server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "config.h" 12 | #include "common.h" 13 | 14 | static void start_conn(int fd); 15 | static void sigchld_action(int signum); 16 | 17 | int main() { 18 | int sockfd; 19 | struct sockaddr_in addr; 20 | struct pollfd pollfd; 21 | struct sigaction act; 22 | sigset_t sigset; 23 | 24 | sigemptyset(&sigset); 25 | act.sa_handler = sigchld_action; 26 | act.sa_mask = sigset; 27 | act.sa_flags = 0; 28 | if (sigaction(SIGCHLD, &act, NULL) == -1) { 29 | perror("Warning: sigaction() failed, defunct processes will " 30 | "spawn"); 31 | } 32 | 33 | if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0) { 34 | perror("Failed to create socket"); 35 | return 1; 36 | } 37 | addr.sin_family = AF_INET; 38 | addr.sin_port = htons(CONFIG_SERVER_PORT); 39 | addr.sin_addr.s_addr = INADDR_ANY; 40 | if (bind(sockfd, (struct sockaddr *) &addr, sizeof addr) == -1) { 41 | perror("Failed to bind socket"); 42 | return 1; 43 | } 44 | if (listen(sockfd, 2) == -1) { 45 | perror("Failed to listen on socket"); 46 | return 1; 47 | } 48 | 49 | pollfd.fd = sockfd; 50 | pollfd.events = POLLIN; 51 | 52 | for (;;) { 53 | struct sockaddr_in addr_buff; 54 | socklen_t len_buff; 55 | int client_fd; 56 | 57 | client_fd = accept(sockfd, (struct sockaddr *) &addr_buff, 58 | &len_buff); 59 | 60 | start_conn(client_fd); 61 | close(client_fd); 62 | } 63 | 64 | return 0; 65 | } 66 | 67 | static void start_conn(int fd) { 68 | pid_t pid; 69 | char buff[1000]; 70 | 71 | /* Swallow header */ 72 | if (read(fd, buff, sizeof buff) < 0) { 73 | perror("Failed to start connection"); 74 | return; 75 | } 76 | if (dputs(fd, "HTTP/1.1 500 Internal Server Error\r\n" 77 | "Content-Length: 9223372036854775807\r\n" 78 | "\r\n")) { 79 | fputs("Failed to send response header\n", stderr); 80 | return; 81 | } 82 | 83 | 84 | pid = fork(); 85 | if (pid == -1) { 86 | return; 87 | } 88 | if (pid == 0) { 89 | dup2(fd, 1); 90 | dup2(fd, 0); 91 | execlp(CONFIG_SERVER_NC, CONFIG_SERVER_NC, "--", 92 | CONFIG_DEST_HOST, STR(CONFIG_DEST_PORT), NULL); 93 | exit(EXIT_FAILURE); 94 | } 95 | } 96 | 97 | static void sigchld_action(int signum) { 98 | wait(NULL); 99 | } 100 | --------------------------------------------------------------------------------