跨平台底层网络库libdnet源码分析系列(二)
-
官网:http://securitytech.cc
源码分析mettle后门工具学习 所使用的依赖库

第 2 章:网络接口管理 (Interface Module)
2.1 实战案例:编写跨平台”网卡信息收集器”
案例目标
实现一个跨平台的网卡信息收集器,能够获取网卡的以下信息:
完整代码示例
编译与运行
Windows (MSVC):
Linux (GCC):
预期输出示例
2.2 核心 API 详解
2.2.1
intf_open– 打开接口句柄功能: 创建并初始化接口管理句柄
返回值:
Windows 实现源码分析 (
intf-win32.c:306-310):关键点:
2.2.2
intf_loop– 遍历所有接口功能: 遍历系统中所有网络接口,对每个接口调用回调函数
参数:
返回值:
Windows 实现源码分析 (
intf-win32.c:396-416):执行流程:
2.2.3
intf_get_dst– 路由查找功能: 根据目标地址查找应使用的出口网卡
参数:
返回值:
重要提示:
Windows 实现源码:
Windows 下的替代方案:
2.3 难点攻克:Linux 与 Windows 的结构体差异
2.3.1 Linux: 使用
ioctl获取接口信息核心数据结构:
典型实现代码:
2.3.2 Windows: 使用
GetAdaptersAddresses获取接口信息核心数据结构:
Windows 实现源码分析 (
intf-win32.c:219-269):2.3.3 关键差异对比表
特性 Linux (ioctl) Windows (GetAdaptersAddresses) 获取方式 同步调用,单次 ioctl 返回链表结构,需要遍历 接口标识 字符串名称 (eth0, wlan0) 数字索引 (IfIndex, Ipv6IfIndex) MAC 地址 ifrhwaddr.sadata PhysicalAddress + PhysicalAddressLength IP 地址 需要多次 ioctl 调用 FirstUnicastAddress 链表 状态标志 IFFUP, IFFRUNNING 等 OperStatus 枚举值 MTU ifr_mtu 字段 Mtu 字段 缓冲区管理 静态结构体 动态分配,可能需要多次重试 2.3.4 平台差异处理策略
2.4 避坑指南:Windows 上获取不到 MAC 地址的问题
2.4.1 问题现象
在 Windows 上运行网卡信息收集器时,发现部分网卡的 MAC 地址显示为空或错误值。
2.4.2 根本原因分析
原因一:PhysicalAddressLength 不匹配
Windows 的
IP_ADAPTER_ADDRESSES结构中:原因二:虚拟网卡/隧道网卡无 MAC 地址
某些虚拟接口(如 VPN、隧道适配器)可能没有物理地址:
原因三:eth_addr 联合体差异
libdnet 中的
intf_link_addr是一个联合体:2.4.3 正确的实现方式
Windows 实现源码分析 (
intf-win32.c:176-182):关键点:
2.4.4 完整的 MAC 地址处理示例
2.4.5 常见错误对比
错误代码 问题 正确代码 memcpy(&entry->intf_link_addr,a->PhysicalAddress,a->PhysicalAddressLength)覆盖了 addrtype 和 addrbits 字段 只复制 addr_eth 字段 if(a->PhysicalAddressLength>0)接受非 6 字节的地址,导致后续解析错误 if(a->PhysicalAddressLength==ETH_ADDR_LEN)直接访问 a->PhysicalAddress而不检查长度可能访问未初始化的数据 先检查长度再复制 memcpy(mac,a->PhysicalAddress,8)复制了 2 字节填充数据 memcpy(mac,a->PhysicalAddress,ETH_ADDR_LEN)2.4.6 调试技巧
添加调试输出:
预期输出:
异常输出:
2.4.7 Windows 特殊情况处理
处理回环接口:
处理隧道接口:
2.5 扩展:其他常用 API
2.5.1
intf_get_src– 根据源地址查找接口功能: 根据 IP 地址查找对应的网络接口
应用场景:
Windows 实现源码分析 (
intf-win32.c:347-373):2.5.2
intf_get_index– 根据索引查找接口功能: 根据接口索引查找接口信息
参数:
Windows 实现源码分析 (
intf-win32.c:329-345):2.5.3
intf_close– 关闭接口句柄功能: 释放接口句柄及相关资源
Windows 实现源码分析 (
intf-win32.c:418-433):
2.6 实战应用:基于源码的高级功能
2.6.1 实现 Windows 路由查找功能
虽然 Windows 原生
intf_get_dst不支持,但我们可以自行实现:2.6.2 获取接口详细信息(Windows)
2.7 总结
2.7.1 关键要点
2.7.2 常见问题排查清单
2.7.3 推荐实践
-
Windows 上 MAC 地址显示异常?→ 检查
PhysicalAddressLength无法遍历所有接口?→ 检查缓冲区分配和
GetAdaptersAddresses返回值路由查找失败?→ Windows 下
intf_get_dst不支持,需自行实现接口名称不识别?→ 注意 Windows 接口名称格式(eth0, eth1 等)
内存泄漏?→ 确保调用
intf_close释放资源 - MAC 地址长度验证
- 接口类型映射
- 错误处理和状态码转换
- 使用
ioctl系统调用 - 直接读取
/proc文件系统 - 使用
GetAdaptersAddresses获取接口信息 - 需要处理动态缓冲区和重试逻辑
- 接口索引与设备名称的映射
af: 地址族 (AF_INET或AF_INET6)index: 接口索引- 确定某个 IP 地址属于哪个网卡
- 实现多网卡环境下的接口选择
PhysicalAddressLength为 0- 或返回的是软件生成的虚拟地址
PhysicalAddress数组大小:MAX_ADAPTER_ADDRESS_LENGTH(8 字节)- 实际 MAC 地址长度:
ETH_ADDR_LEN(6 字节) - Windows 实现不支持此功能 (
intf-win32.c:375-381) - 返回
ENOSYS(函数未实现) - Linux 实现通过读取
/proc/net/route实现 - 成功:返回 0
- 失败:返回 -1
intf: 接口句柄entry: 输出参数,存储找到的接口信息dst: 目标地址- 成功:返回回调函数的返回值
- 失败:返回 -1,设置
errno intf: 接口句柄callback: 回调函数arg: 用户自定义参数- Windows 下只是分配内存,延迟加载接口表
- Linux 下会打开 socket 并初始化 netlink 通信
- 成功:返回
intf_t*句柄 - 失败:返回
NULL,设置errno - IP 地址(IPv4/IPv6)
- MAC 地址
- MTU(最大传输单元)
- 网卡状态(UP/DOWN)
- 使用
intf_loop而非多次intf_get:前者一次性获取所有信息,效率更高 - 分配足够大的缓冲区:
intf_entry.intf_len决定了可存储的别名地址数量 - 检查返回值:所有 API 都可能失败,必须检查返回值
- 平台特定功能:对于平台不支持的 API,考虑使用平台原生 API 实现
- 错误处理:提供有意义的错误信息,便于调试
- 跨平台注意事项:
- Linux 特性:
- 统一接口,平台隔离:libdnet 提供了统一的 API,底层根据平台使用不同的实现
- Windows 特性:
void print_detailed_interface_info(constchar*device){intf_t*intf;struct intf_entry entry;u_char buf[2048];/* 更大的缓冲区以支持更多别名 */if((intf = intf_open())== NULL){perror("intf_open");return;}entry.intf_len =sizeof(buf);strncpy(entry.intf_name, device,sizeof(entry.intf_name));if(intf_get(intf,&entry)<0){perror("intf_get");}else{/* 打印详细信息 */printf("接口: %s\n", entry.intf_name);printf("操作系统名称: %s\n", entry.os_intf_name);printf("驱动描述: %s\n", entry.driver_name);printf("PCAP 设备名: %s\n", entry.pcap_intf_name);/* MAC 地址 */if(entry.intf_link_addr.addr_type == ADDR_TYPE_ETH){char mac_str[18];eth_ntoa_r(&entry.intf_link_addr.addr_eth, mac_str);printf("MAC: %s\n", mac_str);}/* 主 IP 地址 */if(entry.intf_addr.addr_type != ADDR_TYPE_NONE){char ip_str[INET6_ADDRSTRLEN];addr_ntop(&entry.intf_addr, ip_str,sizeof(ip_str));printf("主 IP: %s/%u\n", ip_str, entry.intf_addr.addr_bits);}/* 别名 IP 地址 */for(int i =0; i < entry.intf_alias_num; i++){char alias_str[INET6_ADDRSTRLEN];addr_ntop(&entry.intf_alias_addrs[i],alias_str,sizeof(alias_str));printf("别名 IP: %s/%u\n", alias_str,entry.intf_alias_addrs[i].addr_bits);}}intf_close(intf);}/* Windows 路由查找实现 */int intf_get_dst_win32(intf_t*intf,struct intf_entry *entry,struct addr *dst){if(dst->addr_type != ADDR_TYPE_IP){errno = EINVAL;return(-1);}/* 使用 GetBestRoute API */MIB_IPFORWARDTABLE *routeTable = NULL;DWORD dwSize =0;DWORD ret;DWORD ifIndex;/* 获取路由表 */if(GetIpForwardTable(NULL,&dwSize, FALSE)== ERROR_INSUFFICIENT_BUFFER){routeTable =(MIB_IPFORWARDTABLE *)malloc(dwSize);if(routeTable == NULL){errno = ENOMEM;return(-1);}ret =GetIpForwardTable(routeTable,&dwSize, FALSE);}if(ret != NO_ERROR){free(routeTable);errno = EIO;return(-1);}/* 查找最佳路由 */MIB_IPFORWARDROW bestRoute;DWORD bestMetric = INFINITE;for(DWORD i =0; i < routeTable->dwNumEntries; i++){MIB_IPFORWARDROW *route =&routeTable->table[i];/* 检查目标地址是否匹配 */if((dst->addr_ip & route->dwForwardMask)== route->dwForwardDest){if(route->dwForwardMetric1 < bestMetric){bestRoute =*route;bestMetric = route->dwForwardMetric1;}}}free(routeTable);if(bestMetric == INFINITE){errno = ENETUNREACH;return(-1);}/* 获取接口信息 */ifIndex = bestRoute.dwForwardIfIndex;return intf_get_index(intf, entry, AF_INET, ifIndex);}intf_t*intf_close(intf_t*intf){int i;if(intf != NULL){/* 释放 ifcombo 数组中的索引数组 */for(i =0; i < MIB_IF_TYPE_MAX; i++){if(intf->ifcombo[i].idx)free(intf->ifcombo[i].idx);}/* 释放适配器地址表 */if(intf->iftable)free(intf->iftable);/* 释放句柄本身 */free(intf);}return(NULL);}intf_t*intf_close(intf_t*intf);intintf_get_index(intf_t*intf,struct intf_entry *entry,int af,unsignedint index){IP_ADAPTER_ADDRESSES *a;if(_refresh_tables(intf)<0)return(-1);a = _find_adapter_address_by_index(intf, af, index);if(a == NULL)return(-1);_adapter_address_to_entry(intf, a, entry);return(0);}int intf_get_index(intf_t*intf,struct intf_entry *entry,int af,unsignedint index);intintf_get_src(intf_t*intf,struct intf_entry *entry,struct addr *src){IP_ADAPTER_ADDRESSES *a;IP_ADAPTER_UNICAST_ADDRESS *addr;if(src->addr_type != ADDR_TYPE_IP){errno = EINVAL;return(-1);}if(_refresh_tables(intf)<0)return(-1);/* 遍历所有适配器的所有地址 */for(a = intf->iftable; a != NULL; a = a->Next){for(addr = a->FirstUnicastAddress;addr != NULL; addr = addr->Next){struct addr dnet_addr;addr_ston(addr->Address.lpSockaddr,&dnet_addr);if(addr_cmp(&dnet_addr, src)==0){_adapter_address_to_entry(intf, a, entry);return(0);}}}errno = ENXIO;return(-1);}int intf_get_src(intf_t*intf,struct intf_entry *entry,struct addr *src);if(adapter->IfType== IF_TYPE_TUNNEL ||adapter->IfType== MIB_IF_TYPE_TUNNEL){/* 隧道接口可能无 MAC 地址,或有虚拟 MAC */if(adapter->PhysicalAddressLength!= ETH_ADDR_LEN){entry->intf_link_addr.addr_type = ADDR_TYPE_NONE;return;}}if(adapter->IfType== IF_TYPE_SOFTWARE_LOOPBACK){/* 回环接口通常无 MAC 地址,这是正常的 */entry->intf_link_addr.addr_type = ADDR_TYPE_NONE;return;}Adapter:{VPN-GUID}PhysicalAddressLength:0←无 MAC 地址PhysicalAddress:Adapter:{GUID}PhysicalAddressLength:6←正确的 MAC 地址长度PhysicalAddress:001A2B3C4D5Evoid debug_mac_info(IP_ADAPTER_ADDRESSES *adapter){printf("Adapter: %s\n", adapter->AdapterName);printf(" PhysicalAddressLength: %lu\n", adapter->PhysicalAddressLength);printf(" PhysicalAddress: ");for(ULONG i =0; i < adapter->PhysicalAddressLength; i++){printf("%02X ", adapter->PhysicalAddress[i]);}printf("\n");}void safe_copy_mac(struct intf_entry *entry,IP_ADAPTER_ADDRESSES *adapter){/* 清空地址字段 */memset(&entry->intf_link_addr,0,sizeof(entry->intf_link_addr));/* 检查是否有物理地址且长度正确 */if(adapter->PhysicalAddressLength== ETH_ADDR_LEN){/* 设置地址类型 */entry->intf_link_addr.addr_type = ADDR_TYPE_ETH;entry->intf_link_addr.addr_bits = ETH_ADDR_BITS;/* 精确复制 6 字节 MAC 地址 */memcpy(&entry->intf_link_addr.addr_eth,adapter->PhysicalAddress,ETH_ADDR_LEN);}else{/* 无有效 MAC 地址 */entry->intf_link_addr.addr_type = ADDR_TYPE_NONE;entry->intf_link_addr.addr_bits =0;}}- 检查长度: 必须验证
PhysicalAddressLength==ETH_ADDR_LEN - 精确复制: 只复制 6 字节,不要使用
PhysicalAddressLength - 设置类型: 正确设置
addr_type和addr_bits /* 获取硬件地址 */if(a->PhysicalAddressLength== ETH_ADDR_LEN){entry->intf_link_addr.addr_type = ADDR_TYPE_ETH;entry->intf_link_addr.addr_bits = ETH_ADDR_BITS;memcpy(&entry->intf_link_addr.addr_eth, a->PhysicalAddress,ETH_ADDR_LEN);/* 只复制 6 字节 */}struct addr {uint8_t addr_type;/* 地址类型 */uint16_t addr_bits;/* 地址位数 */union{uint8_t data[16];/* 原始数据 */uint32_t addr_ip;/* IPv4 地址 */uint8_t addr_ip6[16];/* IPv6 地址 */eth_addr_t addr_eth;/* MAC 地址 *//* ... */} __packed;};/* 错误代码示例 */if(a->PhysicalAddressLength>0){/* 直接复制 8 字节,包含 2 字节填充 */memcpy(mac, a->PhysicalAddress, a->PhysicalAddressLength);}/* libdnet 的统一接口实现 *//* Linux 特有实现 */#ifdef SIOCGIFCONFint intf_get(intf_t*intf,struct intf_entry *entry){int sockfd = socket(AF_INET, SOCK_DGRAM,0);struct ifreq ifr;strcpy(ifr.ifr_name, entry->intf_name);/* 获取 IP 地址 */ioctl(sockfd, SIOCGIFADDR,&ifr);/* ... */}#endif/* Windows 特有实现 */#ifdef _WIN32int intf_get(intf_t*intf,struct intf_entry *entry){IP_ADAPTER_ADDRESSES *a;_refresh_tables(intf);a = _find_adapter_address(intf, entry->intf_name);_adapter_address_to_entry(intf, a, entry);/* ... */}#endifstaticint_refresh_tables(intf_t*intf){IP_ADAPTER_ADDRESSES *p;DWORD ret;ULONG len;p = NULL;/* 分配初始缓冲区 (16KB) */len =16384;do{free(p);p = malloc(len);if(p == NULL)return(-1);/* 获取适配器地址信息 */ret =GetAdaptersAddresses(AF_UNSPEC,GAA_FLAG_INCLUDE_PREFIX |GAA_FLAG_SKIP_ANYCAST |GAA_FLAG_SKIP_MULTICAST,NULL, p,&len);}while(ret == ERROR_BUFFER_OVERFLOW);if(ret != NO_ERROR){free(p);return(-1);}intf->iftable = p;/* 映射 Windows 接口索引到内部索引 */for(p = intf->iftable; p != NULL; p = p->Next){int type = _if_type_canonicalize(p->IfType);if(type < MIB_IF_TYPE_MAX)_ifcombo_add(&intf->ifcombo[type], p->IfIndex, p->Ipv6IfIndex);elsereturn(-1);}return(0);}typedefstruct _IP_ADAPTER_ADDRESSES {union{ULONGLONG Alignment;struct{ULONG Length;DWORD IfIndex;};};PIP_ADAPTER_ADDRESSES Next;charAdapterName[MAX_ADAPTER_NAME_LENGTH];PIP_ADAPTER_UNICAST_ADDRESS FirstUnicastAddress;PIP_ADAPTER_PREFIX FirstPrefix;ULONG IfType;intOperStatus;ULONG Mtu;BYTE PhysicalAddress[MAX_ADAPTER_ADDRESS_LENGTH];ULONG PhysicalAddressLength;/* ... 更多字段 */} IP_ADAPTER_ADDRESSES,*PIP_ADAPTER_ADDRESSES;int sockfd = socket(AF_INET, SOCK_DGRAM,0);struct ifreq ifr;/* 获取接口标志 */strcpy(ifr.ifr_name,"eth0");ioctl(sockfd, SIOCGIFFLAGS,&ifr);/* 获取 MAC 地址 */ioctl(sockfd, SIOCGIFHWADDR,&ifr);unsignedchar*mac =(unsignedchar*)ifr.ifr_hwaddr.sa_data;/* 获取 MTU */ioctl(sockfd, SIOCGIFMTU,&ifr);/* Linux 下的 struct ifreq */struct ifreq {char ifr_name[IFNAMSIZ];/* 接口名称 */union{struct sockaddr ifr_addr;struct sockaddr ifr_dstaddr;struct sockaddr ifr_broadaddr;struct sockaddr ifr_netmask;struct sockaddr ifr_hwaddr;short ifr_flags;int ifr_ifindex;int ifr_metric;int ifr_mtu;/* ... */} ifr_ifru;};/* 使用 GetBestRoute API 实现路由查找 */MIB_IPFORWARDROW route;DWORD ifIndex;if(GetBestRoute(dst->addr_ip,0,&route)== NO_ERROR){ifIndex = route.dwForwardIfIndex;/* 使用 intf_get_index 获取接口信息 */intf_get_index(intf, entry, AF_INET, ifIndex);}intintf_get_dst(intf_t*intf,struct intf_entry *entry,struct addr *dst){errno = ENOSYS;SetLastError(ERROR_NOT_SUPPORTED);return(-1);}int intf_get_dst(intf_t*intf,struct intf_entry *entry,struct addr *dst);- 调用
_refresh_tables获取最新的适配器信息 - 遍历
IP_ADAPTER_ADDRESSES链表 - 对每个适配器调用
_adapter_address_to_entry转换格式 - 调用用户回调函数处理
intintf_loop(intf_t*intf, intf_handler callback,void*arg){IP_ADAPTER_ADDRESSES *a;struct intf_entry *entry;u_char ebuf[1024];int ret;/* 1. 刷新接口表 */if(_refresh_tables(intf)<0)return(-1);entry =(struct intf_entry *)ebuf;/* 2. 遍历所有适配器 */for(a = intf->iftable; a != NULL; a = a->Next){entry->intf_len =sizeof(ebuf);_adapter_address_to_entry(intf, a, entry);if((ret =(*callback)(entry, arg))!=0)break;}return(ret);}typedefint(*intf_handler)(struct intf_entry *entry,void*arg);int intf_loop(intf_t*intf, intf_handler callback,void*arg);intf_t*intf_open(void){return(calloc(1,sizeof(intf_t)));}intf_t*intf_open(void);===网卡信息收集器===[方式一]遍历所有网卡:========================================网卡名称: eth0描述:RealtekPCIeGbEFamilyController类型:以太网状态: UPMTU:1500MAC 地址:00:1A:2B:3C:4D:5E主 IP 地址:192.168.1.100/24标志: UP MULTICAST BROADCAST================================================================================网卡名称: lo描述:SoftwareLoopbackInterface1类型:回环状态: UPMTU:65536主 IP 地址:127.0.0.1/8别名 IP 地址:-::1/128标志: UP LOOPBACK========================================gcc -o network_collector network_collector.c -I include -L .-ldnet -lpthread./network_collectorcl network_collector.c /I include libdnet.lib iphlpapi.lib ws2_32.libnetwork_collector.exe/** 网卡信息收集器 - 跨平台实现* 功能:获取系统所有网卡的 IP/MAC/MTU/状态信息*/#include<stdio.h>#include<stdlib.h>#include<string.h>#include"dnet.h"/* MAC 地址格式化输出 */void print_mac(consteth_addr_t*eth){printf("%02X:%02X:%02X:%02X:%02X:%02X",eth->data[0], eth->data[1], eth->data[2],eth->data[3], eth->data[4], eth->data[5]);}/* IP 地址格式化输出 */void print_ip(conststruct addr *addr){char buf[128];if(addr_ntop(addr, buf,sizeof(buf))!= NULL){printf("%s", buf);}else{printf("N/A");}}/* 打印单个网卡信息 */void print_interface_info(struct intf_entry *entry){printf("\n========================================\n");printf("网卡名称: %s\n", entry->intf_name);printf("描述: %s\n", entry->driver_name);/* 网卡类型 */printf("类型: ");switch(entry->intf_type){case INTF_TYPE_ETH: printf("以太网");break;case INTF_TYPE_LOOPBACK: printf("回环");break;case INTF_TYPE_PPP: printf("PPP");break;case INTF_TYPE_TOKENRING: printf("令牌环");break;case INTF_TYPE_TUN: printf("隧道");break;default: printf("其他");}printf("\n");/* 网卡状态 */printf("状态: %s\n",(entry->intf_flags & INTF_FLAG_UP)?"UP":"DOWN");/* MTU */printf("MTU: %u\n", entry->intf_mtu);/* MAC 地址 */if(entry->intf_link_addr.addr_type == ADDR_TYPE_ETH){printf("MAC 地址: ");print_mac(&entry->intf_link_addr.addr_eth);printf("\n");}/* 主 IP 地址 */if(entry->intf_addr.addr_type != ADDR_TYPE_NONE){printf("主 IP 地址: ");print_ip(&entry->intf_addr);printf("/%u\n", entry->intf_addr.addr_bits);}/* 别名 IP 地址 */if(entry->intf_alias_num >0){printf("别名 IP 地址:\n");for(int i =0; i < entry->intf_alias_num; i++){printf(" - ");print_ip(&entry->intf_alias_addrs[i]);printf("/%u\n", entry->intf_alias_addrs[i].addr_bits);}}/* 标志位 */printf("标志: ");if(entry->intf_flags & INTF_FLAG_UP) printf("UP ");if(entry->intf_flags & INTF_FLAG_LOOPBACK) printf("LOOPBACK ");if(entry->intf_flags & INTF_FLAG_MULTICAST) printf("MULTICAST ");if(entry->intf_flags & INTF_FLAG_BROADCAST) printf("BROADCAST ");if(entry->intf_flags & INTF_FLAG_POINTOPOINT) printf("POINTOPOINT ");printf("\n");printf("========================================\n");}/* 回调函数:用于 intf_loop 遍历所有网卡 */int callback(struct intf_entry *entry,void*arg){print_interface_info(entry);return0;/* 返回 0 继续遍历,非 0 停止 */}int main(int argc,char*argv[]){intf_t*intf;int ret =0;printf("=== 网卡信息收集器 ===\n");/* 打开接口句柄 */if((intf = intf_open())== NULL){fprintf(stderr,"无法打开接口: %s\n", strerror(errno));return1;}/* 方式一:遍历所有网卡 */printf("\n[方式一] 遍历所有网卡:\n");if(intf_loop(intf, callback, NULL)<0){fprintf(stderr,"遍历网卡失败: %s\n", strerror(errno));ret =1;goto done;}/* 方式二:获取指定网卡信息(以第一个网卡为例) */printf("\n[方式二] 获取指定网卡信息:\n");struct intf_entry entry;u_char buf[1024];entry.intf_len =sizeof(buf);strncpy(entry.intf_name,"eth0",sizeof(entry.intf_name));if(intf_get(intf,&entry)<0){printf("获取 eth0 失败,可能不存在或名称不同\n");}else{print_interface_info(&entry);}done:/* 关闭接口句柄 */intf_close(intf);return ret;}-
公众号:安全狗的自我修养
-
vx:2207344074
-
http://gitee.com/haidragon
-
http://github.com/haidragon
-
bilibili:haidragonx
-


夜雨聆风