在C语言中配置TUN接口IP地址时遇ioctl(SIOCSIFADDR)无效参数错误求助
问题描述
尝试用C语言创建TUN隧道,但在设置IP地址时遇到问题:调用带SIOCSIFADDR参数的ioctl函数时返回“invalid argument”错误。想知道如果该函数不能用于TUN接口,该如何为其配置IP地址,同时不太理解ioctl的工作机制,相关文档对TUN接口的帮助有限。
原代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <net/if.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <linux/if_tun.h> #include <linux/if.h> #include <fcntl.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #define IP_DEST "127.0.0.1" #define IP_SOURCE "127.0.0.1" int tun_alloc(char* dev) { //dev will store the name of the tun created struct ifreq ifr; int fd, err; char *clonedev = "/dev/net/tun"; //open the clone device if( (fd = open(clonedev, O_RDWR)) < 0 ) { printf("Error opening directory"); return fd; } memset(&ifr, 0, sizeof(ifr)); ifr.ifr_flags = IFF_TUN; if (*dev) { strncpy(ifr.ifr_name, dev, IFNAMSIZ); } //Create the TUN if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ) { close(fd); printf("Error creating the tun"); return err; } strcpy(dev, ifr.ifr_name); return fd; } int main(int argc, char* argv[]) { //Creating the TUN interface char tun_name[IFNAMSIZ]; strncpy(tun_name, "tun3", IFNAMSIZ); int tunfd = tun_alloc(tun_name); if (tunfd < 0) { perror("tun_create"); return 1; } printf("TUN interface %s created\n", tun_name); //Setting its IP Adress struct ifreq ifr; struct sockaddr_in addr; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, tun_name, IFNAMSIZ); addr.sin_family = AF_INET; if(inet_pton(AF_INET,IP_DEST,&(addr.sin_addr))<0){ fprintf(stderr,"ERROR with the IP address"); return 1; }; memcpy(&(ifr.ifr_addr), &addr, sizeof (struct sockaddr)); if (ioctl(tunfd, SIOCSIFADDR, &ifr) < 0) { perror("ioctl"); exit(1); } printf("TUN interface %s set IP address to %s\n", tun_name, IP_DEST); ifr.ifr_flags |= IFF_UP | IFF_RUNNING; if(ioctl(tunfd, SIOCSIFFLAGS, &ifr)<0){ perror("ioctl"); exit(1); }; printf("TUN running"); return 0; }
问题原因与解决方法
错误根源
你不能用TUN设备的文件描述符(tunfd)来调用SIOCSIFADDR、SIOCSIFFLAGS这类网络接口配置命令。ioctl是通用系统调用,不同文件描述符对应不同设备驱动,支持的命令集合完全不同:
tunfd关联/dev/net/tun设备,仅支持TUN专属命令(比如TUNSETIFF、TUNSETOWNER)- 网络接口配置类命令(
SIOCSIFADDR、SIOCSIFFLAGS等)属于内核网络协议栈范畴,必须用通用网络套接字描述符触发。
正确配置步骤
- 创建AF_INET类型的套接字(无需绑定端口,用
SOCK_DGRAM类型即可) - 用这个套接字的fd执行
SIOCSIFADDR、SIOCSIFFLAGS等配置命令 - 配置完成后可关闭该套接字,不影响已创建的TUN设备
修改后的代码(main函数部分)
int main(int argc, char* argv[]) { //Creating the TUN interface char tun_name[IFNAMSIZ]; strncpy(tun_name, "tun3", IFNAMSIZ); int tunfd = tun_alloc(tun_name); if (tunfd < 0) { perror("tun_create"); return 1; } printf("TUN interface %s created\n", tun_name); // 创建用于配置网络接口的套接字 int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { perror("socket"); close(tunfd); return 1; } //Setting its IP Address struct ifreq ifr; struct sockaddr_in addr; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, tun_name, IFNAMSIZ); addr.sin_family = AF_INET; if(inet_pton(AF_INET, IP_DEST, &(addr.sin_addr)) < 0){ fprintf(stderr,"ERROR with the IP address"); close(sockfd); close(tunfd); return 1; }; memcpy(&(ifr.ifr_addr), &addr, sizeof(struct sockaddr)); // 使用sockfd而非tunfd调用ioctl if (ioctl(sockfd, SIOCSIFADDR, &ifr) < 0) { perror("ioctl set addr"); close(sockfd); close(tunfd); exit(1); } printf("TUN interface %s set IP address to %s\n", tun_name, IP_DEST); // 设置接口UP状态(先获取当前flags再修改) if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) { perror("ioctl get flags"); close(sockfd); close(tunfd); exit(1); } ifr.ifr_flags |= IFF_UP | IFF_RUNNING; if(ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0){ perror("ioctl set flags"); close(sockfd); close(tunfd); exit(1); }; printf("TUN running"); // 关闭配置用的套接字 close(sockfd); // tunfd保持打开,用于后续读写数据包 // close(tunfd); return 0; }
补充说明
ioctl核心逻辑:通过fd指定目标设备/子系统,第二个参数指定执行命令,第三个参数是命令参数(通常为结构体),不同子系统命令不能跨fd混用。- TUN设备创建后只是虚拟接口的“数据通道”,接口的网络配置(IP、状态、路由等)需通过网络协议栈接口完成,这就是必须用普通套接字fd的原因。
内容的提问来源于stack exchange,提问作者Antoine Bourgeois
相关产品推荐
相关产品推荐

