int main(int argc, char* argv[]) { // Parse command line arguments. if (argc == 2) { if (std::string(argv[1]) == "once") once = true; } // TODO: Use sigaction. signal(SIGINT, on_signal); signal(SIGKILL, on_signal); signal(SIGHUP, on_signal); // Build a server socket that will accept network connections. Ipv4_socket_address addr(Ipv4_address::any(), 5000); Ipv4_stream_socket server(addr); set_option(server.fd(), reuse_address(true)); // TODO: Handle exceptions. // Configure the dataplane. Ports must be added before // applications are loaded. for (int i = 0; i < 2; i++) { dp.add_port(&ports[i]); port_thread[i].assign(i, port_work); } dp.add_virtual_ports(); dp.load_application("apps/wire.app"); dp.up(); ss.add_read(server.fd()); // FIXME: Factor the accept/ingress code into something a little more // reusable. // Accept connections from the server socket. auto accept = [&](Ipv4_stream_socket& server) { std::cout << "[flowpath] accept\n"; // Accept the connection. Ipv4_socket_address addr; Ipv4_stream_socket client = server.accept(addr); if (!client) return; // TODO: Log the error. // If we already have two endpoints, just return, which will cause // the socket to be closed. if (nports == 2) { std::cout << "[flowpath] reject connection " << addr.port() << '\n'; return; } std::cout << "[flowpath] accept connection " << addr.port() << '\n'; // Update the poll set. ss.add_read(client.fd()); // Bind the socket to a port. // TODO: Emit a port status change to the application. Does // that happen implicitly, or do we have to cause the dataplane // to do it. Port_tcp* port = nullptr; if (nports == 0) port = &ports[0]; if (nports == 1) port = &ports[1]; port->attach(std::move(client)); port_thread[nports].run(); ++nports; // Once we have two connections, start the timer. if (nports == 2) start = now(); // Notify the application of the port change. Application* app = dp.get_application(); app->port_changed(*port); }; // Main loop. running = true; while (running) { // Wait for 100 milliseconds. Note that this can fail with // EINTR, which really isn't an error. // // NOTE: It seems the common practice is to re-poll when EINTR // occurs since like you said, it's not really an error. Most // impls just stick it in a do-while(errno != EINTR); int n = select(ss, 10ms); if (n <= 0) continue; // Is a connection available? if (ss.can_read(server.fd())) accept(server); } // Take the dataplane down. port_thread[0].halt(); port_thread[1].halt(); dp.down(); dp.unload_application(); // Write out stats. double s = duration.count(); double Mb = double(nbytes * 8) / (1 << 20); double Mbps = Mb / s; long Pps = npackets / s; // FIXME: Make this pretty. // std::cout.imbue(std::locale("")); std::cout.precision(6); std::cout << "processed " << npackets << " packets in " << s << " seconds (" << Pps << " Pps)\n"; std::cout << "processed " << nbytes << " bytes in " << s << " seconds (" << Mbps << " Mbps)\n"; return 0; }
int main() { // TODO: Use sigaction. signal(SIGINT, on_signal); signal(SIGKILL, on_signal); signal(SIGHUP, on_signal); // Build a server socket that will accept network connections. Ipv4_socket_address addr(Ipv4_address::any(), 5000); Ipv4_stream_socket server(addr); // TODO: Handle exceptions. // Pre-create all standard ports. Port_eth_tcp port1(0); Port_eth_tcp port2(1); // Configure the dataplane. Ports must be added before // applications are loaded. fp::Dataplane dp = "dp1"; dp.add_port(&port1); dp.add_port(&port2); dp.add_virtual_ports(); dp.load_application("apps/wire.app"); dp.up(); // Set up the initial polling state. Poll_file fds[] { { server.fd(), POLLIN }, { -1, 0 }, { -1, 0 } }; int nports = 0; // Current number of ports // FIXME: Factor the accept/ingress code into something // a little more reusable. // Accept connections from the server socket. auto accept = [&](Ipv4_stream_socket& server) { // Accept the connection. Ipv4_socket_address addr; Ipv4_stream_socket client = server.accept(addr); if (!client) return; // TODO: Log the error. // If we already have two endpoints, just return, which // will cause the socket to be closed. if (nports == 2) { std::cout << "[flowpath] reject connection " << addr.port() << '\n'; return; } std::cout << "[flowpath] accept connection " << addr.port() << '\n'; // Update the poll set. fds[nports + 1] = { client.fd(), POLLIN }; // Bind the socket to a port. // TODO: Emit a port status change to the application. Does // that happen implicitly, or do we have to cause the dataplane // to do it. Port_eth_tcp* port = nullptr; if (nports == 0) port = &port1; if (nports == 1) port = &port2; port->attach(std::move(client)); ++nports; // Notify the application of the port change. Application* app = dp.get_application(); app->port_changed(*port); }; // Handle input from the client socket. // // TODO: This defines the basic ingress pipeline. How // do we refactor this to make it reasonably composable. auto ingress = [&](Port_eth_tcp& port) { // Ingress the packet. Byte buf[2048]; Context cxt(&dp, buf); bool ok = port.recv(cxt); // Handle error or closure. if (!ok) { // Detache the socket. Ipv4_stream_socket client = port.detach(); // Notify the application of the port change. Application* app = dp.get_application(); app->port_changed(port); // Update the poll set. if (nports == 2) { if (client.fd() == fds[1].fd) fds[1] = fds[2]; fds[2] = { -1, 0 }; } else { fds[1] = { -1, 0 }; } --nports; return; } // Otherwise, process the application. // // TODO: This really just runs one step of the pipeline. This needs // to be a loop that continues processing until there are no further // table redirections. Application* app = dp.get_application(); app->process(cxt); // Assuming there's an output send to it. if (Port* out = cxt.output_port()) out->send(cxt); }; // Select a port to handle input. auto input = [&](int fd) { if (fd == port1.fd()) ingress(port1); else ingress(port2); }; // Main lookp. running = true; while (running) { // Poll for 100 milliseconds. Note that this can fail with // EINTR, which really isn't an error. // // NOTE: It seems the common practice is to re-poll when EINTR // occurs since like you said, it's not really an error. Most // impls just stick it in a do-while(errno != EINTR); poll(fds, 1 + nports, 1000); if (fds[0].can_read()) accept(server); if (fds[1].can_read()) input(fds[1].fd); if (fds[2].can_read()) input(fds[2].fd); } // Take the dataplane down. dp.down(); dp.unload_application(); return 0; }
int main() { // TODO: Use sigaction. signal(SIGINT, on_signal); signal(SIGKILL, on_signal); signal(SIGHUP, on_signal); // Build a server socket that will accept network connections. Ipv4_socket_address addr(Ipv4_address::any(), 5000); Ipv4_stream_socket server(addr); set_option(server.fd(), reuse_address(true)); set_option(server.fd(), nonblocking(true)); // TODO: Handle exceptions. // Pre-create all standard ports. Port_eth_tcp port1(1); Port_eth_tcp port2(2); // Reporting stastics init. Port::Statistics p1_stats = {0,0,0,0}; Port::Statistics p2_stats = {0,0,0,0}; // Egress queue. Queue<Context> egress_queue; // Configure the dataplane. Ports must be added before // applications are loaded. fp::Dataplane dp = "dp1"; dp.add_port(&port1); dp.add_port(&port2); dp.add_virtual_ports(); dp.load_application("apps/wire.app"); dp.up(); // Set up the initial polling state. Epoll_set eps(3); // Current number of ports. int nports = 0; // Add the server socket to the select set. eps.add(server.fd()); // FIXME: Factor the accept/ingress code into something // a little more reusable. // Accept connections from the server socket. auto accept = [&](Ipv4_stream_socket& server) { // Accept the connection. Ipv4_socket_address addr; Ipv4_stream_socket client = server.accept(addr); if (!client) return; // TODO: Log the error. // If we already have two endpoints, just return, which // will cause the socket to be closed. if (nports == 2) { std::cout << "[flowpath] reject connection " << addr.port() << '\n'; return; } std::cout << "[flowpath] accept connection " << addr.port() << '\n'; // Update the poll set. eps.add(client.fd()); // Set non-blocking. set_option(client.fd(), nonblocking(true)); // Bind the socket to a port. // TODO: Emit a port status change to the application. Does // that happen implicitly, or do we have to cause the dataplane // to do it. Port_tcp* port = nullptr; if (nports == 0) port = &port1; if (nports == 1) port = &port2; port->attach(std::move(client)); ++nports; // Notify the application of the port change. Application* app = dp.get_application(); app->port_changed(*port); }; // Handle input from the client socket. // // TODO: This defines the basic ingress pipeline. How // do we refactor this to make it reasonably composable. auto ingress = [&](Port_eth_tcp& port) { //std::cout << "[wire] ingress on: " << port.id() << '\n'; // Ingress the packet. Byte buf[2048]; Context cxt(buf, &dp); bool ok = port.recv(cxt); // Handle error or closure. if (!ok) { // Detach the socket. Ipv4_stream_socket client = port.detach(); // Notify the application of the port change. Application* app = dp.get_application(); app->port_changed(port); // Update the poll set. eps.del(client.fd()); --nports; return; } // Otherwise, process the application. // // TODO: This really just runs one step of the pipeline. This needs // to be a loop that continues processing until there are no further // table redirections. Application* app = dp.get_application(); app->process(cxt); // Assuming there's an output send to it. if (cxt.output_port()) egress_queue.enqueue(cxt); }; // Select a port to handle input. auto input = [&](int fd) { if (fd == port1.fd()) ingress(port1); else ingress(port2); }; // Apply egress processing on a port. auto egress = [&](Port_eth_tcp& port) { while (egress_queue.size()) { Context cxt = egress_queue.dequeue(); port.send(cxt); } }; // Select a port to handle output. auto output = [&](int fd) { if (fd == port1.fd()) egress(port1); else egress(port2); }; // Report statistics. auto report = [&]() { auto p1_curr = port1.stats(); auto p2_curr = port2.stats(); // Clears the screen. system("clear"); std::cout << "Receive Rate (Pkt/s): " << (p2_curr.packets_rx - p2_stats.packets_rx) << '\n'; std::cout << "Receive Rate (Gb/s): " << ((p2_curr.bytes_rx - p2_stats.bytes_rx) * 8.0 / (1 << 30)) << '\n'; std::cout << "Transmit Rate (Pkt/s): " << (p1_curr.packets_tx - p1_stats.packets_tx) << "\n"; std::cout << "Transmit Rate (Gb/s): " << ((p1_curr.bytes_tx - p1_stats.bytes_tx) * 8.0 / (1 << 30)) << "\n\n"; p1_stats = p1_curr; p2_stats = p2_curr; }; // Main lookup. running = true; // Init timer for reporting. Time last = now(); Time curr; while (running) { // Wait for 100 milliseconds. Note that this can fail with // EINTR, which really isn't an error. // // NOTE: It seems the common practice is to re-poll when EINTR // occurs since like you said, it's not really an error. Most // impls just stick it in a do-while(errno != EINTR); epoll(eps, 100); if (eps.can_read(server.fd())) accept(server); if (eps.can_read(port2.fd())) input(port2.fd()); if (eps.can_write(port1.fd())) output(port1.fd()); curr = now(); Fp_seconds dur = curr - last; double duration = dur.count(); if (duration >= 1.0) { report(); last = curr; } } eps.clear(); // Take the dataplane down. dp.down(); dp.unload_application(); return 0; }
int main(int argc, char* argv[]) { // Parse command line arguments. if (argc >= 2) { if (std::string(argv[1]) == "once") once = true; } // Check for user provided path. std::string app_path = "apps/"; if (argc == 3) { app_path = std::string(argv[2]); } // TODO: Use sigaction. signal(SIGINT, on_signal); signal(SIGKILL, on_signal); signal(SIGHUP, on_signal); // Build a server socket that will accept network connections. Ipv4_socket_address addr(Ipv4_address::any(), 5000); Ipv4_stream_socket server(addr); // TODO: Handle exceptions. // Pre-create all standard ports. Port_eth_tcp port1(0); // Configure the dataplane. Ports must be added before // applications are loaded. fp::Dataplane dp = "dp1"; dp.add_port(&port1); dp.add_virtual_ports(); dp.load_application(std::string(app_path + "endpoint.app").c_str()); dp.up(); // Set up the initial polling state. Select_set ss; ss.add_read(server.fd()); int nports = 0; // Current number of devices. // Timing information. Time start; // Records when both ends have connected Time stop; // Records when both ends have disconnected Fp_seconds duration(0.0); // Cumulative time spent forwarding // System stats. uint64_t npackets = 0; // Total number of packets processed uint64_t nbytes = 0; // Total number of bytes processed // FIXME: Factor the accept/ingress code into something a little more // reusable. // Accept connections from the server socket. auto accept = [&](Ipv4_stream_socket& server) { std::cout << "[flowpath] accept\n"; // Accept the connection. Ipv4_socket_address addr; Ipv4_stream_socket client = server.accept(addr); if (!client) return; // TODO: Log the error. // If we already have two endpoints, just return, which will cause // the socket to be closed. if (nports == 1) { std::cout << "[flowpath] reject connection " << addr.port() << '\n'; return; } std::cout << "[flowpath] accept connection " << addr.port() << '\n'; // Update the poll set. ss.add_read(client.fd()); // Bind the socket to a port. // TODO: Emit a port status change to the application. Does // that happen implicitly, or do we have to cause the dataplane // to do it. Port_tcp* port = nullptr; if (nports == 0) port = &port1; port->attach(std::move(client)); ++nports; // Once we have two connections, start the timer. if (nports == 1) start = now(); // Notify the application of the port change. Application* app = dp.get_application(); app->port_changed(*port); }; // Handle input from the client socket. // // TODO: This defines the basic ingress pipeline. How // do we refactor this to make it reasonably composable. auto ingress = [&](Port_tcp& port) { // Ingress the packet. Byte buf[2048]; Context cxt(buf, &dp); bool ok = port.recv(cxt); // Handle error or closure. if (!ok) { // Detach the socket. Ipv4_stream_socket client = port.detach(); // Notify the application of the port change. Application* app = dp.get_application(); app->port_changed(port); // Update the poll set. ss.del_read(client.fd()); --nports; // Once both ports have disconnected, accumulate statistics. if (nports == 0) { stop = now(); duration += stop - start; // If running in one-shot mode, stop now. if (once) running = false; } return; } else { ++npackets; nbytes += cxt.packet().size(); } // Otherwise, process the application. // // TODO: This really just runs one step of the pipeline. This needs // to be a loop that continues processing until there are no further // table redirections. Application* app = dp.get_application(); app->process(cxt); // Apply actions after pipeline processing. cxt.apply_actions(); // Assuming there's an output send to it. if (Port* out = cxt.output_port()) out->send(cxt); }; // Select a port to handle input. auto input = [&](int fd) { if (fd == port1.fd()) ingress(port1); }; // Main loop. running = true; while (running) { // Wait for 100 milliseconds. Note that this can fail with // EINTR, which really isn't an error. // // NOTE: It seems the common practice is to re-poll when EINTR // occurs since like you said, it's not really an error. Most // impls just stick it in a do-while(errno != EINTR); int n = select(ss, 10ms); if (n <= 0) continue; // Is a connection available? if (ss.can_read(server.fd())) accept(server); if (port1.fd() > 0 && ss.can_read(port1.fd())) input(port1.fd()); } // Take the dataplane down. dp.down(); dp.unload_application(); // Write out stats. double s = duration.count(); double Mb = double(nbytes * 8) / (1 << 20); double Mbps = Mb / s; long Pps = npackets / s; // FIXME: Make this pretty. // std::cout.imbue(std::locale("")); std::cout.precision(6); std::cout << "processed " << npackets << " packets in " << s << " seconds (" << Pps << " Pps)\n"; std::cout << "processed " << nbytes << " bytes in " << s << " seconds (" << Mbps << " Mbps)\n"; return 0; }