diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d1810eb..417ba192 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -322,8 +322,11 @@ if(NOT ZEPHYR_BASE) check_include_file(net/if.h HAVE_NET_IF_H) check_include_file(ifaddrs.h HAVE_IFADDRS_H) check_include_file(netinet/in.h HAVE_NETINET_IN_H) - check_include_file(sys/epoll.h HAVE_EPOLL_H) - check_include_file(sys/timerfd.h HAVE_TIMERFD_H) + check_include_file(netinet/ip_icmp.h HAVE_NETINET_IP_ICMP_H) + check_include_file(netinet/icmp6.h HAVE_NETINET_ICMP6_H) + check_include_file(linux/errqueue.h HAVE_LINUX_ERRQUEUE_H -D_LINUX_ERRQUEUE_H) + check_include_file(sys/epoll.h HAVE_SYS_EPOLL_H) + check_include_file(sys/timerfd.h HAVE_SYS_TIMERFD_H) check_include_file(arpa/inet.h HAVE_ARPA_INET_H) check_include_file(stdbool.h HAVE_STDBOOL_H) check_include_file(signal.h HAVE_SIGNAL_H) diff --git a/cmake_coap_config.h.in b/cmake_coap_config.h.in index 70c5c0d2..d44de0fc 100644 --- a/cmake_coap_config.h.in +++ b/cmake_coap_config.h.in @@ -32,7 +32,7 @@ #cmakedefine HAVE_DLFCN_H @HAVE_DLFCN_H@ /* Define to 1 if you have the header file. */ -#cmakedefine HAVE_EPOLL_H @HAVE_EPOLL_H@ +#cmakedefine HAVE_SYS_EPOLL_H @HAVE_SYS_EPOLL_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_ERRNO_H @HAVE_ERRNO_H@ @@ -79,6 +79,15 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_NETINET_IN_H @HAVE_NETINET_IN_H@ +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_NETINET_IP_ICMP_H @HAVE_NETINET_IP_ICMP_H@ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_NETINET_ICMP6_H @HAVE_NETINET_ICMP6_H@ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LINUX_ERRQUEUE_H @HAVE_LINUX_ERRQUEUE_H@ + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_PTHREAD_H @HAVE_PTHREAD_H@ @@ -155,7 +164,7 @@ #cmakedefine HAVE_TIME_H @HAVE_TIME_H@ /* Define to 1 if you have the header file. */ -#cmakedefine HAVE_TIMERFD_H @HAVE_TIMERFD_H@ +#cmakedefine HAVE_SYS_TIMERFD_H @HAVE_SYS_TIMERFD_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_UNISTD_H @HAVE_UNISTD_H@ diff --git a/configure.ac b/configure.ac index d1006b51..34eec4e3 100644 --- a/configure.ac +++ b/configure.ac @@ -1090,7 +1090,7 @@ AC_ARG_ENABLE([af_llc], AS_IF([test "x$build_af_llc" = "xyes"], [AC_DEFINE(COAP_AF_LLC_SUPPORT, [1], [Define to build support for LLC sockets.])]) -if test "x$with_epoll" = "xyes"; then +if test "x$build_af_llc" = "xyes"; then AC_CHECK_HEADER([netinet/ether.h]) AC_CHECK_HEADER([linux/llc.h]) fi @@ -1220,6 +1220,7 @@ fi # Checks for header files. AC_CHECK_HEADERS([assert.h arpa/inet.h limits.h netdb.h netinet/in.h \ + netinet/ip_icmp.h netinet/icmp6.h linux/errqueue.h \ pthread.h errno.h winsock2.h ws2tcpip.h signal.h \ stdlib.h string.h strings.h sys/socket.h sys/select.h sys/time.h \ time.h unistd.h sys/unistd.h sys/ioctl.h net/if.h ifaddrs.h \ diff --git a/include/coap3/coap_dgrm_internal.h b/include/coap3/coap_dgrm_internal.h index 86180fa6..da15a918 100644 --- a/include/coap3/coap_dgrm_internal.h +++ b/include/coap3/coap_dgrm_internal.h @@ -21,6 +21,14 @@ extern "C" { #endif +typedef struct coap_icmp_info_t { + uint32_t length; + uint8_t cmsg_level; + uint8_t ee_origin; + uint8_t ee_type; + uint8_t ee_code; +} coap_icmp_info_t; + /** * @ingroup internal_api * @defgroup dgrm Datagram (UDP) Support diff --git a/include/coap3/coap_session_internal.h b/include/coap3/coap_session_internal.h index 7b2e85e3..c2a912a7 100644 --- a/include/coap3/coap_session_internal.h +++ b/include/coap3/coap_session_internal.h @@ -260,6 +260,10 @@ struct coap_session_t { coap_tick_t last_tx; /**< Last time a ratelimited packet is sent */ uint8_t is_rate_limiting; /**< Currently NON rate limiting */ uint32_t ping_failed; /**< Ping failure count */ +#ifdef HAVE_LINUX_ERRQUEUE_H + coap_str_const_t *last_data_write; /**< Last sent data to check against ICMP + responses */ +#endif /* HAVE_LINUX_ERRQUEUE_H */ }; #if COAP_SERVER_SUPPORT diff --git a/src/coap_dgrm_posix.c b/src/coap_dgrm_posix.c index 88a083ab..83684e00 100644 --- a/src/coap_dgrm_posix.c +++ b/src/coap_dgrm_posix.c @@ -40,6 +40,18 @@ #ifdef HAVE_NETINET_IN_H # include #endif +#ifdef HAVE_NETINET_IP_ICMP_H +# include +#endif +#ifdef HAVE_NETINET_ICMP6_H +# include +#endif +#ifdef HAVE_LINUX_ERRQUEUE_H +# include +#endif +#ifdef HAVE_SYS_SELECT_H +# include +#endif #ifdef HAVE_WS2TCPIP_H #include # define OPTVAL_T(t) (const char*)(t) @@ -243,6 +255,12 @@ coap_socket_connect_udp(coap_socket_t *sock, case AF_INET: if (connect_addr.addr.sin.sin_port == 0) connect_addr.addr.sin.sin_port = htons(default_port); +#if IP_RECVERR && defined(HAVE_STRUCT_CMSGHDR) && defined(HAVE_LINUX_ERRQUEUE_H) + if (setsockopt(sock->fd, IPPROTO_IP, IP_RECVERR, OPTVAL_T(&on), + sizeof(on)) == COAP_SOCKET_ERROR) + coap_log_alert("coap_socket_connect_udp: setsockopt IP_RECVERR: %s\n", + coap_socket_strerror()); +#endif /* IP_RECVERR && HAVE_STRUCT_CMSGHDR && HAVE_LINUX_ERRQUEUE_H */ break; #endif /* COAP_IPV4_SUPPORT */ #if COAP_IPV6_SUPPORT @@ -256,6 +274,12 @@ coap_socket_connect_udp(coap_socket_t *sock, coap_log_warn("coap_socket_connect_udp: setsockopt IPV6_V6ONLY: %s\n", coap_socket_strerror()); } +#if IPV6_RECVERR && defined(HAVE_STRUCT_CMSGHDR) && defined(HAVE_LINUX_ERRQUEUE_H) + if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_RECVERR, OPTVAL_T(&on), + sizeof(on)) == COAP_SOCKET_ERROR) + coap_log_alert("coap_socket_connect_udp: setsockopt IPV6_RECVERR: %s\n", + coap_socket_strerror()); +#endif /* IPV6_RECVERR && HAVE_STRUCT_CMSGHDR && HAVE_LINUX_ERRQUEUE_H */ #endif /* COAP_IPV6_SUPPORT */ break; #if COAP_AF_UNIX_SUPPORT @@ -495,6 +519,98 @@ coap_test_cid_tuple_change(coap_session_t *session) { } #endif /* COAP_CLIENT_SUPPORT */ +#if defined(HAVE_LINUX_ERRQUEUE_H) && defined(HAVE_STRUCT_CMSGHDR) +static ssize_t +coap_get_icmp_data(coap_socket_t *sock, uint8_t *data, size_t length) { + coap_fd_t fd = sock->fd; + char control_buf[1024]; + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cmsg; + int kerrno = errno; + int ret; + + if (length < sizeof(coap_icmp_info_t)) { + return -1; + } + iov.iov_base = data + sizeof(coap_icmp_info_t); + iov.iov_len = length - sizeof(coap_icmp_info_t);; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control_buf; + msg.msg_controllen = sizeof(control_buf); + + /* Poll for errors using MSG_ERRQUEUE */ + ret = recvmsg(fd, &msg, MSG_ERRQUEUE); + errno = kerrno; + if (ret < 0) { + return -1; + } + + /* Parse control messages, looking for ICMP error messages */ + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { +#if COAP_IPV4_SUPPORT && IP_RECVERR + if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_RECVERR) { + struct sock_extended_err *err = (struct sock_extended_err *)CMSG_DATA(cmsg); + coap_icmp_info_t *info = (coap_icmp_info_t *)data; + + if (err->ee_origin == SO_EE_ORIGIN_ICMP) { + info->length = (uint32_t)ret; + info->cmsg_level = cmsg->cmsg_level; + info->ee_origin = err->ee_origin; + info->ee_type = err->ee_type; + info->ee_code = err->ee_code; +#if COAP_SERVER_SUPPORT + coap_log_debug("* %s: ICMP Error Type: %d, Code: %d\n", + sock->endpoint ? coap_endpoint_str(sock->endpoint) : + coap_session_str(sock->session), + err->ee_type, err->ee_code); +#else /* ! COAP_SERVER_SUPPORT */ + coap_log_debug("* %s: ICMP Error Type: %d, Code: %d\n", + coap_session_str(sock->session), + err->ee_type, err->ee_code); +#endif /* ! COAP_SERVER_SUPPORT */ + return ret; + } + } +#endif /* COAP_IPV4_SUPPORT && IP_RECVERR */ +#if COAP_IPV6_SUPPORT && IPV6_RECVERR + if (cmsg->cmsg_level == SOL_IPV6 && cmsg->cmsg_type == IPV6_RECVERR) { + struct sock_extended_err *err = (struct sock_extended_err *)CMSG_DATA(cmsg); + coap_icmp_info_t *info = (coap_icmp_info_t *)data; + + if (err->ee_origin == SO_EE_ORIGIN_ICMP6 || err->ee_origin == SO_EE_ORIGIN_ICMP) { + info->length = (uint32_t)ret; + info->cmsg_level = cmsg->cmsg_level; + info->ee_origin = err->ee_origin; + info->ee_type = err->ee_type; + info->ee_code = err->ee_code; +#if COAP_SERVER_SUPPORT + coap_log_debug("* %s: ICMP%s Error Type: %d, Code: %d\n", + sock->endpoint ? coap_endpoint_str(sock->endpoint) : + coap_session_str(sock->session), + err->ee_origin == SO_EE_ORIGIN_ICMP6 ? "6" : "", + err->ee_type, err->ee_code); +#else /* ! COAP_SERVER_SUPPORT */ + coap_log_debug("* %s: ICMP%s Error Type: %d, Code: %d\n", + coap_session_str(sock->session), + err->ee_origin == SO_EE_ORIGIN_ICMP6 ? "6" : "", + err->ee_type, err->ee_code); +#endif /* ! COAP_SERVER_SUPPORT */ + return ret; + } + } +#endif /* COAP_IPV6_SUPPORT && IPV6_RECVERR */ + coap_log_debug("MSG_ERRQUEUE: Level %d, Type: %d\n", cmsg->cmsg_level, cmsg->cmsg_type); + } + return -1; +} +#endif /* HAVE_LINUX_ERRQUEUE_H && HAVE_STRUCT_CMSGHDR */ + /* * dgram * return +ve Number of bytes written. @@ -704,6 +820,9 @@ coap_socket_send(coap_socket_t *sock, coap_session_t *session, ssize_t coap_socket_recv(coap_socket_t *sock, coap_packet_t *packet) { ssize_t len = -1; +#if defined(HAVE_LINUX_ERRQUEUE_H) && defined(HAVE_STRUCT_CMSGHDR) + ssize_t ret; +#endif /* HAVE_LINUX_ERRQUEUE_H && HAVE_STRUCT_CMSGHDR */ assert(sock); assert(packet); @@ -725,13 +844,21 @@ coap_socket_recv(coap_socket_t *sock, coap_packet_t *packet) { #ifdef _WIN32 coap_win_error_to_errno(); #endif /* _WIN32 */ + +#if defined(HAVE_LINUX_ERRQUEUE_H) && defined(HAVE_STRUCT_CMSGHDR) + ret = coap_get_icmp_data(sock, packet->payload, COAP_RXBUFFER_SIZE); + if (ret >= 4) { + /* Information about the ICMP diagnostic packet is in packet->payload */ + return -2; + } +#endif /* HAVE_LINUX_ERRQUEUE_H && HAVE_STRUCT_CMSGHDR */ if (errno == ECONNREFUSED || errno == EHOSTUNREACH || errno == ECONNRESET) { /* client-side ICMP destination unreachable, ignore it */ coap_log_warn("** %s: coap_socket_recv: ICMP: %s\n", sock->session ? coap_session_str(sock->session) : "", coap_socket_strerror()); - return -2; + goto error; } if (errno != EAGAIN) { coap_log_warn("** %s: coap_socket_recv: %s\n", @@ -928,9 +1055,9 @@ coap_socket_dgrm_close(coap_socket_t *sock) { #if COAP_SERVER_SUPPORT coap_context_t *context = sock->session ? sock->session->context : sock->endpoint ? sock->endpoint->context : NULL; -#else /* COAP_SERVER_SUPPORT */ +#else /* ! COAP_SERVER_SUPPORT */ coap_context_t *context = sock->session ? sock->session->context : NULL; -#endif /* COAP_SERVER_SUPPORT */ +#endif /* ! COAP_SERVER_SUPPORT */ if (context != NULL) { int ret; struct epoll_event event; diff --git a/src/coap_dgrm_riot.c b/src/coap_dgrm_riot.c index 18282189..38e05b3a 100644 --- a/src/coap_dgrm_riot.c +++ b/src/coap_dgrm_riot.c @@ -67,7 +67,6 @@ coap_socket_send(coap_socket_t *sock, * dgram * return +ve Number of bytes written. * -1 Error error in errno). - * -2 ICMP error response */ ssize_t coap_socket_recv(coap_socket_t *sock, coap_packet_t *packet) { @@ -94,7 +93,7 @@ coap_socket_recv(coap_socket_t *sock, coap_packet_t *packet) { sock->session ? coap_session_str(sock->session) : "", coap_socket_strerror()); - return -2; + goto error; } if (errno != EAGAIN) { coap_log_warn("** %s: coap_socket_recv: %s\n", diff --git a/src/coap_netif.c b/src/coap_netif.c index 83c39dd4..6b446619 100644 --- a/src/coap_netif.c +++ b/src/coap_netif.c @@ -80,6 +80,21 @@ coap_netif_dgrm_read(coap_session_t *session, coap_packet_t *packet) { coap_session_str(session), packet->length, coap_socket_strerror(), keep_errno, session->state); errno = keep_errno; + } else if (bytes_read == -2) { +#ifdef HAVE_LINUX_ERRQUEUE_H + coap_icmp_info_t *info = (coap_icmp_info_t *)packet->payload; + + if (session->last_data_write && session->last_data_write->length >= info->length) { + if (memcmp(session->last_data_write->s, &info[1], info->length) == 0) { + /* There is a match - need to handle failure */ + coap_log_debug("* %s: netif: failed to read % " PRIdS " bytes (%s (%d)) state %d\n", + coap_session_str(session), packet->length, + coap_socket_strerror(), keep_errno, session->state); + return -2; + } + } +#endif /* HAVE_LINUX_ERRQUEUE_H */ + bytes_read = -1; } else if (bytes_read > 0) { coap_ticks(&session->last_rx_tx); memcpy(&session->addr_info, &packet->addr_info, @@ -153,6 +168,12 @@ coap_netif_dgrm_write(coap_session_t *session, const uint8_t *data, coap_log_debug("* %s: netif: sent %4" PRIdS " of %4" PRIdS " bytes\n", coap_session_str(session), bytes_written, datalen); } +#ifdef HAVE_LINUX_ERRQUEUE_H + if (session->type == COAP_SESSION_TYPE_CLIENT) { + coap_delete_str_const(session->last_data_write); + session->last_data_write = coap_new_str_const(data, datalen); + } +#endif /* HAVE_LINUX_ERRQUEUE_H */ return bytes_written; } diff --git a/src/coap_session.c b/src/coap_session.c index 0048265d..6dee3bbb 100644 --- a/src/coap_session.c +++ b/src/coap_session.c @@ -598,6 +598,10 @@ coap_session_mfree(coap_session_t *session) { if (session->psk_hint) coap_delete_bin_const(session->psk_hint); +#ifdef HAVE_LINUX_ERRQUEUE_H + coap_delete_str_const(session->last_data_write); +#endif /* HAVE_LINUX_ERRQUEUE_H */ + #if COAP_SERVER_SUPPORT coap_cache_entry_t *cp, *ctmp; HASH_ITER(hh, session->context->cache, cp, ctmp) { @@ -1109,28 +1113,22 @@ coap_session_disconnected_lkd(coap_session_t *session, coap_nack_reason_t reason q = q->next; } - if (reason != COAP_NACK_ICMP_ISSUE -#if COAP_CLIENT_SUPPORT - || session->session_failed -#endif /* COAP_CLIENT_SUPPORT */ - ) { - while (session->delayqueue) { - q = session->delayqueue; - session->delayqueue = q->next; - q->next = NULL; - coap_log_debug("** %s: mid=0x%04x: not transmitted after disconnect\n", - coap_session_str(session), q->id); - if (q->pdu->type == COAP_MESSAGE_CON) { - coap_handle_nack(session, q->pdu, reason, q->id); - sent_nack = 1; - } - -#if COAP_CLIENT_SUPPORT - session->doing_send_recv = 0; -#endif /* COAP_CLIENT_SUPPORT */ - coap_delete_node_lkd(q); + while (session->delayqueue) { + q = session->delayqueue; + session->delayqueue = q->next; + q->next = NULL; + coap_log_debug("** %s: mid=0x%04x: not transmitted after disconnect\n", + coap_session_str(session), q->id); + if (q->pdu->type == COAP_MESSAGE_CON) { + coap_handle_nack(session, q->pdu, reason, q->id); + sent_nack = 1; } + + coap_delete_node_lkd(q); } +#if COAP_CLIENT_SUPPORT + session->doing_send_recv = 0; +#endif /* COAP_CLIENT_SUPPORT */ #if COAP_CLIENT_SUPPORT if (!sent_nack && session->lg_crcv) { /* Take the first one */ @@ -1143,11 +1141,6 @@ coap_session_disconnected_lkd(coap_session_t *session, coap_nack_reason_t reason /* Unable to determine which request disconnection was for */ coap_handle_nack(session, NULL, reason, 0); } - if (reason == COAP_NACK_ICMP_ISSUE) { - coap_log_debug("***%s: session ICMP issue (%s)\n", - coap_session_str(session), coap_nack_name(reason)); - return; - } coap_log_debug("***%s: session disconnected (%s)\n", coap_session_str(session), coap_nack_name(reason)); #if COAP_SERVER_SUPPORT