网络配置工具类详解
CNet 网络配置工具类详解
平台:仅支持Linux,大量使用
ioctl系统调用
一、概述
CNet是一个纯静态方法的网络配置工具类,封装了 Linux 下常用的网络操作:
| 功能类别 | 涵盖内容 |
|---|---|
| IP 地址 | 读取/设置本机 IP、子网掩码 |
| 网关 | 读取/添加/删除/设置默认网关 |
| DNS | 读取/写入/etc/resolv.conf |
| MAC 地址 | 读取本机 MAC、生成随机 MAC |
| 网卡状态 | 网线插入检测、网卡使能/禁用、接口是否存在 |
| 路由 metric | 设置网卡路由优先级 |
| Socket 绑定 | 将套接字绑定到指定网卡 |
全部方法都是static,无需实例化即可调用。
二、头文件与依赖
extern"C"{#include<stdio.h>#include<string.h>#include<arpa/inet.h>// inet_ntoa(), inet_addr()#include<sys/ioctl.h>// ioctl 系统调用#include<net/if.h>// ifreq, IFNAMSIZ#include<unistd.h>#include<linux/if.h>#include<linux/ethtool.h>// ETHTOOL_GLINK 网线检测#include<linux/rtnetlink.h>// 路由#include<net/route.h>// rtentry 路由结构#include<sys/time.h>#include<dirent.h>};核心武器是
ioctl——Linux 下操作网络接口的全能接口。
三、IP 地址操作
3.1 整数 ↔ 字符串互转
// 整数 IP(网络字节序) → 字符串 "192.168.1.100"char*IpU32ToStr(uint32_tu32_ip);// 字符串 IP → 整数(网络字节序)unsignedintIpStrToU32(constchar*sz_ip);内部其实就两层皮:
// IpU32ToStr 内部:returninet_ntoa(*(structin_addr*)&u32_ip);// IpStrToU32 内部:returninet_addr(sz_ip);⚠️
inet_addr()在 IP 为0时返回INADDR_NONE,但代码未做校验,实际使用有坑。
3.2 读取本机 IP
boolGetLocalIp(constchar*interface_name,char*sz_ip,uint32_t&u32_ip);实现套路(后文类似方法都这个套路):
intsock_fd=socket(AF_INET,SOCK_DGRAM,0);// 创建套接字strncpy(stIfreq.ifr_name,interface_name,IFNAME_SIZE);ioctl(sock_fd,SIOCGIFADDR,&stIfreq);// 发给内核问 IP// 取出结果,转换,关闭套接字SIOCGIFADDR= Socket I/O Control 获取接口地址。
3.3 设置本机 IP
boolSetLocalIp(constchar*interface_name,constchar*sz_ip);boolSetLocalIp(constchar*interface_name,uint32_tu32_ip);sock=socket(AF_INET,SOCK_DGRAM,0);// 注意是 DGRAM,不是 STREAMstrncpy(ifr.ifr_name,interface_name,IFNAMSIZ);si.sin_family=PF_INET;si.sin_addr.s_addr=u32_ip;memcpy(&ifr.ifr_addr,&si,sizeof(structsockaddr_in));ioctl(sock,SIOCSIFADDR,&ifr);// SIOCS = SetSIOCSIFADDR= Socket I/O Control Set Interface Address。
3.4 子网掩码读取/设置
boolGetNetMask(constchar*interface_name,char*sz_mask);boolGetNetMask(constchar*interface_name,uint32_t&mask);boolSetNetMask(constchar*interface_name,constchar*sz_mask);boolSetNetMask(constchar*interface_name,uint32_tmask);底层用SIOCGIFNETMASK/SIOCSIFNETMASKioctl。
四、网关(路由)操作
这是最复杂的部分,涉及/proc/net/route文件解析。
4.1 核心数据结构
structroute_info{u_int dstAddr;u_int srcAddr;u_int gateWay;charifName[IF_NAMESIZE];};4.2 读取网关
boolGetLocalGateWay(constchar*interface_name,char*sz_gw);boolGetLocalGateWay(constchar*interface_name,uint32_t&gw);实现:打开/proc/net/route,逐行解析:
// /proc/net/route 格式示例:// Iface Destination Gateway Flags RefCnt Use Metric Mask ...// eth0 00000000 0A00020A 0003 ... ... 100 0000F0F8FILE*fp=fopen(PROCNET_ROUTE_PATH,"r");// "/proc/net/route"fgets(buff,130,fp);// 跳过表头while(fgets(buff,130,fp)!=NULL){sscanf(buf,"%15s\t%08lX\t%08lX\t%8X\t",iface,&dest,&gate,&iflags);if(!strcmp(iface,interface_name)){if(iflags&(RTF_UP|RTF_GATEWAY))==(RTF_UP|RTF_GATEWAY)){gw=gate;// gate 是 16 进制整数returntrue;}}}4.3 设置网关(先清后加)
boolSetLocalGateWay(constchar*interface_name,constchar*sz_gw);boolSetLocalGateWay(constchar*interface_name,uint32_tgw);流程:CleanGateWay()→AddLocalGateWay()
boolSetLocalGateWay(constchar*interface_name,uint32_tgw){CleanGateWay(interface_name);// 先删所有旧路由returnAddLocalGateWay(interface_name,gw);// 再加新路由}4.4 添加/删除网关
staticboolAddLocalGateWay(constchar*interface_name,uint32_tgw);staticboolDelLocalGateWay(constchar*interface_name,uint32_tgw);底层通过SIOCADDRT(Add Route)和SIOCDELRT(Delete Route)ioctl 实现:
structrtentryrt;memset(&rt,0,sizeof(structrtentry));rt.rt_flags=(RTF_UP|RTF_GATEWAY);// 标识为网关路由rt.rt_dst.sa_family=PF_INET;rt.rt_genmask.sa_family=PF_INET;sa.sin_addr.s_addr=gw;memcpy(&rt.rt_gateway,&sa,sizeof(structsockaddr));intskfd=socket(AF_INET,SOCK_DGRAM,0);ioctl(skfd,SIOCADDRT,&rt);// 写入内核路由表close(skfd);五、DNS 服务器操作
直接读写/etc/resolv.conf:
boolSetDNSServerIP(constchar*first_dns,constchar*second_dns);boolGetDNSServerIP(char*first_dns,char*second_dns);写操作:
FILE*fp=fopen(RESOLV_CONF,"w");// "/etc/resolv.conf"snprintf(buf,sizeof(buf)-1,"nameserver %s\n""nameserver %s\n",first_dns,second_dns);fwrite(buf,1,strlen(buf),fp);fflush(fp);fclose(fp);读操作:用LastPos()找到每行nameserver后面的空格位置,提取 IP。
六、MAC 地址操作
6.1 读取本机 MAC
boolGetLocalMac(constchar*interface_name,char*sz_mac);通过SIOCGIFHWADDRioctl 获取:
strncpy(ifreq.ifr_name,interface_name,...);ioctl(sockfd,SIOCGIFHWADDR,&ifreq);// 获取硬件地址memcpy(mac_data,ifreq.ifr_hwaddr.sa_data,6);MacNumToStr(mac_data,sz_mac);// 格式化成 "XX:XX:XX:XX:XX:XX"6.2 随机 MAC 生成
boolGetRandomMacAddr(char*sz_mac);生成 6 字节随机数,但会过滤掉全零和多播地址:
do{for(i=0;i<6;i++)mac[i]=rand()&0xFF;}while(!IsValidEtherAddr((char*)mac));// 校验通过为止七、网卡状态检测
7.1 网线是否插入
boolIsCablePluggedIn(constchar*interface_name);使用ethtool接口:
structethtool_valueeth_data;eth_data.cmd=ETHTOOL_GLINK;// 查询链路状态eth_data.data=0;ifr.ifr_data=(char*)ð_data;ioctl(sock,SIOCETHTOOL,&ifr);// 发给 ethtool 驱动// eth_data.data > 0 表示网线插着7.2 网卡是否启用
boolGetNetworkEnableStatus(constchar*interface_name,bool&enable);boolSetNetworkEnableStatus(constchar*interface_name,boolenable);通过SIOCGIFFLAGS(Get)和SIOCSIFFLAGS(Set)操作ifr_flags:
ioctl(sock,SIOCGIFFLAGS,&ifr);enable=(ifr.ifr_flags&IFF_UP)!=0;// IFF_UP 表示已启用// 设置禁用:ifr.ifr_flags&=~IFF_UP;ioctl(sock,SIOCSIFFLAGS,&ifr);// 设置启用:ifr.ifr_flags|=IFF_UP|IFF_RUNNING;7.3 网卡接口是否存在
boolIsNetworkInterfaceExist(constchar*interface_name);boolGetAllNetworkInterface(std::vector<std::string>&interface_name);遍历/sys/class/net目录:
DIR*dir=opendir("/sys/class/net");structdirent*entry;while((entry=readdir(dir))!=NULL){if(entry->d_type==DT_LNK&&!strcmp(entry->d_name,interface_name)){exist=true;}}closedir(dir);八、Socket 绑定到网卡
boolSockBindInterface(constchar*interface_name,intsock_fd);使用SOL_SOCKET/SO_BINDTODEVICE选项:
strncpy(interface.ifr_ifrn.ifrn_name,interface_name,...);setsockopt(sock_fd,SOL_SOCKET,SO_BINDTODEVICE,(char*)&interface_name,sizeof(interface));这样该 socket 收发的数据包都走指定网卡。
九、路由优先级(Metric)
boolSetRouteMetric(constchar*interface_name,uint32_tmetric);ifr.ifr_metric=metric;// 0-255,值越大优先级越低ioctl(sockfd,SIOCSIFMETRIC,&ifr);十、总结
| 技术亮点 | 说明 |
|---|---|
ioctl系统调用 | 贯穿整个模块,操作网络接口的金钥匙 |
/proc/net/route | 读取内核路由表,手动解析文本 |
/etc/resolv.conf | 直接读写 DNS 配置 |
/sys/class/net | 遍历网卡接口 |
ethtoolioctl | 查询网线物理连接状态 |
| 路由表操作 | SIOCADDRT/SIOCDELRT动态增删路由 |
整体是一个贴近 Linux 底层实现的网络配置工具,设计思路务实,不依赖第三方库,适合嵌入式 Linux 场景。
