A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
tap-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("TapFdNetDeviceHelper");
46
47#define TAP_MAGIC 95549
48
59
60void
62{
63 m_modePi = modePi;
64}
65
66void
71
72void
77
78void
83
84void
89
90void
95
98{
100 Ptr<FdNetDevice> device = d->GetObject<FdNetDevice>();
101
102 //
103 // We need to explicitly set the encapsulation mode for the traffic
104 // traversing the TAP device, so the FdNetDevice is able to know
105 // how to treat the traffic in a way that in compatible with the
106 // TAP device.
107 //
108 if (m_modePi)
109 {
110 device->SetEncapsulationMode(FdNetDevice::DIXPI);
111 }
112
113 SetFileDescriptor(device);
114 return device;
115}
116
117void
119{
120 NS_LOG_LOGIC("Creating TAP device");
121
122 //
123 // Call out to a separate process running as suid root in order to create a
124 // TAP device. We do this to avoid having the entire simulation running as root.
125 //
126 int fd = CreateFileDescriptor();
127 device->SetFileDescriptor(fd);
128}
129
130int
132{
133 NS_LOG_FUNCTION(this);
134
135 //
136 // We're going to fork and exec that program soon, but first we need to have
137 // a socket to talk to it with. So we create a local interprocess (Unix)
138 // socket for that purpose.
139 //
140 int sock = socket(PF_UNIX, SOCK_DGRAM, 0);
142 sock == -1,
143 "TapFdNetDeviceHelper::CreateFileDescriptor(): Unix socket creation error, errno = "
144 << strerror(errno));
145
146 //
147 // Bind to that socket and let the kernel allocate an endpoint
148 //
149 struct sockaddr_un un;
150 memset(&un, 0, sizeof(un));
151 un.sun_family = AF_UNIX;
152 int status = bind(sock, (struct sockaddr*)&un, sizeof(sa_family_t));
153 NS_ABORT_MSG_IF(status == -1,
154 "TapFdNetDeviceHelper::CreateFileDescriptor(): Could not bind(): errno = "
155 << strerror(errno));
156 NS_LOG_INFO("Created Unix socket");
157 NS_LOG_INFO("sun_family = " << un.sun_family);
158 NS_LOG_INFO("sun_path = " << un.sun_path);
159
160 //
161 // We have a socket here, but we want to get it there -- to the program we're
162 // going to exec. What we'll do is to do a getsockname and then encode the
163 // resulting address information as a string, and then send the string to the
164 // program as an argument. So we need to get the sock name.
165 //
166 socklen_t len = sizeof(un);
167 status = getsockname(sock, (struct sockaddr*)&un, &len);
169 status == -1,
170 "TapFdNetDeviceHelper::CreateFileDescriptor(): Could not getsockname(): errno = "
171 << strerror(errno));
172
173 //
174 // Now encode that socket name (family and path) as a string of hex digits
175 //
176 std::string path = BufferToString((uint8_t*)&un, len);
177 NS_LOG_INFO("Encoded Unix socket as \"" << path << "\"");
178
179 //
180 // Fork and exec the process to create our socket. If we're us (the parent)
181 // we wait for the child (the creator) to complete and read the socket it
182 // created and passed back using the ancillary data mechanism.
183 //
184 pid_t pid = ::fork();
185 if (pid == 0)
186 {
187 NS_LOG_DEBUG("Child process");
188
189 //
190 // build a command line argument from the encoded endpoint string that
191 // the socket creation process will use to figure out how to respond to
192 // the (now) parent process. We're going to have to give this program
193 // quite a bit of information.
194 //
195 // -d<device-name> The name of the tap device we want to create;
196 // -m<MAC-address> The MAC-48 address to assign to the new tap device;
197 // -i<IPv4-address> The IP v4 address to assign to the new tap device;
198 // -I<IPv6-address> The IP v6 address to assign to the new tap device;
199 // -n<network-IPv4-mask> The network IPv4 mask to assign to the new tap device;
200 // -N<network-IPv6-mask> The network IPv6 mask to assign to the new tap device;
201 // -t Set the IFF_TAP flag
202 // -h Set the IFF_NO_PI flag
203 // -p<path> the path to the unix socket described above.
204 //
205 // Example tap-creator -dnewdev -i1.2.3.1 -m08:00:2e:00:01:23 -n255.255.255.0 -t -h -pblah
206 //
207
208 //
209 // The device-name is something we may want the system to make up in
210 // every case. We also rely on it being configured via an Attribute
211 // through the helper. By default, it is set to the empty string
212 // which tells the system to make up a device name such as "tap123".
213 //
214 std::ostringstream ossDeviceName;
215 if (!m_deviceName.empty())
216 {
217 ossDeviceName << "-d" << m_deviceName;
218 }
219
220 std::ostringstream ossMac;
221 ossMac << "-m" << m_tapMac;
222
223 std::ostringstream ossIp4;
225 {
226 ossIp4 << "-i" << m_tapIp4;
227 }
228
229 std::ostringstream ossIp6;
231 {
232 ossIp6 << "-I" << m_tapIp6;
233 }
234
235 std::ostringstream ossNetmask4;
237 {
238 ossNetmask4 << "-n" << m_tapMask4;
239 }
240
241 std::ostringstream ossPrefix6;
242 ossPrefix6 << "-P" << m_tapPrefix6;
243
244 std::ostringstream ossMode;
245 ossMode << "-t";
246
247 std::ostringstream ossPI;
248 if (m_modePi)
249 {
250 ossPI << "-h";
251 }
252
253 std::ostringstream ossPath;
254 ossPath << "-p" << path;
255
256 //
257 // Execute the socket creation process image.
258 //
259 status = ::execlp(TAP_DEV_CREATOR,
260 TAP_DEV_CREATOR, // argv[0] (filename)
261 ossDeviceName.str().c_str(), // argv[1] (-d<device name>)
262 ossMac.str().c_str(), // argv[2] (-m<MAC address>
263 ossIp4.str().c_str(), // argv[3] (-i<IP v4 address>)
264 ossIp6.str().c_str(), // argv[4] (-I<IP v6 address>)
265 ossNetmask4.str().c_str(), // argv[5] (-n<IP v4 net mask>)
266 ossPrefix6.str().c_str(), // argv[6] (-P<IP v6 prefix>)
267 ossMode.str().c_str(), // argv[7] (-t <tap>)
268 ossPI.str().c_str(), // argv[8] (-h <pi>)
269 ossPath.str().c_str(), // argv[9] (-p<path>)
270 (char*)nullptr);
271
272 //
273 // If the execlp successfully completes, it never returns. If it returns it failed or the
274 // OS is broken. In either case, we bail.
275 //
276 NS_FATAL_ERROR("TapFdNetDeviceHelper::CreateFileDescriptor(): Back from execlp(), status = "
277 << status << ", errno = " << ::strerror(errno));
278 }
279 else
280 {
281 NS_LOG_DEBUG("Parent process");
282 //
283 // We're the process running the emu net device. We need to wait for the
284 // socket creator process to finish its job.
285 //
286 int st;
287 pid_t waited = waitpid(pid, &st, 0);
288 NS_ABORT_MSG_IF(waited == -1,
289 "TapFdNetDeviceHelper::CreateFileDescriptor(): waitpid() fails, errno = "
290 << strerror(errno));
291 NS_ASSERT_MSG(pid == waited, "TapFdNetDeviceHelper::CreateFileDescriptor(): pid mismatch");
292
293 //
294 // Check to see if the socket creator exited normally and then take a
295 // look at the exit code. If it bailed, so should we. If it didn't
296 // even exit normally, we bail too.
297 //
298 if (WIFEXITED(st))
299 {
300 int exitStatus = WEXITSTATUS(st);
301 NS_ABORT_MSG_IF(exitStatus != 0,
302 "TapFdNetDeviceHelper::CreateFileDescriptor(): socket creator exited "
303 "normally with status "
304 << exitStatus);
305 }
306 else
307 {
309 "TapFdNetDeviceHelper::CreateFileDescriptor(): socket creator exited abnormally");
310 }
311
312 //
313 // At this point, the socket creator has run successfully and should
314 // have created our tap device, initialized it with the information we
315 // passed and sent it back to the socket address we provided. A socket
316 // (fd) we can use to talk to this tap device should be waiting on the
317 // Unix socket we set up to receive information back from the creator
318 // program. We've got to do a bunch of grunt work to get at it, though.
319 //
320 // The struct iovec below is part of a scatter-gather list. It describes a
321 // buffer. In this case, it describes a buffer (an integer) that will
322 // get the data that comes back from the socket creator process. It will
323 // be a magic number that we use as a consistency/sanity check.
324 //
325 struct iovec iov;
326 uint32_t magic;
327 iov.iov_base = &magic;
328 iov.iov_len = sizeof(magic);
329
330 //
331 // The CMSG macros you'll see below are used to create and access control
332 // messages (which is another name for ancillary data). The ancillary
333 // data is made up of pairs of struct cmsghdr structures and associated
334 // data arrays.
335 //
336 // First, we're going to allocate a buffer on the stack to receive our
337 // data array (that contains the socket). Sometimes you'll see this called
338 // an "ancillary element" but the msghdr uses the control message termimology
339 // so we call it "control."
340 //
341 constexpr size_t msg_size = sizeof(int);
342 char control[CMSG_SPACE(msg_size)];
343
344 //
345 // There is a msghdr that is used to minimize the number of parameters
346 // passed to recvmsg (which we will use to receive our ancillary data).
347 // This structure uses terminology corresponding to control messages, so
348 // you'll see msg_control, which is the pointer to the ancillary data and
349 // controllen which is the size of the ancillary data array.
350 //
351 // So, initialize the message header that describes the ancillary/control
352 // data we expect to receive and point it to buffer.
353 //
354 struct msghdr msg;
355 msg.msg_name = nullptr;
356 msg.msg_namelen = 0;
357 msg.msg_iov = &iov;
358 msg.msg_iovlen = 1;
359 msg.msg_control = control;
360 msg.msg_controllen = sizeof(control);
361 msg.msg_flags = 0;
362
363 //
364 // Now we can actually receive the interesting bits from the tap
365 // creator process. Lots of pain to get four bytes.
366 //
367 ssize_t bytesRead = recvmsg(sock, &msg, 0);
369 bytesRead != sizeof(int),
370 "TapFdNetDeviceHelper::CreateFileDescriptor(): Wrong byte count from socket creator");
371
372 //
373 // There may be a number of message headers/ancillary data arrays coming in.
374 // Let's look for the one with a type SCM_RIGHTS which indicates it's the
375 // one we're interested in.
376 //
377 struct cmsghdr* cmsg;
378 for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg))
379 {
380 if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS)
381 {
382 //
383 // This is the type of message we want. Check to see if the magic
384 // number is correct and then pull out the socket we care about if
385 // it matches
386 //
387 if (magic == TAP_MAGIC)
388 {
389 NS_LOG_INFO("Got SCM_RIGHTS with correct magic " << magic);
390 int* rawSocket = (int*)CMSG_DATA(cmsg);
391 NS_LOG_INFO("Got the socket from the socket creator = " << *rawSocket);
392 return *rawSocket;
393 }
394 else
395 {
396 NS_LOG_INFO("Got SCM_RIGHTS, but with bad magic " << magic);
397 }
398 }
399 }
400 NS_FATAL_ERROR("Did not get the raw socket from the socket creator");
401 }
402 NS_FATAL_ERROR("Should be unreachable");
403 return 0; // Silence compiler warning about lack of return value
404}
405
406} // namespace ns3
std::string m_deviceName
The Unix/Linux name of the underlying device (e.g., eth0)
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.
@ DIXPI
When using TAP devices, if flag IFF_NO_PI is not set on the device, IP packets will have an extra hea...
Ipv4 addresses are stored in host order in this class.
static Ipv4Address GetZero()
a class to represent an Ipv4 address mask
static Ipv4Mask GetZero()
Describes an IPv6 address.
static Ipv6Address GetZero()
Get the 0 (::) Ipv6Address.
an EUI-48 address
static Mac48Address Allocate()
Allocate a new Mac48Address.
Smart pointer class similar to boost::intrusive_ptr.
int CreateFileDescriptor() const override
Call out to a separate process running as suid root in order to create a TAP device and obtain the fi...
void SetTapMacAddress(Mac48Address mac)
Set the MAC address for the TAP device.
bool m_modePi
The TAP device flag IFF_NO_PI.
void SetTapIpv6Prefix(int prefix)
Set the IPv6 network mask for the TAP device.
Ipv6Address m_tapIp6
The IPv6 address for the TAP device.
Ptr< NetDevice > InstallPriv(Ptr< Node > node) const override
This method creates an ns3::FdNetDevice attached to a virtual TAP network interface.
void SetTapIpv4Mask(Ipv4Mask mask)
Set the IPv4 network mask for the TAP device.
Ipv4Mask m_tapMask4
The network mask IPv4 for the TAP device.
Ipv4Address m_tapIp4
The IPv4 address for the TAP device.
TapFdNetDeviceHelper()
Construct a TapFdNetDeviceHelper.
Mac48Address m_tapMac
The TAP device MAC address.
int m_tapPrefix6
The network prefix IPv6 for the TAP device.
void SetTapIpv4Address(Ipv4Address address)
Set the device IPv4 address.
void SetModePi(bool pi)
Set flag IFF_NO_PI on the device.
void SetTapIpv6Address(Ipv6Address address)
Set the device IPv6 address.
void SetFileDescriptor(Ptr< FdNetDevice > device) const override
Sets a file descriptor on the FileDescriptorNetDevice.
#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_ABORT_MSG_IF(cond, msg)
Abnormal program termination if a condition is true, with a message.
Definition abort.h:97
#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.
#define TAP_MAGIC