网络检测
最近接到一个需求如下:
1. 网关检测:
ARP协议发送给网关IP地址。可得到是否存在ARP欺骗、网关响应速度。(SOCK_RAW)
2. IP冲突检测:
ARP协议发送给本机IP地址。(SOCK_RAW)
2. DHCP地址获取检测:
直发DISCOVER可获取网络内DHCP服务器地址、响应速度。 (SOCK_DGRAM)
已连接情况下直发REQUEST可获取网络内DHCP服务器是否已离线。 (SOCK_DGRAM)
3. DNS检测:
模拟DNS包,可提供当前网络环境下各DNS服务器响应速度。用于诊断‘网速慢’类问题。(SOCK_DGRAM)
4. HTTP连接检测:
DNS后模拟HTTP包,可获取http服务器首包响应速度,即用户家实际访问此域名的网络延迟。(SOCK_STREAM)
对于以上的四个需求对于我的网络协议知识还有有点挑战的。除了在大学学过一段时间计算机网络之外基本平常没怎么做过相关的项目。这次通过研究基本的网络协议掌握了arp协议,DNS协议等一些网络通信方面的协议。我将通过两个篇幅来记录一下我是怎么实现的需要的。
对于第一个需求和第二个需求,哦路过查阅资料得知他们都是基于arp协议的网络通信,首先是arp欺骗的原理需要我们理解他的过程然后才能进行欺骗检测的实现。
1. 网关检测:
ARP协议发送给网关IP地址。可得到是否存在ARP欺骗、网关响应速度。(SOCK_RAW)
arp欺骗是值当前主机需要和主机(A)进行通信因此需要知道主机A的IP和MAC地址。但是他通过查阅自己本机的arp协议表发现没有主机A的IP地址对应的MAC地址,于是乎老大哥就广播出去了一条信息里面包含自己的IP和MAC地址以及A的IP地址天真的他在等待实际的A将A的MAC地址单播发送给他。但是此时可恶的B窃取到了这条广播信息,于是B将自己的MAC地址和A的IP地址组成一个应答包发送给了主机,这样主机接收到这个应答包之后将其存储在自己的arp表中开始给自己认为的A发送消息了。其实是发送给了B。B截获消息之后做了一些自己的小坏事,可恶的B为了防止A像主机告状就又用主机的MAC地址(这是B很容易拿到的数据)给A发了一条假的信息,让A沉迷在没有被欺骗的状态。其实此时的A已经是聋子了。
这就是ARP的大概过程如图所示:

解决方案是:对于获取路由的arp表中的地址后存在其他位置,之后与每次返回的arp的应答包进行对比。看是否存在地址不一样的情况来进行判断,对于网关的响应速度可以使用毫秒级的时间差来等效。重点在于自定义的ARP包的模仿组织如下图:

