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