This commit is contained in:
louiscklaw
2025-01-31 19:24:01 +08:00
parent 1d3678b2fb
commit 4afadb4bfd
54 changed files with 2604 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
TOP_DIR = .
INC_DIR = $(TOP_DIR)/inc
SRC_DIR = $(TOP_DIR)/src
BUILD_DIR = $(TOP_DIR)/build
CC=gcc
FLAGS = -pthread -fPIC -g -ggdb -pedantic -Wall -Wextra -DDEBUG -I$(INC_DIR)
OBJS = $(BUILD_DIR)/cmu_packet.o $(BUILD_DIR)/cmu_tcp.o $(BUILD_DIR)/backend.o
all: server client tests/testing_server
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
$(CC) $(FLAGS) -c -o $@ $<
server: $(OBJS) $(SRC_DIR)/server.c
$(CC) $(FLAGS) $(SRC_DIR)/server.c -o server $(OBJS)
client: $(OBJS) $(SRC_DIR)/client.c
$(CC) $(FLAGS) $(SRC_DIR)/client.c -o client $(OBJS)
tests/testing_server: $(OBJS)
$(CC) $(FLAGS) tests/testing_server.c -o tests/testing_server $(OBJS)
format:
pre-commit run --all-files
test:
sudo -E python3 tests/test_cp1.py
sudo -E python3 tests/test_cp1_basic_ack_packets.py
clean:
rm -f $(BUILD_DIR)/*.o peer client server
rm -f tests/testing_server

View File

@@ -0,0 +1,72 @@
#!/usr/bin/env python3
# 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.
from scapy.all import rdpcap, Raw, IP
import matplotlib.pyplot as plt
# Change this to be your pcap file
# You can capture a pcap file with wireshark or tcpdump
# https://support.rackspace.com/how-to/capturing-packets-with-tcpdump/
FILE_TO_READ = "capture.pcap"
packets = rdpcap(FILE_TO_READ)
packet_list = []
times = []
base = 0
server_port = 15441
num_packets = 0
# This script assumes that only the client is sending data to the server.
for packet in packets:
payload = packet[Raw].load
if IP not in packet:
continue
if int.from_bytes(payload[:4], byteorder="big") != 15441:
continue
# Count the number of data packets going into the network.
if packet[IP].dport == server_port:
hlen = int.from_bytes(payload[16:18], byteorder="big")
plen = int.from_bytes(payload[18:20], byteorder="big")
if plen > hlen: # Data packet
num_packets = num_packets + 1
time = packet.time
if base == 0:
base = time
packet_list.append(num_packets)
times.append(time - base)
# Count the number of ACKs from server to client.
elif packet[IP].sport == server_port:
mask = int.from_bytes(payload[20:21], byteorder="big")
if (mask & 4) == 4: # ACK PACKET
num_packets = max(num_packets - 1, 0)
time = packet.time
if base == 0:
base = time
packet_list.append(num_packets)
times.append(time - base)
if __name__ == "__main__":
# https://matplotlib.org/users/pyplot_tutorial.html for how to format and
# make a good quality graph.
print(packet_list)
plt.scatter(times, packet_list)
plt.savefig("graph.pdf")

View File

@@ -0,0 +1,33 @@
/**
*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 defines the function signatures for the CMU-TCP backend that should
* be exposed. The backend runs in a different thread and handles all the socket
* operations separately from the application.
*/
#ifndef PROJECT_1_ELEC3120_INC_BACKEND_H_
#define PROJECT_1_ELEC3120_INC_BACKEND_H_
/**
* Launches the CMU-TCP backend.
*
* @param in the socket to be used for backend processing.
*/
void* begin_backend(void* in);
#endif // PROJECT_1_ELEC3120_INC_BACKEND_H_

View File

