困扰我三天的问题
IP_RECVERR (since Linux 2.2)
Enable extended reliable error message passing. When enabled on a datagram socket, all
generated errors will be queued in a per-socket error queue. When the user receives an
error from a socket operation, the errors can be received by calling recvmsg(2) with the
MSG_ERRQUEUE flag set. The sock_extended_err structure describing the error will be
passed in an ancillary message with the type IP_RECVERR and the level IPPROTO_IP. This is
useful for reliable error handling on unconnected sockets. The received data portion of
the error queue contains the error packet.
The IP_RECVERR control message contains a sock_extended_err structure:
#define SO_EE_ORIGIN_NONE 0
#define SO_EE_ORIGIN_LOCAL 1
#define SO_EE_ORIGIN_ICMP 2
#define SO_EE_ORIGIN_ICMP6 3
struct sock_extended_err {
uint32_t ee_errno; /* error number */
uint8_t ee_origin; /* where the error originated */
uint8_t ee_type; /* type */
uint8_t ee_code; /* code */
uint8_t ee_pad;
uint32_t ee_info; /* additional information */
uint32_t ee_data; /* other data */
/* More data may follow */
};
struct sockaddr *SO_EE_OFFENDER(struct sock_extended_err *);
ee_errno contains the errno number of the queued error. ee_origin is the origin code of
where the error originated. The other fields are protocol-specific. The macro
SO_EE_OFFENDER returns a pointer to the address of the network object where the error
originated from given a pointer to the ancillary message. If this address is not known,
the sa_family member of the sockaddr contains AF_UNSPEC and the other fields of the sock‐
addr are undefined.
IP uses the sock_extended_err structure as follows: ee_origin is set to SO_EE_ORIGIN_ICMP
for errors received as an ICMP packet, or SO_EE_ORIGIN_LOCAL for locally generated errors.
Unknown values should be ignored. ee_type and ee_code are set from the type and code
fields of the ICMP header. ee_info contains the discovered MTU for EMSGSIZE errors. The
message also contains the sockaddr_in of the node caused the error, which can be accessed
with the SO_EE_OFFENDER macro. The sin_family field of the SO_EE_OFFENDER address is
AF_UNSPEC when the source was unknown. When the error originated from the network, all IP
options (IP_OPTIONS, IP_TTL, etc.) enabled on the socket and contained in the error packet
are passed as control messages. The payload of the packet causing the error is returned
as normal payload. Note that TCP has no error queue; MSG_ERRQUEUE is not permitted on
SOCK_STREAM sockets. IP_RECVERR is valid for TCP, but all errors are returned by socket
function return or SO_ERROR only.
For raw sockets, IP_RECVERR enables passing of all received ICMP errors to the applica‐
tion, otherwise errors are only reported on connected sockets
It sets or retrieves an integer boolean flag. IP_RECVERR defaults to off.
顺便复习一发recvmsg
recvmsg()
The recvmsg() call uses a msghdr structure to minimize the number of directly supplied arguments.
This structure is defined as follows in <sys/socket.h>:
struct iovec { /* Scatter/gather array items */
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes to transfer */
};
struct msghdr {
void *msg_name; /* optional address */
socklen_t msg_namelen; /* size of address */
struct iovec *msg_iov; /* scatter/gather array */
size_t msg_iovlen; /* # elements in msg_iov */
void *msg_control; /* ancillary data, see below */
size_t msg_controllen; /* ancillary data buffer len */
int msg_flags; /* flags on received message */
};
The msg_name field points to a caller-allocated buffer that is used to return the source address
if the socket is unconnected. The caller should set msg_namelen to the size of this buffer
before this call; upon return from a successful call, msg_namelen will contain the length of the
returned address. If the application does not need to know the source address, msg_name can be
specified as NULL.
The fields msg_iov and msg_iovlen describe scatter-gather locations, as discussed in readv(2).
The field msg_control, which has length msg_controllen, points to a buffer for other protocol
control-related messages or miscellaneous ancillary data. When recvmsg() is called, msg_con‐
trollen should contain the length of the available buffer in msg_control; upon return from a suc‐
cessful call it will contain the length of the control message sequence.
The messages are of the form:
struct cmsghdr {
size_t cmsg_len; /* Data byte count, including header
(type is socklen_t in POSIX) */
int cmsg_level; /* Originating protocol */
int cmsg_type; /* Protocol-specific type */
/* followed by
unsigned char cmsg_data[]; */
};
Ancillary data should be accessed only by the macros defined in cmsg(3).
As an example, Linux uses this ancillary data mechanism to pass extended errors, IP options, or
file descriptors over UNIX domain sockets.
The msg_flags field in the msghdr is set on return of recvmsg(). It can contain several flags:
2.1.1.5 Blocking Read
Reading from the error queue is always a non-blocking operation. To block waiting on a timestamp, use poll or select. poll() will return POLLERR in pollfd.revents if any >data is ready on the error queue. There is no need to pass this flag in pollfd.events. This flag is ignored on request. See also "man 2 poll".
我们提到在对端主机上没有创建指定的UDP套接字时,我们向其发送一个UDP包,会得到一个目的端口不可达的ICMP出错报文。但内核在处理完该报文后,给应用程序仅仅返回一个ECONNREFUSED错误号,所以应用程序能知道的全部信息就是连接被拒绝,至于为什么被拒绝,没有办法知道。我们可以通过套接字选项的设置,让内核返回更为详细的出错信息,以利于调试程序,发现问题。下面是通过套接字选项传递扩展出错信息的一个示例程序。
/*************************************************************************
> File Name: extendederrorDemo.cc
> Author: ngkimbing
> Mail: ngkimbing@foxmail.com
> Created Time: Sat 14 Mar 2020 01:51:12 PM CST
************************************************************************/
#include "unp.h"
#include <errno.h>
#include <linux/errqueue.h>
#include <linux/types.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
int ip_control_msg(struct cmsghdr *msg) {
int ret = 0;
switch (msg->cmsg_type) {
case IP_RECVERR: {
struct sock_extended_err *exterr;
exterr = (struct sock_extended_err *)(CMSG_DATA(msg));
printf("ee_errno: %u
", exterr->ee_errno);
printf("ee_origin: %u
", exterr->ee_origin);
printf("ee_type: %u
", exterr->ee_type);
printf("ee_code: %u
", exterr->ee_code);
printf("ee_pad: %u
", exterr->ee_pad);
printf("ee_info: %u
", exterr->ee_info);
printf("ee_data: %u
", exterr->ee_data);
}
ret = -1;
break;
default: break;
}
return ret;
}
int control_msg(struct msghdr *msg) {
int ret = 0;
struct cmsghdr *control_msg = CMSG_FIRSTHDR(msg);
while (control_msg != NULL) {
switch (control_msg->cmsg_level) {
case SOL_IP: ret = ip_control_msg(control_msg); break;
default: break;
}
control_msg = CMSG_NXTHDR(msg, control_msg);
}
return ret;
}
#define MY_IPPROTO_UDP IPPROTO_UDP
#define MY_PF_INET AF_INET
int main() {
int i;
struct sockaddr_in dest;
dest = makeAddr("172.23.0.1", 16000);
// dest = makeAddr("8.8.8.8", 33445);
int fd = socket(MY_PF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
perror("socket");
return -1;
}
if (connect(fd, (struct sockaddr *)&dest, sizeof(dest)) < 0) {
perror("connect");
return -1;
}
int val = 1;
// 最关键的地方!!
if (setsockopt(fd, IPPROTO_IP, IP_RECVERR, &val, sizeof(val)) == -1) {
perror("setsockopt");
return -1;
}
char sendbuf[] = {"whatever"};
int bwrite = send(fd, sendbuf, sizeof(sendbuf), 0);
if (bwrite == -1) {
perror("send");
return -1;
}
char buf[1024];
char control_buf[1024];
struct msghdr msg;
struct iovec iov = {buf, 1024};
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = &control_buf;
msg.msg_controllen = 1024;
sleep(3);
/*
* Reading from the error queue is always a non-blocking operation. To block
* waiting on a timestamp, use poll or select. poll() will return POLLERR in
* pollfd.revents if any data is ready on the error queue. There is no need
* to pass this flag in pollfd.events. This flag is ignored on request. See
* also "man 2 poll".
*/
int bread = recvmsg(fd, &msg, MSG_ERRQUEUE);
if (bread == -1) {
perror("recv");
return -1;
}
if (control_msg(&msg) >= 0)
printf("successed!
");
else
printf("failed!
");
close(fd);
return 0;
}