在TCP/IP 协议中 两台网络设备传输数据,设备的唯一标识符是IP地址。人的标识符很多,区分一个人通过姓名,身份证号码
但是TCP/IP 中只能通过IP地址。IP数据 包的传输必须依赖于物理层,也就是传输的线缆和控制器。举个例子,我们喊一个人的姓名是通过声音传播出去的,如果隔着一栋楼声音是无法到达的。同样可以在微信群里面发一条消息,消息前面写明姓名。
信息是逻辑上的数据,然而数据的传输依赖于物理事物。
在以太网中,数据链路层有自己的一套寻址方式, MAC地址。用来标识不同的网络设备。
实际 的传输是如上图所示的,网卡负责连接网线把数据转换成电信号传输出到网线上。网卡自己的标识符是48位的MAC地址
网卡和网卡之间并不知道什么是IP地址。
上图的右边是以太网帧的数据包结构,它只关心MAC地址,IP地址是上层的数据包,在这里更本不会解析。
就像快递,MAC数据包就是一个快递。快递盒子上的运单内容是{目的地址,原地址,数据长度(快递重量),物品类别}。
快递一旦打包,快递员,司机,分拣人员,派件员 根本不会关心你里面装的是什么东西。
IP地址相当于:前台签收快快递,打开快递。快递里面出现了一张条子,写着给张三。没错张三是IP地址。
下面的例子和上面没有关系,断开;
这个时候领导过来了,对你说帮我把这个文件寄给张总。你知道,寄东西是需要地址的(MAC地址),张总的地址是多少呢
你回去邮箱里面查一下张总的地址,或者去群里问一下谁知道,然后把张总的地址和姓名写到运单上.
就会出现姓名 和 地址这样一个组合
ARP协议就是 通过IP去找MAC,如果本地没有MAC,就去网络上问问。这个问问就是广播“在群里问,谁知道张总的地址”
IP 和 MAC的关系就是通讯录。在LWIP中就是 ARP缓存表。
Windows 中 “win +R" 输入cmd
arp -a
上图为电脑的ARP 缓存表
IP 和MAC
ARP询问MAC地址的包为请求包。回复请求的包为应答包。
以太网正常传输都是网卡到网卡的的数据传输。广播可以在一个网段内发给所有设备。
以下是ARP报文的结构包含了发送端,接收端的IP 和MAC所有的信息都可以通过这格式获取。
ARP 报文结构和抓包实验
LWIP ARP 源码
发送ARP 请求
/**
* Send an ARP request packet asking for ipaddr.
*
* @param netif the lwip network interface on which to send the request
* @param ipaddr the IP address for which to ask
* @return ERR_OK if the request has been sent
* ERR_MEM if the ARP packet couldn't be allocated
* any other err_t on failure
*/
err_t
etharp_request(struct netif *netif, ip_addr_t *ipaddr)
{
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_request: sending ARP request.\n"));
return etharp_raw(netif, (struct eth_addr *)netif->hwaddr, ðbroadcast,
(struct eth_addr *)netif->hwaddr, &netif->ip_addr, ðzero,
ipaddr, ARP_REQUEST);
}
广播包特有的 目的MAC
const struct eth_addr ethbroadcast = {{0xff,0xff,0xff,0xff,0xff,0xff}};
/**
* Send a raw ARP packet (opcode and all addresses can be modified)
*
* @param netif the lwip network interface on which to send the ARP packet
* @param ethsrc_addr the source MAC address for the ethernet header
* @param ethdst_addr the destination MAC address for the ethernet header
* @param hwsrc_addr the source MAC address for the ARP protocol header
* @param ipsrc_addr the source IP address for the ARP protocol header
* @param hwdst_addr the destination MAC address for the ARP protocol header
* @param ipdst_addr the destination IP address for the ARP protocol header
* @param opcode the type of the ARP packet
* @return ERR_OK if the ARP packet has been sent
* ERR_MEM if the ARP packet couldn't be allocated
* any other err_t on failure
*/
#if !LWIP_AUTOIP
static
#endif /* LWIP_AUTOIP */
err_t
etharp_raw(struct netif *netif, const struct eth_addr *ethsrc_addr,
const struct eth_addr *ethdst_addr,
const struct eth_addr *hwsrc_addr, const ip_addr_t *ipsrc_addr,
const struct eth_addr *hwdst_addr, const ip_addr_t *ipdst_addr,
const u16_t opcode)
重点解析etharp_raw中难点
上图是以太网帧的结构。以太网传输都是这种结构
红框内的部分实际上是以太网帧结构的数据段。
以太网只管把数据从原地址到目标地址传输进行校验,其他的都不会解析。
ethhdr = (struct eth_hdr *)p->payload;
hdr = (struct etharp_hdr *)((u8_t*)ethhdr + SIZEOF_ETH_HDR);
pbuf 中playload是整个帧的数据头部ethhdr是上图中黄色部分 14个字节
hdr 是arp 部分对应上图的红框部分
然后对hdr进行组合最后通过netif->linkout 发送以太网数据帧。
如下图所示
ARP 数据包流程 -输入
接收数据后发送中断,中断中调用Ethernet_input 通过low level获取数据到pbuf
pbuf数据使用ethernet_input发往上层
etharp_arp_input 函数比较长,但是源码思路清晰,理解没有什么难点。
ARP 数据包流程 -输出
IP 层输出,调用ip_output_if,从而调用netif->output,指向etharp_output
/**
* Resolve and fill-in Ethernet address header for outgoing IP packet.
*
* For IP multicast and broadcast, corresponding Ethernet addresses
* are selected and the packet is transmitted on the link.
*
* For unicast addresses, the packet is submitted to etharp_query(). In
* case the IP address is outside the local network, the IP address of
* the gateway is used.
*
* @param netif The lwIP network interface which the IP packet will be sent on.
* @param q The pbuf(s) containing the IP packet to be sent.
* @param ipaddr The IP address of the packet destination.
*
* @return
* - ERR_RTE No route to destination (no gateway to external networks),
* or the return type of either etharp_query() or etharp_send_ip().
*/
err_t
etharp_output(struct netif *netif, struct pbuf *q, ip_addr_t *ipaddr)
这个函数理解比较复杂
以太网层发送数据需要考虑的比IP层多
1.直接广播或者多播IP,需要使用特定MAC无需查表
2.如果不在本网段,发往到网关所以需要用网关IP去查网关MAC
3.子网IP MAC寻找,etharp_query处理
/**
* Send an ARP request for the given IP address and/or queue a packet.
*
* If the IP address was not yet in the cache, a pending ARP cache entry
* is added and an ARP request is sent for the given address. The packet
* is queued on this entry.
*
* If the IP address was already pending in the cache, a new ARP request
* is sent for the given address. The packet is queued on this entry.
*
* If the IP address was already stable in the cache, and a packet is
* given, it is directly sent and no ARP request is sent out.
*
* If the IP address was already stable in the cache, and no packet is
* given, an ARP request is sent out.
*
* @param netif The lwIP network interface on which ipaddr
* must be queried for.
* @param ipaddr The IP address to be resolved.
* @param q If non-NULL, a pbuf that must be delivered to the IP address.
* q is not freed by this function.
*
* @note q must only be ONE packet, not a packet queue!
*
* @return
* - ERR_BUF Could not make room for Ethernet header.
* - ERR_MEM Hardware address unknown, and no more ARP entries available
* to query for address or queue the packet.
* - ERR_MEM Could not queue packet due to memory shortage.
* - ERR_RTE No route to destination (no gateway to external networks).
* - ERR_ARG Non-unicast address given, those will not appear in ARP cache.
*
*/
err_t
etharp_query(struct netif *netif, ip_addr_t *ipaddr, struct pbuf *q)
etharp_query的作用注解已经说明了。
ARP 列表
static struct etharp_entry arp_table[ARP_TABLE_SIZE];
struct etharp_entry {
#if ARP_QUEUEING
/** Pointer to queue of pending outgoing packets on this ARP entry. */
struct etharp_q_entry *q;
#else /* ARP_QUEUEING */
/** Pointer to a single pending outgoing packet on this ARP entry. */
struct pbuf *q;
#endif /* ARP_QUEUEING */
ip_addr_t ipaddr;
struct netif *netif;
struct eth_addr ethaddr;
u8_t state;
u8_t ctime;
};
查询ARP列表一般都是发送IP数据包的时候,那么这个数据包就会由*q 来暂存
etharp_find_entry Search the ARP table for a matching or new entry.
arp 时间超时函数,用来满足ARP所有和时间相关的需求
1.刷新ctime
2.超时刷新,存在时间超时的表项从ARP列表中移除
3.请求 ARP等待回复 pending 时间超时停止
4.发送ARP 请求
void
etharp_tmr(void)
{
int i;
LWIP_DEBUGF(ETHARP_DEBUG, ("etharp_timer\n"));
/* remove expired entries from the ARP table */
for (i = 0; i < ARP_TABLE_SIZE; ++i) {
u8_t state = arp_table[i].state;
if (state != ETHARP_STATE_EMPTY
#if ETHARP_SUPPORT_STATIC_ENTRIES
&& (state != ETHARP_STATE_STATIC)
#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
) {
arp_table[i].ctime++;
if ((arp_table[i].ctime >= ARP_MAXAGE) ||
((arp_table[i].state == ETHARP_STATE_PENDING) &&
(arp_table[i].ctime >= ARP_MAXPENDING))) {
/* pending or stable entry has become old! */
LWIP_DEBUGF(ETHARP_DEBUG, ("etharp_timer: expired %s entry %d.\n",
arp_table[i].state >= ETHARP_STATE_STABLE ? "stable" : "pending", i));
/* clean up entries that have just been expired */
etharp_free_entry(i);
} else if (arp_table[i].state == ETHARP_STATE_STABLE_REREQUESTING_1) {
/* Don't send more than one request every 2 seconds. */
arp_table[i].state = ETHARP_STATE_STABLE_REREQUESTING_2;
} else if (arp_table[i].state == ETHARP_STATE_STABLE_REREQUESTING_2) {
/* Reset state to stable, so that the next transmitted packet will
re-send an ARP request. */
arp_table[i].state = ETHARP_STATE_STABLE;
} else if (arp_table[i].state == ETHARP_STATE_PENDING) {
/* still pending, resend an ARP query */
etharp_request(arp_table[i].netif, &arp_table[i].ipaddr);
}
}
}
}
查找ARP LIST中是否有匹配的MAC地址
这部分的代码非常长主要有几个任务
1.找到匹配的
2.如果没有匹配到正确的,创建新 ARP List 条目
寻找空位,如果没有空位就找最后一个快要过期的条目的位置释放资源,然后创建。
etharp_find_entry(const ip4_addr_t *ipaddr, u8_t flags, struct netif *netif)
{
s16_t old_pending = ARP_TABLE_SIZE, old_stable = ARP_TABLE_SIZE;
s16_t empty = ARP_TABLE_SIZE;
s16_t i = 0;
/* oldest entry with packets on queue */
s16_t old_queue = ARP_TABLE_SIZE;
/* its age */
u16_t age_queue = 0, age_pending = 0, age_stable = 0;
LWIP_UNUSED_ARG(netif);
/**
* a) do a search through the cache, remember candidates
* b) select candidate entry
* c) create new entry
*/
/* a) in a single search sweep, do all of this
* 1) remember the first empty entry (if any)
* 2) remember the oldest stable entry (if any)
* 3) remember the oldest pending entry without queued packets (if any)
* 4) remember the oldest pending entry with queued packets (if any)
* 5) search for a matching IP entry, either pending or stable
* until 5 matches, or all entries are searched for.
*/
for (i = 0; i < ARP_TABLE_SIZE; ++i) {
u8_t state = arp_table[i].state;
/* no empty entry found yet and now we do find one? */
if ((empty == ARP_TABLE_SIZE) && (state == ETHARP_STATE_EMPTY)) {
LWIP_DEBUGF(ETHARP_DEBUG, ("etharp_find_entry: found empty entry %d\n", (int)i));
/* remember first empty entry */
empty = i;
} else if (state != ETHARP_STATE_EMPTY) {
LWIP_ASSERT("state == ETHARP_STATE_PENDING || state >= ETHARP_STATE_STABLE",
state == ETHARP_STATE_PENDING || state >= ETHARP_STATE_STABLE);
/* if given, does IP address match IP address in ARP entry? */
if (ipaddr && ip4_addr_cmp(ipaddr, &arp_table[i].ipaddr)
#if ETHARP_TABLE_MATCH_NETIF
&& ((netif == NULL) || (netif == arp_table[i].netif))
#endif /* ETHARP_TABLE_MATCH_NETIF */
) {
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: found matching entry %d\n", (int)i));
/* found exact IP address match, simply bail out */
return i;
}
/* pending entry? */
if (state == ETHARP_STATE_PENDING) {
/* pending with queued packets? */
if (arp_table[i].q != NULL) {
if (arp_table[i].ctime >= age_queue) {
old_queue = i;
age_queue = arp_table[i].ctime;
}
} else
/* pending without queued packets? */
{
if (arp_table[i].ctime >= age_pending) {
old_pending = i;
age_pending = arp_table[i].ctime;
}
}
/* stable entry? */
} else if (state >= ETHARP_STATE_STABLE) {
#if ETHARP_SUPPORT_STATIC_ENTRIES
/* don't record old_stable for static entries since they never expire */
if (state < ETHARP_STATE_STATIC)
#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
{
/* remember entry with oldest stable entry in oldest, its age in maxtime */
if (arp_table[i].ctime >= age_stable) {
old_stable = i;
age_stable = arp_table[i].ctime;
}
}
}
}
}
/* { we have no match } => try to create a new entry */
/* don't create new entry, only search? */
if (((flags & ETHARP_FLAG_FIND_ONLY) != 0) ||
/* or no empty entry found and not allowed to recycle? */
((empty == ARP_TABLE_SIZE) && ((flags & ETHARP_FLAG_TRY_HARD) == 0))) {
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: no empty entry found and not allowed to recycle\n"));
return (s16_t)ERR_MEM;
}
/* b) choose the least destructive entry to recycle:
* 1) empty entry
* 2) oldest stable entry
* 3) oldest pending entry without queued packets
* 4) oldest pending entry with queued packets
*
* { ETHARP_FLAG_TRY_HARD is set at this point }
*/
/* 1) empty entry available? */
if (empty < ARP_TABLE_SIZE) {
i = empty;
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: selecting empty entry %d\n", (int)i));
} else {
/* 2) found recyclable stable entry? */
if (old_stable < ARP_TABLE_SIZE) {
/* recycle oldest stable*/
i = old_stable;
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: selecting oldest stable entry %d\n", (int)i));
/* no queued packets should exist on stable entries */
LWIP_ASSERT("arp_table[i].q == NULL", arp_table[i].q == NULL);
/* 3) found recyclable pending entry without queued packets? */
} else if (old_pending < ARP_TABLE_SIZE) {
/* recycle oldest pending */
i = old_pending;
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: selecting oldest pending entry %d (without queue)\n", (int)i));
/* 4) found recyclable pending entry with queued packets? */
} else if (old_queue < ARP_TABLE_SIZE) {
/* recycle oldest pending (queued packets are free in etharp_free_entry) */
i = old_queue;
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: selecting oldest pending entry %d, freeing packet queue %p\n", (int)i, (void *)(arp_table[i].q)));
/* no empty or recyclable entries found */
} else {
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: no empty or recyclable entries found\n"));
return (s16_t)ERR_MEM;
}
/* { empty or recyclable entry found } */
LWIP_ASSERT("i < ARP_TABLE_SIZE", i < ARP_TABLE_SIZE);
etharp_free_entry(i);
}
LWIP_ASSERT("i < ARP_TABLE_SIZE", i < ARP_TABLE_SIZE);
LWIP_ASSERT("arp_table[i].state == ETHARP_STATE_EMPTY",
arp_table[i].state == ETHARP_STATE_EMPTY);
/* IP address given? */
if (ipaddr != NULL) {
/* set IP address */
ip4_addr_copy(arp_table[i].ipaddr, *ipaddr);
}
arp_table[i].ctime = 0;
#if ETHARP_TABLE_MATCH_NETIF
arp_table[i].netif = netif;
#endif /* ETHARP_TABLE_MATCH_NETIF */
return (s16_t)i;
}
Update (or insert) a IP/MAC address pair in the ARP cache.
etharp_update_arp_entry(struct netif *netif, const ip4_addr_t *ipaddr, struct eth_addr *ethaddr, u8_t flags)