@@ -0,0 +1,182 @@
/**
*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.
*
* Defines a CMU-TCP packet and define helper functions to create and manipulate
* packets.
*
* Do NOT modify this file.
*/
#ifndef PROJECT_1_ELEC3120_INC_CMU_PACKET_H_
#define PROJECT_1_ELEC3120_INC_CMU_PACKET_H_
#include <stdint.h>
typedef struct {
uint32_t identifier; // Identifier for the CMU-TCP protocol.
uint16_t source_port; // Source port.
uint16_t destination_port; // Destination port.
uint32_t seq_num; // Sequence number.
uint32_t ack_num; // Acknowledgement number.
uint16_t hlen; // Header length.
uint16_t plen; // Packet length.
uint8_t flags; // Flags.
uint16_t advertised_window; // Advertised window.
uint16_t extension_length; // Extension length.
uint8_t extension_data[]; // Extension data.
} __attribute__((__packed__)) cmu_tcp_header_t;
#define SYN_FLAG_MASK 0x8
#define ACK_FLAG_MASK 0x4
#define FIN_FLAG_MASK 0x2
#define IDENTIFIER 15441
// Maximum Segment Size. Make sure to update this if your CCA requires extension
// data for all packets, as this reduces the payload and thus the MSS.
#define MSS (MAX_LEN - sizeof(cmu_tcp_header_t))
/* Helper functions to get/set fields in the header */
uint16_t get_src(cmu_tcp_header_t* header);
uint16_t get_dst(cmu_tcp_header_t* header);
uint32_t get_seq(cmu_tcp_header_t* header);
uint32_t get_ack(cmu_tcp_header_t* header);
uint16_t get_hlen(cmu_tcp_header_t* header);
uint16_t get_plen(cmu_tcp_header_t* header);
uint8_t get_flags(cmu_tcp_header_t* header);
uint16_t get_advertised_window(cmu_tcp_header_t* header);
uint16_t get_extension_length(cmu_tcp_header_t* header);
uint8_t* get_extension_data(cmu_tcp_header_t* header);
void set_src(cmu_tcp_header_t* header, uint16_t src);
void set_dst(cmu_tcp_header_t* header, uint16_t dst);
void set_seq(cmu_tcp_header_t* header, uint32_t seq);
void set_ack(cmu_tcp_header_t* header, uint32_t ack);
void set_hlen(cmu_tcp_header_t* header, uint16_t hlen);
void set_plen(cmu_tcp_header_t* header, uint16_t plen);
void set_flags(cmu_tcp_header_t* header, uint8_t flags);
void set_advertised_window(cmu_tcp_header_t* header,
uint16_t advertised_window);
void set_extension_length(cmu_tcp_header_t* header, uint16_t extension_length);
void set_extension_data(cmu_tcp_header_t* header, uint8_t* extension_data);
/**
* Sets all the header fields.
*
* Review TCP headers for more information about what each field means.
*
* @param header The header to set the fields.
* @param src Source port.
* @param dst Destination port.
* @param seq Sequence number.
* @param ack Acknowledgement number.
* @param hlen Header length.
* @param plen Packet length.
* @param flags Packet flags.
* @param advertised_window Advertised window.
* @param extension_length Header extension length.
* @param extension_data Header extension data.
*/
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);
/**
* Gets a pointer to the packet payload.
*
* @param pkt The packet to get the payload.
*
* @return A pointer to the payload.
*/
uint8_t* get_payload(uint8_t* pkt);
/**
* Gets the length of the packet payload.
*
* @param pkt The packet to get the payload length.
*
* @return The length of the payload.
*/
uint16_t get_payload_len(uint8_t* pkt);
/**
* Sets the packet payload.
*
* @param pkt The packet to set the payload.
* @param payload A pointer to the payload to be set.
* @param payload_len The length of the payload.
*/
void set_payload(uint8_t* pkt, uint8_t* payload, uint16_t payload_len);
/**
* Allocates and initializes a packet.
*
* @param src The source port.
* @param dst The destination port.
* @param seq The sequence number.
* @param ack The acknowledgement number.
* @param hlen The header length.
* @param plen The packet length.
* @param flags The flags.
* @param adv_window The advertised window.
* @param ext_len The header extension length.
* @param ext_data The header extension data.
* @param payload The payload.
* @param payload_len The length of the payload.
*
* @return A pointer to the newly allocated packet. User must `free` after use.
*/
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);
/**
* Checks if a given sequence number comes before another sequence number.
*
* @param seq1 the first sequence number.
* @param seq2 the second sequence number.
* @return 1 if seq1 comes before seq2, 0 otherwise.
*/
static inline int before(uint32_t seq1, uint32_t seq2) {
return (int32_t)(seq1 - seq2) < 0;
}
/**
* Checks if a given sequence number comes after another sequence number.
*
* @param seq1 the first sequence number.
* @param seq2 the second sequence number.
* @return 1 if seq1 comes after seq2, 0 otherwise.
*/
static inline int after(uint32_t seq1, uint32_t seq2) {
return before(seq2, seq1);
}
/**
* Checks if a given sequence number is between two others.
*
* @param seq the sequence number.
* @param low the lower bound.
* @param high the upper bound.
* @return 1 if low <= seq <= high, 0 otherwise.
*/
static inline int between(uint32_t seq, uint32_t low, uint32_t high) {
return high - low >= seq - low;
}
#endif // PROJECT_1_ELEC3120_INC_CMU_PACKET_H_

