Skip to content

Conversation

@Raekye
Copy link

@Raekye Raekye commented Sep 22, 2025

IO::Endpoint::Wrapper.bind always does bind and listen, and handles Errno::EOPNOTSUPP, for example for UDP sockets, for which listen is not applicable. However, in some environments, such as in a container, the error may be mapped to EACCES, which as is, is not handled, basically causing the upper level program (e.g. a RubyDNS server) to crash.

This patch ignores Errno::EACCES if it was a UDP socket, since the listen call was meaningless in the first place.

Example C program to compile and run in a container:

// hi.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 5300
#define BACKLOG 4096

int main(void) {
        int sockfd;
        struct sockaddr_in addr;

        sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd < 0) {
                perror("socket");
                exit(EXIT_FAILURE);
        }

        memset(&addr, 0, sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = htonl(INADDR_ANY);
        addr.sin_port = htons(PORT);

        if (bind(sockfd, (struct sockaddr*) &addr, sizeof(addr)) < 0) {
                perror("bind");
                close(sockfd);
                exit(EXIT_FAILURE);
        }

        if (listen(sockfd, BACKLOG) < 0) {
                perror("listen");
                close(sockfd);
                exit(EXIT_FAILURE);
        }

        printf("Cool!\n");
        return 0;
}
podman run --rm -it --volume .:/root:z fedora:42 bash
dnf install gcc
gcc hi.c
./a.out

On my host system (Fedora 42), it prints "listen: Operation not supported" (EOPNOTSUPP) as already handled, but in both a fedora:42 or ubuntu:25.10 container, it prints "listen: Permission denied" (EACCES).

Author note: There currently defined socket types under man 2 socket are SOCK_{STREAM,DGRAM,SEQPACKET,RAM,RDM} and the obsolute SOCK_PACKET. man 2 listen indicates that it is only applicable to a socket of type SOCK_STREAM or SOCK_SEQPACKET. This comment in the existing code explains why Wrapper#bind always calls listen, and handles EOPNOTSUPP after:

Generally speaking, bind/listen is a common pattern, but it's not applicable to all socket types. We ignore the error if it's not supported as the alternative is exposing this upstream, which seems less desirable than handling it here. In other words, bind in this context means "prepare it to accept connections", whatever that means for the given socket type.

The code in this PR tries to be minimal in scope/change; it follows the existing control flow, by catching EACCES (and checking if the socket type was DGRAM), and it doesn't handle EACCES for the other inapplicable socket types, since I figure realistically only DGRAM is relevant for users of this library, and also I haven't tested that the problem manifests in a container for those other socket types.

However, especially since EOPNOTSUPP is not reliable/comprehensive, it's arguablly better to wrap listen under something like if local_address.socktype == ServerSocket::SOCK_STREAM || local_address.socktype == ServerSocket::SOCK_SEQPACKET, matching the documentation for listen(2), and removing the need for begin...rescue.

Types of Changes

  • Bug fix.

Contribution

`IO::Endpoint::Wrapper.bind` always calls `Socket#listen`,
and handles `Errno::EOPNOTSUPP`, for example for UDP sockets,
for which the `listen` call is not applicable (see `man 2 listen`).
However, in some environments, such as in a container,
the error may be mapped to `EACCES`,
which can be verified with a simple C program.
This patch ignores `Errno::EACCES` if it was a UDP socket,
since the `listen` call was meaningless in the first place.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant