Description
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.