Skip to content

Commit

Permalink
✨ 151 ARP 缓存
Browse files Browse the repository at this point in the history
  • Loading branch information
StevenBaby committed Jul 1, 2023
1 parent 779413b commit 9182ce2
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 3 deletions.
17 changes: 17 additions & 0 deletions docs/14 网络协议/151 ARP 缓存.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# ARP 缓存

缓存 IP -> MAC 地址的映射关系;

## 子网掩码

Classless Inter-Domain Routing(CIDR) 无类别域间路由,定义在 RFC4632 [^rfc4632]

互联网的机器都有一个唯一的 IP 地址,但是 IP 地址被分为两个部分,前面的部分称为 网络部分,后面的部分称为主机部分,这两部分通过子网掩码标记,子网掩码的 32 位分成了两部分,高位全为 1,低位全为 0;

比如对于常见的子网掩码:`255.255.255.0`,其二进制为 `11111111_11111111_11111111_00000000`,表示如果两个 IP 地址的前三个字节(高 24 位)如果相等,则两 IP 在同一个局域网中,发包时可以直接发送。比如:`192.168.111.11``192.168.111.22`;由于子网掩码的前面(高位)全为 1,所以很多时候子网掩码可以表示成 1 的数量,与 IP 地址合并在一起表示成 `192.168.111.11/24`

## 参考

- <https://en.wikipedia.org/wiki/Subnetwork>

[^rfc4632]: <https://datatracker.ietf.org/doc/html/rfc4632>
3 changes: 3 additions & 0 deletions src/include/onix/net/addr.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@ err_t inet_aton(const char *str, ip_addr_t addr);
// 比较两 ip 地址是否相等
bool ip_addr_cmp(ip_addr_t addr1, ip_addr_t addr2);

// 比较两地址是否在同一子网
bool ip_addr_maskcmp(ip_addr_t addr1, ip_addr_t addr2, ip_addr_t mask);

#endif
6 changes: 6 additions & 0 deletions src/include/onix/net/arp.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ enum
#define ARP_PROTOCOL_IP 0x0800 // ARP 协议 IP
#define ARP_PROTOCOL_IP_LEN 4 // ARP IP 地址长度 4

#define ARP_ENTRY_TIMEOUT 600 // ARP 缓冲失效时间秒
#define ARP_RETRY 5 // ARP 请求重试次数
#define ARP_DELAY 2 // ARP 请求延迟秒
#define ARP_REFRESH_DELAY 1000 // ARP 刷新间隔毫秒

typedef struct arp_t
{
u16 hwtype; // 硬件类型
Expand All @@ -30,5 +35,6 @@ typedef struct arp_t
} _packed arp_t;

err_t arp_input(netif_t *netif, pbuf_t *pbuf);
err_t arp_eth_output(netif_t *netif, pbuf_t *pbuf, ip_addr_t addr, u16 type, u32 len);

#endif
3 changes: 3 additions & 0 deletions src/include/onix/net/eth.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

#define ETH_FCS_LEN 4

#define ETH_BROADCAST "\xff\xff\xff\xff\xff\xff"
#define ETH_ANY "\x00\x00\x00\x00\x00\x00"

enum
{
ETH_TYPE_IP = 0x0800, // IPV4 协议
Expand Down
6 changes: 5 additions & 1 deletion src/kernel/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <onix/cpu.h>
#include <onix/printk.h>
#include <onix/debug.h>
#include <onix/assert.h>
#include <onix/errno.h>
#include <onix/string.h>
#include <onix/net.h>
Expand All @@ -17,5 +18,8 @@ err_t sys_test()

int len = 1500;
memset(pbuf->eth->payload, 'A', len);
eth_output(netif, pbuf, "\xff\xff\xff\xff\xff\x00", 0x9000, len);

ip_addr_t addr;
assert(inet_aton("192.168.111.1", addr) == EOK);
arp_eth_output(netif, pbuf, addr, 0x9000, len);
}
9 changes: 9 additions & 0 deletions src/net/addr.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,12 @@ bool ip_addr_cmp(ip_addr_t addr1, ip_addr_t addr2)
u32 a2 = *(u32 *)addr2;
return a1 == a2;
}

