This commit is contained in:
louiscklaw
2025-02-01 01:59:20 +08:00
commit e1e1c21cb6
57 changed files with 2652 additions and 0 deletions

View File

@@ -0,0 +1,277 @@
/**
*Copyright (C) 2023 Carnegie Mellon University and
*Hong Kong University of Science and Technology
*This repository is adopted from the TCP in the
*Wild course project from the Computer Networks
*course taught at Carnegie Mellon University, and
*is used for the Computer Networks (ELEC 3120)
*course taught at Hong Kong University of Science
*and Technology.
*No part of the project may be copied and/or
*distributed without the express permission of
*the course staff. Everyone is prohibited from
*releasing their forks in any public places.
*
*
* This file implements the CMU-TCP backend. The backend runs in a different
* thread and handles all the socket operations separately from the application.
*
* This is where most of your code should go. Feel free to modify any function
* in this file.
*/
#include "backend.h"
#include <poll.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "cmu_packet.h"
#include "cmu_tcp.h"
#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
/**
* Tells if a given sequence number has been acknowledged by the socket.
*
* @param sock The socket to check for acknowledgements.
* @param seq Sequence number to check.
*
* @return 1 if the sequence number has been acknowledged, 0 otherwise.
*/
int has_been_acked(cmu_socket_t *sock, uint32_t seq) {
int result;
while (pthread_mutex_lock(&(sock->window.ack_lock)) != 0) {
}
result = after(sock->window.last_ack_received, seq);
pthread_mutex_unlock(&(sock->window.ack_lock));
return result;
}
/**
* Updates the socket information to represent the newly received packet.
*
* In the current stop-and-wait implementation, this function also sends an
* acknowledgement for the packet.
*
* @param sock The socket used for handling packets received.
* @param pkt The packet data received by the socket.
*/
void handle_message(cmu_socket_t *sock, uint8_t *pkt) {
cmu_tcp_header_t *hdr = (cmu_tcp_header_t *)pkt;
uint8_t flags = get_flags(hdr);
switch (flags) {
case ACK_FLAG_MASK: {
uint32_t ack = get_ack(hdr);
if (after(ack, sock->window.last_ack_received)) {
sock->window.last_ack_received = ack;
}
break;
}
default: {
socklen_t conn_len = sizeof(sock->conn);
uint32_t seq = sock->window.last_ack_received;
// No payload.
uint8_t *payload = NULL;
uint16_t payload_len = 0;
// No extension.
uint16_t ext_len = 0;
uint8_t *ext_data = NULL;
uint16_t src = sock->my_port;
uint16_t dst = ntohs(sock->conn.sin_port);
uint32_t ack = get_seq(hdr) + get_payload_len(pkt);
uint16_t hlen = sizeof(cmu_tcp_header_t);
uint16_t plen = hlen + payload_len;
uint8_t flags = ACK_FLAG_MASK;
uint16_t adv_window = 1;
uint8_t *response_packet =
create_packet(src, dst, seq, ack, hlen, plen, flags, adv_window,
ext_len, ext_data, payload, payload_len);
sendto(sock->socket, response_packet, plen, 0,
(struct sockaddr *)&(sock->conn), conn_len);
free(response_packet);
seq = get_seq(hdr);
if (seq == sock->window.next_seq_expected) {
sock->window.next_seq_expected = seq + get_payload_len(pkt);
payload_len = get_payload_len(pkt);
payload = get_payload(pkt);
// Make sure there is enough space in the buffer to store the payload.
sock->received_buf =
realloc(sock->received_buf, sock->received_len + payload_len);
memcpy(sock->received_buf + sock->received_len, payload, payload_len);
sock->received_len += payload_len;
}
}
}
}
/**
* Checks if the socket received any data.
*
* It first peeks at the header to figure out the length of the packet and then
* reads the entire packet.
*
* @param sock The socket used for receiving data on the connection.
* @param flags Flags that determine how the socket should wait for data. Check
* `cmu_read_mode_t` for more information.
*/
void check_for_data(cmu_socket_t *sock, cmu_read_mode_t flags) {
cmu_tcp_header_t hdr;
uint8_t *pkt;
socklen_t conn_len = sizeof(sock->conn);
ssize_t len = 0;
uint32_t plen = 0, buf_size = 0, n = 0;
while (pthread_mutex_lock(&(sock->recv_lock)) != 0) {
}
switch (flags) {
case NO_FLAG:
len = recvfrom(sock->socket, &hdr, sizeof(cmu_tcp_header_t), MSG_PEEK,
(struct sockaddr *)&(sock->conn), &conn_len);
break;
case TIMEOUT: {
// Using `poll` here so that we can specify a timeout.
struct pollfd ack_fd;
ack_fd.fd = sock->socket;
ack_fd.events = POLLIN;
// Timeout after 3 seconds.
if (poll(&ack_fd, 1, 3000) <= 0) {
break;
}
}
// Fallthrough.
case NO_WAIT:
len = recvfrom(sock->socket, &hdr, sizeof(cmu_tcp_header_t),
MSG_DONTWAIT | MSG_PEEK, (struct sockaddr *)&(sock->conn),
&conn_len);
break;
default:
perror("ERROR unknown flag");
}
if (len >= (ssize_t)sizeof(cmu_tcp_header_t)) {
plen = get_plen(&hdr);
pkt = malloc(plen);
while (buf_size < plen) {
n = recvfrom(sock->socket, pkt + buf_size, plen - buf_size, 0,
(struct sockaddr *)&(sock->conn), &conn_len);
buf_size = buf_size + n;
}
handle_message(sock, pkt);
free(pkt);
}
pthread_mutex_unlock(&(sock->recv_lock));
}
/**
* Breaks up the data into packets and sends a single packet at a time.
*
* You should most certainly update this function in your implementation.
*
* @param sock The socket to use for sending data.
* @param data The data to be sent.
* @param buf_len The length of the data being sent.
*/
void single_send(cmu_socket_t *sock, uint8_t *data, int buf_len) {
uint8_t *msg;
uint8_t *data_offset = data;
size_t conn_len = sizeof(sock->conn);
int sockfd = sock->socket;
if (buf_len > 0) {
while (buf_len != 0) {
uint16_t payload_len = MIN(buf_len, MSS);
uint16_t src = sock->my_port;
uint16_t dst = ntohs(sock->conn.sin_port);
uint32_t seq = sock->window.last_ack_received;
uint32_t ack = sock->window.next_seq_expected;
uint16_t hlen = sizeof(cmu_tcp_header_t);
uint16_t plen = hlen + payload_len;
uint8_t flags = 0;
uint16_t adv_window = 1;
uint16_t ext_len = 0;
uint8_t *ext_data = NULL;
uint8_t *payload = data_offset;
msg = create_packet(src, dst, seq, ack, hlen, plen, flags, adv_window,
ext_len, ext_data, payload, payload_len);
buf_len -= payload_len;
while (1) {
// FIXME: This is using stop and wait, can we do better?
sendto(sockfd, msg, plen, 0, (struct sockaddr *)&(sock->conn),
conn_len);
check_for_data(sock, TIMEOUT);
if (has_been_acked(sock, seq)) {
break;
}
}
data_offset += payload_len;
}
}
}
void *begin_backend(void *in) {
cmu_socket_t *sock = (cmu_socket_t *)in;
int death, buf_len, send_signal;
uint8_t *data;
while (1) {
while (pthread_mutex_lock(&(sock->death_lock)) != 0) {
}
death = sock->dying;
pthread_mutex_unlock(&(sock->death_lock));
while (pthread_mutex_lock(&(sock->send_lock)) != 0) {
}
buf_len = sock->sending_len;
if (death && buf_len == 0) {
break;
}
if (buf_len > 0) {
data = malloc(buf_len);
memcpy(data, sock->sending_buf, buf_len);
sock->sending_len = 0;
free(sock->sending_buf);
sock->sending_buf = NULL;
pthread_mutex_unlock(&(sock->send_lock));
single_send(sock, data, buf_len);
free(data);
} else {
pthread_mutex_unlock(&(sock->send_lock));
}
check_for_data(sock, NO_WAIT);
while (pthread_mutex_lock(&(sock->recv_lock)) != 0) {
}
send_signal = sock->received_len > 0;
pthread_mutex_unlock(&(sock->recv_lock));
if (send_signal) {
pthread_cond_signal(&(sock->wait_cond));
}
}
pthread_exit(NULL);
return NULL;
}

