aboutsummaryrefslogtreecommitdiff
path: root/pa3_proxy/proxy.c
diff options
context:
space:
mode:
authorJack Sangdahl <[email protected]>2024-11-16 19:42:31 -0700
committerJack Sangdahl <[email protected]>2024-11-16 19:42:31 -0700
commit17b2b82eba368c8407b05c5bdee6b7c29b8c6abe (patch)
tree9941c6513d1f0f786c56fb3e6ca58b50e91ac5f4 /pa3_proxy/proxy.c
parent102d6be55e2e665b9c9c688725d1119a501139ef (diff)
downloadecen5273-17b2b82eba368c8407b05c5bdee6b7c29b8c6abe.tar.gz
ecen5273-17b2b82eba368c8407b05c5bdee6b7c29b8c6abe.zip
Programming Assignment 3: Proxy
Signed-off-by: Jack Sangdahl <[email protected]>
Diffstat (limited to 'pa3_proxy/proxy.c')
-rw-r--r--pa3_proxy/proxy.c362
1 files changed, 362 insertions, 0 deletions
diff --git a/pa3_proxy/proxy.c b/pa3_proxy/proxy.c
new file mode 100644
index 0000000..a7f2885
--- /dev/null
+++ b/pa3_proxy/proxy.c
@@ -0,0 +1,362 @@
+// HTTP Caching Proxy
+// Jack Sangdahl
+// 28 October 2024
+#include "proxy.h"
+
+static int timeout;
+
+int main(int argc, char **argv) {
+ if (argc < 3) {
+ fprintf(stderr, "usage: %s <port number> <cache timeout>\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+ signal(SIGINT, handle_sigint);
+ int port_num = atoi(argv[1]);
+ timeout = atoi(argv[2]);
+ struct stat st = {0};
+ if (stat(CACHE_DIRECTORY, &st) == -1)
+ mkdir(CACHE_DIRECTORY, 0700);
+ pthread_t tid;
+ pthread_attr_t attr;
+ if (pthread_attr_init(&attr)) {
+ perror("creating thread attributes failed");
+ return EXIT_FAILURE;
+ }
+ if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) {
+ perror("setting detached state failed");
+ return EXIT_FAILURE;
+ }
+ int sockfd = socket(AF_INET, SOCK_STREAM, 0);
+ if (sockfd < 0) {
+ perror("socket failed");
+ return EXIT_FAILURE;
+ }
+ printf("socket created\n");
+ struct sockaddr_in server_addr, client_addr;
+ int server_addr_len = sizeof(server_addr);
+ int client_addr_len = sizeof(client_addr);
+ server_addr.sin_family = AF_INET;
+ server_addr.sin_port = htons(port_num);
+ server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ if (bind(sockfd, (struct sockaddr *)&server_addr, server_addr_len)) {
+ perror("binding failed");
+ return EXIT_FAILURE;
+ }
+ printf("socket bound to address\n");
+ if (listen(sockfd, SOMAXCONN)) {
+ perror("listening failed");
+ return EXIT_FAILURE;
+ }
+ printf("listening for connections...\n");
+ while (1) {
+ int *new_sockfd = (int *)malloc(sizeof(int));
+ *new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr,
+ (socklen_t *)&client_addr_len);
+ if (*new_sockfd < 0)
+ perror("accepting client connection failed");
+ else
+ printf("new connection accepted\n");
+ if (pthread_create(&tid, &attr, handle_connection, (void *)new_sockfd)) {
+ free(new_sockfd);
+ perror("creating thread failed");
+ }
+ }
+ return EXIT_SUCCESS;
+}
+
+void *handle_connection(void *a) {
+ int connection = *(int *)a;
+ struct sockaddr_in server_addr;
+ server_addr.sin_family = AF_INET;
+ server_addr.sin_port = htons(80);
+ struct prefetcher_args pf;
+ pthread_t prefetch_tid;
+ pthread_attr_t prefetch_attr;
+ if (pthread_attr_init(&prefetch_attr)) {
+ perror("creating prefetcher thread attributes failed");
+ pthread_exit(NULL);
+ }
+ if (pthread_attr_setdetachstate(&prefetch_attr, PTHREAD_CREATE_DETACHED)) {
+ perror("setting prefetcher detached state failed");
+ pthread_exit(NULL);
+ }
+ while (1) {
+ bool prefetch = false;
+ char client_proxy_data[DATA_SIZE];
+ char server_proxy_data[DATA_SIZE];
+ char proxy_client_data[DATA_SIZE];
+ char http_version[10], hostname[100], request_type[10], url[DATA_SIZE],
+ md5_checksum[33], cache_filename[50], extracted_ip[20], port_str[10],
+ cached_ip[20];
+ ssize_t bytes_recv =
+ recv(connection, client_proxy_data, sizeof(client_proxy_data), 0);
+ if (!bytes_recv) {
+ printf("request complete\n");
+ break;
+ }
+ sscanf(client_proxy_data, "%s %s %s", request_type, url, http_version);
+ if (!strcmp(request_type, "GET")) {
+ if (strstr(url, "https")) {
+ strcpy(proxy_client_data, RSP_400);
+ send(connection, proxy_client_data, strlen(proxy_client_data),
+ MSG_NOSIGNAL);
+ send(connection, MSG_400_URL, strlen(MSG_400_URL), MSG_NOSIGNAL);
+ continue;
+ }
+ if (file_cached(url)) {
+ printf("valid cache file found\n");
+ send_cached_file(url, connection);
+ } else {
+ calculate_md5sum(url, md5_checksum);
+ sprintf(cache_filename, "%s%s", CACHE_DIRECTORY, md5_checksum);
+ printf("no file found in cache\n");
+ sscanf(url, "%*[^/]%*c%*c%[^/]", hostname);
+ printf("hostname: %s\n", hostname);
+ if (strchr(hostname, ':')) {
+ sscanf(hostname, "%[^:]%*c%[^/]", extracted_ip, port_str);
+ if (blacklisted(extracted_ip)) {
+ strcpy(proxy_client_data, RSP_403);
+ send(connection, proxy_client_data, strlen(proxy_client_data),
+ MSG_NOSIGNAL);
+ send(connection, MSG_403, strlen(MSG_403), MSG_NOSIGNAL);
+ continue;
+ }
+ server_addr.sin_addr.s_addr = inet_addr(extracted_ip);
+ server_addr.sin_port = htons(atoi(port_str));
+ } else {
+ if (blacklisted(hostname)) {
+ strcpy(proxy_client_data, RSP_403);
+ send(connection, proxy_client_data, strlen(proxy_client_data),
+ MSG_NOSIGNAL);
+ send(connection, MSG_403, strlen(MSG_403), MSG_NOSIGNAL);
+ continue;
+ }
+ if (hostname_cached(hostname, cached_ip))
+ server_addr.sin_addr.s_addr = inet_addr(cached_ip);
+ else {
+ struct hostent *server = gethostbyname(hostname);
+ if (!server) {
+ perror("getting server info failed");
+ continue;
+ }
+ memcpy((char *)&server_addr.sin_addr, server->h_addr,
+ server->h_length);
+ cache_hostname(hostname, inet_ntoa(server_addr.sin_addr));
+ }
+ }
+ int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
+ if (server_sockfd < 0) {
+ perror("server socket failed");
+ continue;
+ }
+ if (connect(server_sockfd, (struct sockaddr *)&server_addr,
+ sizeof(server_addr)) < 0 &&
+ errno != EISCONN) {
+ perror("connecting to host failed");
+ continue;
+ }
+ send(server_sockfd, client_proxy_data, strlen(client_proxy_data),
+ MSG_NOSIGNAL);
+ FILE *fp = fopen(cache_filename, "w");
+ time_t curr = time(NULL);
+ fprintf(fp, "%lu\n", curr);
+ while ((bytes_recv = recv(server_sockfd, server_proxy_data,
+ sizeof(server_proxy_data), 0))) {
+ if (strstr(server_proxy_data, "<html"))
+ prefetch = true;
+ printf("bytes received: %zd\n", bytes_recv);
+ fwrite(server_proxy_data, sizeof(char), bytes_recv, fp);
+ send(connection, server_proxy_data, bytes_recv, MSG_NOSIGNAL);
+ memset(server_proxy_data, 0, sizeof(server_proxy_data));
+ }
+ long file_size = ftell(fp);
+ fclose(fp);
+ if (file_size < 20)
+ remove(cache_filename);
+ if (prefetch) {
+ strcpy(pf.filename, cache_filename);
+ strcpy(pf.hostname, hostname);
+ strcpy(pf.ipaddr, inet_ntoa(server_addr.sin_addr));
+ if (pthread_create(&prefetch_tid, &prefetch_attr, prefetcher,
+ (void *)&pf))
+ perror("creating prefetcher thread failed");
+ }
+ }
+ } else {
+ strcpy(proxy_client_data, RSP_400);
+ send(connection, proxy_client_data, strlen(proxy_client_data),
+ MSG_NOSIGNAL);
+ send(connection, MSG_400_METHOD, strlen(MSG_400_METHOD), MSG_NOSIGNAL);
+ }
+ }
+ close(connection);
+ pthread_exit(NULL);
+}
+
+void *prefetcher(void *a) {
+ struct prefetcher_args pf = *(struct prefetcher_args *)a;
+ char html_filename[50];
+ char server_hostname[100];
+ char server_ipaddr[20];
+ strcpy(html_filename, pf.filename);
+ strcpy(server_hostname, pf.hostname);
+ strcpy(server_ipaddr, pf.ipaddr);
+ FILE *html_ptr = fopen(html_filename, "r");
+ if (!html_ptr) {
+ perror("opening html file failed");
+ pthread_exit(NULL);
+ }
+ struct sockaddr_in server_addr;
+ server_addr.sin_family = AF_INET;
+ server_addr.sin_port = htons(80);
+ server_addr.sin_addr.s_addr = inet_addr(server_ipaddr);
+ char *line;
+ size_t length;
+ while (getline(&line, &length, html_ptr) != -1) {
+ char cache_path[50], http_request[DATA_SIZE], recv_data[DATA_SIZE],
+ md5_url[33], full_url[207], rel_url[100],
+ *href_pos = strstr(line, "href");
+ if (href_pos) {
+ int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
+ if (server_sockfd < 0) {
+ perror("server socket failed");
+ pthread_exit(NULL);
+ }
+ if (connect(server_sockfd, (struct sockaddr *)&server_addr,
+ sizeof(server_addr)) < 0 &&
+ errno != EISCONN) {
+ perror("connecting to host failed");
+ pthread_exit(NULL);
+ }
+ char *url_start = strstr(line, "http://");
+ if (url_start)
+ sscanf(url_start, "%[^\"]", full_url);
+ else {
+ sscanf(href_pos, "%*[^=]%*c%*c%[^\"]", rel_url);
+ if (rel_url[0] == '/')
+ sprintf(full_url, "http://%s%s", server_hostname, rel_url);
+ else
+ sprintf(full_url, "http://%s/%s", server_hostname, rel_url);
+ }
+ sprintf(http_request, "GET %s HTTP/1.0\r\n\r\n", full_url);
+ calculate_md5sum(full_url, md5_url);
+ sprintf(cache_path, "%s%s", CACHE_DIRECTORY, md5_url);
+ send(server_sockfd, http_request, strlen(http_request), MSG_NOSIGNAL);
+ printf("cache path: %s\n", cache_path);
+ FILE *fp = fopen(cache_path, "w");
+ time_t curr = time(NULL);
+ fprintf(fp, "%lu\n", curr);
+ ssize_t bytes_recv;
+ while (
+ (bytes_recv = recv(server_sockfd, recv_data, sizeof(recv_data), 0))) {
+ fwrite(recv_data, sizeof(char), bytes_recv, fp);
+ memset(recv_data, 0, sizeof(recv_data));
+ }
+ long file_size = ftell(fp);
+ fclose(fp);
+ if (file_size < 20)
+ remove(cache_path);
+ printf("prefetched link: %s\n", full_url);
+ close(server_sockfd);
+ } else
+ continue;
+ }
+ fclose(html_ptr);
+ printf("prefetching complete\n");
+ pthread_exit(NULL);
+}
+
+void calculate_md5sum(char *url, char *md5_checksum) {
+ EVP_MD_CTX *ctx = EVP_MD_CTX_new();
+ EVP_DigestInit_ex(ctx, EVP_md5(), NULL);
+ EVP_DigestUpdate(ctx, url, strlen(url));
+ uint md5_digest_len = EVP_MD_size(EVP_md5());
+ u_char *md5_digest =
+ (u_char *)OPENSSL_malloc(md5_digest_len * sizeof(u_char));
+ EVP_DigestFinal_ex(ctx, md5_digest, &md5_digest_len);
+ for (int i = 0; i < 16; ++i)
+ sprintf(md5_checksum + i * 2, "%02x", (uint)md5_digest[i]);
+ EVP_MD_CTX_free(ctx);
+ OPENSSL_free(md5_digest);
+}
+
+void send_cached_file(char *url, int connection) {
+ char md5_checksum[33];
+ char filename[50];
+ char data_read[DATA_SIZE];
+ size_t bytes_read;
+ char *line;
+ size_t length;
+ calculate_md5sum(url, md5_checksum);
+ sprintf(filename, "%s%s", CACHE_DIRECTORY, md5_checksum);
+ FILE *fp = fopen(filename, "r");
+ if (getline(&line, &length, fp) == -1)
+ return;
+ while ((bytes_read = fread(data_read, sizeof(char), sizeof(data_read), fp))) {
+ send(connection, data_read, bytes_read, MSG_NOSIGNAL);
+ memset(data_read, 0, sizeof(data_read));
+ }
+ fclose(fp);
+}
+
+bool file_cached(char *url) {
+ char md5_checksum[33];
+ char filename[50];
+ time_t curr = time(NULL), begin, diff;
+ char *line;
+ size_t length;
+ calculate_md5sum(url, md5_checksum);
+ sprintf(filename, "%s%s", CACHE_DIRECTORY, md5_checksum);
+ FILE *fp = fopen(filename, "r");
+ if (!fp)
+ return false;
+ else {
+ getline(&line, &length, fp);
+ sscanf(line, "%ld", &begin);
+ diff = (curr - begin);
+ if (diff > timeout) {
+ fclose(fp);
+ remove(filename);
+ return false;
+ }
+ fclose(fp);
+ return true;
+ }
+}
+
+bool blacklisted(char *ipaddr) {
+ FILE *block_list_ptr = fopen(BLOCKLIST_FILENAME, "a+");
+ char *line;
+ size_t length;
+ while (getline(&line, &length, block_list_ptr) != -1)
+ if (strstr(line, ipaddr))
+ return true;
+ return false;
+}
+
+void cache_hostname(char *hostname, char *ipaddr) {
+ FILE *fp = fopen(HOST_CACHE_FILENAME, "a+");
+ fprintf(fp, "%s,%s\n", hostname, ipaddr);
+ fclose(fp);
+}
+
+bool hostname_cached(char *hostname, char *ipaddr) {
+ strcpy(ipaddr, "none");
+ char *line;
+ size_t length;
+ FILE *fp = fopen(HOST_CACHE_FILENAME, "w+");
+ while (getline(&line, &length, fp) != -1)
+ if (strstr(line, hostname)) {
+ sscanf(line, "%*[^,]%*c%s", ipaddr);
+ break;
+ }
+ fclose(fp);
+ return !strcmp(ipaddr, "none") ? false : true;
+}
+
+void handle_sigint(int sig) {
+ if (sig == SIGINT) {
+ printf("\nterminating proxy...\n");
+ exit(EXIT_SUCCESS);
+ }
+}