代码如下:
/*头文件*/ #ifndef __CHECKIP_H #define __CHECKIP_H struct arpMsg { struct ethhdr ethhdr; /* Ethernet header */ u_short htype; /* hardware type (must be ARPHRD_ETHER) */ u_short ptype; /* protocol type (must be ETH_P_IP) */ u_char hlen; /* hardware address length (must be 6) */ u_char plen; /* protocol address length (must be 4) */ u_short operation; /* ARP opcode */ u_char sHaddr[6]; /* sender's hardware address */ u_char sInaddr[4]; /* sender's IP address */ u_char tHaddr[6]; /* target's hardware address */ u_char tInaddr[4]; /* target's IP address */ u_char pad[18]; /* pad for min. Ethernet payload (60 bytes) */ }; struct server_config_t { u_int32_t server; /* Our IP, in network order */ u_int32_t start; /* Start address of leases, network order */ u_int32_t end; /* End of leases, network order */ struct option_set *options; /* List of DHCP options loaded from the config file */ char *interface; /* The name of the interface to use */ int ifindex; /* Index number of the interface to use */ unsigned char arp[6]; /* Our arp address */ unsigned long lease; /* lease time in seconds (host order) */ unsigned long max_leases; /* maximum number of leases (including reserved address) */ char remaining; /* should the lease file be interpreted as lease time remaining, or * as the time the lease expires */ unsigned long auto_time; /* how long should udhcpd wait before writing a config file. * if this is zero, it will only write one on SIGUSR1 */ unsigned long decline_time; /* how long an address is reserved if a client returns a * decline message */ unsigned long conflict_time; /* how long an arp conflict offender is leased for */ unsigned long offer_time; /* how long an offered address is reserved */ unsigned long min_lease; /* minimum lease a client can request*/ char *lease_file; char *pidfile; char *notify_file; /* What to run whenever leases are written */ u_int32_t siaddr; /* next server bootp option */ char *sname; /* bootp server name */ char *boot_file; /* bootp boot file option */ }; #endif /* __CHECKIP_H */
/*read_interface参数分别表示 网卡设备类型 接口检索索引 主机IP地址 主机arp地址*/
int read_interface(char *interface, int *ifindex, u_int32_t *addr, unsigned char *arp)
{
int fd;
/*ifreq结构定义在/usr/include/net/if.h,用来配置ip地址,激活接口,配置MTU等接口信息的。
其中包含了一个接口的名字和具体内容——(是个共用体,有可能是IP地址,广播地址,子网掩码,MAC号,MTU或其他内容)。
ifreq包含在ifconf结构中。而ifconf结构通常是用来保存所有接口的信息的。
*/
struct ifreq ifr;
struct sockaddr_in *our_ip;
memset(&ifr, 0, sizeof(struct ifreq));
/*建立一个socket函数,SOCK_RAW是为了获取第三个参数的IP包数据,
IPPROTO_RAW提供应用程序自行指定IP头部的功能。
*/
if((fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) >= 0) {
ifr.ifr_addr.sa_family = AF_INET;
/*将网卡类型赋值给ifr_name*/
strcpy(ifr.ifr_name, interface);
if (addr) {
/*SIOCGIFADDR用于检索接口地址*/
if (ioctl(fd, SIOCGIFADDR, &ifr) == 0) {
/*获取本机IP地址,addr是一个指向该地址的指针*/
our_ip = (struct sockaddr_in *) &ifr.ifr_addr;
*addr = our_ip->sin_addr.s_addr;
printf("%s (our ip) = %s/n", ifr.ifr_name, inet_ntoa(our_ip->sin_addr));
} else {
printf("SIOCGIFADDR failed, is the interface up and configured?: %s/n",
strerror(errno));
return -1;
}
}
/*SIOCGIFINDEX用于检索接口索引*/
if (ioctl(fd, SIOCGIFINDEX, &ifr) == 0) {
printf("adapter index %d/n", ifr.ifr_ifindex);
/*指针ifindex 获取索引*/
*ifindex = ifr.ifr_ifindex;
} else {
printf("SIOCGIFINDEX failed!: %s/n", strerror(errno));
return -1;
}
/*SIOCGIFHWADDR用于检索硬件地址*/
if (ioctl(fd, SIOCGIFHWADDR, &ifr) == 0) {
/*所获取的硬件地址复制到结构server_config的数组arp[6]参数中*/
memcpy(arp, ifr.ifr_hwaddr.sa_data, 6);
printf("adapter hardware address %02x:%02x:%02x:%02x:%02x:%02x/n",
arp[0], arp[1], arp[2], arp[3], arp[4], arp[5]);
} else {
printf("SIOCGIFHWADDR failed!: %s/n", strerror(errno));
return -1;
}
}
else {
printf("socket failed!: %s/n", strerror(errno));
return -1;
}
close(fd);
return 0;
}
/*参数说明 目标IP地址,本机IP地址,本机mac地址,网卡类型*/ int arpping(u_int32_t yiaddr, u_int32_t ip, unsigned char *mac, char *interface) { int timeout = 2; int optval = 1; int s; /* socket */ int rv = 1; /* return value */ struct sockaddr addr; /* for interface name */ struct arpMsg arp; fd_set fdset; struct timeval tm; time_t prevTime; /*socket发送一个arp包*/ if ((s = socket (PF_PACKET, SOCK_PACKET, htons(ETH_P_ARP))) == -1) { printf("Could not open raw socket/n"); return -1; } /*设置套接口类型为广播,把这个arp包是广播到这个局域网*/ if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval)) == -1) { printf("Could not setsocketopt on raw socket/n"); close(s); return -1; } /* 对arp设置,这里按照arp包的封装格式赋值即可,详见http://blog.csdn.net/wanxiao009/archive/2010/05/21/5613581.aspx */ memset(&arp, 0, sizeof(arp)); memcpy(arp.ethhdr.h_dest, MAC_BCAST_ADDR, 6); /* MAC DA */ memcpy(arp.ethhdr.h_source, mac, 6); /* MAC SA */ arp.ethhdr.h_proto = htons(ETH_P_ARP); /* protocol type (Ethernet) */ arp.htype = htons(ARPHRD_ETHER); /* hardware type */ arp.ptype = htons(ETH_P_IP); /* protocol type (ARP message) */ arp.hlen = 6; /* hardware address length */ arp.plen = 4; /* protocol address length */ arp.operation = htons(ARPOP_REQUEST); /* ARP op code */ *((u_int *) arp.sInaddr) = ip; /* source IP address */ memcpy(arp.sHaddr, mac, 6); /* source hardware address */ *((u_int *) arp.tInaddr) = yiaddr; /* target IP address */ memset(&addr, 0, sizeof(addr)); strcpy(addr.sa_data, interface); /*发送arp请求*/ if (sendto(s, &arp, sizeof(arp), 0, &addr, sizeof(addr)) < 0) rv = 0; /* 利用select函数进行多路等待*/ tm.tv_usec = 0; time(&prevTime); while (timeout > 0) { FD_ZERO(&fdset); FD_SET(s, &fdset); tm.tv_sec = timeout; if (select(s + 1, &fdset, (fd_set *) NULL, (fd_set *) NULL, &tm) < 0) { printf("Error on ARPING request: %s/n", strerror(errno)); if (errno != EINTR) rv = 0; } else if (FD_ISSET(s, &fdset)) { if (recv(s, &arp, sizeof(arp), 0) < 0 ) rv = 0; /*如果条件 htons(ARPOP_REPLY) bcmp(arp.tHaddr, mac, 6) == 0 *((u_int *) arp.sInaddr) == yiaddr 三者都为真,则ARP应答有效,说明这个地址是已近存在的*/ if (arp.operation == htons(ARPOP_REPLY) && bcmp(arp.tHaddr, mac, 6) == 0 && *((u_int *) arp.sInaddr) == yiaddr) { printf("Valid arp reply receved for this address/n"); rv = 0; break; } } timeout -= time(NULL) - prevTime; time(&prevTime); } close(s); return rv; }
/*checkip.c*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <arpa/inet.h>
#include "checkip.h"
#define MAC_BCAST_ADDR (unsigned char *) "/xff/xff/xff/xff/xff/xff"
#define ETH_INTERFACE "eth0"
struct server_config_t server_config;
int main(int argc, char *argv[])
{
if(argc < 2)
{
printf("Usage: checkip ipaddr/n");
exit(0);
}
/*读以太网接口函数,获取一些配置信息*/
if (read_interface(ETH_INTERFACE, &server_config.ifindex,
&server_config.server, server_config.arp) < 0)
{
exit(0);
}
/*IP检测函数*/
if(check_ip(inet_addr(argv[1])) == 0)
{
printf("mac:%s NO ARP Deception/n", argv[2]);
}
else
{
printf("mac:%s IS ARP Deception/n", argv[2]); } return 0; }
2. IP冲突检测:
ARP协议发送给本机IP地址。(SOCK_RAW)。IP 冲突就和上述的方案很相似了封装好一个arp包,然后广播到局域网中,然后接收,通过接受的应答来解析发出的IP地址是不是一个可用IP
代码如下:
/*参数说明 目标IP地址,本机IP地址,本机mac地址,网卡类型*/ int arpping(u_int32_t yiaddr, u_int32_t ip, unsigned char *mac, char *interface) { int timeout = 2; int optval = 1; int s; /* socket */ int rv = 1; /* return value */ struct sockaddr addr; /* for interface name */ struct arpMsg arp; fd_set fdset; struct timeval tm; time_t prevTime; /*socket发送一个arp包*/ if ((s = socket (PF_PACKET, SOCK_PACKET, htons(ETH_P_ARP))) == -1) { printf("Could not open raw socket/n"); return -1; } /*设置套接口类型为广播,把这个arp包是广播到这个局域网*/ if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval)) == -1) { printf("Could not setsocketopt on raw socket/n"); close(s); return -1; } /* 对arp设置,这里按照arp包的封装格式赋值即可,详见http://blog.csdn.net/wanxiao009/archive/2010/05/21/5613581.aspx */ memset(&arp, 0, sizeof(arp)); memcpy(arp.ethhdr.h_dest, MAC_BCAST_ADDR, 6); /* MAC DA */ memcpy(arp.ethhdr.h_source, mac, 6); /* MAC SA */ arp.ethhdr.h_proto = htons(ETH_P_ARP); /* protocol type (Ethernet) */ arp.htype = htons(ARPHRD_ETHER); /* hardware type */ arp.ptype = htons(ETH_P_IP); /* protocol type (ARP message) */ arp.hlen = 6; /* hardware address length */ arp.plen = 4; /* protocol address length */ arp.operation = htons(ARPOP_REQUEST); /* ARP op code */ *((u_int *) arp.sInaddr) = ip; /* source IP address */ memcpy(arp.sHaddr, mac, 6); /* source hardware address */ *((u_int *) arp.tInaddr) = yiaddr; /* target IP address */ memset(&addr, 0, sizeof(addr)); strcpy(addr.sa_data, interface); /*发送arp请求*/ if (sendto(s, &arp, sizeof(arp), 0, &addr, sizeof(addr)) < 0) rv = 0; /* 利用select函数进行多路等待*/ tm.tv_usec = 0; time(&prevTime); while (timeout > 0) { FD_ZERO(&fdset); FD_SET(s, &fdset); tm.tv_sec = timeout; if (select(s + 1, &fdset, (fd_set *) NULL, (fd_set *) NULL, &tm) < 0) { printf("Error on ARPING request: %s/n", strerror(errno)); if (errno != EINTR) rv = 0; } else if (FD_ISSET(s, &fdset)) { if (recv(s, &arp, sizeof(arp), 0) < 0 ) rv = 0; /*如果条件 htons(ARPOP_REPLY) bcmp(arp.tHaddr, mac, 6) == 0 *((u_int *) arp.sInaddr) == yiaddr 三者都为真,则ARP应答有效,说明这个地址是已近存在的*/ if (arp.operation == htons(ARPOP_REPLY) && bcmp(arp.tHaddr, mac, 6) == 0 && *((u_int *) arp.sInaddr) == yiaddr) { printf("Valid arp reply receved for this address/n"); rv = 0; break; } } timeout -= time(NULL) - prevTime; time(&prevTime); } close(s); return rv; }
后面的三个需求下次再写,周末加班不好。

浙公网安备 33010602011771号