LWIP协议栈---ARP协议(3)ARP数据包发送过程

LWIP协议栈---ARP协议(3)ARP数据包发送过程

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挂接到缓冲队列。

相关推荐

一位老玩家对画江山的评价
bat365官网登录下载

一位老玩家对画江山的评价

📅 08-13 👁️ 8500