View File

@@ -0,0 +1,86 @@
/**
*Copyright (C) 2023 Carnegie Mellon University and
*Hong Kong University of Science and Technology
*This repository is adopted from the TCP in the
*Wild course project from the Computer Networks
*course taught at Carnegie Mellon University, and
*is used for the Computer Networks (ELEC 3120)
*course taught at Hong Kong University of Science
*and Technology.
*No part of the project may be copied and/or
*distributed without the express permission of
*the course staff. Everyone is prohibited from
*releasing their forks in any public places.
*
*
* This file implements a simple CMU-TCP client. Its purpose is to provide
* simple test cases and demonstrate how the sockets will be used.
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "cmu_tcp.h"
void functionality(cmu_socket_t *sock) {
uint8_t buf[9898];
int read;
FILE *fp;
cmu_write(sock, "hi there", 8);
cmu_write(sock, " https://www.youtube.com/watch?v=dQw4w9WgXcQ", 44);
cmu_write(sock, " https://www.youtube.com/watch?v=Yb6dZ1IFlKc", 44);
cmu_write(sock, " https://www.youtube.com/watch?v=xvFZjo5PgG0", 44);
cmu_write(sock, " https://www.youtube.com/watch?v=8ybW48rKBME", 44);
cmu_write(sock, " https://www.youtube.com/watch?v=xfr64zoBTAQ", 45);
cmu_read(sock, buf, 200, NO_FLAG);
cmu_write(sock, "hi there", 9);
cmu_read(sock, buf, 200, NO_FLAG);
printf("R: %s\n", buf);
read = cmu_read(sock, buf, 200, NO_WAIT);
printf("Read: %d\n", read);
fp = fopen("/vagrant/project-1_elec3120/src/cmu_tcp.c", "rb");
read = 1;
while (read > 0) {
read = fread(buf, 1, 2000, fp);
if (read > 0) {
cmu_write(sock, buf, read);
}
}
}
int main() {
int portno;
char *serverip;
char *serverport;
cmu_socket_t socket;
serverip = getenv("server15441");
if (!serverip) {
serverip = "10.0.1.1";
}
serverport = getenv("serverport15441");
if (!serverport) {
serverport = "15441";
}
portno = (uint16_t)atoi(serverport);
if (cmu_socket(&socket, TCP_INITIATOR, portno, serverip) < 0) {
exit(EXIT_FAILURE);
}
functionality(&socket);
if (cmu_close(&socket) < 0) {
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,160 @@
/**
*Copyright (C) 2023 Carnegie Mellon University and
*Hong Kong University of Science and Technology
*This repository is adopted from the TCP in the
*Wild course project from the Computer Networks
*course taught at Carnegie Mellon University, and
*is used for the Computer Networks (ELEC 3120)
*course taught at Hong Kong University of Science
*and Technology.
*No part of the project may be copied and/or
*distributed without the express permission of
*the course staff. Everyone is prohibited from
*releasing their forks in any public places.
*
* This file implements helper functions to create and manipulate packets.
*
* Do NOT modify this file.
*/
#include "cmu_packet.h"
#include <arpa/inet.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
uint16_t get_src(cmu_tcp_header_t* header) {
return ntohs(header->source_port);
}
uint16_t get_dst(cmu_tcp_header_t* header) {
return ntohs(header->destination_port);
}
uint32_t get_seq(cmu_tcp_header_t* header) { return ntohl(header->seq_num); }
uint32_t get_ack(cmu_tcp_header_t* header) { return ntohl(header->ack_num); }
uint16_t get_hlen(cmu_tcp_header_t* header) { return ntohs(header->hlen); }
uint16_t get_plen(cmu_tcp_header_t* header) { return ntohs(header->plen); }
uint8_t get_flags(cmu_tcp_header_t* header) { return header->flags; }
uint16_t get_advertised_window(cmu_tcp_header_t* header) {
return ntohs(header->advertised_window);
}
uint16_t get_extension_length(cmu_tcp_header_t* header) {
return ntohs(header->extension_length);
}
uint8_t* get_extension_data(cmu_tcp_header_t* header) {
return (uint8_t*)(header + 1);
}
void set_src(cmu_tcp_header_t* header, uint16_t src) {
header->source_port = htons(src);
}
void set_dst(cmu_tcp_header_t* header, uint16_t dst) {
header->destination_port = htons(dst);
}
void set_seq(cmu_tcp_header_t* header, uint32_t seq) {
header->seq_num = htonl(seq);
}
void set_ack(cmu_tcp_header_t* header, uint32_t ack) {
header->ack_num = htonl(ack);
}
void set_hlen(cmu_tcp_header_t* header, uint16_t hlen) {
header->hlen = htons(hlen);
}
void set_plen(cmu_tcp_header_t* header, uint16_t plen) {
header->plen = htons(plen);
}
void set_flags(cmu_tcp_header_t* header, uint8_t flags) {
header->flags = flags;
}
void set_advertised_window(cmu_tcp_header_t* header, uint16_t adv_window) {
header->advertised_window = htons(adv_window);
}
void set_extension_length(cmu_tcp_header_t* header, uint16_t ext) {
header->extension_length = htons(ext);
}
void set_extension_data(cmu_tcp_header_t* header, uint8_t* ext_data) {
memcpy(header->extension_data, ext_data, get_extension_length(header));
}
void set_header(cmu_tcp_header_t* header, uint16_t src, uint16_t dst,
uint32_t seq, uint32_t ack, uint16_t hlen, uint16_t plen,
uint8_t flags, uint16_t adv_window, uint16_t ext,
uint8_t* ext_data) {
header->identifier = htonl(IDENTIFIER);
header->source_port = htons(src);
header->destination_port = htons(dst);
header->seq_num = htonl(seq);
header->ack_num = htonl(ack);
header->hlen = htons(hlen);
header->plen = htons(plen);
header->flags = flags;
header->advertised_window = htons(adv_window);
header->extension_length = htons(ext);
memcpy(header->extension_data, ext_data, ext);
}
uint8_t* get_payload(uint8_t* pkt) {
cmu_tcp_header_t* header = (cmu_tcp_header_t*)pkt;
uint16_t ext_len = get_extension_length(header);
int offset = sizeof(cmu_tcp_header_t) + ext_len;
return (uint8_t*)header + offset;
}
uint16_t get_payload_len(uint8_t* pkt) {
cmu_tcp_header_t* header = (cmu_tcp_header_t*)pkt;
return get_plen(header) - get_hlen(header);
}
void set_payload(uint8_t* pkt, uint8_t* payload, uint16_t payload_len) {
cmu_tcp_header_t* header = (cmu_tcp_header_t*)pkt;
uint16_t ext_len = get_extension_length(header);
int offset = sizeof(cmu_tcp_header_t) + ext_len;
memcpy((uint8_t*)header + offset, payload, payload_len);
}
uint8_t* create_packet(uint16_t src, uint16_t dst, uint32_t seq, uint32_t ack,
uint16_t hlen, uint16_t plen, uint8_t flags,
uint16_t adv_window, uint16_t ext_len, uint8_t* ext_data,
uint8_t* payload, uint16_t payload_len) {
if (hlen < sizeof(cmu_tcp_header_t)) {
return NULL;
}
if (plen < hlen) {
return NULL;
}
uint8_t* packet = malloc(sizeof(cmu_tcp_header_t) + payload_len);
if (packet == NULL) {
return NULL;
}
cmu_tcp_header_t* header = (cmu_tcp_header_t*)packet;
set_header(header, src, dst, seq, ack, hlen, plen, flags, adv_window, ext_len,
ext_data);
uint8_t* pkt_payload = get_payload(packet);
memcpy(pkt_payload, payload, payload_len);
return packet;
}