// 比较两地址是否在同一子网
bool ip_addr_maskcmp(ip_addr_t addr1, ip_addr_t addr2, ip_addr_t mask)
{
u32 a1 = *(u32 *)addr1;
u32 a2 = *(u32 *)addr2;
u32 m = *(u32 *)mask;
return (a1 & m) == (a2 & m);
}
214 changes: 212 additions & 2 deletions src/net/arp.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <onix/net.h>
#include <onix/net/arp.h>
#include <onix/list.h>
#include <onix/arena.h>
#include <onix/syscall.h>
Expand All @@ -10,6 +11,154 @@

#define LOGK(fmt, args...) DEBUGK(fmt, ##args)

// ARP 缓存队列
static list_t arp_entry_list;

// ARP 刷新任务
static task_t *arp_task;

// arp 缓存
typedef struct arp_entry_t
{
list_node_t node; // 链表结点
eth_addr_t hwaddr; // MAC 地址
ip_addr_t ipaddr; // IP 地址
u32 expires; // 失效时间
u32 used; // 使用次数
u32 query; // 查询时间
u32 retry; // 重试次数
list_t pbuf_list; // 等待队列
netif_t *netif; // 虚拟网卡
} arp_entry_t;

// 获取 ARP 缓存
static arp_entry_t *arp_entry_get(netif_t *netif, ip_addr_t addr)
{
arp_entry_t *entry = (arp_entry_t *)kmalloc(sizeof(arp_entry_t));
entry->netif = netif;
ip_addr_copy(entry->ipaddr, addr);
eth_addr_copy(entry->hwaddr, ETH_BROADCAST);

entry->expires = 0;
entry->retry = 0;
entry->used = 1;

list_init(&entry->pbuf_list);
list_insert_sort(
&arp_entry_list,
&entry->node,
element_node_offset(arp_entry_t, node, expires));
return entry;
}

// 释放 ARP 缓存
static void arp_entry_put(arp_entry_t *entry)
{
list_t *list = &entry->pbuf_list;
while (!list_empty(list))
{
pbuf_t *pbuf = element_entry(pbuf_t, node, list_popback(list));
assert(pbuf->count == 1);
pbuf_put(pbuf);
}

list_remove(&entry->node);
kfree(entry);
}

static arp_entry_t *arp_lookup(netif_t *netif, ip_addr_t addr)
{
ip_addr_t query;
if (!ip_addr_maskcmp(netif->ipaddr, addr, netif->netmask))
ip_addr_copy(query, netif->gateway);
else
ip_addr_copy(query, addr);

list_t *list = &arp_entry_list;
arp_entry_t *entry = NULL;

for (list_node_t *node = list->head.next; node != &list->tail; node = node->next)
{
entry = element_entry(arp_entry_t, node, node);
if (ip_addr_cmp(entry->ipaddr, query) && (netif == entry->netif))
{
return entry;
}
}

entry = arp_entry_get(netif, query);
return entry;
}

// ARP 查询
static err_t arp_query(arp_entry_t *entry)
{
if (entry->query + ARP_DELAY > time())
{
return -ETIME;
}

LOGK("ARP query %r...\n", entry->ipaddr);

entry->query = time();
entry->retry++;

if (entry->retry > 2)
{
eth_addr_copy(entry->hwaddr, ETH_BROADCAST);
}

pbuf_t *pbuf = pbuf_get();
arp_t *arp = pbuf->eth->arp;

arp->opcode = htons(ARP_OP_REQUEST);
arp->hwtype = htons(ARP_HARDWARE_ETH);
arp->hwlen = ARP_HARDWARE_ETH_LEN;
arp->proto = htons(ARP_PROTOCOL_IP);
arp->protolen = ARP_PROTOCOL_IP_LEN;

eth_addr_copy(arp->hwdst, entry->hwaddr);
ip_addr_copy(arp->ipdst, entry->ipaddr);
ip_addr_copy(arp->ipsrc, entry->netif->ipaddr);
eth_addr_copy(arp->hwsrc, entry->netif->hwaddr);

eth_output(entry->netif, pbuf, entry->hwaddr, ETH_TYPE_ARP, sizeof(arp_t));
return EOK;
}

