#include <sys/param.h>
#endif
+
+#if USE_TUNTAP && defined(__linux__)
+#if 1 // Hide from the style checker since these have to be out of order.
+#include <sys/socket.h> // Has to be included before if.h for some reason.
+
+#endif
+
+#include <linux/if.h>
+#include <linux/if_tun.h>
+
+#endif
+
+#include <fcntl.h>
#include <netinet/in.h>
+#include <sys/ioctl.h>
#include <unistd.h>
+#include <cstring>
#include <deque>
#include <string>
-#include "base/misc.hh"
+#include "base/logging.hh"
#include "base/pollevent.hh"
#include "base/socket.hh"
#include "base/trace.hh"
using namespace std;
-/**
- */
+class TapEvent : public PollEvent
+{
+ protected:
+ EtherTapBase *tap;
+
+ public:
+ TapEvent(EtherTapBase *_tap, int fd, int e)
+ : PollEvent(fd, e), tap(_tap) {}
+
+ void
+ process(int revent) override
+ {
+ // Ensure that our event queue is active. It may not be since we get
+ // here from the PollQueue whenever a real packet happens to arrive.
+ EventQueue::ScopedMigration migrate(tap->eventQueue());
+
+ tap->recvReal(revent);
+ }
+};
+
+EtherTapBase::EtherTapBase(const Params *p)
+ : SimObject(p), buflen(p->bufsz), dump(p->dump), event(NULL),
+ interface(NULL),
+ txEvent([this]{ retransmit(); }, "EtherTapBase retransmit")
+{
+ buffer = new uint8_t[buflen];
+ interface = new EtherTapInt(name() + ".interface", this);
+}
+
+EtherTapBase::~EtherTapBase()
+{
+ delete buffer;
+ delete event;
+ delete interface;
+}
+
+void
+EtherTapBase::serialize(CheckpointOut &cp) const
+{
+ SERIALIZE_SCALAR(buflen);
+ uint8_t *buffer = (uint8_t *)this->buffer;
+ SERIALIZE_ARRAY(buffer, buflen);
+
+ bool tapevent_present = false;
+ if (event) {
+ tapevent_present = true;
+ SERIALIZE_SCALAR(tapevent_present);
+ event->serialize(cp);
+ } else {
+ SERIALIZE_SCALAR(tapevent_present);
+ }
+}
+
+void
+EtherTapBase::unserialize(CheckpointIn &cp)
+{
+ UNSERIALIZE_SCALAR(buflen);
+ uint8_t *buffer = (uint8_t *)this->buffer;
+ UNSERIALIZE_ARRAY(buffer, buflen);
+
+ bool tapevent_present;
+ UNSERIALIZE_SCALAR(tapevent_present);
+ if (tapevent_present) {
+ event = new TapEvent(this, 0, 0);
+ event->unserialize(cp);
+ if (event->queued())
+ pollQueue.schedule(event);
+ }
+}
+
+
+void
+EtherTapBase::pollFd(int fd)
+{
+ assert(!event);
+ event = new TapEvent(this, fd, POLLIN|POLLERR);
+ pollQueue.schedule(event);
+}
+
+void
+EtherTapBase::stopPolling()
+{
+ assert(event);
+ delete event;
+ event = NULL;
+}
+
+
+Port &
+EtherTapBase::getPort(const std::string &if_name, PortID idx)
+{
+ if (if_name == "tap")
+ return *interface;
+ return SimObject::getPort(if_name, idx);
+}
+
+bool
+EtherTapBase::recvSimulated(EthPacketPtr packet)
+{
+ if (dump)
+ dump->dump(packet);
+
+ DPRINTF(Ethernet, "EtherTap sim->real len=%d\n", packet->length);
+ DDUMP(EthernetData, packet->data, packet->length);
+
+ bool success = sendReal(packet->data, packet->length);
+
+ interface->recvDone();
+
+ return success;
+}
+
+void
+EtherTapBase::sendSimulated(void *data, size_t len)
+{
+ EthPacketPtr packet;
+ packet = make_shared<EthPacketData>(len);
+ packet->length = len;
+ packet->simLength = len;
+ memcpy(packet->data, data, len);
+
+ DPRINTF(Ethernet, "EtherTap real->sim len=%d\n", packet->length);
+ DDUMP(EthernetData, packet->data, packet->length);
+ if (!packetBuffer.empty() || !interface->sendPacket(packet)) {
+ DPRINTF(Ethernet, "bus busy...buffer for retransmission\n");
+ packetBuffer.push(packet);
+ if (!txEvent.scheduled())
+ schedule(txEvent, curTick() + retryTime);
+ } else if (dump) {
+ dump->dump(packet);
+ }
+}
+
+void
+EtherTapBase::retransmit()
+{
+ if (packetBuffer.empty())
+ return;
+
+ EthPacketPtr packet = packetBuffer.front();
+ if (interface->sendPacket(packet)) {
+ if (dump)
+ dump->dump(packet);
+ DPRINTF(Ethernet, "EtherTap retransmit\n");
+ packetBuffer.front() = NULL;
+ packetBuffer.pop();
+ }
+
+ if (!packetBuffer.empty() && !txEvent.scheduled())
+ schedule(txEvent, curTick() + retryTime);
+}
+
+
class TapListener
{
protected:
- /**
- */
class Event : public PollEvent
{
protected:
TapListener *listener;
public:
- Event(TapListener *l, int fd, int e)
- : PollEvent(fd, e), listener(l) {}
+ Event(TapListener *l, int fd, int e) : PollEvent(fd, e), listener(l) {}
- virtual void process(int revent) { listener->accept(); }
+ void process(int revent) override { listener->accept(); }
};
friend class Event;
Event *event;
+ void accept();
+
protected:
ListenSocket listener;
- EtherTap *tap;
+ EtherTapStub *tap;
int port;
public:
- TapListener(EtherTap *t, int p)
- : event(NULL), tap(t), port(p) {}
- ~TapListener() { if (event) delete event; }
+ TapListener(EtherTapStub *t, int p) : event(NULL), tap(t), port(p) {}
+ ~TapListener() { delete event; }
- void accept();
void listen();
};
tap->attach(sfd);
}
-/**
- */
-class TapEvent : public PollEvent
-{
- protected:
- EtherTap *tap;
-
- public:
- TapEvent(EtherTap *_tap, int fd, int e)
- : PollEvent(fd, e), tap(_tap) {}
- virtual void process(int revent) { tap->process(revent); }
-};
-EtherTap::EtherTap(const Params *p)
- : EtherObject(p), event(NULL), socket(-1), buflen(p->bufsz), dump(p->dump),
- interface(NULL), txEvent(this)
+EtherTapStub::EtherTapStub(const Params *p) : EtherTapBase(p), socket(-1)
{
if (ListenSocket::allDisabled())
- fatal("All listeners are disabled! EtherTap can't work!");
+ fatal("All listeners are disabled! EtherTapStub can't work!");
- buffer = new char[buflen];
listener = new TapListener(this, p->port);
listener->listen();
- interface = new EtherTapInt(name() + ".interface", this);
}
-EtherTap::~EtherTap()
+EtherTapStub::~EtherTapStub()
{
- if (event)
- delete event;
- if (buffer)
- delete [] buffer;
-
- delete interface;
delete listener;
}
void
-EtherTap::attach(int fd)
+EtherTapStub::serialize(CheckpointOut &cp) const
{
- if (socket != -1)
- close(fd);
+ EtherTapBase::serialize(cp);
- buffer_offset = 0;
- data_len = 0;
- socket = fd;
- DPRINTF(Ethernet, "EtherTap attached\n");
- event = new TapEvent(this, socket, POLLIN|POLLERR);
- pollQueue.schedule(event);
+ SERIALIZE_SCALAR(socket);
+ SERIALIZE_SCALAR(buffer_used);
+ SERIALIZE_SCALAR(frame_len);
}
void
-EtherTap::detach()
+EtherTapStub::unserialize(CheckpointIn &cp)
{
- DPRINTF(Ethernet, "EtherTap detached\n");
- delete event;
- event = 0;
- close(socket);
- socket = -1;
-}
+ EtherTapBase::unserialize(cp);
-bool
-EtherTap::recvPacket(EthPacketPtr packet)
-{
- if (dump)
- dump->dump(packet);
+ UNSERIALIZE_SCALAR(socket);
+ UNSERIALIZE_SCALAR(buffer_used);
+ UNSERIALIZE_SCALAR(frame_len);
+}
- DPRINTF(Ethernet, "EtherTap output len=%d\n", packet->length);
- DDUMP(EthernetData, packet->data, packet->length);
- uint32_t len = htonl(packet->length);
- ssize_t ret = write(socket, &len, sizeof(len));
- if (ret != sizeof(len))
- return false;
- ret = write(socket, packet->data, packet->length);
- if (ret != packet->length)
- return false;
- interface->recvDone();
+void
+EtherTapStub::attach(int fd)
+{
+ if (socket != -1)
+ close(fd);
- return true;
+ buffer_used = 0;
+ frame_len = 0;
+ socket = fd;
+ DPRINTF(Ethernet, "EtherTapStub attached\n");
+ pollFd(socket);
}
void
-EtherTap::sendDone()
-{}
+EtherTapStub::detach()
+{
+ DPRINTF(Ethernet, "EtherTapStub detached\n");
+ stopPolling();
+ close(socket);
+ socket = -1;
+}
void
-EtherTap::process(int revent)
+EtherTapStub::recvReal(int revent)
{
if (revent & POLLERR) {
detach();
return;
}
- char *data = buffer + sizeof(uint32_t);
if (!(revent & POLLIN))
return;
- if (buffer_offset < data_len + sizeof(uint32_t)) {
- int len = read(socket, buffer + buffer_offset, buflen - buffer_offset);
- if (len == 0) {
- detach();
- return;
- }
+ // Read in as much of the new data as we can.
+ int len = read(socket, buffer + buffer_used, buflen - buffer_used);
+ if (len == 0) {
+ detach();
+ return;
+ }
+ buffer_used += len;
- buffer_offset += len;
+ // If there's not enough data for the frame length, wait for more.
+ if (buffer_used < sizeof(uint32_t))
+ return;
- if (data_len == 0)
- data_len = ntohl(*(uint32_t *)buffer);
+ if (frame_len == 0)
+ frame_len = ntohl(*(uint32_t *)buffer);
- DPRINTF(Ethernet, "Received data from peer: len=%d buffer_offset=%d "
- "data_len=%d\n", len, buffer_offset, data_len);
- }
+ DPRINTF(Ethernet, "Received data from peer: len=%d buffer_used=%d "
+ "frame_len=%d\n", len, buffer_used, frame_len);
- while (data_len != 0 && buffer_offset >= data_len + sizeof(uint32_t)) {
- EthPacketPtr packet;
- packet = make_shared<EthPacketData>(data_len);
- packet->length = data_len;
- memcpy(packet->data, data, data_len);
-
- buffer_offset -= data_len + sizeof(uint32_t);
- assert(buffer_offset >= 0);
- if (buffer_offset > 0) {
- memmove(buffer, data + data_len, buffer_offset);
- data_len = ntohl(*(uint32_t *)buffer);
- } else
- data_len = 0;
-
- DPRINTF(Ethernet, "EtherTap input len=%d\n", packet->length);
- DDUMP(EthernetData, packet->data, packet->length);
- if (!interface->sendPacket(packet)) {
- DPRINTF(Ethernet, "bus busy...buffer for retransmission\n");
- packetBuffer.push(packet);
- if (!txEvent.scheduled())
- schedule(txEvent, curTick() + retryTime);
- } else if (dump) {
- dump->dump(packet);
- }
- }
-}
+ uint8_t *frame_start = &buffer[sizeof(uint32_t)];
+ while (frame_len != 0 && buffer_used >= frame_len + sizeof(uint32_t)) {
+ sendSimulated(frame_start, frame_len);
-void
-EtherTap::retransmit()
-{
- if (packetBuffer.empty())
- return;
+ // Bookkeeping.
+ buffer_used -= frame_len + sizeof(uint32_t);
+ if (buffer_used > 0) {
+ // If there's still any data left, move it into position.
+ memmove(buffer, frame_start + frame_len, buffer_used);
+ }
+ frame_len = 0;
- EthPacketPtr packet = packetBuffer.front();
- if (interface->sendPacket(packet)) {
- if (dump)
- dump->dump(packet);
- DPRINTF(Ethernet, "EtherTap retransmit\n");
- packetBuffer.front() = NULL;
- packetBuffer.pop();
+ if (buffer_used >= sizeof(uint32_t))
+ frame_len = ntohl(*(uint32_t *)buffer);
}
-
- if (!packetBuffer.empty() && !txEvent.scheduled())
- schedule(txEvent, curTick() + retryTime);
}
-EtherInt*
-EtherTap::getEthPort(const std::string &if_name, int idx)
+bool
+EtherTapStub::sendReal(const void *data, size_t len)
{
- if (if_name == "tap") {
- if (interface->getPeer())
- panic("Interface already connected to\n");
- return interface;
- }
- return NULL;
+ uint32_t frame_len = htonl(len);
+ ssize_t ret = write(socket, &frame_len, sizeof(frame_len));
+ if (ret != sizeof(frame_len))
+ return false;
+ return write(socket, data, len) == len;
}
-//=====================================================================
+#if USE_TUNTAP
-void
-EtherTap::serialize(CheckpointOut &cp) const
+EtherTap::EtherTap(const Params *p) : EtherTapBase(p)
{
- SERIALIZE_SCALAR(socket);
- SERIALIZE_SCALAR(buflen);
- uint8_t *buffer = (uint8_t *)this->buffer;
- SERIALIZE_ARRAY(buffer, buflen);
- SERIALIZE_SCALAR(buffer_offset);
- SERIALIZE_SCALAR(data_len);
+ int fd = open(p->tun_clone_device.c_str(), O_RDWR | O_NONBLOCK);
+ if (fd < 0)
+ panic("Couldn't open %s.\n", p->tun_clone_device);
+
+ struct ifreq ifr;
+ memset(&ifr, 0, sizeof(ifr));
+ ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
+ strncpy(ifr.ifr_name, p->tap_device_name.c_str(), IFNAMSIZ - 1);
+
+ if (ioctl(fd, TUNSETIFF, (void *)&ifr) < 0)
+ panic("Failed to access tap device %s.\n", ifr.ifr_name);
+ // fd now refers to the tap device.
+ tap = fd;
+ pollFd(tap);
+}
- bool tapevent_present = false;
- if (event) {
- tapevent_present = true;
- SERIALIZE_SCALAR(tapevent_present);
- event->serialize(cp);
- }
- else {
- SERIALIZE_SCALAR(tapevent_present);
- }
+EtherTap::~EtherTap()
+{
+ stopPolling();
+ close(tap);
+ tap = -1;
}
void
-EtherTap::unserialize(CheckpointIn &cp)
+EtherTap::recvReal(int revent)
{
- UNSERIALIZE_SCALAR(socket);
- UNSERIALIZE_SCALAR(buflen);
- uint8_t *buffer = (uint8_t *)this->buffer;
- UNSERIALIZE_ARRAY(buffer, buflen);
- UNSERIALIZE_SCALAR(buffer_offset);
- UNSERIALIZE_SCALAR(data_len);
+ if (revent & POLLERR)
+ panic("Error polling for tap data.\n");
- bool tapevent_present;
- UNSERIALIZE_SCALAR(tapevent_present);
- if (tapevent_present) {
- event = new TapEvent(this, socket, POLLIN|POLLERR);
-
- event->unserialize(cp);
+ if (!(revent & POLLIN))
+ return;
- if (event->queued()) {
- pollQueue.schedule(event);
+ ssize_t ret;
+ while ((ret = read(tap, buffer, buflen))) {
+ if (ret < 0) {
+ if (errno == EAGAIN)
+ break;
+ panic("Failed to read from tap device.\n");
}
+
+ sendSimulated(buffer, ret);
}
}
-//=====================================================================
+bool
+EtherTap::sendReal(const void *data, size_t len)
+{
+ int n;
+ pollfd pfd[1];
+ pfd->fd = tap;
+ pfd->events = POLLOUT;
+
+ // `tap` is a nonblock fd. Here we try to write until success, and use
+ // poll to make a blocking wait.
+ while ((n = write(tap, data, len)) != len) {
+ if (errno != EAGAIN)
+ panic("Failed to write data to tap device.\n");
+ pfd->revents = 0;
+ int ret = poll(pfd, 1, -1);
+ // timeout is set to inf, we shouldn't get 0 in any case.
+ assert(ret != 0);
+ if (ret == -1 || (ret == 1 && (pfd->revents & POLLERR))) {
+ panic("Failed when polling to write data to tap device.\n");
+ }
+ }
+ return true;
+}
EtherTap *
EtherTapParams::create()
{
return new EtherTap(this);
}
+
+#endif
+
+EtherTapStub *
+EtherTapStubParams::create()
+{
+ return new EtherTapStub(this);
+}