IPv4 socket Address structure

This structure, named sockaddr_in, is the one you will use most often for standard Internet programming. It is defined in the <netinet/in.h> header.

struct in_addr {
  in_addr_t s_addr; /* 32-bit IPv4 address */
                    /* network byte ordered */
};
struct sockaddr_in {
  uint8_t sin_len;         /* length of structure (16) */
  sa_family_t sin_family;  /* AF_INET */
  in_port_t sin_port;      /* 16-bit TCP or UDP port number */
                    /* network byte ordered */
  struct in_addr sin_addr; /* 32-bit IPv4 address */
                           /* network byte ordered */
  char sin_zero[8];        /* unused */
};
  • sin_family: Always set to AF_INET for IPv4.
  • sin_port: The 16-bit port number (e.g., 80 for Web). Important: This must be stored in network byte order (Big Endian).
  • sin_addr: The 32-bit IPv4 address. This is also stored in network byte order. The address is actually stored inside a smaller structure called in_addr, which contains just one field: s_addr. So, to access the address, you type serv.sin_addr.s_addr.
  • sin_zero: An unused 8-byte field. We always fill this with zeros (usually by clearing the whole structure with bzero or memset first). Its purpose is to make the structure exactly 16 bytes long, which was a requirement for older protocols.

The Generic Socket Address Structure

Socket functions (like bind and connect) need to work with any protocol (IPv4, IPv6, Unix Domain, etc.). However, C did not have a “generic” pointer type (like void *) when these functions were originally written. To solve this, the designers created a generic structure: struct sockaddr.

struct sockaddr {
  uint8_t sa_len;
  sa_family_t sa_family; /* address family: AF_xxx value */
  char sa_data[14];      /* protocol-specific address */
};

Note

Whenever you call a socket function, you must cast your specific pointer (like struct sockaddr_in *) to a generic pointer (struct sockaddr *).

The IPv6 Socket Address Structure

For the modern Internet protocol (IPv6), we use sockaddr_in6, defined in <netinet/in.h>

struct in6_addr {
  uint8_t s6_addr[16]; /* 128-bit IPv6 address */
};
#define SIN6_LEN /* required for compile-time tests */
struct sockaddr_in6 {
  uint8_t sin6_len; /* length of this struct (28) */
  sa_family_t sin6_family; /* AF_INET6 */
  in_port_t sin6_port; /* transport layer port# */
  uint32_t sin6_flowinfo; /* flow information, undefined */
  struct in6_addr sin6_addr; /* IPv6 address */
  uint32_t sin6_scope_id; /* set of interfaces for a scope */
};
  • It is larger (28 bytes) to accommodate the massive 128-bit IPv6 address.
  • sin6_family: Always set to AF_INET6.
  • sin6_addr: The 128-bit IPv6 address.
  • sin6_flowinfo: Used for traffic class and flow labeling (advanced QoS features).
  • sin6_scope_id: Used for scoped addresses (like link-local addresses), identifying which network interface the address belongs to.

New Generic Socket Address Structure

  • Unlike the old struct sockaddr, this structure is large enough to hold any socket address supported by the system (IPv4, IPv6, or others).
  • This is useful when you don’t know what kind of address you’ll receive (e.g., writing a server that handles both IPv4 and IPv6). You can declare a variable of this type, receive the address into it, and then check the ss_family field to cast it to the correct type later.