網絡子係統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輸給高通