在Linux中socket所提供的功能十分强大,可以访问各层的数据(网络接口层、IP层以及运输层)。在Linux之socket编程:网络基础中有讲解socket的用法。这里就不重复了。TCP/IP中,层与层之间是相互独立的,在Linux中可以直接通过设置socket不同参数来实现各个层的数据读取以及操作。下图为Linux中分层,应用层可以访问每个层的数据
数据链路层访问
在Linux下数据链路层的访问通常是通过编写内核驱动程序来实现的,在应用程序中也可以使用SOCK_PACKET类型的协议来实现部分功能,如自定义协议,ARP协议请求。
建立套接字type选择SOCK_PACKET类型时,从网卡接收到的数据(帧)不会交付给上层的IP协议栈处理,而是直接交付给用户。用户获取到的数据是帧的格式,即最原始的数据。可以使用如下方法来生成一个套接字:fd = socket(AF_INET, SOCK_PACKET,3),这里的3表示截取的数据帧的类型为不确定,即可以获取物理层上的所有的包。
以太网的数据结构如下,在Linux中的**< netinet/if_ether.h >文件中有定义以太网帧的数据结构struct ethhdr**,也有类型的定义。其中帧尾的CRC校验基本上是由硬件负责,软件不需要对于进行处理(即读取到的数据包不会包括这个字段的数据,其有MAC处理)。
#define ETH_ALEN 6 /* Octets in one ethernet addr */
#define ETH_HLEN 14 /* Total octets in header. */
#define ETH_ZLEN 60 /* Min. octets in frame sans FCS */
#define ETH_DATA_LEN 1500 /* Max. octets in payload */
#define ETH_FRAME_LEN 1514 /* Max. octets in frame sans FCS */
#define ETH_FCS_LEN 4 /* Octets in the FCS */
/*以太网帧中类型的定义 常用的位IP(0x0800) 、ARP(0x0806) */
#define ETH_P_LOOP 0x0060 /* Ethernet Loopback packet */
#define ETH_P_PUP 0x0200 /* Xerox PUP packet */
#define ETH_P_PUPAT 0x0201 /* Xerox PUP Addr Trans packet */
#define ETH_P_IP 0x0800 /* Internet Protocol packet */
#define ETH_P_X25 0x0805 /* CCITT X.25 */
#define ETH_P_ARP 0x0806 /* Address Resolution packet */
#define ETH_P_BPQ 0x08FF /* G8BPQ AX.25 Ethernet Packet [ NOT AN OFFICIALLY REGISTERED ID ] */
#define ETH_P_IEEEPUP 0x0a00 /* Xerox IEEE802.3 PUP packet */
#define ETH_P_IEEEPUPAT 0x0a01 /* Xerox IEEE802.3 PUP Addr Trans packet */
#define ETH_P_DEC 0x6000 /* DEC Assigned proto */
#define ETH_P_DNA_DL 0x6001 /* DEC DNA Dump/Load */
#define ETH_P_DNA_RC 0x6002 /* DEC DNA Remote Console */
#define ETH_P_DNA_RT 0x6003 /* DEC DNA Routing */
#define ETH_P_LAT 0x6004 /* DEC LAT */
#define ETH_P_DIAG 0x6005 /* DEC Diagnostics */
#define ETH_P_CUST 0x6006 /* DEC Customer use */
#define ETH_P_SCA 0x6007 /* DEC Systems Comms Arch */
#define ETH_P_RARP 0x8035 /* Reverse Addr Res packet */
struct ethhdr {
unsigned char h_dest[ETH_ALEN]; /* destination eth addr */
unsigned char h_source[ETH_ALEN]; /* source ether addr */
__be16 h_proto; /* packet type ID field 包的类型,可以使用上面的宏定义,IP是0x0800,ARP是0x0806*/
} __attribute__((packed));
所以要是想在以太网上传输私有协议格式数据,咱们可以直接使用SOCK_PACKET类型的套接字来实现。现在协议栈很完善,要是有些协议没有支持,咱们也是通过这种socket类型的套接字来完成的。
在网络中ARP协议是非常重要的,其完成了IP地址到MAC地址之间的转换,在以太网上传输地址,最后使用的是物理地址。所以在传输数据之前必须获取IP对应的MAC地址。ARP的格式如下,其中帧类型为ETH_P_ARP(0x0806) ,ARP协议在ARP介绍中有讲解。其中ARP的数据结构在netinet/if_arp.h中有定义,其结构为struct arphdr
/* ARP protocol opcodes. */
#define ARPOP_REQUEST 1 /* ARP request */
#define ARPOP_REPLY 2 /* ARP reply */
#define ARPOP_RREQUEST 3 /* RARP request */
#define ARPOP_RREPLY 4 /* RARP reply */
#define ARPOP_InREQUEST 8 /* InARP request */
#define ARPOP_InREPLY 9 /* InARP reply */
#define ARPOP_NAK 10 /* (ATM)ARP NAK */
/* ARP protocol HARDWARE identifiers. */
#define ARPHRD_NETROM 0 /* from KA9Q: NET/ROM pseudo */
#define ARPHRD_ETHER 1 /* Ethernet 10Mbps */
#define ARPHRD_EETHER 2 /* Experimental Ethernet */
#define ARPHRD_AX25 3 /* AX.25 Level 2 */
#define ARPHRD_PRONET 4 /* PROnet token ring */
#define ARPHRD_CHAOS 5 /* Chaosnet */
#define ARPHRD_IEEE802 6 /* IEEE 802.2 Ethernet/TR/TB */
#define ARPHRD_ARCNET 7 /* ARCnet */
#define ARPHRD_APPLETLK 8 /* APPLEtalk */
#define ARPHRD_DLCI 15 /* Frame Relay DLCI */
#define ARPHRD_ATM 19 /* ATM */
#define ARPHRD_METRICOM 23 /* Metricom STRIP (new IANA id) */
#define ARPHRD_IEEE1394 24 /* IEEE 1394 IPv4 - RFC 2734 */
#define ARPHRD_EUI64 27 /* EUI-64 */
#define ARPHRD_INFINIBAND 32 /* InfiniBand */
struct arphdr
{
__be16 ar_hrd; /* format of hardware address 硬件类型,可以直接使用面的宏定义1为以太网硬件地址*/
__be16 ar_pro; /* format of protocol address 协议类型,0x0800表示高层使用的是IP协议*/
unsigned char ar_hln; /* length of hardware address 硬件地址长度*/
unsigned char ar_pln; /* length of protocol address 协议地址长度*/
__be16 ar_op; /* ARP opcode (command) ARP操作码,常见的位1、2、3、4*/
#if 0
/*
* Ethernet looks like this : This bit is variable sized however...
*/
unsigned char ar_sha[ETH_ALEN]; /* sender hardware address */
unsigned char ar_sip[4]; /* sender IP address */
unsigned char ar_tha[ETH_ALEN]; /* target hardware address */
unsigned char ar_tip[4]; /* target IP address */
#endif
};
下面的例子是使用数据链路层接口来模拟arp协议,即查找IP对应的MAC地址。在实际应用中基本上不会自己来实现,因为协议栈里面有很完善的arp机制。这里只是为了讲解直接从数据链路层获取数据。
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/if_ether.h>
#include<stdio.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdlib.h>
typedef struct __arppacket{
unsigned short ar_hrd;
unsigned short ar_pro;
unsigned char ar_hln;
unsigned char ar_pln;
unsigned short ar_op;
unsigned char src_ha[ETH_ALEN];
unsigned int src_ip;
unsigned char dest_ha[ETH_ALEN];
unsigned int dest_ip;
}arp_packet_st;
int main(int argc ,char*argv[])
{
int fd;
char ef[ETH_FRAME_LEN]={0};
arp_packet_st *p_arp;
struct ethhdr *p_ethhdr;
char eth_dest[ETH_ALEN] = {0xff,0xff,0xff,0xff,0xff,0xff};/*由于开始不知道对方的MAC,所有使用广播MAC地址*/
char eth_src[ETH_ALEN] = {0x00,0x0c,0x29,0x7f,0x5e,0x3f};
fd = socket(AF_INET, SOCK_PACKET,htons(3));
if(fd < 0){perror("socket err");exit(-1);}
p_ethhdr = (struct ethhdr*)ef;
memcpy(p_ethhdr->h_dest,eth_dest,ETH_ALEN);
memcpy(p_ethhdr->h_source,eth_src,ETH_ALEN);
p_ethhdr->h_proto = htons(ETH_P_ARP);/*帧类型,ARP协议*/
p_arp = (arp_packet_st*)(ef + ETH_HLEN);
p_arp->ar_hrd = htons(1);/*1表示类型类型为以太网*/
p_arp->ar_pro = htons(0x800);/*0x0800表示上层使用的是IP协议*/
p_arp->ar_hln = 6;
p_arp->ar_pln = 4;
p_arp->ar_op = htons(1);/*1为arp请求操作、2为arp响应*/
memcpy(p_arp->src_ha,eth_src,ETH_ALEN);
p_arp->src_ip = inet_addr("192.168.0.10");
memset(p_arp->dest_ha,0,ETH_ALEN);
p_arp->dest_ip = inet_addr(argv[1]);
int i = 0,j,ret;
char buf[ETH_FRAME_LEN] = {0},ips[30];
for(i = 0 ; i < 8;i++){
write(fd,ef,ETH_FRAME_LEN);
ret = read(fd,buf,ETH_FRAME_LEN);
if(ret > 0){
printf("ret byte = %d \n",ret);
p_ethhdr = (struct ethhdr*)buf;
printf("dset hardaddr %02x-%02x-%02x-%02x-%02x-%02x\n",p_ethhdr->h_dest[0],p_ethhdr->h_dest[1],
p_ethhdr->h_dest[2],p_ethhdr->h_dest[3],p_ethhdr->h_dest[4],p_ethhdr->h_dest[5]);
printf("src hardaddr %02x-%02x-%02x-%02x-%02x-%02x\n",p_ethhdr->h_source[0],p_ethhdr->h_source[1],
p_ethhdr->h_source[2],p_ethhdr->h_source[3],p_ethhdr->h_source[4],p_ethhdr->h_source[5]);
printf("protocol %x\n",ntohs(p_ethhdr->h_proto));
p_arp = (arp_packet_st *)(buf + ETH_HLEN);
printf("ar_hrd %d\n",ntohs(p_arp->ar_hrd));
printf("ar_pro %x\n",ntohs(p_arp->ar_pro));
printf("h_len %d ip len %d op %d\n",p_arp->ar_hln,p_arp->ar_pln,ntohs(p_arp->ar_op));
printf("src hardaddr %02x-%02x-%02x-%02x-%02x-%02x\n",p_arp->src_ha[0],p_arp->src_ha[1],
p_arp->src_ha[2],p_arp->src_ha[3],p_arp->src_ha[4],p_arp->src_ha[5]);
printf("dset hardaddr %02x-%02x-%02x-%02x-%02x-%02x\n",p_arp->dest_ha[0],p_arp->dest_ha[1],
p_arp->dest_ha[2],p_arp->dest_ha[3],p_arp->dest_ha[4],p_arp->dest_ha[5]);
printf("src ip %s \n",inet_ntop(AF_INET,&p_arp->src_ip,ips,sizeof(ips)));
printf("dest ip %s \n",inet_ntop(AF_INET,&p_arp->dest_ip,ips,sizeof(ips)));
}
sleep(1);
printf("\n\n");
memset(buf,0,sizeof(buf));
}
close(fd);
运行的结果如下,我电脑的IP地址为192.168.1.130以及192.168.0.23,这两个IP都可以获取到MAC地址。这里虚拟机由于使用的是桥接,所以效果不是很明显。
这里提供直接操作网络接口的套接字,所以咱们也可以不使用内核的协议栈(推荐使用协议栈)。假如想自己编写私有的IP数据包或者是传输层数据包以及应用层的数据,其中netinet/ip.h、netinet/tcp.h、netinet/udp.h文件中分别定义了IP包头、TCP包头和UDP包头的数据结构。结构名为struct iphdr、struct udphdr.h、struct tcphdr.h,这些定义和协议里面的格式是一样的,对应的头文件中也有些常用宏的定义。各层的包头结构名都是struct xxxhdr,其中xxx为协议,如ip、tcp、udp、eth、arp等等,其中hdr为headrest的缩写。
- IP头部数据结构定义,对应的协议格式
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u8 ihl:4,
version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
__u8 version:4,
ihl:4;
#else
#error "Please fix <asm/byteorder.h>"
#endif
__u8 tos;
__be16 tot_len;
__be16 id;
__be16 frag_off;
__u8 ttl;
__u8 protocol;
__sum16 check;
__be32 saddr;
__be32 daddr;
/*The options start here. */
};
- TCP头部数据结构的定义,以及对应的协议格式
struct tcphdr {
__be16 source;
__be16 dest;
__be32 seq;
__be32 ack_seq;
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u16 res1:4,
doff:4,
fin:1,
syn:1,
rst:1,
psh:1,
ack:1,
urg:1,
ece:1,
cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD)
__u16 doff:4,
res1:4,
cwr:1,
ece:1,
urg:1,
ack:1,
psh:1,
rst:1,
syn:1,
fin:1;
#else
#error "Adjust your <asm/byteorder.h> defines"
#endif
__be16 window;
__sum16 check;
__be16 urg_ptr;
};
- UDP头部数据结构定义,以及对应的协议格式
struct udphdr {
__be16 source;
__be16 dest;
__be16 len;
__sum16 check;
};
- ICMP头部数据结构定义(icmp.h),以及对应的协议格式.
struct icmphdr {
__u8 type;
__u8 code;
__sum16 checksum;
union {
struct {
__be16 id;
__be16 sequence;
} echo;
__be32 gateway;
struct {
__be16 __unused;
__be16 mtu;
} frag;
} un;
};
- IGMP头部数据结构定义,以及对应的数据格式,IGMP由于分为V1、V2以及V3,这里不做详解。到时会有专门的博客进行IGMP的讲述。
struct igmphdr
{
__u8 type;
__u8 code; /* For newer IGMP */
__sum16 csum;
__be32 group;
};
/* V3 group record types [grec_type] */
#define IGMPV3_MODE_IS_INCLUDE 1
#define IGMPV3_MODE_IS_EXCLUDE 2
#define IGMPV3_CHANGE_TO_INCLUDE 3
#define IGMPV3_CHANGE_TO_EXCLUDE 4
#define IGMPV3_ALLOW_NEW_SOURCES 5
#define IGMPV3_BLOCK_OLD_SOURCES 6
struct igmpv3_grec {
__u8 grec_type;
__u8 grec_auxwords;
__be16 grec_nsrcs;
__be32 grec_mca;
__be32 grec_src[0];
};
struct igmpv3_report {
__u8 type;
__u8 resv1;
__be16 csum;
__be16 resv2;
__be16 ngrec;
struct igmpv3_grec grec[0];
};
struct igmpv3_query {
__u8 type;
__u8 code;
__be16 csum;
__be32 group;
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u8 qrv:3,
suppress:1,
resv:4;
#elif defined(__BIG_ENDIAN_BITFIELD)
__u8 resv:4,
suppress:1,
qrv:3;
#else
#error "Please fix <asm/byteorder.h>"
#endif
__u8 qqic;
__be16 nsrcs;
__be32 srcs[0];
};
IP网络层数据的访问
原始套接字是一种对原始网络报文进行处理的套接字。原始套接字的创建使用与通用套接字创建的方法是一致的,只是套接字类型使用SOCK_RAW,其protocol可以为IPPROTO_IP、IPPROTO_ICMP、IPPROTO_TCP、IPPROTO_UDP、IPPROTO_RAW。这个其实和上述的SOCK_PACKET类型类似。只不过其获取的数据是IP层的数据,即去除了MAC地址,帧类型以及CRC等。原始套接字接收报文的规则为:如果接收的报文数据中的协议类型与自定义的原始套接字匹配,那么将接收的所有数据复制到套接字中;如果套接字绑定了本地地址,那么只有当接收的报文数据IP头中的目的地址等于本地地址时,才复制数据;如果套接字定义了远端地址,那么只有接收数据IP头中对应的源地址与远端地址匹配时,才复制数据。
其中原始套接字的主要用途有:使用原始套接字处理ICMP、IGMP分组,由于ICMP和IGMP属于IP层的协议,不会上交给运输层,所以需要使用原始套接字来处理;使用原始套接字处理特殊的IP数据报,内核不处理这些数据报的协议字段,大多数内核只处理1(ICMP)、2(IGMP)、6(TCP)、17(UDP)等协议的数据,像下图中的OSPF协议不支持,所以需要自己处理;使用原始套接字构造自己的特定类型的分组,通过设置IP_HDRINCL可以对IP头部进行操作,可以修改IP数据和IP层之上的各层数据。其中可以通过setsockopt接口设置IP_HDRINCL使接收到的数据包含IP头部,这时我们可以自己修改IP头部的数据;同样发送的时候也是需要进行头部处理的,如校验处理、TTL等。如设置了IP_HDRINCL,那么接收(或发送)到的数据指向包头;否则指向IP数据分组的载体(数据)。如果IP以分片形式到达,则所有分片都已经接收到并重组后才传给原始套接字;内核不能识别的协议、格式等也会传递给原始套接字;接收到的UDP、TCP协议的数据不会传给任何原始套接字接口,这些协议的数据需要通过数据链路层获取。
在进行原始套接字报文处理的时候,通过会操作IP头部、ICMP头部、IGMP头部、UDP、TCP头部等。这些数据结构分别为struct Iphdr、struct icmphdr、struct igmphdr、struct udphdr、struct tcphdr。从原始套接字中直接获取数据,然后根据自己的需求修改相应的字段。有空再补充使用原始套接字实现ping的和操作IGMP的例子 ,下面是通过原始套接字ping的例子,只是简单的使用,要是追求完整的可以查看busybox中的源码。
ping属于ICMP协议,其格式如下。其中类型为8的时候是请求包,而类型为0是响应包(#define ICMP_ECHO 8 /* Echo Request / #define ICMP_ECHOREPLY 0 / Echo Reply */ )。代码这里没有使用默认为0。
校验和采用的是反码算术运算和,方法如下:把协议中的数据划分为许多16位字的序列,并把校验和字段置零。用反码算术运算把所有16位字相加后,将得到的和的反码写入校验和字段。
在ping包中,第4-8字节分别用标识符和序列号来表示包所属的进程以及序列号,其中标识符(常用PID表示)可以识别是那个进程发过来的,而序列号可以分辨发送和接收的数据是否是一对。用wireshark抓包可以分析其格式:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/ip_icmp.h>
#include<netinet/ip.h>
#include<arpa/inet.h>
#include<pthread.h>
#include<sys/time.h>
#include<signal.h>
#include<string.h>
/*icmp 中的校验采用的是校验和(即用反码算术运算把所有16位字相加后,将得到的和的反码写入校验和字段)。
*由于在网络上传输的字节序为大端字节序,
*所以data长度为奇数的时候最后一个字节的数据为高位字节,剩余的以0进行填充。得到的结构也是网络序的。不需要进行转序
*/
static unsigned short checksum(unsigned char*data ,int len)
{
int sum = 0;
int odd = len%2;
while(len & 0xfffe){
sum += *(unsigned short*)data;
data += 2;
len -= 2;
}
if(odd){
unsigned short tmp =((*data) << 8)&0xff00;
sum += tmp;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >>16);
return ~sum;
}
/*用来校验checksum,在IP中很多头部字节使用的是checksum而不使用CRC校验,因为CRC校验的时间比较长*/
int calc_checksum(unsigned char * data,int len)
{
int sum = 0;
int odd = len%2;
while(len &0xfffe){
sum +=*(unsigned short*)data;
data += 2;
len -= 2;
}
if(odd){
sum +=((*data << 8) &0xff00);
}
return sum == 0? 1 : 0;
}
/*计算RTT*/
static struct timeval icmp_tvsub(struct timeval end,struct timeval begin)
{
struct timeval tv;
tv.tv_sec = end.tv_sec - begin.tv_sec;
tv.tv_usec = end.tv_usec - begin.tv_usec;
if(tv.tv_usec < 0){
tv.tv_sec--;
tv.tv_usec += 1000000;
}
return tv;
}
/*把RTT转为ms*/
float convert_time(struct timeval tv)
{
return tv.tv_sec*1000+(float)tv.tv_usec/1000;
}
/*定义结构体存放icmp包的信息*/
typedef struct {
struct timeval begin,end;
int flag;
int seq;
int type;
}icmp_queue_st;
#define QUEUE_LENGTH 128
icmp_queue_st icmp_queue[QUEUE_LENGTH];
static int alive = 1;
static int rawsock;/*通信中使用的套接字*/
struct sockaddr_in dest;/*ping 目的地址*/
struct timeval begin_tv,end_tv;/*ping开始时间和*/
int send_packet,recv_packet;/*发送和接收的包数*/
char dest_ip_str[20];/*目的IP地址字符串(点分十进制)*/
int icmp_find(int seq)
{
int i = 0;
if(seq < 0){
for(i = 0; i < QUEUE_LENGTH;i++){
if(icmp_queue[i].flag == 0){
return i;
}
}
}else{
for(i = 0;i < QUEUE_LENGTH;i++){
if(icmp_queue[i].seq == seq){
return i;
}
}
}
return -1;/*err*/
}
/*icmp数据结构在ip_icmp.h中定义,生成请求包*/
void icmp_request(struct icmp *icmp,int seq,struct timeval *tv,int length)
{
unsigned char i = 0;
icmp->icmp_type = ICMP_ECHO;
icmp->icmp_code = 0;
icmp->icmp_cksum=0;
icmp->icmp_hun.ih_idseq.icd_id = htons(getpid()&0xffff);
icmp->icmp_hun.ih_idseq.icd_seq = htons(seq);
for(i = 0; i < length;i++){
icmp->icmp_dun.id_data[i] = i;
}
icmp->icmp_cksum = checksum((unsigned char*)icmp,length+8);
/*htons(checksum((unsigned char*)icmp,length+8));这里不需要字节序转换,因为checksum中已经按照网络序来计算的*/
//printf("generate check sum %#04x\n",(icmp->icmp_cksum));
}
/*分析icmp包*/
int icmp_analysis(char* buf,int len)
{
int seq,index;
struct icmp *p_icmp = (struct icmp*)buf;
if(p_icmp->icmp_type != ICMP_ECHOREPLY)
goto err;
if(p_icmp->icmp_code != 0)
goto err;
if(ntohs(p_icmp->icmp_hun.ih_idseq.icd_id) != (getpid()&0xffff))
goto err;
//printf("recv icmp sequeue %d \n",ntohs(p_icmp->icmp_hun.ih_idseq.icd_seq));
seq = ntohs(p_icmp->icmp_hun.ih_idseq.icd_seq);
index = icmp_find(seq);
if(index >= 0){
icmp_queue[index].flag = 0;
gettimeofday(&icmp_queue[index].end,NULL);
//icmp_queue[index].seq = 0;
}
int ret = calc_checksum(buf,len);
//printf("ret %d checksum %#04x\n",ret,htons(p_icmp->icmp_cksum));
recv_packet++;
return seq;
err:
printf("recv data err\n");
}
/*IP的结果体定义在<netinet/ip.h>中*/
void ip_analysis(char *buf,int len)
{
struct ip *p_ip = (struct ip*)buf;
short icmp_len = ntohs(p_ip->ip_len) - p_ip->ip_hl*4;
short ttl = p_ip->ip_ttl;
char src_ip[30] = {0};
int seq,index;
inet_ntop(AF_INET,&p_ip->ip_src,src_ip,sizeof(src_ip)); /*把二进制IP地址转换为点分十进制的IP字符串*/
seq = icmp_analysis(buf+p_ip->ip_hl*4,icmp_len);
index = icmp_find(seq);
printf("%d bytes from %s: icmp_seq=%d ttl=%d time=%fms\n",icmp_len,src_ip,seq,ttl,
convert_time(icmp_tvsub(icmp_queue[index].end,icmp_queue[index].begin)));
}
void * icmp_send(void*argv)
{
struct icmp icmppack;
gettimeofday(&begin_tv,NULL);
int ret;
while(alive){
icmp_request(&icmppack,send_packet,NULL,32);
ret = icmp_find(-1);
if(ret < 0){printf("send queue is empty\n");}
sendto(rawsock,&icmppack,40,0,(struct sockaddr *)&dest,sizeof(dest));
gettimeofday(&icmp_queue[ret].begin,NULL);
icmp_queue[ret].flag = 1;
icmp_queue[ret].seq = send_packet;
send_packet++;
sleep(1);
}
}
void *icmp_recv(void*argv)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 2000;
fd_set readfd;
char recv_buf[2048];
struct sockaddr_in recvsock;
int len = sizeof(recvsock);
int size;
while(alive){
int ret = 0;
FD_ZERO(&readfd);
FD_SET(rawsock,&readfd);
ret = select(rawsock+1,&readfd,NULL,NULL,&tv);
switch(ret){
case -1:
printf("select err\n");
break;
case 0:
break;
default:
/*这里接收到的recv_buf是指向IP头部,而不是指向数据开始。20为IP头部*/
size = recvfrom(rawsock,recv_buf,sizeof(recv_buf),0,(struct sockaddr*)&recvsock,&len);
//printf("recv size %d \n",size);
ip_analysis(recv_buf,size);
}
}
}
void analysis_statistics(void)
{
int total_time ;
gettimeofday(&end_tv,NULL);
total_time = convert_time(icmp_tvsub(end_tv,begin_tv));
printf("\n--- %s ping statistics ---\n",dest_ip_str);
printf("%d packets transmitted, %d received, %f packet loss, time %d ms\n",
send_packet,recv_packet,(float)(send_packet - recv_packet)/send_packet,total_time);
}
void sig_handler(int sig)
{
alive = 0;
analysis_statistics();
}
int main(int argc,char *argv[])
{
pthread_t pthd[2];
rawsock = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);
if(rawsock < 0){perror("socket");}
dest.sin_family = AF_INET;
dest.sin_addr.s_addr= inet_addr(argv[1]);
memcpy(dest_ip_str,argv[1],strlen(argv[1]));
signal(SIGINT,sig_handler);
pthread_create(&pthd[0],NULL,icmp_recv,NULL);
pthread_create(&pthd[1],NULL,icmp_send,NULL);
pthread_join(pthd[0],NULL);
pthread_join(pthd[1],NULL);
close(rawsock);
}
执行效果如下。由于是ping自己的电脑,所以时间特别短。
其实利用原始套接字也可以现实igmp的响应,后面有空在补充下。但是很多时候咱们都使用协议栈里面的接口。毕竟是现成的东西。在使用原始套接字的时候,其默认是包括IP首部的,即收到的数据指向IP头部。还有IP层只使用到了IP而没有port,所以在填写sockaddr_in的时候只需要填写IP地址就可以。
运输层数据访问
运输层数据的访问可以直接使用标准的TCP/UDP套接字接口。其中TCP的创建为socket(AF_INET,SOCK_STREAM,0);而UDP的创建为socket(AF_INET, SOCK_DGRAM,0)。从运输层获取到的数据直接是对应协议的数据包。这个是最为常用的,这里就不讲解了。