收录于:
【LWIP】LWIP协议|相关知识汇总|LWIP学习笔记
之前我们学习了原始套接字(SOCK_RAW),通过原始套接字可以越过传输层,直接在IP层进行数据的发送和接收。 通过原始套接字,可以构建自定义的IP包。
其实,还有一种套接字比它更厉害,可以构建自定义以太网包–AF_PACKET套接字
使用socket(AF_PACKET, SOCK_RAW, ETHTYPE_*)创建的套接字可以接收链路层报文。 那为什么AF_PACKET协议域的套接字可以接收链路层报文呢?
今日追踪了一下: 1.首先在socket函数中,对不同的协议域设置了不同入口。 本文进入的是packet_socket函数。
/* 选择协议域 */ switch (domain) { case AF_UNIX: /* UNIX 域协议 */ pafunix = unix_socket(domain, type, protocol); ... case AF_PACKET: /* PACKET */ pafpacket = packet_socket(domain, type, protocol); ... case AF_INET: case AF_INET6: /* IPv4 / v6 */ iLwipFd = lwip_socket(domain, type, protocol); ... default: }在packet_socket->__packetCreate函数中,创建AF_PACKET控制块,并加入AF_PACKET链表。
static AF_PACKET_T *__packetCreate (INT iType, INT iProtocol) { AF_PACKET_T *pafpacket; pafpacket = (AF_PACKET_T *)__SHEAP_ALLOC(sizeof(AF_PACKET_T)); ... pafpacket->PACKET_iType = iType; pafpacket->PACKET_iProtocol = iProtocol; ... /* 将AF_PACKET控制块加入全局链表 */ _List_Line_Add_Ahead(&pafpacket->PACKET_lineManage, &_G_plineAfPacket); ... return (pafpacket); }2.通过recvfrom函数接收数据。 选择AF_PACKET协议域,进入packet_recvfrom函数。
ssize_t recvfrom (int s, void *mem, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen) { ... switch (psock->SOCK_iFamily) { case AF_UNIX: /* UNIX 域协议 */ sstRet = (ssize_t)unix_recvfrom(psock->SOCK_pafunix, mem, len, flags, from, fromlen); break; case AF_PACKET: /* PACKET */ sstRet = (ssize_t)packet_recvfrom(psock->SOCK_pafpacket, mem, len, flags, from, fromlen); break; default: sstRet = (ssize_t)lwip_recvfrom(psock->SOCK_iLwipFd, mem, len, flags, from, fromlen); break; } ... }在packet_recvfrom 函数中判断AF_PACKET控制块是否获取到数据,并读取。
ssize_t packet_recvfrom (AF_PACKET_T *pafpacket, void *mem, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen) { ... /* 当前节点是否有数据 */ if (__packetCanRead(pafpacket, flags, len)) { /* 接收一个数据包 */ sstTotal = __packetBufRecv(pafpacket, mem, len, (struct sockaddr_ll *)from, flags); } ... }3.获取到数据的源头在tcpip.c文件中,tcpip_input函数是LWIP协议栈的入口,在tcpip_input函数中添加了一个回调函数,用于AF_PACKET获取链路层报文。
tcpip_input(struct pbuf *p, struct netif *inp) { #if defined(SYLIXOS) && defined(LWIP_HOOK_LINK_INPUT) /* SylixOS 添加的回调函数(AF_PACKET获取链路层报文) */ if (LWIP_HOOK_LINK_INPUT(p, inp)) { pbuf_free(p); return ERR_OK; } ... }4.通过sendto函数发送数据。 和之前一样,选择AF_PACKET协议域,进入packet_sendto函数。 在packet_sendto->__packetEthRawSendto函数中,组装一个以太网报文,并通过netif->linkoutput函数将数据通过网卡驱动发送出去。
errno_t __packetEthRawSendto (CPVOID pvPacket, size_t stBytes, struct sockaddr_ll *psockaddrll) { ... /* 获取网络接口 */ pnetif = (struct netif *)netif_get_by_index((UINT)psockaddrll->sll_ifindex); ... /* 分配带有 PAD 的以太网报头 pbuf */ pbuf_hdr = pbuf_alloc(PBUF_RAW, ETH_HLEN + ETH_PAD_SIZE, PBUF_RAM); ... /* 拷贝数据至pbuf */ lib_memcpy(((u8_t *)pbuf_hdr->payload) + ETH_PAD_SIZE, pvPacket, ETH_HLEN); ... /* 通过网卡驱动发送函数,将数据发送出去 */ err = pnetif->linkoutput(pnetif, pbuf_hdr); ... }至此,AF_PACKET套接字的创建即使用介绍完毕。 AF_PACKET套接字工作流程图如下: