A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
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
53namespace ns3
54{
55
56NS_LOG_COMPONENT_DEFINE("EmuFdNetDeviceHelper");
57
58#define EMU_MAGIC 65867
59
61{
62 m_deviceName = "undefined";
63 m_hostQdiscBypass = false;
64}
65
66void
68{
69 m_deviceName = deviceName;
70}
71
72void
74{
75 m_hostQdiscBypass = hostQdiscBypass;
76}
77
78std::string
80{
81 return m_deviceName;
82}
83
86{
88 Ptr<FdNetDevice> device = d->GetObject<FdNetDevice>();
89 SetFileDescriptor(device);
90 return device;
91}
92
93void
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
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
220int
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 constexpr 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.