From bcec13606f3bb21b0d09b3b6a0133a098dbb1e9e Mon Sep 17 00:00:00 2001 From: Craig Dowell Date: Tue, 27 Jan 2009 12:36:46 -0800 Subject: [PATCH] checkpoint tap development --- examples/csma-tap-bridge.cc | 157 +++++++ examples/wscript | 4 + src/devices/tap-bridge/tap-bridge.cc | 292 +++++++++++++ src/devices/tap-bridge/tap-bridge.h | 128 ++++++ src/devices/tap-bridge/tap-encode-decode.cc | 110 +++++ src/devices/tap-bridge/tap-encode-decode.h | 33 ++ src/devices/tap-bridge/tap-sock-creator.cc | 456 ++++++++++++++++++++ src/devices/tap-bridge/tap.h | 8 + src/devices/tap-bridge/waf | 1 + src/devices/tap-bridge/wscript | 41 ++ src/helper/tap-bridge-helper.cc | 54 +++ src/helper/tap-bridge-helper.h | 44 ++ src/helper/wscript | 2 + src/wscript | 2 + 14 files changed, 1332 insertions(+) create mode 100644 examples/csma-tap-bridge.cc create mode 100644 src/devices/tap-bridge/tap-bridge.cc create mode 100644 src/devices/tap-bridge/tap-bridge.h create mode 100644 src/devices/tap-bridge/tap-encode-decode.cc create mode 100644 src/devices/tap-bridge/tap-encode-decode.h create mode 100644 src/devices/tap-bridge/tap-sock-creator.cc create mode 100644 src/devices/tap-bridge/tap.h create mode 100755 src/devices/tap-bridge/waf create mode 100644 src/devices/tap-bridge/wscript create mode 100644 src/helper/tap-bridge-helper.cc create mode 100644 src/helper/tap-bridge-helper.h diff --git a/examples/csma-tap-bridge.cc b/examples/csma-tap-bridge.cc new file mode 100644 index 000000000..efe4b3ab4 --- /dev/null +++ b/examples/csma-tap-bridge.cc @@ -0,0 +1,157 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// Network topology +// +// +----------+ +----------+ +// | external | | external | +// | Linux | | Linux | +// | Host | | Host | +// +----------+ +----------+ +// | n0 n3 | +// | +--------+ +--------+ | +// +-------| tap | | tap |-------+ +// | bridge | ... | bridge | +// +--------+ +--------+ +// | CSMA | | CSMA | +// +--------+ +--------+ +// | | +// | | +// | n1 n2 | +// | | | | +// ================ +// LAN + +#include +#include + +#include "ns3/simulator-module.h" +#include "ns3/node-module.h" +#include "ns3/core-module.h" +#include "ns3/helper-module.h" + +using namespace ns3; + +NS_LOG_COMPONENT_DEFINE ("CsmaTapBridgeExample"); + +int +main (int argc, char *argv[]) +{ + // + // Users may find it convenient to turn on explicit debugging + // for selected modules; the below lines suggest how to do this + // +#if 0 + LogComponentEnable ("CsmaOneSubnetExample", LOG_LEVEL_INFO); +#endif + + // + // Make the random number generators generate reproducible results. + // + RandomVariable::UseGlobalSeed (1, 1, 2, 3, 5, 8); + + // + // Allow the user to override any of the defaults and the above Bind() at + // run-time, via command-line arguments + // + CommandLine cmd; + cmd.Parse (argc, argv); + + // + // Create the nodes required by the topology (shown above). + // + NS_LOG_INFO ("Create nodes."); + NodeContainer nodes; + nodes.Create (4); + + // + // Create and install the network. + // + NS_LOG_INFO ("Build Topology"); + CsmaHelper csma; + csma.SetChannelAttribute ("DataRate", DataRateValue (5000000)); + csma.SetChannelAttribute ("Delay", TimeValue (MilliSeconds (2))); + + NetDeviceContainer devices = csma.Install (nodes); + + InternetStackHelper internet; + internet.Install (nodes); + + // + // Add the tap bridges to nodes zero and one to enable external Linux + // processes to talk to the CSMA devices. + // + TapBridgeHelper bridge; + NetDeviceContainer bridgeDevices; + bridgeDevices.Add (bridge.Install (nodes.Get (0), devices.Get (0))); + bridgeDevices.Add (bridge.Install (nodes.Get (3), devices.Get (3))); + + // + // We've got the "hardware" in place. Now add IP addresses. We mjust not + // add IP addresses to the devices that we bridged using the TapBridgeHelper + // above. The IP addresses are added to the bridge itself and are propagated + // to the tap device on the host. We do need to add IP addresses to the CSMA + // devices that are attached to the nodes that are entirely contained within + // the simulation (not connected to any other external host). + // + NS_LOG_INFO ("Assign IP Addresses."); + NetDeviceContainer ndc; + ndc.Add (bridgeDevices.Get (0)); + ndc.Add (devices.Get (1)); + ndc.Add (devices.Get (2)); + ndc.Add (bridgeDevices.Get (0)); + + Ipv4AddressHelper ipv4; + ipv4.SetBase ("10.1.1.0", "255.255.255.0"); + Ipv4InterfaceContainer interfaces = ipv4.Assign (ndc); + +#if 1 + // + // Testing only -- send a packet from an internal node to an external node + // + uint32_t packetSize = 1024; + uint32_t maxPacketCount = 100; + Time interPacketInterval = Seconds (1.); + UdpEchoClientHelper client (interfaces.GetAddress (0), 9); + client.SetAttribute ("MaxPackets", UintegerValue (maxPacketCount)); + client.SetAttribute ("Interval", TimeValue (interPacketInterval)); + client.SetAttribute ("PacketSize", UintegerValue (packetSize)); + ApplicationContainer apps = client.Install (nodes.Get (1)); + apps.Start (Seconds (2.0)); + apps.Stop (Seconds (10.0)); +#endif + + // + // Configure tracing of all enqueue, dequeue, and NetDevice receive events. + // +#if 0 + NS_LOG_INFO ("Configure Tracing."); + std::ofstream ascii; + ascii.open ("csma-tap-bridge.tr"); + CsmaHelper::EnableAsciiAll (ascii); + CsmaHelper::EnablePcapAll ("csma-tap-bridge"); +#endif + + // + // Now, do the actual simulation. Run for a few minutes to allow the user a chance + // to run some applications on the Linux hosts. + // + Simulator::Stop (Seconds (3. * 60.)); + NS_LOG_INFO ("Run Simulation."); + Simulator::Run (); + Simulator::Destroy (); + NS_LOG_INFO ("Done."); +} diff --git a/examples/wscript b/examples/wscript index ed6032f9e..c35f005fb 100644 --- a/examples/wscript +++ b/examples/wscript @@ -48,6 +48,10 @@ def build(bld): ['csma', 'internet-stack']) obj.source = 'csma-one-subnet.cc' + obj = bld.create_ns3_program('csma-tap-bridge', + ['csma', 'tap-bridge', 'internet-stack']) + obj.source = 'csma-tap-bridge.cc' + obj = bld.create_ns3_program('csma-bridge', ['bridge', 'csma', 'internet-stack']) obj.source = 'csma-bridge.cc' diff --git a/src/devices/tap-bridge/tap-bridge.cc b/src/devices/tap-bridge/tap-bridge.cc new file mode 100644 index 000000000..ea40d0ee9 --- /dev/null +++ b/src/devices/tap-bridge/tap-bridge.cc @@ -0,0 +1,292 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2009 University of Washington + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "tap-bridge.h" +#include "ns3/node.h" +#include "ns3/channel.h" +#include "ns3/packet.h" +#include "ns3/log.h" +#include "ns3/boolean.h" +#include "ns3/simulator.h" + +NS_LOG_COMPONENT_DEFINE ("TapBridge"); + +namespace ns3 { + +NS_OBJECT_ENSURE_REGISTERED (TapBridge); + +TypeId +TapBridge::GetTypeId (void) +{ + static TypeId tid = TypeId ("ns3::TapBridge") + .SetParent () + .AddConstructor () + ; + return tid; +} + + +TapBridge::TapBridge () + : m_node (0), + m_ifIndex (0) +{ + NS_LOG_FUNCTION_NOARGS (); +} + +TapBridge::~TapBridge() +{ + NS_LOG_FUNCTION_NOARGS (); +} + + void +TapBridge::DoDispose () +{ + NS_LOG_FUNCTION_NOARGS (); + NetDevice::DoDispose (); +} + +void +TapBridge::SetBridgedDevice (Ptr bridgedDevice) +{ + NS_LOG_FUNCTION_NOARGS (); + + NS_ASSERT_MSG (m_node != 0, "TapBridge::SetBridgedDevice: Bridge not installed in a node"); + NS_ASSERT_MSG (bridgedDevice != this, "TapBridge::SetBridgedDevice: Cannot bridge to self"); + NS_ASSERT_MSG (m_bridgedDevice == 0, "TapBridge::SetBridgedDevice: Already bridged"); + + if (!Mac48Address::IsMatchingType (bridgedDevice->GetAddress ())) + { + NS_FATAL_ERROR ("TapBridge::SetBridgedDevice: Device does not support eui 48 addresses: cannot be added to bridge."); + } + + if (!bridgedDevice->SupportsSendFrom ()) + { + NS_FATAL_ERROR ("TapBridge::SetBridgedDevice: Device does not support SendFrom: cannot be added to bridge."); + } + + // + // Tell the bridged device to forward its received packets here. + // + m_node->RegisterProtocolHandler (MakeCallback (&TapBridge::ReceiveFromBridgedDevice, this), 0, bridgedDevice, true); + m_bridgedDevice = bridgedDevice; +} + +void +TapBridge::ReceiveFromBridgedDevice ( + Ptr device, + Ptr packet, + uint16_t protocol, + Address const &src, + Address const &dst, + PacketType packetType) +{ + NS_LOG_FUNCTION (device << packet << protocol << src << dst << packetType); + NS_ASSERT_MSG (device == m_bridgedDevice, "TapBridge::SetBridgedDevice: Received packet from unexpected device"); + + NS_LOG_DEBUG ("Packet UID is " << packet->GetUid ()); + + Mac48Address src48 = Mac48Address::ConvertFrom (src); + Mac48Address dst48 = Mac48Address::ConvertFrom (dst); + + // + // We have received a packet from the ns-3 net device that has been associated + // with this bridge. We want to take these bits and send them off to the + // Tap device on the Linux host. + // + NS_LOG_LOGIC ("TapBridge::ReceiveFromDevice: Not implemented"); +} + +void +TapBridge::SetName(const std::string name) +{ + NS_LOG_FUNCTION_NOARGS (); + m_name = name; +} + +std::string +TapBridge::GetName(void) const +{ + NS_LOG_FUNCTION_NOARGS (); + return m_name; +} + +void +TapBridge::SetIfIndex(const uint32_t index) +{ + NS_LOG_FUNCTION_NOARGS (); + m_ifIndex = index; +} + +uint32_t +TapBridge::GetIfIndex(void) const +{ + NS_LOG_FUNCTION_NOARGS (); + return m_ifIndex; +} + +Ptr +TapBridge::GetChannel (void) const +{ + NS_LOG_FUNCTION_NOARGS (); + return 0; +} + +Address +TapBridge::GetAddress (void) const +{ + NS_LOG_FUNCTION_NOARGS (); + return m_address; +} + +bool +TapBridge::SetMtu (const uint16_t mtu) +{ + NS_LOG_FUNCTION_NOARGS (); + m_mtu = mtu; + return true; +} + +uint16_t +TapBridge::GetMtu (void) const +{ + NS_LOG_FUNCTION_NOARGS (); + return m_mtu; +} + + +bool +TapBridge::IsLinkUp (void) const +{ + NS_LOG_FUNCTION_NOARGS (); + return true; +} + +void +TapBridge::SetLinkChangeCallback (Callback callback) +{ + NS_LOG_FUNCTION_NOARGS (); +} + +bool +TapBridge::IsBroadcast (void) const +{ + NS_LOG_FUNCTION_NOARGS (); + return true; +} + +Address +TapBridge::GetBroadcast (void) const +{ + NS_LOG_FUNCTION_NOARGS (); + return Mac48Address ("ff:ff:ff:ff:ff:ff"); +} + +bool +TapBridge::IsMulticast (void) const +{ + NS_LOG_FUNCTION_NOARGS (); + return true; +} + +Address +TapBridge::GetMulticast (Ipv4Address multicastGroup) const +{ + NS_LOG_FUNCTION (this << multicastGroup); + Mac48Address multicast = Mac48Address::GetMulticast (multicastGroup); + return multicast; +} + +bool +TapBridge::IsPointToPoint (void) const +{ + NS_LOG_FUNCTION_NOARGS (); + return false; +} + +bool +TapBridge::IsBridge (void) const +{ + NS_LOG_FUNCTION_NOARGS (); + return true; +} + +bool +TapBridge::Send (Ptr packet, const Address& dst, uint16_t protocol) +{ + NS_LOG_FUNCTION (packet << dst << protocol); + NS_FATAL_ERROR ("TapBridge::Send: You may not call Send on a TapBridge directly"); + return false; +} + +bool +TapBridge::SendFrom (Ptr packet, const Address& src, const Address& dst, uint16_t protocol) +{ + NS_LOG_FUNCTION (packet << src << dst << protocol); + NS_FATAL_ERROR ("TapBridge::Send: You may not call SendFrom on a TapBridge directly"); + return false; +} + +Ptr +TapBridge::GetNode (void) const +{ + NS_LOG_FUNCTION_NOARGS (); + return m_node; +} + +void +TapBridge::SetNode (Ptr node) +{ + NS_LOG_FUNCTION_NOARGS (); + m_node = node; +} + +bool +TapBridge::NeedsArp (void) const +{ + NS_LOG_FUNCTION_NOARGS (); + return true; +} + +void +TapBridge::SetReceiveCallback (NetDevice::ReceiveCallback cb) +{ + NS_LOG_FUNCTION_NOARGS (); + m_rxCallback = cb; +} + +void +TapBridge::SetPromiscReceiveCallback (NetDevice::PromiscReceiveCallback cb) +{ + NS_LOG_FUNCTION_NOARGS (); + m_promiscRxCallback = cb; +} + +bool +TapBridge::SupportsSendFrom () const +{ + NS_LOG_FUNCTION_NOARGS (); + return true; +} + +Address TapBridge::GetMulticast (Ipv6Address addr) const +{ + NS_LOG_FUNCTION (this << addr); + return Mac48Address::GetMulticast (addr); +} + +} // namespace ns3 diff --git a/src/devices/tap-bridge/tap-bridge.h b/src/devices/tap-bridge/tap-bridge.h new file mode 100644 index 000000000..5d1d56bde --- /dev/null +++ b/src/devices/tap-bridge/tap-bridge.h @@ -0,0 +1,128 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2009 University of Washington + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef TAP_BRIDGE_H +#define TAP_BRIDGE_H + +#include "ns3/net-device.h" +#include "ns3/mac48-address.h" +#include "ns3/nstime.h" + +namespace ns3 { + +class Node; + +/** + * \ingroup devices + * \defgroup tap-bridge TapBridge + * + * \brief A bridge to make it appear that a host is connected to an ns-3 net device. + * + * The Tap Bridge lives in a kind of a gray world somewhere between a Linux host and + * an ns-3 bridge device. From the Linux perspective, this code appears as the user + * mode handler for a Tap net device. That is, when the Linux host writes to the + * /dev/tapx device that we create for it, the write is redirected into the TapBridge + * and from that perspective, becomes a read. The TapBridge then redirects the data + * written (by the Linux host) to the tap device on out the ns-3 net device to which + * we are bridged. When a packet comes in from the ns-3 world to the ns-3 net device + * we are bridging, it appears via a callback from that net device. Our job is to + * take those bits and write them back to the host using the user mode handler for + * /dev/tapx. This write to the device will then appear to the Linux host as if a + * packet has arrived on its device. + * + * The upshot is that the Tap Bridge appears to bridge a tap device on a Linux host + * in the "real world" to an ns-3 net device in the simulation. In order to do this + * we need a "ghost node" in the simulation to hold the bridged ns-3 net device and + * this Tap Bridge. It won't actually do anything else in the simulation. You will + * be able to perform typical ns-3 operations on that node, but they will have no + * effect other than to set up, tear down and configure the net devices and bridges + * mentioned above. + */ + +/** + * \ingroup tap-bridge + * \brief A bridge to make it appear that a host is connected to an ns-3 net device. + */ + +class TapBridge : public NetDevice +{ +public: + static TypeId GetTypeId (void); + + TapBridge (); + virtual ~TapBridge (); + + /** \brief Set the device to bridge. + * + * This method tells the bridge which ns-3 net device it should use to connect + * the simulation side of the bridge. + * + * \attention The ns-3 net device that is being set as the device must not + * have an IP address. This address is a property of the host Linux device. + */ + void SetBridgedDevice (Ptr bridgedDevice); + + // inherited from NetDevice base class. + virtual void SetName(const std::string name); + virtual std::string GetName(void) const; + virtual void SetIfIndex(const uint32_t index); + virtual uint32_t GetIfIndex(void) const; + virtual Ptr GetChannel (void) const; + virtual Address GetAddress (void) const; + virtual bool SetMtu (const uint16_t mtu); + virtual uint16_t GetMtu (void) const; + virtual bool IsLinkUp (void) const; + virtual void SetLinkChangeCallback (Callback callback); + virtual bool IsBroadcast (void) const; + virtual Address GetBroadcast (void) const; + virtual bool IsMulticast (void) const; + virtual Address GetMulticast (Ipv4Address multicastGroup) const; + virtual bool IsPointToPoint (void) const; + virtual bool IsBridge (void) const; + virtual bool Send (Ptr packet, const Address& dest, uint16_t protocolNumber); + virtual bool SendFrom (Ptr packet, const Address& source, const Address& dest, uint16_t protocolNumber); + virtual Ptr GetNode (void) const; + virtual void SetNode (Ptr node); + virtual bool NeedsArp (void) const; + virtual void SetReceiveCallback (NetDevice::ReceiveCallback cb); + virtual void SetPromiscReceiveCallback (NetDevice::PromiscReceiveCallback cb); + virtual bool SupportsSendFrom () const; + virtual Address GetMulticast (Ipv6Address addr) const; + +protected: + virtual void DoDispose (void); + + void ReceiveFromBridgedDevice (Ptr device, Ptr packet, uint16_t protocol, + Address const &src, Address const &dst, PacketType packetType); + +private: + NetDevice::ReceiveCallback m_rxCallback; + NetDevice::PromiscReceiveCallback m_promiscRxCallback; + + Mac48Address m_address; + Ptr m_node; + std::string m_name; + uint32_t m_ifIndex; + uint16_t m_mtu; + + Ptr m_bridgedDevice; +}; + +} // namespace ns3 + +#endif /* TAP_BRIDGE_H */ diff --git a/src/devices/tap-bridge/tap-encode-decode.cc b/src/devices/tap-bridge/tap-encode-decode.cc new file mode 100644 index 000000000..9d85519ab --- /dev/null +++ b/src/devices/tap-bridge/tap-encode-decode.cc @@ -0,0 +1,110 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2009 University of Washington + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include + +namespace ns3 { + +/** + * \brief Convert a byte buffer to a string containing a hex representation + * of the buffer. Make the string pretty by adding a colon (':') between + * the hex. + * + * \param buffer The input buffer to be converted. + * \param len The length of the input buffer. + * \returns A string containing a hex representation of the data in buffer. + */ + std::string +TapBufferToString (uint8_t *buffer, uint32_t len) +{ + std::ostringstream oss; + // + // Tell the stream to make hex characters, zero-filled + // + oss.setf (std::ios::hex, std::ios::basefield); + oss.fill('0'); + + // + // Loop through the buffer, separating the two-digit-wide hex bytes + // with a colon. + // + for (uint8_t i = 0; i < len; i++) + { + oss << ":" << std::setw (2) << (uint32_t)buffer[i]; + } + return oss.str (); +} + +/** + * \brief Convert string encoded by the inverse function (TapBufferToString) + * back into a byte buffer. + * + * \param s The input string. + * \param buffer The buffer to initialize with the converted bits. + * \param len The length of the data that is valid in the buffer. + * \returns True indicates a successful conversion. + */ + bool +TapStringToBuffer (std::string s, uint8_t *buffer, uint32_t *len) +{ + // + // If the string was made by our inverse function, the string length must + // be a multiple of three characters in length. Use this fact to do a + // quick reasonableness test. + // + if ((s.length () % 3) != 0) + { + return false; + } + + std::istringstream iss; + iss.str (s); + + uint8_t n = 0; + + while (iss.good ()) + { + // + // The first character in the "triplet" we're working on is always the + // the ':' separator. Read that into a char and make sure we're skipping + // what we think we're skipping. + // + char c; + iss.read (&c, 1); + if (c != ':') + { + return false; + } + + // + // And then read in the real bits and convert them. + // + uint32_t tmp; + iss >> std::hex >> tmp; + buffer[n] = tmp; + n++; + } + + *len = n; + return true; +} + +} // namespace ns3 diff --git a/src/devices/tap-bridge/tap-encode-decode.h b/src/devices/tap-bridge/tap-encode-decode.h new file mode 100644 index 000000000..28dc59a55 --- /dev/null +++ b/src/devices/tap-bridge/tap-encode-decode.h @@ -0,0 +1,33 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2009 University of Washington + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef TAP_ENCODE_DECODE_H +#define TAP_ENCODE_DECODE_H + +#include + +namespace ns3 { + + std::string TapBufferToString (uint8_t *buffer, uint32_t len); + bool TapStringToBuffer (std::string s, uint8_t *buffer, uint32_t *len); + + +} // namespace ns3 + +#endif // TAP_ENCODE_DECODE_H + diff --git a/src/devices/tap-bridge/tap-sock-creator.cc b/src/devices/tap-bridge/tap-sock-creator.cc new file mode 100644 index 000000000..74bb96661 --- /dev/null +++ b/src/devices/tap-bridge/tap-sock-creator.cc @@ -0,0 +1,456 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2009 University of Washington + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tap-encode-decode.h" + +#define TAP_MAGIC 95549 + +static int gVerbose = 0; + +#define LOG(msg) \ + if (gVerbose) \ + { \ + std::cout << __FUNCTION__ << "(): " << msg << std::endl; \ + } + +#define ABORT(msg, printErrno) \ + std::cout << __FILE__ << ": fatal error at line " << __LINE__ << ": " << __FUNCTION__ << "(): " << msg << std::endl; \ + if (printErrno) \ + { \ + std::cout << " errno = " << errno << " (" << strerror (errno) << ")" << std::endl; \ + } \ + exit (-1); + +#define ABORT_IF(cond, msg, printErrno) \ + if (cond) \ + { \ + ABORT(msg, printErrno); \ + } + +// +// Thanks, Mathieu, for the beginning of these functions +// +#define ASCII_DOT (0x2e) +#define ASCII_ZERO (0x30) +#define ASCII_a (0x41) +#define ASCII_z (0x5a) +#define ASCII_A (0x61) +#define ASCII_Z (0x7a) +#define ASCII_COLON (0x3a) +#define ASCII_ZERO (0x30) + +static char +AsciiToLowCase (char c) +{ + if (c >= ASCII_a && c <= ASCII_z) { + return c; + } else if (c >= ASCII_A && c <= ASCII_Z) { + return c + (ASCII_a - ASCII_A); + } else { + return c; + } +} + +static uint32_t +AsciiToIpv4 (const char *address) +{ + uint32_t host = 0; + while (true) { + uint8_t byte = 0; + while (*address != ASCII_DOT && + *address != 0) { + byte *= 10; + byte += *address - ASCII_ZERO; + address++; + } + host <<= 8; + host |= byte; + if (*address == 0) { + break; + } + address++; + } + return host; +} + +static void +AsciiToMac48 (const char *str, uint8_t addr[6]) +{ + int i = 0; + while (*str != 0 && i < 6) + { + uint8_t byte = 0; + while (*str != ASCII_COLON && *str != 0) + { + byte <<= 4; + char low = AsciiToLowCase (*str); + if (low >= ASCII_a) + { + byte |= low - ASCII_a + 10; + } + else + { + byte |= low - ASCII_ZERO; + } + str++; + } + addr[i] = byte; + i++; + if (*str == 0) + { + break; + } + str++; + } +} + +static void +SetInetAddress (sockaddr *ad, uint32_t networkOrder) +{ + struct sockaddr_in *sin = (struct sockaddr_in*)ad; + sin->sin_family = AF_INET; + sin->sin_port = 0; // unused + sin->sin_addr.s_addr = htonl (networkOrder); +} + +/** + * \brief Send the socket file descriptor we created back to the tap bridge, + * which will read it as soon as we're done. + * + * \param path The socket address information from the Unix socket we use + * to send the created socket back to. + * \param fd The socket we're going to send. + */ + static void +SendSocket (const char *path, int fd) +{ + // + // Open a Unix (local interprocess) socket to call back to the tap bridge + // + LOG ("Create Unix socket"); + int sock = socket (PF_UNIX, SOCK_DGRAM, 0); + ABORT_IF (sock == -1, "Unable to open socket", 1); + + // + // We have this string called path, which is really a hex representation + // of the endpoint that the tap bridge created. It used a forward encoding + // method (TapBufferToString) to take the sockaddr_un it made and passed + // the resulting string to us. So we need to take the inverse method + // (TapStringToBuffer) and build the same sockaddr_un over here. + // + socklen_t clientAddrLen; + struct sockaddr_un clientAddr; + + LOG ("Decode address " << path); + bool rc = ns3::TapStringToBuffer (path, (uint8_t *)&clientAddr, &clientAddrLen); + ABORT_IF (rc == false, "Unable to decode path", 0); + + LOG ("Connect"); + int status = connect (sock, (struct sockaddr*)&clientAddr, clientAddrLen); + ABORT_IF (status == -1, "Unable to connect to tap bridge", 1); + + LOG ("Connected"); + + // + // This is arcane enough that a few words are worthwhile to explain what's + // going on here. + // + // The interesting information (the socket FD) is going to go back to the + // tap bridge as an integer of ancillary data. Ancillary data is bits + // that are not a part a socket payload (out-of-band data). We're also + // going to send one integer back. It's just initialized to a magic number + // we use to make sure that the tap bridge is talking to the tap socket + // creator and not some other creator process (emu, specifically) + // + // The struct iovec below is part of a scatter-gather list. It describes a + // buffer. In this case, it describes a buffer (an integer) containing the + // data that we're going to send back to the tap bridge (that magic number). + // + struct iovec iov; + uint32_t magic = TAP_MAGIC; + iov.iov_base = &magic; + iov.iov_len = sizeof(magic); + + // + // The CMSG macros you'll see below are used to create and access control + // messages (which is another name for ancillary data). The ancillary + // data is made up of pairs of struct cmsghdr structures and associated + // data arrays. + // + // First, we're going to allocate a buffer on the stack to contain our + // data array (that contains the socket). Sometimes you'll see this called + // an "ancillary element" but the msghdr uses the control message termimology + // so we call it "control." + // + size_t msg_size = sizeof(int); + char control[CMSG_SPACE(msg_size)]; + + // + // There is a msghdr that is used to minimize the number of parameters + // passed to sendmsg (which we will use to send our ancillary data). This + // structure uses terminology corresponding to control messages, so you'll + // see msg_control, which is the pointer to the ancillary data and controllen + // which is the size of the ancillary data array. + // + // So, initialize the message header that describes our ancillary/control data + // and point it to the control message/ancillary data we just allocated space + // for. + // + struct msghdr msg; + msg.msg_name = 0; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = sizeof (control); + msg.msg_flags = 0; + + // + // A cmsghdr contains a length field that is the length of the header and + // the data. It has a cmsg_level field corresponding to the originating + // protocol. This takes values which are legal levels for getsockopt and + // setsockopt (here SOL_SOCKET). We're going to use the SCM_RIGHTS type of + // cmsg, that indicates that the ancillary data array contains access rights + // that we are sending back to the tap bridge. + // + // We have to put together the first (and only) cmsghdr that will describe + // the whole package we're sending. + // + struct cmsghdr *cmsg; + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(msg_size); + // + // We also have to update the controllen in case other stuff is actually + // in there we may not be aware of (due to macros). + // + msg.msg_controllen = cmsg->cmsg_len; + + // + // Finally, we get a pointer to the start of the ancillary data array and + // put our file descriptor in. + // + int *fdptr = (int*) (CMSG_DATA(cmsg)); + *fdptr = fd; // + + // + // Actually send the file descriptor back to the tap bridge. + // + ssize_t len = sendmsg(sock, &msg, 0); + ABORT_IF (len == -1, "Could not send socket back to tap bridge", 1); + + LOG ("sendmsg complete"); +} + + static int +CreateTap (const char *dev, const char *gw, const char *ip, const char *netmask, const char *mac) +{ + // + // Creation and management of Tap devices is done via the tun device + // + int tap = open ("/dev/net/tun", O_RDWR); + ABORT_IF (tap == -1, "Could not open /dev/net/tun", true); + + // + // Allocate a tap device, making sure that it will not send the tun_pi header. + // If we provide a null name to the ifr.ifr_name, we tell the kernel to pick + // a name for us (i.e., tapn where n = 0..255 + // + struct ifreq ifr; + ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + strcpy (ifr.ifr_name, dev); + int status = ioctl (tap, TUNSETIFF, (void *) &ifr); + ABORT_IF (status == -1, "Could not allocate tap device", true); + + std::string tapDeviceName = (char *)ifr.ifr_name; + LOG ("Allocated TAP device " << tapDeviceName); + + // + // Set the hardware (MAC) address of the new device + // + ifr.ifr_hwaddr.sa_family = 1; // this is ARPHRD_ETHER from if_arp.h + AsciiToMac48 (mac, (uint8_t*)ifr.ifr_hwaddr.sa_data); + status = ioctl (tap, SIOCSIFHWADDR, &ifr); + ABORT_IF (status == -1, "Could not set MAC address", true); + LOG ("Set device MAC address to " << mac); + + int fd = socket (AF_INET, SOCK_DGRAM, 0); + + // + // Bring the interface up. + // + status = ioctl (fd, SIOCGIFFLAGS, &ifr); + ABORT_IF (status == -1, "Could not get flags for interface", true); + ifr.ifr_flags |= IFF_UP | IFF_RUNNING; + status = ioctl (fd, SIOCSIFFLAGS, &ifr); + ABORT_IF (status == -1, "Could not bring interface up", true); + LOG ("Device is up"); + + // + // Set the IP address of the new interface/device. + // + SetInetAddress (&ifr.ifr_addr, AsciiToIpv4 (ip)); + status = ioctl (fd, SIOCSIFADDR, &ifr); + ABORT_IF (status == -1, "Could not set IP address", true); + LOG ("Set device IP address to " << ip); + + // + // Set the net mask of the new interface/device + // + SetInetAddress (&ifr.ifr_netmask, AsciiToIpv4 (netmask)); + status = ioctl (fd, SIOCSIFNETMASK, &ifr); + ABORT_IF (status == -1, "Could not set net mask", true); + LOG ("Set device Net Mask to " << netmask); + + return tap; +} + + int +main (int argc, char *argv[]) +{ + int c; + char *dev = NULL; + char *gw = NULL; + char *ip = NULL; + char *mac = NULL; + char *netmask = NULL; + char *path = NULL; + + opterr = 0; + + while ((c = getopt (argc, argv, "vd:g:i:m:n:p:")) != -1) + { + switch (c) + { + case 'v': + gVerbose = true; + break; + case 'd': + dev = optarg; // name of the new tap device + break; + case 'g': + gw = optarg; // gateway address for the new device + break; + case 'i': + ip = optarg; // ip address of the new device + break; + case 'm': + mac = optarg; // mac address of the new device + break; + case 'n': + netmask = optarg; // net mask for the new device + break; + case 'p': + path = optarg; // path back to the tap bridge + break; + } + } + + // + // We have got to be able to coordinate the name of the tap device we are + // going to create and or open with the device that an external Linux host + // will use. THis name is given in dev + // + ABORT_IF (dev == NULL, "Device Name is a required argument", 0); + LOG ("Provided Device Name is \"" << dev << "\""); + + // + // We have got to be able to provide a gateway to the external Linux host + // so it can talk to the ns-3 network. This ip address is provided in + // gw. + // + ABORT_IF (gw == NULL, "Gateway Address is a required argument", 0); + LOG ("Provided Gateway Address is \"" << dev << "\""); + + // + // We have got to be able to assign an IP address to the tap device we are + // allocating. This address is allocated in the simulation and assigned to + // the tap bridge. This address is given in ip. + // + ABORT_IF (ip == NULL, "IP Address is a required argument", 0); + LOG ("Provided IP Address is \"" << ip << "\""); + + // + // We have got to be able to assign a Mac address to the tap device we are + // allocating. This address is allocated in the simulation and assigned to + // the bridged device. This allows packets addressed to the bridged device + // to appear in the Linux host as if they were received there. + // + ABORT_IF (mac == NULL, "MAC Address is a required argument", 0); + LOG ("Provided MAC Address is \"" << mac << "\""); + + // + // We have got to be able to assign a net mask to the tap device we are + // allocating. This mask is allocated in the simulation and given to + // the bridged device. + // + ABORT_IF (netmask == NULL, "Net Mask is a required argument", 0); + LOG ("Provided Net Mask is \"" << netmask << "\""); + + // + // This program is spawned by a tap bridge running in a simulation. It + // wants to create a socket as described below. We are going to do the + // work here since we're running suid root. Once we create the socket, + // we have to send it back to the tap bridge. We do that over a Unix + // (local interprocess) socket. The tap bridge created a socket to + // listen for our response on, and it is expected to have encoded the address + // information as a string and to have passed that string as an argument to + // us. We see it here as the "path" string. We can't do anything useful + // unless we have that string. + // + ABORT_IF (path == NULL, "path is a required argument", 0); + LOG ("Provided path is \"" << path << "\""); + + // + // The whole reason for all of the hoops we went through to call out to this + // program will pay off here. We created this program to run as suid root + // in order to keep the main simulation program from having to be run with + // root privileges. We need root privileges to be able to futz with the + // Tap device underlying all of this. So all of these hoops are to allow + // us to exeucte the following code: + // + LOG ("Creating Tap"); + int sock = CreateTap (dev, gw, ip, mac, netmask); + ABORT_IF (sock == -1, "main(): Unable to create tap socket", 1); + + // + // Send the socket back to the tap net device so it can go about its business + // + SendSocket (path, sock); + + return 0; +} diff --git a/src/devices/tap-bridge/tap.h b/src/devices/tap-bridge/tap.h new file mode 100644 index 000000000..65072e8b0 --- /dev/null +++ b/src/devices/tap-bridge/tap.h @@ -0,0 +1,8 @@ +/** + * \ingroup devices + * \defgroup TapBridgeModel Tap Bridge Model + * + * \section TapBridgeModelOverview TapBridge Model Overview + * + * The tap bridge ... + */ diff --git a/src/devices/tap-bridge/waf b/src/devices/tap-bridge/waf new file mode 100755 index 000000000..4283ec141 --- /dev/null +++ b/src/devices/tap-bridge/waf @@ -0,0 +1 @@ +exec "`dirname "$0"`"/../../../waf "$@" diff --git a/src/devices/tap-bridge/wscript b/src/devices/tap-bridge/wscript new file mode 100644 index 000000000..b6dd7706f --- /dev/null +++ b/src/devices/tap-bridge/wscript @@ -0,0 +1,41 @@ +## -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*- + +def configure(conf): + if conf.env['ENABLE_THREADING']: + conf.env['ENABLE_TAP'] = conf.check(header_name='linux/if_ether.h', + define_name='HAVE_IF_ETHER_H') + conf.report_optional_feature("TapBridge", "Tap Bridge", + conf.env['ENABLE_TAP'], + " include not detected") + else: + conf.report_optional_feature("TapBridge", "Tap Bridge", + False, + "needs threading support which is not available") + +def build(bld): + module = bld.create_ns3_module('tap-bridge', ['node']) + module.source = [ + ] + headers = bld.new_task_gen('ns3header') + headers.module = 'tap-bridge' + headers.source = [ + 'tap.h', + ] + + env = bld.env_of_name('default') + if env['ENABLE_TAP']: + module.source.extend([ + 'tap-bridge.cc', + 'tap-encode-decode.cc', + ]) + headers.source.extend([ + 'tap-bridge.h', + ]) + + obj = bld.create_suid_program('tap-sock-creator') + obj.source = [ + 'tap-sock-creator.cc', + 'tap-encode-decode.cc', + ] + + diff --git a/src/helper/tap-bridge-helper.cc b/src/helper/tap-bridge-helper.cc new file mode 100644 index 000000000..9322681d9 --- /dev/null +++ b/src/helper/tap-bridge-helper.cc @@ -0,0 +1,54 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2009 University of Washington + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "ns3/log.h" +#include "ns3/node.h" +#include "ns3/tap-bridge.h" +#include "tap-bridge-helper.h" + +NS_LOG_COMPONENT_DEFINE ("TapBridgeHelper"); + +namespace ns3 { + +TapBridgeHelper::TapBridgeHelper () +{ + NS_LOG_FUNCTION_NOARGS (); + m_deviceFactory.SetTypeId ("ns3::TapBridge"); +} + +void +TapBridgeHelper::SetDeviceAttribute (std::string n1, const AttributeValue &v1) +{ + NS_LOG_FUNCTION (n1 << &v1); + m_deviceFactory.Set (n1, v1); +} + + Ptr +TapBridgeHelper::Install (Ptr node, Ptr nd) +{ + NS_LOG_FUNCTION (node << nd); + NS_LOG_LOGIC ("Install TapBridge on node " << node->GetId () << " bridging net device " << nd); + + Ptr bridge = m_deviceFactory.Create (); + node->AddDevice (bridge); + bridge->SetBridgedDevice (nd); + + return bridge; +} + +} // namespace ns3 diff --git a/src/helper/tap-bridge-helper.h b/src/helper/tap-bridge-helper.h new file mode 100644 index 000000000..fe55be06d --- /dev/null +++ b/src/helper/tap-bridge-helper.h @@ -0,0 +1,44 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2009 University of Washington + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef TAP_BRIDGE_HELPER_H +#define TAP_BRIDGE_HELPER_H + +#include "net-device-container.h" +#include "ns3/object-factory.h" +#include + +namespace ns3 { + +class Node; +class AttributeValue; + +class TapBridgeHelper +{ +public: + TapBridgeHelper (); + void SetDeviceAttribute (std::string n1, const AttributeValue &v1); + Ptr Install (Ptr node, Ptr nd); +private: + ObjectFactory m_deviceFactory; +}; + +} // namespace ns3 + + +#endif /* TAP_BRIDGE_HELPER_H */ diff --git a/src/helper/wscript b/src/helper/wscript index d1f2ef6a6..bf53247db 100644 --- a/src/helper/wscript +++ b/src/helper/wscript @@ -21,6 +21,7 @@ def build(bld): 'ipv4-interface-container.cc', 'udp-echo-helper.cc', 'bridge-helper.cc', + 'tap-bridge-helper.cc', 'yans-wifi-helper.cc', 'v4ping-helper.cc', ] @@ -46,6 +47,7 @@ def build(bld): 'ipv4-interface-container.h', 'udp-echo-helper.h', 'bridge-helper.h', + 'tap-bridge-helper.h', 'yans-wifi-helper.h', 'v4ping-helper.h', ] diff --git a/src/wscript b/src/wscript index d91e6b8cc..1377096c1 100644 --- a/src/wscript +++ b/src/wscript @@ -23,6 +23,7 @@ all_modules = ( 'devices/csma', 'devices/emu', 'devices/bridge', + 'devices/tap-bridge', 'applications/onoff', 'applications/packet-sink', 'applications/udp-echo', @@ -54,6 +55,7 @@ def configure(conf): conf.sub_config('core') conf.sub_config('simulator') conf.sub_config('devices/emu') + conf.sub_config('devices/tap-bridge') conf.sub_config('contrib') conf.sub_config('internet-stack')