📄 if_etherarp.c
字号:
#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 voidin_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); /* it's from me, ignore it. */ 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) { m_freem(m); return; } arphacklock=1; trueip=1; (void)memcpy(ithardaddr, ea->arp_sha, sizeof(ea->arp_sha)); fromsubr=3; arprequest(ac, &myaddr, &isaddr, ac->ac_enaddr); 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; } 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,it's dst send a request 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结点 */ /**/
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -