how is the sockaddr struct defined

I am teaching myself socket programming in C.  I understand how to do the stuff I need to get sockets working and cummunicating.  My question is more about how something underlying works.

in most socket functions you have to cast your address structure to a generic pointer to a sockaddr struct.

My question is how does the definition for the sockaddr struct work and what does the compiler do when it comes upon this "(struct sockaddr *)sockaddr_*"

I have looked at socket.h and I find this a transparent union...  I dont understand much of the transparent union but I am trying to see how that union can be used to cast different size/variable size address structures to a generic sockaddr struct.  

Any one have an idea?
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

evilrixSenior Software Engineer (Avast)Commented:
evilrixSenior Software Engineer (Avast)Commented:
BTW ^^^ as a resource, Beej's Guide to Network Programming is probably about the best on the web.
justinl525Author Commented:
That is a great website but I am looking for a explanation of how the attached code fragment from socket.h relates to the casting syntax I mentioned.

/* This is the type we use for generic socket address arguments.
  62:    With GCC 2.7 and later, the funky union causes redeclarations or
  63:    uses with any of the listed types to be allowed without complaint.
  64:    G++ 2.7 does not support transparent unions so there we want the
  65:    old-style declaration, too.  */
  66: #if defined __cplusplus || !__GNUC_PREREQ (2, 7) || !defined __USE_GNU
  67: # define __SOCKADDR_ARG         struct sockaddr *__restrict
  68: # define __CONST_SOCKADDR_ARG   __const struct sockaddr *
  69: #else
  70: /* Add more `struct sockaddr_AF' types here as necessary.
  71:    These are all the ones I found on NetBSD and Linux.  */
  72: # define __SOCKADDR_ALLTYPES \
  73:   __SOCKADDR_ONETYPE (sockaddr) \
  74:   __SOCKADDR_ONETYPE (sockaddr_at) \
  75:   __SOCKADDR_ONETYPE (sockaddr_ax25) \
  76:   __SOCKADDR_ONETYPE (sockaddr_dl) \
  77:   __SOCKADDR_ONETYPE (sockaddr_eon) \
  78:   __SOCKADDR_ONETYPE (sockaddr_in) \
  79:   __SOCKADDR_ONETYPE (sockaddr_in6) \
  80:   __SOCKADDR_ONETYPE (sockaddr_inarp) \
  81:   __SOCKADDR_ONETYPE (sockaddr_ipx) \
  82:   __SOCKADDR_ONETYPE (sockaddr_iso) \
  83:   __SOCKADDR_ONETYPE (sockaddr_ns) \
  84:   __SOCKADDR_ONETYPE (sockaddr_un) \
  85:   __SOCKADDR_ONETYPE (sockaddr_x25)
  87: # define __SOCKADDR_ONETYPE(type) struct type *__restrict __##type##__;
  88: typedef union { __SOCKADDR_ALLTYPES
  89:               } __SOCKADDR_ARG __attribute__ ((__transparent_union__));
  90: # undef __SOCKADDR_ONETYPE
  91: # define __SOCKADDR_ONETYPE(type) __const struct type *__restrict __##type##__;
  92: typedef union { __SOCKADDR_ALLTYPES
  93:               } __CONST_SOCKADDR_ARG __attribute__ ((__transparent_union__));
  94: # undef __SOCKADDR_ONETYPE
  95: #endif

Open in new window

Cloud Class® Course: Python 3 Fundamentals

This course will teach participants about installing and configuring Python, syntax, importing, statements, types, strings, booleans, files, lists, tuples, comprehensions, functions, and classes.

evilrixSenior Software Engineer (Avast)Commented:
They all have the same binary footprint, which is commonised as sockaddr. This is because C doesn't support function overloading so all the standard socket functions take a type of sockaddr *, which is cast from the sockaddr of the type your using. The socket functions also take options to tell the function what type the sockaddr actually represents.

It's just a C hack of getting around lack of function overloading and generic functions in C.
There are numerous socket tutorials, which I'm sure you have seen since you are self-studying. But just to make sure, here's a set of short set socket instructional videos that have accompanying source code:

From what I read in this link, it appears that transparent_union is not standard C. As the following link indicates - "The language feature is an orthogonal extension to C89, C99, Standard C++ and C++98, and has been implemented to facilitate porting programs originally developed with GNU C."

"Whenever a transparent union is the type of a function parameter and that function is called, the transparent union can accept an argument of any type that matches that of one of its members without an explicit cast."

Looking at the foo() example, what we see is that foo requires a union object; yet what is permitted thanks to "transparent union" is a union member type instead. Interesting idea - why didn't I think of that!

So, it appears to me that the purpose is to be able to take legacy code and not have to deal with stronger C compiler type rules.

In your code, __SOCKADDR_ARG is defined in a typedef and it represents a union of pointers. And since all pointers have the same length, that rule about transparent unions (from the link) is satisfied. And since some of the socket calls (e.g., bind) takes in pointers to different types, and since today's modern compilers have stronger type checking than yesteryear, then by creating this transparent union, any non-casted legacy code can now compile without having to go in and cast everything to the correct pointer type.


I'm using cygwin which have no references to ONETYPE, most likely because cygwin does not support transparent unions.


>> how does the definition for the sockaddr struct work and ...
First, some background to make sure we're on the same page:

