You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将C中system("ip tuntap add ...")转为基于rtnetlink的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:

  1. Creating the tuntap device via the /dev/net/tun character device (with auto-cleanup enabled)
  2. 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 for SIGKILL, 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 add command without spawning a shell. The set_ip_address function constructs a netlink message to send directly to the kernel.
  • Signal handlers: Catch SIGINT (Ctrl+C) and SIGTERM to 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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:19:06