毛毛's profileWindows Live 共享空间PhotosBlogListsMore Tools Help

Windows Live 共享空间

毛毛 武

No list items have been added yet.
No list items have been added yet.
Photo 1 of 1
November 04

computer system a programmer's perspective 深入理解计算机系统 Randal E.Bryant--(一)

       看过Randy Pausch的最后一课的话可能会注意到结尾的the other Randy,很幽默谦逊的一个老人,其实他是CMU计算机科学主任教授和计算机学院长,IEEE和ACM的fellow。computer system a programmer's perspective的作者。

        bryant-02

        http://www.cs.cmu.edu/~bryant/这是他的个人主页,有兴趣的话可以上去瞻仰一下牛人。关于这本书有一个专门的网页http://csapp.cs.cmu.edu/,可以在上面下载相关的资料。

        自己花半个月时间粗粗的过了一遍这本书,受益匪浅,澄清了许多以前的困惑。而且这本书条理及其的清楚,解释的也很深入到位,一点都不晦涩。所以把一些看书中的体会纪录下来。个人强烈推荐想学计算机编程的都认真阅读这本书,感受一下大师的风采。

September 05

WinPcap编程(五)——杂碎

(4)几个抓包函数pcap_loop(),pcap_dispatch(); pcap_next(),pcap_next_ex().

    当适配器被打开,捕获工作就可以用 pcap_dispatch()pcap_loop()进行。 这两个函数非常的相似,区别就是 pcap_ dispatch() 当超时时间到了(timeout expires)就返回 (尽管不能保证) ,而 pcap_loop() 不会因此而返回,只有当 cnt 数据包被捕获,所以,pcap_loop()会在一小段时间内,阻塞网络的利用。pcap_loop()对于我们这个简单的范例来说,可以满足需求,不过, pcap_dispatch() 函数一般用于比较复杂的程序中。这两个函数都有一个 回调 参数, packet_handler指向一个可以接收数据包的函数。 这个函数会在收到每个新的数据包并收到一个通用状态时被libpcap所调用 ( 与函数 pcap_loop()pcap_dispatch() 中的 user 参数相似),数据包的首部一般有一些诸如时间戳,数据包长度的信息,还有包含了协议首部的实际数据。 注意:冗余校验码CRC不再支持,因为帧到达适配器,并经过校验确认以后,适配器就会将CRC删除,与此同时,大部分适配器会直接丢弃CRC错误的数据包,所以,WinPcap没法捕获到它们。

int
pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
{
    register int n;

#ifdef HAVE_REMOTE
    /* Checks the capture type */
    if (p->rmt_clientside)
    {
        /* We are on an remote capture */
        if (!p->rmt_capstarted)
        {
            // if the capture has not started yet, please start it
            if (pcap_startcapture_remote(p) )
                return -1;
        }
    }
#endif /* HAVE_REMOTE */

    for (;;) {
        if (p->sf.rfile != NULL) {
            /*
             * 0 means EOF, so don't loop if we get 0.
             */
            n = pcap_offline_read(p, cnt, callback, user);
        } else {
            /*
             * XXX keep reading until we get something
             * (or an error occurs)
             */
            do {
                n = p->read_op(p, cnt, callback, user);
            } while (n == 0);
        }
        if (n <= 0)
            return (n);
        if (cnt > 0) {
            cnt -= n;
            if (cnt <= 0)
                return (0);
        }
    }
}

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

#ifdef HAVE_REMOTE
    /* Checks the capture type */
    if (p->rmt_clientside)
    {
        /* We are on an remote capture */
        if (!p->rmt_capstarted)
        {
            // if the capture has not started yet, please start it
            if (pcap_startcapture_remote(p) )
                return -1;
        }
    }
#endif /* HAVE_REMOTE */

    return p->read_op(p, cnt, callback, user);
}

    两个函数抓包的核心都在p->read_op(p, cnt, callback, user);不同的是pcap_loop是一个循环,直到read_op返回0值;而pcap_dispatch直接调用一次read_op就返回。那read_op是个什么函数,loop和dispatch形参列表的pcap_handler有啥用呢。

    首先read_op是个函数指针,以前看pcap_t的定义时候也看到了,再啰嗦一次。

    int    (*read_op)(pcap_t *, int cnt, pcap_handler, u_char *);

    实际loop和dispatch里面的read_op是指向什么函数呢?这个函数指针其实是在打开设备的时候赋值的,我没有找到pcap_open_dead和pcap_open_offline两个函数的具体赋值过程,不过pcap_open_live里写得很清楚。

    p->read_op = pcap_read_nit;
    来看pcap_read_nit函数吧(这套代码很奇怪,不知道是写错了还是怎么的,int和nit后缀似乎表达的是一个意思)。

static int pcap_read_nit(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
{
    register int cc, n;
    register struct bpf_insn *fcode = p->fcode.bf_insns;
    register u_char *bp, *cp, *ep;
    register struct nit_hdr *nh;
    register int caplen;

    cc = p->cc;
    if (cc == 0) {
        cc = read(p->fd, (char *)p->buffer, p->bufsize);
        if (cc < 0) {
            if (errno == EWOULDBLOCK)
                return (0);
            snprintf(p->errbuf, sizeof(p->errbuf), "pcap_read: %s",
                pcap_strerror(errno));
            return (-1);
        }
        bp = p->buffer;
    } else
        bp = p->bp;

    /*
     * Loop through each packet.  The increment expression
     * rounds up to the next int boundary past the end of
     * the previous packet.
     */
    n = 0;
    ep = bp + cc;
    while (bp < ep)
{
        /*
         * Has "pcap_breakloop()" been called?
         * If so, return immediately - if we haven't read any
         * packets, clear the flag and return -2 to indicate
         * that we were told to break out of the loop, otherwise
         * leave the flag set, so that the *next* call will break
         * out of the loop without having read any packets, and
         * return the number of packets we've processed so far.
         */
       if (p->break_loop) {
            if (n == 0) {
                p->break_loop = 0;
                return (-2);
            } else {
                p->cc = ep - bp;
                p->bp = bp;
                return (n);
            }
        }

        nh = (struct nit_hdr *)bp;
        cp = bp + sizeof(*nh);

        switch (nh->nh_state) {

        case NIT_CATCH:
            break;

        case NIT_NOMBUF:
        case NIT_NOCLUSTER:
        case NIT_NOSPACE:
            p->md.stat.ps_drop = nh->nh_dropped;
            continue;

        case NIT_SEQNO:
            continue;

        default:
            snprintf(p->errbuf, sizeof(p->errbuf),
                "bad nit state %d", nh->nh_state);
            return (-1);
        }
        ++p->md.stat.ps_recv;
        bp += ((sizeof(struct nit_hdr) + nh->nh_datalen +
            sizeof(int) - 1) & ~(sizeof(int) - 1));

        caplen = nh->nh_wirelen;
        if (caplen > p->snapshot)
            caplen = p->snapshot;
        if (bpf_filter(fcode, cp, nh->nh_wirelen, caplen)) {
            struct pcap_pkthdr h;
            h.ts = nh->nh_timestamp;
            h.len = nh->nh_wirelen;
            h.caplen = caplen;
           (*callback)(user, &h, cp);
            if (++n >= cnt && cnt >= 0) {
                p->cc = ep - bp;
                p->bp = bp;
                return (n);
            }
        }
    }
    p->cc = 0;
    return (n);
}  

   大体上的过程是这样的,

     n = 0;
    ep = bp + cc;
     while (bp < ep) 开始读取一个设备缓存的长度

    if (p->break_loop) {
            if (n == 0) {
                p->break_loop = 0;
                return (-2);
            } else {
                p->cc = ep - bp;
                p->bp = bp;
                return (n);
            }
        } 如果break_loop被调用,并且这个时候没有处理过相关数据包,返回-2;如果不是返回当前处理过的数据包数,注意这个时候不可能返回0值的;

    if (bpf_filter(fcode, cp, nh->nh_wirelen, caplen)) 如果捕获的数据包符合设备定义的过滤器。开始正式的处理过程

      (*callback)(user, &h, cp);呵呵我们在pcap_loop和pcap_dispatch定义的回调函数原来在这里。

     if (++n >= cnt && cnt >= 0) {
                p->cc = ep - bp;
                p->bp = bp;
                return (n);
     }如果处理过的数据包已经达到cnt时返回处理过的数据包个数,注意当cnt为负数时这部分永远都不会执行,所以这里的return也不能可能返回0值;cnt为0零时每次只捕获一个符合条件的数据包就返回。

     while循环结束后还有一个return语句,这个return可能返回0值并且只有这一处可能返回0值,当且仅当设备缓存当中没有符合条件的数据包。

       所以pcap_loop()两种情况下可以返回:1是break_loop()并且当前没有处理到任何符合过滤规则的数据包(如果已经开始读取一个缓存则只有等这个缓存处理完毕才能退出);2是当前缓冲当中不存在符合过滤规则的数据包。

 

        pcap_next()和pcap_next_ex()

        pcap_next()其实就是pcap_dispatch(),只不过它的回调函数是已经定义好的。pcap_next() 有一些不好的地方。首先,它效率低下,尽管它隐藏了回调的方式,但它依然依赖于函数 pcap_dispatch()。第二,它不能检测到文件末尾这个状态(EOF),因此,如果数据包是从文件读取来的,那么它就不那么有用了。

static void
pcap_oneshot(u_char *userData, const struct pcap_pkthdr *h, const u_char *pkt)
{
    struct singleton *sp = (struct singleton *)userData;
    *sp->hdr = *h;
    sp->pkt = pkt;
}

const u_char *
pcap_next(pcap_t *p, struct pcap_pkthdr *h)
{
    struct singleton s;

    s.hdr = h;
    if (pcap_dispatch(p, 1, pcap_oneshot, (u_char*)&s) <= 0)
        return (0);
    return (s.pkt);
}

        pcap_next_ex(),其实它的回调函数和pcap_next是一样一样一样的啊~

static void
pcap_fakecallback(u_char *userData, const struct pcap_pkthdr *h,
    const u_char *pkt)
{
    struct pkt_for_fakecallback *sp = (struct pkt_for_fakecallback *)userData;

    *sp->hdr = *h;
    *sp->pkt = pkt;
}

int
pcap_next_ex(pcap_t *p, struct pcap_pkthdr **pkt_header,
    const u_char **pkt_data)
{
    struct pkt_for_fakecallback s;

    s.hdr = &p->pcap_header;
    s.pkt = pkt_data;

    /* Saves a pointer to the packet headers */
    *pkt_header= &p->pcap_header;

#ifdef HAVE_REMOTE
    /* Checks the capture type */
    if (p->rmt_clientside)
    {
        /* We are on an remote capture */
        if (!p->rmt_capstarted)
        {
            // if the capture has not started yet, please start it
            if (pcap_startcapture_remote(p) )
                return -1;
        }

        return pcap_read_nocb_remote(p, pkt_header, (u_char **) pkt_data);
    }
#endif /* HAVE_REMOTE */

    if (p->sf.rfile != NULL) {
        int status;

        /* We are on an offline capture */
        status = pcap_offline_read(p, 1, pcap_fakecallback,
            (u_char *)&s);

        /*
         * Return codes for pcap_offline_read() are:
         *   -  0: EOF
         *   - -1: error
         *   - >1: OK
         * The first one ('0') conflicts with the return code of
         * 0 from pcap_read() meaning "no packets arrived before
         * the timeout expired", so we map it to -2 so you can
         * distinguish between an EOF from a savefile and a
         * "no packets arrived before the timeout expired, try
         * again" from a live capture.
         */
        if (status == 0)
            return (-2);
        else
            return (status);
    }

    /*
     * Return codes for pcap_read() are:
     *   -  0: timeout
     *   - -1: error
     *   - -2: loop was broken out of with pcap_breakloop()
     *   - >1: OK
     * The first one ('0') conflicts with the return code of 0 from
     * pcap_offline_read() meaning "end of file".
    */
    return (p->read_op(p, 1, pcap_fakecallback, (u_char *)&s));
}


September 04

WinPcap编程(四)

(1)pcap_t:一个已打开的捕捉实例的描述符。这个结构体对用户来说是不透明的,它通过wpcap.dll提供的函数,维护了它的内容。

这到底是个啥东西呢,看看吧

<pcap.h>定义pcap_t, typedef struct pcap pcap_t

<pcap-int.h>定义pcap

struct pcap {
#ifdef WIN32
    ADAPTER *adapter;
    LPPACKET Packet;
    int timeout;
    int nonblock;
#else
    int fd;
    int selectable_fd;
    int send_fd;
#endif /* WIN32 */
    int snapshot;
    int linktype;
    int tzoff;        /* timezone offset */
    int offset;        /* offset for proper alignment */

    int break_loop;        /* flag set to force break from packet-reading loop */

#ifdef PCAP_FDDIPAD
    int fddipad;
#endif

#ifdef MSDOS
        int inter_packet_wait;   /* offline: wait between packets */
        void (*wait_proc)(void); /*          call proc while waiting */
#endif

    struct pcap_sf sf;
    struct pcap_md md;

    /*
     * Read buffer.
     */
    int bufsize;
    u_char *buffer;
    u_char *bp;
    int cc;

    /*
     * Place holder for pcap_next().
     */
    u_char *pkt;

    /* We're accepting only packets in this direction/these directions. */
    pcap_direction_t direction;

    /*
     * Methods.
     */
    int    (*read_op)(pcap_t *, int cnt, pcap_handler, u_char *);
    int    (*inject_op)(pcap_t *, const void *, size_t);
    int    (*setfilter_op)(pcap_t *, struct bpf_program *);
    int    (*setdirection_op)(pcap_t *, pcap_direction_t);
    int    (*set_datalink_op)(pcap_t *, int);
    int    (*getnonblock_op)(pcap_t *, char *);
    int    (*setnonblock_op)(pcap_t *, int, char *);
    int    (*stats_op)(pcap_t *, struct pcap_stat *);
    void    (*close_op)(pcap_t *);

    /*
     * Placeholder for filter code if bpf not in kernel.
     */
    struct bpf_program fcode;

    char errbuf[PCAP_ERRBUF_SIZE + 1];
    int dlt_count;
    u_int *dlt_list;

    struct pcap_pkthdr pcap_header;    /* This is needed for the pcap_next_ex() to work */

#ifdef HAVE_REMOTE
#ifndef WIN32    // Win32 already defines 'timeout'
    int timeout;                //!< timeout to be used in the pcap_open()
#endif
    /*! \brief '1' if we're the network client; needed by several functions (like pcap_setfilter() ) to know if
        they have to use the socket or they have to open the local adapter. */
    int rmt_clientside;

    SOCKET rmt_sockctrl;        //!< socket ID of the socket used for the control connection
    SOCKET rmt_sockdata;        //!< socket ID of the socket used for the data connection
    int rmt_flags;                //!< we have to save flags, since they are passed by the pcap_open_live(), but they are used by the pcap_startcapture()
    int rmt_capstarted;            //!< 'true' if the capture is already started (needed to knoe if we have to call the pcap_startcapture()
    struct pcap_samp rmt_samp;    //!< Keeps the parameters related to the sampling process.
    char *currentfilter;        //!< Pointer to a buffer (allocated at run-time) that stores the current filter. Needed when flag PCAP_OPENFLAG_NOCAPTURE_RPCAP is turned on.
#endif /* HAVE_REMOTE */
};

所以一般只用pcap_t的指针,因为头文件<pcap.h>里面没定义嘛。

(2)文档上的一个错误?

typedef void(*) pcap_handler (u_char *user, const struct pcap_pkthdr *pkt_header, const u_char *pkt_data)

实际pcap.h中的定义是

typedef void (*pcap_handler)(u_char *, const struct pcap_pkthdr *, const u_char *);

可能函数指针能象前者那么定义?我反正是没见过。

(3)pcap_open()

    新的设备打开函数,注意:仅在win32平台上可用,开发小组建议用这个函数来替代所有的pcap_open_xxx()函数,下面看看这个bt函数吧。

    首先注意你include<pcap.h>不一定能调用成功哦,为啥呢?因为它不在pcap.h里声明啊,笨!又被文档骗了!实际上它是在头文件remote-ext.h里声明的,而函数的定义pcap-new.c里面。这样的函数一共五个:

pcap_t *pcap_open(const char *source, int snaplen, int flags, int read_timeout, struct pcap_rmtauth *auth, char *errbuf);
int pcap_createsrcstr(char *source, int type, const char *host, const char *port, const char *name, char *errbuf);
int pcap_parsesrcstr(const char *source, int *type, char *host, char *port, char *name, char *errbuf);
int pcap_findalldevs_ex(char *source, struct pcap_rmtauth *auth, pcap_if_t **alldevs, char *errbuf);
struct pcap_samp *pcap_setsampling(pcap_t *p);

    那为啥有时候include<pcap.h>能调用成功呢?看看pcap.h源代码吧:

#ifdef HAVE_REMOTE
/* Includes most of the public stuff that is needed for the remote capture */
#include "remote-ext.h"
#endif     /* HAVE_REMOTE */

    明白了?原来还得定义HAVE_REMOTE。否则老老实实写include<remote-ext.h>.

September 02

WinPcap编程(三)

5. 利用winpcap编写应用程序
    winpcap自带的文档和例子很全面,边写边查文档吧。大笑

WinPcap编程(二)

3.工作原理

    数据包抓取和过滤一般包括两部分,一是内核级的抓包过滤模块,二是用户级的数据传输模块。WinPcap的结构是基于BSD Capturing Componets。下图是两者的结构图。

image

image

    可以看到两者抓取数据包和过滤数据包的方式基本是相同的,首先通过设备驱动程序获取数据包,其次通过过滤讲用户感兴趣的数据包拷贝到内核的缓存当中,最后用户程序通过两者提供的接口将这些数据读入到用户数据缓存当中做相应的处理。

    那么两者的不同点在于:

    1.BSD当中内核级抓包过滤模块BPF当中的内核缓存是包括两部分,一部分用来读取网络数据store,一部分向用户缓存输出hold;而WinPcap当中的相应部分NPF当中的内核缓存是一块环行的缓存。相比较而言NPF的结构内存利用率和效率相对更高一些。而且NPF当中的缓存是固定的32KB,而BPF的内核缓存是可变的,默认值是1MB。

    2.统计信息监控的0-COPY机制,NPF支持内核级的数据包监控,数据包不需要拷贝到用户缓存当中,监控结果定期上报给用户应用程序,提高了效率。

4.实现

    监控WinPcap包括三个模块:一是内核级模块NPF用来完成数据包过滤;二是低级的用户级模块packet.dll,packet.dll可以直接访问驱动的函数,并且依赖于微软操作系统的可编程接口,用来帮助用户级应用和低层内核进行通信;三是高级的用户级模块wpcap.dll,wpcap.dll不依赖于系统和网络硬件,它通过调用和封装packet.dll中的函数来提供一些应用开发所需的高级功能,如数据包过滤器的生成和用户级数据缓存等。一般来讲wpcap.dll更方便用户级应用程序的开发,当然直接使用packet.dll与内核进行功能交互也是可行的,但是一般不这样做。下图说明了三个模块间的关系。

image

非常重要的注意事项,请仔细阅读!

Packet.dll 的源代码是开放的,并且有完整的文档。 然而,packet.dll应该被认为是核心API,因为它建立在WinPcap内部的目的,就是为真正的公共API:wpcap.dll建立一层"隔离墙"。

由于应用程序使用WinPcap的常规(normal)方式推荐(suggested)方式,是通过wpcap.dll,所以,我们不保证packet.dll的API不会在未来的WinPcap的发行版中被修改,并且,我们不提供这个API的支持。

[1]Fulvio Risso, Loris Degioanni, An Architecture for High Performance Network Analysis, Proceedings of the 6th IEEE

   Symposium on Computers and Communications (ISCC 2001), Hammamet, Tunisia, July 2001

 
感谢访问!
Please wait...
Sorry, the comment you entered is too long. Please shorten it.
You didn't enter anything. Please try again.
Sorry, we can't add your comment right now. Please try again later.
To add a comment, you need permission from your parent. Ask for permission
Your parent has turned off comments.
Sorry, we can't delete your comment right now. Please try again later.
You've exceeded the maximum number of comments that can be left in one day. Please try again in 24 hours.
Your account has had the ability to leave comments disabled because our systems indicate that you may be spamming other users. If you believe that your account has been disabled in error please contact Windows Live support.
Complete the security check below to finish leaving your comment.
The characters you type in the security check must match the characters in the picture or audio.