收录于:
之前我们学习了原始套接字(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套接字工作流程图如下: