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

22 篇文章 0 订阅
4 篇文章 0 订阅

大概思路是:获取本主机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;
	}
}




  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值