Commit 2fb75286 authored by Adam Blank's avatar Adam Blank
Browse files

Initial commit

parents
No related merge requests found
Pipeline #23609 failed with stage
Showing with 827 additions and 0 deletions
+827 -0
*.o
proxy
proxy:
script: "/testers/cs24/project07/proxy/test"
CC = clang-with-asan
CFLAGS = -Wall -Wextra -Iinclude
LDFLAGS = -lpthread
all: bin/proxy
run: bin/proxy
$(shell killall -9 proxy 2> /dev/null || true)
./bin/proxy $(shell cs24-port)
test-http:
bash -c "./tests/sequential.sh tests/http-sites"
test-repeat:
bash -c "./tests/sequential.sh tests/repeat-sites"
test-concurrent:
bash -c "timeout -s 9 8s ./tests/concurrent.sh"
out/%.o: src/%.c
$(CC) $(CFLAGS) -c $^ -o $@
bin/proxy: out/proxy.o out/buffer.o out/client_thread.o
$(CC) $(CFLAGS) $^ -o $@
clean:
rm -f out/*.o bin/proxy
#ifndef BUFFER_H
#define BUFFER_H
#include <stddef.h>
#include <stdint.h>
/* Growable byte buffer */
typedef struct buffer_t buffer_t;
/* Allocate the data necessary for the buffer and returns it */
buffer_t *buffer_create(size_t initial_capacity);
/* Free the buffer */
void buffer_free(buffer_t *);
/* Get the underlying bytes of the buffer */
uint8_t *buffer_data(buffer_t *);
/* '\0'-terminate the buffer so it can be interpreted as a string */
char *buffer_string(buffer_t *);
/* Get vector length */
size_t buffer_length(buffer_t *);
/* Append char to buffer */
void buffer_append_char(buffer_t *, char c);
/* Append byte array to buffer */
void buffer_append_bytes(buffer_t *, uint8_t *bytes, size_t length);
#endif // BUFFER_H
#ifndef CLIENT_THREAD_H
#define CLIENT_THREAD_H
/* If you want verbose output on error,
* #define VERBOSE. */
#ifdef VERBOSE
# define verbose_printf(...) printf(__VA_ARGS__)
#else
# define verbose_printf(...)
#endif
/* Given a clientfd, handles the HTTP request sent on
* cfd and sends the result back on cfd */
void *handle_request(void *cfd);
#endif
#include "buffer.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
struct buffer_t {
uint8_t *data;
size_t length;
size_t capacity;
};
/* Resizes buffer to have given new capacity */
static void buffer_resize(buffer_t *buf, size_t new_capacity) {
if (new_capacity <= buf->capacity) {
/* Already large enough */
return;
}
/* Grow by at least a factor of 2 */
size_t grown_capacity = buf->capacity * 2;
if (new_capacity < grown_capacity) {
new_capacity = grown_capacity;
}
buf->data = realloc(buf->data, sizeof(uint8_t[new_capacity]));
assert(buf->data != NULL);
buf->capacity = new_capacity;
}
buffer_t *buffer_create(size_t initial_capacity) {
buffer_t *buf = malloc(sizeof(*buf));
assert(buf != NULL);
if (initial_capacity == 0) {
initial_capacity = 1;
}
buf->data = NULL;
buf->capacity = 0;
buffer_resize(buf, initial_capacity);
buf->length = 0;
return buf;
}
void buffer_free(buffer_t *buf) {
if (buf == NULL) {
return;
}
free(buf->data);
free(buf);
}
uint8_t *buffer_data(buffer_t *buf) {
assert(buf != NULL);
return buf->data;
}
char *buffer_string(buffer_t *buf) {
buffer_resize(buf, buffer_length(buf) + 1);
buf->data[buf->length] = '\0';
return (char *) buf->data;
}
size_t buffer_length(buffer_t *buf) {
assert(buf != NULL);
return buf->length;
}
void buffer_append_char(buffer_t *buf, char c) {
buffer_append_bytes(buf, (uint8_t *) &c, 1);
}
void buffer_append_bytes(buffer_t *buf, uint8_t *bytes, size_t length) {
buffer_resize(buf, buffer_length(buf) + length);
/* memcpy is safe here since we've ensured our buffer is large enough */
memcpy(buf->data + buf->length, bytes, length);
buf->length += length;
}
#define _GNU_SOURCE
#include <assert.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include "client_thread.h"
#include "buffer.h"
#define BUFFER_SIZE 8192
static int open_client_fd(char *hostname, int port, int *err) {
int client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd < 0) {
return -1;
}
/* Fill in the server's IP address and port */
struct addrinfo *address;
char port_str[sizeof("65535")];
sprintf(port_str, "%d", port);
*err = getaddrinfo(hostname, port_str, NULL, &address);
if (*err != 0) {
return -2;
}
/* Establish a connection with the server */
bool success = connect(client_fd, address->ai_addr, address->ai_addrlen) >= 0;
freeaddrinfo(address);
return success ? client_fd : -1;
}
/* Writes a string to a file descriptor, returns whether successful */
static bool write_string(int fd, char *str) {
return write(fd, str, strlen(str)) >= 0;
}
/* Sends a status message to client with the status line specified by
* status with a message body described by msg.
* Returns whether successful */
static bool send_status_code(int client_fd, char *status, char *msg) {
char *format =
"HTTP/1.0 %s\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n"
"\r\n"
"<html>"
"<head><title>%s</title></head>"
"<body>%s</body>"
"</html>";
/* Fill out the response template and send it to the client */
char response[strlen(format) + 2 * strlen(status) + strlen(msg)];
sprintf(response, format, status, status, msg);
return write_string(client_fd, response);
}
/* Opens connection to full_host and returns the file descriptor or
* returns -1 on error */
static int open_server_connection(int client_fd, char *full_host) {
int port;
char *port_str = strchr(full_host, ':');
if (port_str == NULL) {
port = 80;
}
else {
/* Host string was separated into hostname and port */
full_host[port_str - full_host] = '\0';
port = atoi(port_str + 1);
if (port <= 0 || port > 65535) {
verbose_printf("Malformed request string: Invalid port\n");
return -1;
}
}
/* Open connection to requested server */
int server_error;
int server_fd = open_client_fd(full_host, port, &server_error);
if (server_fd == -1) {
verbose_printf("open_client_fd error: %s\n", strerror(errno));
return -1;
}
if (server_fd == -2) {
switch (server_error) {
case EAI_FAIL:
case EAI_NONAME:
/* Don't bother checking exit code, since we are returning error
* afterwards anyway */
send_status_code(client_fd, "502 Bad Gateway",
"DNS could not resolve address.");
return -1;
case EAI_AGAIN:
/* Don't bother checking exit code, since we are returning error
* afterwards anyway */
send_status_code(client_fd, "502 Bad Gateway",
"DNS temporarily could not resolve address.");
return -1;
case EAI_NODATA:
/* Don't bother checking exit code, since we are returning error
* afterwards anyway */
send_status_code(client_fd, "502 Bad Gateway",
"DNS could has no network addresses for host.");
return -1;
}
verbose_printf("getaddrinfo error: %s\n", gai_strerror(server_error));
return -1;
}
return server_fd;
}
/* Send get request to server, returns whether successful */
static bool send_get_header(int server_fd, char *path) {
return write_string(server_fd, "GET ") &&
write_string(server_fd, path) &&
write_string(server_fd, " HTTP/1.0\r\n");
}
/* Reads a line from fd until a \r\n is reached. Returns an allocated
* buffer that must be freed by the user containing the line read in upon
* success. Returns NULL on error. */
static buffer_t *read_full_line(int fd) {
buffer_t *line = buffer_create(BUFFER_SIZE);
/* Read until we reach \r\n */
char c = '\0', last_c;
do {
last_c = c;
ssize_t chars_read = read(fd, &c, sizeof(c));
if (chars_read <= 0) {
if (chars_read < 0) {
/* Error occurred */
verbose_printf("Read error: %s\n", strerror(errno));
}
buffer_free(line);
return NULL;
}
buffer_append_char(line, c);
} while (!(last_c == '\r' && c == '\n'));
return line;
}
static bool starts_with(char *str, char *prefix) {
return strncmp(str, prefix, strlen(prefix)) == 0;
}
/* Produces a GET header from client's GET header
* Sets *full_host to the 'host:port' string specified in the GET
* Sets *path to the part of the GET request after the port, excluding
* the leading /
* *full_host and *path must be freed by the user if function returns 0.
* Returns whether successful. */
static bool make_get_header(int client_fd, char **full_host, char **path) {
*full_host = NULL;
*path = NULL;
/* Read the first line (with the GET request) separately */
buffer_t *buf = read_full_line(client_fd);
if (buf == NULL) {
verbose_printf("No request string\n");
goto MALFORMED_ERROR;
}
/* Parse first line. We are expecting one of a few cases:
* GET http://<HOST>/[<PATH>[/]] HTTP/..
* GET http://<HOST>:#..#/[<PATH>[/]] HTTP/..
*
* We reject any other request (and terminate the connection),
* because we believe it to be malformed.
*/
char *data = buffer_string(buf);
char *prefix = strtok(data, " ");
sleep(1);
char *url = strtok(NULL, " ");
char *version = strtok(NULL, " ");
if (prefix == NULL || url == NULL || version == NULL || strtok(NULL, " ") != NULL) {
verbose_printf("Malformed request string: GET requests have"
" three parts\n");
goto MALFORMED_ERROR;
}
if (strcmp(prefix, "GET") != 0) {
verbose_printf("Unsupported request string: This proxy only"
" handles GET requests\n");
goto NOT_IMPLEMENTED_ERROR;
}
if (!starts_with(version, "HTTP/")) {
verbose_printf("Malformed request string: The third part of the"
" GET request should be an HTTP version\n");
goto MALFORMED_ERROR;
}
if (!starts_with(url, "http://")) {
verbose_printf("Malformed request string: The URL of the request"
" should start with 'http://'\n");
goto MALFORMED_ERROR;
}
char *host = url + strlen("http://");
/* Allocate path separately so we can free line buffer.
* The path starts at the first '/' in the URL.
* If there is no '/' (e.g. "http://ucla.edu"), the path is just "/". */
char *path_start = strchr(host, '/');
*path = strdup(path_start == NULL ? "/" : path_start);
assert(*path != NULL);
/* Copy host, so that we have a separate copy for later */
if (path_start != NULL) {
*path_start = '\0';
}
*full_host = strdup(host);
assert(*full_host != NULL);
printf("Handling Request: %s%s\n", *full_host, *path);
buffer_free(buf);
return true;
MALFORMED_ERROR:
/* Don't bother checking exit code, since we are returning error
* afterwards anyway */
send_status_code(client_fd, "400 Bad Request",
"Invalid request sent to proxy.");
goto ERROR;
NOT_IMPLEMENTED_ERROR:
/* Don't bother checking exit code, since we are returning error
* afterwards anyway */
send_status_code(client_fd, "501 Not Implemented",
"Invalid request sent to proxy.");
goto ERROR;
ERROR:
buffer_free(buf);
free(*path);
free(*full_host);
return false;
}
/* To be called after make_get_header. Reads the remaining headers from
* clientfd and sends them to serverfd after modification as follows:
*
* All Keep-Alive headers are dropped
* Connection headers have their value replaced with 'close'
* Proxy-Connection headers have their value replaced with 'close'
* If a Host header is not found, a Host header is added
*
* Returns whether successful
*/
static bool filter_rest_headers(int client_fd, int server_fd, char *host) {
bool sent_host_header = false, sent_connection_header = false;
while (true) {
buffer_t *buf = read_full_line(client_fd);
/* read_full_line() errored out */
if (buf == NULL) {
verbose_printf("Malformed header: Not terminated by new line\n");
return false;
}
char *line = buffer_string(buf);
/* Detect end of header (make sure we sent host line) */
if (strcmp(line, "\r\n") == 0) {
buffer_free(buf);
break;
}
/* Remove Keep-Alive line */
if (starts_with(line, "Keep-Alive:")) {
buffer_free(buf);
continue;
}
/* Deal with host line (if we recieve one */
if (starts_with(line, "Host:")) {
sent_host_header = true;
}
/* Connection: * -> Connection: close */
else if (starts_with(line, "Connection:")) {
line = "Connection: close\r\n";
sent_connection_header = 1;
}
/* Proxy-Connection: * -> Proxy-Connection: close */
else if (starts_with(line, "Proxy-Connection:")) {
line = "Proxy-Connection: close\r\n";
}
/* Send line to server */
bool success = write_string(server_fd, line);
buffer_free(buf);
if (!success) {
return false;
}
}
/* Done sending headers. Make sure the necessary headers were sent */
if (!sent_host_header) {
bool success = write_string(server_fd, "Host: ") &&
write_string(server_fd, host) &&
write_string(server_fd, "\r\n");
if (!success) {
return false;
}
}
if (!sent_connection_header) {
if (!write_string(server_fd, "Connection: close\r\n")) {
return false;
}
}
return write_string(server_fd, "\r\n");
}
/* Sends the server's response to the client.
* Returns whether successful */
static bool send_response(int client_fd, int server_fd) {
/* Loop until server sends an EOF */
while (true) {
uint8_t buf[BUFFER_SIZE];
ssize_t bytes_read = read(server_fd, buf, sizeof(buf));
if (bytes_read < 0) {
verbose_printf("read error: %s\n", strerror(errno));
return false;
}
/* Server sent EOF */
if (bytes_read == 0) {
return true;
}
ssize_t bytes_written = write(client_fd, buf, bytes_read);
if (bytes_written < 0) {
return false;
}
}
}
void *handle_request(void *cfd) {
int client_fd = *(int *) cfd;
free(cfd);
char *host = NULL, *path = NULL;
if (!make_get_header(client_fd, &host, &path)) {
goto CLIENT_ERROR;
}
/* Establish connection with requested server */
int server_fd = open_server_connection(client_fd, host);
if (server_fd < 0) {
goto CLIENT_ERROR;
}
/* Send GET request to server */
if (!send_get_header(server_fd, path)) {
goto SERVER_ERROR;
}
/* Modify and send request headers to ensure no persistent connections and
* ensure the presence of a Host header */
if (!filter_rest_headers(client_fd, server_fd, host)) {
verbose_printf("filter_rest_headers error: %s\n", strerror(errno));
goto SERVER_ERROR;
}
/* Forward response from server to client, and store the response in the
* cache if possible */
if (!send_response(client_fd, server_fd)) {
verbose_printf("send_reponse error: %s\n", strerror(errno));
/* Fall through, since we're done anyway */
}
close(server_fd);
/* Close the write end of the client socket and wait for it to send EOF. */
if (shutdown(client_fd, SHUT_WR) < 0) {
verbose_printf("shutdown error: %s\n", strerror(errno));
goto CLIENT_ERROR;
}
uint8_t discard_buffer[BUFFER_SIZE];
if (read(client_fd, discard_buffer, sizeof(discard_buffer)) < 0) {
verbose_printf("read error: %s\n", strerror(errno));
goto CLIENT_ERROR;
}
close(client_fd);
free(host);
free(path);
return NULL;
SERVER_ERROR:
verbose_printf("Error in writing to server\n");
close(server_fd);
CLIENT_ERROR:
close(client_fd);
free(host);
free(path);
return NULL;
}
#include <assert.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include "client_thread.h"
/* Maximum number of connections to queue up */
#define LISTENQ 1024
static int open_listen_fd(int port) {
/* Create a socket descriptor */
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0) {
return -1;
}
/* Eliminates "Address already in use" error from bind. */
int value = 1;
if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)) < 0) {
return -1;
}
/* listen_fd will be an endpoint for all requests to port
on any IP address for this host */
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(port);
if (bind(listen_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {
return -1;
}
/* Make it a listening socket ready to accept connection requests */
if (listen(listen_fd, LISTENQ) < 0) {
return -1;
}
return listen_fd;
}
static void cleanup(void) {
}
static void sigint_handler(int sig) {
(void) sig;
exit(0);
}
static void usage(char *program) {
printf("Usage: %s <port>\n", program);
exit(1);
}
int main(int argc, char *argv[]) {
/* Ignore broken pipes */
signal(SIGPIPE, SIG_IGN);
/* Stop process when CTRL+C is pressed */
signal(SIGINT, sigint_handler);
if (argc != 2) {
usage(argv[0]);
}
int port = atoi(argv[1]);
if (port <= 0 || port > 65535) {
usage(argv[0]);
}
/* Open listen socket */
int listen_fd = open_listen_fd(port);
if (listen_fd < 0) {
perror("Listen error");
return 1;
}
/* Register cleanup code to run at exit */
if (atexit(cleanup) != 0) {
printf("Could not register clean up function\n");
return 1;
}
printf("Proxy listening on port %d\n", port);
while (true) {
int *cfd = malloc(sizeof(int));
assert(cfd != NULL);
*cfd = accept(listen_fd, NULL, NULL);
if (*cfd == -1) {
perror("Accept error");
free(cfd);
continue;
}
handle_request(cfd);
}
}
# Start new proxy
make run &
sleep 1
# Start HTTP servers
#kill -9 $(ps ax | grep simple-http | grep python | sed -e "s/\([0-9]\+\) .*/\1/") > /dev/null
./tests/simple-http.py &
sleep 1
PROXY_PORT=$(cs24-port)
slow=compute-cpu2.cms.caltech.edu:8080
curl --proxy localhost:$PROXY_PORT --silent --output downloads/${slow//\//_} http://${slow} &
sleep 1
fast=compute-cpu2.cms.caltech.edu:8081
curl --proxy localhost:$PROXY_PORT --silent --output downloads/${fast//\//_} http://${fast}
kill -9 $(ps ax | grep simple-http | grep python | sed -e "s/\([0-9]\+\) .*/\1/") > /dev/null
killall -9 proxy > /dev/null
echo -e "\u001b[32;1mSuccess.\u001b[0m"
example.com
courses.cms.caltech.edu/cs24/19fa/
www.ucla.edu
apache.org
caltechtotem.github.io
lloyd.caltech.edu/lloyd/index
page.caltech.edu
web.mit.edu/
www.eecs.mit.edu/
www.myflorida.com
web.mit.edu:80/a
web.mit.edu:80/b
web.mit.edu:80/c
web.mit.edu:80/d
web.mit.edu:80/e
web.mit.edu:80/f
web.mit.edu:80/g
web.mit.edu:80/h
web.mit.edu:80/i
web.mit.edu:80/j
web.mit.edu:80/k
web.mit.edu:80/l
web.mit.edu:80/m
web.mit.edu:80/n
web.mit.edu:80/o
web.mit.edu:80/p
web.mit.edu:80/q
web.mit.edu:80/r
web.mit.edu:80/s
web.mit.edu:80/t
web.mit.edu:80/u
web.mit.edu:80/v
web.mit.edu:80/w
web.mit.edu:80/x
web.mit.edu:80/y
web.mit.edu:80/z
web.mit.edu:80/a1
web.mit.edu:80/b1
web.mit.edu:80/c1
web.mit.edu:80/d1
web.mit.edu:80/e1
web.mit.edu:80/f1
web.mit.edu:80/g1
web.mit.edu:80/h1
web.mit.edu:80/i1
web.mit.edu:80/j1
web.mit.edu:80/k1
web.mit.edu:80/l1
web.mit.edu:80/m1
web.mit.edu:80/n1
web.mit.edu:80/o1
web.mit.edu:80/p1
web.mit.edu:80/q1
web.mit.edu:80/r1
web.mit.edu:80/s1
web.mit.edu:80/t1
web.mit.edu:80/u1
web.mit.edu:80/v1
web.mit.edu:80/w1
web.mit.edu:80/x1
web.mit.edu:80/y1
web.mit.edu:80/z1
get_site() {
site=$1;
curl --proxy localhost:${PROXY_PORT} --silent --output downloads/${site//\//_} http://${site};
if ! diff <(cat downloads/${site//\//_}) <(curl --silent http://${site}); then
echo -e "\u001b[31mFailed $site.\u001b[0m";
exit 1
fi
}
# Start new proxy
make run &
PROXY_PORT=$(cs24-port)
sleep 1
pids=""
for site in $(cat $1); do
get_site $site &
pids="$pids $!"
done
ERROR=false
for pid in $pids; do
wait $pid || ERROR=true;
done
if ! $ERROR; then
echo -e "\u001b[32;1mSuccess.\u001b[0m"
fi
#!/usr/bin/env python3
# Reflects the requests from HTTP methods GET, POST, PUT, and DELETE
# Written by Nathan Hamiel (2010)
import time
import _thread as thread
from http.server import HTTPServer, BaseHTTPRequestHandler
from optparse import OptionParser
class SlowRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
time.sleep(10000)
class FastRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.end_headers()
def main():
def slow():
slow = HTTPServer(('compute-cpu2.cms.caltech.edu', 8080), SlowRequestHandler)
slow.serve_forever()
def fast():
fast = HTTPServer(('compute-cpu2.cms.caltech.edu', 8081), FastRequestHandler)
fast.serve_forever()
t1 = thread.start_new_thread(slow, ())
fast()
if __name__ == "__main__":
parser = OptionParser()
parser.usage = ("Creates an http-server that will echo out any GET or POST parameters\n"
"Run:\n\n"
" simple-http.py")
(options, args) = parser.parse_args()
main()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment