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