James Smith
James Smith

Reputation: 121

Why do we need Value-Result arguments in socket programming? (Clarification required)

I am a little confused when asked a question such as "What is a Value-Result argument and WHY is it needed in Socket Programming?"

I am struggling to fully understand what exactly a value result argument is, despite reading countless pages and other questions on here.

My understanding is that in a value result argument the kernel is able to make changes to the argument passed(because we give a reference/pointer to it, not just its value) and return it to the the process/function that called it. It is both a "value" when the function is called(tells kernel the size of struct so it doesn't write too much for example) and a result when the function returns(how much we actually wrote in the struct).

What I am struggling to answer, is why this is so important in Socket Programming? In particular, when we are dealing with sockaddr Structs and passing their reference AND size over, i.e. accept()

I realize this question may sound somewhat silly, but any clarification on this would be great, so thanks in advance.

Upvotes: 7

Views: 2431

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 597590

You have the correct understanding of what a value-result parameter is. You assign an input value to a variable and pass it by-reference so the function can modify the variable with an output value. This saves having to pass a separate parameter for the output, or change the semantics of the function's return value.

The reason this is needed for sockaddr parameters is because different transports implement different sockaddr_... structures, which have different sizes and layouts (sockaddr_in for IPv4, sockaddr_in6 for IPv6, sockaddr_un for UNIX domain sockets, etc). Most platforms also provide an implementation of the sockaddr_storage structure, which is large enough to hold all other sockaddr_... structs, and is useful when writing multi-transport code.

So, in the case of accept(), you have to pass in the FULL size of the buffer that will receive the client's sockaddr_.... When a new client arrives, accept() will validate that the buffer is large enough to receive the client's actual sockaddr_... data before then populating the buffer, and will return how much of the buffer was actually filled in.

For example, if you know the socket only supports IPv4 (was created as an AF_INET-family socket), then you can use a sockaddr_in as the buffer and sizeof(sockaddr_in) as the buffer size, or you can use a sockaddr_storage as the buffer and sizeof(sockaddr_storage) as the buffer size. In either case, accept() will populate the buffer with a sockaddr_in and return the buffer size as sizeof(sockaddr_in). Same with an IPv6-only socket (created as an AF_INET6-family socket), just with sockaddr_in6 instead.

Now, lets say you have a dual-stack socket that supports both IPv4 and IPv6 (an AF_INET6-family socket with the IPV6_V6ONLY option disabled). You can use a sockaddr_storage as the buffer and sizeof(sockaddr_storage) as the buffer size, and accept() will populate the buffer with either a sockaddr_in or sockaddr_in6 and return an appropriate buffer size, depending on whether an IPv4 or IPv6 client was accepted. You can then read the ss_family field of the sockaddr_storage and type-cast the data to either sockaddr_in for AF_INET or sockaddr_in6 for AF_INET6.

Upvotes: 11

Related Questions