View File

@@ -0,0 +1,201 @@
/**
*Copyright (C) 2023 Carnegie Mellon University and
*Hong Kong University of Science and Technology
*This repository is adopted from the TCP in the
*Wild course project from the Computer Networks
*course taught at Carnegie Mellon University, and
*is used for the Computer Networks (ELEC 3120)
*course taught at Hong Kong University of Science
*and Technology.
*No part of the project may be copied and/or
*distributed without the express permission of
*the course staff. Everyone is prohibited from
*releasing their forks in any public places.
*
*
* This file implements the high-level API for CMU-TCP sockets.
*/
#include "cmu_tcp.h"
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include "backend.h"
int cmu_socket(cmu_socket_t *sock, const cmu_socket_type_t socket_type,
const int port, const char *server_ip) {
int sockfd, optval;
socklen_t len;
struct sockaddr_in conn, my_addr;
len = sizeof(my_addr);
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("ERROR opening socket");
return EXIT_ERROR;
}
sock->socket = sockfd;
sock->received_buf = NULL;
sock->received_len = 0;
pthread_mutex_init(&(sock->recv_lock), NULL);
sock->sending_buf = NULL;
sock->sending_len = 0;
pthread_mutex_init(&(sock->send_lock), NULL);
sock->type = socket_type;
sock->dying = 0;
pthread_mutex_init(&(sock->death_lock), NULL);
// FIXME: Sequence numbers should be randomly initialized. The next expected
// sequence number should be initialized according to the SYN packet from the
// other side of the connection.
sock->window.last_ack_received = 0;
sock->window.next_seq_expected = 0;
pthread_mutex_init(&(sock->window.ack_lock), NULL);
if (pthread_cond_init(&sock->wait_cond, NULL) != 0) {
perror("ERROR condition variable not set\n");
return EXIT_ERROR;
}
switch (socket_type) {
case TCP_INITIATOR:
if (server_ip == NULL) {
perror("ERROR server_ip NULL");
return EXIT_ERROR;
}
memset(&conn, 0, sizeof(conn));
conn.sin_family = AF_INET;
conn.sin_addr.s_addr = inet_addr(server_ip);
conn.sin_port = htons(port);
sock->conn = conn;
my_addr.sin_family = AF_INET;
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
my_addr.sin_port = 0;
if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr)) < 0) {
perror("ERROR on binding");
return EXIT_ERROR;
}
break;
case TCP_LISTENER:
memset(&conn, 0, sizeof(conn));
conn.sin_family = AF_INET;
conn.sin_addr.s_addr = htonl(INADDR_ANY);
conn.sin_port = htons((uint16_t)port);
optval = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval,
sizeof(int));
if (bind(sockfd, (struct sockaddr *)&conn, sizeof(conn)) < 0) {
perror("ERROR on binding");
return EXIT_ERROR;
}
sock->conn = conn;
break;
default:
perror("Unknown Flag");
return EXIT_ERROR;
}
getsockname(sockfd, (struct sockaddr *)&my_addr, &len);
sock->my_port = ntohs(my_addr.sin_port);
pthread_create(&(sock->thread_id), NULL, begin_backend, (void *)sock);
return EXIT_SUCCESS;
}
int cmu_close(cmu_socket_t *sock) {
while (pthread_mutex_lock(&(sock->death_lock)) != 0) {
}
sock->dying = 1;
pthread_mutex_unlock(&(sock->death_lock));
pthread_join(sock->thread_id, NULL);
if (sock != NULL) {
if (sock->received_buf != NULL) {
free(sock->received_buf);
}
if (sock->sending_buf != NULL) {
free(sock->sending_buf);
}
} else {
perror("ERROR null socket\n");
return EXIT_ERROR;
}
return close(sock->socket);
}
int cmu_read(cmu_socket_t *sock, void *buf, int length, cmu_read_mode_t flags) {
uint8_t *new_buf;
int read_len = 0;
if (length < 0) {
perror("ERROR negative length");
return EXIT_ERROR;
}
while (pthread_mutex_lock(&(sock->recv_lock)) != 0) {
}
switch (flags) {
case NO_FLAG:
while (sock->received_len == 0) {
pthread_cond_wait(&(sock->wait_cond), &(sock->recv_lock));
}
// Fall through.
case NO_WAIT:
if (sock->received_len > 0) {
if (sock->received_len > length)
read_len = length;
else
read_len = sock->received_len;
memcpy(buf, sock->received_buf, read_len);
if (read_len < sock->received_len) {
new_buf = malloc(sock->received_len - read_len);
memcpy(new_buf, sock->received_buf + read_len,
sock->received_len - read_len);
free(sock->received_buf);
sock->received_len -= read_len;
sock->received_buf = new_buf;
} else {
free(sock->received_buf);
sock->received_buf = NULL;
sock->received_len = 0;
}
}
break;
default:
perror("ERROR Unknown flag.\n");
read_len = EXIT_ERROR;
}
pthread_mutex_unlock(&(sock->recv_lock));
return read_len;
}
int cmu_write(cmu_socket_t *sock, const void *buf, int length) {
while (pthread_mutex_lock(&(sock->send_lock)) != 0) {
}
if (sock->sending_buf == NULL)
sock->sending_buf = malloc(length);
else
sock->sending_buf = realloc(sock->sending_buf, length + sock->sending_len);
memcpy(sock->sending_buf + sock->sending_len, buf, length);
sock->sending_len += length;
pthread_mutex_unlock(&(sock->send_lock));
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,88 @@
/**
*Copyright (C) 2023 Carnegie Mellon University and
*Hong Kong University of Science and Technology
*This repository is adopted from the TCP in the
*Wild course project from the Computer Networks
*course taught at Carnegie Mellon University, and
*is used for the Computer Networks (ELEC 3120)
*course taught at Hong Kong University of Science
*and Technology.
*No part of the project may be copied and/or
*distributed without the express permission of
*the course staff. Everyone is prohibited from
*releasing their forks in any public places.
*
*
* This file implements a simple CMU-TCP server. Its purpose is to provide
* simple test cases and demonstrate how the sockets will be used.
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "cmu_tcp.h"
#define BUF_SIZE 10000
/*
* Param: sock - used for reading and writing to a connection
*
* Purpose: To provide some simple test cases and demonstrate how
* the sockets will be used.
*
*/
void functionality(cmu_socket_t *sock) {
uint8_t buf[BUF_SIZE];
FILE *fp;
int n;
n = cmu_read(sock, buf, BUF_SIZE, NO_FLAG);
printf("R: %s\n", buf);
printf("N: %d\n", n);
cmu_write(sock, "hi there", 9);
n = cmu_read(sock, buf, 200, NO_FLAG);
printf("R: %s\n", buf);
printf("N: %d\n", n);
cmu_write(sock, "https://www.youtube.com/watch?v=dQw4w9WgXcQ", 44);
sleep(1);
n = cmu_read(sock, buf, BUF_SIZE, NO_FLAG);
printf("N: %d\n", n);
fp = fopen("/tmp/file.c", "w");
fwrite(buf, 1, n, fp);
fclose(fp);
}
int main() {
int portno;
char *serverip;
char *serverport;
cmu_socket_t socket;
serverip = getenv("server15441");
if (!serverip) {
serverip = "10.0.1.1";
}
serverport = getenv("serverport15441");
if (!serverport) {
serverport = "15441";
}
portno = (uint16_t)atoi(serverport);
if (cmu_socket(&socket, TCP_LISTENER, portno, serverip) < 0) {
exit(EXIT_FAILURE);
}
functionality(&socket);
if (cmu_close(&socket) < 0) {
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}