Linux之socket编程:TCP/IP中各层的数据访问

在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)。从运输层获取到的数据直接是对应协议的数据包。这个是最为常用的,这里就不讲解了。

发布了35 篇原创文章 · 获赞 1 · 访问量 1870

猜你喜欢

转载自blog.csdn.net/lzj_linux188/article/details/105372720
今日推荐