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;
}
Example #2
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;
}
Example #4
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;
}