ping
命令来测试网络连通性。要检查与 IP 地址为 192.168.1.1 的主机之间的连接,可以在终端中输入:,,“bash,ping 192.168.1.1,
“,,这将发送 ICMP 回显请求到指定的 IP 地址,并显示响应时间及丢包情况。在Linux系统中,ping
命令是一种常用的网络工具,用于测试主机之间的连通性以及测量数据包的往返时间,以下是关于如何在Linux上实现ping
命令的详细步骤和解释:
一、理解ICMP协议
ping
命令基于ICMP(Internet Control Message Protocol)协议工作,ICMP允许主机或路由器报告差错情况或提供有关异常情况的报告。ping
命令使用的是ICMP回送请求和回送应答报文类型,以判断目标主机是否可达。
二、创建原始套接字
在Linux中,要发送ICMP包,需要使用原始套接字(raw socket),原始套接字可以直接访问网络层,允许我们构造自定义的网络协议数据包。
int ip_fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (ip_fd < 0) { perror("socket error"); exit(1); }
三、设置ICMP包头
ICMP包头包括类型、代码、校验和、标识符和序列号等字段,对于ICMP回送请求,类型字段应设置为8。
struct icmphdr { u_int8_t icmp_type; /* ICMP message type */ u_int8_t icmp_code; /* Optional ICMP code */ u_int16_t icmp_cksum; /* Optional ICMP checksum */ union { u_int32_t ih_idseq; /* ICMP ID and sequence number */ u_int32_t ih_gateway; /* Target gateway address */ struct { u_int16_t unused; u_int16_t mtu; } ih_pmtu; } icmp_hun; };
四、计算校验和
由于IP协议是不可靠的传输协议,为了确保数据在传输过程中没有发生变化,需要在ICMP包头中加入校验和字段,校验和的计算方法是将ICMP包头视为由若干个16位整数组成,对这些整数进行二进制反码求和,然后取反码得到校验和。
unsigned short cal_chksum(void *buffer, int len) { unsigned long sum = 0; unsigned short *data = buffer; while (len > 1) { sum += *data++; if (sum & 0x80000000) sum = (sum & 0xFFFF) + (sum >> 16); len -= 2; } if (len) sum += *(unsigned char *)data; while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16); return ~sum; }
五、构造并发送ICMP包
构造ICMP回送请求包,并使用原始套接字发送出去,需要记录当前时间,以便后续计算往返时间。
struct timeval begin_time; gettimeofday(&begin_time, NULL); struct icmphdr icmphdr; icmphdr.icmp_type = ICMP_ECHO; icmphdr.icmp_code = 0; icmphdr.icmp_cksum = 0; icmphdr.icmp_seq = htons(++sequence); icmphdr.icmp_id = getpid() & 0xFFFF; // Use process ID as the ID field icmphdr.icmp_cksum = cal_chksum(&icmphdr, sizeof(icmphdr)); // Update checksum after setting other fields sendto(ip_fd, &icmphdr, sizeof(icmphdr), 0, (struct sockaddr*)&send_addr, sizeof(send_addr));
六、接收ICMP回显应答包并处理
接收到ICMP回显应答包后,解析包头信息,并计算往返时间,如果收到的是其他类型的ICMP包,则忽略。
struct sockaddr_in recv_addr; socklen_t addrlen = sizeof(recv_addr); char buffer[BUFFER_SIZE]; memset(buffer, 0, BUFFER_SIZE); if (recvfrom(ip_fd, buffer, sizeof(buffer), 0, (struct sockaddr*)&recv_addr, &addrlen) <= 0) { perror("recvfrom error"); exit(1); } struct iphdr *iphdr = buffer; struct icmphdr *icmphdr = (struct icmphdr*)(buffer + (iphdr->ip_hl << 2)); // IP header length is in bytes, shift left by 2 to get it in 32-bit words if (icmphdr->icmp_type == ICMP_ECHOREPLY && icmphdr->icmp_id == getpid() & 0xFFFF) { struct timeval recv_time; gettimeofday(&recv_time, NULL); int rtt = (recv_time.tv_sec begin_time.tv_sec) * 1000 + (recv_time.tv_usec begin_time.tv_usec) / 1000.0; // Calculate round-trip time in milliseconds printf("From: %s, RTT: %d ms ", inet_ntoa(recv_addr.sin_addr), rtt); } else { printf("Received non-echo reply or invalid packet "); }
七、主函数逻辑
主函数负责解析命令行参数,设置信号处理函数以定时发送ICMP包,并根据用户输入的目标地址构造并发送ICMP包。
int main(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "Usage: %s <hostname/IP> ", argv[0]); exit(1); } // Create raw socket for ICMP int ip_fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (ip_fd < 0) { perror("socket error"); exit(1); } // Set up signal handler for alarm to send ICMP packets at regular intervals struct sigaction act; act.sa_handler = handle_alarm; act.sa_flags = SA_SIGINFO; // Use SA_SIGINFO to pass additional data to signal handler sigemptyset(&act.sa_mask); sigaction(SIGALRM, &act, NULL); // Set up destination address based on command line argument struct sockaddr_in send_addr; memset(&send_addr, 0, sizeof(send_addr)); send_addr.sin_family = AF_INET; if (inet_aton(argv[1], &send_addr.sin_addr) == 0) { // Check if it's an IP address struct hostent *host = gethostbyname(argv[1]); if (host == NULL) { perror("Unknown host"); exit(1); } memcpy(&send_addr.sin_addr, host->h_addr, host->h_length); } // Send ICMP packets at regular intervals until interrupted by user (Ctrl+C) while (1) { sleep(1); // Sleep for 1 second before sending next packet alarm(1); // Set alarm to trigger after 1 second } close(ip_fd); // Close the raw socket when done return 0; }
八、FAQs问答环节
问:为什么在发送ICMP包之前要将校验和字段置为0?
答:在发送ICMP包之前将校验和字段置为0是因为ICMP协议要求在传输过程中对整个包(包括校验和字段本身)进行校验和计算,通过将校验和字段先置为0,可以确保在计算校验和时不会受到之前值的影响,接收方在接收到ICMP包后也会重新计算校验和,并与包中的校验和字段进行比较,以验证数据的完整性,如果校验和不匹配,则说明数据在传输过程中发生了变化,该ICMP包将被丢弃。
问:在ping命令中,如何指定发送的数据包数量和间隔时间?
答:在Linux的ping命令中,可以通过-c
选项来指定发送的数据包数量,例如ping -c 4 www.example.com
表示发送4个数据包,通过-i
选项可以指定发送数据包的间隔时间,例如ping -i 0.5 www.example.com
表示每0.5秒发送一个数据包,这些选项使得ping命令更加灵活,可以根据实际需求进行调整。
原创文章,作者:未希,如若转载,请注明出处:https://www.kdun.com/ask/1496396.html
本网站发布或转载的文章及图片均来自网络,其原创性以及文中表达的观点和判断不代表本网站。如有问题,请联系客服处理。
发表回复