现在的位置: 首页 > 综合 > 正文

利用pcap编写自己的sniffer程序

2013年06月06日 ⁄ 综合 ⁄ 共 15637字 ⁄ 字号 评论关闭

转自:http://blog.csdn.net/dangdanding/archive/2004/05/17/22419.aspx

 

Programming with pcap


Tim Carstens
timcarst at
yahoo dot
com
The latest version of this document can be found at http://broker.dhs.org/pcap.html

 

原文:
http://www.tcpdump.org/pcap.htm

翻译:
dangdanding@163.com

 

本文读者对象:需要基本的
C
语言基础知识,否则除非你只是想了解
pcap
编程的基本理论知识也可以阅读此文。当然你也不一定必须是网络编程的高手,因为本文所涉及的领域仅需要为有丰富网络编程经验的人所理解(言下之意是如果你对这方面不感兴趣或无意于向这方面发展就无所谓了)。本文中的所有代码示例均在缺省内核版本
BSD4.3
下经过测试(我在
RedHat 6.2 with kernel-2.2.14-5
下亦测试通过)。

 

Get Started: The format of a pcap application

首先让我们了解一个
pcap
应用程序的常用设计。代码的流程如下所示:

1、
 

首先决定将要用来
sniff
的网络接口。在
Linux
下可能是
eth0

BSD
下可能是
xl1
等。我们要么在一个字符串(
char *
)中定义这个设备,或用用户在命令行直接指定用来
sniff
的设备接口名。

2、
 

初始化
pcap
。这是明确指定用来
sniff
的的网络接口的地方。当然我们可以在多个接口设备上
sniff
。通过句柄(
handle
)我们可以区分这些不同的
sniff
设备接口。就像我们打开一个用来读或写的文件一样,我们必须命名我们的
sniffer session
以便于区别这些不同的
session

3、
 

通常情况下我们只希望
sniff
特定的网络通信(比如:
tcp
数据包,所有发往
23
端口的
tcp
数据包)。通常我们制定这样一个定义特定网络通信的规则集,将其编译以后加载(
apply to

pcap
引擎上。这是编写
pcap
应用程序最主要的步骤,而且必须紧密关联。规则被保存在一个字符串中,通过编译被转换成
pcap
引擎能够识别的格式。事实上所谓的编译不过是在我们自己的程序中调用特定的函数就可以完成,并不涉及到任何外部的应用程序。然后我们可以告诉
pcap
引擎应用编译的规则作为我们
sniff
的规则(
filter
)。

4、
 

最后我们通知
pcap
引擎进入主要的处理流程:
pcap
接受并处理匹配指定规则的制定数目的数据包。每当捕获一个新的数据包,
pcap
调用自定义的回调函数进行相应的处理。在回调函数中可以做任何我们想做的事情:解剖捕获的数据包并打印到用户控制台,或者保存到文件中,当然也可以什么也不做(如果什么也不做,我们为什么要写这些代码呢?如果。。。那么。。。呢,呵呵已经很多人开始呕吐并晕倒了)

5、
 

结束
sniff
并关闭
pcap
会话句柄。

 

事实上,是用
pcap
编程是一个非常简单的过程,一共
5
个步骤,而且令你备感困惑的第
3
步还是可选的。详细实现如下。

 

Setting the Device

这是一个极其简单的操作(原文:

This is terribly simple
)。有两种方法可以设置用来
sniff
的网络接口。

1、


用户在命令行指定定监听的网络接口:

#include <stdio.h>
#include <pcap.h>
int main(int argc, char *argv[])
{
    char *dev = argv[1];
    printf("Device: %s/n", dev);
    return(0);
}

用户通过命令行参数传入监听接口。

译注:在实际的项目开发中务必对命令行参数进行判断:

     
if (argc < 2) {

     
     
printf(“Usage: %s <option>/n”, argv[0]);

     
     
exit(1);

     
}

 

2、
 

通过
pcap
引擎设定监听的网络接口:

#include <stdio.h>
#include <pcap.h>
int main()
{
    char *dev, errbuf[PCAP_ERRBUF_SIZE];
    dev = pcap_lookupdev(errbuf);
    printf("Device: %s/n", dev);
    return(0);
}

在这种情况下,
pcap
引擎自己设置用来监听的接口。但是
errbuf
字符串用来做什么呢?大多数的
pcap
函数允许我们传递这样一个字符串作为其参数。这个字符串参数用来在
pcap
函数调用失败以后用来设置出错信息。在上面的例子中,如果
pcap_lookup
函数调用失败,出错信息将被保存在
errbuf
中。

译注:增加的错误检查的代码如下:

      
if (NULL == (dev = pcap_lookupdev(errbuf))) {

      
      
fprintf(stderr, “pcap_lookupdev() error: %s/n”, errbuf);

      
      
exit(-1);

      
}

      
printf(“Device: %s/n”, dev);

 

Opening the device for sniffing

创建
sniff
会话的任务非常简单。我们使用
pcap_open_live()
创建
sniff
会话。函数原型:

pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)

      
device

:上节中我们制定的监听设备接口;

      
snaplen

:制定
pcap
捕获的最大数目的网络数据包;

      
promisc


>0
指定
device
接口工作在混杂模式(
promiscous Mode
);

      
to_ms

:制定经过特定时间(
ms
)后读超时;
0
表示遇到错误退出,
-1
指定永不超时;

      
ebuf

:制定用来存储出错信息的字符串

      
pcap_t

:返回值为用于监听的
pcap
会话。

示例代码:

#include <pcap.h>
    ...
    pcap_t *handle;
    handle = pcap_open_live(somedev, BUFSIZ, 1, 0, errbuf);

上面的代码打开
somedev
指定的设备并读取(捕获)
BUFSIZ
字节,同时我们设置接口工作在混杂模式,一直监听到有任何错误发生则退出,并将出错信息保存在
errbuf
指定的字符串中。

 

关于混杂模式
vs.
非混杂模式:通常情况在非混杂模式下仅监听直接发往主机的数据包:发往、源自或通过主机路由的数据包都将被
pcap

获;混杂模式下,所有发送到物理链路上的数据包都将被捕获。在一个共享式的网络环境中,这将导致整个网络的数据流被监听。混合监听模式是可以被检测的:可
以通过测试强可靠性来发现网络中是否有主机正在以混合模式监听,另外混杂工作模式仅仅在非交换式的网络中有效,而且在一个高负载的网络环境中,混杂模式将
消耗大量的系统资源。

 

Filter traffic

通常我们只对特定网络通信感兴趣。比如我们只打算监听
Telnet
服务(
port 23
)以捕获用户名和口令信息。获知对
FTP

port 21
)或
DNS

UDP port 53
)数据流感兴趣。可以通过
pcap_compile()

pcap_setfilter
来设置数据流过滤规则(
filter

函数原型:

      


int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)

p
:表示
pcap
会话句柄;

fp
:存放编译以后的规则;

str
:规则表达式格式的过滤规则(
filter
),同
tcpdump
中的
filter


optimize
:制定优化选项:
0 false, 1 true


netmask
:监听接口的网络掩码;

返回值:
-1
表示操作失败,其他值表成功。

int pcap_setfilter(pcap_t *p, struct bpf_program *fp)

             
p

:表示
pcap
的会话句柄;

             
fp

:表示经过编译后的过滤规则;

             

返回值:
-1
表示操作失败,其他值表成功。

 

示例代码:

#include <pcap.h>
    ...
    pcap_t *handle;                           /* Session handle */
    char dev[] = "rl0";                        /* Device to sniff on */
    char errbuf[PCAP_ERRBUF_SIZE];    /* Error string */
    struct bpf_program filter;               /* The compiled filter expression */
    char filter_app[] = "port 23";          /* The filter expression */
    bpf_u_int32 mask;                        /* The netmask of our sniffing device */
    bpf_u_int32 net;                           /* The IP of our sniffing device */
    pcap_lookupnet(dev, &net, &mask, errbuf);
    handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);
    pcap_compile(handle, &filter, filter_app, 0, net);
    pcap_setfilter(handle, &filter);

      

上面的代码设备
rl0
上以混杂模式监听所有发往或源自端口
23
的数据包。
Pcap_lookupnet()
函数返回给定接口的
IP
地址和子网掩码。

 

The actual sniffing

现在我们开始准备捕获数据包:有两种方法可以用来捕获数据包。要么一次捕获一个满足条件的数据包,要么进入一个循环过程捕获指定数量数据包然后退出。首先来了解使用
pcap_next()
一次捕获单一数据包。

函数原型:

u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)

      
p


pcap
会话句柄;

      
h

:指向
pcap_pkthdr
接口的指针,在此结构中保存了所捕获的数据包的通用信息。包括:时间信息、数据包的长度和包头部分的长度(结构定义在后面定义)。

      

返回值:返回指向实际捕获的数据包的
u_char *
型指针。

代码示例:

#include <pcap.h>
    #include <stdio.h>
    int main()
    {
        pcap_t *handle;                        /* Session handle */
        char *dev;                                /* The device to sniff on */
        char errbuf[PCAP_ERRBUF_SIZE]; /* Error string */
        struct bpf_program filter;            /* The compiled filter */
        char filter_app[] = "port 23";       /* The filter expression */
        bpf_u_int32 mask;                     /* Our netmask */
        bpf_u_int32 net;                        /* Our IP */
        struct pcap_pkthdr header;          /* The header that pcap gives us */
        const u_char *packet;                 /* The actual packet */
        /* Define the device */
        dev = pcap_lookupdev(errbuf);
        /* Find the properties for the device */
        pcap_lookupnet(dev, &net, &mask, errbuf);
        /* Open the session in promiscuous mode */
        handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);
        /* Compile and apply the filter */
        pcap_compile(handle, &filter, filter_app, 0, net);
        pcap_setfilter(handle, &filter);
        /* Grab a packet */
        packet = pcap_next(handle, &header);
        /* Print its length */
        printf("Jacked a packet with length of [%d]/n", header.len);
        /* And close the session */
        pcap_close(handle);
        return(0);
    }

上面的代码将所有从
pcap_lookupdev()
返回的接口置于混杂模式监听状态。
Pcap
捕获端口
23
的一个数据包并打印该包的长度。然后调用
pcap_close()
关闭
pcap
会话。

 

当然我们可以使用更复杂和更强大的功能
pcap_loop

pcap_dispatch
。通常很少有
sniffer
使用
pcap_next
,他们更通常的使用
pcap_loop

pcap_dispatch
。为便于理解这两个函数,需要现了解回调函数的概念。

 

回调函数并不是一个新概念,在很多的
API
中都使用了回调函数的概念。可以通过
pcap_loop

pcap_dispatch
定义用户自己的回调函数。事实上
pcap_loop

pcap_dispatch
的功能非常相似,当
pcap
捕获的满足规则的数据包时,着两个函数将调用我们自己定义的回调函数执行我们自己的处理。

 

函数原型:

int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)

p

pcap
会话句柄;

cnt
:定义
sniff
捕获的数据包的数目;

callback
:自定义的回调函数句柄;

user
:传递给回调函数的参数,如没有参数可以设为
NULL

函数
pcap_dispatch

pcap_loop
的用法几乎相同,两者之间的唯一的差别是处理超时的方式不同(在
pcap_open_live()
中设置的超时参数将在这里起作用:
pcap_loop
将忽略超时参数而
pcap_dispatch
在制定时间到时将产生读超时的错误)。查阅
pcap
的帮助获得更多信息。

 

回调函数的原型:

void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);

      
args

:对应于
pcap_loop
中的最后一个参数;

      
header

:指向
pcap
数据包包头的指针;

      
packet

:指向
pcap
捕获到的数据包的指针,
packet
指针指向的字符串包含了整个数据包;

      

返回值:回调函数不能返回任何值。

定义回调函数时,需要严格遵守原型定义,否则
pcap_loop
将不能正确调用回调函数。

      
pcap_pkthdr

结构的定义如下:

      


struct pcap_pkthdr {
             
struct timeval ts; /* time stamp */
      
      
bpf_u_int32 caplen; /* length of portion present */
      
      
bpf_u_int32 len; /* length this packet (off wire) */

};

 

      

怎样使用(处理)
packet
指针变量呢?一个
packet
指针所指的结构包含了很多属性,它并不是一个真正的字符串,而是多个结构组成的集合(比如:一个
TCP/IP
数据包包括以太网头、
IP
包头、
TCP
头和数据包中有效的数据负载)。首先需要定义这些结构:

/* Ethernet header */

 

...

怎样使用(处理)packet指针变量呢?一个packet指针所指的结构包含了很多属性,它并不是一个真正的字符串,而是多个结构组成的集合(比如:一个TCP/IP数据包包括以太网头、IP包头、TCP头和数据包中有效的数据负载)。首先需要定义这些结构:

/* Ethernet header */
struct sniff_ethernet {
  u_char ether_dhost[ETHER_ADDR_LEN]; /* Destination host address */
  u_char ether_shost[ETHER_ADDR_LEN]; /* Source host address */
  u_short ether_type; /* IP? ARP? RARP? etc */
};
/* IP header */
struct sniff_ip {
  #if BYTE_ORDER == LITTLE_ENDIAN
  u_int ip_hl:4, /* header length */
  ip_v:4; /* version */
  #if BYTE_ORDER == BIG_ENDIAN
  u_int ip_v:4, /* version */
  ip_hl:4; /* header length */
  #endif
  #endif /* not _IP_VHL */
  u_char ip_tos; /* type of service */
  u_short ip_len; /* total length */
  u_short ip_id; /* identification */
  u_short ip_off; /* fragment offset field */
  #define IP_RF 0x8000 /* reserved fragment flag */
  #define IP_DF 0x4000 /* dont fragment flag */
  #define IP_MF 0x2000 /* more fragments flag */
  #define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
  u_char ip_ttl; /* time to live */
  u_char ip_p; /* protocol */
  u_short ip_sum; /* checksum */
  struct in_addr ip_src,ip_dst; /* source and dest address */
};
/* TCP header */
struct sniff_tcp {
  u_short th_sport; /* source port */
  u_short th_dport; /* destination port */
  tcp_seq th_seq; /* sequence number */
  tcp_seq th_ack; /* acknowledgement number */
  #if BYTE_ORDER == LITTLE_ENDIAN
  u_int th_x2:4, /* (unused) */
  th_off:4; /* data offset */
  #endif
  #if BYTE_ORDER == BIG_ENDIAN
  u_int th_off:4, /* data offset */
  th_x2:4; /* (unused) */
  #endif
  u_char th_flags;
  #define TH_FIN 0x01
  #define TH_SYN 0x02
  #define TH_RST 0x04
  #define TH_PUSH 0x08
  #define TH_ACK 0x10
  #define TH_URG 0x20
  #define TH_ECE 0x40
  #define TH_CWR 0x80
  #define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)
  u_short th_win; /* window */
  u_short th_sum; /* checksum */
  u_short th_urp; /* urgent pointer */
};

注:这些结构定义在不同的系统实现中可能存在差异,请查阅相关文档。

另:搞不懂作者什么意思,为什么非要自己定义这些结构,干嘛不用系统自己定义的实现呢?

省略了原作者关于定义这些结构的描述…

假设我们通过以太网处理TCP/IP数据包(其他的物理网络类似),如下代码将packet指针所指的结构分解为不同的结构体:

const struct sniff_ethernet *ethernet; /* The ethernet header */
const struct sniff_ip *ip; /* The IP header */
const struct sniff_tcp *tcp; /* The TCP header */
const char *payload; /* Packet payload */
/* For readability, we'll make variables for the sizes of each of the structures */
int size_ethernet = sizeof(struct sniff_ethernet);
int size_ip = sizeof(struct sniff_ip);
int size_tcp = sizeof(struct sniff_tcp);

And now we do our magical typecasting:
ethernet = (struct sniff_ethernet*)(packet);
ip = (struct sniff_ip*)(packet + size_ethernet);
tcp = (struct sniff_tcp*)(packet + size_ethernet + size_ip);
payload = (u_char *)(packet + size_ethernet + size_ip + size_tcp);

如果packet值(该指针变量所指的地址)为X,则上面所述的结构在内存中的布局如下所示:

Variable
Location (in bytes)

sniff_ethernet
X

sniff_ip
X + 14

sniff_tcp
X + 14 + 20

payload
X + 14 + 20 + 20

Wrapping up


