-
Notifications
You must be signed in to change notification settings - Fork 242
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding networking.h to provide RAII and iterator support for networking calls #497
base: master
Are you sure you want to change the base?
Adding networking.h to provide RAII and iterator support for networking calls #497
Conversation
include/wil/networking.h
Outdated
//! @file | ||
//! Helpers for using BSD sockets and Winsock functions and structures. | ||
//! Does not require the use of the STL or C++ exceptions (see _nothrow functions) | ||
#ifndef __WIL_NETWORKING_INCLUDED |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why networking.h
? Why not network.h
?
include/wil/networking.h
Outdated
//! Link libs for functions referenced in this file: ws2_32.lib, ntdll.lib, and Fwpuclnt.lib (for secure socket functions) | ||
|
||
#if !defined(_WINSOCK2API_) && defined(_WINSOCKAPI_) | ||
#error The Winsock 1.1 winsock.h header was included before the Winsock 2 winsock2.h header - this will cause compilation errors - define WIN32_LEAN_AND_MEAN to avoid winsock.h included by windows.h |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment above says the problem is avoided by #define
'ing WIN32_LEAN_AND_MEAN
-OR- #include
'ing winsock.h
before Windows.h
but the error message only says the former. The error message should include both options
include/wil/networking.h
Outdated
//! Functions and classes that support networking operations and structures | ||
namespace networking | ||
{ | ||
class socket_address; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this line (unnecessary)
Forward declaration here but I see no references to it before the class is defined (line 295)
include/wil/networking.h
Outdated
LPFN_WSASENDMSG WSASendMsg{nullptr}; | ||
}; | ||
|
||
struct winsock_extension_function_table |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this needed? Why not rely on the usual build time import linkage and IAT?
include/wil/networking.h
Outdated
|
||
constexpr DWORD controlCode{SIO_GET_MULTIPLE_EXTENSION_FUNCTION_POINTER}; | ||
constexpr DWORD bytes{sizeof(table.f)}; | ||
GUID rioGuid = WSAID_MULTIPLE_RIO; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: GUID rioGuid{ WSAID_MULTIPLE_RIO };
ES.23: Prefer the {}-initializer syntax
Same comment applies throughout. Usually done, this leaped at given the 2 immediately previous lines do it but this line didn't. Any other like cases should be equally updated
include/wil/networking.h
Outdated
::memset(&table.f, 0, bytes); | ||
table.f.cbSize = bytes; | ||
|
||
DWORD unused_bytes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: DWORD unused_bytes{};
Always initialize variables
include/wil/networking.h
Outdated
nullptr, | ||
nullptr) != 0) | ||
{ | ||
const auto gle = ::WSAGetLastError(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const auto gle{ ::WSAGetLastError() };
see previous comment
include/wil/networking.h
Outdated
} | ||
|
||
constexpr DWORD controlCode{SIO_GET_EXTENSION_FUNCTION_POINTER}; | ||
constexpr DWORD bytes{sizeof(void*)}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better if sizeof(functionPtr)
include/wil/networking.h
Outdated
} | ||
case 1: | ||
{ | ||
constexpr GUID connectex_guid = WSAID_CONNECTEX; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a rather novel use of loop+switch. Why not more straightforward linear code e.g.
inline HRESULT load_function(SOCKET localSocket, GUID extensionGuid, void* functionPtr)
{
constexpr DWORD controlCode{SIO_GET_EXTENSION_FUNCTION_POINTER};
constexpr DWORD bytes{sizeof(functionPtr)};
DWORD unused_bytes{};
RETURN_IF_WIN32_BOOL_FALSE(::WSAIoctl(localSocket, controlCode, &extensionGuid, DWORD{sizeof(extensionGuid)},
functionPtr, bytes, &unused_bytes, nullptr, nullptr));
return S_OK;
}
inline winsock_extension_function_table winsock_extension_function_table::load() WI_NOEXCEPT
{
winsock_extension_function_table table;
// if WSAStartup failed, immediately exit
if (!table.wsa_reference_count)
{
return table;
}
// we need a temporary socket for the IOCTL to load the functions
const ::wil::unique_socket localSocket{::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)};
if (INVALID_SOCKET == localSocket.get())
{
return S_OK;
}
// Load the functions into the table
if (FAILED_LOG(load_function(localSocket.get(), WSAID_ACCEPTEX, &table.f.AcceptEx)) ||
FAILED_LOG(load_function(localSocket.get(), WSAID_CONNECTEX, &table.f.ConnectEx)) ||
FAILED_LOG(load_function(localSocket.get(), WSAID_DISCONNECTEX, &table.f.DisconnectEx)) ||
FAILED_LOG(load_function(localSocket.get(), WSAID_GETACCEPTEXSOCKADDRS, &table.f.GetAcceptExSockaddrs)) ||
FAILED_LOG(load_function(localSocket.get(), WSAID_TRANSMITFILE, &table.f.TransmitFile)) ||
FAILED_LOG(load_function(localSocket.get(), WSAID_TRANSMITPACKETS, &table.f.TransmitPackages)) ||
FAILED_LOG(load_function(localSocket.get(), WSAID_WSARECVMSG, &table.f.WSARecvMsg)) ||
FAILED_LOG(load_function(localSocket.get(), WSAID_WSASENDMSG, &table.f.WSASendMsg)))
{
// if any failed to be found, something is very broken
// all should load, or all should fail
::memset(&table.f, 0, sizeof(table.f));
}
return table;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR description has code starting with
#include
#include "c:/Users/khorton/source/repos/wil-keith-horton/include/wil/networking.h"
...
Is that 1st #include
a typo or missing something?
P.S. PR descriptions support markdown and would be easier to read e.g. the code marked as code
#include "c:/Users/khorton/source/repos/wil-keith-horton/include/wil/networking.h"
int wmain(int argc, wchar_t** argv)
try
{
if (argc != 2)
{
return 0;
}
...
…all 4 addrinfo* structures. Need to add more tests for the other addrinfo types.
…ution. (not yet complete)
Additions:
I #include’d network headers in the “special order” that gives access to all functions and structures
a. This is because most networking headers follow the horrible practice of tight inter-dependencies
i. I.e., networking headers will have #ifdef’s hiding functions & structures requiring you previously included some other header.
ii. Rinse & repeat this process across a few headers, and you see a common grievance: “what’s the magical header includes to use to get this dang thing to compile!”
b. Currently I just #include’d them all by default.
i. I was tempted to only #include them all if the caller defined a special #define before including my header.
c. I’m very much interesting in thoughts (especially by those who have had to deal with header inclusion issues with other wil headers)
i. Additionally some #if guards to help developers avoid the confusing mess of Winsock 1.1 and Winsock 2.2. headers (they cannot both be included). [and by default Windows.h includes the old 1.1 headers …. Something that breaks almost every Windows network developer at some point]
2 structures to load function pointers that are somewhat unpleasant to deal with.
a. Winsock has many extensibility models – almost all being a huge PITA and failed miserably.
b. One of the extensibility models is to expose “extension functions” (functions added after the Winsock APIs were agreed upon) by calling a Winsock IOCTL function (WSAIoctl) with special GUID values to receive function pointers for newer functions.
c. I wrote 2 fairly straight forward structures that will load all of this stuff and just expose a struct with those function pointers.
d. E.g. (from a test case)
::wil::networking::winsock_extension_function_table test_table = ::wil::networking::winsock_extension_function_table::load();
.
.
.
auto acceptex_return = test_table.f.AcceptEx(
listeningSocket.get(),
acceptSocket.get(),
acceptex_output_buffer,
0,
singleAddressOutputBufferSize,
singleAddressOutputBufferSize,
&acceptex_bytes_received,
&acceptex_overlapped);
e. Notice that I just make the structure within the winsock_extension_function_table visible – with the simple name ‘f’.
f. This looks somewhat kludgy – so I’m open to thoughts on this. I followed the other extension API set (RIO – ‘registered I/O’) that returns a well-defined function table: RIO_EXTENSION_FUNCTION_TABLE.
i. vs. creating methods off of these wil extension classes – mirroring the function declarations in these function tables. Which seemed low value.
A class to encapsulate the most painful structure (imho) in Networking – the sockaddr for IPv4 and IPv6 addresses.
a. A ‘sockaddr’ is a BSD structure – a 1970’s version of “inheritance” implemented in C. It’s required that all “socket address structures” have the exact same fields as the root ‘sockaddr’ in the exact same layout – then just add on new properties as needed.
i. For example, for IPv4 addresses there’s a ‘sockaddr_in’; for IPv6 addresses there’s a sockaddr_in6.
ii. E.g. SOCKADDR_IN (ws2def.h) - Win32 apps | Microsoft Learn, sockaddr(3type) - Linux manual page, Internet Address Formats (The GNU C Library)
b. If you’ve read much networking code, you might find the strangest/hackiest/full-of-random-C-casts code all dealing with these unpleasant data structures.
i. This comment from my header might help explain some of the goals:
//
// encapsulates working with the sockaddr datatype
//
// sockaddr is a generic type - similar to a base class, but designed for C with BSD sockets (1983-ish)
// 'derived' structures are cast back to sockaddr* (so the initial struct members must be aligned)
//
// this data type was built to be 'extensible' so new network types could create their own address structures
// - appending fields to the initial fields of the sockaddr
//
// note that the address and port fields of TCPIP sockaddr* types were designed to be encoded in network-byte order
// - hence the common use of "host-to-network" and "network-to-host" APIs, e.g. htons(), htonl(), ntohs(), ntohl()
//
// Socket APIs that accept a socket address will accept 2 fields:
// - the sockaddr* (the address of the derived sockaddr type, cast down to a sockaddr*)
// - the length of the 'derived' socket address structure referenced by the sockaddr*
//
// Commonly used sockaddr* types that are using with TCPIP networking:
//
// sockaddr_storage / SOCKADDR_STORAGE
// - a sockaddr* derived type that is guaranteed to be large enough to hold any possible socket address
// sockaddr_in / SOCKADDR_IN
// - a sockaddr* derived type designed to contain an IPv4 address and port number
// sockaddr_in6 / SOCKADDR_IN6
// - a sockaddr* derived type designed to contain an IPv6 address, port, scope id, and flow info
// SOCKADDR_INET
// - a union of sockaddr_in and sockaddr_in6 -- i.e., large enough to contain any TCPIP address
// in_addr / IN_ADDR
// - the raw address portion of a sockaddr_in
// in6_addr / IN6_ADDR
// - the raw address portion of a sockaddr_in6
//
// SOCKET_ADDRESS
// - not a derived sockaddr* type
// - a structure containing both a sockaddr* and its length fields, returned from some networking functions
//
c. The class is hopefully straight forward – it handles many of the unpleasantries moving between types, dealing with embedded types, proving type-safe accessors, and in handling the cases when one must deal with the unpleasant task of knowing when & how to convert between host-byte-order and network-byte-order and vice versa.
i. Many ways to construct/set_* the IP address in the class – including from a string expression of the address.
ii. Setting other properties of an address (port, scope id, flow info).
iii. Writing out an address to either a WCHAR[] or CHAR[] guaranteed to be large enough to hold the string (so no dynamic memory allocation – just a small variable pushed on the stack); as well as a std::wstring if that’s what’s requested.
iv. Providing many accessors to retrieve various pointers into the underlying sockaddr.
A small class that provides RAII + iterator semantics for walking the returned list of IP addresses after resolving a name:
//! class addr_info encapsulates the ADDRINFO structure
//! this structure contains a linked list of addresses returned from resolving a name via GetAddrInfo
//! iterator semantics are supported to safely access these addresses
class addr_info
a. And free standing functions to call the underlying API to resolve a name – returning the addr_info containing the response.
b. E.g. a few lines of code makes a tool to dump out local addresses, and prints out addresses when resolving a name:
#include
#include "c:/Users/khorton/source/repos/wil-keith-horton/include/wil/networking.h"
int wmain(int argc, wchar_t** argv)
try
{
if (argc != 2)
{
return 0;
}
const auto wsa_cleanup = wil::networking::WSAStartup();
wprintf(L"Local addresses:\n");
for (const auto& address : wil::networking::resolve_local_addresses())
{
wprintf(L" - %ws\n", address.write_address().c_str());
}
wprintf(L"\nResolving %ws:\n", argv[1]);
for (const auto& address : wil::networking::resolve_name(argv[1]))
{
wprintf(L" - %ws\n", address.write_address().c_str());
}
}
catch (const std::exception& e)
{
wprintf(L"\nFailed - %hs\n", e.what());
}
C:\Users\khorton\source\repos\test\x64\Debug>test.exe microsoft.com
Local addresses:
Resolving microsoft.com: