Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1003584
  • 博文数量: 96
  • 博客积分: 1553
  • 博客等级: 上尉
  • 技术积分: 1871
  • 用 户 组: 普通用户
  • 注册时间: 2011-12-25 14:50
个人简介

专注点,细心点,耐心点 知行合一

文章分类

全部博文(96)

文章存档

2018年(1)

2014年(4)

2013年(31)

2012年(56)

2011年(4)

分类: C/C++

2012-09-04 13:55:22

unix network programming 笔记——SOCK_RAW
sock_raw(注意一定要在root下使用)原始套接字编程可以接收到本机网卡上的数据帧或者数据包,对与监听网络的流量和分析是很有作用的.一共可以有3种方式创建这种socket
 
1.socket(AF_INET, SOCK_RAW, IPPROTO_TCP|IPPROTO_UDP|IPPROTO_ICMP)发送接收ip数据包,不能用IPPROTO_IP,因为如果是用了IPPROTO_IP,系统根本就不知道该用什么协议。
2.socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))发送接收以太网数据帧
3.socket(AF_INET, SOCK_PACKET, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))过时了,不要用啊
 
理解一下SOCK_RAW的原理, 比如网卡收到了一个 14+20+8+100+4(以太网头+ip头+udp头+数据+crc) 的udp的以太网数据帧.
 
首先,网卡对该数据帧进行硬过滤(根据网卡的模式不同会有不同的动作,如果设置了promisc混杂模式的话,则不做任何过滤直接交给下一层输入例程,否则非本机mac或者广播mac会被直接丢弃).按照上面的例子,如果成功的话,会进入ip输入例程.但是在进入ip输入例程之前,系统会检查系统中是否有通过socket(AF_PACKET, SOCK_RAW, ..)创建的套接字.如果有的话并且协议相符,在这个例子中就是需要ETH_P_IP或者ETH_P_ALL类型.系统就给每个这样的socket接收缓冲区发送一个数据帧拷贝.然后进入下一步.
 
其次,进入了ip输入例程(ip层会对该数据包进行软过滤,就是检查校验或者丢弃非本机ip或者广播ip的数据包等,具体要参考源代码),例子中就是如果成功的话会进入udp输入例程.但是在交给udp输入例程之前,系统会检查系统中是否有通过socket(AF_INET, SOCK_RAW, ..)创建的套接字.如果有的话并且协议相符,在这个例子中就是需要IPPROTO_UDP类型.系统就给每个这样的socket接收缓冲区发送一个数据帧拷贝.然后进入下一步.
 
最后,进入udp输入例程 ...
 
ps:如果校验和出错的话,内核会直接丢弃该数据包的.而不会拷贝给sock_raw的套接字,因为校验和都出错了,数据肯定有问题的包括所有信息都没有意义了.
 
进一步分析他们的能力.
1. socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
能:该套接字可以接收协议类型为(tcp udp icmp等)发往本机的ip数据包,从上面看的就是20+8+100.
不能:不能收到非发往本地ip的数据包(ip软过滤会丢弃这些不是发往本机ip的数据包).
不能:不能收到从本机发送出去的数据包.
发送的话需要自己组织tcp udp icmp等头部.可以setsockopt来自己包装ip头部
这种套接字用来写个ping程序比较适合
     
2. socket(PF_PACKET, SOCK_RAW, htons(x)); 
这个套接字比较强大,创建这种套接字可以监听网卡上的所有数据帧.从上面看就是20+20+8+100.最后一个以太网crc从来都不算进来的,因为内核已经判断过了,对程序来说没有任何意义了.
能: 接收发往本地mac的数据帧
能: 接收从本机发送出去的数据帧(第3个参数需要设置为ETH_P_ALL)
能: 接收非发往本地mac的数据帧(网卡需要设置为promisc混杂模式)
协议类型一共有四个
ETH_P_IP  0x800      只接收发往本机mac的ip类型的数据帧
ETH_P_ARP 0x806      只接受发往本机mac的arp类型的数据帧
ETH_P_ARP 0x8035     只接受发往本机mac的rarp类型的数据帧
ETH_P_ALL 0x3        接收发往本机mac的所有类型ip arp rarp的数据帧, 接收从本机发出的所有类型的数据帧.(混杂模式打开的情况下,会接收到非发往本地mac的数据帧)
 
发送的时候需要自己组织整个以太网数据帧.所有相关的地址使用struct sockaddr_ll 而不是struct sockaddr_in(因为协议簇是PF_PACKET不是AF_INET了),比如发送给某个机器,对方的地址需要使用struct sockaddr_ll.
 
