您的位置:首页 > 其它

socket实现ping嗅探,获取局域网所有活动主机

2015-12-11 12:06 791 查看
大概思路是:获取本主机IP,然后将它和子网掩码进行与操作,可知道子网的主机号范围,然后逐个进行Ping,最多ping4次。

ping是基于icmp报文的,它被封装在ip中发送出去。

主要问题是,这样单线程Ping速度真的非常慢,所以我尝试了多线程,不过过程并不顺利,主要是在recvIcmp过程中,内部或许有些更复杂的步骤。

不过,有个简单的实现方法是通过system函数调用DOS指令ping,并将它的输出重定向,然后检索一下输出结果就好了,只是楼主比较执着于自己实现ping功能。

这个ping是我一个p2p局域网通信的子功能,接下来先写syn端口嗅探咯,回头再优化ping。

.完整的代码就不贴出来了,不过核心的就这些

先获取本主机IP,这个结构和winpcap很相似,用过的大哥应该都清楚。

PIP_ADAPTER_INFO pAdapterInfo = nullptr;
PIP_ADAPTER_INFO pCurrAdater = nullptr;
// 假设设备只有一个
unsigned long adapterSum = sizeof(IP_ADAPTER_INFO);

pAdapterInfo = (PIP_ADAPTER_INFO)malloc(sizeof(IP_ADAPTER_INFO));
if (pAdapterInfo == nullptr)
{
AfxMessageBox((LPCTSTR)"malloc failed, in const std::vector<in_addr>& CNetTrans::GetHostname()");
}

// 如果获取过程返回ERROR_BUFFER_OVERFLOW, 说明分配的内存不够,这是adapterSum被修改为适合的大小
if (GetAdaptersInfo(pAdapterInfo, &adapterSum) == ERROR_BUFFER_OVERFLOW)
{
free(pAdapterInfo);
pAdapterInfo = nullptr;
// 分配合适的内存
pAdapterInfo = (PIP_ADAPTER_INFO)malloc(adapterSum);
if (pAdapterInfo == nullptr)
{
AfxMessageBox((LPCTSTR)"malloc failed, in CNetTrans::GetHostname()");
}

}
char description[256];
char ip[16];
char netmask[16];
char gate[16];
if (GetAdaptersInfo(pAdapterInfo, &adapterSum) != NO_ERROR)
{
AfxMessageBox((LPCTSTR)"GetAdapterInfo error, in  CNetTrans::GetHostname()");
}
pCurrAdater = pAdapterInfo;
while (pCurrAdater)
{
memset(description, 0, sizeof(description));
memset(gate, 0, sizeof(gate));
memset(ip, 0, sizeof(ip));
memset(netmask, 0, sizeof(netmask));
// 获取设备描述,IP,子网掩码
strncpy(description, pCurrAdater->Description, strlen(description));
strncpy(ip, pCurrAdater->IpAddressList.IpAddress.String, sizeof(ip));
strncpy(netmask, pCurrAdater->IpAddressList.IpMask.String, sizeof(netmask));
strncpy(gate, pCurrAdater->GatewayList.IpAddress.String, sizeof(gate));

pCurrAdater = pCurrAdater->Next;
// 过滤很多没用的设备,即没有IP地址,用于一些window系统内部的功能
// 还有安装了虚拟机的也会有,一起过滤
if (strcmp(ip, "0.0.0.0") == 0 || strcmp(gate, "0.0.0.0") == 0)
continue;
struct device one = {description, gate, ip, netmask};

CNetTrans::m_Host.push_back(one);
}


发送ping请求,ip/icmp首部结构不知道的在百度搜一搜

SOCKET sock = INVALID_SOCKET;

if ((sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == SOCKET_ERROR)
{
AfxMessageBox((LPCTSTR)"socket error, in CNetTrans::GetLanActiveUsers() ");
}

unsigned long nIp = inet_addr(dev.ip.c_str());
unsigned long nGate = inet_addr(dev.gate.c_str());
unsigned long nNetmask = inet_addr(dev.netmask.c_str());
int subNetHostSum = ((~nNetmask) >> 24) - 1; // 减去主机号为全0 和 全1的地址
// 计算子网掩码中Bit为1的个数
unsigned long check = 1;
int oneCount = 0;
for (int k = 0; k < 32; ++k)
{
if (check & nNetmask)
oneCount++;
check = check << 1;
}
///////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////单线程,Ping局域网////////////////////////////////

struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = 0;
addr.sin_addr.s_addr = (nIp & nNetmask) + (1 << oneCount);
int addrlen = sizeof(struct sockaddr_in);
for (int index = 0; index < subNetHostSum; ++index)
{
// 忽略本主机 和 网关
if (addr.sin_addr.s_addr == nGate
|| addr.sin_addr.s_addr == nIp)
{
addr.sin_addr.s_addr = addr.sin_addr.s_addr
+ (1 << oneCount);
continue;
}
for (int count1 = 0; count1 < 4; ++count1)
{

if (SendIcmp(sock, addr, addrlen) == false)
continue;
int ret = RecvIcmp(sock, addr, addrlen);

if (ret == -1)	// 请求超时 或者 select 调用出错
continue;
else if (ret == -2)
{
count1--;
continue;
}
m_LanUsers.m_ActiveHost.push_back(addr.sin_addr.s_addr);
char *c = (char *)malloc(16);
strncpy(c, inet_ntoa(addr.sin_addr), 16);
m_test.push_back(c);
break;
}
addr.sin_addr.s_addr = addr.sin_addr.s_addr
+ (1 << oneCount);
}
closesocket(sock);


bool CNetTrans::SendIcmp(SOCKET sock, sockaddr_in addr, int addrlen)
{
icmp_req iHdr;
iHdr.icmphdr.type = ICMP_REQ;
iHdr.icmphdr.code = 0;
iHdr.icmphdr.checksum = 0;
// RFC说这个一般为0,不知道有什么用
iHdr.icmphdr.flag = 0;
iHdr.icmphdr.seque = 0;

for (size_t index = 0; index < sizeof(iHdr.data); ++index)
{
iHdr.data[index] = '1' + index;
}
// 检验和一定要计算
iHdr.icmphdr.checksum = this->checksum((u_short *)&iHdr, sizeof(icmp_req));

int nRet = sendto(sock, (char *)&iHdr, sizeof(icmp_req), 0, (struct sockaddr *)&addr, addrlen);
if (nRet == SOCKET_ERROR)
{
//AfxMessageBox((LPCTSTR)"sendto error, in CNetTrans::SendIcmp(SOCKET sock, const sockaddr_in &pAddr, int addrlen)");
return false;
}
return true;
}

int CNetTrans::RecvIcmp(SOCKET sock, sockaddr_in addr, int addrlen)
{
/*
2015/12/9/23:11
线程的执行存在问题,如当192.168.1.x正确收到回复报文后
因为发送了四个报文,所以回复也可能是多个,后面连续的IP竟然会recvfrom得到x的回复数据包
究竟在创建套接字 还是 recvfrom 调用有问题?
下面copy备份作判断。
*/
struct sockaddr_in a = addr;
char buf[128];
int pLen = addrlen;
fd_set rdSet;
struct timeval time;
rdSet.fd_count = 1;
rdSet.fd_array[0] = sock;
time.tv_sec = 1;
//微妙
/*
2015/12/9/20:40
用单线程ping测试,发现提高超时的时间长度可提高准确率,比如1秒
太小的超时时间会导致得到多余的错误的IP,但按照不可达的情况来说,按照这个函数
实现的逻辑来说,明显是直接忽略的,为什么会导致返回1???
不可达的时候本主机会回复一个icmp报文给自己。
*/
time.tv_usec = 0;
/*
2015/12/8/18:55
这里有个问题:由于目标主机不可达的情况下,回复报文的时间过长,使得和请求超时分辨不出来,
导致icmp网络嗅探过程时间长。
目前想到的解决办法,用多线程。
*/
int iRet = select(sock + 1, &rdSet, nullptr, nullptr, &time);
// 请求超时
if (iRet == 0 || iRet == SOCKET_ERROR)
{
if (iRet == SOCKET_ERROR)
return -2;
return -1;
}
/*
这里有个问题:有时recvfrom阻塞不往下执行,通过select设定一个定时器可以解决。

发生请求超时时,那是因为局域网的确有此地址的主机,路由表有他的表项,但主机并不在线,
这是并没有包被回复。
和目标主机不可达不同,他有包被回复,来自本主机ip,recvfrom可以感知。
*/

if (iRet == 1)
{

int size = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *)&addr, &pLen);
if (size == SOCKET_ERROR)
{
//MessageBox(buf,"");//"recvfrom error, in CNetTrans::RecvIcmp(SOCKET sock, const sockaddr_in &pAddr, int addrlen)"));
return -2;
}

// 接受到的数据是IP层的
icmp_reply iReply;
iReply.iphdr = (struct ip_hdr *)buf;
int len = ((iReply.iphdr->ver4_hlen4 & 0xf)) * 4;
iReply.icmphdr = (struct icmp_hdr *)(buf + len);

struct in_addr a1;
a1.s_addr = iReply.iphdr->souraddr;
char *test = inet_ntoa(a1);

unsigned long us = inet_addr(m_Host[0].netmask.c_str())
& inet_addr(m_Host[0].ip.c_str());
unsigned long you = iReply.iphdr->souraddr & inet_addr(m_Host[0].netmask.c_str());

unsigned long testip = inet_addr("192.168.1.107");
/*
2015/12/9/22:14
创建端口为0的icmp原始套接字相当于一个网络嗅探器?有接收到源地址不是本局域网的地址的情况
us == you 是针对这种情况的判断
2015/12/10/11:09
的确是的,所以才会出现一些局域网内奇怪的IP在包含,recvIcmp接收到非目的IP得回复,
但Thread_GetLanUsers线程函数不知道,以为是本局域网ip可以ping通。
*/
if (addr.sin_addr.s_addr == inet_addr("192.168.1.107"))
return -1;
if (a.sin_addr.s_addr != addr.sin_addr.s_addr)
return -2;
if (iReply.icmphdr->type == ICMP_REPLY)
return 1;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: