發送數據包


      提起發送數據包大家可能會想到使用SOCKET編程來實現,但其實WinPcap也提供了發送數據包的API,盡管從名字上來看它應該是用來數據捕捉的。值得注意的是,libpcap不支持發送數據包的功能,因此下面提到的函數都是WinPcap的擴展,在UNIX平台下是不支持的。下面這個實例程序正是利用了WinPcap中的pcap_sendpacket()來發送單個數據包。

#include <stdlib.h>
#include <stdio.h>
#define HAVE_REMOTE
#include <pcap.h>


int main(int argc, char **argv)
{
pcap_t *fp;
char errbuf[PCAP_ERRBUF_SIZE];
u_char packet[100];
int i;

/* 檢查命令行參數的合法性 */
if (argc != 2)
{
printf("usage: %s interface (e.g. 'rpcap://eth0')", argv[0]);
return -1;
}

/* 打開輸出設備 */
if ( (fp= pcap_open(argv[1], // 設備名
100, // 要捕獲的部分 (只捕獲前100個字節)
PCAP_OPENFLAG_PROMISCUOUS, // 混雜模式
1000, // 讀超時時間
NULL, // 遠程機器驗證
errbuf // 錯誤緩沖
) ) == NULL)
{
fprintf(stderr,"\nUnable to open the adapter. %s is not supported by WinPcap\n", argv[1]);
return -1;
}

/* 假設在以太網上,設置MAC的目的地址為 1:1:1:1:1:1 */
packet[0]=1;
packet[1]=1;
packet[2]=1;
packet[3]=1;
packet[4]=1;
packet[5]=1;

/* 設置MAC源地址為 2:2:2:2:2:2 */
packet[6]=2;
packet[7]=2;
packet[8]=2;
packet[9]=2;
packet[10]=2;
packet[11]=2;

/* 填充剩下的內容 */
for(i=12; i<100; i++)
{
packet[i]=i%256;
}

/* 發送數據包 */
if (pcap_sendpacket(fp, packet, 100 /* size */) != 0)
{
fprintf(stderr,"\nError sending the packet: %s\n", pcap_geterr(fp));
return -1;
}

return 0;
}

      下面是pcap_sendpacket()函數的聲明:

      int pcap_sendpacket(pcap_t *, const u_char *, int);

      這三個參數的含義分別是發送數據包的適配器、要發送的數據和緩沖的長度。需要注意的是,緩沖直接發送至網絡,不會有任何的控制和加工。這就意味着程序需要構造正確的協議頭,使得數據包更有意義。該函數的返回值為0表示發送成功,-1表示發送失敗。

      但是我們知道,在真正的應用程序中我們往往需要發送多個數據包,事實上WinPcap也支持發送多個數據包,而且WinPcap的這種方式會更高級、更強大,結構會更優。它支持發送隊列的方法,即利用隊列作為將要發送至網絡的數據包的容器。我們來看看下面這個例子。

#include <stdlib.h>
#include <stdio.h>

#define HAVE_REMOTE
#define WPCAP
#include <pcap.h>

void usage();

int main(int argc, char **argv)
{
pcap_t *indesc,*outdesc;
char errbuf[PCAP_ERRBUF_SIZE];
char source[PCAP_BUF_SIZE];
FILE *capfile;
int caplen, sync;
u_int res;
pcap_send_queue *squeue;
struct pcap_pkthdr *pktheader;
const u_char *pktdata;
float cpu_time;
u_int npacks = 0;

/* 檢查命令行參數的合法性 */
if (argc <= 2 || argc >= 5)
{
usage();
return -1;
}

/* 獲取捕獲文件長度 */
capfile=fopen(argv[1],"rb");
if(!capfile)
{
printf("Capture file not found!\n");
return -1;
}

fseek(capfile , 0, SEEK_END);
caplen= ftell(capfile)- sizeof(struct pcap_file_header);
fclose(capfile);

/* 檢查時間戳是否合法 */
if(argc == 4 && argv[3][0] == 's')
sync = TRUE;
else
sync = FALSE;

/* 開始捕獲 */
/* 根據WinPcap的新語法創建一個源字符串 */
if ( pcap_createsrcstr( source, // 源字符串
PCAP_SRC_FILE, // 我們要打開的文件
NULL, // 遠程主機
NULL, // 遠程主機的端口
argv[1], // 我們要打開的文件名
errbuf // 錯誤緩沖
) != 0)
{
fprintf(stderr,"\nError creating a source string\n");
return -1;
}

/* 打開捕獲文件 */
if ( (indesc= pcap_open(source, 65536, PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, errbuf) ) == NULL)
{
fprintf(stderr,"\nUnable to open the file %s.\n", source);
return -1;
}

/* 打開要輸出的適配器 */
if ( (outdesc= pcap_open(argv[2], 100, PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, errbuf) ) == NULL)
{
fprintf(stderr,"\nUnable to open adapter %s.\n", source);
return -1;
}

/* 檢查MAC的類型 */
if (pcap_datalink(indesc) != pcap_datalink(outdesc))
{
printf("Warning: the datalink of the capture differs from the one of the selected interface.\n");
printf("Press a key to continue, or CTRL+C to stop.\n");
getchar();
}

/* 分配發送隊列 */
squeue = pcap_sendqueue_alloc(caplen);

/* 從文件中將數據包填充到發送隊列 */
while ((res = pcap_next_ex( indesc, &pktheader, &pktdata)) == 1)
{
if (pcap_sendqueue_queue(squeue, pktheader, pktdata) == -1)
{
printf("Warning: packet buffer too small, not all the packets will be sent.\n");
break;
}

npacks++;
}

if (res == -1)
{
printf("Corrupted input file.\n");
pcap_sendqueue_destroy(squeue);
return -1;
}

/* 發送隊列 */

cpu_time = (float)clock ();

if ((res = pcap_sendqueue_transmit(outdesc, squeue, sync)) < squeue->len)
{
printf("An error occurred sending the packets: %s. Only %d bytes were sent\n", pcap_geterr(outdesc), res);
}

cpu_time = (clock() - cpu_time)/CLK_TCK;

printf ("\n\nElapsed time: %5.3f\n", cpu_time);
printf ("\nTotal packets generated = %d", npacks);
printf ("\nAverage packets per second = %d", (int)((double)npacks/cpu_time));
printf ("\n");

/* 釋放發送隊列 */
pcap_sendqueue_destroy(squeue);

/* 關閉輸入文件 */
pcap_close(indesc);

/*
* 釋放輸出適配器
* IMPORTANT: 記得一定要關閉適配器,不然就不能保證
* 所有的數據包都回被發送出去
*/
pcap_close(outdesc);


return 0;
}


void usage()
{

printf("\nSendcap, sends a libpcap/tcpdump capture file to the net. Copyright (C) 2002 Loris Degioanni.\n");
printf("\nUsage:\n");
printf("\t sendcap file_name adapter [s]\n");
printf("\nParameters:\n");
printf("\nfile_name: the name of the dump file that will be sent to the network\n");
printf("\nadapter: the device to use. Use \"WinDump -D\" for a list of valid devices\n");
printf("\ns: if present, forces the packets to be sent synchronously, i.e. respecting the timestamps in the dump file. This option will work only under Windows NTx.\n\n");

exit(0);
}

      介紹一個新的數據類型,pcap_send_queue結構體,下面是該結構體的定義。

/*!
\brief A queue of raw packets that will be sent to the network with pcap_sendqueue_transmit().
*/
struct pcap_send_queue
{
u_int maxlen; ///< Maximum size of the the queue, in bytes. This variable contains the size of the buffer field.
u_int len; ///< Current size of the queue, in bytes.
char *buffer; ///< Buffer containing the packets to be sent.
};

typedef struct pcap_send_queue pcap_send_queue;

      pcap_send_queue結構體正如其名,它是發送數據隊列,具體字段的含義源碼中已有說明。
      我們再來看看這個發送隊列的生命周期~~

      1. 創建發送隊列。通過調用pcap_sendqueue_alloc()函數:

          pcap_send_queue* pcap_sendqueue_alloc(u_int memsize);
         其中memsize表示申請隊列的大小,返回的是隊列結構體指針。

      2. 向隊列加入要發送的數據包。通過調用pcap_sendqueue_queue()函數:

           int pcap_sendqueue_queue(pcap_send_queue* queue, const struct pcap_pkthdr *pkt_header, const u_char *pkt_data);

          queue表示指向發送隊列的指針;pkt_header表示指向pcap_pkthdr結構體(dump文件中數據包的首部)的指針;pkt_data即為將要發送的數據。

          本例中是從文件中讀取pkt_header和pkt_data后再將數據包添加至隊列中的。

      3. 發送數據隊列。通過調用pcap_sendqueue_transmit()函數:

           u_int pcap_sendqueue_transmit(pcap_t *p, pcap_send_queue* queue, int sync);

          這里只需要注意第三個參數sync。如果非0,則發送是同步進行的,也就是說,時間戳相符的數據包才會被處理。當然這樣會消耗大量的CPU資源,因為同步操作由內核驅動中“忙等”循環實現。盡管這個操作對CPU要求很高,但它對數據包的處理結果通常是很精確的。(通常在數微秒左右,或更少)

          需要注意的是利用pcap_sendqueue_transmit()發送數據隊列比通過send_packet()發送一系列數據包要高效得多,這是因為發送隊列保存在內核級的緩沖區,減少了上下文交換的次數。

      4. 釋放數據隊列。通過調用pcap_sendqueue_destroy()函數:

           void pcap_sendqueue_destroy(pcap_send_queue* queue);

      另外需要注意到的一點是,程序中比較了dump文件的鏈路層和發送數據包的適配器的鏈路層,如果不同則會打印warning,因為這樣發送將毫無意義可言。

      最后看一下第二個程序在本機上運行的結果:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM