linux eth_p_ip,linux数据链路访问之ETH_P_ALL等等

本文详细解析了Linux中如何通过PF_PACKET套接字接收和发送不同类型的以太网数据链路帧,特别关注了LLDP报文的处理。作者展示了如何创建原始套接字、设置协议类型以及利用libpcap库的局限性。重点介绍了使用0x88CC类型接收LLDP报文的实例,并揭示了当目的地址设置错误时的捕获行为。
摘要由CSDN通过智能技术生成

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:gstime:2013/9/23

function:send L2 packets

*/

#include#include#include#include#include/*for uint16_t*/

#include#include#includestruct 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个字节,当做目的地址。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值