此为止,我们已经可以用pcap编写一个sniffer应用程序了。我们已经了解了pcap编程的基础知识,包括打开一个pcap会话句柄,处理pcap
会话句柄的属性,监听数据包,应用过滤规则,并使用回调函数定义我们自己的处理过程。随原文提供的示例程序:sniffer.c

This
document is Copyright 2002 Tim Carstens. All rights
reserved. Redistribution and use, with or without modification, are
permitted provided that the following conditions are met: 1.
Redistribution must retain the above copyright notice and this list of
conditions. 2. The name of Tim Carstens may not be used to endorse or
promote products derived from this document without specific prior
written permission.
/* Insert 'wh00t' for the BSD license here */

附录:例用pcap编写的示例程序

/*

    编译:gcc –Wall –o testpcap testpcap.c -lpcap

*/

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <pcap.h>

#include <sys/types.h>

#include <netinet/ip.h>

#include <netinet/ether.h>

#include <net/ethernet.h>

#include <netinet/udp.h>

#include <netinet/tcp.h>

#include <netinet/in.h>

#include <arpa/inet.h>

/* MACRO to print debug info */

//#define DEBUG 1

#ifdef DEBUG 

#define debug(stderr, msg) fprintf(stderr, msg)

#define _ ,

#else /* if no define DEBUG */

#define debug(stderr, msg)

#endif /* end of BEBUG */

#define LOOKUPDEV_ERR -1

#define OPEN_LIVE_ERR -2

#define COMPILE_ERR  -3

/* protocol ID's */

#define IPPRO 8 /* IP protocol */

/* call back function invoke by pcap_loop, major process for ourselves */

void

got_packet(u_char *args, const struct pcap_pkthdr *header,

     const u_char *packet);

/* handle ethernet header */

u_int16_t

handle_ethernet(u_char *args,const struct pcap_pkthdr* pkthdr,

     const u_char* packet);

/* handle IP header */

void

handle_IP(u_char *args,const struct pcap_pkthdr* pkthdr,const u_char* packet);

    

   

int

main(int argc, char *argv[])

{

   char *dev = NULL; /* device to sniff on */

   char errbuf[PCAP_ERRBUF_SIZE]; /* buffer to store error msg */

   pcap_t *handle = NULL; /* pcap session handle */

   struct bpf_program filter; //compiled filter expression

   char filter_app[] = "port 23"; /* filter ruler for sniffing */

   bpf_u_int32 mask; //netmask of our sniffing device

   bpf_u_int32 net; //the ip of our sniffing device

  

   int num = 0; /* number of packets captured */

   /* variables for getopt */

   long total = -1; /* total packets to sniff */

   char *flter = filter_app; /* filter ruler for sniffing */

   int c; /* temprory char variable */

   while ((c = getopt(argc, argv, "n:f:")) != -1) {

     switch(c) {

       case 'n':

          total = atoi(optarg);

          break;

       case 'f':

          flter = optarg;

          break;

    

       case '?':

          fprintf(stderr, "Usage: %s -n <num> -f <filter string>/n", argv[0]);

          exit(1);

       default:

          fprintf(stdout, "Using fitler: port 23 and sniffing util interrupt by console!/n");

     }

   }

   if (NULL == (dev = pcap_lookupdev(errbuf))) {

     fprintf(stderr, "pcap_lookupdev() error: %s/n", errbuf);

     exit(LOOKUPDEV_ERR);

   }

   fprintf(stdout, "Sniffing on device: %s/n/n", dev);

   pcap_lookupnet(dev, &net, &mask, errbuf);

   /* open a new pcap session */

   if (NULL == (handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf))) {

     fprintf(stderr, "pcap_open_live() error: %s/n", errbuf);

     exit(OPEN_LIVE_ERR);

   }

   /* compile capture rule */

   if (-1 == pcap_compile(handle, &filter, flter, 1, net)) {

     fprintf(stderr, "pcap_compile() error!/n");

     exit(COMPILE_ERR);

   }

   pcap_setfilter(handle, &filter);

/* using while + pcap_next instead of pcap_loop or pcap_dispatch */

