Merge remote-tracking branch 'official/lab5-startercode'

This commit is contained in:
ridethepig 2023-02-18 05:40:09 +00:00
commit 86ab4dfba4
18 changed files with 970 additions and 7 deletions

View File

@ -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
apps/tcp_ip_ethernet.cc Normal file
View File

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

View File

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

View File

@ -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
libsponge/tap.sh Executable file
View File

@ -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" "$@"

View File

@ -0,0 +1,88 @@
#include "arp_message.hh"
#include <arpa/inet.h>
#include <iomanip>
#include <sstream>
using namespace std;
ParseResult ARPMessage::parse(const Buffer buffer) {
NetParser p{buffer};
if (p.buffer().size() < ARPMessage::LENGTH) {
return ParseResult::PacketTooShort;
}
hardware_type = p.u16();
protocol_type = p.u16();
hardware_address_size = p.u8();
protocol_address_size = p.u8();
opcode = p.u16();
if (not supported()) {
return ParseResult::Unsupported;
}
// read sender addresses (Ethernet and IP)
for (auto &byte : sender_ethernet_address) {
byte = p.u8();
}
sender_ip_address = p.u32();
// read target addresses (Ethernet and IP)
for (auto &byte : target_ethernet_address) {
byte = p.u8();
}
target_ip_address = p.u32();
return p.get_error();
}
bool ARPMessage::supported() const {
return hardware_type == TYPE_ETHERNET and protocol_type == EthernetHeader::TYPE_IPv4 and
hardware_address_size == sizeof(EthernetHeader::src) and protocol_address_size == sizeof(IPv4Header::src) and
((opcode == OPCODE_REQUEST) or (opcode == OPCODE_REPLY));
}
string ARPMessage::serialize() const {
if (not supported()) {
throw runtime_error(
"ARPMessage::serialize(): unsupported field combination (must be Ethernet/IP, and request or reply)");
}
string ret;
NetUnparser::u16(ret, hardware_type);
NetUnparser::u16(ret, protocol_type);
NetUnparser::u8(ret, hardware_address_size);
NetUnparser::u8(ret, protocol_address_size);
NetUnparser::u16(ret, opcode);
/* write sender addresses */
for (auto &byte : sender_ethernet_address) {
NetUnparser::u8(ret, byte);
}
NetUnparser::u32(ret, sender_ip_address);
/* write target addresses */
for (auto &byte : target_ethernet_address) {
NetUnparser::u8(ret, byte);
}
NetUnparser::u32(ret, target_ip_address);
return ret;
}
string ARPMessage::to_string() const {
stringstream ss{};
string opcode_str = "(unknown type)";
if (opcode == OPCODE_REQUEST) {
opcode_str = "REQUEST";
}
if (opcode == OPCODE_REPLY) {
opcode_str = "REPLY";
}
ss << "opcode=" << opcode_str << ", sender=" << ::to_string(sender_ethernet_address) << "/"
<< inet_ntoa({htobe32(sender_ip_address)}) << ", target=" << ::to_string(target_ethernet_address) << "/"
<< inet_ntoa({htobe32(target_ip_address)});
return ss.str();
}

View File

@ -0,0 +1,47 @@
#ifndef SPONGE_LIBSPONGE_ARP_MESSAGE_HH
#define SPONGE_LIBSPONGE_ARP_MESSAGE_HH
#include "ethernet_header.hh"
#include "ipv4_header.hh"
using EthernetAddress = std::array<uint8_t, 6>;
//! \brief [ARP](\ref rfc::rfc826) message
struct ARPMessage {
static constexpr size_t LENGTH = 28; //!< ARP message length in bytes
static constexpr uint16_t TYPE_ETHERNET = 1; //!< ARP type for Ethernet/Wi-Fi as link-layer protocol
static constexpr uint16_t OPCODE_REQUEST = 1;
static constexpr uint16_t OPCODE_REPLY = 2;
//! \name ARPheader fields
//!@{
uint16_t hardware_type = TYPE_ETHERNET; //!< Type of the link-layer protocol (generally Ethernet/Wi-Fi)
uint16_t protocol_type = EthernetHeader::TYPE_IPv4; //!< Type of the Internet-layer protocol (generally IPv4)
uint8_t hardware_address_size = sizeof(EthernetHeader::src);
uint8_t protocol_address_size = sizeof(IPv4Header::src);
uint16_t opcode{}; //!< Request or reply
EthernetAddress sender_ethernet_address{};
uint32_t sender_ip_address{};
EthernetAddress target_ethernet_address{};
uint32_t target_ip_address{};
//!@}
//! Parse the ARP message from a string
ParseResult parse(const Buffer buffer);
//! Serialize the ARP message to a string
std::string serialize() const;
//! Return a string containing the ARP message in human-readable format
std::string to_string() const;
//! Is this type of ARP message supported by the parser?
bool supported() const;
};
//! \struct ARPMessage
//! This struct can be used to parse an existing ARP message or to create a new one.
#endif // SPONGE_LIBSPONGE_ETHERNET_HEADER_HH

View File

@ -0,0 +1,24 @@
#include "ethernet_frame.hh"
#include "parser.hh"
#include "util.hh"
#include <stdexcept>
#include <string>
using namespace std;
ParseResult EthernetFrame::parse(const Buffer buffer) {
NetParser p{buffer};
_header.parse(p);
_payload = p.buffer();
return p.get_error();
}
BufferList EthernetFrame::serialize() const {
BufferList ret;
ret.append(_header.serialize());
ret.append(_payload);
return ret;
}

View File

@ -0,0 +1,30 @@
#ifndef SPONGE_LIBSPONGE_ETHERNET_FRAME_HH
#define SPONGE_LIBSPONGE_ETHERNET_FRAME_HH
#include "buffer.hh"
#include "ethernet_header.hh"
//! \brief Ethernet frame
class EthernetFrame {
private:
EthernetHeader _header{};
BufferList _payload{};
public:
//! \brief Parse the frame from a string
ParseResult parse(const Buffer buffer);
//! \brief Serialize the frame to a string
BufferList serialize() const;
//! \name Accessors
//!@{
const EthernetHeader &header() const { return _header; }
EthernetHeader &header() { return _header; }
const BufferList &payload() const { return _payload; }
BufferList &payload() { return _payload; }
//!@}
};
#endif // SPONGE_LIBSPONGE_ETHERNET_FRAME_HH

View File

@ -0,0 +1,83 @@
#include "ethernet_header.hh"
#include "util.hh"
#include <iomanip>
#include <sstream>
using namespace std;
ParseResult EthernetHeader::parse(NetParser &p) {
if (p.buffer().size() < EthernetHeader::LENGTH) {
return ParseResult::PacketTooShort;
}
/* read destination address */
for (auto &byte : dst) {
byte = p.u8();
}
/* read source address */
for (auto &byte : src) {
byte = p.u8();
}
/* read the frame's type (e.g. IPv4, ARP, or something else) */
type = p.u16();
return p.get_error();
}
string EthernetHeader::serialize() const {
string ret;
ret.reserve(LENGTH);
/* write destination address */
for (auto &byte : dst) {
NetUnparser::u8(ret, byte);
}
/* write source address */
for (auto &byte : src) {
NetUnparser::u8(ret, byte);
}
/* write the frame's type (e.g. IPv4, ARP or something else) */
NetUnparser::u16(ret, type);
return ret;
}
//! \returns A string with a textual representation of an Ethernet address
string to_string(const EthernetAddress address) {
stringstream ss{};
for (auto it = address.begin(); it != address.end(); it++) {
ss.width(2);
ss << setfill('0') << hex << int(*it);
if (it != address.end() - 1) {
ss << ":";
}
}
return ss.str();
}
//! \returns A string with the header's contents
string EthernetHeader::to_string() const {
stringstream ss{};
ss << "dst=" << ::to_string(dst);
ss << ", src=" << ::to_string(src);
ss << ", type=";
switch (type) {
case TYPE_IPv4:
ss << "IPv4";
break;
case TYPE_ARP:
ss << "ARP";
break;
default:
ss << "[unknown type " << hex << type << "!]";
break;
}
return ss.str();
}

View File

@ -0,0 +1,43 @@
#ifndef SPONGE_LIBSPONGE_ETHERNET_HEADER_HH
#define SPONGE_LIBSPONGE_ETHERNET_HEADER_HH
#include "parser.hh"
#include <array>
//! Helper type for an Ethernet address (an array of six bytes)
using EthernetAddress = std::array<uint8_t, 6>;
//! Ethernet broadcast address (ff:ff:ff:ff:ff:ff)
constexpr EthernetAddress ETHERNET_BROADCAST = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
//! Printable representation of an EthernetAddress
std::string to_string(const EthernetAddress address);
//! \brief Ethernet frame header
struct EthernetHeader {
static constexpr size_t LENGTH = 14; //!< Ethernet header length in bytes
static constexpr uint16_t TYPE_IPv4 = 0x800; //!< Type number for [IPv4](\ref rfc::rfc791)
static constexpr uint16_t TYPE_ARP = 0x806; //!< Type number for [ARP](\ref rfc::rfc826)
//! \name Ethernet header fields
//!@{
EthernetAddress dst;
EthernetAddress src;
uint16_t type;
//!@}
//! Parse the Ethernet fields from the provided NetParser
ParseResult parse(NetParser &p);
//! Serialize the Ethernet fields to a string
std::string serialize() const;
//! Return a string containing a header in human-readable format
std::string to_string() const;
};
//! \struct EthernetHeader
//! This struct can be used to parse an existing Ethernet header or to create a new one.
#endif // SPONGE_LIBSPONGE_ETHERNET_HEADER_HH

View File

@ -1,5 +1,6 @@
#include "tcp_sponge_socket.hh"
#include "network_interface.hh"
#include "parser.hh"
#include "tun.hh"
#include "util.hh"
@ -211,7 +212,7 @@ void TCPSpongeSocket<AdaptT>::connect(const TCPConfig &c_tcp, const FdAdapterCon
_datagram_adapter.config_mut() = c_ad;
cerr << "DEBUG: Connecting to " << c_ad.destination.to_string() << "... ";
cerr << "DEBUG: Connecting to " << c_ad.destination.to_string() << "...\n";
_tcp->connect();
const TCPState expected_state = TCPState::State::SYN_SENT;
@ -222,7 +223,7 @@ void TCPSpongeSocket<AdaptT>::connect(const TCPConfig &c_tcp, const FdAdapterCon
}
_tcp_loop([&] { return _tcp->state() == TCPState::State::SYN_SENT; });
cerr << "done.\n";
cerr << "Successfully connected to " << c_ad.destination.to_string() << ".\n";
_tcp_thread = thread(&TCPSpongeSocket::_tcp_main, this);
}
@ -240,12 +241,12 @@ void TCPSpongeSocket<AdaptT>::listen_and_accept(const TCPConfig &c_tcp, const Fd
_datagram_adapter.config_mut() = c_ad;
_datagram_adapter.set_listening(true);
cerr << "DEBUG: Listening for incoming connection... ";
cerr << "DEBUG: Listening for incoming connection...\n";
_tcp_loop([&] {
const auto s = _tcp->state();
return (s == TCPState::State::LISTEN or s == TCPState::State::SYN_RCVD or s == TCPState::State::SYN_SENT);
});
cerr << "new connection from " << _datagram_adapter.config().destination.to_string() << ".\n";
cerr << "New connection from " << _datagram_adapter.config().destination.to_string() << ".\n";
_tcp_thread = thread(&TCPSpongeSocket::_tcp_main, this);
}
@ -275,6 +276,9 @@ template class TCPSpongeSocket<TCPOverUDPSocketAdapter>;
//! Specialization of TCPSpongeSocket for TCPOverIPv4OverTunFdAdapter
template class TCPSpongeSocket<TCPOverIPv4OverTunFdAdapter>;
//! Specialization of TCPSpongeSocket for TCPOverIPv4OverEthernetAdapter
template class TCPSpongeSocket<TCPOverIPv4OverEthernetAdapter>;
//! Specialization of TCPSpongeSocket for LossyTCPOverUDPSocketAdapter
template class TCPSpongeSocket<LossyTCPOverUDPSocketAdapter>;
@ -293,3 +297,34 @@ void CS144TCPSocket::connect(const Address &address) {
TCPOverIPv4SpongeSocket::connect(tcp_config, multiplexer_config);
}
static const string LOCAL_TAP_IP_ADDRESS = "169.254.10.9";
static const string LOCAL_TAP_NEXT_HOP_ADDRESS = "169.254.10.1";
EthernetAddress random_private_ethernet_address() {
EthernetAddress addr;
for (auto &byte : addr) {
byte = random_device()(); // use a random local Ethernet address
}
addr.at(0) |= 0x02; // "10" in last two binary digits marks a private Ethernet address
addr.at(0) &= 0xfe;
return addr;
}
FullStackSocket::FullStackSocket()
: TCPOverIPv4OverEthernetSpongeSocket(TCPOverIPv4OverEthernetAdapter(TapFD("tap10"),
random_private_ethernet_address(),
Address(LOCAL_TAP_IP_ADDRESS, "0"),
Address(LOCAL_TAP_NEXT_HOP_ADDRESS, "0"))) {}
void FullStackSocket::connect(const Address &address) {
TCPConfig tcp_config;
tcp_config.rt_timeout = 100;
FdAdapterConfig multiplexer_config;
multiplexer_config.source = {LOCAL_TAP_IP_ADDRESS, to_string(uint16_t(random_device()()))};
multiplexer_config.destination = address;
TCPOverIPv4OverEthernetSpongeSocket::connect(tcp_config, multiplexer_config);
}

