Skip to content

Commit

Permalink
CS144 Lab checkpoint 5 starter code
Browse files Browse the repository at this point in the history
  • Loading branch information
keithw committed Oct 20, 2021
1 parent 9d00589 commit b0006b4
Show file tree
Hide file tree
Showing 18 changed files with 970 additions and 7 deletions.
1 change: 1 addition & 0 deletions apps/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ add_sponge_exec (tcp_native stream_copy)
add_sponge_exec (tun)
add_sponge_exec (tcp_udp stream_copy)
add_sponge_exec (tcp_ipv4 stream_copy)
add_sponge_exec (tcp_ip_ethernet stream_copy)
add_sponge_exec (webget)
add_sponge_exec (tcp_benchmark)
142 changes: 142 additions & 0 deletions apps/tcp_ip_ethernet.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#include "bidirectional_stream_copy.hh"
#include "tcp_config.hh"
#include "tcp_sponge_socket.hh"
#include "tun.hh"

#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <random>
#include <string>
#include <tuple>

using namespace std;

constexpr const char *TAP_DFLT = "tap10";
const string LOCAL_ADDRESS_DFLT = "169.254.10.9";
const string GATEWAY_DFLT = "169.254.10.1";

static void show_usage(const char *argv0, const char *msg) {
cout << "Usage: " << argv0 << " [options] <host> <port>\n\n"
<< " Option Default\n"
<< " -- --\n\n"

<< " -a <addr> Set IP source address (client mode only) " << LOCAL_ADDRESS_DFLT << "\n"
<< " -s <port> Set TCP source port (client mode only) (random)\n\n"
<< " -n <addr> Set IP next-hop address " << GATEWAY_DFLT << "\n"

<< " -w <winsz> Use a window of <winsz> bytes " << TCPConfig::MAX_PAYLOAD_SIZE
<< "\n\n"

<< " -t <tmout> Set rt_timeout to tmout " << TCPConfig::TIMEOUT_DFLT << "\n\n"

<< " -d <tapdev> Connect to tap <tapdev> " << TAP_DFLT << "\n\n"

<< " -h Show this message.\n\n";

if (msg != nullptr) {
cout << msg;
}
cout << endl;
}

static void check_argc(int argc, char **argv, int curr, const char *err) {
if (curr + 3 >= argc) {
show_usage(argv[0], err);
exit(1);
}
}

static tuple<TCPConfig, FdAdapterConfig, Address, string> get_config(int argc, char **argv) {
TCPConfig c_fsm{};
FdAdapterConfig c_filt{};
string tapdev = TAP_DFLT;

int curr = 1;

string source_address = LOCAL_ADDRESS_DFLT;
string source_port = to_string(uint16_t(random_device()()));
string next_hop_address = GATEWAY_DFLT;

while (argc - curr > 2) {
if (strncmp("-a", argv[curr], 3) == 0) {
check_argc(argc, argv, curr, "ERROR: -a requires one argument.");
source_address = argv[curr + 1];
curr += 2;

} else if (strncmp("-s", argv[curr], 3) == 0) {
check_argc(argc, argv, curr, "ERROR: -s requires one argument.");
source_port = argv[curr + 1];
curr += 2;

} else if (strncmp("-n", argv[curr], 3) == 0) {
check_argc(argc, argv, curr, "ERROR: -n requires one argument.");
next_hop_address = argv[curr + 1];
curr += 2;

} else if (strncmp("-w", argv[curr], 3) == 0) {
check_argc(argc, argv, curr, "ERROR: -w requires one argument.");
c_fsm.recv_capacity = strtol(argv[curr + 1], nullptr, 0);
curr += 2;

} else if (strncmp("-t", argv[curr], 3) == 0) {
check_argc(argc, argv, curr, "ERROR: -t requires one argument.");
c_fsm.rt_timeout = strtol(argv[curr + 1], nullptr, 0);
curr += 2;

} else if (strncmp("-d", argv[curr], 3) == 0) {
check_argc(argc, argv, curr, "ERROR: -t requires one argument.");
tapdev = argv[curr + 1];
curr += 2;

} else if (strncmp("-h", argv[curr], 3) == 0) {
show_usage(argv[0], nullptr);
exit(0);

} else {
show_usage(argv[0], string("ERROR: unrecognized option " + string(argv[curr])).c_str());
exit(1);
}
}

// parse positional command-line arguments
c_filt.destination = {argv[curr], argv[curr + 1]};
c_filt.source = {source_address, source_port};

Address next_hop{next_hop_address, "0"};

return make_tuple(c_fsm, c_filt, next_hop, tapdev);
}

int main(int argc, char **argv) {
try {
if (argc < 3) {
show_usage(argv[0], "ERROR: required arguments are missing.");
return EXIT_FAILURE;
}

// choose a random local Ethernet address (and make sure it's private, i.e. not owned by a manufacturer)
EthernetAddress local_ethernet_address;
for (auto &byte : local_ethernet_address) {
byte = random_device()(); // use a random local Ethernet address
}
local_ethernet_address.at(0) |= 0x02; // "10" in last two binary digits marks a private Ethernet address
local_ethernet_address.at(0) &= 0xfe;

auto [c_fsm, c_filt, next_hop, tap_dev_name] = get_config(argc, argv);

TCPOverIPv4OverEthernetSpongeSocket tcp_socket(TCPOverIPv4OverEthernetAdapter(
TCPOverIPv4OverEthernetAdapter(TapFD(tap_dev_name), local_ethernet_address, c_filt.source, next_hop)));

tcp_socket.connect(c_fsm, c_filt);

bidirectional_stream_copy(tcp_socket);
tcp_socket.wait_until_closed();
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}

return EXIT_SUCCESS;
}
46 changes: 46 additions & 0 deletions libsponge/network_interface.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#include "network_interface.hh"

#include "arp_message.hh"
#include "ethernet_frame.hh"

#include <iostream>

// Dummy implementation of a network interface
// Translates from {IP datagram, next hop address} to link-layer frame, and from link-layer frame to IP datagram

// For Lab 5, please replace with a real implementation that passes the
// automated checks run by `make check_lab5`.

// You will need to add private members to the class declaration in `network_interface.hh`

template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}

using namespace std;

//! \param[in] ethernet_address Ethernet (what ARP calls "hardware") address of the interface
//! \param[in] ip_address IP (what ARP calls "protocol") address of the interface
NetworkInterface::NetworkInterface(const EthernetAddress &ethernet_address, const Address &ip_address)
: _ethernet_address(ethernet_address), _ip_address(ip_address) {
cerr << "DEBUG: Network interface has Ethernet address " << to_string(_ethernet_address) << " and IP address "
<< ip_address.ip() << "\n";
}

//! \param[in] dgram the IPv4 datagram to be sent
//! \param[in] next_hop the IP address of the interface to send it to (typically a router or default gateway, but may also be another host if directly connected to the same network as the destination)
//! (Note: the Address type can be converted to a uint32_t (raw 32-bit IP address) with the Address::ipv4_numeric() method.)
void NetworkInterface::send_datagram(const InternetDatagram &dgram, const Address &next_hop) {
// convert IP address of next hop to raw 32-bit representation (used in ARP header)
const uint32_t next_hop_ip = next_hop.ipv4_numeric();

DUMMY_CODE(dgram, next_hop, next_hop_ip);
}

//! \param[in] frame the incoming Ethernet frame
optional<InternetDatagram> NetworkInterface::recv_frame(const EthernetFrame &frame) {
DUMMY_CODE(frame);
return {};
}

//! \param[in] ms_since_last_tick the number of milliseconds since the last call to this method
void NetworkInterface::tick(const size_t ms_since_last_tick) { DUMMY_CODE(ms_since_last_tick); }
67 changes: 67 additions & 0 deletions libsponge/network_interface.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#ifndef SPONGE_LIBSPONGE_NETWORK_INTERFACE_HH
#define SPONGE_LIBSPONGE_NETWORK_INTERFACE_HH

#include "ethernet_frame.hh"
#include "tcp_over_ip.hh"
#include "tun.hh"

#include <optional>
#include <queue>

//! \brief A "network interface" that connects IP (the internet layer, or network layer)
//! with Ethernet (the network access layer, or link layer).

//! This module is the lowest layer of a TCP/IP stack
//! (connecting IP with the lower-layer network protocol,
//! e.g. Ethernet). But the same module is also used repeatedly
//! as part of a router: a router generally has many network
//! interfaces, and the router's job is to route Internet datagrams
//! between the different interfaces.

//! The network interface translates datagrams (coming from the
//! "customer," e.g. a TCP/IP stack or router) into Ethernet
//! frames. To fill in the Ethernet destination address, it looks up
//! the Ethernet address of the next IP hop of each datagram, making
//! requests with the [Address Resolution Protocol](\ref rfc::rfc826).
//! In the opposite direction, the network interface accepts Ethernet
//! frames, checks if they are intended for it, and if so, processes
//! the the payload depending on its type. If it's an IPv4 datagram,
//! the network interface passes it up the stack. If it's an ARP
//! request or reply, the network interface processes the frame
//! and learns or replies as necessary.
class NetworkInterface {
private:
//! Ethernet (known as hardware, network-access-layer, or link-layer) address of the interface
EthernetAddress _ethernet_address;

//! IP (known as internet-layer or network-layer) address of the interface
Address _ip_address;

//! outbound queue of Ethernet frames that the NetworkInterface wants sent
std::queue<EthernetFrame> _frames_out{};

public:
//! \brief Construct a network interface with given Ethernet (network-access-layer) and IP (internet-layer) addresses
NetworkInterface(const EthernetAddress &ethernet_address, const Address &ip_address);

//! \brief Access queue of Ethernet frames awaiting transmission
std::queue<EthernetFrame> &frames_out() { return _frames_out; }

//! \brief Sends an IPv4 datagram, encapsulated in an Ethernet frame (if it knows the Ethernet destination address).

//! Will need to use [ARP](\ref rfc::rfc826) to look up the Ethernet destination address for the next hop
//! ("Sending" is accomplished by pushing the frame onto the frames_out queue.)
void send_datagram(const InternetDatagram &dgram, const Address &next_hop);

//! \brief Receives an Ethernet frame and responds appropriately.

//! If type is IPv4, returns the datagram.
//! If type is ARP request, learn a mapping from the "sender" fields, and send an ARP reply.
//! If type is ARP reply, learn a mapping from the "sender" fields.
std::optional<InternetDatagram> recv_frame(const EthernetFrame &frame);

//! \brief Called periodically when time elapses
void tick(const size_t ms_since_last_tick);
};

#endif // SPONGE_LIBSPONGE_NETWORK_INTERFACE_HH
113 changes: 113 additions & 0 deletions libsponge/tap.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/bin/bash

show_usage () {
echo "Usage: $0 <start | stop | restart | check> [tapnum ...]"
exit 1
}

start_tap () {
local TAPNUM="$1" TAPDEV="tap$1" LLADDR="02:B0:1D:FA:CE:"`printf "%02x" $1`
ip tuntap add mode tap user "${SUDO_USER}" name "${TAPDEV}"
ip link set "${TAPDEV}" address "${LLADDR}"

ip addr add "${TUN_IP_PREFIX}.${TAPNUM}.1/24" dev "${TAPDEV}"
ip link set dev "${TAPDEV}" up
ip route change "${TUN_IP_PREFIX}.${TAPNUM}.0/24" dev "${TAPDEV}" rto_min 10ms

# Apply NAT (masquerading) only to traffic from CS144's network devices
iptables -t nat -A PREROUTING -s ${TUN_IP_PREFIX}.${TAPNUM}.0/24 -j CONNMARK --set-mark ${TAPNUM}
iptables -t nat -A POSTROUTING -j MASQUERADE -m connmark --mark ${TAPNUM}
echo 1 > /proc/sys/net/ipv4/ip_forward
}

stop_tap () {
local TAPDEV="tap$1"
iptables -t nat -D PREROUTING -s ${TUN_IP_PREFIX}.${1}.0/24 -j CONNMARK --set-mark ${1}
iptables -t nat -D POSTROUTING -j MASQUERADE -m connmark --mark ${1}
ip tuntap del mode tap name "$TAPDEV"
}

start_all () {
while [ ! -z "$1" ]; do
local INTF="$1"; shift
start_tap "$INTF"
done
}

stop_all () {
while [ ! -z "$1" ]; do
local INTF="$1"; shift
stop_tap "$INTF"
done
}

restart_all() {
stop_all "$@"
start_all "$@"
}

check_tap () {
[ "$#" != 1 ] && { echo "bad params in check_tap"; exit 1; }
local TAPDEV="tap${1}"
# make sure tap is healthy: device is up, ip_forward is set, and iptables is configured
ip link show ${TAPDEV} &>/dev/null || return 1
[ "$(cat /proc/sys/net/ipv4/ip_forward)" = "1" ] || return 2
}

check_sudo () {
if [ "$SUDO_USER" = "root" ]; then
echo "please execute this script as a regular user, not as root"
exit 1
fi
if [ -z "$SUDO_USER" ]; then
# if the user didn't call us with sudo, re-execute
exec sudo $0 "$MODE" "$@"
fi
}

# check arguments
if [ -z "$1" ] || ([ "$1" != "start" ] && [ "$1" != "stop" ] && [ "$1" != "restart" ] && [ "$1" != "check" ]); then
show_usage
fi
MODE=$1; shift

# set default argument
if [ "$#" = "0" ]; then
set -- 10
fi

# execute 'check' before trying to sudo
# - like start, but exit successfully if everything is OK
if [ "$MODE" = "check" ]; then
declare -a INTFS
MODE="start"
while [ ! -z "$1" ]; do
INTF="$1"; shift
check_tap ${INTF}
RET=$?
if [ "$RET" = "0" ]; then
continue
fi

if [ "$((RET > 1))" = "1" ]; then
MODE="restart"
fi
INTFS+=($INTF)
done

# address only the interfaces that need it
set -- "${INTFS[@]}"
if [ "$#" = "0" ]; then
exit 0
fi
echo -e "[$0] Bringing up tunnels ${INTFS[@]}:"
fi

# sudo if necessary
check_sudo "$@"

# get configuration
. "$(dirname "$0")"/etc/tunconfig

# start, stop, or restart all intfs
eval "${MODE}_all" "$@"
Loading

0 comments on commit b0006b4

Please sign in to comment.