/*  while (1) {

     debug(stderr, "in pcap_next while/n");

     packet = pcap_next(handle, &header);

     printf("Captured a packet with lengthen of [%d]/n", header.len);

     debug(stderr, "The packet captured: %s/n" _ packet + header.caplen);

   }

*/

   num = pcap_loop(handle, total, got_packet, NULL);

   if (-1 == num) {

     pcap_perror(handle, "pcap_loop error: ");

   }  

   if (-2 == num) {

     pcap_perror(handle, "pcap_loop break by pcap_breakloop: ");

   }

   pcap_close(handle);

   return 0;

}

void

got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet)

{

  u_int16_t type;

   type = handle_ethernet(args, header, packet);

   debug(stderr, "protocol type: %i/n" _ type);

  switch(type) {

     case IPPRO:

       debug(stderr, "protocol type: IP/n");

       handle_IP(args, header, packet);

       break;

     case ETHERTYPE_ARP:

       /* handle arp protocol */ 

       break;

     case ETHERTYPE_REVARP:

       /* handle rarp protocol */ 

       break;

     default:

       fprintf(stdout, "Protocol is ignored/n");

   }

     fprintf(stdout,"/n");

   return;

} //end of got_packet

u_int16_t

handle_ethernet(u_char *args,const struct pcap_pkthdr* pkthdr,

     const u_char* packet)

{

   struct ether_header *eptr; /* net/ethernet.h */

  /* lets start with the ether header... */

  eptr = (struct ether_header *) packet;

  fprintf(stdout,"ETH: %s --> "

      ,ether_ntoa((struct ether_addr *)(eptr->ether_shost)));

  fprintf(stdout,"%s "

      ,ether_ntoa((struct ether_addr *)(eptr->ether_dhost)));

  /* check to see if we have an ip packet */

  if (ntohs (eptr->ether_type) == ETHERTYPE_IP)

  {

    fprintf(stdout,"(IP)");

  }else if (ntohs (eptr->ether_type) == ETHERTYPE_ARP)

  {

    fprintf(stdout,"(ARP)");

  }else if (ntohs (eptr->ether_type) == ETHERTYPE_REVARP)

  {

    fprintf(stdout,"(RARP)");

  }else {

    fprintf(stdout,"(?)");

    exit(1);

  }

  return eptr->ether_type;

} //end of handle_ethernet

void

handle_IP(u_char *args,const struct pcap_pkthdr* pkthdr,const u_char* packet)

{

   const struct iphdr *ip = (const struct iphdr *)(packet + sizeof(struct ether_header));

   u_int length = pkthdr->len;

   u_int hlen,off,version;

   int len;

   struct in_addr in;

   debug(stderr, "total pcap pkt length: %i/n" _ length);

   debug(stderr, "total pcap pkt header length: %i/n" _ pkthdr->caplen);

  /* jump pass the ethernet header */

  length =- sizeof(struct ether_header);

  /* check to see we have a packet of valid length */

  if (length < sizeof(struct iphdr))

  {

    printf("truncated ip %d",length);

    return;

  }

  len   = ntohs(ip->tot_len);

   debug(stderr, "total ip pkt length: %i/n" _ len);

  hlen  = ip->ihl; /* header length */

   debug(stderr, "ip header length: %i/n" _ hlen);

  version = ip->version;/* ip version */

   debug(stderr, "ip version: %i/n" _ version);

  /* check version */

  if(version != 4)

  {

   fprintf(stdout,"Unknown version %d/n",version);

   return ;

  }

  /* check header length */

  if(hlen < 5 )

  {

    fprintf(stdout,"bad-hlen %d /n",hlen);

  }

  /* see if we have as much packet as we should */

  if(length < len)

    printf("/ntruncated IP - %d bytes missing/n",len - length);

  /* Check to see if we have the first fragment */

  off = ntohs(ip->frag_off);

  if((off & 0x1fff) == 0 )/* aka no 1's in first 13 bits */

  {/* print SOURCE DESTINATION hlen version len offset */

    fprintf(stdout,"/nIP: ");

   in.s_addr = ip->saddr;

    fprintf(stdout,"%s ",inet_ntoa(in));

   in.s_addr = ip->daddr;

    fprintf(stdout,"%s %d %d %d %d/n",

        inet_ntoa(in),

        hlen,version,len,off);

  }
  return;
}

抱歉!评论已关闭.