網絡子係統16_arp傳輸接收接口
// 鄰居子係統的配置屬性:
// 1.ARP_ANNOUNCE用於生成arp請求時,源ip的選擇
// 1.1 內核中產生的請求,使用arp_send傳輸arp_solicitation請求
// 1.2 用戶空間中產生的請求,arp_solicition調用neigh_app_ns來通知相應的用戶空間程序,需要生成一個solicitation
// 2.ARP_IGNORE 當ARP請求的目的地址為本機地址時,根據目的地地址與接收接口的關係,來決定是否應答
// 3.ARP_FILTER 當ARP請求的目的地址為本機地址時,處理一台主機有多個NIC連接到同一LAN,並且配置同一子網時,控製一個接口是對入口ARP應答。常見於虛擬服務器
// 4.medium-id 當ARP請求的目的地址非本機地址時,處理ip子網橫跨不同的lan,且該子網中提供arp請求的主機有多快nic,設置這些nic是否在同一lan
//
// 發送arp消息
// arp協議是ip->l2地址的轉換協議,可以運行在多種共享介質的l2鏈路類型
// 參數:
// type : arp頭中的操作碼
// ptype : l2幀頭中填寫的l3協議類型
// 函數主要任務:
// 1.檢查是否需要進行地址解析
// 2.創建arp封包
// 3.發送數據包
1.1 void arp_send(int type, int ptype, u32 dest_ip,
struct net_device *dev, u32 src_ip,
unsigned char *dest_hw, unsigned char *src_hw,
unsigned char *target_hw)
{
struct sk_buff *skb;
//表明該接口不能執行arp
if (dev->flags&IFF_NOARP)
return;
//創建arp封包
skb = arp_create(type, ptype, dest_ip, dev, src_ip,
dest_hw, src_hw, target_hw);
if (skb == NULL) {
return;
}
//傳輸出去
arp_xmit(skb);
}
// 創建arp封包
// 調用路徑:
// arp_send->arp_create
// 參數:
// type : arp頭中的操作碼
// ptype : l2幀頭中填寫的l3協議類型
// dest_ip, src_ip, target_hw, src_hw, arp協議中填寫的內容
// dest_hw, l2幀頭填寫的目標地址
// dev : 出口設備
// 函數主要任務:
// 1.填充l2幀頭
// 2.填寫arp協議字段
1.2 struct sk_buff *arp_create(int type, int ptype, u32 dest_ip,
struct net_device *dev, u32 src_ip,
unsigned char *dest_hw, unsigned char *src_hw,
unsigned char *target_hw)
{
struct sk_buff *skb;
struct arphdr *arp;
unsigned char *arp_ptr;
//封包大小 = arp頭 + (arp源ip地址,源mac地址,目的ip地址,目的mac地址) + l2頭
skb = alloc_skb(sizeof(struct arphdr)+ 2*(dev->addr_len+4)
+ LL_RESERVED_SPACE(dev), GFP_ATOMIC);
if (skb == NULL)
return NULL;
//移動skb->data,騰出mac的長度
skb_reserve(skb, LL_RESERVED_SPACE(dev));//處理設備相關的l2鏈路類型
//初始化l3頭指針
skb->nh.raw = skb->data;
//移動skb->tail到skb->data + arp報文長度
arp = (struct arphdr *) skb_put(skb,sizeof(struct arphdr) + 2*(dev->addr_len+4));
skb->dev = dev;//設置出口設備
skb->protocol = htons(ETH_P_ARP);//skb的協議類型
if (src_hw == NULL)
src_hw = dev->dev_addr;//如果沒有提供源地址,則設置為出口設備的l2地址
if (dest_hw == NULL)
dest_hw = dev->broadcast;//如果沒有目的地址,則設置為出口設備的廣播地址
//填充mac頭
if (dev->hard_header &&
dev->hard_header(skb,dev,ptype,dest_hw,src_hw,skb->len) < 0)
goto out;
//填充協議字段
switch (dev->type) {
default:
arp->ar_hrd = htons(dev->type);//arp頭的硬件類型
arp->ar_pro = htons(ETH_P_IP);//arp頭的協議類型,ip協議
break;
....
}
arp->ar_hln = dev->addr_len;//硬件地址長度
arp->ar_pln = 4;//協議地址長度
arp->ar_op = htons(type);//操作碼
arp_ptr=(unsigned char *)(arp+1);//arp_ptr指向協議頭後的第一個字節
memcpy(arp_ptr, src_hw, dev->addr_len);//拷貝出口設備地址到源地址
arp_ptr+=dev->addr_len;//移動指針
memcpy(arp_ptr, &src_ip,4);//拷貝源ip到源ip
arp_ptr+=4;
//拷貝目地l2地址
if (target_hw != NULL)
memcpy(arp_ptr, target_hw, dev->addr_len);
else
memset(arp_ptr, 0, dev->addr_len);
arp_ptr+=dev->addr_len;
memcpy(arp_ptr, &dest_ip, 4);
return skb;
out:
kfree_skb(skb);
return NULL;
}
// 發送arp封包
// 銜接netfilter處理點NF_ARP_OUT
// 調用路徑: arp_send->arp_xmit
1.3 void arp_xmit(struct sk_buff *skb)
{
//通過NETFILTER HOOK,最後由dev_queue_xmit傳輸出去
NF_HOOK(NF_ARP, NF_ARP_OUT, skb, NULL, skb->dev, dev_queue_xmit);
}
// arp協議接收例程
// 保存在ptype_base的hash表中,在netif_receive_skb中,根據l2幀頭中指定的l3協議號,調用此函數
// 調用路徑:netif_receive_skb->arp_rcv
// 函數主要任務:
// 1.報文合理性檢查:
// 1.1 在不支持arp的設備上,收到了arp報文,丟掉
// 1.2 非本機l2地址,如果開啟網橋功能,則由橋接處理
// 1.3 l2地址為回環地址
// 2.由於arp協議可能會修改sk_buff,如果skb被其他子係統引用,則拷貝一份
// 3.銜接netfilter處理點NF_ARP_IN
// 注:arp報文的l2地址,可以為廣播地址或l2多播地址,用於解析發送者自己的l2地址
2.1 int arp_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
{
struct arphdr *arp;
//使skb->data-tail足夠一個完整的arp報文
if (!pskb_may_pull(skb, (sizeof(struct arphdr) +
(2 * dev->addr_len) +
(2 * sizeof(u32)))))
goto freeskb;
arp = skb->nh.arph;
//報文有效性檢查:
// 1.在不支持arp的設備上,收到了arp報文,丟掉
// 2.非本機l2地址,如果開啟網橋功能,則由橋接處理
// 3.l2地址為回環地址,l2地址可以為廣播地址或l2多播地址
if (arp->ar_hln != dev->addr_len ||
dev->flags & IFF_NOARP |
skb->pkt_type == PACKET_OTHERHOST ||
skb->pkt_type == PACKET_LOOPBACK ||//回環
arp->ar_pln != 4)
goto freeskb;
//如果報文引用計數!=1,拷貝一份
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
goto out_of_mem;
//NETFILTER HOOK,在執行實際的arp處理之前,執行HOOK調用
return NF_HOOK(NF_ARP, NF_ARP_IN, skb, dev, NULL, arp_process);
freeskb:
kfree_skb(skb);
out_of_mem:
return 0;
}
// 處理arp報文
// 調用路徑:arp_rcv->netfilter->arp_process
// 函數主要任務:
// 1.入口設備需要配置有ipv4信息
// 2.arp合理性檢查:
// 2.1 arp隻處理l3協議為ip的地址解析
// 2.2 arp隻處理l2協議為以太網,IEEE802的地址解析
// 2.3 arp報文請求解析的目標ip地址不能為多播地址,或回環地址地址
// 3.入口設備的l2類型應該與arp報文中指定的l2類型相同
// 4.arp操作合法性檢查:
// 4.1 隻處理arp_request
// 4.2 隻處理arp_reply
// 5.處理重複地址檢查:
// 5.1 如果arp報文中沒有指定源ip地址,則此arp報文用於重複地址檢查
// 5.2 應答重複地址檢查的條件:
// 5.2.1 目的ip地址為本機
// 5.2.2 arp ignore配置為可可應答
// 5.2 使用源ip,目的ip均為arp報文中指定的目的ip進行應答,然後退出
// 6.目標ip為本機的arp request
// 6.1 判斷目標ip為本機的辦法:
// 通過路由子係統路由該目的地址,可達,且路由項指出路由類型為本機
// 6.2 被動學習,接收到arp request觸發創建一個新鄰居項
// 6.3 根據arp ignore,arp filter 決定是否進行應答
// 6.4 應答此arp, 然後退出
// 7.目的ip非本機,處理arp代理
// 8.更新鄰居項:
// 8.1 存在對應的鄰居項
// 8.2 如果為arp應答,且目的ip為本機,則說明鄰居項可達,更新鄰居項狀態為REACHABLE
// 8.3 否則更新鄰居狀態為STALE, 表明鄰居項有可用的l2地址,但是可可到達性需要確認。
// 注:arp ignore為全局配置,arp filter為接口配置,對代理的arp請求可以被延遲處理
2.2 static int arp_process(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
struct in_device *in_dev = in_dev_get(dev);
struct arphdr *arp;
unsigned char *arp_ptr;
struct rtable *rt;
unsigned char *sha, *tha;
u32 sip, tip;
u16 dev_type = dev->type;
int addr_type;
struct neighbour *n;
//獲取輸入設備的ipv4配置信息
if (in_dev == NULL)
goto out;
//skb的以太網頭,此指針由驅動程序移動跨過l2地址時,完成更新
arp = skb->nh.arph;
//設備類型
switch (dev_type) {
default:
if (arp->ar_pro != htons(ETH_P_IP) ||//arp隻處理ip協議
htons(dev_type) != arp->ar_hrd)//接收此skb的設備的設備類型,不是arp報文中指定的設備類型
goto out;
break;
...
if ((arp->ar_hrd != htons(ARPHRD_ETHER) &&//arp報文中指定的硬件地址類型不是以太網,或者IEEE802
arp->ar_hrd != htons(ARPHRD_IEEE802)) ||
arp->ar_pro != htons(ETH_P_IP))//非ip協議
goto out;
break;
...
}
//處理的操作碼隻限定在ARP應答與請求
if (arp->ar_op != htons(ARPOP_REPLY) &&
arp->ar_op != htons(ARPOP_REQUEST))
goto out;
//獲取源mac,源ip,目的mac,目的ip
arp_ptr= (unsigned char *)(arp+1);
sha = arp_ptr;
arp_ptr += dev->addr_len;
memcpy(&sip, arp_ptr, 4);
arp_ptr += 4;
tha = arp_ptr;
arp_ptr += dev->addr_len;
memcpy(&tip, arp_ptr, 4);
//arp不處理回環地址與多播地址
if (LOOPBACK(tip) || MULTICAST(tip))
goto out;
//如果設備類型為DLCI
//源地址為設備的廣播地址ff:ff:ff:ff:ff:ff
if (dev_type == ARPHRD_DLCI)
sha = dev->broadcast;
//源ip沒有指定,說明此arp請求用於重複地址檢查
if (sip == 0) {
//arp請求 && 目的ip為
if (arp->ar_op == htons(ARPOP_REQUEST) &&
inet_addr_type(tip) == RTN_LOCAL &&//本機ip
!arp_ignore(in_dev,dev,sip,tip))//判斷是否忽略此arp
arp_send(ARPOP_REPLY,ETH_P_ARP,tip,dev,tip,sha,dev->dev_addr,dev->dev_addr);//arp報文的源ip,目的ip均為arp請求中的目的ip
goto out;
}
//ARP請求,處理無端arp,更新鄰居項
if (arp->ar_op == htons(ARPOP_REQUEST) &&
ip_route_input(skb, tip, sip, 0, dev) == 0) {//路由目的地址與源地址
rt = (struct rtable*)skb->dst;//獲取查詢的路由信息
addr_type = rt->rt_type;
if (addr_type == RTN_LOCAL) {//本機ip
n = neigh_event_ns(&arp_tbl, sha, &sip, dev);//被動學習
if (n) {//
int dont_send = 0;
if (!dont_send)
dont_send |= arp_ignore(in_dev,dev,sip,tip);
if (!dont_send && IN_DEV_ARPFILTER(in_dev))//ARP_FILTER為接口選項,隻能開啟或者關閉,設置該選項後,隻有在內核知道如何到達發送方的ip地址
//並且隻有到達發送方ip地址的設備為接收此ARP_REQUEST的設備,才進行應答
//該選項主要用於當一台主機有多個NIC連接到同一LAN,且配置在同一個IP子網上時,控製一個接口是否對arp請求進行應答
dont_send |= arp_filter(sip,tip,dev);
if (!dont_send)//沒有過濾到此arp,則進行應答
arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha);
neigh_release(n);
}
goto out;
} else if (IN_DEV_FORWARD(in_dev)) {//非本機ip,但本地接口具有轉發功能
if ((rt->rt_flags&RTCF_DNAT) ||//進一步檢查是否為代理
(addr_type == RTN_UNICAST && rt->u.dst.dev != dev &&
(arp_fwd_proxy(in_dev, rt) || pneigh_lookup(&arp_tbl, &tip, dev, 0)))) {
n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
if (n)
neigh_release(n);
if (skb->stamp.tv_sec == LOCALLY_ENQUEUED ||
skb->pkt_type == PACKET_HOST ||
in_dev->arp_parms->proxy_delay == 0) {
arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha);
} else {
pneigh_enqueue(&arp_tbl, in_dev->arp_parms, skb);
in_dev_put(in_dev);
return 0;
}
goto out;
}
}
}
//存在鄰居項
n = __neigh_lookup(&arp_tbl, &sip, dev, 0);
...
//存在對應的鄰居項,此skb為鄰居可達性的證據,更新鄰居狀態
if (n) {
int state = NUD_REACHABLE;//更新鄰居項的可達性
int override;
//判斷距離上一次修改鄰居項的屬性是否已經經曆了n->parms->locktime這麼久
override = time_after(jiffies, n->updated + n->parms->locktime);
//非arp應答,或者封包目的地非本機
if (arp->ar_op != htons(ARPOP_REPLY) ||
skb->pkt_type != PACKET_HOST)
state = NUD_STALE;
//更新鄰居項
neigh_update(n, sha, state, override ? NEIGH_UPDATE_F_OVERRIDE : 0);
neigh_release(n);
}
out:
if (in_dev)
in_dev_put(in_dev);
kfree_skb(skb);
return 0;
}
//檢查是否忽略此arp
2.3 static int arp_ignore(struct in_device *in_dev, struct net_device *dev,
u32 sip, u32 tip)
{
int scope;
//查看此ipv4上配置的屬性
switch (IN_DEV_ARP_IGNORE(in_dev)) {
case 0: //對任何本機的地址進行應答
return 0;
case 1://目的ip配置到接收報文的接口上,才應答
sip = 0;
scope = RT_SCOPE_HOST;//本地地址
break;
case 2:
scope = RT_SCOPE_HOST;//目的ip配置到接收報文的接口上,並且源ip與本機在同一子網
break;
case 3: //不應答本子網地址
sip = 0;
scope = RT_SCOPE_LINK;
dev = NULL;
break;
case 4: //保留
case 5:
case 6:
case 7:
return 0;
case 8://不應答
return 1;
default:
return 0;
}
//確定tip是否屬於scope指定的範圍
return !inet_confirm_addr(dev, sip, tip, scope);
}
最後更新:2017-04-03 15:22:03
上一篇:
2013-09-28
下一篇:
objective-c下的消息機製
WCF技術剖析之二:再談IIS與ASP.NET管道
9月26日雲棲精選夜讀:阿裏Java代碼規約插件即將全球首發,邀您來發布儀式現場
深入淺出了解 JavaScript 中的 this
上雲,讓業務盡情擁抱互聯網:阿裏雲在企業專有雲與混合雲最佳實踐
《Spring Data官方文檔》翻譯邀請
Android開發問題 - Some projects cannot be imported because they already exist in the workspace
listView下拉刷新(仿sina微博Android客戶端效果)
分布式環境下的性能追蹤介紹
開源大數據周刊-第63期
英特爾擊敗AMD輸給高通