Net.zig: passing in an undefined net.Address for posix.accept's client addr

I have the following code. Calling posix.accept (on linux) with the undefined client variables results in accept failing with SocketNotListening.

// IMPORTANT: the variable named, "socket" has already been 
// succesfully bound to a local address and listen was called
// without a failure. This was confirmed via strace.

// all of these items live in the same function (i.e., are located in the stack).
var client_addr: net.Address = undefined;
var client_addr_len: net.Address = undefined;
...
...
// this fails with "SocketNotListening" when I set the client_* variables
// to undefined.
const conn = try posix.accept(socket, &client_addr.any, &client_addr_len, 0); 

My assumptions were as follows:

  • Setting a variable as undefined allocates the required space (i.e., stack in this case).
  • The values are set to 0xaa as defined by the documentation.
    • *for debug mode

Now, I realize you’re not supposed to use the values when they’re set to undefined, but I figured that the linux accept syscall would set the client_addr and as such I was free to leave it as undefined.

I guess my question boils down to: is my understanding of undefined correct? Does this issue arise from linux’s accept syscall interpreting the garbage values in a way that I hadn’t expected?

The fix was as simple as: var client_addr: std.net.Address = .{ .in = .{ .sa = .{ .port = 9090, .addr = 0 } } };.

But I’m still confused where my misunderstanding is? Is it in the undefined semantics? Is it in the linux syscall?

It also begs the question of whether we can add better error logging. In this case the accept syscall returned EINVAL (Invalid Argument), but I went down the wrong rabbit hole since the returned error was “SocketNotListening”.

I appreciate all the help. I know this one was on me, just looking to better my Zig understanding!

If better logging can be added, I’m more than happy to dig into the std lib source code. But want to get other people’s thoughts. I’m not as familiar as I should be with syscalls so I’m not sure if this is an issue of, “linux doesn’t provide enough information to differentiate between the different issues”.

1 Like

var is allocating the local stack strorage for the type (net.Address).
The documentation says about undefined:

undefined means “Not a meaningful value. Using this value would be a bug. The value will be unused, or overwritten before being used.”


std.posix.accept expects a pointer to a sockaddr structure and its size.
net.Address is a union that may contain the required sockaddr.
As third argument, a pointer to 0xaaaaaaaa values is passed for sockaddr (&client_addr_len) instead of a pointer to the sockaddr size; since sockaddr does not know a sockaddr of this huge size, it returns an error.

2 Likes

I see… makes sense. Thanks for the help.

1 Like

The type of the third arguemnt is wrong:. The len arguemnt is usually just some int type under the hood (socklen_t is u32 in this case). you want to pass in &posix.socklen_t. usually you set it to sizeof(sockaddr) but the zig api has it has optional

edit looking at the source i have no idea why it is optional.

you want

var size:: socklen_t  = @sizeOf(net.Address.any);
//...
accept(socket, &ddd.any, &size, 0);

as long as addr.any is the C sockaddr_t type.

1 Like

I adopted the following by looking at tests in the standard library:

var client_addr = net.Address.initIp4(.{0,0,0,0}, 0);
var client_addr_len = client_addr.getOsSockLen();
conn = try posix.accept(socket, &client_addr.any, &client_addr_len, 0);
3 Likes

Whoops, typo in the post. I had posix.socketlen_t in my code (or the relevant posix type in the func declaration). Thanks for the clarification, definitely clears things up.

Just as a secondary question that I’m still working through. Isn’t the whole point of that third argument (client_addr_len) to act as a return value (i.e., a remnant of the C-syscall since they didn’t have multi-value return in the modern-sense)? Why would it’s being set to 0xaa…aa impact the accept call if the accept function is meant to fill that value for us (or is it doing both? or not filling it at all?).

It would seem to me that if it weren’t acting as a return value, then why wasn’t that defined as a normal int (or usize) like in functions like bind?

Just a little confused from a semantics point of view.

Edit: I realize this isn’t so much a zig question and more a linux syscall question, but I digress.

Ahhh I see! I’m coming from Go and this is my first time working with the socket syscalls… it’s been a very eye opening experience to say the least :smile:.

Thanks!

1 Like

Interestingly enough, it’s both an input and an output argument! The OS needs the size of the incoming address structure (it could be IPv4, IPv6, etc. hence different sizes) and then it writes the size of the actually accepted client address. I believe there are quite a few syscal related functions that behave this same way. As a tip, I find it helpful to lookup these functions’ man pages at the POSIX level which tend to clearly explain what everything does: accept

3 Likes

The addrlen argument is a value-result argument: the caller must initialize it to contain the size (in bytes) of the structure pointed to by addr; on return it will contain the actual size of the peer address. The returned address is truncated if the buffer provided is too small; in this case, addrlen will return a value greater than was supplied to the call.

I see it now. I wasn’t aware we were doing that in C - ha! I appreciate the pointers (pun intended :joy:).

This community’s help has been nothing short of tremendous (your allocation/initialization post was very helpful), hope to give back some day lol. Thanks to you all!

3 Likes

It is both an input and output argument.
From the man page:

The addrlen argument is a value-result argument: the caller must initialize it to contain the size (in bytes) of the structure pointed to by addr; on return it will contain the actual size of the peer address.
The returned address is truncated if the buffer provided is too small; in this case, addrlen will return a value greater than was supplied to the call.

EDIT: sorry was already answered

2 Likes