From 423bcec36b93414fdb0a80b202e1a229582ba80f Mon Sep 17 00:00:00 2001 From: Keith Winstein Date: Tue, 16 Nov 2021 17:07:02 -0800 Subject: [PATCH] CS144 Lab checkpoint 7 starter code --- apps/CMakeLists.txt | 2 + apps/bouncer.cc | 70 ++++++++++ apps/lab7.cc | 313 ++++++++++++++++++++++++++++++++++++++++++++ writeups/lab7.md | 34 +++++ 4 files changed, 419 insertions(+) create mode 100644 apps/bouncer.cc create mode 100644 apps/lab7.cc create mode 100644 writeups/lab7.md diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 43c3cac..c206277 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -9,3 +9,5 @@ add_sponge_exec (tcp_ip_ethernet stream_copy) add_sponge_exec (webget) add_sponge_exec (tcp_benchmark) add_sponge_exec (network_simulator) +add_sponge_exec (lab7 stream_copy) +add_sponge_exec (bouncer) diff --git a/apps/bouncer.cc b/apps/bouncer.cc new file mode 100644 index 0000000..f8975c1 --- /dev/null +++ b/apps/bouncer.cc @@ -0,0 +1,70 @@ +#include "eventloop.hh" +#include "socket.hh" + +#include +#include +#include + +using namespace std; + +void program_body() { + EventLoop loop; + vector sockets; + vector> peers; + sockets.reserve(66000); + peers.reserve(66000); + + for (uint16_t lower_port = 1024; lower_port <= 64000; lower_port += 2) { + sockets.emplace_back(); + sockets.emplace_back(); + peers.emplace_back(); + peers.emplace_back(); + UDPSocket &x = sockets.at(sockets.size() - 2); + UDPSocket &y = sockets.at(sockets.size() - 1); + optional
&x_peer = peers.at(peers.size() - 2); + optional
&y_peer = peers.at(peers.size() - 1); + + x.bind(Address{"0", lower_port}); + y.bind(Address{"0", uint16_t(lower_port + 1)}); + + loop.add_rule(x, Direction::In, [&] { + auto rec = x.recv(); + if (not x_peer.has_value() or x_peer.value() != rec.source_address) { + x_peer = rec.source_address; + cerr << "Learned new address for X ( " << x.local_address().to_string() << " at " + << x_peer.value().to_string() << "\n"; + } + if (y_peer.has_value() and not rec.payload.empty()) { + y.sendto(y_peer.value(), rec.payload); + } + }); + + loop.add_rule(y, Direction::In, [&] { + auto rec = y.recv(); + if (not y_peer.has_value() or y_peer.value() != rec.source_address) { + y_peer = rec.source_address; + cerr << "Learned new address for Y ( " << y.local_address().to_string() << " at " + << y_peer.value().to_string() << "\n"; + } + if (x_peer.has_value() and not rec.payload.empty()) { + x.sendto(x_peer.value(), rec.payload); + } + }); + } + + cerr << "Starting event loop...\n"; + + while (EventLoop::Result::Exit != loop.wait_next_event(-1)) { + } +} + +int main() { + try { + program_body(); + } catch (const exception &e) { + cerr << e.what() << "\n"; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/apps/lab7.cc b/apps/lab7.cc new file mode 100644 index 0000000..bd48290 --- /dev/null +++ b/apps/lab7.cc @@ -0,0 +1,313 @@ +#include "address.hh" +#include "arp_message.hh" +#include "bidirectional_stream_copy.hh" +#include "router.hh" +#include "tcp_over_ip.hh" +#include "tcp_sponge_socket.cc" +#include "util.hh" + +#include +#include +#include + +using namespace std; + +auto rd = get_random_generator(); + +EthernetAddress random_host_ethernet_address() { + EthernetAddress addr; + for (auto &byte : addr) { + byte = rd(); // 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; +} + +EthernetAddress random_router_ethernet_address() { + EthernetAddress addr; + for (auto &byte : addr) { + byte = rd(); // use a random local Ethernet address + } + addr.at(0) = 0x02; // "10" in last two binary digits marks a private Ethernet address + addr.at(1) = 0; + addr.at(2) = 0; + + return addr; +} + +string summary(const EthernetFrame &frame) { + string ret; + ret += frame.header().to_string(); + switch (frame.header().type) { + case EthernetHeader::TYPE_IPv4: { + InternetDatagram dgram; + if (dgram.parse(frame.payload().concatenate()) == ParseResult::NoError) { + ret += " " + dgram.header().summary(); + if (dgram.header().proto == IPv4Header::PROTO_TCP) { + TCPSegment tcp_seg; + if (tcp_seg.parse(dgram.payload().concatenate(), dgram.header().pseudo_cksum()) == + ParseResult::NoError) { + ret += " " + tcp_seg.header().summary(); + } + } + } else { + ret += " (bad IPv4)"; + } + } break; + case EthernetHeader::TYPE_ARP: { + ARPMessage arp; + if (arp.parse(frame.payload()) == ParseResult::NoError) { + ret += " " + arp.to_string(); + } else { + ret += " (bad ARP)"; + } + } + default: + break; + } + return ret; +} + +class NetworkInterfaceAdapter : public TCPOverIPv4Adapter { + private: + NetworkInterface _interface; + Address _next_hop; + pair _data_socket_pair = socket_pair_helper(SOCK_DGRAM); + + void send_pending() { + while (not _interface.frames_out().empty()) { + _data_socket_pair.first.write(_interface.frames_out().front().serialize()); + _interface.frames_out().pop(); + } + } + + public: + NetworkInterfaceAdapter(const Address &ip_address, const Address &next_hop) + : _interface(random_host_ethernet_address(), ip_address), _next_hop(next_hop) {} + + optional read() { + EthernetFrame frame; + if (frame.parse(_data_socket_pair.first.read()) != ParseResult::NoError) { + return {}; + } + + // Give the frame to the NetworkInterface. Get back an Internet datagram if frame was carrying one. + optional 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 {}; + } + void write(TCPSegment &seg) { + _interface.send_datagram(wrap_tcp_in_ip(seg), _next_hop); + send_pending(); + } + void tick(const size_t ms_since_last_tick) { + _interface.tick(ms_since_last_tick); + send_pending(); + } + NetworkInterface &interface() { return _interface; } + queue frames_out() { return _interface.frames_out(); } + + operator FileDescriptor &() { return _data_socket_pair.first; } + FileDescriptor &frame_fd() { return _data_socket_pair.second; } +}; + +class TCPSocketLab7 : public TCPSpongeSocket { + Address _local_address; + + public: + TCPSocketLab7(const Address &ip_address, const Address &next_hop) + : TCPSpongeSocket(NetworkInterfaceAdapter(ip_address, next_hop)) + , _local_address(ip_address) {} + + void connect(const Address &address) { + FdAdapterConfig multiplexer_config; + + _local_address = Address{_local_address.ip(), uint16_t(random_device()())}; + cerr << "DEBUG: Connecting from " << _local_address.to_string() << "...\n"; + multiplexer_config.source = _local_address; + multiplexer_config.destination = address; + + TCPSpongeSocket::connect({}, multiplexer_config); + } + + void bind(const Address &address) { + if (address.ip() != _local_address.ip()) { + throw runtime_error("Cannot bind to " + address.to_string()); + } + _local_address = Address{_local_address.ip(), address.port()}; + } + + void listen_and_accept() { + FdAdapterConfig multiplexer_config; + multiplexer_config.source = _local_address; + TCPSpongeSocket::listen_and_accept({}, multiplexer_config); + } + + NetworkInterfaceAdapter &adapter() { return _datagram_adapter; } +}; + +void program_body(bool is_client, const string &bounce_host, const string &bounce_port, const bool debug) { + UDPSocket internet_socket; + Address bounce_address{bounce_host, bounce_port}; + + /* let bouncer know where we are */ + internet_socket.sendto(bounce_address, ""); + internet_socket.sendto(bounce_address, ""); + internet_socket.sendto(bounce_address, ""); + + /* set up the router */ + Router router; + + unsigned int host_side, internet_side; + + if (is_client) { + host_side = router.add_interface({random_router_ethernet_address(), {"192.168.0.1"}}); + internet_side = router.add_interface({random_router_ethernet_address(), {"10.0.0.192"}}); + router.add_route(Address{"192.168.0.0"}.ipv4_numeric(), 16, {}, host_side); + router.add_route(Address{"10.0.0.0"}.ipv4_numeric(), 8, {}, internet_side); + router.add_route(Address{"172.16.0.0"}.ipv4_numeric(), 12, Address{"10.0.0.172"}, internet_side); + } else { + host_side = router.add_interface({random_router_ethernet_address(), {"172.16.0.1"}}); + internet_side = router.add_interface({random_router_ethernet_address(), {"10.0.0.172"}}); + router.add_route(Address{"172.16.0.0"}.ipv4_numeric(), 12, {}, host_side); + router.add_route(Address{"10.0.0.0"}.ipv4_numeric(), 8, {}, internet_side); + router.add_route(Address{"192.168.0.0"}.ipv4_numeric(), 16, Address{"10.0.0.192"}, internet_side); + } + + /* set up the client */ + TCPSocketLab7 sock = + is_client ? TCPSocketLab7{{"192.168.0.50"}, {"192.168.0.1"}} : TCPSocketLab7{{"172.16.0.100"}, {"172.16.0.1"}}; + + atomic exit_flag{}; + + /* set up the network */ + thread network_thread([&]() { + try { + EventLoop event_loop; + // Frames from host to router + event_loop.add_rule(sock.adapter().frame_fd(), Direction::In, [&] { + EthernetFrame frame; + if (frame.parse(sock.adapter().frame_fd().read()) != ParseResult::NoError) { + return; + } + if (debug) { + cerr << " Host->router: " << summary(frame) << "\n"; + } + router.interface(host_side).recv_frame(frame); + router.route(); + }); + + // Frames from router to host + event_loop.add_rule(sock.adapter().frame_fd(), + Direction::Out, + [&] { + auto &f = router.interface(host_side).frames_out(); + if (debug) { + cerr << " Router->host: " << summary(f.front()) << "\n"; + } + sock.adapter().frame_fd().write(f.front().serialize()); + f.pop(); + }, + [&] { return not router.interface(host_side).frames_out().empty(); }); + + // Frames from router to Internet + event_loop.add_rule(internet_socket, + Direction::Out, + [&] { + auto &f = router.interface(internet_side).frames_out(); + if (debug) { + cerr << " Router->Internet: " << summary(f.front()) << "\n"; + } + internet_socket.sendto(bounce_address, f.front().serialize()); + f.pop(); + }, + [&] { return not router.interface(internet_side).frames_out().empty(); }); + + // Frames from Internet to router + event_loop.add_rule(internet_socket, Direction::In, [&] { + EthernetFrame frame; + if (frame.parse(internet_socket.read()) != ParseResult::NoError) { + return; + } + if (debug) { + cerr << " Internet->router: " << summary(frame) << "\n"; + } + router.interface(internet_side).recv_frame(frame); + router.route(); + }); + + while (true) { + if (EventLoop::Result::Exit == event_loop.wait_next_event(50)) { + cerr << "Exiting...\n"; + return; + } + router.interface(host_side).tick(50); + router.interface(internet_side).tick(50); + if (exit_flag) { + return; + } + } + } catch (const exception &e) { + cerr << "Thread ending from exception: " << e.what() << "\n"; + } + }); + + try { + if (is_client) { + sock.connect({"172.16.0.100", 1234}); + } else { + sock.bind({"172.16.0.100", 1234}); + sock.listen_and_accept(); + } + + bidirectional_stream_copy(sock); + sock.wait_until_closed(); + } catch (const exception &e) { + cerr << "Exception: " << e.what() << "\n"; + } + + cerr << "Exiting... "; + exit_flag = true; + network_thread.join(); + cerr << "done.\n"; +} + +void print_usage(const string &argv0) { + cerr << "Usage: " << argv0 << " client HOST PORT [debug]\n"; + cerr << "or " << argv0 << " server HOST PORT [debug]\n"; +} + +int main(int argc, char *argv[]) { + try { + if (argc <= 0) { + abort(); // For sticklers: don't try to access argv[0] if argc <= 0. + } + + if (argc != 4 and argc != 5) { + print_usage(argv[0]); + return EXIT_FAILURE; + } + + if (argv[1] != "client"s and argv[1] != "server"s) { + print_usage(argv[0]); + return EXIT_FAILURE; + } + + program_body(argv[1] == "client"s, argv[2], argv[3], argc == 5); + } catch (const exception &e) { + cerr << e.what() << "\n"; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/writeups/lab7.md b/writeups/lab7.md new file mode 100644 index 0000000..c215f53 --- /dev/null +++ b/writeups/lab7.md @@ -0,0 +1,34 @@ +Lab 7 Writeup +============= + +My name: [your name here] + +My SUNet ID: [your sunetid here] + +My lab partner's SUNet ID: [your sunetid here] + +I also worked with or collaborated with: [their 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. + +Solo portion: +[] + +Group portion: +[] + +Creative portion (optional): +[] + +Other remarks: +[] + +- 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]