-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
970 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ðernet_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); } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ðernet_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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" "$@" |
Oops, something went wrong.