在使用SOCK_RAW, SOCK_DGRAM和SOCK_PACKET的区别(详请参考http://blog.csdn.net/tqyou85/archive/2008/10/21/3115664.aspx)
在socket的第一个参数使用PF_PACKET的时候,上述三种socket的类型都可以使用。但是有区别。
(1)使用SOCK_RAW发送的数据必须包含链路层的协议头,接受得到的数据包,包含链路层协议头。而使用SOCK_DGRAM则都不含链路层的协议头。
(2)SOCK_PACKET也是可以使用的,但是已经废弃,以后不保证还能支持,不推荐使用。
(3)在使用SOCK_RAW或SOCK_DGRAM和SOCK_PACKET时,在sendto和recvfrom中使用的地址类型不同,前两者使用sockaddr_ll类型的地址,而后者使用sockaddr类型的地址。
(4)如socket的第一个参数使用PF_INET,第二个参数使用SOCK_RAW,则可以得到原始的IP包。
这种socket大小通吃,强悍
下面是一段相关的代码:
 

     ...
    int sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    struct sockaddr_ll sll;
    memset( &sll, 0, sizeof(sll) );
    sll.sll_family = AF_PACKET;
    struct ifreq ifstruct;
    strcpy(ifstruct.ifr_name, "eth0");
    ioctl(sockfd, SIOCGIFINDEX, &ifstruct);
    sll.sll_ifindex = ifstruct.ifr_ifindex;
    sll.sll_protocol = htons(ETH_P_ALL);
    if(bind(fd, (struct sockaddr *) &sll, sizeof(sll)) == -) {
       perror("bind()");
    ...

 

int set_promisc(char *interface, int fd) {
        struct ifreq ifr;
        strcpy(ifr.ifr_name, interface);
        if(ioctl(fd, SIOCGIFFLAGS, &ifr) == -1) {
                perror("iotcl()");
                return -1;
        }
        ifr.ifr_flags |= IFF_PROMISC;
        if(ioctl(fd, SIOCSIFFLAGS, &ifr) == -1) {
                perror("iotcl()");
                return -1;
        }
        return 0;
}
 
int unset_promisc(char *interface, int fd) {
        struct ifreq ifr;
        strcpy(ifr.ifr_name, interface);
        if(ioctl(fd, SIOCGIFFLAGS, &ifr) == -1) {
                perror("iotcl()");
                return -1;
        }
        ifr.ifr_flags &= ~IFF_PROMISC;
        if(ioctl(fd, SIOCSIFFLAGS, &ifr) == -1) {
                perror("iotcl()");
                return -1;
        }
        return 0;
}

 

3. socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL))这个最好不要用,反正我不用...
 
总结使用方法: 1.只想收到发往本机某种协议的ip数据包的话用第一种就足够了
            2. 更多的详细的内容请使用第二种.包括ETH_P_ALL参数和混杂模式都可以使它的能力不断的加强.
ps:很多自己的想法.虚拟机测试环境.有错欢迎指出交流
qq:110024218
 
 
我写的ping
 

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
 
#include "unistd.h"
#include "sys/types.h"
#include "sys/socket.h"
#include "netinet/in.h"
#include "netinet/ip.h"
#include "netinet/ip_icmp.h"
#include "netdb.h"
#include "errno.h"
#include "arpa/inet.h"
#include "signal.h"
#include "sys/time.h"
 
extern int errno;
 
int sockfd;
struct sockaddr_in addr; //peer addr
char straddr[128]; //peer addr ip(char*)
char sendbuf[2048];
char recvbuf[2048];
int sendnum;
int recvnum;
int datalen = 30;
 
 
unsigned short my_cksum(unsigned short *data, int len) {
        int result = 0;
        for(int i=0; i<len/2; i++) {
                result += *data;
                data++;
        }
        while(result >> 16)result = (result&0xffff) + (result>>16);
        return ~result;
}
void tv_sub(struct timeval* recvtime, const struct timeval* sendtime) {
        int sec = recvtime->tv_sec - sendtime->tv_sec;
        int usec = recvtime->tv_usec - sendtime->tv_usec;
        if(usec >= 0) {
                recvtime->tv_sec = sec;
                recvtime->tv_usec = usec;
        } else {
                recvtime->tv_sec = sec-1;
                recvtime->tv_usec = -usec;
        }
}
 
