Chinaunix首页 | 论坛 | 博客
  • 博客访问: 76111
  • 博文数量: 17
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 67
  • 用 户 组: 普通用户
  • 注册时间: 2015-03-23 11:45
文章分类

全部博文(17)

文章存档

2015年(17)

我的朋友

分类: LINUX

2015-05-25 22:04:19

        最近写了一个局域网IP段扫描器,思路是发送一个PING数据包,得到后解析,然后循环,这要就能获得局域网中在线主机的信息。
        以下是我的源码:
        

点击(此处)折叠或打开

  1. /*
  2.  *Name:ping.c
  3.  *Module:PING
  4.  *Description:realize the funtion that check a PC's status
  5.  *History:2015/5/13
  6.  */
  7. #include "./ping.h"

  8. static struct timeval tvrecv;
  9. static char send_packet[PACKET_SIZE];
  10. static char recv_packet[PACKET_SIZE];
  11. static struct sockaddr_in send_addr;
  12. static struct sockaddr_in recv_addr;
  13. static int sock_fd;
  14. static int nsend;
  15. static int nrecv;
  16. static pid_t pid;
  17. static int ret;
  18. static int datalength = 56;

  19. static int pack(int seqno);
  20. static int unpack(int len);
  21. static void send_packet_func();
  22. static void recv_packet_func();
  23. static unsigned short cal_chksum(unsigned short *data,int len);

  24. int __ping(char *addr)//接口函数,供外部调用,addr为目的点分十进制IP地址
  25. {
  26.     int size;
  27.     if(inet_addr(addr)==INADDR_NONE){
  28.         printf("IP addr is error\n");
  29.         exit(EXIT_FAILURE);
  30.     }
  31.     nsend=0;
  32.     nrecv=0;

  33.     //get the autority
  34.     //initialize the socket
  35.     sock_fd = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);
  36.     if(sock_fd == -1){
  37.         perror("socket error");
  38.         exit(EXIT_FAILURE);
  39.     }
  40.     //initialize the addr
  41.     bzero(&send_addr,sizeof(send_addr));
  42.     send_addr.sin_family = AF_INET;
  43.     send_addr.sin_port = htons(0);
  44.     send_addr.sin_addr.s_addr = inet_addr(addr);

  45.     setsockopt(sock_fd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size) );
  46.     pid = getpid();        
  47.     
  48.     send_packet_func();
  49.     recv_packet_func();

  50.     return 0;
  51. }

  52. //发送PING数据包
  53. void send_packet_func()
  54. {
  55.     int packetsize;
  56.     while(nsend < SEND_RECV_TIMES){
  57.         packetsize=pack(nsend);
  58.         nsend++;
  59.         ret = sendto(sock_fd,send_packet,packetsize,0,(struct sockaddr *)&send_addr,sizeof(send_addr));
  60.         if(ret < 0){
  61.             perror("sendto error");
  62.             continue;
  63.         }
  64.     }
  65. }
  66. //接收PING数据包
  1. void recv_packet_func()
  2. {
  3.     int recvlen;
  4.     recvlen = sizeof(recv_addr);
  5.     while(nrecv < nsend)
  6.     {
  7.         nrecv++;
  8.         ret = recvfrom(sock_fd,recv_packet,sizeof(recv_packet),0,(struct sockaddr *)&recv_addr,&recvlen);
  9.         if(ret == 84){
  10.             printf("%s is on",inet_ntoa(send_addr.sin_addr));
  11.         }
  12.         else
  13.             printf("%s is off",inet_ntoa(send_addr.sin_addr);
  14.         }
  15.     }
  16. }
  17. //构造ICMP报文
  18. int pack(int seqno)
  19. {
  20.     int packetsize;
  21.     struct icmp *_icmp;
  22.     struct timeval *tval;
  23.     
  24.     _icmp = (struct icmp*)send_packet;
  25.     _icmp->icmp_type = ICMP_ECHO;
  26.     _icmp->icmp_code = 0;
  27.     _icmp->icmp_cksum = 0;
  28.     _icmp->icmp_id = pid;
  29.     _icmp->icmp_seq = seqno;
  30.     
  31.     packetsize = 8 + datalength;
  32.     tval = (struct timeval *)_icmp->icmp_data;
  33.     gettimeofday(tval,NULL);
  34.     _icmp->icmp_cksum = cal_chksum((unsigned short *)_icmp,packetsize);
  35.     
  36.     return packetsize;
  37. }
  38. //计算校验位
  39. unsigned short cal_chksum(unsigned short *addr,int len)
  40. {
  41.     int nleft=len;
  42.         int sum=0;
  43.         unsigned short *w=addr;
  44.         unsigned short answer=0;
  45.         
  46.     /*把ICMP报头二进制数据以2字节为单位累加起来*/
  47.         while(nleft>1){
  48.         sum+=*w++;
  49.                 nleft-=2;
  50.         }
  51.     if(nleft==1){
  52.         *(unsigned char *)(&answer)=*(unsigned char *)w;
  53.                 sum+=answer;
  54.         }
  55.         sum=(sum>>16)+(sum&0xffff);
  56.         sum+=(sum>>16);
  57.         answer=~sum;
  58.         return answer;
  59. }
            说明,利用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。加了定时器的源码如下:
            