// 刷新 ARP 缓存
static err_t arp_refresh(netif_t *netif, pbuf_t *pbuf)
{
arp_t *arp = pbuf->eth->arp;

if (!ip_addr_maskcmp(arp->ipsrc, netif->ipaddr, netif->netmask))
return -EADDR;

arp_entry_t *entry = arp_lookup(netif, arp->ipsrc);

eth_addr_copy(entry->hwaddr, arp->hwsrc);
entry->expires = time() + ARP_ENTRY_TIMEOUT;
entry->retry = 0;
entry->used = 0;

list_remove(&entry->node);
list_insert_sort(
&arp_entry_list,
&entry->node,
element_node_offset(arp_entry_t, node, expires));

list_t *list = &entry->pbuf_list;
while (!list_empty(list))
{
pbuf_t *pbuf = element_entry(pbuf_t, node, list_popback(list));
eth_addr_copy(pbuf->eth->dst, entry->hwaddr);
netif_output(netif, pbuf);
}

LOGK("ARP reply %r -> %m \n", arp->ipsrc, arp->hwsrc);
return EOK;
}

static err_t arp_reply(netif_t *netif, pbuf_t *pbuf)
{
arp_t *arp = pbuf->eth->arp;
Expand Down Expand Up @@ -50,14 +199,75 @@ err_t arp_input(netif_t *netif, pbuf_t *pbuf)
case ARP_OP_REQUEST:
return arp_reply(netif, pbuf);
case ARP_OP_REPLY:
LOGK("arp reply %r -> %m\n", arp->ipsrc, arp->hwsrc);
break;
return arp_refresh(netif, pbuf);
default:
return -EPROTO;
}
return EOK;
}

// 发送数据包到对应的 IP 地址
err_t arp_eth_output(netif_t *netif, pbuf_t *pbuf, ip_addr_t addr, u16 type, u32 len)
{
pbuf->eth->type = htons(type);
eth_addr_copy(pbuf->eth->src, netif->hwaddr);
pbuf->length = sizeof(eth_t) + len;

arp_entry_t *entry = arp_lookup(netif, addr);
if (entry->expires > time())
{
entry->used += 1;
eth_addr_copy(pbuf->eth->dst, entry->hwaddr);
netif_output(netif, pbuf);
return EOK;
}

list_push(&entry->pbuf_list, &pbuf->node);
arp_query(entry);
return EOK;
}

// ARP 刷新线程
static void arp_thread()
{
list_t *list = &arp_entry_list;
while (true)
{
// LOGK("ARP timer %d...\n", time());
task_sleep(ARP_REFRESH_DELAY);
for (list_node_t *node = list->tail.prev; node != &list->head;)
{
arp_entry_t *entry = element_entry(arp_entry_t, node, node);

node = node->prev;

if (entry->expires > time())
continue;

if (entry->retry > ARP_RETRY)
{
LOGK("ARP retries %d times for %r...\n",
entry->retry, entry->ipaddr);
arp_entry_put(entry);
continue;
}

if (entry->used == 0)
{
LOGK("ARP entry %r timeout...\n", entry->ipaddr);
arp_entry_put(entry);
continue;
}

arp_query(entry);
}
}
}

// 初始化 ARP 协议
void arp_init()
{
LOGK("Address Resolution Protocol init...\n");
list_init(&arp_entry_list);
arp_task = task_create(arp_thread, "arp", 5, KERNEL_USER);
}

0 comments on commit 9182ce2

Please sign in to comment.