Network Sockets Concepts

Byte-Ordering

TCP/IP has an universal standard to allow communication between any platforms. Thus, it's necessary to have method arranging information so that big-endian and little-endian machines can understand each other. On platforms where data is already correctly ordered, the functions do nothing and are frequently macro'd into empty statements. Byte-ordering functions should always be used as they do not impact performance on systems that are already correctly ordered and they promote code portability.

BSDµC/TCPIP FunctionsDescription
htonl NET_UTIL_HOST_TO_NET_32 Convert 32-bit integer values from network-order to CPU host-order.
htons() NET_UTIL_HOST_TO_NET_16() Convert 16-bit integer values from network-order to CPU host-order.
ntohl() NET_UTIL_NET_TO_HOST_32() Convert 32-bit integer values from network-order to CPU host-order.
ntohs() NET_UTIL_NET_TO_HOST_16() Convert 16-bit integer values from network-order to CPU host-order.

The four byte-ordering functions  stand for host to network short, host to network long, network to host short, and network to host long. htons()  translates a short integer from host byte-order to network byte-order. htonl  is similar, translating a long integer. The other two functions do the reverse, translating from network byte-order to host byte-order.

IP Addresses and Data Structures

Communication using sockets requires configuring or reading network addresses from network socket address structures.

IP Address Versions

There are two version of IP. The most known version is the IPv4 address which is 4 bytes long and commonly written in dots and numbers format, such as

192.168.1.1

The next generation of IP is IPv6 which is also supported by µC/TCP-IP. The IPv6 address is 128 bits long and it is represented using numbers, letters and semi-colons such as:

fe80:14f:c9d2:12::51

IPv4 Addresses

The BSD socket API defines a generic socket address structure as a blank template with no address-specific configuration.

Listing - Generic (non-address-specific) address structures
struct  sockaddr {                          /* Generic BSD   socket address structure    */
    CPU_INT16U  sa_family;                  /*  Socket address family                    */
    CPU_CHAR    sa_data[14];                /*  Protocol-specific address information    */
};

 
typedef  struct  net_sock_addr {            /* Generic µC/TCP-IP socket address structure */
    NET_SOCK_ADDR_FAMILY   AddrFamily;
    CPU_INT08U             Addr[14];
} NET_SOCK_ADDR;

…as well as specific socket address structures to configure each specific protocol address family’s network address configuration (e.g., IPv4 socket addresses):

Listing - Internet (IPv4) address structures
struct  in_addr {
    NET_IP_ADDR  s_addr;                         /* IPv4 address (32 bits)                  */
};

 
struct  sockaddr_in {                            /* BSD    IPv4 socket address structure    */
    CPU_INT16U       sin_family;                 /*  Internet address family (e.g. AF_INET) */
    CPU_INT16U       sin_port;                   /*  Socket  address port number (16 bits)  */
    struct in_addr   sin_addr;                   /*  IPv4   address       (32 bits)         */
    CPU_CHAR         sin_zero[8];                /*  Not used (all zeroes)                  */
};

 
typedef  struct  net_sock_addr_ipv4 {            /* µC/TCP-IP socket address structure     */
    NET_SOCK_ADDR_FAMILY   AddrFamily;
    NET_PORT_NBR           Port;
    NET_IPv4_ADDR          Addr;
    CPU_INT08U             Unused[8];
} NET_SOCK_ADDR_IPv4;

A socket address structure’s AddrFamily/sa_family/sin_family value must be read/written in host CPU byte order, while all Addr/sa_data values must be read/written in network byte order (big endian).

Even though socket functions – both µC/TCP-IP and BSD – pass pointers to the generic socket address structure, applications must declare and pass an instance of the specific protocol’s socket address structure (e.g., an IPv4 address structure). For microprocessors, that requires data access to be aligned to appropriate word boundaries. This forces compilers to declare an appropriately-aligned socket address structure so that all socket address members are correctly aligned to their appropriate word boundaries.

Caution: Applications should avoid, or be cautious when, declaring and configuring a generic byte array as a socket address structure, since the compiler may not correctly align the array or the socket address structure’s members to appropriate word boundaries.

The figure below shows a sample IPv4 instance of the µC/TCP-IP NET_SOCK_ADDR_IPv4 (sockaddr_in) structure overlaid on top of a NET_SOCK_ADDR (sockaddr) structure.

Figure - NET_SOCK_ADDR_IP is the IPv4 specific instance of the generic NET_SOCK_ADDR data structure


A socket could configure the example socket address structure in the figure below  to bind on IPv4 address 10.10.1.65 and port number 49876 with the following code:

Listing - Bind on 10.10.1.65
NET_SOCK_ADDR_IPv4   addr_local;
NET_IPv4_ADDR        addr_ip;
NET_PORT_NBR         addr_port;
NET_SOCK_RTN_CODE    rtn_code;
NET_ERR              err;

 
addr_ip   = NetASCII_Str_to_IPv4("10.10.1.65", &err);
addr_port = 49876;
Mem_Clr((void   *)&addr_local,
        (CPU_SIZE_T) sizeof(addr_local));