View File

@ -5,9 +5,9 @@
#include "eventloop.hh"
#include "fd_adapter.hh"
#include "file_descriptor.hh"
#include "network_interface.hh"
#include "tcp_config.hh"
#include "tcp_connection.hh"
#include "tcp_over_ip.hh"
#include "tuntap_adapter.hh"
#include <atomic>
@ -23,9 +23,11 @@ class TCPSpongeSocket : public LocalStreamSocket {
//! Stream socket for reads and writes between owner and TCP thread
LocalStreamSocket _thread_data;
protected:
//! Adapter to underlying datagram socket (e.g., UDP or IP)
AdaptT _datagram_adapter;
private:
//! Set up the TCPConnection and the event loop
void _initialize_TCP(const TCPConfig &config);
@ -96,6 +98,8 @@ class TCPSpongeSocket : public LocalStreamSocket {
using TCPOverUDPSpongeSocket = TCPSpongeSocket<TCPOverUDPSocketAdapter>;
using TCPOverIPv4SpongeSocket = TCPSpongeSocket<TCPOverIPv4OverTunFdAdapter>;
using TCPOverIPv4OverEthernetSpongeSocket = TCPSpongeSocket<TCPOverIPv4OverEthernetAdapter>;
using LossyTCPOverUDPSpongeSocket = TCPSpongeSocket<LossyTCPOverUDPSocketAdapter>;
using LossyTCPOverIPv4SpongeSocket = TCPSpongeSocket<LossyTCPOverIPv4OverTunFdAdapter>;
@ -126,4 +130,14 @@ class CS144TCPSocket : public TCPOverIPv4SpongeSocket {
void connect(const Address &address);
};
//! Helper class that makes a TCPOverIPv4overEthernetSpongeSocket behave more like a (kernel) TCPSocket
class FullStackSocket : public TCPOverIPv4OverEthernetSpongeSocket {
public:
//! Construct a TCP (stream) socket, using the CS144 TCPConnection object,
//! that encapsulates TCP segments in IP datagrams, then encapsulates
//! those IP datagrams in Ethernet frames sent to the Ethernet address of the next hop.
FullStackSocket();
void connect(const Address &address);
};
#endif // SPONGE_LIBSPONGE_TCP_SPONGE_SOCKET_HH

View File

@ -2,5 +2,58 @@
using namespace std;
//! \param[in] tap Raw network device that will be owned by the adapter
//! \param[in] eth_address Ethernet address (local address) of the adapter
//! \param[in] ip_address IP address (local address) of the adapter
//! \param[in] next_hop IP address of the next hop (typically a router or default gateway)
TCPOverIPv4OverEthernetAdapter::TCPOverIPv4OverEthernetAdapter(TapFD &&tap,
const EthernetAddress &eth_address,
const Address &ip_address,
const Address &next_hop)
: _tap(move(tap)), _interface(eth_address, ip_address), _next_hop(next_hop) {
// Linux seems to ignore the first frame sent on a TAP device, so send a dummy frame to prime the pump :-(
EthernetFrame dummy_frame;
_tap.write(dummy_frame.serialize());
}
optional<TCPSegment> TCPOverIPv4OverEthernetAdapter::read() {
// Read Ethernet frame from the raw device
EthernetFrame frame;
if (frame.parse(_tap.read()) != ParseResult::NoError) {
return {};
}
// Give the frame to the NetworkInterface. Get back an Internet datagram if frame was carrying one.
optional<InternetDatagram> ip_dgram = _interface.recv_frame(frame);
// The incoming frame may have caused the NetworkInterface to send a frame.
send_pending();
// Try to interpret IPv4 datagram as TCP
if (ip_dgram) {
return unwrap_tcp_in_ip(ip_dgram.value());
}
return {};
}
//! \param[in] ms_since_last_tick the number of milliseconds since the last call to this method
void TCPOverIPv4OverEthernetAdapter::tick(const size_t ms_since_last_tick) {
_interface.tick(ms_since_last_tick);
send_pending();
}
//! \param[in] seg the TCPSegment to send
void TCPOverIPv4OverEthernetAdapter::write(TCPSegment &seg) {
_interface.send_datagram(wrap_tcp_in_ip(seg), _next_hop);
send_pending();
}
void TCPOverIPv4OverEthernetAdapter::send_pending() {
while (not _interface.frames_out().empty()) {
_tap.write(_interface.frames_out().front().serialize());
_interface.frames_out().pop();
}
}
//! Specialize LossyFdAdapter to TCPOverIPv4OverTunFdAdapter
template class LossyFdAdapter<TCPOverIPv4OverTunFdAdapter>;

View File

@ -1,7 +1,8 @@
#ifndef SPONGE_LIBSPONGE_TUNFD_ADAPTER_HH
#define SPONGE_LIBSPONGE_TUNFD_ADAPTER_HH
#include "tcp_over_ip.hh"
#include "ethernet_header.hh"
#include "network_interface.hh"
#include "tun.hh"
#include <optional>
@ -39,4 +40,37 @@ class TCPOverIPv4OverTunFdAdapter : public TCPOverIPv4Adapter {
//! Typedef for TCPOverIPv4OverTunFdAdapter
using LossyTCPOverIPv4OverTunFdAdapter = LossyFdAdapter<TCPOverIPv4OverTunFdAdapter>;
//! \brief A FD adapter for IPv4 datagrams read from and written to a TAP device
class TCPOverIPv4OverEthernetAdapter : public TCPOverIPv4Adapter {
private:
TapFD _tap; //!< Raw Ethernet connection
NetworkInterface _interface; //!< NIC abstraction
Address _next_hop; //!< IP address of the next hop
void send_pending(); //!< Sends any pending Ethernet frames
public:
//! Construct from a TapFD
explicit TCPOverIPv4OverEthernetAdapter(TapFD &&tap,
const EthernetAddress &eth_address,
const Address &ip_address,
const Address &next_hop);
//! Attempts to read and parse an Ethernet frame containing an IPv4 datagram that contains a TCP segment
std::optional<TCPSegment> read();
//! Sends a TCP segment (in an IPv4 datagram, in an Ethernet frame).
void write(TCPSegment &seg);
//! Called periodically when time elapses
void tick(const size_t ms_since_last_tick);
//! Access the underlying raw Ethernet connection
operator TapFD &() { return _tap; }
//! Access the underlying raw Ethernet connection
operator const TapFD &() const { return _tap; }
};
#endif // SPONGE_LIBSPONGE_TUNFD_ADAPTER_HH

113
tap.sh Executable file
View File

@ -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" "$@"

View File

@ -1,4 +1,4 @@
add_library (spongechecks STATIC send_equivalence_checker.cc tcp_fsm_test_harness.cc byte_stream_test_harness.cc)
add_library (spongechecks STATIC send_equivalence_checker.cc tcp_fsm_test_harness.cc byte_stream_test_harness.cc network_interface_test_harness.cc)
macro (add_test_exec exec_name)
add_executable ("${exec_name}" "${exec_name}.cc")
@ -50,3 +50,4 @@ add_test_exec (send_ack)
add_test_exec (send_window)
add_test_exec (send_close)
add_test_exec (send_extra)
add_test_exec (net_interface)

29
writeups/lab5.md Normal file
View File

@ -0,0 +1,29 @@
Lab 5 Writeup
=============
My name: [your name here]
My SUNet ID: [your sunetid here]
I collaborated with: [list sunetids here]
I would like to thank/reward these classmates for their help: [list sunetids here]
This lab took me about [n] hours to do. I [did/did not] attend the lab session.
Program Structure and Design of the NetworkInterface:
[]
Implementation Challenges:
[]
Remaining Bugs:
[]
- Optional: I had unexpected difficulty with: [describe]
- Optional: I think you could make this lab better by: [describe]
- Optional: I was surprised by: [describe]
- Optional: I'm not sure about: [describe]