View File

@@ -0,0 +1,145 @@
/**
*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 defines the API for the CMU TCP implementation.
*/
#ifndef PROJECT_1_ELEC3120_INC_CMU_TCP_H_
#define PROJECT_1_ELEC3120_INC_CMU_TCP_H_
#include <netinet/in.h>
#include <pthread.h>
#include <stdint.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "cmu_packet.h"
#include "grading.h"
#define EXIT_SUCCESS 0
#define EXIT_ERROR -1
#define EXIT_FAILURE 1
typedef struct {
uint32_t next_seq_expected;
uint32_t last_ack_received;
pthread_mutex_t ack_lock;
} window_t;
/**
* CMU-TCP socket types. (DO NOT CHANGE.)
*/
typedef enum {
TCP_INITIATOR = 0,
TCP_LISTENER = 1,
} cmu_socket_type_t;
/**
* This structure holds the state of a socket. You may modify this structure as
* you see fit to include any additional state you need for your implementation.
*/
typedef struct {
int socket;
pthread_t thread_id;
uint16_t my_port;
struct sockaddr_in conn;
uint8_t* received_buf;
int received_len;
pthread_mutex_t recv_lock;
pthread_cond_t wait_cond;
uint8_t* sending_buf;
int sending_len;
cmu_socket_type_t type;
pthread_mutex_t send_lock;
int dying;
pthread_mutex_t death_lock;
window_t window;
} cmu_socket_t;
/*
* DO NOT CHANGE THE DECLARATIONS BELOW
*/
/**
* Read mode flags supported by a CMU-TCP socket.
*/
typedef enum {
NO_FLAG = 0, // Default behavior: block indefinitely until data is available.
NO_WAIT, // Return immediately if no data is available.
TIMEOUT, // Block until data is available or the timeout is reached.
} cmu_read_mode_t;
/**
* Constructs a CMU-TCP socket.
*
* An Initiator socket is used to connect to a Listener socket.
*
* @param sock The structure with the socket state. It will be initialized by
* this function.
* @param socket_type Indicates the type of socket: Listener or Initiator.
* @param port Port to either connect to, or bind to. (Based on socket_type.)
* @param server_ip IP address of the server to connect to. (Only used if the
* socket is an initiator.)
*
* @return 0 on success, -1 on error.
*/
int cmu_socket(cmu_socket_t* sock, const cmu_socket_type_t socket_type,
const int port, const char* server_ip);
/**
* Closes a CMU-TCP socket.
*
* @param sock The socket to close.
*
* @return 0 on success, -1 on error.
*/
int cmu_close(cmu_socket_t* sock);
/**
* Reads data from a CMU-TCP socket.
*
* If there is data available in the socket buffer, it is placed in the
* destination buffer.
*
* @param sock The socket to read from.
* @param buf The buffer to read into.
* @param length The maximum number of bytes to read.
* @param flags Flags that determine how the socket should wait for data. Check
* `cmu_read_mode_t` for more information. `TIMEOUT` is not
* implemented for CMU-TCP.
*
* @return The number of bytes read on success, -1 on error.
*/
int cmu_read(cmu_socket_t* sock, void* buf, const int length,
cmu_read_mode_t flags);
/**
* Writes data to a CMU-TCP socket.
*
* @param sock The socket to write to.
* @param buf The data to write.
* @param length The number of bytes to write.
*
* @return 0 on success, -1 on error.
*/
int cmu_write(cmu_socket_t* sock, const void* buf, int length);
/*
* You can declare more functions after this point if you need to.
*/
#endif // PROJECT_1_ELEC3120_INC_CMU_TCP_H_

View File

@@ -0,0 +1,43 @@
/**
*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 files defines constants used for grading. Do NOT modify this file as
* any changes will be overwritten by the autograder.
*/
#ifndef PROJECT_1_ELEC3120_INC_GRADING_H_
#define PROJECT_1_ELEC3120_INC_GRADING_H_
/*
* DO NOT CHANGE THIS FILE
* This contains the variables for your TCP implementation
* and we will replace this file during the autolab testing with new variables.
*/
// packet lengths
#define MAX_LEN 1400
// window variables
#define WINDOW_INITIAL_WINDOW_SIZE (MSS * 16)
#define WINDOW_INITIAL_SSTHRESH (MSS * 64)
#define WINDOW_INITIAL_RTT 3000 // ms
#define WINDOW_INITIAL_ADVERTISED MSS
// Max TCP Buffer
#define MAX_NETWORK_BUFFER 65535 // (2^16 - 1) bytes
#endif // PROJECT_1_ELEC3120_INC_GRADING_H_

View File

@@ -0,0 +1,56 @@
attrs==22.1.0
bcrypt==4.0.0
certifi==2022.9.24
cffi==1.15.1
chardet==5.0.0
charset-normalizer==2.1.1
contourpy==1.0.5
cryptography==38.0.1
cycler==0.11.0
DataProperty==0.55.0
dbus-python==1.2.18
distro==1.6.0
docker==6.0.0
fabric==2.7.1
fonttools==4.37.4
humanreadable==0.1.0
idna==3.4
iniconfig==1.1.1
invoke==1.7.3
kiwisolver==1.4.4
loguru==0.6.0
matplotlib==3.6.0
mbstrdecoder==1.1.1
msgfy==0.2.0
numpy==1.23.3
packaging==21.3
paramiko==2.11.0
path==16.5.0
pathlib2==2.3.7.post1
pathvalidate==2.5.2
Pillow==9.2.0
pluggy==1.0.0
py==1.11.0
pycparser==2.21
PyGObject==3.42.0
PyNaCl==1.5.0
pyparsing==3.0.9
pyroute2==0.7.3
pytest==7.1.3
python-dateutil==2.8.2
pytz==2022.4
requests==2.28.1
scapy==2.4.5
SimpleSQLite==1.3.0
six==1.16.0
sqliteschema==1.3.0
ssh-import-id==5.11
subprocrunner==2.0.0
tabledata==1.3.0
tcconfig==0.28.0
tomli==2.0.1
typepy==1.3.0
typing==3.7.4.3
urllib3==1.26.12
voluptuous==0.13.1
websocket-client==1.4.1

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# 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.
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential vim \
emacs tree tmux git gdb valgrind python3-dev libffi-dev libssl-dev \
clang-format iperf3 tshark iproute2 iputils-ping net-tools tcpdump cppcheck
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y python3 python3-pip \
python-tk libpython3.10-dev libcairo2 libcairo2-dev pre-commit
pip3 install --upgrade pip
pip3 install -r $SCRIPT_DIR/../requirements.txt

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;
}

View File

@@ -0,0 +1 @@
Describe your tests here

View File

@@ -0,0 +1,124 @@
# 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.
import os
import subprocess
from scapy.all import (
Packet,
IntField,
ShortField,
StrLenField,
ByteEnumField,
bind_layers,
)
from scapy.layers.l2 import Ether
from scapy.layers.inet import IP, UDP
CODE_DIR = "/vagrant/project-1_elec3120"
PCAP = "/vagrant/project-1_elec3120/tests/test.pcap"
IFNAME = os.getenv("IFNAME")
# Which host are we running this pytest script on, server or client?
# If we are running pytest on the server VM, we want the testing host to be the
# client VM and vice versa.
HOSTNAME = subprocess.check_output("hostname").strip()
if HOSTNAME.decode("utf-8") == "client":
TESTING_HOSTNAME = "server"
HOSTNAME = "client"
elif HOSTNAME.decode("utf-8") == "server":
TESTING_HOSTNAME = "client"
HOSTNAME = "server"
else:
raise RuntimeError(
f"Unexpected hostname: {HOSTNAME}. You must run these tests in the "
f"client or server VM."
)
# You might need to update these for the network setting on your VMs.
IP_ADDRS = {"client": "10.0.1.2", "server": "10.0.1.1"}
MAC_ADDRS = {"client": "08:00:27:a7:fe:b1", "server": "08:00:27:22:47:1c"}
HOST_IP = IP_ADDRS[HOSTNAME]
HOST_MAC = MAC_ADDRS[HOSTNAME]
HOST_PORT = 1234
TESTING_HOST_IP = IP_ADDRS[TESTING_HOSTNAME]
TESTING_HOST_MAC = MAC_ADDRS[TESTING_HOSTNAME]
TESTING_HOST_PORT = 15441
# We can use these commands to start/stop the testing server in a background
# process.
START_TESTING_SERVER_CMD = (
"tmux new -s pytest_server -d /vagrant/"
"project-1_elec3120/tests/testing_server"
)
STOP_TESTING_SERVER_CMD = "tmux kill-session -t pytest_server"
# Default scapy packets headers we'll use to send packets.
eth = Ether(src=HOST_MAC, dst=TESTING_HOST_MAC)
ip = IP(src=HOST_IP, dst=TESTING_HOST_IP)
udp = UDP(sport=HOST_PORT, dport=TESTING_HOST_PORT)
FIN_MASK = 0x2
ACK_MASK = 0x4
SYN_MASK = 0x8
TIMEOUT = 3
"""
These tests assume there is only one connection in the PCAP
and expects the PCAP to be collected on the server. All of
the basic tests pass on the starter code, without you having
to make any changes. You will need to add to these tests as
you add functionality to your implementation. It is also
important to understand what the given tests are testing for!
"""
# we can make CMUTCP packets using scapy
class CMUTCP(Packet):
name = "CMU TCP"
fields_desc = [
IntField("identifier", 15441),
ShortField("source_port", HOST_PORT),
ShortField("destination_port", TESTING_HOST_PORT),
IntField("seq_num", 0),
IntField("ack_num", 0),
ShortField("hlen", 25),
ShortField("plen", 25),
ByteEnumField(
"flags",
0,
{
FIN_MASK: "FIN",
ACK_MASK: "ACK",
SYN_MASK: "SYN",
FIN_MASK | ACK_MASK: "FIN ACK",
SYN_MASK | ACK_MASK: "SYN ACK",
},
),
ShortField("advertised_window", 1),
ShortField("extension_length", 0),
StrLenField(
"extension_data",
None,
length_from=lambda pkt: pkt.extension_length,
),
]
def answers(self, other):
return isinstance(other, CMUTCP)
bind_layers(UDP, CMUTCP)

View File

@@ -0,0 +1,185 @@
#!/usr/bin/env python3
# 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.
from pathlib import Path
from scapy.all import rdpcap
from fabric import Connection
from common import PCAP, CMUTCP, ACK_MASK, IP_ADDRS
def test_pcap_packets_max_size():
"""Basic test: Check packets are smaller than max size"""
print("Running test_pcap_packets_max_size()")
print(
"Please note that it's now testing on a sample test.pcap file. "
"You should generate your own pcap file and run this test."
)
packets = rdpcap(PCAP)
if len(packets) <= 10:
print("Test Failed")
return
for pkt in packets:
if CMUTCP in pkt:
if len(pkt[CMUTCP]) > 1400:
print("Found packet with length greater than max size")
print("Test Failed")
return
print("Test passed")
def test_pcap_acks():
"""Basic test: Check that every data packet sent has a corresponding ACK
Ignore handshake packets.
"""
print("Running test_pcap_acks()")
print(
"Please note that it's now testing on a sample test.pcap file. "
"You should generate your own pcap file and run this test."
)
packets = rdpcap(PCAP)
if len(packets) <= 10:
print("Test Failed")
return
expected_acks = []
ack_nums = []
for pkt in packets:
if CMUTCP in pkt:
# Ignore handshake packets, should test in a different test.
if pkt[CMUTCP].flags == 0:
payload_len = pkt[CMUTCP].plen - pkt[CMUTCP].hlen
expected_acks.append(pkt[CMUTCP].seq_num + payload_len)
elif pkt[CMUTCP].flags == ACK_MASK:
ack_nums.append(pkt[CMUTCP].ack_num)
# Probably not the best way to do this test!
if set(expected_acks) == set(ack_nums):
print("Test Passed")
else:
print("Test Failed")
# This will try to run the server and client code.
def test_run_server_client():
"""Basic test: Run server and client, and initiate the file transfer."""
print("Running test_run_server_client()")
# We are using `tmux` to run the server and client in the background.
#
# This might also help you debug your code if the test fails. You may call
# `getchar()` in your code to pause the program at any point and then use
# `tmux attach -t pytest_server` or `tmux attach -t pytest_client` to
# attach to the relevant TMUX session and see the output.
start_server_cmd = (
"tmux new -s pytest_server -d /vagrant/project-1_elec3120/server"
)
start_client_cmd = (
"tmux new -s pytest_client -d /vagrant/project-1_elec3120/client"
)
stop_server_cmd = "tmux kill-session -t pytest_server"
stop_client_cmd = "tmux kill-session -t pytest_client"
failed = False
original_file = Path("/vagrant/project-1_elec3120/src/cmu_tcp.c")
received_file = Path("/tmp/file.c")
received_file.unlink(missing_ok=True)
with (
Connection(
host=IP_ADDRS["server"],
user="vagrant",
connect_kwargs={"password": "vagrant"},
) as server_conn,
Connection(
host=IP_ADDRS["client"],
user="vagrant",
connect_kwargs={"password": "vagrant"},
) as client_conn,
):
try:
server_conn.run(start_server_cmd)
server_conn.run("tmux has-session -t pytest_server")
client_conn.run(start_client_cmd)
client_conn.run("tmux has-session -t pytest_client")
# Exit when server finished receiving file.
server_conn.run(
"while tmux has-session -t pytest_server; do sleep 1; done",
hide=True,
)
except Exception:
failed = True
try:
client_conn.run("tmux has-session -t pytest_client", hide=True)
print("stop client")
client_conn.run(stop_client_cmd, hide=True)
except Exception:
# Ignore error here that may occur if client already shut down.
pass
try:
server_conn.local("tmux has-session -t pytest_server", hide=True)
print("stop server")
server_conn.local(stop_server_cmd, hide=True)
except Exception:
# Ignore error here that may occur if server already shut down.
pass
if failed:
print("Test failed: Error running server or client")
return
# Compare SHA256 hashes of the files.
server_hash_result = server_conn.run(f"sha256sum {received_file}")
client_hash_result = client_conn.run(f"sha256sum {original_file}")
if not server_hash_result.ok or not client_hash_result.ok:
print("Test failed: Error getting file hashes")
return
server_hash = server_hash_result.stdout.split()[0]
client_hash = client_hash_result.stdout.split()[0]
if server_hash != client_hash:
print("Test failed: File hashes do not match")
return
print("Test passed")
def test_basic_reliable_data_transfer():
"""Basic test: Check that when you run server and client starter code
that the input file equals the output file
"""
# Can you think of how you can test this? Give it a try!
pass
def test_basic_retransmit():
"""Basic test: Check that when a packet is lost, it's retransmitted"""
# Can you think of how you can test this? Give it a try!
pass
if __name__ == "__main__":
test_pcap_packets_max_size()
test_pcap_acks()
test_run_server_client()

View File