addr_local.AddrFamily = NET_SOCK_ADDR_FAMILY_IP_V4;                   /* = AF_INET†† Figure 9-1 */
addr_local.Addr       = NET_UTIL_HOST_TO_NET_32(addr_ip);
addr_local.Port       = NET_UTIL_HOST_TO_NET_16(addr_port);
rtn_code              = NetSock_Bind((NET_SOCK_ID      ) sock_id,
                                     (NET_SOCK_ADDR   *)&addr_local,  /* Cast to generic addr† */
                                     (NET_SOCK_ADDR_LEN) sizeof(addr_local),
                                     (NET_ERR         *)&err);


† The address of the specific IPv4 socket address structure is cast to a pointer to the generic socket address structure.

IPv6 Addresses

Socket addresses structures for IPv6 are similar to IPv4. 

Listing - Internet (IPv6) address structures
struct  in6_addr {
    in6_addr_t             s_addr[16];
};


struct  sock_addr_in6 {
            sa_family_t   sin6_family;           /* Internet address family (AF_INET6)           */
            in_port_t     sin6_port;             /* Socket  address port number (16 bits)        */
            CPU_INT32U    sin6_flowinfo;         /* Flow info.                                   */
    struct  in6_addr      sin6_addr;             /* IPv6   address.  (128 bits)                  */
            CPU_INT32U    sin6_scope_id;         /* Scope zone ix.                               */
};

 
typedef  struct  net_sock_addr_ipv6 {
    NET_SOCK_ADDR_FAMILY    AddrFamily;                       
    NET_PORT_NBR            Port;                                       
    CPU_INT32U              FlowInfo;
    NET_IPv6_ADDR           Addr;                                       
    CPU_INT32U              ScopeID;  
} NET_SOCK_ADDR_IPv6;


A socket could configure the example socket address structure in the figure below  to bind on IPv6 address FE80::1111:AAAA and port number 49876 with the following code:

NET_SOCK_ADDR_IPv6   addr_local;
NET_IPv6_ADDR        addr_ip;
NET_PORT_NBR         addr_port;
NET_SOCK_RTN_CODE    rtn_code;
NET_ERR              err;

 
addr_ip   = NetASCII_Str_to_IPv6("FE80::1111:AAAA", &err);
addr_port = 49876;
Mem_Clr((void   *)&addr_local,
        (CPU_SIZE_T) sizeof(addr_local));
addr_local.AddrFamily = NET_SOCK_ADDR_FAMILY_IP_V6;                   /* = AF_INET6 */
Mem_Copy((void     *)&addr_local->Addr,
         (void     *)&addr_ip,
         (CPU_SIZE_T) sizeof(addr_local));
addr_local.Port       = NET_UTIL_HOST_TO_NET_16(addr_port);
rtn_code              = NetSock_Bind((NET_SOCK_ID     ) sock_id,
                                    (NET_SOCK_ADDR   *)&addr_local,  /* Cast to generic addr† */
                                    (NET_SOCK_ADDR_LEN) sizeof(addr_local),
                                    (NET_ERR         *)&err);

† The address of the specific IPv6 socket address structure is cast to a pointer to the generic socket address structure.

Complete send() Operation

send()  returns the number of bytes actually sent out. This might be less than the number that are available to send. The function will send as much of the data as it can. The developer must make sure that the rest of the packet is sent later.


Listing - Completing a send()
{ 
    int  total     = 0;        /* how many bytes we've sent      */ 
    int  bytesleft = *len;     /* how many we have left to send  */
    int  n; 

 
    while (total < *len) { 
        n = send(s, buf + total, bytesleft, 0);                                             (1)
        if (n == -1) { 
            break; 
        } 
        total += n;                                                                         (2)
        bytesleft -= n;                                                                     (3)
    }
} 

(1) Send as many bytes as there are transmit network buffers available.

(2) Increase the number of bytes sent.

(3) Calculate how many bytes are left to send.


This is another example that, for a TCP/IP stack to operate smoothly, sufficient memory to define enough buffers for transmission and reception is a design decision that requires attention if optimum performance for the given hardware is desired.

IPv4 to IPv6

For a client migrating from IPv4 to IPv6 is basically just using the correct structure following the IP address version to use. For a server, if both IP address must be supported at same time, the application might require some modification, depending it's design since two listening socket will be required one for IPv4 and another for IPv6.

Loopback for Inter-Process Communication

It is possible for tasks to communicate with sockets via the localhost interface which must be enabled. See Network Interfaces Configuration.

µC/TCP-IP Socket Error Codes

When socket functions return error codes, the error codes should be inspected to determine if the error is a temporary, non-fault condition (such as no data to receive) or fatal (such as the socket has been closed).

Fatal Socket Error Codes

Whenever any of the following fatal error codes are returned by any µC/TCP-IP socket function, that socket  must  be immediately  closed() ’d without further access by any other socket functions:

NET_SOCK_ERR_INVALID_FAMILY
NET_SOCK_ERR_INVALID_PROTOCOL
NET_SOCK_ERR_INVALID_TYPE
NET_SOCK_ERR_INVALID_STATE
NET_SOCK_ERR_FAULT

Whenever any of the following fatal error codes are returned by any µC/TCP-IP socket function, that socket must not be accessed by any other socket functions but must also not be closed()’d:

NET_SOCK_ERR_NOT_USED

Socket Error Code List

See net_err.h for a brief explanation of all µC/TCP-IP socket error codes.