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