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