Linux BSD Socket编程实现以太帧的捕获与分析

转自:http://nking.blog.sohu.com/141551979.html

编程背景材料:

一、LinuxC语言编程环境介绍

1、使用gedit编辑器输入程序代码

(1) 单击“主菜单”(桌面左下方的图标)—>“附件”->“文本编辑器”,进入gedit编辑界面,输入程序。

(2) 保存文件的方法与MS Windows中类似,注意保存位置,建议将保存位置设为:/home,文件名为:file.c

2、使用gcc编译器

在桌面上单击右键,选择“新建终端”,弹出一对话框,在命令提示符#后依次输入:

# cd /home          //进入程序所在目录

# ls                //显示该目录下的文件及文件夹(应能见到file.c)

# gcc –g –o file file.c     (例如 # gcc –g –o example example.c )   //编译程序

# ./file              (例如 # ./example)                   //执行程序

      

二、   监听的实施

以太网上进行数据传输时,在同一网段上连接的所有网卡事实上都可以收到在共享的物理介质上传输的所有数据。但在系统正常工作时,某个主机的网络接口只响应两种数据帧:一是帧的目标MAC地址与本主机网卡地址相符的帧; 二是帧的目标地址是广播地址的帧,除此之外的数据帧都将被丢弃不作处理。要监听流经网卡的不属于自己主机的数据,必须绕过系统正常工作的处理机制,直接访问网络底层。

 

三、   以太网数据帧、IP数据报、TCP首部格式

1、以太帧数据帧格式如下图所示:

 

目标地址

源地址

类型

  帧 中 数 据

CRC校验和

 

 

      目的地址和源地址都是6字节的MAC地址(网卡地址),类型指出帧中封装的载荷为何协议类型的报文,类型标识主要有:

       0x0800:   IP(网际协议);

       0x0806:   ARP(地址解析协议);

       0x8035:   RARP(反向地址解析协议);

       IP、ARP或RARP协议数据单元的报头则位于帧载荷数据中。由于以太网最小帧长为64字节,最大帧长为1518字节,而帧头加幀尾共18字节,故帧中数据为46~1500字节。

 

       2、以太帧载荷中的 IP 数据报格式及 IP 头中的各字段

 

        IP 信包的头部中 “协议”字段指出 IP 信包中的数据部分封装了何种类型的高层报文,常见的类型有:

    1----ICMP,6----TCP,17----UDP

3、IP 信包载荷数据中的 T C P 段格式及TCP段头部中的各字段

 

四、   Linux编程要点

1、设置套接口以捕获链路帧。Linux下编写网络监听程序,比较简单的方法是在超级用户模式下,利用类型为SOCK_PACKET的套接口(用socket ( ) 函数创建)来捕获链路帧。Linux程序中需引用如下头文件:

# include <sys/socket.h>

# include <sys/ioctl.h>              /* ioctl command */

# include <netinet/if_ether.h>        /* ethhdr struct */

# include <net/if.h>                /* ifreq struct */

# include <netinet/in.h>             /* in_addr structure */

# include <netinet/ip.h>             /* iphdr struct */

# include <netinet/udp.h>            /* udphdr struct */

# include <netinet/tcp.h>            /*tcphdr struct */

socket函数的语法是:int socket(int domain, int type, int protocol); 其中domain参数表示所使用的协议族,type参数表示套接口的类型,protocol参数表示所使用的协议族中某个特定的协议。如果函数调用成功,套接口的描述符(非负整数)就作为函数的返回值,若为-1,就表明有错误发生。

Linux C扩展了套接口函数,增加了SOCK_PACKET类型,使之可以用于监听链路层的数据帧,进而得以分析各层的协议数据单元。使用socket函数捕获链路层数据帧时,domain参数应指定为AF_INET协议族,表示采用Internet协议族;type参数指定为SOCK_PACKET,表示获取链路层数据,不作处理;protocol参数采用htons(0x0003),表示使用的特定协议是截取所有类型的数据帧。此处需用htons函数,用于短整数的字节顺序转换。监听捕获时,socket函数调用形式为:

int fd;       //fd是套接口的描述符

fd = socket(AF_INET, SOCK_PACKET, htons(0x0003));

       要使建立的套接口能够真正监听到同一网段其他站点在网上传输的数据帧,还必须使用ioctl函数设置网卡工作于“混杂”模式,相应的Linux C程序段如下:

       char *dev = “eth0”;      //(char *)dev标识设备名,eth0表示系统中的第一块以太网卡

       struct ifreq  ifr;

       strcpy(ifr.ifr_name, dev);              //“eth0”写入ifr结构的一个字段中

       i = ioctl (fd, SIOCGIFFLAGS, &ifr);    // SIOCGIFFLAGS(0x8913)表示要求取出接口标

// 志位

       if (i<0)

              {

                     close(fd);

                     perror(“can’t get flags /n”);

                     exit(0);

}

       ifr.ifr_flags |= IFF_PROMISC;         //在标志位中加入“混杂”方式

       i = ioctl(fd, SIOCSIFFLAGS, &ifr);     // SIOCSIFFLAGS(0x8914)表示要求保存接口标

//志位

       if (i<0)

              {

                     perror(“can’t set promiscuous /n”);

                     exit(0);

}

2、从套接口读取链路帧。套接口建立以后,就可以从中循环读取捕获的链路层以太帧。因此要建立以太帧的缓冲区,把帧头结构的指针指向这一缓冲区的首地址:

char ep[ETH_FRAME_LEN];             //以太帧缓冲区

struct ethhdr *eh;

int fl;

eh = (struct ethhdr *)ep;                  //eh指向帧头

fl = read(fd, (etherpacket *)ep, sizeof(ep));   //fl为返回的实际捕获的以太帧的帧长

这里幀头结构类型ethhdr在/usr/include/linux/if_ether.h中定义:

struct ethhdr

{

         unsigned char    h_dest[ETH_ALEN];            //目标MAC地址

         unsigned char    h_source[ETH_ALEN];          //源MAC地址

         unsigned short    h_proto;                      //帧中数据协议类型代码

}

基于上述定义,一旦以太帧缓冲区ep中读入帧的各字节,随即可以通过eh->h_dest、eh->h_source、eh->h_proto获取幀头信息。

//display destination MAC Adress

  printf("dest MAC: ");

  for(i=0; i<5; i++)

printf("%02x-", eh->h_dest[i]);

  printf("%02x/n ", eh->h_dest[5]);

 

 //display source MAC Adress

  printf("src MAC: ");

  for(i=0; i<5; i++)

    printf("%02x-", eh->h_source[i]);

  printf("%02x/n ", eh->h_source[5]);

 

  //display protocol: 0x0800 IP, 0x0806 ARP, 0x8035 RARP

  printf("protocol: 0x%04x", ntohs(eh->h_proto));

3、定位IP信包头。若捕获的以太帧中h_proto的取值为0x0800,将类型为iphdr的结构指针指向帧头后面载荷数据的起始位置,则IP信包基本报头的数据结构将一览无余。以下程序段表明这一定位过程:

if(ntohs(eh->h_proto)==0x0800)       //0x0800 : IP Packet

{

struct iphdr *ip;

ip = (struct iphdr ) ((unsigned long)ep + ETH_HLEN);    //ETH_HLEN为帧头长(14)

printf("src ip:%s/n", inet_ntoa(ip->saddr));

printf("dest ip:%s/n", inet_ntoa(ip->daddr));              //取出源和目标IP地址

}

4、定位TCP报头。若IP报头的协议域取值为6,那么紧跟在IP报头之后的就是TCP报头。IP报头的长度可以通过ihl域取得。这样,假如接收缓冲区ep存放监听得到的以太帧,iph是指向其中IP基本报头结构的指针,而tcph是指向TCP报头结构的指针,那么,定位TCP报头的结构信息就尽在*tcph中。

if(ip->protocol==6)

{      

struct tcphdr* tcph;

tcph =(struct tcphdr*)(ip+ip->ihl*4);

printf("src port:%d/n", ntohs(tcph->source));

printf("dest port:%d/n", ntohs(tcph->dest));

}

5、定位、分析应用层报文数据。定位了TCP段后,其中的数据载荷部分便为应用层报文数据,根据TCP报头中的源、目的端口号可分析出载荷为何种类型的应用层报文数据。

 

Linux编程参考网址

[1] Beej's Guide to Network Programming Using Internet Sockets

http://beej.us/guide/bgnet/output/htmlsingle/bgnet.html

[2] Davin's collection of unix programming links

http://www.cse.buffalo.edu/~milun/unix.programming.html

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值