A Discrete-Event Network Simulator
API
emu-fd-net-device-helper.cc
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2012 INRIA, 2012 University of Washington
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 as
6  * published by the Free Software Foundation;
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  */
17 
19 
20 #include "encode-decode.h"
21 
22 #include "ns3/abort.h"
23 #include "ns3/config.h"
24 #include "ns3/fd-net-device.h"
25 #include "ns3/log.h"
26 #include "ns3/names.h"
27 #include "ns3/object-factory.h"
28 #include "ns3/packet.h"
29 #include "ns3/simulator.h"
30 #include "ns3/trace-helper.h"
31 
32 #include <arpa/inet.h>
33 #include <errno.h>
34 #include <iomanip>
35 #include <iostream>
36 #include <limits>
37 #include <memory>
38 #include <net/ethernet.h>
39 #include <net/if.h>
40 #include <netinet/in.h>
41 #include <netpacket/packet.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <string>
45 #include <sys/ioctl.h>
46 #include <sys/socket.h>
47 #include <sys/stat.h>
48 #include <sys/un.h>
49 #include <sys/wait.h>
50 #include <time.h>
51 #include <unistd.h>
52 
53 namespace ns3
54 {
55 
56 NS_LOG_COMPONENT_DEFINE("EmuFdNetDeviceHelper");
57 
58 #define EMU_MAGIC 65867
59 
61 {
62  m_deviceName = "undefined";
63  m_hostQdiscBypass = false;
64 }
65 
66 void
67 EmuFdNetDeviceHelper::SetDeviceName(std::string deviceName)
68 {
69  m_deviceName = deviceName;
70 }
71 
72 void
74 {
75  m_hostQdiscBypass = hostQdiscBypass;
76 }
77 
78 std::string
80 {
81  return m_deviceName;
82 }
83 
86 {
88  Ptr<FdNetDevice> device = d->GetObject<FdNetDevice>();
89  SetFileDescriptor(device);
90  return device;
91 }
92 
93 void
95 {
96  NS_LOG_LOGIC("Creating EMU socket");
97 
98  if (m_deviceName == "undefined")
99  {
100  NS_FATAL_ERROR("EmuFdNetDeviceHelper::SetFileDescriptor (): m_deviceName is not set");
101  }
102 
103  //
104  // Call out to a separate process running as suid root in order to get a raw
105  // socket. We do this to avoid having the entire simulation running as root.
106  //
107  int fd = CreateFileDescriptor();
108  device->SetFileDescriptor(fd);
109 
110  //
111  // Figure out which interface index corresponds to the device name in the corresponding
112  // attribute.
113  //
114  ifreq ifr;
115  bzero(&ifr, sizeof(ifr));
116  strncpy((char*)ifr.ifr_name, m_deviceName.c_str(), IFNAMSIZ - 1);
117 
118  NS_LOG_LOGIC("Getting interface index");
119  int32_t rc = ioctl(fd, SIOCGIFINDEX, &ifr);
120  if (rc == -1)
121  {
122  NS_FATAL_ERROR("EmuFdNetDeviceHelper::SetFileDescriptor (): Can't get interface index");
123  }
124 
125  //
126  // Bind the socket to the interface we just found.
127  //
128  struct sockaddr_ll ll;
129  bzero(&ll, sizeof(ll));
130 
131  ll.sll_family = AF_PACKET;
132  ll.sll_ifindex = ifr.ifr_ifindex;
133  ll.sll_protocol = htons(ETH_P_ALL);
134 
135  NS_LOG_LOGIC("Binding socket to interface");
136 
137  rc = bind(fd, (struct sockaddr*)&ll, sizeof(ll));
138  if (rc == -1)
139  {
141  "EmuFdNetDeviceHelper::SetFileDescriptor (): Can't bind to specified interface");
142  }
143 
144  rc = ioctl(fd, SIOCGIFFLAGS, &ifr);
145  if (rc == -1)
146  {
147  NS_FATAL_ERROR("EmuFdNetDeviceHelper::SetFileDescriptor (): Can't get interface flags");
148  }
149 
150  if (m_hostQdiscBypass)
151  {
152 #ifdef PACKET_QDISC_BYPASS
153  static const int32_t sock_qdisc_bypass = 1;
154  int32_t sock_qdisc_ret = setsockopt(fd,
155  SOL_PACKET,
156  PACKET_QDISC_BYPASS,
157  &sock_qdisc_bypass,
158  sizeof(sock_qdisc_bypass));
159 
160  if (sock_qdisc_ret == -1)
161  {
162  NS_LOG_ERROR("Cannot use the qdisc bypass option");
163  }
164 #else
165  // PACKET_QDISC_BYPASS is defined since Linux 3.14
166  NS_LOG_ERROR("PACKET_QDISC_BYPASS undefined; cannot use the qdisc bypass option");
167 #endif
168  }
169 
170  //
171  // This device only works if the underlying interface is up in promiscuous
172  // mode. We could have turned it on in the socket creator, but the situation
173  // is that we expect these devices to be used in conjunction with virtual
174  // machines with connected host-only (simulated) networks, or in a testbed.
175  // There is a lot of setup and configuration happening outside of this one
176  // issue, and we expect that configuration to include choosing a valid
177  // interface (e.g, "ath1"), ensuring that the device supports promiscuous
178  // mode, and placing it in promiscuous mode. We just make sure of the
179  // end result.
180  //
181  if ((ifr.ifr_flags & IFF_PROMISC) == 0)
182  {
183  NS_FATAL_ERROR("EmuFdNetDeviceHelper::SetFileDescriptor (): "
184  << m_deviceName << " is not in promiscuous mode");
185  }
186 
187  if ((ifr.ifr_flags & IFF_BROADCAST) != IFF_BROADCAST)
188  {
189  // We default m_isBroadcast to true but turn it off here if not
190  // supported, because in the common case, overlying IP code will
191  // assert during configuration time if this is false, before this
192  // method has a chance to set it during runtime
193  device->SetIsBroadcast(false);
194  }
195 
196  if ((ifr.ifr_flags & IFF_MULTICAST) == IFF_MULTICAST)
197  {
198  // This one is OK to enable at runtime
199  device->SetIsMulticast(true);
200  }
201 
202  // Set the MTU of the device to the mtu of the associated network interface
203  ifreq ifr2;
204 
205  bzero(&ifr2, sizeof(ifr2));
206  strcpy(ifr2.ifr_name, m_deviceName.c_str());
207 
208  int32_t mtufd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
209 
210  rc = ioctl(mtufd, SIOCGIFMTU, &ifr2);
211  if (rc == -1)
212  {
213  NS_FATAL_ERROR("FdNetDevice::SetFileDescriptor (): Can't ioctl SIOCGIFMTU");
214  }
215 
216  close(mtufd);
217  device->SetMtu(ifr2.ifr_mtu);
218 }
219 
220 int
222 {
223  NS_LOG_FUNCTION(this);
224 
225  //
226  // We want to create a raw socket for our net device. Unfortunately for us
227  // you have to have root privileges to do that. Instead of running the
228  // entire simulation as root, we decided to make a small program who's whole
229  // reason for being is to run as suid root and create a raw socket. We're
230  // going to fork and exec that program soon, but we need to have a socket
231  // to talk to it with. So we create a local interprocess (Unix) socket
232  // for that purpose.
233  //
234  int sock = socket(PF_UNIX, SOCK_DGRAM, 0);
235  if (sock == -1)
236  {
238  "EmuFdNetDeviceHelper::CreateFileDescriptor(): Unix socket creation error, errno = "
239  << strerror(errno));
240  }
241 
242  //
243  // Bind to that socket and let the kernel allocate an endpoint
244  //
245  struct sockaddr_un un;
246  memset(&un, 0, sizeof(un));
247  un.sun_family = AF_UNIX;
248  int status = bind(sock, (struct sockaddr*)&un, sizeof(sa_family_t));
249  if (status == -1)
250  {
251  NS_FATAL_ERROR("EmuFdNetDeviceHelper::CreateFileDescriptor(): Could not bind(): errno = "
252  << strerror(errno));
253  }
254 
255  NS_LOG_INFO("Created Unix socket");
256  NS_LOG_INFO("sun_family = " << un.sun_family);
257  NS_LOG_INFO("sun_path = " << un.sun_path);
258 
259  //
260  // We have a socket here, but we want to get it there -- to the program we're
261  // going to exec. What we'll do is to do a getsockname and then encode the
262  // resulting address information as a string, and then send the string to the
263  // program as an argument. So we need to get the sock name.
264  //
265  socklen_t len = sizeof(un);
266  status = getsockname(sock, (struct sockaddr*)&un, &len);
267  if (status == -1)
268  {
270  "EmuFdNetDeviceHelper::CreateFileDescriptor(): Could not getsockname(): errno = "
271  << strerror(errno));
272  }
273 
274  //
275  // Now encode that socket name (family and path) as a string of hex digits
276  //
277  std::string path = BufferToString((uint8_t*)&un, len);
278  NS_LOG_INFO("Encoded Unix socket as \"" << path << "\"");
279  //
280  // Fork and exec the process to create our socket. If we're us (the parent)
281  // we wait for the child (the socket creator) to complete and read the
282  // socket it created using the ancillary data mechanism.
283  //
284  // Tom Goff reports the possibility of a deadlock when trying to acquire the
285  // python GIL here. He says that this might be due to trying to access Python
286  // objects after fork() without calling PyOS_AfterFork() to properly reset
287  // Python state (including the GIL). There is no code to cause the problem
288  // here in emu, but this was visible in similar code in tap-bridge.
289  //
290  pid_t pid = ::fork();
291  if (pid == 0)
292  {
293  NS_LOG_DEBUG("Child process");
294 
295  //
296  // build a command line argument from the encoded endpoint string that
297  // the socket creation process will use to figure out how to respond to
298  // the (now) parent process.
299  //
300  std::ostringstream oss;
301  oss << "-p" << path;
302  NS_LOG_INFO("Parameters set to \"" << oss.str() << "\"");
303 
304  //
305  // Execute the socket creation process image.
306  //
307  status = ::execlp(RAW_SOCK_CREATOR,
308  RAW_SOCK_CREATOR, // argv[0] (filename)
309  oss.str().c_str(), // argv[1] (-p<path?
310  (char*)nullptr);
311 
312  //
313  // If the execlp successfully completes, it never returns. If it returns it failed or the
314  // OS is broken. In either case, we bail.
315  //
316  NS_FATAL_ERROR("EmuFdNetDeviceHelper::CreateFileDescriptor(): Back from execlp(), status = "
317  << status << ", errno = " << ::strerror(errno));
318  }
319  else
320  {
321  NS_LOG_DEBUG("Parent process");
322  //
323  // We're the process running the emu net device. We need to wait for the
324  // socket creator process to finish its job.
325  //
326  int st;
327  pid_t waited = waitpid(pid, &st, 0);
328  if (waited == -1)
329  {
330  NS_FATAL_ERROR("EmuFdNetDeviceHelper::CreateFileDescriptor(): waitpid() fails, errno = "
331  << strerror(errno));
332  }
333  NS_ASSERT_MSG(pid == waited, "EmuFdNetDeviceHelper::CreateFileDescriptor(): pid mismatch");
334 
335  //
336  // Check to see if the socket creator exited normally and then take a
337  // look at the exit code. If it bailed, so should we. If it didn't
338  // even exit normally, we bail too.
339  //
340  if (WIFEXITED(st))
341  {
342  int exitStatus = WEXITSTATUS(st);
343  if (exitStatus != 0)
344  {
345  NS_FATAL_ERROR("EmuFdNetDeviceHelper::CreateFileDescriptor(): socket creator "
346  "exited normally with status "
347  << exitStatus);
348  }
349  }
350  else
351  {
353  "EmuFdNetDeviceHelper::CreateFileDescriptor(): socket creator exited abnormally");
354  }
355 
356  //
357  // At this point, the socket creator has run successfully and should
358  // have created our raw socket and sent it back to the socket address
359  // we provided. Our socket should be waiting on the Unix socket. We've
360  // got to do a bunch of grunto work to get at it, though.
361  //
362  // The struct iovec below is part of a scatter-gather list. It describes a
363  // buffer. In this case, it describes a buffer (an integer) that will
364  // get the data that comes back from the socket creator process. It will
365  // be a magic number that we use as a consistency/sanity check.
366  //
367  iovec iov;
368  uint32_t magic;
369  iov.iov_base = &magic;
370  iov.iov_len = sizeof(magic);
371 
372  //
373  // The CMSG macros you'll see below are used to create and access control
374  // messages (which is another name for ancillary data). The ancillary
375  // data is made up of pairs of struct cmsghdr structures and associated
376  // data arrays.
377  //
378  // First, we're going to allocate a buffer on the stack to receive our
379  // data array (that contains the socket). Sometimes you'll see this called
380  // an "ancillary element" but the msghdr uses the control message terminology
381  // so we call it "control."
382  //
383  size_t msg_size = sizeof(int);
384  char control[CMSG_SPACE(msg_size)];
385 
386  //
387  // There is a msghdr that is used to minimize the number of parameters
388  // passed to recvmsg (which we will use to receive our ancillary data).
389  // This structure uses terminology corresponding to control messages, so
390  // you'll see msg_control, which is the pointer to the ancillary data and
391  // controller which is the size of the ancillary data array.
392  //
393  // So, initialize the message header that describes the ancillary/control
394  // data we expect to receive and point it to buffer.
395  //
396  msghdr msg;
397  msg.msg_name = nullptr;
398  msg.msg_namelen = 0;
399  msg.msg_iov = &iov;
400  msg.msg_iovlen = 1;
401  msg.msg_control = control;
402  msg.msg_controllen = sizeof(control);
403  msg.msg_flags = 0;
404 
405  //
406  // Now we can actually receive the interesting bits from the socket
407  // creator process.
408  //
409  ssize_t bytesRead = recvmsg(sock, &msg, 0);
410  if (bytesRead != sizeof(int))
411  {
412  NS_FATAL_ERROR("EmuFdNetDeviceHelper::CreateFileDescriptor(): Wrong byte count from "
413  "socket creator");
414  }
415 
416  //
417  // There may be a number of message headers/ancillary data arrays coming in.
418  // Let's look for the one with a type SCM_RIGHTS which indicates it' the
419  // one we're interested in.
420  //
421  struct cmsghdr* cmsg;
422  for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg))
423  {
424  if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS)
425  {
426  //
427  // This is the type of message we want. Check to see if the magic
428  // number is correct and then pull out the socket we care about if
429  // it matches
430  //
431  if (magic == EMU_MAGIC)
432  {
433  NS_LOG_INFO("Got SCM_RIGHTS with correct magic " << magic);
434  int* rawSocket = (int*)CMSG_DATA(cmsg);
435  NS_LOG_INFO("Got the socket from the socket creator = " << *rawSocket);
436  return *rawSocket;
437  }
438  else
439  {
440  NS_LOG_INFO("Got SCM_RIGHTS, but with bad magic " << magic);
441  }
442  }
443  }
444  NS_FATAL_ERROR("Did not get the raw socket from the socket creator");
445  }
446  NS_FATAL_ERROR("Should be unreachable");
447  return 0; // Silence compiler warning about lack of return value
448 }
449 
450 } // namespace ns3
bool m_hostQdiscBypass
True if request host qdisc bypass.
std::string m_deviceName
The Unix/Linux name of the underlying device (e.g., eth0)
void HostQdiscBypass(bool hostQdiscBypass)
Request host qdisc bypass.
void SetDeviceName(std::string deviceName)
Set the device name of this device.
Ptr< NetDevice > InstallPriv(Ptr< Node > node) const override
This method creates an ns3::FdNetDevice attached to a physical network interface.
virtual void SetFileDescriptor(Ptr< FdNetDevice > device) const
Sets a file descriptor on the FileDescriptorNetDevice.
std::string GetDeviceName()
Get the device name of this device.
virtual int CreateFileDescriptor() const
Call out to a separate process running as suid root in order to get a raw socket.
EmuFdNetDeviceHelper()
Construct a EmuFdNetDeviceHelper.
virtual Ptr< NetDevice > InstallPriv(Ptr< Node > node) const
This method creates an ns3::FdNetDevice and associates it to a node.
a NetDevice to read/write network traffic from/into a file descriptor.
Definition: fd-net-device.h:84
Smart pointer class similar to boost::intrusive_ptr.
Definition: ptr.h:77
#define EMU_MAGIC
#define NS_ASSERT_MSG(condition, message)
At runtime, in debugging builds, if this condition is not true, the program prints the message to out...
Definition: assert.h:86
#define NS_FATAL_ERROR(msg)
Report a fatal error with a message and terminate.
Definition: fatal-error.h:179
#define NS_LOG_ERROR(msg)
Use NS_LOG to output a message of level LOG_ERROR.
Definition: log.h:254
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
Definition: log.h:202
#define NS_LOG_DEBUG(msg)
Use NS_LOG to output a message of level LOG_DEBUG.
Definition: log.h:268
#define NS_LOG_LOGIC(msg)
Use NS_LOG to output a message of level LOG_LOGIC.
Definition: log.h:282
#define NS_LOG_FUNCTION(parameters)
If log level LOG_FUNCTION is enabled, this macro will output all input parameters separated by ",...
#define NS_LOG_INFO(msg)
Use NS_LOG to output a message of level LOG_INFO.
Definition: log.h:275
Every class exported by the ns3 library is enclosed in the ns3 namespace.
std::string BufferToString(uint8_t *buffer, uint32_t len)
Convert a byte buffer to a string containing a hex representation of the buffer.
ns3::StringValue attribute value declarations.