Differences Between Network Calls in Windows and Linux

Network Berkeley and Microsoft socket models that are mostly compatible on the source code level are not so cross-platform in practice.

Network

Let’s examine some subtle differences in their implementation. These differences were found when writing a cross-platform RPC for redirection of network calls of some process from one OS to another.

Socket Types

  1. BSD:
    int
  2. Win:
    void * // macros SOCKET

While the processor capacity is 32 bits, there are no problems in mutual displaying. On Windows 64 bits, the SOCKET type is twice larger in size.

The socket descriptor on BSD does not differ from the file descriptor. It means that some system calls accept descriptors of sockets and files simultaneously (for example, such commonly used calls as close()fcntl(), and ioctl()).

There is one more side effect that appears in some cases. The matter is that systems, which support Berkeley model, have a small numerical value of the socket descriptor (less than 100) and the descriptors that are created in succession differ on 1. In the Microsoft model, such descriptors have values that are approximately more than 200 at once, and the descriptors created in succession differ on sizeof(SOCKET).

Related:- How to Set Up Microsoft Teams

Error Handling

  1. BSD:   Calls return -1, global variable errno is set.
  2. Win:    Calls return -1 (SOCKET_ERROR macro), we receive the status with WSAGetLastError().

errno constants and Windows error codes have absolutely different values.

Socket creation

socket(int af, int type, int protocol);

Constants for the first argument have absolutely different values on BSD and Windows. Constants for the second argument coincide so far.

Socket Setting

1. BSD:

getsockopt(int sockfd, int level, int option_name, void *option_value, socklen_t *option_len);
setsockopt(int sockfd, int level, int option_name, void const *option_value, socklen_t option_len);

2. Win:

getsockopt(SOCKET sock, int level, int option_name, void *option_value,  socklen_t *option_len);
setsockopt(SOCKET sock, int level, int option_name, void const *option_value,  socklen_t option_len)

Flag constants for the second and third arguments have absolutely different values on BSD and Windows.

Socket Setting 2

  1. BSD:
    fcntl(int fd, int cmd, ...);
  2. Win:
    ioctlsocket(SOCKET sock, long cmd, long unsigned *arg);

The only completely correct correspondence is as follows:

fcnlt(descriptor, F_SETFL, O_NONBLOCK) -> ioctlsocket(descriptor, FIONBIO, address of the variable with the O_NONBLOCK value).

Flag numerical values should be considered in regard to the target system (they are different on BSD and Windows systems).

At the same time, we can return 0 or O_RDWR for the call of the fcnlt(descriptor, F_GETFL) type.

Socket Setting 3

  1. BSD:
    ioctl(int fd, int cmd, ...);
  2. Win:
    ioctlsocket(SOCKET sock, long cmd, long unsigned *arg);

The cases of real usage of ioctl() with the socket as the first argument have not been discovered so far.

Work with DNS

getaddrinfo(char const *node, char const *service, struct addrinfo const *hints, struct addrinfo **res)

1. BSD:

    struct addrinfo
{
    int ai_flags;
    int ai_family;
    int ai_socktype;
    int ai_protocol;
    socklen_t ai_addrlen;
    struct sockaddr *ai_addr;
    char *ai_canonname;
    struct addrinfo *ai_next;
};

2. Win:

typedef struct addrinfo
{
    int ai_flags;
    int ai_family;
    int ai_socktype;
    int ai_protocol;
    size_t ai_addrlen;
    char *ai_canonname;
    struct sockaddr_ *ai_addr;
    struct addrinfo_ *ai_next;
} ADDRINFOA, *PADDRINFOA;

Pay attention to the invariants of these structures. ai_addr and ai_canonname have different offsets from the beginning of the structure. Developers just rearranged them (or mixed up?).

Related:- Windows 10 Users Face Driver and Issues

Waiting for Operations 2

1. BSD:

select(int nfds, fd_set *readfds, fd_set *writefds, fd_set  *errorfds, struct timeval *timeout);
typedef struct
{
            long fds_bits[FD_SETSIZE / 8 * sizeof(long)];
} fd_set;

2. Win:

 select(int nfds, FDSET *readfds, FDSET *writefds, FDSET *errorfds,  struct timeval *timeout);
typedef struct fd_set
       {
          unsigned fd_count;
          SOCKET fd_array[FD_SETSIZE];
} FDSET, *PFDSET;

The problem in the select procedure appears while mutual reflection of the fd_set structure. Let’s recollect how select() works. This call accepts three sets of sockets: for checking reading, writing, and errors during some period of time. You can add your own socket for checking to one of these sets via the FD_SET(socket, set) macro. To check the socket on being installed, use the FD_ISSET(socket, set) macro; to delete one socket from the set, use the FD_CLR(socket, set) macro; to delete all sockets, use the FD_ZERO(set) macro. After the call, select() leaves only those sockets in the corresponding sets, which got the expected state during the time out defined by the last argument.

Implementation Details

Here is a certain useful code that I used in my project.

First, it is supposed that we somehow managed to redirect standard network calls to the GLibC library to our implementations (for example, see http://apriorit.com/dev-blog/181-elf-hook). Besides, we have some mechanism of a synchronous RPC that performs the serialization of parameters and the transfer of calls from Linux to Windows. Also, there are declarations of all required Windows constants so that they do not cross with Linux analogs.