-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Various posix.[sg]etsockopt improvements #25085
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
CC @alexrp and @rootbeer - Thoughts on whether this is a reasonable approach? My aim here is Zig software that wants to use these kinds of system interfaces shouldn't have to directly rely on There are other alternative pathways to consider here, perhaps. We could make some minor breaking changes to the current set and get interfaces to unlock at least some of these edge cases. For example, the get call could return a slice of the optval buffer instead of void and assert a returned length less than or equal to the input optval len, for the case that the returned length is less than the buffer input length and the caller needs to know it. And the set call could accept an optional slice as input to handle a case like Even if we did both of those things, Linux We could also try to do something way-fancier with comptime and/or runtime switching on the optname itself, for varying the internal behavior per-option for all the known edge cases, and return some complex struct or tuple that can represent all the odd cases, and even specialize the error return outcomes to the optname as well? I suspect if we tried to go down this path, this would (a) get very unwieldy over time with a ton of nested switches and a ton of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These changes seem reasonable to me. I'm just a drive-by contributor, so take my directional comments here with a large grain of salt: I think the goal of the std.posix layer is to provide posix-y APIs that are familiar, with a bit of Zig safety if it can be squeezed in. If you want to fully Ziggify the socket option APIs with robust safety for all of the options, that probably makes more sense to try in Zig's std.net layer (vs. deviating from POSIX APIs here in the std.posix layer).
The EINVAL
handling in this diff is better, too.
I recommend adding some test cases if you can. Even simple cases that will get an error at runtime are fine, as otherwise the unreachable code won't even get compiled.
lib/std/posix.zig
Outdated
} || UnexpectedError; | ||
|
||
/// Get a socket's options. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe document that getsockopt
is only for socket options that are a well-know size, and getsockoptRaw should be used otherwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in new push (but with new naming)
lib/std/posix.zig
Outdated
/// optlen, which will usually be shorter than the buffer length, and thus | ||
/// would fail the otherwise-useful optlen assertion check in the the regular | ||
/// getsockopt() interface. | ||
pub fn getsockoptRaw(fd: socket_t, level: i32, optname: u32, optval: [*]u8, optlen: *socklen_t) GetSockOptError!void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe call this getsockoptVariable
? Or call this getsockopt
(since its closer to the semantics of the underlying call) and rename the Ziggified wrapper function to getsockoptFixed
? (getsockoptSimple
?). My names aren't obviously better, so feel free to leave as is if you like the Raw
suffix. (I don't see an existing naming convention to follow for simplifying wrapper functions like this.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe call this
getsockoptVariable
?
Yes, this name is the right answer here I think! 🥇
To codify one of the alternative paths - if we're willing to change/break the existing interfaces a bit, and lose a little bit of ergonomics in the get-case, it could look something like this:
The tradeoffs with this approach:
Linux This could also be blended with @rootbeer 's Fixed/Simple suffix as well in the get case, just to clean up some of the negatives above. Basically, on top of the changed get interface above, have a |
Yeah, I tend to think I want this as well. It is a little tricky, as any decent test will inevitably have consequences. But we could, perhaps, find very-trivial cases that are fairly portable? Maybe create a temporary tcp socket (just socket creation, no binding), get |
Well, I was going to add a test, but then I realized that even the most-basic test of the existing New force-push first adds a basic test of set+get for socket options (which fails on Windows), then fixes up the Windows calls, then applies the various fixes I had here before, and then has a final commit tacked on which represents the alternative "best of all worlds" mentioned earlier. I could go either way on that alternative outcome, personally. I think it's a little nicer than the leaving in the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like a solid improvement to me.
If the test on Windows (or other platforms) has more problems, its generally acceptable to file a bug and return errors.SkipZigTest
on the problematic platforms.
lib/std/posix.zig
Outdated
/// options with a known, fixed size, e.g. simple integer values. | ||
pub fn getsockoptFixed(fd: socket_t, level: i32, optname: u32, opt: []u8) GetSockOptError!void { | ||
const returned = try getsockopt(fd, level, optname, opt); | ||
std.debug.assert(returned.len == opt.len); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
L4427 below is a bare assert
(without the std.debug prefix), so I think you can drop that here too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, fixed in upcoming push
lib/std/posix.zig
Outdated
/// optlen, which will usually be shorter than the buffer length, and thus | ||
/// would fail the otherwise-useful optlen assertion check in the | ||
/// getsockoptFixed() interface. | ||
pub fn getsockopt(fd: socket_t, level: i32, optname: u32, opt: []u8) GetSockOptError![]u8 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding a return value here is a bit funky (as it diverges from the POSIX API a bit), but does communicate the result length in a relatively safe way. Given most callers should be able to use getsockoptFixed()
this seems worth the tradeoff to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, in some abstract sense, returning a sub-slice of opt is the meaning of the full POSIX API. But I agree it's funky for the common case. Flipping around from having a Fixed()
variant to having a Variable()
variant in next push instead!
Also, probably fine to un-draft this now? |
After another night to sleep on this: I think since the [also cleaned up what I could from earlier comments, and fixed failing test on macos] |
Note this test fails for me on Windows targets when testing from Linux using -fwine, with errors like: compile test Debug thumb-windows-msvc 1 errors lib/std/c.zig:10717:12: error: dependency on libc must be explicitly specified in the build command pub extern "c" fn getsockopt(sockfd: fd_t, level: i32, optname: u32, noalias optval: ?*anyopaque, noalias optlen: *socklen_t) c_int; I suspect that posix.getsockopt() needs a specific variant using ws2_32 like setsockopt() already has.
getsockopt() needed new code to use ws2_32 like setsockopt() already did. Since I had to research the error values for getsockopt(), I went ahead and did the same for setsockopt() to clean up the error set there as well. Works for me with emulation via -fwine anyways. Now I get a clean run for test-std with: -fqemu -fwine -fwasmtime -Dtest-filter='posix.test.test.sockopt'
In both the set and get cases, EINVAL can happen for reasons other than code-level programmer error for *nix targets as well, and should be a condition that users can catch and handle. Examples are given in the doc comments. getsockopt() ERANGE known case: Linux SO_PEERSEC. There is no documented buffer size that will always be large-enough. If the buffer is too small, getsockopt returns ERANGE and returns an appropriate buffer size via optlen. We can't return this size to the posix.getsockopt() caller without a breaking interface change, but at least with this, they could do some kind of grow-by-X-and-retry loop. While this is better than nothing for such a case, there's still the problem that the returned value's actual length cannot be known without reading the return optlen value, and need not be sentinel-terminated within the opt buffer, either, in the general case. We need some kind of new or changed interface here to support exposing the returned optlen in some way.
getsockoptVariable() is for the edge cases where the caller needs to know the actual data length returned by the system, which may be smaller than the supplied buffer size. Known example cases are Linux's SO_PEERSEC and SO_BINDTODEVICE.
By making setsockopt()'s opt argument optional, we can support the edge case of needing to send null as the optval. Known example is clearing an existing filter via SO_ACCEPTFILTER on supporting BSDs.
LGTM! Hopefully the Zig team likes it too. :) |
Some of these may require some debate or correction, so I split this up into several distinct commits and will mark as draft initially.