checkpoint tap bridge
This commit is contained in:
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
|
||||
#include "tap-bridge.h"
|
||||
#include "tap-encode-decode.h"
|
||||
|
||||
#include "ns3/node.h"
|
||||
#include "ns3/channel.h"
|
||||
#include "ns3/packet.h"
|
||||
@@ -24,10 +26,26 @@
|
||||
#include "ns3/boolean.h"
|
||||
#include "ns3/simulator.h"
|
||||
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <net/ethernet.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netpacket/packet.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <limits>
|
||||
#include <stdlib.h>
|
||||
|
||||
NS_LOG_COMPONENT_DEFINE ("TapBridge");
|
||||
|
||||
namespace ns3 {
|
||||
|
||||
#define TAP_MAGIC 95549
|
||||
|
||||
NS_OBJECT_ENSURE_REGISTERED (TapBridge);
|
||||
|
||||
TypeId
|
||||
@@ -40,10 +58,11 @@ TapBridge::GetTypeId (void)
|
||||
return tid;
|
||||
}
|
||||
|
||||
|
||||
TapBridge::TapBridge ()
|
||||
: m_node (0),
|
||||
m_ifIndex (0)
|
||||
m_ifIndex (0),
|
||||
m_mtu (0),
|
||||
m_sock (-1)
|
||||
{
|
||||
NS_LOG_FUNCTION_NOARGS ();
|
||||
}
|
||||
@@ -60,6 +79,248 @@ TapBridge::DoDispose ()
|
||||
NetDevice::DoDispose ();
|
||||
}
|
||||
|
||||
void
|
||||
TapBridge::CreateTap (void)
|
||||
{
|
||||
NS_LOG_FUNCTION_NOARGS ();
|
||||
//
|
||||
// We want to create a tap device on the host. Unfortunately for us
|
||||
// you have to have root privileges to do that. Instead of running the
|
||||
// entire simulation as root, we decided to make a small program who's whole
|
||||
// reason for being is to run as suid root and do what it takes to create the
|
||||
// tap. We're going to fork and exec that program soon, but we need to have
|
||||
// a socket to talk to it with. So we create a local interprocess (Unix)
|
||||
// socket for that purpose.
|
||||
//
|
||||
int sock = socket (PF_UNIX, SOCK_DGRAM, 0);
|
||||
if (sock == -1)
|
||||
{
|
||||
NS_FATAL_ERROR ("TapBridge::CreateTap(): Unix socket creation error, errno = " << strerror (errno));
|
||||
}
|
||||
|
||||
//
|
||||
// Bind to that socket and let the kernel allocate an endpoint
|
||||
//
|
||||
struct sockaddr_un un;
|
||||
memset (&un, 0, sizeof (un));
|
||||
un.sun_family = AF_UNIX;
|
||||
int status = bind (sock, (struct sockaddr*)&un, sizeof (sa_family_t));
|
||||
if (status == -1)
|
||||
{
|
||||
NS_FATAL_ERROR ("TapBridge::CreateTap(): Could not bind(): errno = " << strerror (errno));
|
||||
}
|
||||
|
||||
NS_LOG_INFO ("Created Unix socket");
|
||||
NS_LOG_INFO ("sun_family = " << un.sun_family);
|
||||
NS_LOG_INFO ("sun_path = " << un.sun_path);
|
||||
|
||||
//
|
||||
// We have a socket here, but we want to get it there -- to the program we're
|
||||
// going to exec. What we'll do is to do a getsockname and then encode the
|
||||
// resulting address information as a string, and then send the string to the
|
||||
// program as an argument. So we need to get the sock name.
|
||||
//
|
||||
socklen_t len = sizeof (un);
|
||||
status = getsockname (sock, (struct sockaddr*)&un, &len);
|
||||
if (status == -1)
|
||||
{
|
||||
NS_FATAL_ERROR ("TapBridge::CreateTap(): Could not getsockname(): errno = " << strerror (errno));
|
||||
}
|
||||
|
||||
//
|
||||
// Now encode that socket name (family and path) as a string of hex digits
|
||||
//
|
||||
std::string path = TapBufferToString((uint8_t *)&un, len);
|
||||
NS_LOG_INFO ("Encoded Unix socket as \"" << path << "\"");
|
||||
//
|
||||
// Fork and exec the process to create our socket. If we're us (the parent)
|
||||
// we wait for the child (the creator) to complete and read the socket it created using the ancillary data mechanism.
|
||||
//
|
||||
pid_t pid = ::fork ();
|
||||
if (pid == 0)
|
||||
{
|
||||
NS_LOG_DEBUG ("Child process");
|
||||
|
||||
//
|
||||
// build a command line argument from the encoded endpoint string that
|
||||
// the socket creation process will use to figure out how to respond to
|
||||
// the (now) parent process. We're going to have to give this program
|
||||
// quite a bit of information and we use program arguments to do so.
|
||||
//
|
||||
// -d<device-name> The name of the tap device we want to create;
|
||||
// -g<gateway-address> The IP address to use as the default gateway;
|
||||
// -i<IP-address> The IP address to assign to the new tap device;
|
||||
// -m<MAC-address> The MAC-48 address to assign to the new tap device;
|
||||
// -n<network-mask> The network mask to assign to the new tap device;
|
||||
// -p<path> the path to the unix socket described above.
|
||||
//
|
||||
// Example tap-sock-creator -dnewdev -g1.2.3.2 -i1.2.3.1 -m08:00:2e:00:01:23 -n255.255.255.0 -pblah
|
||||
//
|
||||
std::ostringstream oss;
|
||||
oss << "-d" << m_tapDeviceName << " -g" << m_tapGateway << " -i" << m_tapIp << " -m" << m_tapMac
|
||||
<< " -n" << m_tapNetmask << " -p" << path;
|
||||
NS_LOG_INFO ("creator arguments set to \"" << oss.str () << "\"");
|
||||
|
||||
//
|
||||
// Execute the socket creation process image.
|
||||
//
|
||||
status = ::execl (FindCreator ().c_str (), "tap-sock-creator", oss.str ().c_str (), (char *)NULL);
|
||||
|
||||
//
|
||||
// If the execl successfully completes, it never returns. If it returns it failed or the OS is
|
||||
// broken. In either case, we bail.
|
||||
//
|
||||
NS_FATAL_ERROR ("TapBridge::CreateTap(): Back from execl(), errno = " << ::strerror (errno));
|
||||
}
|
||||
else
|
||||
{
|
||||
NS_LOG_DEBUG ("Parent process");
|
||||
//
|
||||
// We're the process running the emu net device. We need to wait for the
|
||||
// socket creator process to finish its job.
|
||||
//
|
||||
int st;
|
||||
pid_t waited = waitpid (pid, &st, 0);
|
||||
if (waited == -1)
|
||||
{
|
||||
NS_FATAL_ERROR ("TapBridge::CreateTap(): waitpid() fails, errno = " << strerror (errno));
|
||||
}
|
||||
NS_ASSERT_MSG (pid == waited, "TapBridge::CreateTap(): pid mismatch");
|
||||
|
||||
//
|
||||
// Check to see if the socket creator exited normally and then take a
|
||||
// look at the exit code. If it bailed, so should we. If it didn't
|
||||
// even exit normally, we bail too.
|
||||
//
|
||||
if (WIFEXITED (st))
|
||||
{
|
||||
int exitStatus = WEXITSTATUS (st);
|
||||
if (exitStatus != 0)
|
||||
{
|
||||
NS_FATAL_ERROR ("TapBridge::CreateTap(): socket creator exited normally with status " << exitStatus);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NS_FATAL_ERROR ("TapBridge::CreateTap(): socket creator exited abnormally");
|
||||
}
|
||||
|
||||
//
|
||||
// At this point, the socket creator has run successfully and should
|
||||
// have created our tap device, initialized it with the information we
|
||||
// passed and sent it back to the socket address we provided. A socket
|
||||
// (fd) we can use to talk to this tap device should be waiting on the
|
||||
// Unix socket we set up to receive information back from the creator
|
||||
// program. We've got to do a bunch of grunt work to get at it, though.
|
||||
//
|
||||
// The struct iovec below is part of a scatter-gather list. It describes a
|
||||
// buffer. In this case, it describes a buffer (an integer) that will
|
||||
// get the data that comes back from the socket creator process. It will
|
||||
// be a magic number that we use as a consistency/sanity check.
|
||||
//
|
||||
struct iovec iov;
|
||||
uint32_t 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 receive 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 recvmsg (which we will use to receive 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 the ancillary/control
|
||||
// data we expect to receive and point it to buffer.
|
||||
//
|
||||
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;
|
||||
|
||||
//
|
||||
// Now we can actually receive the interesting bits from the tap
|
||||
// creator process.
|
||||
//
|
||||
ssize_t bytesRead = recvmsg (sock, &msg, 0);
|
||||
if (bytesRead != sizeof(int))
|
||||
{
|
||||
NS_FATAL_ERROR ("TapBridge::CreateTap(): Wrong byte count from socket creator");
|
||||
}
|
||||
|
||||
//
|
||||
// There may be a number of message headers/ancillary data arrays coming in.
|
||||
// Let's look for the one with a type SCM_RIGHTS which indicates it' the
|
||||
// one we're interested in.
|
||||
//
|
||||
struct cmsghdr *cmsg;
|
||||
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg))
|
||||
{
|
||||
if (cmsg->cmsg_level == SOL_SOCKET &&
|
||||
cmsg->cmsg_type == SCM_RIGHTS)
|
||||
{
|
||||
//
|
||||
// This is the type of message we want. Check to see if the magic
|
||||
// number is correct and then pull out the socket we care about if
|
||||
// it matches
|
||||
//
|
||||
if (magic == TAP_MAGIC)
|
||||
{
|
||||
NS_LOG_INFO ("Got SCM_RIGHTS with correct magic " << magic);
|
||||
int *rawSocket = (int*)CMSG_DATA (cmsg);
|
||||
NS_LOG_INFO ("Got the socket from the socket creator = " << *rawSocket);
|
||||
m_sock = *rawSocket;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
NS_LOG_INFO ("Got SCM_RIGHTS, but with bad magic " << magic);
|
||||
}
|
||||
}
|
||||
}
|
||||
NS_FATAL_ERROR ("Did not get the raw socket from the socket creator");
|
||||
}
|
||||
}
|
||||
|
||||
std::string
|
||||
TapBridge::FindCreator (void)
|
||||
{
|
||||
struct stat st;
|
||||
std::string debug = "./build/debug/src/devices/tap-bridge/tap-sock-creator";
|
||||
std::string optimized = "./build/optimized/src/devices/tap-bridge/tap-sock-creator";
|
||||
|
||||
if (::stat (debug.c_str (), &st) == 0)
|
||||
{
|
||||
return debug;
|
||||
}
|
||||
|
||||
if (::stat (optimized.c_str (), &st) == 0)
|
||||
{
|
||||
return optimized;
|
||||
}
|
||||
|
||||
NS_FATAL_ERROR ("TapBridge::FindCreator(): Couldn't find creator");
|
||||
return ""; // quiet compiler
|
||||
}
|
||||
|
||||
void
|
||||
TapBridge::SetBridgedDevice (Ptr<NetDevice> bridgedDevice)
|
||||
{
|
||||
|
||||
@@ -111,6 +111,20 @@ protected:
|
||||
Address const &src, Address const &dst, PacketType packetType);
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* Call out to a separate process running as suid root in order to get our
|
||||
* tap device created. We do this to avoid having the entire simulation
|
||||
* running as root. If this method returns, we'll have a socket waiting
|
||||
* for us in m_sock that we can use to talk to the tap device.
|
||||
*/
|
||||
void CreateTap (void);
|
||||
|
||||
/**
|
||||
* Figure out where the tap creation program lives on the system.
|
||||
*/
|
||||
std::string FindCreator (void);
|
||||
|
||||
NetDevice::ReceiveCallback m_rxCallback;
|
||||
NetDevice::PromiscReceiveCallback m_promiscRxCallback;
|
||||
|
||||
@@ -120,6 +134,14 @@ private:
|
||||
uint32_t m_ifIndex;
|
||||
uint16_t m_mtu;
|
||||
|
||||
int32_t m_sock;
|
||||
|
||||
std::string m_tapDeviceName;
|
||||
std::string m_tapGateway;
|
||||
std::string m_tapIp;
|
||||
std::string m_tapMac;
|
||||
std::string m_tapNetmask;
|
||||
|
||||
Ptr<NetDevice> m_bridgedDevice;
|
||||
};
|
||||
|
||||
|
||||
@@ -277,7 +277,7 @@ SendSocket (const char *path, int fd)
|
||||
}
|
||||
|
||||
static int
|
||||
CreateTap (const char *dev, const char *gw, const char *ip, const char *netmask, const char *mac)
|
||||
CreateTap (const char *dev, const char *gw, const char *ip, const char *mac, const char *netmask)
|
||||
{
|
||||
//
|
||||
// Creation and management of Tap devices is done via the tun device
|
||||
@@ -343,7 +343,7 @@ CreateTap (const char *dev, const char *gw, const char *ip, const char *netmask,
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
int c;
|
||||
char *dev = NULL;
|
||||
char *dev = (char *)"";
|
||||
char *gw = NULL;
|
||||
char *ip = NULL;
|
||||
char *mac = NULL;
|
||||
@@ -383,9 +383,9 @@ main (int argc, char *argv[])
|
||||
//
|
||||
// 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
|
||||
// will use. If this name is provided we use it. If not we let the system
|
||||
// create the device for us. This name is given in dev
|
||||
//
|
||||
ABORT_IF (dev == NULL, "Device Name is a required argument", 0);
|
||||
LOG ("Provided Device Name is \"" << dev << "\"");
|
||||
|
||||
//
|
||||
@@ -394,7 +394,7 @@ main (int argc, char *argv[])
|
||||
// gw.
|
||||
//
|
||||
ABORT_IF (gw == NULL, "Gateway Address is a required argument", 0);
|
||||
LOG ("Provided Gateway Address is \"" << dev << "\"");
|
||||
LOG ("Provided Gateway Address is \"" << gw << "\"");
|
||||
|
||||
//
|
||||
// We have got to be able to assign an IP address to the tap device we are
|
||||
@@ -447,6 +447,14 @@ main (int argc, char *argv[])
|
||||
int sock = CreateTap (dev, gw, ip, mac, netmask);
|
||||
ABORT_IF (sock == -1, "main(): Unable to create tap socket", 1);
|
||||
|
||||
#if 1
|
||||
for (;;)
|
||||
{
|
||||
LOG ("z");
|
||||
sleep (1);
|
||||
}
|
||||
#endif
|
||||
|
||||
//
|
||||
// Send the socket back to the tap net device so it can go about its business
|
||||
//
|
||||
@@ -32,9 +32,9 @@ def build(bld):
|
||||
'tap-bridge.h',
|
||||
])
|
||||
|
||||
obj = bld.create_suid_program('tap-sock-creator')
|
||||
obj = bld.create_suid_program('tap-creator')
|
||||
obj.source = [
|
||||
'tap-sock-creator.cc',
|
||||
'tap-creator.cc',
|
||||
'tap-encode-decode.cc',
|
||||
]
|
||||
|
||||
|
||||
@@ -45,12 +45,11 @@ def set_options(opt):
|
||||
" --run and --shell; moreover, only works in some"
|
||||
" specific platforms, such as Linux and Solaris)"),
|
||||
action="store_true", dest='enable_rpath', default=False)
|
||||
|
||||
|
||||
opt.add_option('--enable-modules',
|
||||
help=("Build only these modules (and dependencies)"),
|
||||
dest='enable_modules')
|
||||
|
||||
|
||||
def configure(conf):
|
||||
conf.sub_config('core')
|
||||
conf.sub_config('simulator')
|
||||
|
||||
12
wscript
12
wscript
@@ -133,7 +133,10 @@ def set_options(opt):
|
||||
help=('Run a shell with an environment suitably modified to run locally built programs'),
|
||||
action="store_true", default=False,
|
||||
dest='shell')
|
||||
|
||||
opt.add_option('--enable-sudo',
|
||||
help=('Use sudo to setup suid bits on ns3 executables.'),
|
||||
dest='enable_sudo', action='store_true',
|
||||
default=False)
|
||||
opt.add_option('--regression',
|
||||
help=("Enable regression testing; only used for the 'check' target"),
|
||||
default=False, dest='regression', action="store_true")
|
||||
@@ -144,10 +147,6 @@ def set_options(opt):
|
||||
help=('For regression testing, only run/generate the indicated regression tests, '
|
||||
'specified as a comma separated list of test names'),
|
||||
dest='regression_tests', type="string")
|
||||
opt.add_option('--enable-sudo',
|
||||
help=('Use sudo to setup suid bits on ns3 executables.'),
|
||||
dest='enable_sudo', action='store_true',
|
||||
default=False)
|
||||
opt.add_option('--with-regression-traces',
|
||||
help=('Path to the regression reference traces directory'),
|
||||
default=None,
|
||||
@@ -280,7 +279,7 @@ class SuidBuildTask(Task.TaskBase):
|
||||
def __init__(self, bld, program):
|
||||
self.m_display = 'build-suid'
|
||||
self.__program = program
|
||||
self.__env = bld.env ()
|
||||
self.__env = bld.env.copy ()
|
||||
super(SuidBuildTask, self).__init__()
|
||||
|
||||
def run(self):
|
||||
@@ -305,6 +304,7 @@ def create_suid_program(bld, name):
|
||||
program.module_deps = list()
|
||||
program.name = name
|
||||
program.target = name
|
||||
|
||||
if bld.env['SUDO'] and Options.options.enable_sudo:
|
||||
SuidBuildTask(bld, program)
|
||||
return program
|
||||
|
||||
Reference in New Issue
Block a user