// A C reproducer for the issue reported in Java https://bugs.openjdk.org/browse/JDK-8279920. // This reproducer shows that, on macosx, even when SO_OOBINLINE is disabled on the socket, the socket // continues to read()/recv() out-of-band data sent from the server side of the socket (which // too has SO_OOBINLINE disabled). This only happens when using non-loopback address. When // loopback address is used, the read()/recv() correctly exclude the out-of-band data. // Test uses IPv4 addresses. #include #include #include #include #include #include #include #include #include #include #define TCP_PROTOCOL 6 #define FAIL 1 #define BUF_SIZE 42 typedef struct sockaddr_in SOCKADDR_IN; void send_normal_data(const int dest_sock_fd) { // send some data send(dest_sock_fd, "12345", 5, 0); } void send_oob_data(const int dest_sock_fd) { // send 1 byte of out-of-band data (MSG_OOB) send(dest_sock_fd, "U", 1, MSG_OOB); } int create_socket() { const int socket_fd = socket(PF_INET, SOCK_STREAM, TCP_PROTOCOL); if (socket_fd == -1) { fprintf(stderr, "socket creation failed, errno=%d - %s\n", errno, strerror(errno)); exit(FAIL); } return socket_fd; } // verifies that SO_OOBINLINE socket option on the socket is disabled (i.e. set to 0, implying no out-of-band support) void assert_oobinline_disabled(const int sock_fd) { int val = -1; void *arg; arg = (void *) &val; socklen_t arglen = sizeof(int); if (getsockopt(sock_fd, SOL_SOCKET, SO_OOBINLINE, arg, &arglen) != 0) { fprintf(stderr, "failed to get SO_OOBINLINE on socket, fd=%d, errno=%d - %s\n", sock_fd, errno, strerror(errno)); exit(FAIL); } if (val != 0) { fprintf(stderr, "unexpected SO_OOBINLINE value=%d on socket, fd=%d, expected=0\n", val, sock_fd); exit(FAIL); } // verified that SO_OOBINLINE is 0 for socket } // returns a non-loopback ipv4 local address of the host to which the server will bind to in_addr_t determine_ipv4_nonloopback_hostaddr() { struct ifaddrs *ifaddrs = NULL; if (getifaddrs(&ifaddrs) != 0) { fprintf(stderr, "failed to getifaddrs, errno=%d - %s\n", errno, strerror(errno)); exit(FAIL); } struct ifaddrs *iter = ifaddrs; for (; iter != NULL; iter = iter->ifa_next) { if (iter->ifa_addr != NULL) { if (iter->ifa_name[0] != '\0') { if ((iter->ifa_flags & IFF_LOOPBACK) != 0) { // skip loopback address continue; } const int family = iter->ifa_addr->sa_family; if (family != AF_INET) { // we are only interested in IPv4 continue; } // found a non-loopback ipv4 interface SOCKADDR_IN addr = *(SOCKADDR_IN *) iter->ifa_addr; return addr.sin_addr.s_addr; } } } // fail fprintf(stderr, "failed to determine a non-loopback IPv4 bind address"); exit(FAIL); } // accept()s a connection from a client and then sends data on the connection void *server_handle_connection(void *fd) { fprintf(stderr, "connection accept() thread started\n"); const int *server_sock_fd = (int *) fd; fprintf(stderr, "accepting connection on fd=%d\n", *server_sock_fd); SOCKADDR_IN accepted_sockaddr; socklen_t sock_len = sizeof(SOCKADDR_IN); const int accepted_conn_fd = accept(*server_sock_fd, (struct sockaddr *) &accepted_sockaddr, &sock_len); if (accepted_conn_fd == -1) { fprintf(stderr, "failed to accept a socket connection, errno=%d - %s\n", errno, strerror(errno)); pthread_exit(NULL); } const char *client_conn_addr = inet_ntoa(accepted_sockaddr.sin_addr); fprintf(stderr, "accepted connection from %s:%d\n", client_conn_addr, accepted_sockaddr.sin_port); // verify that the SO_OOBINLINE is (still) disabled assert_oobinline_disabled(*server_sock_fd); assert_oobinline_disabled(accepted_conn_fd); // send some normal data fprintf(stderr, "server sending normal data\n"); send_normal_data(accepted_conn_fd); // now send out-of-band data fprintf(stderr, "server sending out-of-band data\n"); send_oob_data(accepted_conn_fd); // now send additional normal data send_normal_data(accepted_conn_fd); fprintf(stderr, "server completed sending all data\n"); // cleanup and return close(accepted_conn_fd); pthread_exit(NULL); } int main(int argc, char **argv) { int use_loopback = 0; if (argc == 2 && strcmp("loopback", argv[1]) == 0) { use_loopback = 1; } const int disable_oobinline = 0; // create a socket which will be used to act as a server const int server_sock_fd = create_socket(); fprintf(stderr, "created server socket, fd=%d\n", server_sock_fd); // disable out-of-band buf on the server socket if (setsockopt(server_sock_fd, SOL_SOCKET, SO_OOBINLINE, &disable_oobinline, sizeof(int)) != 0) { fprintf(stderr, "failed to set SO_OOBINLINE on server socket, fd=%d, errno=%d - %s\n", server_sock_fd, errno, strerror(errno)); exit(FAIL); } SOCKADDR_IN bind_addr; bind_addr.sin_port = 0; bind_addr.sin_family = AF_INET; if (use_loopback) { bind_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); fprintf(stderr, "using loopback address for test\n"); } else { // determine a non-loopback ipv4 address on the current host to which we will bind the server and listen // for connection bind_addr.sin_addr.s_addr = determine_ipv4_nonloopback_hostaddr(); fprintf(stderr, "using non-loopback address for test\n"); } // bind the server socket (to an ephemeral port) const char *bind_addr_host = inet_ntoa(bind_addr.sin_addr); fprintf(stderr, "attempting to bind to %s\n", bind_addr_host); const int bound = bind(server_sock_fd, (const struct sockaddr *) &bind_addr, sizeof(SOCKADDR_IN)); if (bound != 0) { fprintf(stderr, "failed to bind socket, errno=%d - %s\n", errno, strerror(errno)); exit(FAIL); } // determine the bound address (which includes the allocated port) SOCKADDR_IN server_bound_addr; socklen_t sock_len = sizeof(SOCKADDR_IN); if (getsockname(server_sock_fd, (struct sockaddr *) &server_bound_addr, &sock_len) != 0) { fprintf(stderr, "failed to determine bound address of socket, fd=%d, errno=%d - %s\n", server_sock_fd, errno, strerror(errno)); exit(FAIL); } // listen on the server socket const int backlog = 5; if (listen(server_sock_fd, backlog) != 0) { fprintf(stderr, "failed to listen on socket, errno=%d - %s\n", errno, strerror(errno)); exit(FAIL); } const char *server_host = inet_ntoa(server_bound_addr.sin_addr); fprintf(stderr, "server is listening at %s:%d\n", server_host, server_bound_addr.sin_port); // create a client socket const int socket_fd = create_socket(); fprintf(stderr, "created client socket, fd=%d\n", socket_fd); // disable out-of-band data on the client socket if (setsockopt(socket_fd, SOL_SOCKET, SO_OOBINLINE, &disable_oobinline, sizeof(int)) != 0) { fprintf(stderr, "failed to set SO_OOBINLINE on client socket, errno=%d - %s\n", errno, strerror(errno)); exit(FAIL); } // start a (server) thread which will accept() client connection pthread_t server_thread; if (pthread_create(&server_thread, NULL, &server_handle_connection, (void *) &server_sock_fd) != 0) { fprintf(stderr, "failed to launch server thread to accept connection, errno=%d - %s\n", errno, strerror(errno)); exit(FAIL); } // connect to the server socket fprintf(stderr, "attempting connection to %s:%d\n", server_host, server_bound_addr.sin_port); if (connect(socket_fd, (const struct sockaddr *) &server_bound_addr, sock_len) != 0) { fprintf(stderr, "failed to connect to server, errno=%d - %s\n", errno, strerror(errno)); exit(FAIL); } fprintf(stderr, "connected to server\n"); // verify the out-of-band data is still disabled assert_oobinline_disabled(socket_fd); void *serverRetVal; if (pthread_join(server_thread, &serverRetVal) != 0) { fprintf(stderr, "failed to join on server thread, errno=%d - %s\n", errno, strerror(errno)); exit(FAIL); } // server has completed sending the data, now start reading the entire data fprintf(stderr, "reading data on client\n"); char buf[BUF_SIZE]; ssize_t num_rcvd = 0; ssize_t total_rcvd = 0; // Even replacing read(socket_fd, (buf + num_rcvd), BUF_SIZE) with // recv(socket_fd, (buf + num_rcvd), BUF_SIZE, 0) results receiving the // unexpected/unwanted out-of-band data while ((num_rcvd = read(socket_fd, (buf + num_rcvd), BUF_SIZE)) != 0) { if (num_rcvd == -1) { int err = errno; if (err == EAGAIN) { // continue continue; } fprintf(stderr, "error while reading data from server, errno=%d - %s\n", err, strerror(errno)); exit(FAIL); } total_rcvd += num_rcvd; fprintf(stderr, "received %ld bytes from server\n", num_rcvd); } buf[total_rcvd] = '\0'; // done receiving the data fprintf(stderr, "closing sockets\n"); close(socket_fd); close(server_sock_fd); // verify we got the correct data - it should be "1234512345" const char *expected = "1234512345"; if (total_rcvd != strlen(expected)) { fprintf(stderr, "ERROR: expected: %s but received: %s\n", expected, buf); exit(FAIL); } fprintf(stderr, "SUCCESS: completed successfully, expected: %s, received: %s\n", expected, buf); return 0; }