@@ -0,0 +1,128 @@
#!/usr/bin/env python3
# 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.
from fabric import Connection
from scapy.all import sr1, Raw
from common import (
CMUTCP,
ACK_MASK,
TIMEOUT,
IFNAME,
SYN_MASK,
START_TESTING_SERVER_CMD,
STOP_TESTING_SERVER_CMD,
TESTING_HOST_IP,
ip,
udp,
)
payloads = ["pa", "pytest 1234567"]
def test_basic_ack_packets():
print("Running test_basic_ack_packets()")
"""Basic test: Check if when you send data packets, the server responds
with correct ack packet with a correct ack number.
"""
print("Running test_sequence_number()")
for payload in payloads:
print("Testing payload size " + str(len(payload)))
with Connection(
host=TESTING_HOST_IP,
user="vagrant",
connect_kwargs={"password": "vagrant"},
) as conn:
try:
conn.run(START_TESTING_SERVER_CMD)
conn.run("tmux has-session -t pytest_server")
syn_pkt = (
ip /
udp /
CMUTCP(plen=25, seq_num=1000, flags=SYN_MASK)
)
syn_ack_pkt = sr1(syn_pkt, timeout=TIMEOUT, iface=IFNAME)
if (
syn_ack_pkt is None
or syn_ack_pkt[CMUTCP].flags != SYN_MASK | ACK_MASK
or syn_ack_pkt[CMUTCP].ack_num != 1000 + 1
):
print(
"Listener (server) did not properly respond to SYN "
"packet."
)
print("Test Failed")
conn.run(STOP_TESTING_SERVER_CMD)
return
print(syn_ack_pkt[CMUTCP].seq_num)
ack_pkt = (
ip /
udp /
CMUTCP(
plen=25,
seq_num=1001,
ack_num=syn_ack_pkt[CMUTCP].seq_num + 1,
flags=ACK_MASK,
)
)
empty_pkt = sr1(ack_pkt, timeout=0.5, iface=IFNAME)
if empty_pkt is not None:
print("Listener (server) should not respond to ack pkt.")
print("Test Failed")
conn.run(STOP_TESTING_SERVER_CMD)
return
data_pkt = (
ip /
udp /
CMUTCP(
plen=25 + len(payload),
seq_num=1001,
ack_num=syn_ack_pkt[CMUTCP].seq_num + 1,
flags=ACK_MASK,
)
/ Raw(load=payload)
)
server_ack_pkt = sr1(data_pkt, timeout=TIMEOUT, iface=IFNAME)
if (
server_ack_pkt is None
or server_ack_pkt[CMUTCP].flags != ACK_MASK
or server_ack_pkt[CMUTCP].ack_num != 1001 + len(payload)
):
print(
"Listener (server) did not properly respond to data "
"packet."
)
print("Test Failed")
conn.run(STOP_TESTING_SERVER_CMD)
return
finally:
try:
conn.run(STOP_TESTING_SERVER_CMD)
except Exception:
# Ignore error here that may occur if server stopped.
pass
print("Test Passed")
if __name__ == "__main__":
test_basic_ack_packets()

View File

@@ -0,0 +1,87 @@
/**
*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 used for testing.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "cmu_tcp.h"
/*
* Param: sock - used for reading and writing to a connection
*
*
*/
void functionality(cmu_socket_t *sock) {
uint8_t buf[9898];
FILE *fp;
int n;
int read;
// Wait to hear from an initiator
n = 0;
while (n == 0) {
n = cmu_read(sock, buf, 9898, NO_FLAG);
}
printf("read something %d\n", n);
// Send over a random file
fp = fopen("/vagrant/project-1_elec3120/tests/random.input", "rb");
read = 1;
while (read > 0) {
read = fread(buf, 1, 2000, fp);
if (read > 0) cmu_write(sock, buf, read);
}
}
/*
* Param: argc - count of command line arguments provided
* Param: argv - values of command line arguments provided
*
* Purpose: To provide a sample listener for the TCP connection.
*
*/
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);
printf("starting initiator\n");
if (cmu_socket(&socket, TCP_LISTENER, portno, serverip) < 0)
exit(EXIT_FAILURE);
printf("finished socket\n");
functionality(&socket);
sleep(5);
if (cmu_close(&socket) < 0) exit(EXIT_FAILURE);
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env bash
# 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.
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
HOST=$(hostname)
IFNAME=$(ifconfig | grep -B1 10.0.1. | grep -o "^\w*")
FUNCTION_TO_RUN=$1
PCAP_NAME=$2
if [ -z "$FUNCTION_TO_RUN" ]
then
echo "usage: ./capture_packets.sh < start | stop | analyze > PCAP_NAME"
echo "Expecting name of function to run: start, stop, or analyze."
exit 1
fi
if [ -z "$PCAP_NAME" ]
then
PCAP_NAME=$HOST.pcap
echo NO PCAP_NAME PARAM SO USING DEFAULT FILE: $PCAP_NAME
fi
start() {
sudo tcpdump -i $IFNAME -w $PCAP_NAME udp > /dev/null 2> /dev/null < \
/dev/null &
}
stop() {
sudo pkill -f "tcpdump -i $IFNAME -w $PCAP_NAME udp"
}
analyze() {
tshark -X lua_script:$DIR/tcp.lua -R "cmutcp and not icmp" -r $PCAP_NAME \
-T fields \
-e frame.time_relative \
-e ip.src \
-e cmutcp.source_port \
-e ip.dst \
-e cmutcp.destination_port \
-e cmutcp.seq_num \
-e cmutcp.ack_num \
-e cmutcp.hlen \
-e cmutcp.plen \
-e cmutcp.flags \
-e cmutcp.advertised_window \
-e cmutcp.extension_length \
-E header=y -E separator=, \
-2
}
$FUNCTION_TO_RUN

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# 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.
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
echo "Preparing submission..."
echo "Make sure to commit all files that you want to submit."
cd $SCRIPT_DIR/../..
git archive -o handin.tar.gz HEAD
echo "Upload handin.tar.gz to Gradescope."

