使用if_nameindex获取网口索引和名称

Linux网口索引与名称映射
本文介绍Linux系统中网络接口的索引与名称之间的转换方法,包括如何使用if_nametoindex和if_indextoname函数进行转换,以及获取所有网络接口列表的方法。

在linux中,每个网口都有唯一的名字和正值索引,索引号从1开始(并不是0),linux提供了4个接口用于索引和名称之间的映射,定义如下:

#include <net/if.h>
unsigned int if_nametoindex(const char *ifname);
作用:网口名称映射成网口索引
返回值:成功返回索引,失败返回0
char *if_indextoname(unsigned int ifindex, char *ifname);
作用:网口索引映射成名称
返回值:成功返回名字内存的指针,失败返回NULL
struct if_nameindex *if_nameindex(void);
作用:返回所有的名称索引结构体列表
返回值:成功返回非空指针,失败返回NULL
voif if_freenamrindex(struct if_nameindex *ptr);
作用:释放为了记录struct if_nameindex而创建的内存

例子:

#include <net/if.h>
#include <stdio.h>

int 
main(int argc, char **argv)
{
	char *if_name;
	int index;
	struct if_nameindex *if_list, *if_one;
	
	if_list = if_nameindex();

	index = 0;
	for ( ; ((struct if_nameindex *)(if_list + index))->if_index != 0; index++) {
		if_one = (struct if_nameindex *)(if_list + index);
		printf("%d : %s\n", if_one->if_index, if_one->if_name);
	}

	if_freenameindex(if_list);
	
	return 0;
}

执行结果,可以看到索引确实从1开始,并且顺序和ip addr show的结果一致:

[root@localhost if_nametoindex]# ./a.out
1 : lo
2 : ens33
3 : ens160
4 : ens192
<think>我们需要使用f-stack(一个基于DPDK的用户态协议栈)来实现类似原始套接字的功能来收取网口上的报文。f-stack提供了socket接口,可以像普通socket一样使用,但性能更高。 步骤: 1. 初始化f-stack环境。 2. 创建一个原始套接字(SOCK_RAW)。 3. 绑定到指定的网口(通过IP地址或接口索引)。 4. 设置混杂模式(如果需要捕获所有报文)。 5. 循环接收数据并处理。 注意:f-stack的原始套接字可以接收所有经过该网口的IP报文(包括二层头,取决于设置)。 以下是示例代码: ```c #include <ff_api.h> #include <ff_config.h> #include <arpa/inet.h> #include <net/if.h> #include <netinet/if_ether.h> #include <netinet/ip.h> #include <stdio.h> #define INTERFACE_NAME "eth0" // 网口名称 #define BUFFER_SIZE 2048 // 接收缓冲区大小 int main() { // 初始化f-stack ff_init(0, NULL); // 0表示使用默认配置,NULL表示配置文件路径为默认 // 创建原始套接字,指定协议为ETH_P_ALL(接收所有协议) int sock = ff_socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (sock < 0) { perror("ff_socket"); return -1; } // 获取接口索引 struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, INTERFACE_NAME, IFNAMSIZ - 1); if (ff_ioctl(sock, SIOCGIFINDEX, &ifr) < 0) { perror("ff_ioctl: SIOCGIFINDEX"); ff_close(sock); return -1; } int ifindex = ifr.ifr_ifindex; // 绑定到指定接口 struct sockaddr_ll sll; memset(&sll, 0, sizeof(sll)); sll.sll_family = AF_PACKET; sll.sll_ifindex = ifindex; sll.sll_protocol = htons(ETH_P_ALL); if (ff_bind(sock, (struct sockaddr*)&sll, sizeof(sll)) < 0) { perror("ff_bind"); ff_close(sock); return -1; } // 设置混杂模式(可选) if (ff_ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) { perror("ff_ioctl: SIOCGIFFLAGS"); ff_close(sock); return -1; } ifr.ifr_flags |= IFF_PROMISC; if (ff_ioctl(sock, SIOCSIFFLAGS, &ifr) < 0) { perror("ff_ioctl: SIOCSIFFLAGS"); ff_close(sock); return -1; } printf("开始捕获 %s 上的报文...\n", INTERFACE_NAME); char buffer[BUFFER_SIZE]; while (1) { // 接收报文 int n = ff_recv(sock, buffer, BUFFER_SIZE, 0); if (n < 0) { perror("ff_recv"); continue; } // 打印接收到的报文长度 printf("收到 %d 字节\n", n); // 这里可以解析报文,例如二层头、IP头等 // 示例:解析以太网头 struct ethhdr *eth = (struct ethhdr *)buffer; printf("源MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", eth->h_source[0], eth->h_source[1], eth->h_source[2], eth->h_source[3], eth->h_source[4], eth->h_source[5]); printf("目的MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", eth->h_dest[0], eth->h_dest[1], eth->h_dest[2], eth->h_dest[3], eth->h_dest[4], eth->h_dest[5]); // 如果是以太网类型为IP,则解析IP头 if (ntohs(eth->h_proto) == ETH_P_IP) { struct iphdr *iph = (struct iphdr *)(buffer + sizeof(struct ethhdr)); printf("IP 协议: %d, 源IP: %s, 目的IP: %s\n", iph->protocol, inet_ntoa(*(struct in_addr*)&iph->saddr), inet_ntoa(*(struct in_addr*)&iph->daddr)); } // 注意:实际应用中可能需要更健壮的解析(校验长度等) } ff_close(sock); return 0; } ``` **编译与运行:** 1. 确保已经安装并配置了f-stack环境,包括设置环境变量`FF_PATH`等。 2. 编译时需要链接f-stack的库,例如: ```bash gcc -o fstack_raw_socket fstack_raw_socket.c -I${FF_PATH}/lib -L${FF_PATH}/lib -lff ``` 3. 运行前需要配置f-stack的配置文件(通常位于`/etc/f-stack.conf`),确保指定了正确的网口参数。 4. 运行程序: ```bash sudo ./fstack_raw_socket ``` **注意事项:** - f-stack需要DPDK环境,因此运行前需要绑定网卡到DPDK驱动(如vfio-pci)。 - 程序需要以root权限运行。 - 该程序会捕获指定网口上的所有报文(包括二层头),并打印基本信息。 - 在实际使用中,可能需要根据业务需求解析更多协议或过滤特定报文。 §§相关问题§§ 1. 如何在f-stack中实现只捕获特定协议(如TCP)的报文? 2. f-stack环境下如何同时处理多个网口的报文? 3. 如何将f-stack原始套接字捕获的报文保存为pcap文件? 4. 在f-stack中如何处理VLAN报文? 5. f-stack与普通Linux内核协议栈在原始套接字处理上有哪些性能差异? </think>以下是一个使用F-Stack框架实现类似原始套接字抓取网口报文的完整C++代码示例。F-Stack是基于DPDK的高性能用户态网络协议栈,提供类BSD Socket API接口: ```cpp #include <ff_api.h> #include <ff_config.h> #include <arpa/inet.h> #include <netinet/if_ether.h> #include <netinet/ip.h> #include <netinet/tcp.h> #include <netinet/udp.h> #include <cstdio> #include <cstring> #include <csignal> #include <unistd.h> #define INTERFACE "eth4" // 监听的网口名称 #define BUFFER_SIZE 2048 // 接收缓冲区大小 static volatile bool running = true; // 信号处理函数 void signal_handler(int signum) { running = false; printf("\n收到信号 %d,程序退出中...\n", signum); } // 打印以太网头部信息 void print_eth_header(const struct ether_header* eth) { printf("以太网 | 源MAC: %02X:%02X:%02X:%02X:%02X:%02X → ", eth->ether_shost[0], eth->ether_shost[1], eth->ether_shost[2], eth->ether_shost[3], eth->ether_shost[4], eth->ether_shost[5]); printf("目的MAC: %02X:%02X:%02X:%02X:%02X:%02X | ", eth->ether_dhost[0], eth->ether_dhost[1], eth->ether_dhost[2], eth->ether_dhost[3], eth->ether_dhost[4], eth->ether_dhost[5]); printf("类型: 0x%04X\n", ntohs(eth->ether_type)); } // 打印IP头部信息 void print_ip_header(const struct ip* iphdr) { printf("IP | 源地址: %s → ", inet_ntoa(*(struct in_addr*)&iphdr->ip_src)); printf("目的地址: %s | ", inet_ntoa(*(struct in_addr*)&iphdr->ip_dst)); printf("协议: "); switch (iphdr->ip_p) { case IPPROTO_TCP: printf("TCP"); break; case IPPROTO_UDP: printf("UDP"); break; case IPPROTO_ICMP: printf("ICMP"); break; default: printf("%d", iphdr->ip_p); } printf(" | 长度: %d\n", ntohs(iphdr->ip_len)); } // 打印TCP头部信息 void print_tcp_header(const struct tcphdr* tcphdr) { printf("TCP | 源端口: %d → 目的端口: %d | ", ntohs(tcphdr->th_sport), ntohs(tcphdr->th_dport)); printf("序列号: %u | ACK号: %u\n", ntohl(tcphdr->th_seq), ntohl(tcphdr->th_ack)); } // 打印UDP头部信息 void print_udp_header(const struct udphdr* udphdr) { printf("UDP | 源端口: %d → 目的端口: %d | 长度: %d\n", ntohs(udphdr->uh_sport), ntohs(udphdr->uh_dport), ntohs(udphdr->uh_ulen)); } // 主抓包函数 void packet_capture() { // 创建原始套接字 int sockfd = ff_socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (sockfd < 0) { perror("创建原始套接字失败"); return; } // 获取接口索引 struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, INTERFACE, IFNAMSIZ); if (ff_ioctl(sockfd, SIOCGIFINDEX, &ifr) < 0) { perror("获取接口索引失败"); ff_close(sockfd); return; } // 绑定到指定接口 struct sockaddr_ll sll; memset(&sll, 0, sizeof(sll)); sll.sll_family = AF_PACKET; sll.sll_ifindex = ifr.ifr_ifindex; sll.sll_protocol = htons(ETH_P_ALL); if (ff_bind(sockfd, (struct sockaddr*)&sll, sizeof(sll)) < 0) { perror("绑定接口失败"); ff_close(sockfd); return; } // 设置混杂模式 if (ff_ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) { perror("获取接口标志失败"); ff_close(sockfd); return; } ifr.ifr_flags |= IFF_PROMISC; if (ff_ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0) { perror("设置混杂模式失败"); ff_close(sockfd); return; } printf("开始捕获 %s 网口上的报文...\n", INTERFACE); unsigned long pkt_count = 0; char buffer[BUFFER_SIZE]; while (running) { // 接收原始报文 ssize_t packet_size = ff_recv(sockfd, buffer, BUFFER_SIZE, 0); if (packet_size < 0) { if (errno != EAGAIN) { perror("接收报文错误"); break; } continue; } pkt_count++; printf("\n[报文 #%lu | 长度: %zd 字节]\n", pkt_count, packet_size); // 解析以太网头部 struct ether_header* eth = (struct ether_header*)buffer; print_eth_header(eth); // 检查是否为IP报文 if (ntohs(eth->ether_type) == ETHERTYPE_IP) { // 解析IP头部 struct ip* iphdr = (struct ip*)(buffer + sizeof(struct ether_header)); print_ip_header(iphdr); // 解析传输层协议 switch (iphdr->ip_p) { case IPPROTO_TCP: { struct tcphdr* tcphdr = (struct tcphdr*)(buffer + sizeof(struct ether_header) + (iphdr->ip_hl << 2)); print_tcp_header(tcphdr); break; } case IPPROTO_UDP: { struct udphdr* udphdr = (struct udphdr*)(buffer + sizeof(struct ether_header) + (iphdr->ip_hl << 2)); print_udp_header(udphdr); break; } } } // 每10个报文刷新一次统计 if (pkt_count % 10 == 0) { printf("\n--- 已捕获 %lu 个报文 ---\n", pkt_count); } } ff_close(sockfd); } int main(int argc, char* argv[]) { // 注册信号处理 signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); // 初始化F-Stack环境 ff_init(argc, argv); // 启动抓包 packet_capture(); printf("程序正常退出\n"); return 0; } ``` ### F-Stack环境配置指南 1. **编译依赖**: ```bash sudo apt install libnuma-dev libpcap-dev ``` 2. **获取F-Stack源码**: ```bash git clone https://github.com/F-Stack/f-stack.git cd f-stack ``` 3. **配置DPDK环境**: ```bash # 设置环境变量 export FF_PATH=/path/to/f-stack export FF_DPDK=/path/to/dpdk # 配置DPDK cd dpdk/usertools ./dpdk-setup.sh ``` 4. **编译F-Stack**: ```bash cd $FF_PATH make sudo make install ``` 5. **F-Stack配置文件**: 创建 `/etc/f-stack.conf`: ```ini [dpdk] # DPDK参数 lcore_mask=3 memory=1024 pci=0000:00:04.0 # eth4的PCI地址 [port0] # 网口配置 name=eth4 addr=192.168.1.100 netmask=255.255.255.0 broadcast=192.168.1.255 gateway=192.168.1.1 ``` 6. **编译程序**: ```bash g++ -o fstack_capture capture.cpp -I${FF_PATH}/lib \ -L${FF_PATH}/lib -lff -lpthread -lnuma ``` 7. **运行程序**: ```bash sudo ./fstack_capture ``` ### 程序功能说明 1. **原始报文捕获**: - 创建原始套接字捕获所有以太网帧 - 绑定到指定网络接口 - 启用混杂模式捕获所有流量 2. **协议解析**: - 以太网帧头解析(源/目的MAC地址) - IPv4头部解析(源/目的IP地址、协议类型) - TCP/UDP头部解析(源/目的端口) 3. **输出示例**: ```plaintext [报文 #15 | 长度: 98 字节] 以太网 | 源MAC: 00:11:22:33:44:55 → 目的MAC: FF:FF:FF:FF:FF:FF | 类型: 0x0800 IP | 源地址: 192.168.1.101 → 目的地址: 192.168.1.255 | 协议: UDP | 长度: 84 UDP | 源端口: 5353 → 目的端口: 5353 | 长度: 64 [报文 #16 | 长度: 66 字节] 以太网 | 源MAC: A4:83:E7:12:34:56 → 目的MAC: 00:11:22:33:44:55 | 类型: 0x0800 IP | 源地址: 8.8.8.8 → 目的地址: 192.168.1.101 | 协议: TCP | 长度: 52 TCP | 源端口: 443 → 目的端口: 54321 | 序列号: 123456789 | ACK号: 987654321 ``` ### 关键优势 1. **高性能**: - 基于DPDK绕过内核协议栈 - 零拷贝技术减少内存复制 - 轮询模式避免中断开销 2. **兼容性**: - 提供类Linux Socket API - 支持标准网络编程接口 - 与现有网络应用兼容 3. **灵活配置**: - 通过配置文件调整DPDK参数 - 支持多队列RSS - 可扩展各种网络协议栈 ### 注意事项 1. F-Stack需要独占网卡,需绑定DPDK驱动 2. 运行时需要root权限 3. 配置文件需匹配实际网卡PCI地址 4. 当前示例捕获所有流量,实际应用中应添加过滤逻辑 5. 协议解析部分需要根据实际报文扩展
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值