Cross-Platform Socket Header (and additional reference)
#1
Last few weeks I've been brushing up on my network/socket programming in C.
Since I primarily use Windows, I'm more familiar with the WinAPI version of sockets and using winsock2 instead of all the other headers that Linux requires.

I know that typically Windows and Linux systems programming is pretty drastically different, but found that Windows seems to 'emulate' Berkeley sockets already, only using a few different functions/structures/headers while maintaining a relatively large portion of portability.

To finish the job, I wrote a small header that should facilitate writing portable C socket code. Yes, you can use it with C++ as long as you use the respective functions from either Berkeley or Winsock sockets.

This header is all just a giant macro. There should be no difference in performance if you use this header or write your code in a non-portable fashion because it will include respective files and convert any macro functions to its actual respective system function.

Here's a link to the (minified) version: https://pastebin.com/MXkSrCdG


The rest of this thread is explaining what the macros do and why, and add some extra documentation on the portability of current APIs (and how respective structs are similar/identical despite having different names.).


Header Functions:


ISVALIDSOCKET(socket)
Code:
#if defined(_WIN32)
#define ISVALIDSOCKET(s) ((s) != INVALID_SOCKET)
#else
#define ISVALIDSOCKET(s) ((s) >= 0)
#endif

When creating a socket using socket(), you want to verify that it was created successfully.

On Windows, there's a constant declared in <winsock2.h> that you can check your socket against to verify if it's valid or not. Note: This will return TRUE on a valid socket to follow naming convention.
On Linux, when socket() fails, it will return -1. This just verifies that the returned socket is valid (positive.)

Example usage:
Code:
if(!ISVALIDSOCKET(socket_listen)) {
    fprintf(stderr, "socket() failed.\n");
    return 1;
}



CLOSESOCKET(socket)
Code:
#if defined(_WIN32)
#define CLOSESOCKET(s) closesocket(s)
#else
#define CLOSESOCKET(s) close(s)
#endif

Pretty straightforward. The functions do the exact same thing, closing a socket. The only difference is that they are named differently between Windows and Berkeley sockets.
On Windows, the function is closesocket(), specific to Winsock2.
On Linux, the function is close() (syscall) because sockets are treated identically to regular file descriptors.

Example:
Code:
CLOSESOCKET(socket_peer);




GETSOCKETERRNO()
Code:
#if defined(_WIN32)
#define GETSOCKETERRNO() (WSAGetLastError())
#else
#define GETSOCKETERRNO() (errno)
#endif

This will return the error code whenever something fails.
On Windows, the error number is written to a buffer that can be retrieved with WSAGetLastError().
On Linux, since sockets are file descriptors, the error code is written to the errno variable (in <errno.h>).

Example:
Code:
if(!ISVALIDSOCKET(socket_peer)) {
    fprintf(stderr, "socket() failed. (%d)\n", GETSOCKETERRNO());
    return 1;
}



Additional Notes

WinAPI tends to define anything that's an integer type on Linux as a DWORD type (4 bytes). It's essentially legacy code, but whenever you see a DWORD type, just think: integer.

For instance, a socket on Linux is a file descriptor, or an int type. On Windows, that makes it a DWORD type, although DWORD is just a typedef for an int. You can use it interchangeably, but be sure to try and stay consistent with your code depending on the platform.

On top of this, most Windows code also has legacy types beyond just DWORDS.
LPVOID, PCSTR, LPWSTR, etc.

You don't need to memorize them all. There's a pretty simple convention to follow as long as you can memorize the 'longest' prefix (or at least what it expands to):

LP[type] (Long Pointer to [type])

Types can include the following:
VOID (i.e. no type, castable)
CSTR (C-string, or null-terminated character array)
WSTR (Wide String, or each character is two bytes long for 16bit Unicode, null-terminated)
CHAR (character, self-explanatory)

And that's not all. But you get the idea. Read the first few characters, it will most likely be L or P, (or both), then figure out what type it is.
If you know these basics, then you can determine that PCSTR is a Pointer (16bit) to a C-string. Or LPVOID is a Long (32bit) Pointer to Void.

So when browsing through the MSDN documentation for the first time and you see the following function definition:
Code:
INT WSAAPI getaddrinfo(
  PCSTR          pNodeName,
  PCSTR          pServiceName,
  const ADDRINFOA *pHints,
  PADDRINFOA      *ppResult
);

You can figure out the arguments:
-- Pointer to C-string
-- Pointer to C-string
-- (pointer to) Addrinfoa*
-- (pointer to) Pointer to Addrinfoa*




If you all have any suggestions for anything else to include in the header please let me know.
Like I said, I don't do a ton with Linux programming so if I'm missing any important (i.e. commonly used) headers for sockets specifically (I excluded stdio.h and stdlib.h for a reason) then let me know and I'll add it. But I think I've covered more or less all bases to write TCP client/server interfaces.

Also if you're aware of any functions like the ones I already have macros for which operate similarly but have different names, also let me know and I'll add it here. Or typedefs for structures that vary slightly. Anything goes.
Reply
#2
Thanks, great information for cross-platform malware.
Generally, platform specific code is easier to write, and since most of the users are on Windows, I only write Linux code for the server-side.
But your headers are a great solution to create cross-platform code, providing a larger attack surface.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  How to cycle through all Windows files and directories in C? ueax 2 19,664 02-04-2021, 10:02 PM
Last Post: ueax