困扰我三天的问题

困扰我三天的问题

       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;
}

原文地址:https://www.cnblogs.com/Kimbing-Ng/p/12492974.html