ARP数据包发送过程
先看一些指向流程图,ARP数据包发送的过程:
主要看看右边这块内容,ip_output() 调用etharp_output() 函数发送出ip层的内容。而该函数又根据数据包是否是多播,广播,单播,分别调用不同的函数处理,最终是调用netif->linkoutput完成传输。
(1)广播与多播包发送:
当发送数据包为多播或广播数据包时,etharp_output()会构造一个特殊的MAC地址,同时把ip地址和MAC地址传递给etharp_send_ip函数使用。
1.etharp_output()
先看代码。
err_t etharp_output(struct netif *netif, struct pbuf *q, ip_addr_t *ipaddr)
{
struct eth_addr *dest;
struct eth_addr mcastaddr;
ip_addr_t *dst_addr = ipaddr; //初始化目的IP地址。
/* 调整PBUF 中payload 指针,使其指向以太网帧头部,失败则返回 */
if (pbuf_header(q, sizeof(struct eth_hdr)) != 0) {
return ERR_BUF;
}
/* 判断是否是广播地址? */
if (ip_addr_isbroadcast(ipaddr, netif)) {
/* 若是,则dest 指向广播地址的MAC */
dest = (struct eth_addr *)ðbroadcast;
/*是多播地址吗? */
} else if (ip_addr_ismulticast(ipaddr)) {
/* 若是,则构造多播地址.*/
mcastaddr.addr[0] = LL_MULTICAST_ADDR_0;
mcastaddr.addr[1] = LL_MULTICAST_ADDR_1;
mcastaddr.addr[2] = LL_MULTICAST_ADDR_2;
mcastaddr.addr[3] = ip4_addr2(ipaddr) & 0x7f;
mcastaddr.addr[4] = ip4_addr3(ipaddr);
mcastaddr.addr[5] = ip4_addr4(ipaddr);
/* dest 指向多播地址 */
dest = &mcastaddr;
} else { //如果是单播地址
s8_t i;
/* 判断目的IP地址是否为本地的子网上,若不在,则修改ipaddr */
if (!ip_addr_netcmp(ipaddr, &(netif->ip_addr), &(netif->netmask)) &&
!ip_addr_islinklocal(ipaddr)) {
{ //将数据包发送到网关上,由网关转发
if (!ip_addr_isany(&netif->gw)) { //若网关配置了
dst_addr = &(netif->gw); //更改目标ip地址为网关,由网关转发
} else { //若网关未配置
return ERR_RTE;
}
}
}
//对于 单播包,调用query函数 查询其mac 并发送数据包。
return etharp_query(netif, dst_addr, q);
}
/*对于多播或广播,由于得到了MAC地址,所以直接发送*/
return etharp_send_ip(netif, q, (struct eth_addr*)(netif->hwaddr), dest);
}
我们整理一下执行的流程:
先判断数据包是单播,多播,还是广播。
-
若是广播包,则将MAC地址配置为ff-ff-ff-ff-ff-ff-ff,然后发送
-
若是多播包,则计算MAC地址,然后发送出去。
-
若是单播包,则于本地IP进行比较,是否在同一个局域网内部,若不是,要将目标地址改为网关地址,再发送etharp_query(),查找MAC地址,并发送出去。
2. etharp_send_ip() 较为简单,就不再详解了。
static err_t etharp_send_ip(struct netif *netif, struct pbuf *p, struct eth_addr *src, struct eth_addr *dst)
{
struct eth_hdr *ethhdr = (struct eth_hdr *)p->payload; //以太网帧头部
ETHADDR32_COPY(ðhdr->dest, dst);
ETHADDR16_COPY(ðhdr->src, src);
ethhdr->type = PP_HTONS(ETHTYPE_IP);
return netif->linkoutput(netif, p); //发送出去
}
(2)单播包的发送
接下来就是本篇最后一个函数的讲解了!
etharp_query()
该函数功能是向指定的IP地址处发送一个IP数据包或一个ARP请求。
//q :指向以太网数据帧的pbuf.
err_t etharp_query(struct netif *netif, ip_addr_t *ipaddr, struct pbuf *q)
{
struct eth_addr * srcaddr = (struct eth_addr *)netif->hwaddr;
err_t result = ERR_MEM;
s8_t i; /* ARP entry index */
/*调用函数 查找或创建一个ARP表项 */
i = etharp_find_entry(ipaddr, ETHARP_FLAG_TRY_HARD);
/*若i<0 ,查询失败 */
if (i < 0) {
return (err_t)i;
}
/* 若状态为empty 说明是刚创建的,改变状态为stable */
if (arp_table[i].state == ETHARP_STATE_EMPTY) {
arp_table[i].state = ETHARP_STATE_PENDING;
}
/* 如果表项为pending 状态,或者数据包为空 */
if ((arp_table[i].state == ETHARP_STATE_PENDING) || (q == NULL)) {
result = etharp_request(netif, ipaddr); //发送ARP请求包。
if (result != ERR_OK) {
}
if (q == NULL) {
return result;
}
}
//如果 数据包不为空,根据表项的状态,则进行数据包的发送,或者将数据包挂接在缓冲队列上。
if (arp_table[i].state >= ETHARP_STATE_STABLE) {
result = etharp_send_ip(netif, q, srcaddr, &(arp_table[i].ethaddr));
} else if (arp_table[i].state == ETHARP_STATE_PENDING) { //若是 pending状态,则 挂接数据包
struct pbuf *p;
int copy_needed = 0; //是否需要重新拷贝数据包?
p = q;
while (p) {
if(p->type != PBUF_ROM) { //是否需要重新拷贝数据包,数据包由 PBUF_ROM类型的PBUF组成,才不需要拷贝
copy_needed = 1;
break;
}
p = p->next;
}
if(copy_needed) {
p = pbuf_alloc(PBUF_RAW, p->tot_len, PBUF_RAM);
if(p != NULL) {
if (pbuf_copy(p, q) != ERR_OK) { //执行拷贝动作
pbuf_free(p);
p = NULL;
}
}
} else { //如果不需要拷贝
p = q;
pbuf_ref(p); //增加pbuf 的ret的值。
}
//到这里,p指向了我们需要挂接的数据包,下面执行挂接操作
if (p != NULL) {
struct etharp_q_entry *new_entry;
/* allocate a new arp queue entry */
new_entry = (struct etharp_q_entry *)memp_malloc(MEMP_ARP_QUEUE);
if (new_entry != NULL) { //申请成功,进行挂接
new_entry->next = 0;
new_entry->p = p;
if(arp_table[i].q != NULL) { //若缓冲队列不为空,
struct etharp_q_entry *r;
r = arp_table[i].q;
while (r->next != NULL) { //找到最后一个缓冲包结构
r = r->next;
}
r->next = new_entry; //将新的数据包挂接在队列尾部
} else { //缓冲队列为空
/*直接挂接在缓冲队列首部 */
arp_table[i].q = new_entry;
}
result = ERR_OK;
} else { //etharp_q_entry 结构申请失败,则释放数据包空间
pbuf_free(p);
result = ERR_MEM;
}
}
}
return result;
}
具体的执行流程如下:
-
首先调用etharp_find_entry()函数返回一个arp表项,该表项可能是pending 或stable状态,也有可能是新申请的empty状态,然后更改为pending 状态。
-
判断要发送的数据包是否为空,或者 ARP表项 是否为pending 状态,只要符合一个成立,就发送一个arp请求包。
-
如果待发送的数据包不为空,要根据ARP表项的状态进行不同的操作:
-
若是stable状态,则调用 etharp_send_ip()发送出去。
-
若是pending状态,则将数据包 挂接到 表项的待发送数据链表上,当内核收到arp应答时,会改变为stable状态,并把数据包发送出去。
-
-
接下来将数据包挂接到发送队列上:
-
要判断pbuf的类型,对于PBUF_REF,PBUF_POOL,PBUF_RAM类型的数据包,不能直接挂接在发送链表上,因为这些数据被挂接在发送队列中不会立刻被发送,在等待期间,数据可能被上层更改。
-
需要将数据拷贝到新的pbuf中,然后将新的PBUF挂接到缓冲队列。
-