问题描述
目标:
我需要能够 ping 网络交换机以确定它是否可用.这是为了告诉用户网络电缆被拔掉,网络交换机不可用,或者网络通信路径中存在一些其他问题.我意识到这不是一个全面的诊断工具,但有总比没有好.
I need to be able to ping a network switch to determine whether or not it is available. This is meant to tell the user that either the network cabling is unplugged, the network switch is unavailable, or some other problem lies within the network communication pathway. I realize this is not a comprehensive diagnosis tool, but something is better than nothing.
设计:
我计划使用带有原始套接字的 ICMP 以 IPv4 点符号向特定地址发送五 (5) 条 ping 消息.我将在套接字上设置一个 ICMP 过滤器,并且不会创建我自己的 IP 标头.ICMP 的传输将通过 sendto 方法和通过 recvfrom 方法接收.这将发生在单个线程上(尽管可以使用另一个线程将传输和接收分开).通过将接收到的消息的 ID 与发送的 ID 进行匹配,将进一步过滤消息的接收.存储的 ID 将是应用程序的运行进程 ID.如果收到 ICMP_ECHOREPLY 消息并且消息的 ID 与存储的 ID 匹配,则计数器递增,直到达到五 (4)(计数器从零开始).我将尝试发送 ping,等待其回复,然后重复此过程五 (5) 次.
I planned on using ICMP with raw sockets to send five (5) ping messages to a particular address in IPv4 dot-notation. I will setup an ICMP filter on the socket and will not be creating my own IP header. Transmission of the ICMP will be through the sendto method and reception through the recvfrom method. This will occur on a single thread (though another thread can be used to break transmission and reception apart). Reception of a message will further be filtered by matching the ID of the received message to the ID that was transmitted. The ID stored will be the running process ID of the application. If an ICMP_ECHOREPLY message is received and the ID of the message and the stored ID match, then a counter is incremented until five (4) has been reached (the counter is zero-based). I will attempt to send a ping, wait for its reply, and repeat this process five (5) times.
问题:
在实现我的设计之后,每当我与一个活动的网络参与者 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"时,我都会收到From 192.168.40.50 icmp_seq=xx Destination Host Unreachable".我不应该收到 ICMP_DEST_UNREACH 消息而不是 ICMP_ECHOREPLY 消息吗?
After having implemented my design, whenever I ping a particular valid network address (say 192.168.11.15) with an active network participant, I receive ICMP_ECHOREPLY messages for each of the five (5) pings. However, whenever I ping a valid network address (say 192.168.30.30) with inactive network participants (meaning no device is connected to the particular address), I get one (1) ICMP_DEST_UNREACH, and four (4) ICMP_ECHOREPLY messages. The ID in the reply messages match the ID stored within the software. Whenever I perform a 'ping 192.168.30.30' from the commandline, I get 'From 192.168.40.50 icmp_seq=xx Destination Host Unreachable'. Am I not supposed to be receiving ICMP_DEST_UNREACH messages instead of ICMP_ECHOREPLY messages?
代码:
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;
};
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);
}
为了编译,只需在 Linux 机器的命令行中输入g++ main.cpp -o ping",它就会编译(也就是说,如果安装了所有源代码).
In order to compile, just type 'g++ main.cpp -o ping' into the command-line of a Linux box, and it should compile (that is, if all of the source code is installed).
结论:
谁能告诉我为什么我从不在该特定网络地址上的设备收到一 (1) 条 ICMP_DEST_UNREACH 和四 (4) 条 ICMP_ECHOREPLY 消息?
Can anyone tell me why I am receiving one (1) ICMP_DEST_UNREACH and four (4) ICMP_ECHOREPLY messages from a device that isn't on that particular network address?
注意:您可以从 main.cpp 文件中更改网络 IP 地址.只需将 IP 更改为您网络上实际存在的设备或网络上不存在的设备即可.
NOTE: You can change the network IP address from the main.cpp file. Just change the IP to a device that actually exists on your network or a device that doesn't exist on your network.
我对编码风格的批评也不感兴趣.我知道它不漂亮,'C' 风格的转换与 C++ 的转换混合在一起,内存管理很差,等等,但这只是原型代码.它并不意味着漂亮.
I'm also not interested in criticisms about coding style. I know it isn't pretty, has 'C' style casting mixed with C++ casts, has poor memory management, etc, but this is only prototype code. It isn't meant to be pretty.
推荐答案
好的,我找到了错误.看看这两行.
Ok i found the error. Look at this two lines.
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);
两个函数都使用 pingaddr
指针作为参数,但应该避免这种情况,因为在 sendto()
函数中用于指向 icmp 数据包的目标 IP,但在recvfrom()
用于获取回复的主机的 IP.
both functions uses pingaddr
pointer as parameter, but this should avoided because in the sendto()
function is used to point the destination IP of the icmp packet but in the recvfrom()
is used to get back the IP of the host that's replying.
假设 pingaddr
设置了一个无法访问的 IP.在您的第一个 ICMP_REQUEST
之后,第一个网关将回复您一个 ICMP_DEST_UNREACH
并且...这里出现错误...当 recvfrom 被调用时,pingaddr 结构将被覆盖网关的IP.
Let's say pingaddr
is set with an IP not reachable. After your first ICMP_REQUEST
the first gateway will reply to you with a ICMP_DEST_UNREACH
and... here comes the error... when recvfrom is called, pingaddr structure will be overwritten with the IP of the gateway.
所以...从第二个 ping 开始,您将指向网关 IP,该 IP 显然存在并且将回复 ICMP_ECHOREPLY
.
SO... from the second ping you'll be pointing to the gateway IP that, obviously, exists and will reply with a ICMP_ECHOREPLY
.
解决方案:
避免将相同的 sockaddr_in
结构指针传递给 sendto()
和 recvfrom()
.
avoid pass the same sockaddr_in
structure pointer to both sendto()
and recvfrom()
.
这篇关于对无法到达的目的地进行 ICMP 回显请求/回复的正确过程是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!