有关ICMP的内容在此不做阐述,仅说下ICMP报文何时会发送(ping除外),ICMP报文中的数据又是什么,如何处理接收到的ICMP报文
何时产生ICMP
当发送一段udp报文到一个不可达的机器,或者ttl耗尽,或者MTU大于在某处路由的mtu,此时均会收到ICMP报文。上述所说均是常见例子,分别对应3,3;11,0;3,4
ICMP数据
携带的正是你发送的UDP数据,会将其原原本本的发送回来,其中会包含IP层的数据
如下是通过wireshark捕获的3,3报文。此时由于客户端的突然关闭,但是路由链路还存在,故主机回收到此udp,然而程序退出,故回复了ICMP报文
下面分别是udp报文和ICMP报文
// udp数据
1f 40 d1 27 00 1c 2d 20
21 00 00 29 78 7e a0 a2 74 09 b9 7a 00 10 00 00 98 6a 48 23
// ICMP报文
03 03 96 ec 00 00 00 00 // ICMP头部,以下是数据部分
45 68 00 30 7f 8b 40 00 35 11 2c 08 31 ** ** dc c0 a8 14 55 // IP头部 **是屏蔽了公网IP
1f 40 d1 27 00 1c 2d 20 // udp头部
21 00 00 29 78 7e a0 a2 74 09 b9 7a 00 10 00 00 98 6a 48 23 // udp数据
ICMP报文处理
1、设置套接字
int on = 1;
setsockopt(fd, SOL_IP, IP_RECVERR, &on, sizeof(on); // 仅限IPv4
2、通过poll/epoll返回 POLLERR/EPOLLERR后读取消息
unsigned char vec_buf[4096], ancillary_buf[4096];
struct iovec iov = {vec_buf, sizeof(vec_buf)};
struct sockaddr_in remote;
struct msghdr msg;
ssize_t len;
struct cmsghdr *cmsg;
struct sock_extended_err *e;
struct sockaddr *icmp_addr;
struct sockaddr_in *icmp_sin;
memset(&msg, 0, sizeof(msg));
msg.msg_name = &remote;
msg.msg_namelen = sizeof(remote);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_flags = 0;
msg.msg_control = ancillary_buf;
msg.msg_controllen = sizeof(ancillary_buf);
EINTR_LOOP(len, recvmsg(udpfd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT));
if (len < 0) {
if (errno != EAGAIN || errno != EWOULDBLOCK) {
LOGE("recvmsg error. [%d,%s]", errno, strerror(errno));
}
break;
}
// 收到几个ICMP就循环几次。可通过man 3 cmsg查看详情
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_type != IP_RECVERR) {
LOGD("Unhandled errqueue type: %d\n", cmsg->cmsg_type);
continue;
}
if (cmsg->cmsg_level != SOL_IP) {
LOGD("Unhandled errqueue level: %d\n", cmsg->cmsg_level);
continue;
}
LOGD("errqueue: IP_RECVERR, SOL_IP, len %zd\n", cmsg->cmsg_len);
if (remote.sin_family != AF_INET) {
LOGD("Address family is %d, not AF_INET. Ignoring\n", remote.sin_family);
continue;
}
LOGD("Remote host: %s:%d\n", inet_ntoa(remote.sin_addr), ntohs(remote.sin_port));
e = (struct sock_extended_err *)CMSG_DATA(cmsg);
if (!e) {
LOGD("errqueue: sock_extended_err is NULL?\n");
continue;
}
if (e->ee_origin != SO_EE_ORIGIN_ICMP) {
LOGD("errqueue: Unexpected origin: %d\n", e->ee_origin);
continue;
}
LOGD(" ee_errno: %d\n", e->ee_errno);
LOGD(" ee_origin: %d\n", e->ee_origin);
LOGD(" ee_type: %d\n", e->ee_type);
LOGD(" ee_code: %d\n", e->ee_code);
LOGD(" ee_info: %d\n", e->ee_info); // discovered MTU for EMSGSIZE errors
LOGD(" ee_data: %d\n", e->ee_data);
// "Node that caused the error"
// "Node that generated the error"
icmp_addr = (struct sockaddr *)SO_EE_OFFENDER(e);
icmp_sin = (struct sockaddr_in *)icmp_addr;
if (icmp_addr->sa_family != AF_INET) {
LOGD("ICMP's address family is %d, not AF_INET?\n", icmp_addr->sa_family);
continue;
}
if (icmp_sin->sin_port != 0) {
LOGD("ICMP's 'port' is not 0?\n");
continue;
}
LOGD("msg_flags: 0x%x", msg.msg_flags);
// if (msg.msg_flags & MSG_TRUNC)
// fprintf(stderr, " MSG_TRUNC");
// if (msg.msg_flags & MSG_CTRUNC)
// fprintf(stderr, " MSG_CTRUNC");
// if (msg.msg_flags & MSG_EOR)
// fprintf(stderr, " MSG_EOR");
// if (msg.msg_flags & MSG_OOB)
// fprintf(stderr, " MSG_OOB");
// if (msg.msg_flags & MSG_ERRQUEUE)
// fprintf(stderr, " MSG_ERRQUEUE");
// fprintf(stderr, "\n");
if (e->ee_type == 3 && e->ee_code == 4) { // utp猜测的MTU过大
LOGD("ICMP: type 3, code 4: Fragmentation error, discovered MTU %d\n", e->ee_info);
utp_process_icmp_fragmentation(m_utpContex, vec_buf, len, (struct sockaddr *)&remote, sizeof(remote), e->ee_info);
} else {
LOGD("ICMP: type %d, code %d\n", e->ee_type, e->ee_code);
utp_process_icmp_error(m_utpContex, vec_buf, len, (struct sockaddr *)&remote, sizeof(remote));
}
}