linux数据链路访问之ETH_P_ALL等等

linux自身有两种从数据链路层接收分组:

一种为fd=socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL));

另一种为fd=socket(AF_INET,SOCK_PACKET,htons(ETH_P_ALL));

这其中协议族为PF_PACKET套接字使用较多。

ETH_P_ALL自身定义于  /usr/include/linux/if_ether.h中,

#define ETH_P_ALL       0x0003

ETH_P_ALL占两个字节值为0x0003

其他的:

#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
#define ETH_P_IEEEPUP   0x0a00          /* Xerox IEEE802.3 PUP packet */        
#define ETH_P_IEEEPUPAT 0x0a01          /* Xerox IEEE802.3 PUP Addr Trans packet*/

以太网封装中,数据链路帧中类型字段为0800指示的数据为ip报文,0806为ARP报文等等。

在socket函数中,若指定第三个参数为ETH_P_ARP则指示只将接收的帧中的哪些类型的帧传递给所创建的套接字。

知道了这些,那么我们可以接收一些if_ether.h里并没有定义的类型的数据链路帧

比如:

socket(PF_PACKET, SOCK_RAW, htons(0x88CC));

这个就是向套接字传递数据链路报文数据类型为88CC的报文,0x88cc报文为LLDP报文,if_ether.h并没有定义。我们使用recvfrom就能够获取到该类型的数据链路帧。

而使用sendto函数可以发送由我们自己定义的数据链路帧报头(这里可以将类型字段设置为0x88cc)。

 

想想内核将接收的报文传递给原始套接字两个的限制条件:

1)一是TCP/UDP分组绝不会传递给Raw socket

二是大多数ICMP在内核处理完后,传递给Raw socket

三是所有的IGMP在内核处理完后,传递给Raw socket
四是内核不认识其协议字段的IP数据报文传递给Raw socket

五是某个数据以分片的形式到达,则在所有分片到达重组之后,才传递给Raw socket

 若以上5个条件满足,那么下面的3个条件也是必需要满足:

2)一是创建原始套接字时,协议字段非0,则接收的数据报文协议字段,必需匹配该值,否则,不传递该报文

二是bind了本地IP地址,数据报的目的地址必需和本地IP地址匹配

三是connect某个外地IP地址,那么则数据报源地址也必须匹配。

 

也就是说原始套接字实际上是不能够接收tcp/udp分组的,只能使用libpcap库来接收。但是libpcap库的一点限制就是只能接收不能发。

如以下实例发送以太网数据链路帧,指定上层报文类型为0x88cc

/*

    author:gs

    time:2013/9/23

    function:send L2 packets

*/

#include

#include

#include

#include

#include/*for uint16_t*/

#include

#include

#include



struct lldpdu

{

	char dst[6];

	char src[6];

	uint16_t type;

};



int main(int argc,char* argv[])

{

	struct lldpdu* lpacket = malloc(sizeof(struct lldpdu));

	memset(lpacket,0,sizeof(struct lldpdu));

	struct sockaddr_ll sll;

	memset(&sll,0,sizeof(struct sockaddr_ll));

	int sk = socket(PF_PACKET, SOCK_RAW, htons(0x88CC));/*使用pf_packet接口创建套接字*/



	sll.sll_family = PF_PACKET;

	sll.sll_ifindex = if_nametoindex("eth0");/*sll虽然是用来指定目的地址的,但是在这个结构体中sll_ifindex 却指定的是本机发送报文的接口索引*/

	sll.sll_protocol = htons(0x88CC); 

	sll.sll_addr[0] = 0x34;/*sll_addr指定目的MAC地址*/

	sll.sll_addr[1] = 0xb0;

	sll.sll_addr[2] = 0x52;

	sll.sll_addr[3] = 0xda;

	sll.sll_addr[4] = 0xda;

	sll.sll_addr[5] = 0x18;



	lpacket->dst[0] = 0x34;/*自己构造的L2 报文的目的地址*/

	lpacket->dst[1] = 0xb0;

	lpacket->dst[2] = 0x52;

	lpacket->dst[3] = 0xda;

	lpacket->dst[4] = 0xda;

	lpacket->dst[5] = 0x18;



	lpacket->src[0] = 0x00;/*自己构造的L2 报文的源地址*/

	lpacket->src[1] = 0x0c;

	lpacket->src[2] = 0x29;

	lpacket->src[3] = 0x8d;

	lpacket->src[4] = 0xf1;

	lpacket->src[5] = 0x04;	

	lpacket->type = htons(0x88cc);/*报文类型*/

	

	while(1)

	{

                /*只有L2头,数据为空。每隔3秒发送0X88CC报文,即LLDP报文*/

		sendto(sk, lpacket, 14, 0, (struct sockaddr*)&sll, sizeof(struct sockaddr_ll));

		printf("send one lldp packet\n");

		sleep(3);

	}

	return 0;

}

这之中要注意的是sockaddr_ll,其中的sll_ifindex指定的为本机发送接口索引,而非目的地址接口索引。

以上程序发送的LLDP报文,能够在wireshark中捕捉到。

发现一个奇怪的现象,当sll中指定的目的地址和报文中的目的地址不一致时,会出现什么呢?

经实际测试,发现这种情况下,wireshark捕捉的报文以报文中的目的地址为准。而实际上,sll中不指定目的的MAC地址,报文也能被发送到正确的目的地去。它会使用报文的前6个字节,当做目的地址。


你可能感兴趣的:(linux)