点击(此处)折叠或打开

  1. /*
  2.  *Name:ping.c
  3.  *Module:PING
  4.  *Description:realize the funtion that check a PC's status
  5.  *History:2015/5/13
  6.  */
  7. #include "./ping.h"

  8. static struct timeval tvrecv;
  9. static char send_packet[PACKET_SIZE];
  10. static char recv_packet[PACKET_SIZE];
  11. static struct sockaddr_in send_addr;
  12. static struct sockaddr_in recv_addr;
  13. static int sock_fd;
  14. static int nsend;
  15. static int nrecv;
  16. static pid_t pid;
  17. static int ret;
  18. static int datalength = 56;


  19. static void dealSigAlarm(int signo);//定时器退出的时候,要执行的函数

  20. static int pack(int seqno);
  21. static int unpack(int len);
  22. static void send_packet_func();
  23. static void recv_packet_func();
  24. static unsigned short cal_chksum(unsigned short *data,int len);

  25. int __ping(char *addr)
  26. {
  27.     int size;
  28.     if(inet_addr(addr)==INADDR_NONE){
  29.         printf("IP addr is error\n");
  30.         exit(EXIT_FAILURE);
  31.     }
  32.     nsend=0;
  33.     nrecv=0;

  34.     //get the autority
  35.     //initialize the socket
  36.     sock_fd = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);
  37.     if(sock_fd == -1){
  38.         perror("socket error");
  39.         exit(EXIT_FAILURE);
  40.     }
  41.     //initialize the addr
  42.     bzero(&send_addr,sizeof(send_addr));
  43.     send_addr.sin_family = AF_INET;
  44.     send_addr.sin_port = htons(0);
  45.     send_addr.sin_addr.s_addr = inet_addr(addr);

  46.     setsockopt(sock_fd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size) );
  47.     pid = getpid();        
  48.     
  49.     send_packet_func();
  50.     recv_packet_func();

  51.     return 0;
  52. }

  53. void send_packet_func()
  54. {
  55.     int packetsize;
  56.     while(nsend < SEND_RECV_TIMES){
  57.         packetsize=pack(nsend);
  58.         nsend++;
  59.         ret = sendto(sock_fd,send_packet,packetsize,0,(struct sockaddr *)&send_addr,sizeof(send_addr));
  60.         if(ret < 0){
  61.             perror("sendto error");
  62.             continue;
  63.         }
  64.     }
  65. }

  66. void recv_packet_func()
  67. {
  68.     int recvlen;
  69.     recvlen = sizeof(recv_addr);
  70.     
  71.     //定时器初始化
  72.     struct sigaction alrmact;
  73.     bzero(&alrmact,sizeof(alrmact));
  74.     alrmact.sa_handler=dealSigAlarm;
  75.     alrmact.sa_flags=SA_NOMASK;
  76.     alrmact.sa_restorer=NULL;
  77.     sigaction(SIGALRM,&alrmact,NULL);
  78.     
  79.     while(nrecv < nsend)
  80.     {
  81.         alarm(5)//定时5秒
  82.         nrecv++;
  83.         ret = recvfrom(sock_fd,recv_packet,sizeof(recv_packet),0,(struct sockaddr *)&recv_addr,&recvlen);
  84.         if(ret == 84){
  85.             printf("%s is on",inet_ntoa(send_addr.sin_addr));
  86.         }
  87.         else{
  88.             printf("%s is off",inet_ntoa(send_addr.sin_addr));
  89.         }
  90.     }
  91.     alarm(0);//定时器退出
  92. }
  93. int pack(int seqno)
  94. {
  95.     int packetsize;
  96.     struct icmp *_icmp;
  97.     struct timeval *tval;
  98.     
  99.     _icmp = (struct icmp*)send_packet;
  100.     _icmp->icmp_type = ICMP_ECHO;
  101.     _icmp->icmp_code = 0;
  102.     _icmp->icmp_cksum = 0;
  103.     _icmp->icmp_id = pid;
  104.     _icmp->icmp_seq = seqno;
  105.     
  106.     packetsize = 8 + datalength;
  107.     tval = (struct timeval *)_icmp->icmp_data;
  108.     gettimeofday(tval,NULL);
  109.     _icmp->icmp_cksum = cal_chksum((unsigned short *)_icmp,packetsize);
  110.     
  111.     return packetsize;
  112. }

  113. unsigned short cal_chksum(unsigned short *addr,int len)
  114. {
  115.     int nleft=len;
  116.         int sum=0;
  117.         unsigned short *w=addr;
  118.         unsigned short answer=0;
  119.         
  120.     /*把ICMP报头二进制数据以2字节为单位累加起来*/
  121.         while(nleft>1){
  122.         sum+=*w++;
  123.                 nleft-=2;
  124.         }
  125.     if(nleft==1){
  126.         *(unsigned char *)(&answer)=*(unsigned char *)w;
  127.                 sum+=answer;
  128.         }
  129.         sum=(sum>>16)+(sum&0xffff);
  130.         sum+=(sum>>16);
  131.         answer=~sum;
  132.         return answer;
  133. }
            定时器定时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回显数据包。
           改进后的源码如下:

点击(此处)折叠或打开

  1. /*
  2.  *Name:ping.c
  3.  *Module:PING
  4.  *Description:realize the funtion that check a PC's status
  5.  *History:2015/5/13
  6.  */
  7. #include "./ping.h"

  8. static struct timeval tvrecv;
  9. static char send_packet[PACKET_SIZE];
  10. static char recv_packet[PACKET_SIZE];
  11. static struct sockaddr_in send_addr;
  12. static int sock_fd;

  13. static struct sockaddr_ll test_addr;
  14. static int test_fd;
  15. static void display_mac(char *ptr);
  16. static u_int8_t mac[ETH_ALEN];
  17. char mac_address[32];


  18. static int nsend;
  19. static int nrecv;
  20. static pid_t pid;
  21. static int ret;
  22. static int datalength = 56;
  23. static int times=0;


  24. static void dealSigAlarm(int signo);
  25. static int pack(int seqno);
  26. static int unpack(int len);
  27. static void send_packet_func();
  28. static void recv_packet_func();
  29. static unsigned short cal_chksum(unsigned short *data,int len);

  30. int __ping(char *addr)
  31. {
  32.     int size;
  33.     if(inet_addr(addr)==INADDR_NONE){
  34.         printf("IP addr is error\n");
  35.         exit(EXIT_FAILURE);
  36.     }
  37.     nsend=0;
  38.     nrecv=0;

  39.     //get the autority
  40.     //initialize the socket
  41.     sock_fd = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);
  42.     if(sock_fd == -1){
  43.         perror("socket error");
  44.         exit(EXIT_FAILURE);
  45.     }
  46.     //initialize the addr
  47.     bzero(&send_addr,sizeof(send_addr));
  48.     send_addr.sin_family = AF_INET;
  49.     send_addr.sin_port = htons(0);
  50.     send_addr.sin_addr.s_addr = inet_addr(addr);

  51.     setsockopt(sock_fd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size) );
  52.     pid = getpid();        
  53.     
  54.     send_packet_func();
  55.     close(sock_fd);
  56.     test_fd = socket(PF_PACKET,SOCK_RAW,htons(ETH_P_IP));
  57.     recv_packet_func();
  58.     close(test_fd);
  59.     
  60.     return 0;
  61. }

  62. void send_packet_func()
  63. {
  64.     int packetsize;
  65.     while(nsend < SEND_RECV_TIMES){
  66.         packetsize=pack(nsend);
  67.         nsend++;
  68.         ret = sendto(sock_fd,send_packet,packetsize,0,(struct sockaddr *)&send_addr,sizeof(send_addr));
  69.         if(ret < 0){
  70.             perror("sendto error");
  71.             continue;
  72.         }
  73.     }
  74. }

  75. void recv_packet_func()
  76. {
  77.     int recvlen;
  78.     int used;
  79.     u_int8_t protocol=1;
  80.     u_int8_t type=0;
  81.     recvlen = sizeof(test_addr);

  82.     while(nrecv < nsend)
  83.     {
  84.         nrecv++;
  85.         times++;
  86.         ret = recvfrom(sock_fd,recv_packet,sizeof(recv_packet),0,(struct sockaddr *)&test_addr,&recvlen);
  87.         if(recv_packet[23]==protocol){
  88.                 if(recv_packet[34]==type){
  89.                     printf("MAC\t%02X:%02X:%02X:%02X:%02X:%02X\n",recv_packet[6],recv_packet[7],recv_packet[8],\
  90.                                     recv_packet[9],recv_packet[10],recv_packet[11]);
  91.                     printf("IP \t%s\n",inet_ntoa(send_addr.sin_addr));
  92.                     printf("is on!\n");   
  93.             }
  94.         }
  95.         else{
  96.                     printf("MAC\t%02X:%02X:%02X:%02X:%02X:%02X\n",recv_packet[6],recv_packet[7],recv_packet[8],\           
  97.                                 recv_packet[9],recv_packet[10],recv_packet[11]);
  98.                     printf("IP \t%s\n",inet_ntoa(send_addr.sin_addr));
  99.                     printf("is off!\n"); 
  100.         }
  101.     }
  102. }

  103. int pack(int seqno)
  104. {
  105.     int packetsize;
  106.     struct icmp *_icmp;
  107.     struct timeval *tval;
  108.     
  109.     _icmp = (struct icmp*)send_packet;
  110.     _icmp->icmp_type = ICMP_ECHO;
  111.     _icmp->icmp_code = 0;
  112.     _icmp->icmp_cksum = 0;
  113.     _icmp->icmp_id = pid;
  114.     _icmp->icmp_seq = seqno;
  115.     
  116.     packetsize = 8 + datalength;
  117.     tval = (struct timeval *)_icmp->icmp_data;
  118.     gettimeofday(tval,NULL);
  119.     _icmp->icmp_cksum = cal_chksum((unsigned short *)_icmp,packetsize);
  120.     
  121.     return packetsize;
  122. }

  123. unsigned short cal_chksum(unsigned short *addr,int len)
  124. {
  125.     int nleft=len;
  126.         int sum=0;
  127.         unsigned short *w=addr;
  128.         unsigned short answer=0;
  129.         
  130.     /*把ICMP报头二进制数据以2字节为单位累加起来*/
  131.         while(nleft>1){
  132.         sum+=*w++;
  133.                 nleft-=2;
  134.         }
  135.     if(nleft==1){
  136.         *(unsigned char *)(&answer)=*(unsigned char *)w;
  137.                 sum+=answer;
  138.         }
  139.         sum=(sum>>16)+(sum&0xffff);
  140.         sum+=(sum>>16);
  141.         answer=~sum;
  142.         return answer;
  143. }
            通过抓包有两种情况:
            1)正确接收,立刻就能接收到,数据帧字节为98字节(84字节的IP数据报+14字节的以太头);
            2)没有回显,是其他不相关数据包;
            此时,不能通过接收到的数据包字节长度来唯一确定PING回显数据包,所以我通过链路层的协议字段和IP层类型字段来判断是否是ICMP回显。
            此时没有阻塞等待的情况,应为该接收原始套接字能匹配上发往本机IP的所有IP数据包。



阅读(1946) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~