ethertap.cc revision 12334
12SN/A/* 21762SN/A * Copyright (c) 2003-2005 The Regents of The University of Michigan 32SN/A * All rights reserved. 42SN/A * 52SN/A * Redistribution and use in source and binary forms, with or without 62SN/A * modification, are permitted provided that the following conditions are 72SN/A * met: redistributions of source code must retain the above copyright 82SN/A * notice, this list of conditions and the following disclaimer; 92SN/A * redistributions in binary form must reproduce the above copyright 102SN/A * notice, this list of conditions and the following disclaimer in the 112SN/A * documentation and/or other materials provided with the distribution; 122SN/A * neither the name of the copyright holders nor the names of its 132SN/A * contributors may be used to endorse or promote products derived from 142SN/A * this software without specific prior written permission. 152SN/A * 162SN/A * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 172SN/A * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 182SN/A * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 192SN/A * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 202SN/A * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 212SN/A * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 222SN/A * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 232SN/A * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 242SN/A * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 252SN/A * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 262SN/A * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 272665SN/A * 282665SN/A * Authors: Nathan Binkert 292SN/A */ 302SN/A 312SN/A/* @file 322SN/A * Interface to connect a simulated ethernet device to the real world 332SN/A */ 342SN/A 3511263Sandreas.sandberg@arm.com#include "dev/net/ethertap.hh" 3611263Sandreas.sandberg@arm.com 37873SN/A#if defined(__OpenBSD__) || defined(__APPLE__) 382SN/A#include <sys/param.h> 3911263Sandreas.sandberg@arm.com 402SN/A#endif 4112056Sgabeblack@google.com 4212056Sgabeblack@google.com#if USE_TUNTAP && defined(__linux__) 4312056Sgabeblack@google.com#if 1 // Hide from the style checker since these have to be out of order. 4412056Sgabeblack@google.com#include <sys/socket.h> // Has to be included before if.h for some reason. 4512056Sgabeblack@google.com 4612056Sgabeblack@google.com#endif 4712056Sgabeblack@google.com 4812056Sgabeblack@google.com#include <linux/if.h> 4912056Sgabeblack@google.com#include <linux/if_tun.h> 5012056Sgabeblack@google.com 5112056Sgabeblack@google.com#endif 5212056Sgabeblack@google.com 5312056Sgabeblack@google.com#include <fcntl.h> 542SN/A#include <netinet/in.h> 5512056Sgabeblack@google.com#include <sys/ioctl.h> 562SN/A#include <unistd.h> 572SN/A 5812056Sgabeblack@google.com#include <cstring> 592SN/A#include <deque> 602SN/A#include <string> 612SN/A 6212334Sgabeblack@google.com#include "base/logging.hh" 63146SN/A#include "base/pollevent.hh" 64146SN/A#include "base/socket.hh" 65146SN/A#include "base/trace.hh" 668232SN/A#include "debug/Ethernet.hh" 678232SN/A#include "debug/EthernetData.hh" 6811263Sandreas.sandberg@arm.com#include "dev/net/etherdump.hh" 6911263Sandreas.sandberg@arm.com#include "dev/net/etherint.hh" 7011263Sandreas.sandberg@arm.com#include "dev/net/etherpkt.hh" 712SN/A 722SN/Ausing namespace std; 732SN/A 7412055Sgabeblack@google.comclass TapEvent : public PollEvent 7512055Sgabeblack@google.com{ 7612055Sgabeblack@google.com protected: 7712055Sgabeblack@google.com EtherTapBase *tap; 7812055Sgabeblack@google.com 7912055Sgabeblack@google.com public: 8012055Sgabeblack@google.com TapEvent(EtherTapBase *_tap, int fd, int e) 8112055Sgabeblack@google.com : PollEvent(fd, e), tap(_tap) {} 8212055Sgabeblack@google.com virtual void process(int revent) { tap->recvReal(revent); } 8312055Sgabeblack@google.com}; 8412055Sgabeblack@google.com 8512055Sgabeblack@google.comEtherTapBase::EtherTapBase(const Params *p) 8612055Sgabeblack@google.com : EtherObject(p), buflen(p->bufsz), dump(p->dump), event(NULL), 8712130Sspwilson2@wisc.edu interface(NULL), 8812130Sspwilson2@wisc.edu txEvent([this]{ retransmit(); }, "EtherTapBase retransmit") 8912055Sgabeblack@google.com{ 9012055Sgabeblack@google.com buffer = new uint8_t[buflen]; 9112055Sgabeblack@google.com interface = new EtherTapInt(name() + ".interface", this); 9212055Sgabeblack@google.com} 9312055Sgabeblack@google.com 9412055Sgabeblack@google.comEtherTapBase::~EtherTapBase() 9512055Sgabeblack@google.com{ 9612055Sgabeblack@google.com delete buffer; 9712055Sgabeblack@google.com delete event; 9812055Sgabeblack@google.com delete interface; 9912055Sgabeblack@google.com} 10012055Sgabeblack@google.com 10112055Sgabeblack@google.comvoid 10212055Sgabeblack@google.comEtherTapBase::serialize(CheckpointOut &cp) const 10312055Sgabeblack@google.com{ 10412055Sgabeblack@google.com SERIALIZE_SCALAR(buflen); 10512055Sgabeblack@google.com uint8_t *buffer = (uint8_t *)this->buffer; 10612055Sgabeblack@google.com SERIALIZE_ARRAY(buffer, buflen); 10712055Sgabeblack@google.com 10812055Sgabeblack@google.com bool tapevent_present = false; 10912055Sgabeblack@google.com if (event) { 11012055Sgabeblack@google.com tapevent_present = true; 11112055Sgabeblack@google.com SERIALIZE_SCALAR(tapevent_present); 11212055Sgabeblack@google.com event->serialize(cp); 11312055Sgabeblack@google.com } else { 11412055Sgabeblack@google.com SERIALIZE_SCALAR(tapevent_present); 11512055Sgabeblack@google.com } 11612055Sgabeblack@google.com} 11712055Sgabeblack@google.com 11812055Sgabeblack@google.comvoid 11912055Sgabeblack@google.comEtherTapBase::unserialize(CheckpointIn &cp) 12012055Sgabeblack@google.com{ 12112055Sgabeblack@google.com UNSERIALIZE_SCALAR(buflen); 12212055Sgabeblack@google.com uint8_t *buffer = (uint8_t *)this->buffer; 12312055Sgabeblack@google.com UNSERIALIZE_ARRAY(buffer, buflen); 12412055Sgabeblack@google.com 12512055Sgabeblack@google.com bool tapevent_present; 12612055Sgabeblack@google.com UNSERIALIZE_SCALAR(tapevent_present); 12712055Sgabeblack@google.com if (tapevent_present) { 12812055Sgabeblack@google.com event = new TapEvent(this, 0, 0); 12912055Sgabeblack@google.com event->unserialize(cp); 13012055Sgabeblack@google.com if (event->queued()) 13112055Sgabeblack@google.com pollQueue.schedule(event); 13212055Sgabeblack@google.com } 13312055Sgabeblack@google.com} 13412055Sgabeblack@google.com 13512055Sgabeblack@google.com 13612055Sgabeblack@google.comvoid 13712055Sgabeblack@google.comEtherTapBase::pollFd(int fd) 13812055Sgabeblack@google.com{ 13912055Sgabeblack@google.com assert(!event); 14012055Sgabeblack@google.com event = new TapEvent(this, fd, POLLIN|POLLERR); 14112055Sgabeblack@google.com pollQueue.schedule(event); 14212055Sgabeblack@google.com} 14312055Sgabeblack@google.com 14412055Sgabeblack@google.comvoid 14512055Sgabeblack@google.comEtherTapBase::stopPolling() 14612055Sgabeblack@google.com{ 14712055Sgabeblack@google.com assert(event); 14812055Sgabeblack@google.com delete event; 14912055Sgabeblack@google.com event = NULL; 15012055Sgabeblack@google.com} 15112055Sgabeblack@google.com 15212055Sgabeblack@google.com 15312055Sgabeblack@google.comEtherInt* 15412055Sgabeblack@google.comEtherTapBase::getEthPort(const std::string &if_name, int idx) 15512055Sgabeblack@google.com{ 15612055Sgabeblack@google.com if (if_name == "tap") { 15712055Sgabeblack@google.com if (interface->getPeer()) 15812055Sgabeblack@google.com panic("Interface already connected to\n"); 15912055Sgabeblack@google.com return interface; 16012055Sgabeblack@google.com } 16112055Sgabeblack@google.com return NULL; 16212055Sgabeblack@google.com} 16312055Sgabeblack@google.com 16412055Sgabeblack@google.combool 16512055Sgabeblack@google.comEtherTapBase::recvSimulated(EthPacketPtr packet) 16612055Sgabeblack@google.com{ 16712055Sgabeblack@google.com if (dump) 16812055Sgabeblack@google.com dump->dump(packet); 16912055Sgabeblack@google.com 17012055Sgabeblack@google.com DPRINTF(Ethernet, "EtherTap sim->real len=%d\n", packet->length); 17112055Sgabeblack@google.com DDUMP(EthernetData, packet->data, packet->length); 17212055Sgabeblack@google.com 17312055Sgabeblack@google.com bool success = sendReal(packet->data, packet->length); 17412055Sgabeblack@google.com 17512055Sgabeblack@google.com interface->recvDone(); 17612055Sgabeblack@google.com 17712055Sgabeblack@google.com return success; 17812055Sgabeblack@google.com} 17912055Sgabeblack@google.com 18012055Sgabeblack@google.comvoid 18112055Sgabeblack@google.comEtherTapBase::sendSimulated(void *data, size_t len) 18212055Sgabeblack@google.com{ 18312055Sgabeblack@google.com EthPacketPtr packet; 18412055Sgabeblack@google.com packet = make_shared<EthPacketData>(len); 18512055Sgabeblack@google.com packet->length = len; 18612055Sgabeblack@google.com packet->simLength = len; 18712055Sgabeblack@google.com memcpy(packet->data, data, len); 18812055Sgabeblack@google.com 18912055Sgabeblack@google.com DPRINTF(Ethernet, "EtherTap real->sim len=%d\n", packet->length); 19012055Sgabeblack@google.com DDUMP(EthernetData, packet->data, packet->length); 19112055Sgabeblack@google.com if (!packetBuffer.empty() || !interface->sendPacket(packet)) { 19212055Sgabeblack@google.com DPRINTF(Ethernet, "bus busy...buffer for retransmission\n"); 19312055Sgabeblack@google.com packetBuffer.push(packet); 19412055Sgabeblack@google.com if (!txEvent.scheduled()) 19512055Sgabeblack@google.com schedule(txEvent, curTick() + retryTime); 19612055Sgabeblack@google.com } else if (dump) { 19712055Sgabeblack@google.com dump->dump(packet); 19812055Sgabeblack@google.com } 19912055Sgabeblack@google.com} 20012055Sgabeblack@google.com 20112055Sgabeblack@google.comvoid 20212055Sgabeblack@google.comEtherTapBase::retransmit() 20312055Sgabeblack@google.com{ 20412055Sgabeblack@google.com if (packetBuffer.empty()) 20512055Sgabeblack@google.com return; 20612055Sgabeblack@google.com 20712055Sgabeblack@google.com EthPacketPtr packet = packetBuffer.front(); 20812055Sgabeblack@google.com if (interface->sendPacket(packet)) { 20912055Sgabeblack@google.com if (dump) 21012055Sgabeblack@google.com dump->dump(packet); 21112055Sgabeblack@google.com DPRINTF(Ethernet, "EtherTap retransmit\n"); 21212055Sgabeblack@google.com packetBuffer.front() = NULL; 21312055Sgabeblack@google.com packetBuffer.pop(); 21412055Sgabeblack@google.com } 21512055Sgabeblack@google.com 21612055Sgabeblack@google.com if (!packetBuffer.empty() && !txEvent.scheduled()) 21712055Sgabeblack@google.com schedule(txEvent, curTick() + retryTime); 21812055Sgabeblack@google.com} 21912055Sgabeblack@google.com 22012055Sgabeblack@google.com 2212SN/Aclass TapListener 2222SN/A{ 2232SN/A protected: 2242SN/A class Event : public PollEvent 2252SN/A { 2262SN/A protected: 2272SN/A TapListener *listener; 2282SN/A 2292SN/A public: 23012055Sgabeblack@google.com Event(TapListener *l, int fd, int e) : PollEvent(fd, e), listener(l) {} 2312SN/A 23212055Sgabeblack@google.com void process(int revent) override { listener->accept(); } 2332SN/A }; 2342SN/A 2352SN/A friend class Event; 2362SN/A Event *event; 2372SN/A 23812055Sgabeblack@google.com void accept(); 23912055Sgabeblack@google.com 2402SN/A protected: 2412SN/A ListenSocket listener; 24212054Sgabeblack@google.com EtherTapStub *tap; 2432SN/A int port; 2442SN/A 2452SN/A public: 24612055Sgabeblack@google.com TapListener(EtherTapStub *t, int p) : event(NULL), tap(t), port(p) {} 24712055Sgabeblack@google.com ~TapListener() { delete event; } 2482SN/A 2492SN/A void listen(); 2502SN/A}; 2512SN/A 2522SN/Avoid 2532SN/ATapListener::listen() 2542SN/A{ 2552SN/A while (!listener.listen(port, true)) { 2562SN/A DPRINTF(Ethernet, "TapListener(listen): Can't bind port %d\n", port); 2572SN/A port++; 2582SN/A } 2592SN/A 2602SN/A ccprintf(cerr, "Listening for tap connection on port %d\n", port); 2612SN/A event = new Event(this, listener.getfd(), POLLIN|POLLERR); 2622SN/A pollQueue.schedule(event); 2632SN/A} 2642SN/A 2652SN/Avoid 2662SN/ATapListener::accept() 2672SN/A{ 26810156SN/A // As a consequence of being called from the PollQueue, we might 26910156SN/A // have been called from a different thread. Migrate to "our" 27010156SN/A // thread. 27110156SN/A EventQueue::ScopedMigration migrate(tap->eventQueue()); 27210156SN/A 2732SN/A if (!listener.islistening()) 2742SN/A panic("TapListener(accept): cannot accept if we're not listening!"); 2752SN/A 2762SN/A int sfd = listener.accept(true); 2772SN/A if (sfd != -1) 2782SN/A tap->attach(sfd); 2792SN/A} 2802SN/A 2812SN/A 28212055Sgabeblack@google.comEtherTapStub::EtherTapStub(const Params *p) : EtherTapBase(p), socket(-1) 2832SN/A{ 2845523SN/A if (ListenSocket::allDisabled()) 28512054Sgabeblack@google.com fatal("All listeners are disabled! EtherTapStub can't work!"); 2865523SN/A 2874981SN/A listener = new TapListener(this, p->port); 2882SN/A listener->listen(); 2892SN/A} 2902SN/A 29112054Sgabeblack@google.comEtherTapStub::~EtherTapStub() 2922SN/A{ 2932SN/A delete listener; 2942SN/A} 2952SN/A 2962SN/Avoid 29712055Sgabeblack@google.comEtherTapStub::serialize(CheckpointOut &cp) const 29812055Sgabeblack@google.com{ 29912055Sgabeblack@google.com EtherTapBase::serialize(cp); 30012055Sgabeblack@google.com 30112055Sgabeblack@google.com SERIALIZE_SCALAR(socket); 30212055Sgabeblack@google.com SERIALIZE_SCALAR(buffer_used); 30312055Sgabeblack@google.com SERIALIZE_SCALAR(frame_len); 30412055Sgabeblack@google.com} 30512055Sgabeblack@google.com 30612055Sgabeblack@google.comvoid 30712055Sgabeblack@google.comEtherTapStub::unserialize(CheckpointIn &cp) 30812055Sgabeblack@google.com{ 30912055Sgabeblack@google.com EtherTapBase::unserialize(cp); 31012055Sgabeblack@google.com 31112055Sgabeblack@google.com UNSERIALIZE_SCALAR(socket); 31212055Sgabeblack@google.com UNSERIALIZE_SCALAR(buffer_used); 31312055Sgabeblack@google.com UNSERIALIZE_SCALAR(frame_len); 31412055Sgabeblack@google.com} 31512055Sgabeblack@google.com 31612055Sgabeblack@google.com 31712055Sgabeblack@google.comvoid 31812054Sgabeblack@google.comEtherTapStub::attach(int fd) 3192SN/A{ 3202SN/A if (socket != -1) 3212SN/A close(fd); 3222SN/A 32312055Sgabeblack@google.com buffer_used = 0; 32412055Sgabeblack@google.com frame_len = 0; 3252SN/A socket = fd; 32612054Sgabeblack@google.com DPRINTF(Ethernet, "EtherTapStub attached\n"); 32712055Sgabeblack@google.com pollFd(socket); 3282SN/A} 3292SN/A 3302SN/Avoid 33112054Sgabeblack@google.comEtherTapStub::detach() 3322SN/A{ 33312054Sgabeblack@google.com DPRINTF(Ethernet, "EtherTapStub detached\n"); 33412055Sgabeblack@google.com stopPolling(); 3352SN/A close(socket); 3362SN/A socket = -1; 3372SN/A} 3382SN/A 3392SN/Avoid 34012055Sgabeblack@google.comEtherTapStub::recvReal(int revent) 3412SN/A{ 3422SN/A if (revent & POLLERR) { 3432SN/A detach(); 3442SN/A return; 3452SN/A } 3462SN/A 3472SN/A if (!(revent & POLLIN)) 3482SN/A return; 3492SN/A 35012055Sgabeblack@google.com // Read in as much of the new data as we can. 35112055Sgabeblack@google.com int len = read(socket, buffer + buffer_used, buflen - buffer_used); 35212055Sgabeblack@google.com if (len == 0) { 35312055Sgabeblack@google.com detach(); 35412055Sgabeblack@google.com return; 35512055Sgabeblack@google.com } 35612055Sgabeblack@google.com buffer_used += len; 35712055Sgabeblack@google.com 35812055Sgabeblack@google.com // If there's not enough data for the frame length, wait for more. 35912055Sgabeblack@google.com if (buffer_used < sizeof(uint32_t)) 36012055Sgabeblack@google.com return; 36112055Sgabeblack@google.com 36212055Sgabeblack@google.com if (frame_len == 0) 36312055Sgabeblack@google.com frame_len = ntohl(*(uint32_t *)buffer); 36412055Sgabeblack@google.com 36512055Sgabeblack@google.com DPRINTF(Ethernet, "Received data from peer: len=%d buffer_used=%d " 36612055Sgabeblack@google.com "frame_len=%d\n", len, buffer_used, frame_len); 36712055Sgabeblack@google.com 36812055Sgabeblack@google.com uint8_t *frame_start = &buffer[sizeof(uint32_t)]; 36912055Sgabeblack@google.com while (frame_len != 0 && buffer_used >= frame_len + sizeof(uint32_t)) { 37012055Sgabeblack@google.com sendSimulated(frame_start, frame_len); 37112055Sgabeblack@google.com 37212055Sgabeblack@google.com // Bookkeeping. 37312055Sgabeblack@google.com buffer_used -= frame_len + sizeof(uint32_t); 37412055Sgabeblack@google.com if (buffer_used > 0) { 37512055Sgabeblack@google.com // If there's still any data left, move it into position. 37612055Sgabeblack@google.com memmove(buffer, frame_start + frame_len, buffer_used); 3772SN/A } 37812055Sgabeblack@google.com frame_len = 0; 3792SN/A 38012055Sgabeblack@google.com if (buffer_used >= sizeof(uint32_t)) 38112055Sgabeblack@google.com frame_len = ntohl(*(uint32_t *)buffer); 3822SN/A } 3832SN/A} 3842SN/A 38512055Sgabeblack@google.combool 38612055Sgabeblack@google.comEtherTapStub::sendReal(const void *data, size_t len) 3872SN/A{ 38812055Sgabeblack@google.com uint32_t frame_len = htonl(len); 38912055Sgabeblack@google.com ssize_t ret = write(socket, &frame_len, sizeof(frame_len)); 39012055Sgabeblack@google.com if (ret != sizeof(frame_len)) 39112055Sgabeblack@google.com return false; 39212055Sgabeblack@google.com return write(socket, data, len) == len; 3932SN/A} 3942SN/A 395253SN/A 39612056Sgabeblack@google.com#if USE_TUNTAP 39712056Sgabeblack@google.com 39812056Sgabeblack@google.comEtherTap::EtherTap(const Params *p) : EtherTapBase(p) 39912056Sgabeblack@google.com{ 40012056Sgabeblack@google.com int fd = open(p->tun_clone_device.c_str(), O_RDWR); 40112056Sgabeblack@google.com if (fd < 0) 40212056Sgabeblack@google.com panic("Couldn't open %s.\n", p->tun_clone_device); 40312056Sgabeblack@google.com 40412056Sgabeblack@google.com struct ifreq ifr; 40512056Sgabeblack@google.com memset(&ifr, 0, sizeof(ifr)); 40612056Sgabeblack@google.com ifr.ifr_flags = IFF_TAP | IFF_NO_PI; 40712056Sgabeblack@google.com strncpy(ifr.ifr_name, p->tap_device_name.c_str(), IFNAMSIZ); 40812056Sgabeblack@google.com 40912056Sgabeblack@google.com if (ioctl(fd, TUNSETIFF, (void *)&ifr) < 0) 41012056Sgabeblack@google.com panic("Failed to access tap device %s.\n", ifr.ifr_name); 41112056Sgabeblack@google.com // fd now refers to the tap device. 41212056Sgabeblack@google.com tap = fd; 41312056Sgabeblack@google.com pollFd(tap); 41412056Sgabeblack@google.com} 41512056Sgabeblack@google.com 41612056Sgabeblack@google.comEtherTap::~EtherTap() 41712056Sgabeblack@google.com{ 41812056Sgabeblack@google.com stopPolling(); 41912056Sgabeblack@google.com close(tap); 42012056Sgabeblack@google.com tap = -1; 42112056Sgabeblack@google.com} 42212056Sgabeblack@google.com 42312056Sgabeblack@google.comvoid 42412056Sgabeblack@google.comEtherTap::recvReal(int revent) 42512056Sgabeblack@google.com{ 42612056Sgabeblack@google.com if (revent & POLLERR) 42712056Sgabeblack@google.com panic("Error polling for tap data.\n"); 42812056Sgabeblack@google.com 42912056Sgabeblack@google.com if (!(revent & POLLIN)) 43012056Sgabeblack@google.com return; 43112056Sgabeblack@google.com 43212056Sgabeblack@google.com ssize_t ret = read(tap, buffer, buflen); 43312056Sgabeblack@google.com if (ret < 0) 43412056Sgabeblack@google.com panic("Failed to read from tap device.\n"); 43512056Sgabeblack@google.com 43612056Sgabeblack@google.com sendSimulated(buffer, ret); 43712056Sgabeblack@google.com} 43812056Sgabeblack@google.com 43912056Sgabeblack@google.combool 44012056Sgabeblack@google.comEtherTap::sendReal(const void *data, size_t len) 44112056Sgabeblack@google.com{ 44212056Sgabeblack@google.com if (write(tap, data, len) != len) 44312056Sgabeblack@google.com panic("Failed to write data to tap device.\n"); 44412056Sgabeblack@google.com return true; 44512056Sgabeblack@google.com} 44612056Sgabeblack@google.com 44712056Sgabeblack@google.comEtherTap * 44812056Sgabeblack@google.comEtherTapParams::create() 44912056Sgabeblack@google.com{ 45012056Sgabeblack@google.com return new EtherTap(this); 45112056Sgabeblack@google.com} 45212056Sgabeblack@google.com 45312056Sgabeblack@google.com#endif 45412056Sgabeblack@google.com 45512054Sgabeblack@google.comEtherTapStub * 45612054Sgabeblack@google.comEtherTapStubParams::create() 4572SN/A{ 45812054Sgabeblack@google.com return new EtherTapStub(this); 4592SN/A} 460