Skip to content

std.posix.send with a UDP socket leads to unexpected errno but this errno is to be expected #20219

@gaultier

Description

@gaultier

Zig Version

0.13.0 (but also reproduced with 0.12.0)

Steps to Reproduce and Observed Behavior

Send with a UDP socket when the other end is not listening:

const std = @import("std");

pub fn main() !void {
    const socket = try std.posix.socket(std.posix.AF.INET, std.posix.SOCK.DGRAM, 0);
    const address = std.net.Address.parseIp4("0.0.0.0", 9999) catch unreachable;
    try std.posix.connect(socket, &address.any, address.getOsSockLen());

    var i: usize = 0;
    while (i < 2) : (i = i + 1) {
        _ = try std.posix.send(socket, "hello", 0);
    }
}

When we run it we get:

$ zig run src/main.zig
unexpected errno: 111
/snap/zig/11625/lib/std/debug.zig:197:31: 0x106778f in dumpCurrentStackTrace (main)
        writeCurrentStackTrace(stderr, debug_info, io.tty.detectConfig(io.getStdErr()), start_addr) catch |err| {
                              ^
/snap/zig/11625/lib/std/posix.zig:7320:40: 0x10399be in unexpectedErrno (main)
        std.debug.dumpCurrentStackTrace(null);
                                       ^
/snap/zig/11625/lib/std/posix.zig:6029:49: 0x103a37f in sendto (main)
            else => |err| return unexpectedErrno(err),
                                                ^
/snap/zig/11625/lib/std/posix.zig:6059:18: 0x10369a6 in send (main)
    return sendto(sockfd, buf, flags, null, 0) catch |err| switch (err) {
                 ^
/home/pg/scratch/zig-udp-repro/src/main.zig:10:31: 0x10360de in main (main)
        _ = try std.posix.send(socket, "hello", 0);
                              ^
/snap/zig/11625/lib/std/start.zig:524:37: 0x1035ea5 in posixCallMainAndExit (main)
            const result = root.main() catch |err| {
                                    ^
/snap/zig/11625/lib/std/start.zig:266:5: 0x10359c1 in _start (main)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
error: Unexpected
/snap/zig/11625/lib/std/posix.zig:7322:5: 0x10399c7 in unexpectedErrno (main)
    return error.Unexpected;
    ^
/snap/zig/11625/lib/std/posix.zig:6029:27: 0x103a38f in sendto (main)
            else => |err| return unexpectedErrno(err),
                          ^
/snap/zig/11625/lib/std/posix.zig:6069:21: 0x1036a2f in send (main)
        else => |e| return e,
                    ^
/home/pg/scratch/zig-udp-repro/src/main.zig:10:13: 0x1036100 in main (main)
        _ = try std.posix.send(socket, "hello", 0);
            ^

When we strace it we see we get ECONNREFUSED on the second send (why not also on the first one, I don't know):

socket(AF_INET, SOCK_DGRAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(9999), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
sendto(3, "hello", 5, 0, NULL, 0)       = 5
sendto(3, "hello", 5, 0, NULL, 0)       = -1 ECONNREFUSED (Connection refused)

This is not Zig specific, we can reproduce the same with the equivalent C program:

#include <assert.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdio.h>
#include <sys/socket.h>

int main() {
  const int sock = socket(AF_INET, SOCK_DGRAM, 0);
  assert(sock > 0);

  struct sockaddr_in addr = {.sin_family = AF_INET,
                             .sin_port = htons(9999),
                             .sin_addr = {.s_addr = 0}};
  assert(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == 0);

  for (int i = 0; i < 2; i++) {
    const int res = send(sock, "hello", 5, 0);
    if (res == -1)
      printf("%d\n", errno);
  }
}

And the strace output is identical:

socket(AF_INET, SOCK_DGRAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(9999), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
sendto(3, "hello", 5, 0, NULL, 0)       = 5
sendto(3, "hello", 5, 0, NULL, 0)       = -1 ECONNREFUSED (Connection refused)

I am running Linux 6.5.0, Ubuntu, if that matters.

Expected Behavior

Linux returns ECONNREFUSED from the sendto syscall for a UDP socket when the other end is not listening, which should be expected, but the standard library treats it as unexpected.

Although this error code is not listed on the man pages for send(2) and sendto(2), I suggest that it should be added to the list of 'expected' errors since it happens in practice.

I hope I did not miss anything and that I am indeed setting up the socket correctly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugObserved behavior contradicts documented or intended behaviorcontributor friendlyThis issue is limited in scope and/or knowledge of Zig internals.standard libraryThis issue involves writing Zig code for the standard library.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions