如何理解stdlib.h里的_countof()宏

计数宏_countof解析
本文解析了标准库中的_countof宏,详细介绍了其内部函数__countof_helper的语法及工作原理,包括数组引用、数组指针、函数返回数组指针的声明语法等关键概念。

在stdlib.h里有一个宏_countof,如下:

extern "C++"
{
template <typename _CountofType, size_t _SizeOfArray>
char (*__countof_helper(UNALIGNED _CountofType (&_Array)[_SizeOfArray]))[_SizeOfArray];


#define _countof(_Array) sizeof(*__countof_helper(_Array))
}

 

这个宏的作用就是得到一个数组元素的大小。

 

如下使用:

#include <stdlib.h>

int main()

{

      int a[5];

      ......

      int arraySize = _countof(a); //得到数组a的大小 

}

////////////////////////////////////////////////////////////////////////////////////////////////

 

这个宏使用了一些大家平时都比较少用到的语法。所以理解起来,一下子不是很明白。

所以这里简单说说,如何理解它吧。

为了说明方便,改写如下:
template <typename T, size_t N>
char (* __countof_helper ( T (&_Array)[N] )  )[N];

 

这是一个函数模板声明,没有定义实现体。
__countof_helper是一个函数(红色部分);
其参数是一个“数组引用”T (&)[N](蓝色部分);
而返回值是一个“数组指针”(指向一个数组的指针) char (*)[N](绿色部分);


首先,需要理解3个C++的语法:


1) 数组引用

T (&)[N] (注意:有个括号)。比如:
int a[5] = {0};
int (&ra)[5] = a; 这里ra就是一个对数组a的引用,其这个数组的大小也是5;

 

数组的信息包括:数组元素的类型,和数组的大小;声明“数组引用”时,类型和大小都要匹配,否则是错误的。比如:
int a[5] = {0};
int (&ra)[4] = a; //编译错误:error C2440: 'initializing' : cannot convert from 'int [5]' to 'int (&)[4]'

 

注意:C++里没有“引用数组”这个语法概念。"int &ra[5]"这样的语法是通不过编译的。

 

2) 数组指针

T (*)[N] (注意:有个括号)。比如:
int a[5];
int (*p)[5] = &a;// 是一个指针,该指针指向“一个有5个元素的数组”;简称为“数组指针”

 

同样,声明时类型和大小都要匹配,如下是错误的。
int a[4];
int (*p)[5] = &a;//error C2440: '=' : cannot convert from 'int (*)[4]' to 'int (*)[5]'

 

上面说了,C++不支持“引用数组”;但是“指针数组”是绝对支持的,平常都会经常到的。比如如下:
char * names[] = {
                       "Mike",
                       "John",
                       "Tom" };
names就是一个指针数组,该数组有3个元素,每个元素都是一个char类型指针,指向一个字符串。
从上面的例子可以看出,指针数组里面的指针所指向的那块内存区域里的数据大小可以是不同的。
因为指针本身所带的信息只知道被指的内存是什么数据类型,不知道大小信息。比如:
int a[4]; int b[5];
int * p = 0;
p = a; // ok
p = b; // ok

 

3) 函数返回“数组指针”的声明语法

T (* Fun( param_list ) )[N];
Fun是一个函数,其返回值类型是 T (*)[N];

为什么不可以如下这么声明?
T (*)[N] Fun( param_list ); 这么声明多好理解啊。但是这么是不行的。
C++语法比较复杂,语言设计者对(),[],*,&,&&这些符号所放的位置有特殊的要求。
<<Thinking in C++>>的第3章有一节“Aside: complicated declarations & definitions”,举了一些复杂声明语法的例子。

 

第二,需要知道的是:sizeof是在编译期完成的。
sizeof是C/C++语言中的keyword,不是函数。对其参数里的表达式是不会在执行期去执行的,而只是在编译期去推算整个表达式的最后的类型信息。
比如: int a = 1; sizeof(a++); cout << a;   // a还是1. 不会是2;
因为是在编译期完成,编译器只需要类型信息,不需要函数的实现体。所以就可以不用提供定义体。
下面这个例子可能更能说明这个问题。
int Fun(); // only declare it without definition
sizeof( Fun() );  // 这里Fun()函数不会在运行的时候被执行,仅仅是在编译时,编译器需要知道的是:Fun()函数返回值的类型是什么。


