目标:
我需要能够ping网络交换机以确定它是否可用.这是为了告诉用户网络布线未插入,网络交换机不可用,或者网络通信路径中存在其他一些问题.我意识到这不是一个全面的诊断工具,但总比没有好.
设计:
我计划使用带有原始套接字的ICMP,以IPv4点式表示法向特定地址发送五(5)个ping消息.我将在套接字上设置ICMP过滤器,而不会创建自己的IP头.ICMP的传输将通过sendto方法和通过recvfrom方法接收.这将发生在一个线程上(虽然另一个线程可用于分开传输和接收).通过将接收到的消息的ID与发送的ID相匹配,将进一步过滤消息的接收.存储的ID将是应用程序的运行进程ID.如果接收到ICMP_ECHOREPLY消息并且消息的ID和存储的ID匹配,则递增计数器直到达到五(4)(计数器从零开始).我将尝试发送ping,等待其回复,并重复此过程五(5)次.
问题:
在实现我的设计之后,每当我使用活动网络参与者ping特定的有效网络地址(例如192.168.11.15)时,我会收到五(5)个ping中的每一个的ICMP_ECHOREPLY消息.但是,每当我使用非活动网络参与者(即没有设备连接到特定地址)ping有效网络地址(例如192.168.30.30)时,我会得到一(1)个ICMP_DEST_UNREACH和四(4)个ICMP_ECHOREPLY消息.回复消息中的ID与软件中存储的ID相匹配.每当我从命令行执行'ping 192.168.30.30'时,我得到'从192.168.40.50 icmp_seq = xx目标主机无法访问'.我不应该接收ICMP_DEST_UNREACH消息而不是ICMP_ECHOREPLY消息吗?
代码:
Ping.h:
#include <netinet/in.h>
#include <linux/ip.h>
#include <linux/ipmc.h>
#include <arpa/inet.h>
#include <cstdio>
#include <cstdlib>
#include <stdint.h>
#include <time.h>
#include <errno.h>
#include <string>
#include <cstring>
#include <netdb.h>
class Ping
{
public:
Ping(std::string host) : _host(host) {}
~Ping() {}
void start()
{
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if(sock < 0)
{
printf("Failed to create socket!\n");
close(sock);
exit(1);
}
setuid(getuid());
sockaddr_in pingaddr;
memset(&pingaddr, 0, sizeof(sockaddr_in));
pingaddr.sin_family = AF_INET;
hostent *h = gethostbyname(_host.c_str());
if(not h)
{
printf("Failed to get host by name!\n");
close(sock);
exit(1);
}
memcpy(&pingaddr.sin_addr, h->h_addr, sizeof(pingaddr.sin_addr));
// Set the ID of the sender (will go into the ID of the echo msg)
int pid = getpid();
// Only want to receive the following messages
icmp_filter filter;
filter.data = ~((1<<ICMP_SOURCE_QUENCH) |
(1<<ICMP_DEST_UNREACH) |
(1<<ICMP_TIME_EXCEEDED) |
(1<<ICMP_REDIRECT) |
(1<<ICMP_ECHOREPLY));
if(setsockopt(sock, SOL_RAW, ICMP_FILTER, (char *)&filter, sizeof(filter)) < 0)
{
perror("setsockopt(ICMP_FILTER)");
exit(3);
}
// Number of valid echo receptions
int nrec = 0;
// Send the packet
for(int i = 0; i < 5; ++i)
{
char packet[sizeof(icmphdr)];
memset(packet, 0, sizeof(packet));
icmphdr *pkt = (icmphdr *)packet;
pkt->type = ICMP_ECHO;
pkt->code = 0;
pkt->checksum = 0;
pkt->un.echo.id = htons(pid & 0xFFFF);
pkt->un.echo.sequence = i;
pkt->checksum = checksum((uint16_t *)pkt, sizeof(packet));
int bytes = sendto(sock, packet, sizeof(packet), 0, (sockaddr *)&pingaddr, sizeof(sockaddr_in));
if(bytes < 0)
{
printf("Failed to send to receiver\n");
close(sock);
exit(1);
}
else if(bytes != sizeof(packet))
{
printf("Failed to write the whole packet --- bytes: %d, sizeof(packet): %d\n", bytes, sizeof(packet));
close(sock);
exit(1);
}
while(1)
{
char inbuf[192];
memset(inbuf, 0, sizeof(inbuf));
int addrlen = sizeof(sockaddr_in);
bytes = recvfrom(sock, inbuf, sizeof(inbuf), 0, (sockaddr *)&pingaddr, (socklen_t *)&addrlen);
if(bytes < 0)
{
printf("Error on recvfrom\n");
exit(1);
}
else
{
if(bytes < sizeof(iphdr) + sizeof(icmphdr))
{
printf("Incorrect read bytes!\n");
continue;
}
iphdr *iph = (iphdr *)inbuf;
int hlen = (iph->ihl << 2);
bytes -= hlen;
pkt = (icmphdr *)(inbuf + hlen);
int id = ntohs(pkt->un.echo.id);
if(pkt->type == ICMP_ECHOREPLY)
{
printf(" ICMP_ECHOREPLY\n");
if(id == pid)
{
nrec++;
if(i < 5) break;
}
}
else if(pkt->type == ICMP_DEST_UNREACH)
{
printf(" ICMP_DEST_UNREACH\n");
// Extract the original data out of the received message
int offset = sizeof(iphdr) + sizeof(icmphdr) + sizeof(iphdr);
if(((bytes + hlen) - offset) == sizeof(icmphdr))
{
icmphdr *p = reinterpret_cast<icmphdr *>(inbuf + offset);
id = ntohs(p->un.echo.id);
if(origid == pid)
{
printf(" IDs match!\n");
break;
}
}
}
}
}
}
printf("nrec: %d\n", nrec);
}
private:
int32_t checksum(uint16_t *buf, int32_t len)
{
int32_t nleft = len;
int32_t sum = 0;
uint16_t *w = buf;
uint16_t answer = 0;
while(nleft > 1)
{
sum += *w++;
nleft -= 2;
}
if(nleft == 1)
{
*(uint16_t *)(&answer) = *(uint8_t *)w;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
answer = ~sum;
return answer;
}
std::string _host;
};
Run Code Online (Sandbox Code Playgroud)
main.cpp中:
#include "Ping.h"
int main()
{
// Ping ping("192.168.11.15");
Ping ping("192.168.30.30");
ping.start();
while(1) sleep(10);
}
Run Code Online (Sandbox Code Playgroud)
为了编译,只需在Linux框的命令行中键入"g ++ main.cpp -o ping",它就应该编译(即,如果安装了所有源代码).
结论:
任何人都可以告诉我为什么我从一个不在该特定网络地址的设备收到一(1)个ICMP_DEST_UNREACH和四(4)个ICMP_ECHOREPLY消息?
注意:您可以从main.cpp文件更改网络IP地址.只需将IP更改为网络上实际存在的设备或网络上不存在的设备即可.
我对编码风格的批评也不感兴趣.我知道它不漂亮,'C'风格的演员阵容混合了C++演员阵容,内存管理能力差等等,但这只是原型代码.它并不意味着漂亮.
好的,我发现了错误.看看这两行.
int bytes = sendto(sock, packet, sizeof(packet), 0, (sockaddr *)&pingaddr, sizeof(sockaddr_in));
bytes = recvfrom(sock, inbuf, sizeof(inbuf), 0, (sockaddr *)&pingaddr, (socklen_t *)&addrlen);
Run Code Online (Sandbox Code Playgroud)
这两个函数都使用pingaddr指针作为参数,但这应该避免,因为该 sendto()函数用于指向icmp数据包的目标IP,但recvfrom()用于返回正在回复的主机的IP.
假设pingaddr设置了IP无法访问.在你的第ICMP_REQUEST一个网关将用一个ICMP_DEST_UNREACH和... 回复你...这里出现错误...当调用recvfrom时,pingaddr结构将被网关的IP覆盖.
所以...从第二次ping你将指向显然存在的网关IP,并将回复ICMP_ECHOREPLY.
解:
避免将相同的sockaddr_in结构指针传递给两者sendto()和recvfrom().
| 归档时间: |
|
| 查看次数: |
11021 次 |
| 最近记录: |