void send_icmp() {
        struct icmp* icmp = (struct icmp*)sendbuf;
        icmp->icmp_type = ICMP_ECHO;
        icmp->icmp_code = 0;
        icmp->icmp_cksum = 0;
        icmp->icmp_id = getpid(); //needn't use htons() call, because peer networking kernel didn't handle this data and won't make different meanings(bigdian litteldian) 
        icmp->icmp_seq = ++sendnum; //needn'
t use hotns() call too.
        gettimeofday((struct timeval*)icmp->icmp_data, NULL);
        int len = 8+datalen;
        icmp->icmp_cksum = my_cksum((unsigned short*)icmp, len);
        int retval = sendto(sockfd, sendbuf, len, 0, (struct sockaddr*)&addr, sizeof(addr));
        if(retval == -1){
                perror("sendto()");
                exit(-1);
        } else {
// printf("send icmp request to %s(%d) bytes/n", straddr, len);
        }
}
void recv_icmp() {
        struct timeval *sendtime;
        struct timeval recvtime;
 
        for(;;) {
                int n = recvfrom(sockfd, recvbuf, sizeof(recvbuf), 0, 0, 0);
                if(== -1) {
                        if(errno == EINTR)continue;
                        else {
                                perror("recvfrom()");
                                exit(-1);
                        }
                } else {
                        gettimeofday(&recvtime, NULL);
                        struct ip *ip = (struct ip*)recvbuf;
                        if(ip->ip_src.s_addr != addr.sin_addr.s_addr) {
// printf("ip_src is not : %s/n", straddr);
                                continue;
                        }
                        struct icmp *icmp = (struct icmp*)(recvbuf + ((ip->ip_hl)<<2));
                        if(icmp->icmp_id != getpid()) {
// printf("icmp_id is not :%d/n", getpid());
                                continue;
                        }
                        recvnum++;
                        sendtime = (struct timeval*)icmp->icmp_data;
                        tv_sub(&recvtime, sendtime);
                        printf("imcp echo from %s(%dbytes)/tttl=%d/tseq=%d/ttime=%d.%06d s/n", straddr, n, ip->ip_ttl, icmp->icmp_seq, recvtime.tv_sec, recvtime.tv_usec); 
                }
        }
}
void catch_sigalrm(int signum) {
        send_icmp();
        alarm(1);
}
void catch_sigint(int signum) {
        printf("/nPing statics:send %d packets, recv %d packets, %d%% lost.../n", sendnum,recvnum, (int)((float)(sendnum-recvnum)/sendnum)*100);
        exit(0);
}
 
int main(int argc, char **argv) {
        if(argc != 2) {
                printf("please use format: ping hostname/n");
                exit(-1);
        }
 
        sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
        if(sockfd == -1) {
                perror("socket()");
                return -1;
        }
 
/* 
        int sendbufsize = 180;
        socklen_t sendbufsizelen = sizeof(sendbufsize);
        if(setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &sendbufsize, sendbufsizelen) ==-1)perror("setsockopt()");
        int recvbufsize;
        socklen_t recvbufsizelen;
        if(getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &recvbufsize, &recvbufsizelen) ==-1)perror("getsockopt()");
*/
 
        memset(&addr, 0, sizeof(addr));
        addr.sin_family = AF_INET;
        int retval = inet_pton(AF_INET, argv[1], &addr.sin_addr);
        if(retval == -|| retval == 0) {
                struct hostent* host = gethostbyname(argv[1]);
                if(host == NULL) {
                        fprintf(stderr, "gethostbyname(%s):%s/n", argv[1], strerror(errno));
                        exit(-1);
                }
/*
                if(host->h_name != NULL)printf("hostent.h_name:%s/n", host->h_name);
                if(host->h_aliases != NULL && *(host->h_aliases) !=NULL)printf("hostent.h_aliases:%s/n", *(host->h_aliases));
                printf("hostent.h_addrtype:%d/n", host->h_addrtype);
                printf("hostent.h_length:%d/n", host->h_length);
*/
                if(host->h_addr_list != NULL && *(host->h_addr_list) != NULL) {
                        strncpy((char*)&addr.sin_addr, *(host->h_addr_list), 4);
                        inet_ntop(AF_INET, *(host->h_addr_list), straddr, sizeof(straddr));
                }
                printf("Ping address:%s(%s)/n/n", host->h_name, straddr);
        } else {
                strcpy(straddr, argv[1]);
                printf("Ping address:%s(%s)/n/n", straddr, straddr);
        }
 
        struct sigaction sa1;
        memset(&sa1, 0, sizeof(sa1));
        sa1.sa_handler = catch_sigalrm;
        sigemptyset(&sa1.sa_mask);
        sa1.sa_flags = 0;
        if(sigaction(SIGALRM, &sa1, NULL) == -1)perror("sigaction()");
        struct sigaction sa2;
        memset(&sa2, 0, sizeof(sa2));
        sa2.sa_handler = catch_sigint;
        sigemptyset(&sa2.sa_mask);
        sa2.sa_flags = 0;
        if(sigaction(SIGINT, &sa2, NULL) == -1)perror("sigaction()");
 
        alarm(1);
        recv_icmp();
 
        return 0;
}

阅读(2364) | 评论(0) | 转发(1) |
0

上一篇:netstat工具

下一篇:mpich2编译

给主人留下些什么吧!~~