View File

@@ -0,0 +1,72 @@
-- 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.
tcp = Proto("cmutcp", "CMU TCP")
local f_identifier = ProtoField.uint32("cmutcp.identifier", "Identifier")
local f_source_port = ProtoField.uint16("cmutcp.source_port", "Source Port")
local f_destination_port = ProtoField.uint16("cmutcp.destination_port", "Destination Port")
local f_seq_num = ProtoField.uint32("cmutcp.seq_num", "Sequence Number")
local f_ack_num = ProtoField.uint32("cmutcp.ack_num", "ACK Number")
local f_hlen = ProtoField.uint16("cmutcp.hlen", "Header Length")
local f_plen = ProtoField.uint16("cmutcp.plen", "Packet Length")
local f_flags = ProtoField.uint8("cmutcp.flags", "Flags")
local f_advertised_window = ProtoField.uint16("cmutcp.advertised_window", "Advertised Window")
local f_extension_length = ProtoField.uint16("cmutcp.extension_length", "Extension Length")
local f_extension_data = ProtoField.string("cmutcp.extension_data", "Extension Data")
tcp.fields = { f_identifier, f_source_port, f_destination_port, f_seq_num, f_ack_num, f_hlen, f_plen, f_flags, f_advertised_window, f_extension_length , f_extension_data}
function tcp.dissector(tvb, pInfo, root) -- Tvb, Pinfo, TreeItem
if (tvb:len() ~= tvb:reported_len()) then
return 0 -- ignore partially captured packets
-- this can/may be re-enabled only for unfragmented UDP packets
end
local t = root:add(tcp, tvb(0,25))
t:add(f_identifier, tvb(0,4))
t:add(f_source_port, tvb(4,2))
t:add(f_destination_port, tvb(6,2))
t:add(f_seq_num, tvb(8,4))
t:add(f_ack_num, tvb(12,4))
t:add(f_hlen, tvb(16,2))
t:add(f_plen, tvb(18,2))
local f = t:add(f_flags, tvb(20,1))
t:add(f_advertised_window, tvb(21,2))
t:add(f_extension_length, tvb(23,2))
local extension_length = tvb(23,1):int()
t:add(f_extension_data, tvb(25,extension_length))
local flag = tvb(20,1):uint()
if bit.band(flag, 8) ~= 0 then
f:add(tvb(20,1), "SYN")
end
if bit.band(flag, 4) ~= 0 then
f:add(tvb(20,1), "ACK")
end
if bit.band(flag, 2) ~= 0 then
f:add(tvb(20,1), "FIN")
end
pInfo.cols.protocol = "CMU TCP"
end
-- have to put the port for the server here
local udpDissectorTable = DissectorTable.get("udp.port")
udpDissectorTable:add("15441", tcp)
io.stderr:write("tcp.lua is successfully loaded\n")