From the various links (or you already knew) the struct sockaddr is defined to be 16 bytes long:
    typedef uint16_t sa_family_t;
    struct sockaddr {
        sa_family_t   sa_family;       /* address family, AF_xxx      */
        char              sa_data[14];   /* 14 bytes of protocol address      */

Then there's struct sockaddr_in (for your Internet (IP) socket address) that is also 16 bytes long. My sockaddr_in is 8 bytes + 8 bytes of padding to get to a full 16 bytes. Note that the 8 bytes of padding is not really needed (says Stevens in "Unix Network Programming").
(Note: I wrote the following and then decided to learn about transparent_union, so the example I give probably may not appear in your code since typecasting a bind argument should not be necessary thanks to the transparent_union.)

>> ... and what does the compiler do when it comes upon this "(struct sockaddr *)sockaddr_*" ?
Well, what the compiler does depends on more details on where your casting is used. But, here's a typical casting that follows the pattern in your question. (In other contexts, a pointer cast allows you to access members that are appropriate to the casted type.)

struct sockaddr_in my;  ...
bind(sock, (struct sockaddr *) &my, sizeof(my));
At the caller level, the cast is merely allowing the compiler to proceed since bind profile is:
    int bind (int, const struct sockaddr *__my_addr, socklen_t __addrlen);

Now we know that sockaddr_in is one of the pseudo-union members of sockaddr, but the compiler needs to know that we are accepting this conversion. All the compiler will do for bind's 2nd argument is pass a pointer (usually 4 bytes) to the "my" variable.

Now bind gets this 4 byte pointer. What bind does is dependent on how the caller set sa_family. If it is set to AF_INET, then bind knows that the sockaddr_in structure was passed in, and not some other. Needless to say, it is up to the programmer to keep things consistent.

If it is AF_LOCAL or AF_UNIX, then bind knows that the sockaddr_un struct was passed in. Notice that we are saying that we are passing in a pointer to a 16 byte struct, but we're fibbing a bit, since its struct looks that shown below. In this case bind now knows that the struct is not 16 bytes but 110 bytes.

Now, why didn't the socket designers just create a union of all the different sockaddr types? Because, then the size of every sockaddr related structure becomes the size of the largest member. From the code below it would be at least 110 bytes for the unix structure. If struct sockaddr were defined today, I believe that it would be defined as a union since speed and resources are not the problem of yesteryear.

/* POSIX requires only at least 100 bytes */
#define UNIX_PATH_LEN   108
struct sockaddr_un {
  sa_family_t       sun_family;              /* address family AF_LOCAL/AF_UNIX */
  char               sun_path[UNIX_PATH_LEN]; /* 108 bytes of socket address     */
justinl525Author Commented:
Dude that is what I was looking for... excellent explanation!

One last question that really started me on the this little quest...

you said --> "If it is AF_LOCAL or AF_UNIX, then bind knows that the sockaddr_un struct was passed in. Notice that we are saying that we are passing in a pointer to a 16 byte struct, but we're fibbing a bit, since its struct looks that shown below. In this case bind now knows that the struct is not 16 bytes but 110 bytes."

does bind know that its a pointer to a 110 byte pointer instead of a 16 byte becasue of this line then?

__SOCKADDR_ONETYPE (sockaddr_un)

Open in new window

No. the __SOCKADDR_ONETYPE is a macro that via preprocessor converts the arg, sockaddr_un, into a pointer to sockaddr_un. Right?

Now, think about what bind sees. I mean, think about what you would have to do if you were required to write the code for bind() using the standard bind() api.

All you see for the 2nd arg is a lousy 4 byte pointer. You know that it can mean one of many different data structures but you don't have a clue by directly looking at the passed in pointer. It has that all-encompassing const struct sockaddr * data type, which can mean anything. About the only thing you know is that what you are looking at is NOT a pointer pointing to a sockaddr data type. Ironic, isn't it.

This might actually have been clearer if the bind 2nd argument were just an amorphous void * pointer. Then you know that the bind is like a generic function that can handle multiple types. (Just like qsort has several void * in its api.)

But when the bind() dereferences the pointer, it looks at the first short field to find out what kind of structure is being sent to it. If, for example, it sees AF_UNIX, then you could have a switch to have something like:

struct sockaddr_un *  p_afunix = (struct sockaddr_un *) p_2nd_arg; where p_2nd_arg is the dummy name you gave to the 2nd arg in the bind function.

Now, p_afunix using the -> operator can properly reference the struct sockaddr_un member elements in its structure; and since you would likely have bind delegate the processing to a unix aware type bind function, there is now no problem with regards to what size the structure is - it is a unix structure, so it is 110 bytes.

Simple, eh? Not really. I had never heard of this transparent union attribute before, so it was interesting to sort things out. Summary: the transparent union attribute is a non-standard C extension to allow legacy code that is not properly typed at the bind caller level to compile without having the need to go in and start type-casting everything (which adds no functional value at the bind caller level). This allows the pointer to the start of one of the data structures to be passed in. It is the data in the struct that tells bind how it should cast the pointer so that it can properly know the names of the members of the data structure, and so that it knows the size of the data structure.

On size, I see there are a lot of different data structure in your list. And for IP and unix, the size is well-known and fixed. But possibly for some other structures (I am not sure about this), the length could conceivably be data dependent. If this is the case, then there would have to be within the data structure itself discriminators that would indicate the size.


Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
justinl525Author Commented:
Awesome explanation!  Wish they all could be this detailed!
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today

From novice to tech pro — start learning today.