第三,需要理解的是“模板自动推导(template deduction)”
模板推导是由编译器在编译期(compile time)完成的,而不是在执行期(run time)完成的。

这个是理解的关键之一。不涉及内存布局分配的问题。编译期间,编译器只关心声明信息(也就是声明式里面所带的类型信息)。模板推导会自动推导模板参数的各种信息(类型,传递过来的数值等)。以下是一个例子:
template<int x, int y>
struct sum_
{
  enum { value = x + y };
};
int sum = sum_<3, 4>::value; // sum的值在编译期间就已经确定下来了,不是在执行期间由CPU运算得到。
这个例子里的模板,仅仅只有数值的传递,没有包含类型。这个可能大家很少见到的。但是模板是支持的。

 

例子二:
template< typename T, size_t N>
void Fun( T (&)[N] );

这个模板里既有类型T,又有数值N;模板推导时,是根据Fun的实参来推导得到的。比如:
int a[5];
Fun(a); // 编译器经过推导就知道,T=int,N=5。注意这里的N能得到5,是因为Fun的参数声明决定的。这里Fun的参数是:数组引

用。对于“数组引用”,上面说过,编译器能够从实参那里知道数组的类型,又能知道数组的大小。

 

同时,编译器在进行模板推导时,会对“实参”和“形参”能够匹配的所有情况进行推导,经过所有的尝试都不能匹配,就会报告编译失败信息。比如,对于这个例子,我们将一个指针作为实参传递给它:
int * p = NULL;
Fun( p ); // error C2784: 'void Fun(T (&)[N])' : could not deduce template argument for 'T (&)[N]' from 'int *'
因为一个指针不可能赋值给一个数组引用。所以编译就会出错。


所以对于stdlib.h里的这个_countof(),如果传递一个指针给它,编译就会失败。

 

同时,需要指出的是,VC6对模板支持的很不好,一些复杂的模板语法就编译不了。根据我的测试,就这里所说的_countof(),在VC6里就编译不了。VC2005/2008就已经支持的很好的。所以这个_countof()是在VC2005中才提供的。在VC6所带的stdlib.h里没有这个_countof()宏的。

 

【题外话:模板是泛型编程的基础。利用模板自动推导的能力,可以构建出很有技巧性的代码实现。比如:Boost库。更甚者:Loki库<Modern C++ Design>(中文名:C++设计新思维)这本书附带的库。关于模板的语法和自动推导,可以看<C++ Templates the

complete guide>,大陆翻译的中译本编译的不好。侯捷翻译的《C++ Templates全览》比较好,网上有电子的pdf可以找到,但是只

能在电脑上看,不能打印,也不能copy里面的文字。】

 

第四:注意事项
template <typename _CountofType, size_t _SizeOfArray>
char (*__countof_helper(UNALIGNED _CountofType (&_Array)[_SizeOfArray]))[_SizeOfArray];
#define _countof(_Array) sizeof(*__countof_helper(_Array))

 

a) #define里的sizeof()的参数是 “*__countof_helper(_Array)”(注意:有个*),而不是“__countof_helper(_Array)”,
如果是“__countof_helper(_Array)”,那么_countof()返回的值永远是一个指针的大小(在32位系统上总是4)。
因为__countof_helper返回的是一个“数组指针”,加上*,就是取得该指针所指向的数组。


sizeof(一个指针)和sizeof(一个数组),是有很大的区别的。看下面的例子:


int a[5];
int *p = a;
int (*pa)[5] = &a;


// p和pa都是指针,但是指针的类型不同:p的类型是int*;pa的类型是int(*)[5]。


sizeof(p)       // = 4;
sizeof(a)       // = 20 = 5*sizeof(int)
sizeof(*p)     // = sizeof(int) = 4;
sizeof(pa)    // = 4;
sizeof(*pa)  // = sizeof(a) = 20;

 

C/C++对于数组和指针是明确区分其类型的。数组的信息包括(类型+大小),而指针则没有大小信息。
在这里附带要说一下,数组经过函数参数传递后,数组的大小信息就丢失了。比如:
void Fun( int a[5] )
{
   int n = sizeof(a);  // n为4,相对于就是sizeof(int*).因为编译器只传递过来了数组的地址,大小信息没有了。
}


所以,上面Fun的参数声明里面的数组的具体数字是被忽略掉的。其等价声明式可以是:
void Fun( int a[10] );
void Fun( int a[] );
void Fun( int *a );


为了需要把大小信息传递到函数里面去,需要额外加一个参数来表示大小信息,这个大小信息需要调用者来提供,并且有调用者来保证大小信息的正确性。
void Fun( int a[], int size );
或者使用stl里的vector模板类来代替:void Fun( vector<int>& v); 因为vector是一个类,它有size()方法能得到大小信息。

 

b)__countof_helper函数的类型是char,而不是_CountofType。因为sizeof( * (char(*)[N]) )【注:这么写只是为了示范说明,

在代码里不能这么写,通不过编译的】 刚好等于N,因为sizeof(char)在任何操作系统上都是=1;

 

c) 这个模板函数只有声明(declaration),没有定义(definition)。没有定义体的原因是:sizeof是在编译期完成的,不需要具体

的定义体出现(上面第二点里说了)。同时,模板推导也是在编译期完成的。所以_countof()整个宏在编译期就完成了。不需要函

数的具体实现体。
如果定义了实现体,_countof依然能工作,但是那些实现体都是不会被用到的,而这些实现体会在程序运行起来之后,加载到内存

里。这样就浪费内存了。
因为__countof_helper()没有定义体,所以使用的时候只能使用_countof宏本身,可不要像下面那样调用__countof_helper()。
int a[5];
__countof_helper(a); // 能编译通过,但是链接时,链接器会告诉你找不到实现体。


d) google chrome浏览器源代码里也有一个类似的宏如下:
template <typename T, size_t N>
char (&ArraySizeHelper(T (&array)[N]))[N];
#define arraysize(array) (sizeof(ArraySizeHelper(array)))

跟_countof的区别是:
1) ArraySizeHelper函数返回的是 char (&)[N],而不是char (*)[N];
2) #define里没有*号。因为sizeof( char (&)[N] ) = N;

 

为什么stdlib.h里的_countof宏,不用&,而要用*?
在winnt.h里其实也有个同样功能的宏RTL_NUMBER_OF_V2【注:VC6里没有,VC2005才提供的】,其中有

一句注释是这样的:
“We could return a reference instead of a pointer but older compilers do not accept that.”:)
That's it.

 

winnt.h 里的注释说明的很好,值得看看。摘抄如下:
//
// RtlpNumberOf is a function that takes a reference to an array of N Ts.
//
// typedef T array_of_T[N];
// typedef array_of_T &reference_to_array_of_T;
//
// RtlpNumberOf returns a pointer to an array of N chars.
// We could return a reference instead of a pointer but older compilers do not accept that.
//
// typedef char array_of_char[N];
// typedef array_of_char *pointer_to_array_of_char;
//
// sizeof(array_of_char) == N
// sizeof(*pointer_to_array_of_char) == N
//
// pointer_to_array_of_char RtlpNumberOf(reference_to_array_of_T);
//
// We never even call RtlpNumberOf, we just take the size of dereferencing its return type.
// We do not even implement RtlpNumberOf, we just decare it.
//
// Attempts to pass pointers instead of arrays to this macro result in compile time errors.
// That is the point.
//
extern "C++" // templates cannot be declared to have 'C' linkage
template <typename T, size_t N>
char (*RtlpNumberOf( UNALIGNED T (&)[N] ))[N];

#define RTL_NUMBER_OF_V2(A) (sizeof(*RtlpNumberOf(A)))

 

P.S. 下面这个链接值得看。其中的解释相当好。
http://blogs.msdn.com/the1/archive/2004/05/07/128242.aspx

 

附图:一些语法

我给你我的代码,看是否正确。共六份。#ifndef _ARP_RECV_H #define _ARP_RECV_H #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <netinet/if_ether.h> #include <net/if_arp.h> #include <net/ethernet.h> /* 以太网帧首部长度 */ #define ETHER_HEADER_LEN sizeof(struct ether_header) /* 整个arp结构长度 */ #define ETHER_ARP_LEN sizeof(struct ether_arp) /* 以太网 + 整个arp结构长度 */ #define ETHER_ARP_PACKET_LEN ETHER_HEADER_LEN + ETHER_ARP_LEN /* IP地址长度 */ #define IP_ADDR_LEN 4 void err_exit(const char *err_msg); #ifndef LOCAL #define LOCAL static #endif #endif。。#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <netinet/if_ether.h> #include <net/if_arp.h> #include <net/ethernet.h> #include"arp_recv.h" #include "nvmp_utils.h" #include "nsd_common.h" #include "libds.h" #include "libdms.h" #include "flashio.h" #include "dms_tool.h" LOCAL void err_exit(const char *err_msg) { perror(err_msg); exit(1); } LOCAL void arp_recv_main() { struct ether_arp *arp_packet; char buf[ETHER_ARP_PACKET_LEN]; int sock_raw_fd, ret_len, i; if ((sock_raw_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP))) == -1) { err_exit("socket()"); } while (1) { bzero(buf, ETHER_ARP_PACKET_LEN); ret_len = recv(sock_raw_fd, buf, ETHER_ARP_PACKET_LEN, 0); if (ret_len > 0) { /* 剥去以太头部 */ arp_packet = (struct ether_arp *)(buf + ETHER_HEADER_LEN); /* arp操作码为代表arp应答 */ if (ntohs(arp_packet->arp_op) == 2) { printf("==========================ARP replay======================\n"); printf("Sender IP address: "); for (i = 0; i < IP_ADDR_LEN; i++) { printf("%u", arp_packet->arp_spa[i]); if(i != (IP_ADDR_LEN-1)) { printf("."); } } printf("\nSender MAC address: "); for (i = 0; i < ETH_ALEN; i++) { printf("%02x", arp_packet->arp_sha[i]); if(i != (ETH_ALEN-1)) { printf(":"); } } printf("\nTarget IP address: "); for (i = 0; i < IP_ADDR_LEN; i++) { printf("%u", arp_packet->arp_tpa[i]); if(i != (IP_ADDR_LEN-1)) { printf("."); } } printf("\nTarget MAC address: "); for (i = 0; i < ETH_ALEN; i++) { printf("%02x", arp_packet->arp_tha[i]); if(i != (ETH_ALEN-1)) { printf(":"); } } printf("\n"); } } } close(sock_raw_fd); } NSD_INIT(arp_recv_main);。。#ifndef _ARP_REQUEST_H #define _ARP_REQUEST_H #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <netinet/if_ether.h> #include <net/ethernet.h> #include <net/if_arp.h> #include <net/if.h> #include <netpacket/packet.h> /* 以太网帧首部长度 */ #define ETHER_HEADER_LEN sizeof(struct ether_header) /* 整个arp结构长度 */ #define ETHER_ARP_LEN sizeof(struct ether_arp) /* 以太网 + 整个arp结构长度 */ #define ETHER_ARP_PACKET_LEN ETHER_HEADER_LEN + ETHER_ARP_LEN /* IP地址长度 */ #define IP_ADDR_LEN 4 /* 广播地址 */ #define BROADCAST_ADDR {0xff, 0xff, 0xff, 0xff, 0xff, 0xff} void err_exit(const char *err_msg); struct ether_arp *fill_arp_packet(const unsigned char *src_mac_addr, const char *src_ip, const char *dst_ip); void arp_request(const char *if_name, const char *dst_ip); #endif。。/* Copyright(c) * * file arp_request.c * brief This is a work of sending arp request. * * author Zhou Shijun * version 1.0.1 * date 24Aug28 * * history * * */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <netinet/if_ether.h> #include <net/ethernet.h> #include <net/if_arp.h> #include <net/if.h> #include <netpacket/packet.h> #include"arp_request.h" void err_exit(const char *err_msg) { perror(err_msg); exit(1); } /* 填充arp包 */ struct ether_arp *fill_arp_packet(const unsigned char *src_mac_addr, const char *src_ip, const char *dst_ip) { struct ether_arp *arp_packet; struct in_addr src_in_addr, dst_in_addr; unsigned char dst_mac_addr[ETH_ALEN] = BROADCAST_ADDR; /* IP地址转换 */ inet_pton(AF_INET, src_ip, &src_in_addr); inet_pton(AF_INET, dst_ip, &dst_in_addr); /* 整个arp包 */ arp_packet = (struct ether_arp *)malloc(ETHER_ARP_LEN); arp_packet->arp_hrd = htons(ARPHRD_ETHER); arp_packet->arp_pro = htons(ETHERTYPE_IP); arp_packet->arp_hln = ETH_ALEN; arp_packet->arp_pln = IP_ADDR_LEN; arp_packet->arp_op = htons(ARPOP_REQUEST); memcpy(arp_packet->arp_sha, src_mac_addr, ETH_ALEN); memcpy(arp_packet->arp_tha, dst_mac_addr, ETH_ALEN); memcpy(arp_packet->arp_spa, &src_in_addr, IP_ADDR_LEN); memcpy(arp_packet->arp_tpa, &dst_in_addr, IP_ADDR_LEN); return arp_packet; } /* arp请求 */ void arp_request(const char *if_name, const char *dst_ip) { struct sockaddr_ll saddr_ll; struct ether_header *eth_header; struct ether_arp *arp_packet; struct ifreq ifr; char buf[ETHER_ARP_PACKET_LEN]; unsigned char src_mac_addr[ETH_ALEN]; unsigned char dst_mac_addr[ETH_ALEN] = BROADCAST_ADDR; char *src_ip; int sock_raw_fd, ret_len, i; if ((sock_raw_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP))) == -1) { err_exit("socket()"); } bzero(&saddr_ll, sizeof(struct sockaddr_ll)); bzero(&ifr, sizeof(struct ifreq)); /* 网卡接口名 */ memcpy(ifr.ifr_name, if_name, strlen(if_name)); /* 获取网卡接口索引 */ if (ioctl(sock_raw_fd, SIOCGIFINDEX, &ifr) == -1) { err_exit("ioctl() get ifindex"); } saddr_ll.sll_ifindex = ifr.ifr_ifindex; saddr_ll.sll_family = PF_PACKET; /* 获取网卡接口IP */ if (ioctl(sock_raw_fd, SIOCGIFADDR, &ifr) == -1) { err_exit("ioctl() get ip"); } src_ip = inet_ntoa(((struct sockaddr_in *)&(ifr.ifr_addr))->sin_addr); printf("local ip:%s\n", src_ip); /* 获取网卡接口MAC地址 */ if (ioctl(sock_raw_fd, SIOCGIFHWADDR, &ifr)) { err_exit("ioctl() get mac"); } memcpy(src_mac_addr, ifr.ifr_hwaddr.sa_data, ETH_ALEN); printf("local mac"); for (i = 0; i < ETH_ALEN; i++) { printf(":%0x", src_mac_addr[i]); } printf("\n"); bzero(buf, ETHER_ARP_PACKET_LEN); /* 填充以太首部 */ eth_header = (struct ether_header *)buf; memcpy(eth_header->ether_shost, src_mac_addr, ETH_ALEN); memcpy(eth_header->ether_dhost, dst_mac_addr, ETH_ALEN); eth_header->ether_type = htons(ETHERTYPE_ARP); /* arp包 */ arp_packet = fill_arp_packet(src_mac_addr, src_ip, dst_ip); memcpy(buf + ETHER_HEADER_LEN, arp_packet, ETHER_ARP_LEN); /* 发送请求 */ ret_len = sendto(sock_raw_fd, buf, ETHER_ARP_PACKET_LEN, 0, (struct sockaddr *)&saddr_ll, sizeof(struct sockaddr_ll)); if (ret_len > 0) { printf("Send successfully!\n"); } close(sock_raw_fd); } LOCAL void arp_request_main(int argc, const char *argv[]) { if (argc != 3) { printf("usage:%s device_name dst_ip\n", argv[0]); exit(1); } arp_request(argv[1], argv[2]); return 0; } NSD_INIT(arp_request_main);。。#ifndef _ARP_SCAN_H #define _ARP_SCAN_H #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <netinet/if_ether.h> #include <net/if_arp.h> #include <netpacket/packet.h> #include <sys/ioctl.h> #include <net/if.h> #include <time.h> #include <pthread.h> /* --- 定义: 参数默认值 --- */ #define ETHER_HEADER_LEN sizeof(struct ether_header) #define ETHER_ARP_LEN sizeof(struct ether_arp) #define ETHER_ARP_PACKET_LEN (ETHER_HEADER_LEN + ETHER_ARP_LEN) #define BROADCAST_ADDR {0xff, 0xff, 0xff, 0xff, 0xff, 0xff} #define IP_ADDR_LEN 4 #define MAX_ARP_ENTRIES 100 /* --- 数据结构定义 --- */ typedef struct { unsigned char mac[ETH_ALEN]; char ip[INET_ADDRSTRLEN]; time_t last_seen; } arp_entry_t; typedef struct { int enabled; /* 功能开关*/ int scan_interval; /* 扫描周期(秒)*/ int entry_lifetime; /* 有效期(秒)*/ int packet_interval; /* 发包间隔(毫秒)*/ char start_ip[INET_ADDRSTRLEN]; char end_ip[INET_ADDRSTRLEN]; arp_entry_t arp_entries[MAX_ARP_ENTRIES]; int entry_count; /* 当前ARP条目数量*/ pthread_mutex_t lock; /* 互斥锁*/ } arp_config_t; arp_config_t arp_config = { .enabled = 1, /* 功能开关开启*/ .scan_interval = 60, /* 扫描周期为60秒*/ .entry_lifetime = 300, /* 有效期为300秒*/ .packet_interval = 100, /* 发包间隔为100毫秒*/ .start_ip = "192.168.1.100", /* 起始IP*/ .end_ip = "192.168.1.200", /* 结束IP*/ .entry_count = 0 /*初始化ARP条目数量*/ }; /* --- 函数定义 --- */ void err_exit(const char *err_msg); struct ether_arp *fill_arp_packet(const unsigned char *src_mac_addr, const char *src_ip, const char *dst_ip); void add_arp_entry(const char *ip, const unsigned char *mac); void cleanup_arp_entries(); void arp_scan(const char *if_name); LOCAL int arp_scan_init(); LOCAL int arp_scan_check(); LOCAL int arp_scan_start(); LOCAL int arp_scan_reload(DS_MSG *msg); LOCAL void arp_scan_main(); #ifndef LOCAL #define LOCAL static #endif #endif。。#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <netinet/if_ether.h> #include <net/if_arp.h> #include <netpacket/packet.h> #include <sys/ioctl.h> #include <net/if.h> #include <time.h> #include <pthread.h> #include "arp_scan.h" void err_exit(const char *err_msg) { perror(err_msg); exit(1); } struct ether_arp *fill_arp_packet(const unsigned char *src_mac_addr, const char *src_ip, const char *dst_ip) { struct ether_arp *arp_packet = (struct ether_arp *)malloc(ETHER_ARP_LEN); unsigned char dst_mac_addr[ETH_ALEN] = BROADCAST_ADDR; struct in_addr src_in_addr, dst_in_addr; inet_pton(AF_INET, src_ip, &src_in_addr); inet_pton(AF_INET, dst_ip, &dst_in_addr); arp_packet->arp_hrd = htons(ARPHRD_ETHER); arp_packet->arp_pro = htons(ETHERTYPE_IP); arp_packet->arp_hln = ETH_ALEN; arp_packet->arp_pln = IP_ADDR_LEN; arp_packet->arp_op = htons(ARPOP_REQUEST); memcpy(arp_packet->arp_sha, src_mac_addr, ETH_ALEN); memcpy(arp_packet->arp_tha, dst_mac_addr, ETH_ALEN); memcpy(arp_packet->arp_spa, &src_in_addr, IP_ADDR_LEN); memcpy(arp_packet->arp_tpa, &dst_in_addr, IP_ADDR_LEN); return arp_packet; } void add_arp_entry(const char *ip, const unsigned char *mac) { pthread_mutex_lock(&arp_config.lock); if (arp_config.entry_count < MAX_ARP_ENTRIES) { strcpy(arp_config.arp_entries[arp_config.entry_count].ip, ip); memcpy(arp_config.arp_entries[arp_config.entry_count].mac, mac, ETH_ALEN); arp_config.arp_entries[arp_config.entry_count].last_seen = time(NULL); arp_config.entry_count++; } pthread_mutex_unlock(&arp_config.lock); } void cleanup_arp_entries() { pthread_mutex_lock(&arp_config.lock); time_t now = time(NULL); for (int i = 0; i < arp_config.entry_count; i++) { if (now - arp_config.arp_entries[i].last_seen > arp_config.entry_lifetime) { // 删除过期条目 for (int j = i; j < arp_config.entry_count - 1; j++) { arp_config.arp_entries[j] = arp_config.arp_entries[j + 1]; } arp_config.entry_count--; i--; // 调整索引 } } pthread_mutex_unlock(&arp_config.lock); } void arp_scan(const char *if_name) { struct sockaddr_ll saddr_ll; struct ether_header *eth_header; struct ether_arp *arp_packet; struct ifreq ifr; char buf[ETHER_ARP_PACKET_LEN]; unsigned char src_mac_addr[ETH_ALEN]; char src_ip[INET_ADDRSTRLEN]; int sock_raw_fd; if ((sock_raw_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP))) == -1) err_exit("socket()"); memset(&saddr_ll, 0, sizeof(struct sockaddr_ll)); memset(&ifr, 0, sizeof(struct ifreq)); memcpy(ifr.ifr_name, "eth0", strlen("eth0")); // 使用默认接口名 if (ioctl(sock_raw_fd, SIOCGIFINDEX, &ifr) == -1) err_exit("ioctl() get ifindex"); saddr_ll.sll_ifindex = ifr.ifr_ifindex; saddr_ll.sll_family = PF_PACKET; if (ioctl(sock_raw_fd, SIOCGIFADDR, &ifr) == -1) err_exit("ioctl() get ip"); strcpy(src_ip, inet_ntoa(((struct sockaddr_in *)&(ifr.ifr_addr))->sin_addr)); if (ioctl(sock_raw_fd, SIOCGIFHWADDR, &ifr) == -1) err_exit("ioctl() get mac"); memcpy(src_mac_addr, ifr.ifr_hwaddr.sa_data, ETH_ALEN); while (arp_config.enabled) { char target_ip[INET_ADDRSTRLEN]; for (int i = inet_addr(arp_config.start_ip); i <= inet_addr(arp_config.end_ip); i++) { struct in_addr addr; addr.s_addr = i; strcpy(target_ip, inet_ntoa(addr)); memset(buf, 0, ETHER_ARP_PACKET_LEN); eth_header = (struct ether_header *)buf; memcpy(eth_header->ether_shost, src_mac_addr, ETH_ALEN); memcpy(eth_header->ether_dhost, BROADCAST_ADDR, ETH_ALEN); eth_header->ether_type = htons(ETHERTYPE_ARP); arp_packet = fill_arp_packet(src_mac_addr, src_ip, target_ip); memcpy(buf + ETHER_HEADER_LEN, arp_packet, ETHER_ARP_LEN); sendto(sock_raw_fd, buf, ETHER_ARP_PACKET_LEN, 0, (struct sockaddr *)&saddr_ll, sizeof(struct sockaddr_ll)); free(arp_packet); printf("ARP request sent to %s\n", target_ip); usleep(arp_config.packet_interval * 1000); // 发包间隔 } cleanup_arp_entries(); // 清理过期的ARP条目 sleep(arp_config.scan_interval); // 等待下一次扫描 } close(sock_raw_fd); } LOCAL int arp_scan_init() { if (0 == ds_read(ARP_DATA_PATH, &arp_config_t, sizeof(arp_config_t))) { return SLP_ESYSTEM; } /* Initialize socket */ if ((sockfd = socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, htons(ETH_P_ARP))) < 0) { perror("socket"); return ERROR; } /* Initialize ip_mac_table */ U8 table_len = arp_config_t.end_ip[3] - arp_config_t.start_ip[3] + 1; ip_mac_table = (ARP_IPMAC_TABLE *)malloc(sizeof(ARP_IPMAC_TABLE) * table_len); struct timeval cur_time; gettimeofday(&cur_time, NULL); for (int i = 0; i < table_len; i++) { ip_mac_table[i].renew_time = cur_time; ip_mac_table[i].device_is_exist = 0; memset(ip_mac_table[i].mac, 0, ETH_ALEN); } msg_attach_handler(MSGID_DMS_CMD, arp_call_handle); return OK; } LOCAL int arp_scan_check() { if (arp_config_t.end_ip[2] != sender_ip[2] || arp_config_t.start_ip[2] != sender_ip[2] || arp_config_t.end_ip[3] <= arp_config_t.start_ip[3]) { ARP_DEBUG("Invalid IP address range, please check.\n"); return ERROR; } return OK; } LOCAL int arp_scan_start() { /* Start address expiration check thread */ pthread_create(&time_tid, NULL, check_table_renew_time, NULL); pthread_detach(time_tid); /* Scan loop */ while (scanning_flag) { scan_once(); usleep(arpco.scan_interval); } return OK; } LOCAL int arp_scan_reload(DS_MSG *msg) { /* Stop scanning */ scanning_flag = 0; if (ds_path_id_exist(msg->id, msg->num, ARP_DATA_PATH)) { arp_config_t arp_data; memset(&arp_data, 0, sizeof(arp_config_t)); if (0 == ds_read(ARP_DATA_PATH, (U8 *)&arp_data, sizeof(arp_config_t))) { ARP_ERROR("Read arp data ERROR"); return ERROR; } /* Reload params */ memcpy(arp_config_t.start_ip, arp_data.start_ip, ARP_IPV4_LEN); memcpy(arp_config_t.end_ip, arp_data.end_ip, ARP_IPV4_LEN); arp_config_t.scan_interval = arp_data.scan_interval; arp_config_t.packet_interval = arp_data.packet_interval; arp_config_t.entry_lifetime = arp_data.entry_lifetime; /* Cancel old checking thread */ pthread_cancel(time_tid); /* Realloc ip_mac_table */ U8 table_len = arp_config_t.end_ip[3] - arp_config_t.start_ip[3] + 1; ARP_IPMAC_TABLE *new_table; new_table = (ARP_IPMAC_TABLE *)realloc(ip_mac_table, sizeof(ARP_IPMAC_TABLE) * table_len); if (NULL == new_table) { ARP_ERROR("Realloc ipmac_table ERROR"); free(ip_mac_table); ip_mac_table = NULL; return ERROR; } ip_mac_table = new_table; struct timeval cur_time; gettimeofday(&cur_time, NULL); for (int i = 0; i < table_len; i++) { ip_mac_table[i].renew_time = cur_time; ip_mac_table[i].device_is_exist = 0; memset(ip_mac_table[i].mac, 0, ETH_ALEN); } /* Restart checking thread */ /* Start scanning */ scanning_flag = 1; arp_scan_start(); return OK; } } LOCAL void arp_scan_main() { DS_OPT_DESC main_options[] = { DS_SWITCH_OPT(arp_config_t, enabled, OPT_FLAG_NORM), DS_STR_OPT(arp_config_t, start_ip, OPT_FLAG_NORM), DS_STR_OPT(arp_config_t, end_ip, OPT_FLAG_NORM), DS_S32_OPT(arp_config_t, scan_interval, OPT_FLAG_NORM), DS_S32_OPT(arp_config_t, packet_interval, OPT_FLAG_NORM), DS_S32_OPT(arp_config_t, entry_lifetime, OPT_FLAG_NORM), }; DS_SEG_DESC main_segments[] = { DS_STRUCT_SEG("config", SEG_LIM_RW, SEG_GROUP_ROOT, arp_config_t, main_options), }; DS_SECT_DESC arp_scan_sections[] = { DS_STRUCT_SECT("config", main_segments), }; DS_TBL_DESC arp_scan_tables[] = { DS_STRUCT_TBL("arp_scan", TBL_ATTR_STC, arp_scan_sections), }; DS_DAT_MON_DESC arp_data_monitor[] = { DS_DAT_MON(ARP_DATA_PATH, DATA_ATTRI_NOTIFY), }; DS_MOD_DESC arp_scan_module = DS_STRUCT_MOD("arp_scan", arp_scan_init, arp_scan_check, arp_scan_reload, arp_scan_start, arp_scan_tables, arp_data_monitor); MODULE *module_node = ds_register_module("arp_scan", &arp_module); NSD_ASSERT(NULL != module_node); } NSD_INIT(arp_scan_main);
最新发布
08-27
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值