最近写了一个局域网IP段扫描器,思路是发送一个PING数据包,得到后解析,然后循环,这要就能获得局域网中在线主机的信息。
以下是我的源码:
-
/*
-
*Name:ping.c
-
*Module:PING
-
*Description:realize the funtion that check a PC's status
-
*History:2015/5/13
-
*/
-
#include "./ping.h"
-
-
static struct timeval tvrecv;
-
static char send_packet[PACKET_SIZE];
-
static char recv_packet[PACKET_SIZE];
-
static struct sockaddr_in send_addr;
-
static struct sockaddr_in recv_addr;
-
static int sock_fd;
-
static int nsend;
-
static int nrecv;
-
static pid_t pid;
-
static int ret;
-
static int datalength = 56;
-
-
static int pack(int seqno);
-
static int unpack(int len);
-
static void send_packet_func();
-
static void recv_packet_func();
-
static unsigned short cal_chksum(unsigned short *data,int len);
-
-
int __ping(char *addr)//接口函数,供外部调用,addr为目的点分十进制IP地址
-
{
-
int size;
-
if(inet_addr(addr)==INADDR_NONE){
-
printf("IP addr is error\n");
-
exit(EXIT_FAILURE);
-
}
-
nsend=0;
-
nrecv=0;
-
-
//get the autority
-
//initialize the socket
-
sock_fd = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);
-
if(sock_fd == -1){
-
perror("socket error");
-
exit(EXIT_FAILURE);
-
}
-
//initialize the addr
-
bzero(&send_addr,sizeof(send_addr));
-
send_addr.sin_family = AF_INET;
-
send_addr.sin_port = htons(0);
-
send_addr.sin_addr.s_addr = inet_addr(addr);
-
-
setsockopt(sock_fd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size) );
-
pid = getpid();
-
-
send_packet_func();
-
recv_packet_func();
-
-
return 0;
-
}
-
-
//发送PING数据包
-
void send_packet_func()
-
{
-
int packetsize;
-
while(nsend < SEND_RECV_TIMES){
-
packetsize=pack(nsend);
-
nsend++;
-
ret = sendto(sock_fd,send_packet,packetsize,0,(struct sockaddr *)&send_addr,sizeof(send_addr));
-
if(ret < 0){
-
perror("sendto error");
-
continue;
-
}
-
}
-
}
-
//接收PING数据包
-
void recv_packet_func()
-
{
-
int recvlen;
-
recvlen = sizeof(recv_addr);
-
while(nrecv < nsend)
-
{
-
nrecv++;
-
ret = recvfrom(sock_fd,recv_packet,sizeof(recv_packet),0,(struct sockaddr *)&recv_addr,&recvlen);
-
if(ret == 84){
-
printf("%s is on",inet_ntoa(send_addr.sin_addr));
-
}
-
else{
-
printf("%s is off",inet_ntoa(send_addr.sin_addr);
-
}
-
}
-
}
-
//构造ICMP报文
-
int pack(int seqno)
-
{
-
int packetsize;
-
struct icmp *_icmp;
-
struct timeval *tval;
-
-
_icmp = (struct icmp*)send_packet;
-
_icmp->icmp_type = ICMP_ECHO;
-
_icmp->icmp_code = 0;
-
_icmp->icmp_cksum = 0;
-
_icmp->icmp_id = pid;
-
_icmp->icmp_seq = seqno;
-
-
packetsize = 8 + datalength;
-
tval = (struct timeval *)_icmp->icmp_data;
-
gettimeofday(tval,NULL);
-
_icmp->icmp_cksum = cal_chksum((unsigned short *)_icmp,packetsize);
-
-
return packetsize;
-
}
-
//计算校验位
-
unsigned short cal_chksum(unsigned short *addr,int len)
-
{
-
int nleft=len;
-
int sum=0;
-
unsigned short *w=addr;
-
unsigned short answer=0;
-
-
/*把ICMP报头二进制数据以2字节为单位累加起来*/
-
while(nleft>1){
-
sum+=*w++;
-
nleft-=2;
-
}
-
if(nleft==1){
-
*(unsigned char *)(&answer)=*(unsigned char *)w;
-
sum+=answer;
-
}
-
sum=(sum>>16)+(sum&0xffff);
-
sum+=(sum>>16);
-
answer=~sum;
-
return answer;
-
}
说明,利用IP地址构建一个网络层原始套接字,然后自己构造ICMP报文,发送数据包。接收的时候,是利用同一个套接字接收的,程序虽然可以运行,但是对于无连接的套接字,建议大家建立一个发送套接字和一个接收套接字。我通过抓取数据包来分析有recvfrom有三种情况(前提,我构造的是ICMP报文64字节,整个IP数据包是84字节):
1)正确接收ICMP回显报文,长度是84字节;
2)接收报文,但是数据字节长度为112字节;
3)一直没有接收到报文,程序在recvfrom阻塞等待;
所以我通过recvfrom的返回值--接收到了多少字节来判断是否接收到争取的数据包,如果是第1)中情况,就显示该IP在线,否则,显示不在线。
此处,任然还有一个问题,若IP段是192.168.1.1/24~192.168.1.254/24;当ping到192.168.1.44的时候,出现了上述的第三种情况,那样程序就一直阻塞等待,想到的方法是在此处加一个定时器alarm。加了定时器的源码如下:
-
/*
-
*Name:ping.c
-
*Module:PING
-
*Description:realize the funtion that check a PC's status
-
*History:2015/5/13
-
*/
-
#include "./ping.h"
-
-
static struct timeval tvrecv;
-
static char send_packet[PACKET_SIZE];
-
static char recv_packet[PACKET_SIZE];
-
static struct sockaddr_in send_addr;
-
static struct sockaddr_in recv_addr;
-
static int sock_fd;
-
static int nsend;
-
static int nrecv;
-
static pid_t pid;
-
static int ret;
-
static int datalength = 56;
-
-
-
static void dealSigAlarm(int signo);//定时器退出的时候,要执行的函数
-
-
static int pack(int seqno);
-
static int unpack(int len);
-
static void send_packet_func();
-
static void recv_packet_func();
-
static unsigned short cal_chksum(unsigned short *data,int len);
-
-
int __ping(char *addr)
-
{
-
int size;
-
if(inet_addr(addr)==INADDR_NONE){
-
printf("IP addr is error\n");
-
exit(EXIT_FAILURE);
-
}
-
nsend=0;
-
nrecv=0;
-
-
//get the autority
-
//initialize the socket
-
sock_fd = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);
-
if(sock_fd == -1){
-
perror("socket error");
-
exit(EXIT_FAILURE);
-
}
-
//initialize the addr
-
bzero(&send_addr,sizeof(send_addr));
-
send_addr.sin_family = AF_INET;
-
send_addr.sin_port = htons(0);
-
send_addr.sin_addr.s_addr = inet_addr(addr);
-
-
setsockopt(sock_fd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size) );
-
pid = getpid();
-
-
send_packet_func();
-
recv_packet_func();
-
-
return 0;
-
}
-
-
void send_packet_func()
-
{
-
int packetsize;
-
while(nsend < SEND_RECV_TIMES){
-
packetsize=pack(nsend);
-
nsend++;
-
ret = sendto(sock_fd,send_packet,packetsize,0,(struct sockaddr *)&send_addr,sizeof(send_addr));
-
if(ret < 0){
-
perror("sendto error");
-
continue;
-
}
-
}
-
}
-
-
void recv_packet_func()
-
{
-
int recvlen;
-
recvlen = sizeof(recv_addr);
-
-
//定时器初始化
-
struct sigaction alrmact;
-
bzero(&alrmact,sizeof(alrmact));
-
alrmact.sa_handler=dealSigAlarm;
-
alrmact.sa_flags=SA_NOMASK;
-
alrmact.sa_restorer=NULL;
-
sigaction(SIGALRM,&alrmact,NULL);
-
-
while(nrecv < nsend)
-
{
-
alarm(5)//定时5秒
-
nrecv++;
-
ret = recvfrom(sock_fd,recv_packet,sizeof(recv_packet),0,(struct sockaddr *)&recv_addr,&recvlen);
-
if(ret == 84){
-
printf("%s is on",inet_ntoa(send_addr.sin_addr));
-
}
-
else{
-
printf("%s is off",inet_ntoa(send_addr.sin_addr));
-
}
-
}
-
alarm(0);//定时器退出
-
}
-
int pack(int seqno)
-
{
-
int packetsize;
-
struct icmp *_icmp;
-
struct timeval *tval;
-
-
_icmp = (struct icmp*)send_packet;
-
_icmp->icmp_type = ICMP_ECHO;
-
_icmp->icmp_code = 0;
-
_icmp->icmp_cksum = 0;
-
_icmp->icmp_id = pid;
-
_icmp->icmp_seq = seqno;
-
-
packetsize = 8 + datalength;
-
tval = (struct timeval *)_icmp->icmp_data;
-
gettimeofday(tval,NULL);
-
_icmp->icmp_cksum = cal_chksum((unsigned short *)_icmp,packetsize);
-
-
return packetsize;
-
}
-
-
unsigned short cal_chksum(unsigned short *addr,int len)
-
{
-
int nleft=len;
-
int sum=0;
-
unsigned short *w=addr;
-
unsigned short answer=0;
-
-
/*把ICMP报头二进制数据以2字节为单位累加起来*/
-
while(nleft>1){
-
sum+=*w++;
-
nleft-=2;
-
}
-
if(nleft==1){
-
*(unsigned char *)(&answer)=*(unsigned char *)w;
-
sum+=answer;
-
}
-
sum=(sum>>16)+(sum&0xffff);
-
sum+=(sum>>16);
-
answer=~sum;
-
return answer;
-
}
定时器定时5s,此处也可以设置为2s,这个具体看响应时间来设定。此时就避免了第三种情况。这样整个IP段都扫描到了。
新问题的出现:需要获取到在线主机的MAC地址。
思路:将上述程序的socket()函数中的AF_INET,改成PF_PACKET,第三个参数改成htons(ETH_P_IP),创建一个链路层原始套接字,然后封包的时候,自己构造IP头部,以太头。
结果:报错,说sendto的参数invalid;
办法:当使用链路层原始套接字的时候,地址信息不能用sockaddr_in这个结构体,而要使用sockaddr_ll这个结构体。并且联合struct ifreq 这个结构体和相关函数获取指定网口的IP地址和MAC地址信息。并且思路应该改为用一个网络层原始套接字发送PING数据包,然后另外创建一个链路层原始套接字,来接收PING回显数据包。
改进后的源码如下:
-
/*
-
*Name:ping.c
-
*Module:PING
-
*Description:realize the funtion that check a PC's status
-
*History:2015/5/13
-
*/
-
#include "./ping.h"
-
-
static struct timeval tvrecv;
-
static char send_packet[PACKET_SIZE];
-
static char recv_packet[PACKET_SIZE];
-
static struct sockaddr_in send_addr;
-
static int sock_fd;
-
-
static struct sockaddr_ll test_addr;
-
static int test_fd;
-
static void display_mac(char *ptr);
-
static u_int8_t mac[ETH_ALEN];
-
char mac_address[32];
-
-
-
static int nsend;
-
static int nrecv;
-
static pid_t pid;
-
static int ret;
-
static int datalength = 56;
-
static int times=0;
-
-
-
static void dealSigAlarm(int signo);
-
static int pack(int seqno);
-
static int unpack(int len);
-
static void send_packet_func();
-
static void recv_packet_func();
-
static unsigned short cal_chksum(unsigned short *data,int len);
-
-
int __ping(char *addr)
-
{
-
int size;
-
if(inet_addr(addr)==INADDR_NONE){
-
printf("IP addr is error\n");
-
exit(EXIT_FAILURE);
-
}
-
nsend=0;
-
nrecv=0;
-
-
//get the autority
-
//initialize the socket
-
sock_fd = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);
-
if(sock_fd == -1){
-
perror("socket error");
-
exit(EXIT_FAILURE);
-
}
-
//initialize the addr
-
bzero(&send_addr,sizeof(send_addr));
-
send_addr.sin_family = AF_INET;
-
send_addr.sin_port = htons(0);
-
send_addr.sin_addr.s_addr = inet_addr(addr);
-
-
setsockopt(sock_fd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size) );
-
pid = getpid();
-
-
send_packet_func();
-
close(sock_fd);
-
test_fd = socket(PF_PACKET,SOCK_RAW,htons(ETH_P_IP));
-
recv_packet_func();
-
close(test_fd);
-
-
return 0;
-
}
-
-
void send_packet_func()
-
{
-
int packetsize;
-
while(nsend < SEND_RECV_TIMES){
-
packetsize=pack(nsend);
-
nsend++;
-
ret = sendto(sock_fd,send_packet,packetsize,0,(struct sockaddr *)&send_addr,sizeof(send_addr));
-
if(ret < 0){
-
perror("sendto error");
-
continue;
-
}
-
}
-
}
-
-
void recv_packet_func()
-
{
-
int recvlen;
-
int used;
-
u_int8_t protocol=1;
-
u_int8_t type=0;
-
recvlen = sizeof(test_addr);
-
-
while(nrecv < nsend)
-
{
-
nrecv++;
-
times++;
-
ret = recvfrom(sock_fd,recv_packet,sizeof(recv_packet),0,(struct sockaddr *)&test_addr,&recvlen);
-
if(recv_packet[23]==protocol){
-
if(recv_packet[34]==type){
-
printf("MAC\t%02X:%02X:%02X:%02X:%02X:%02X\n",recv_packet[6],recv_packet[7],recv_packet[8],\
-
recv_packet[9],recv_packet[10],recv_packet[11]);
-
printf("IP \t%s\n",inet_ntoa(send_addr.sin_addr));
-
printf("is on!\n");
-
}
-
}
-
else{
-
printf("MAC\t%02X:%02X:%02X:%02X:%02X:%02X\n",recv_packet[6],recv_packet[7],recv_packet[8],\
-
recv_packet[9],recv_packet[10],recv_packet[11]);
-
printf("IP \t%s\n",inet_ntoa(send_addr.sin_addr));
-
printf("is off!\n");
-
}
-
}
-
}
-
-
int pack(int seqno)
-
{
-
int packetsize;
-
struct icmp *_icmp;
-
struct timeval *tval;
-
-
_icmp = (struct icmp*)send_packet;
-
_icmp->icmp_type = ICMP_ECHO;
-
_icmp->icmp_code = 0;
-
_icmp->icmp_cksum = 0;
-
_icmp->icmp_id = pid;
-
_icmp->icmp_seq = seqno;
-
-
packetsize = 8 + datalength;
-
tval = (struct timeval *)_icmp->icmp_data;
-
gettimeofday(tval,NULL);
-
_icmp->icmp_cksum = cal_chksum((unsigned short *)_icmp,packetsize);
-
-
return packetsize;
-
}
-
-
unsigned short cal_chksum(unsigned short *addr,int len)
-
{
-
int nleft=len;
-
int sum=0;
-
unsigned short *w=addr;
-
unsigned short answer=0;
-
-
/*把ICMP报头二进制数据以2字节为单位累加起来*/
-
while(nleft>1){
-
sum+=*w++;
-
nleft-=2;
-
}
-
if(nleft==1){
-
*(unsigned char *)(&answer)=*(unsigned char *)w;
-
sum+=answer;
-
}
-
sum=(sum>>16)+(sum&0xffff);
-
sum+=(sum>>16);
-
answer=~sum;
-
return answer;
-
}
通过抓包有两种情况:
1)正确接收,立刻就能接收到,数据帧字节为98字节(84字节的IP数据报+14字节的以太头);
2)没有回显,是其他不相关数据包;
此时,不能通过接收到的数据包字节长度来唯一确定PING回显数据包,所以我通过链路层的协议字段和IP层类型字段来判断是否是ICMP回显。
此时没有阻塞等待的情况,应为该接收原始套接字能匹配上发往本机IP的所有IP数据包。
阅读(1946) | 评论(0) | 转发(0) |