将C中system("ip tuntap add ...")转为基于rtnetlink的C代码
Replace
system("ip tuntap add ...") with RTnetlink & Tuntap IOCTL in C Got it, let's fix this properly. Using system() to create tuntap devices is a quick hack, but it comes with annoying downsides—like leftover devices when your app gets killed abruptly. RTnetlink is the native Linux way to communicate directly with the network stack, and when combined with the right tuntap configuration flags, it solves the cleanup problem entirely.
Why Ditch system()?
- Inefficient & insecure:
system()spawns a shell process, which is unnecessary overhead and a security risk if you're handling untrusted input. - Leftover devices: When your app is force-terminated (e.g.,
SIGKILL), the shell process can't clean up the tuntap device, leaving it hanging around until you manually delete it. - Better control: RTnetlink lets you handle errors gracefully and configure network attributes (like IP addresses, MTU) without relying on external commands.
Full Implementation
This code combines two key parts:
- Creating the tuntap device via the
/dev/net/tuncharacter device (with auto-cleanup enabled) - Configuring the device's IP address using RTnetlink (replacing
ip addr add ...)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <net/if.h> #include <netinet/in.h> #include <linux/netlink.h> #include <linux/rtnetlink.h> #include <signal.h> static int tun_fd = -1; static char tun_name[IFNAMSIZ] = "mytun0"; // Cleanup handler: closes the tun fd to trigger device deletion void cleanup(int sig) { printf("\nCleaning up tuntap device...\n"); if (tun_fd != -1) { close(tun_fd); // Closing this fd auto-deletes the non-persistent device } exit(sig == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } // Create a tuntap device (TUN mode; use IFF_TAP for Ethernet tap) int create_tuntap(const char *name) { int fd = open("/dev/net/tun", O_RDWR); if (fd < 0) { perror("Failed to open /dev/net/tun"); return -1; } struct ifreq ifr = {0}; // IFF_NO_PERSIST is critical: device is deleted when fd is closed ifr.ifr_flags = IFF_TUN | IFF_NO_PERSIST; strncpy(ifr.ifr_name, name, IFNAMSIZ); if (ioctl(fd, TUNSETIFF, &ifr) < 0) { perror("Failed to configure tuntap device"); close(fd); return -1; } // Save the actual device name (in case we used a wildcard like tun%d) strncpy(tun_name, ifr.ifr_name, IFNAMSIZ); printf("Created tuntap device: %s\n", tun_name); return fd; } // Set IP address on the device using RTnetlink int set_ip_address(const char *dev_name, const char *ip_cidr) { int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (sock < 0) { perror("Failed to open netlink socket"); return -1; } struct sockaddr_nl sa = {0}; sa.nl_family = AF_NETLINK; sa.nl_groups = 0; if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) { perror("Failed to bind netlink socket"); close(sock); return -1; } // Parse IP address and prefix length from CIDR notation char ip_str[INET_ADDRSTRLEN]; int prefix; if (sscanf(ip_cidr, "%[^/]/%d", ip_str, &prefix) != 2) { fprintf(stderr, "Invalid IP CIDR format (e.g., 10.0.0.1/24)\n"); close(sock); return -1; } struct in_addr ip_addr; if (inet_pton(AF_INET, ip_str, &ip_addr) <= 0) { perror("Invalid IP address"); close(sock); return -1; } // Calculate netmask from prefix length struct in_addr netmask; netmask.s_addr = htonl(~((1 << (32 - prefix)) - 1)); // Build the netlink message char buf[4096]; struct nlmsghdr *nlh = (struct nlmsghdr *)buf; struct ifaddrmsg *ifa = (struct ifaddrmsg *)(nlh + 1); struct rtattr *rta = (struct rtattr *)(ifa + 1); nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifa)); nlh->nlmsg_type = RTM_NEWADDR; nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL; nlh->nlmsg_seq = 1; nlh->nlmsg_pid = getpid(); ifa->ifa_family = AF_INET; ifa->ifa_prefixlen = prefix; ifa->ifa_flags = IFA_F_PERMANENT; ifa->ifa_scope = RT_SCOPE_UNIVERSE; ifa->ifa_index = if_nametoindex(dev_name); if (ifa->ifa_index == 0) { perror("Failed to get interface index"); close(sock); return -1; } // Add IP address attribute rta->rta_type = IFA_LOCAL; rta->rta_len = RTA_LENGTH(sizeof(ip_addr)); memcpy(RTA_DATA(rta), &ip_addr, sizeof(ip_addr)); nlh->nlmsg_len += rta->rta_len; // Add netmask attribute rta = (struct rtattr *)((char *)rta + rta->rta_len); rta->rta_type = IFA_MASK; rta->rta_len = RTA_LENGTH(sizeof(netmask)); memcpy(RTA_DATA(rta), &netmask, sizeof(netmask)); nlh->nlmsg_len += rta->rta_len; // Send the message to the kernel struct iovec iov = {nlh, nlh->nlmsg_len}; struct msghdr msg = {&sa, sizeof(sa), &iov, 1, NULL, 0, 0}; if (sendmsg(sock, &msg, 0) < 0) { perror("Failed to send netlink message"); close(sock); return -1; } // Check for kernel response errors memset(buf, 0, sizeof(buf)); ssize_t len = recvmsg(sock, &msg, 0); if (len < 0) { perror("Failed to receive netlink response"); close(sock); return -1; } for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) { if (nlh->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlh); fprintf(stderr, "Netlink error: %s\n", strerror(-err->error)); close(sock); return -1; } } close(sock); printf("Set IP %s on %s\n", ip_cidr, dev_name); return 0; } int main() { // Register signal handlers for clean exit signal(SIGINT, cleanup); signal(SIGTERM, cleanup); // Create the tuntap device tun_fd = create_tuntap("mytun0"); if (tun_fd == -1) { cleanup(EXIT_FAILURE); } // Assign IP address using RTnetlink if (set_ip_address(tun_name, "10.0.0.1/24") != 0) { cleanup(EXIT_FAILURE); } // Bring the interface up (using ioctl for simplicity) struct ifreq ifr = {0}; strncpy(ifr.ifr_name, tun_name, IFNAMSIZ); if (ioctl(tun_fd, SIOCGIFFLAGS, &ifr) == 0) { ifr.ifr_flags |= IFF_UP | IFF_RUNNING; if (ioctl(tun_fd, SIOCSIFFLAGS, &ifr) == 0) { printf("Interface %s is up and running\n", tun_name); } else { perror("Failed to bring interface up"); } } else { perror("Failed to get interface flags"); } // Keep the program running for testing printf("Press Ctrl+C to exit...\n"); while (1) { sleep(1); } cleanup(EXIT_SUCCESS); return 0; }
Key Details
- Auto-cleanup with
IFF_NO_PERSIST: This flag ensures the tuntap device is automatically deleted when the tun file descriptor is closed. Even if your app is abruptly killed (except forSIGKILL, which can't be caught), the OS will close all open file descriptors, so the device gets cleaned up. - RTnetlink for IP configuration: Replaces the
ip addr addcommand without spawning a shell. Theset_ip_addressfunction constructs a netlink message to send directly to the kernel. - Signal handlers: Catch
SIGINT(Ctrl+C) andSIGTERMto explicitly close the tun fd and exit cleanly.
Compile & Run
Compile with standard GCC:
gcc -o tuntap_manager tuntap_manager.c
Run with root privileges (network device creation requires admin rights):
sudo ./tuntap_manager
内容的提问来源于stack exchange,提问作者Nathan F.
相关产品推荐
相关产品推荐

