是否可以在IP协议下使用ICMP套接字?也许是这样的:ICMP套接字(linux)
socket(PF_INET, <type>, IPPROTO_ICMP)?
我应该把什么在<类型>字段?我看到了一些使用SOCK_RAW的例子,但是这不会阻止操作系统执行IP协议的工作吗?
另一件事。操作系统如何知道应该发送ICMP数据报的过程,因为协议中没有涉及端口?
是否可以在IP协议下使用ICMP套接字?也许是这样的:ICMP套接字(linux)
socket(PF_INET, <type>, IPPROTO_ICMP)?
我应该把什么在<类型>字段?我看到了一些使用SOCK_RAW的例子,但是这不会阻止操作系统执行IP协议的工作吗?
另一件事。操作系统如何知道应该发送ICMP数据报的过程,因为协议中没有涉及端口?
是的,这是可能的,因为ping
命令执行ICMP。
要找出涉及的系统调用,你可以使用strace
那个命令(在root下)。
您还可以查看该命令的源代码,例如, Debian's ping
还有就是liboping库,帮助你...
的Linux都可以用使用一种特殊的ICMP套接字类型:
socket(PF_INET, SOCK_DGRAM IPPROTO_ICMP);
这样您就可以只发送ICMP回应请求内核将专门处理它(匹配请求/响应,填入校验和)。
这仅适用于设置了special sysctl的情况。默认情况下,即使root也不能使用这种套接字。您指定可以访问它的用户组。以允许根(组0)使用ICMP插座,做到:
sysctl -w net.ipv4.ping_group_range="0 0"
下面是一个例子程序来演示发送ICMP回应请求的非常基本的用法:
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <sys/select.h>
//note, to allow root to use icmp sockets, run:
//sysctl -w net.ipv4.ping_group_range="0 0"
void ping_it(struct in_addr *dst)
{
struct icmphdr icmp_hdr;
struct sockaddr_in addr;
int sequence = 0;
int sock = socket(AF_INET,SOCK_DGRAM,IPPROTO_ICMP);
if (sock < 0) {
perror("socket");
return ;
}
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_addr = *dst;
memset(&icmp_hdr, 0, sizeof icmp_hdr);
icmp_hdr.type = ICMP_ECHO;
icmp_hdr.un.echo.id = 1234;//arbitrary id
for (;;) {
unsigned char data[2048];
int rc;
struct timeval timeout = {3, 0}; //wait max 3 seconds for a reply
fd_set read_set;
socklen_t slen;
struct icmphdr rcv_hdr;
icmp_hdr.un.echo.sequence = sequence++;
memcpy(data, &icmp_hdr, sizeof icmp_hdr);
memcpy(data + sizeof icmp_hdr, "hello", 5); //icmp payload
rc = sendto(sock, data, sizeof icmp_hdr + 5,
0, (struct sockaddr*)&addr, sizeof addr);
if (rc <= 0) {
perror("Sendto");
break;
}
puts("Sent ICMP\n");
memset(&read_set, 0, sizeof read_set);
FD_SET(sock, &read_set);
//wait for a reply with a timeout
rc = select(sock + 1, &read_set, NULL, NULL, &timeout);
if (rc == 0) {
puts("Got no reply\n");
continue;
} else if (rc < 0) {
perror("Select");
break;
}
//we don't care about the sender address in this example..
slen = 0;
rc = recvfrom(sock, data, sizeof data, 0, NULL, &slen);
if (rc <= 0) {
perror("recvfrom");
break;
} else if (rc < sizeof rcv_hdr) {
printf("Error, got short ICMP packet, %d bytes\n", rc);
break;
}
memcpy(&rcv_hdr, data, sizeof rcv_hdr);
if (rcv_hdr.type == ICMP_ECHOREPLY) {
printf("ICMP Reply, id=0x%x, sequence = 0x%x\n",
icmp_hdr.un.echo.id, icmp_hdr.un.echo.sequence);
} else {
printf("Got ICMP packet with type 0x%x ?!?\n", rcv_hdr.type);
}
}
}
int main(int argc, char *argv[])
{
if (argc != 2) {
printf("usage: %s destination_ip\n", argv[0]);
return 1;
}
struct in_addr dst;
if (inet_aton(argv[1], &dst) == 0) {
perror("inet_aton");
printf("%s isn't a valid IP address\n", argv[1]);
return 1;
}
ping_it(&dst);
return 0;
}
注意,内核如果发送的数据没有适当的ICMP头的空间,并且ICMP type
必须是8(ICMP_ECHO)并且ICMP代码必须是0,则将拒绝并且失败sendto()调用。
您是否创建套接字是错的? SOCK_DRAM是OSI/ISO 7层堆栈的传输层的一部分... ICMP是网络层中第3层的一部分。我一直在阅读Stevens的Unix套接字编程,为了做到这一点,你需要声明一个SOCK_RAW来获得icmp包。看看这个代码[here](http://www.binarytides.com/packet-sniffer-code-c-linux/) –
@Florida_Jake SOCK_DGRAM不绑定到第7层。Unix套接字编程书不描述如何要使用linux特定的ICMP回显套接字,这里的例子工作。发送/接收ICMP消息的传统方式确实是使用SOCK_RAW,并自己构建ICMP消息。我发布的代码使用了一个替代的,特定于linux的功能来发送和接收ICMP回显消息。 – nos
不错,对我很好,很快就得到了结果。现在是查看IPv6 ping的时候了。 :) –