-
Notifications
You must be signed in to change notification settings - Fork 270
Network Helpers
WIL network helpers are free-function helpers and classes for working with BSD sockets, Winsock functions, and network address structures. They provide simplified, type-safe access to Windows networking APIs with RAII types and error handling.
To use WIL network helpers, add wil/network.h to your C++ source file:
Some of the network functions have specific requirements:
| Requires | |
|---|---|
| Functions that throw | Requires C++ exceptions |
| Functions that are no_throw | Do not require or use C++ exceptions |
| STL string return types | Requires C++ exceptions and <string>
|
| Link libs | Links with ntdll.lib and ws2_32.lib
|
/wil/network.h has the below #include set of Winsock and networking headers to be referenced in a deliberate sequence to ensure all types and functions are available.
Windows network headers can have intra-header dependencies, creating difficulties when needing access to various functions and types if not included in the correct order.
#include <winsock2.h>
#include <ws2def.h>
#include <ws2ipdef.h>
#include <mswsock.h>
#include <mstcpip.h>
#include <ws2tcpip.h>
#include <windns.h>
#include <iphlpapi.h>WSAStartup must be called before using any Winsock functions. WIL provides RAII types to simplify this process. The returned object automatically calls WSACleanup when it goes out of scope. It is recommended to keep the WSAStartup return value in a variable for the lifetime of all your networking operations.
| Error Handling | Function name | Full Signature |
|---|---|---|
| Exception | wil::network::WSAStartup |
wil::network::unique_wsacleanup_call WSAStartup() |
| No-throw | wil::network::WSAStartup_nothrow |
wil::network::unique_wsacleanup_call WSAStartup_nothrow() WI_NOEXCEPT |
| Fail-fast | wil::network::WSAStartup_failfast |
wil::network::unique_wsacleanup_call WSAStartup_failfast() WI_NOEXCEPT |
Returns a wil::network::unique_wsacleanup_call RAII object that automatically calls WSACleanup when destroyed.
The object evaluates to false if WSAStartup failed (no-throw version only).
// handling WSAStartup failure without exceptions
const auto wsaCleanup = wil::network::WSAStartup_nothrow();
if (!wsaCleanup)
{
// WSAStartup failed, handle error
return;
}The wil::network::socket_address class provides a type-safe encapsulation around the network structures used with the sockaddr structure.
Commonly used sockaddr* types which the wil::network::socket_address class provides utility in working through type-safe interfaces. Note that the wil::network::socket_address class handles all required byte-order conversions.
| Native type | Description |
|---|---|
sockaddr_storage / SOCKADDR_STORAGE |
A sockaddr-derived type that is guaranteed to be large enough to hold any possible socket address - not limited to TCPIP related-addresses. |
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 / SOCKADDR_INET |
A union of sockaddr_in and sockaddr_in6. Large enough to contain any TCPIP IPv4 or IPv6 address. |
in_addr / IN_ADDR |
The address field in a sockaddr_in. |
in6_addr / IN6_ADDR |
The address field in a sockaddr_in6. |
SOCKET_ADDRESS |
Not a derived sockaddr* type. A structure with members containing both a sockaddr* and its length fields. Returned from some networking functions |
| Constructor | Description |
|---|---|
socket_address() |
Default constructor - sets the famaily to AF_UNSPEC (zero) |
socket_address(ADDRESS_FAMILY family) |
Construct with the specified address family. The family must be AF_UNSPEC, AF_INET, or AF_INET6
|
socket_address(const SOCKADDR* addr, size_t addr_size) |
Constructs from the provided sockaddr |
socket_address(const SOCKADDR_IN* addr) |
Constructs from the provided IPv4 sockaddr |
socket_address(const SOCKADDR_IN6* addr) |
Constructs from the provided IPv6 sockaddr |
socket_address(const IN_ADDR* addr, unsigned short port = 0) |
Constructs from the provided IPv4 address and port |
socket_address(const IN6_ADDR* addr, unsigned short port = 0) |
Constructs from the provided IPv6 address and port |
socket_address(PCWSTR addr, unsigned short port = 0) |
Constructs from the provided string and port (requires exceptions) |
| Method | Description |
|---|---|
void reset() |
Resets the contained address to the all-zero address with the AF_UNSPEC (zero) family |
void reset(ADDRESS_FAMILY family) |
Resets the contained address to the all-zero address with the specified address family. The family must be AF_UNSPEC, AF_INET, or AF_INET6
|
void set_address_any() |
Sets the contained address to the all-zero any address (also called the wildcard address) |
void set_address_loopback() |
Sets the contained address to loopback address |
void set_port(USHORT port) |
Sets the port number |
void set_scope_id(ULONG scopeId) |
Sets the IPv6 scope ID |
void set_flow_info(ULONG flowInfo) |
Sets the IPv6 flow info |
All values are returned in host byte order (they are stored in network byte order internally).
| Method | Description |
|---|---|
ADDRESS_FAMILY family() |
Returns the address family |
USHORT port() |
Returns the port number |
ULONG scope_id() |
Returns the IPv6 scope ID |
ULONG flow_info() |
Returns the IPv6 flow info |
bool is_address_loopback() |
Checks if loopback address |
bool is_address_linklocal() |
Checks if link-local address |
NL_ADDRESS_TYPE address_type() |
Get address type classification |
| Method | Description |
|---|---|
HRESULT format_address_nothrow(socket_address_wstring& address) |
Formats the IP address to the provided string object (no-throw) |
HRESULT format_complete_address_nothrow(socket_address_wstring& address) |
Formats the complete IP address with port to the provided string object (no-throw) |
std::wstring format_address() |
Formats the IP address to the returned std::wstring object (requires exceptions) |
std::wstring format_complete_address() |
Formats the complete address with port to the returned std::wstring object (requires exceptions) |
| Method | Description |
|---|---|
SOCKADDR* sockaddr() |
Returns the encapsulated address as a generic sockaddr* |
SOCKADDR_IN* sockaddr_in() |
Returns the encapsulated address as IPv4 sockaddr_in* |
SOCKADDR_IN6* sockaddr_in6() |
Returns the encapsulated address as IPv6 sockaddr_in6* |
IN_ADDR* in_addr() |
Returns the encapsulated IPv4 address in_addr* |
IN6_ADDR* in6_addr() |
Returns the encapsulated IPv6 address in6_addr* |
SOCKADDR_STORAGE sockaddr_storage() |
Returns the encapsulated address into a returned sockaddr_storage structure |
Functions and classes are provided to simplify resolving hostnames to addresses, including managing the memory of the returned ADDRINFO structures.
The addr_info_iterator class provides STL-compatible iteration over ADDRINFO structures. Three variants are provided for ANSI, Unicode, and extended ADDRINFOEX structures.
This iterator can be used in range-based for loops and STL algorithms.
This iterator works with the related RAII object wil::unique_addrinfo which manages the memory of the returned ADDRINFO* list.
| Iterator Type | Description |
|---|---|
addr_info_ansi_iterator |
Iterates over ADDRINFOA* structures |
addr_info_iterator |
Iterates over ADDRINFOW* structures |
addr_infoex_iterator |
Iterates over ADDRINFOEXW* structures |
#include "wil/network.h"
// the example shows resolving a name when C++ exceptions are enabled
// the code assumes WSAStartup has already been called
// e.g.:
// auto wsaCleanup = wil::network::WSAStartup();
//
// It is generally recommended that WSAStartup should be scoped for the lifetime of all networking operations
// not calling WSAStartup/WSACleanup per-function
wil::unique_socket connect_to_hostname(PCWSTR hostname)
{
// Resolve a hostname
auto addresses = wil::network::resolve_name(hostname);
// Iterate through all resolved addresses
for (const auto& addr : wil::network::addr_info_iterator{addresses.get()})
{
// Try to connect to each address
wil::unique_socket sock{::socket(addr.family(), SOCK_STREAM, IPPROTO_TCP)};
if (::connect(sock.get(), addr.sockaddr(), addr.size()) == 0)
{
// Connected successfully
return sock;
}
}
// If we reach here, no connection was successful
return {};
}WIL provides RAII types for loading Winsock extension functions. These returned extension function table objects automatically maintain a WSAStartup reference and load the function pointers on construction. The function tables handle the complexity of using WSAIoctls to dynamically load these extension functions.
| Type | Description |
|---|---|
winsock_extension_function_table |
Standard Winsock extension functions (AcceptEx, ConnectEx, etc.) |
rio_extension_function_table |
Registered I/O (RIO) extension functions |
process_socket_notification_table |
Socket notification functions (Windows 10 version 2004+) |
The winsock_extension_function_table provides access to the standard Winsock extension functions that have been available since Windows Vista.
| Function | Description |
|---|---|
AcceptEx |
Accepts a new connection, returns the local and remote address, and receives the first block of data sent by the client application |
ConnectEx |
Establishes a connection to a specified socket, and optionally sends data after the connection is established |
DisconnectEx |
Closes a connection and optionally allows the socket to be reused |
GetAcceptExSockaddrs |
Parses the data obtained from AcceptEx and passes the local and remote addresses |
TransmitFile |
Transmits file data over a connected socket |
TransmitPackets |
Transmits in-memory data or file data over a connected socket |
WSARecvMsg |
Can be used to receive data and optional control information. Can only be used with datagram and raw sockets. |
WSASendMsg |
Can be used to send data and optional control information. Can only be used with datagram and raw sockets. |
The rio_extension_function_table provides access to Registered I/O (RIO) extension functions for high-performance networking.
RIO is designed for applications that require extremely high throughput and low CPU overhead.
| Function | Description |
|---|---|
RIOReceive |
Receives network data on a connected RIO socket |
RIOReceiveEx |
Receives network data with additional control information |
RIOSend |
Sends network data on a connected RIO socket |
RIOSendEx |
Sends network data with additional control information |
RIOCloseCompletionQueue |
Closes a completion queue |
RIOCreateCompletionQueue |
Creates a completion queue for RIO sockets |
RIOCreateRequestQueue |
Creates a request queue for sending and receiving data |
RIODequeueCompletion |
Removes entries from a completion queue |
RIODeregisterBuffer |
Deregisters a buffer used with RIO |
RIONotify |
Registers a method to use for notification |
RIORegisterBuffer |
Registers a buffer for use with RIO |
RIOResizeCompletionQueue |
Resizes a completion queue |
RIOResizeRequestQueue |
Resizes a request queue |
The process_socket_notification_table provides access to socket notification functions available in Windows 10 version 2004 and later.
These functions enable efficient event-driven socket programming.
| Function | Description |
|---|---|
ProcessSocketNotifications |
Associates a set of sockets with a completion port, and retrieves any notifications that are already pending on that port. |
See ProcessSocketNotifications on Microsoft Learn for detailed documentation.
// Default construction (AF_UNSPEC)
wil::network::socket_address default_addr;
// Construct with address family
wil::network::socket_address v4_addr{AF_INET};
wil::network::socket_address v6_addr{AF_INET6};
// From raw address structures
SOCKADDR_IN v4_sockaddr{};
v4_sockaddr.sin_family = AF_INET;
v4_sockaddr.sin_port = htons(8080); // the raw structure requires network byte order
wil::network::socket_address addr_from_v4{&v4_sockaddr};
// From IN_ADDR/IN6_ADDR with optional port
in_addr ipv4_address{};
wil::network::socket_address addr{&ipv4_address, 8080};
// From string (requires exceptions)
wil::network::socket_address addr_from_string{L"192.168.1.1", 8080};
// Set to wildcard/any address
wil::network::socket_address addr;
addr.set_address_any(AF_INET); // 0.0.0.0
addr.set_port(8080); // assinging ports does not require conversion to network byte order
// Set to loopback address
addr.set_address_loopback(AF_INET6); // ::1
addr.set_port(443);
// Set IPv6-specific properties
addr.set_scope_id(10000);
addr.set_flow_info(123456);
// Set specific address from string
addr.reset_address_nothrow(L"127.0.0.1");
addr.set_port(80);
// Get address family
ADDRESS_FAMILY family = addr.family(); // AF_INET
// Get port (returned in host byte order)
USHORT port = addr.port(); // 80
// Check address characteristics
bool isLoopback = addr.is_address_loopback(); // true
bool isLinkLocal = addr.is_address_linklocal(); // false
NL_ADDRESS_TYPE type = addr.address_type(); // NlatUnicastwil::network::socket_address addr{L"2001::1", 8080};
// Format just the IP address (nothrow version)
wil::network::socket_address_wstring address_str;
HRESULT hr = addr.format_address_nothrow(address_str);
// Returns "2001::1"
// Format complete address with port and scope (nothrow version)
wil::network::socket_address_wstring complete_str;
hr = addr.format_complete_address_nothrow(complete_str);
// Returns "[2001::1]:8080"
// Exception-based versions
std::wstring addr_str = addr.format_address();
std::wstring complete = addr.format_complete_address();
// ANSI string versions also available
wil::network::socket_address_string ansi_str;
hr = addr.format_address_nothrow(ansi_str);wil::network::socket_address addr1{L"192.168.1.1", 80};
wil::network::socket_address addr2{L"192.168.1.2", 80};
// Equality comparison
if (addr1 == addr2)
{
// Addresses are identical
}
// Ordering (useful for std::map, std::set)
if (addr1 < addr2)
{
// addr1 sorts before addr2
}
// Manual comparison (-1, 0, 1 like memcmp)
int result = addr1.compare(addr2);// Convert IPv4 address to IPv4-mapped IPv6 for dual-mode sockets
wil::network::socket_address ipv4_addr{L"192.168.1.1", 8080};
// mapped is an IPv6 address, containing the IPv4-mapped address
wil::network::socket_address mapped = wil::network::map_dual_mode_4to6(ipv4_addr);
// Can now connect dual-mode IPv6 socket to IPv4 address
wil::unique_socket dualModeSocket{::socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)};
// Set the IPV6_V6ONLY option to 0 (dual-mode)
DWORD opt = 0; // 0 to disable IPV6_V6ONLY
if (setsockopt(dualModeSocket.get(), IPPROTO_IPV6, IPV6_V6ONLY, (char *)&opt, sizeof(opt)) == SOCKET_ERROR) {
return ::WSAGetLastError();
}
// Connect to the mapped IPv6 address
if (::connect(dualModeSocket.get(), mapped.sockaddr(), mapped.size()) == SOCKET_ERROR) {
return ::WSAGetLastError();
}#include "wil/network.h"
// Assumes WSAStartup has already been called
// create a listening socket and post an AcceptEx on it
int accept_a_connection_over_loopback(uint16_t port)
{
wil::network::winsock_extension_function_table extension_functions;
wil::unique_socket listeningSocket{::socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)};
if (!listeningSocket)
{
return ::WSAGetLastError();
}
wil::network::socket_address listenAddress{AF_INET6};
listenAddress.set_address_loopback();
listenAddress.set_port(port);
const auto bind_error = ::bind(listeningSocket.get(), listenAddress.sockaddr(), listenAddress.size());
if (bind_error != 0)
{
return ::WSAGetLastError();
}
const auto listen_error = ::listen(listeningSocket.get(), 1);
if (listen_error != 0)
{
return ::WSAGetLastError();
}
// the buffer to supply to AcceptEx to capture the address information
static constexpr size_t singleAddressOutputBufferSize = listenAddress.size() + 16;
char acceptex_output_buffer[singleAddressOutputBufferSize * 2]{};
wil::unique_socket acceptSocket{::socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)};
if (!acceptSocket)
{
return ::WSAGetLastError();
}
DWORD acceptex_bytes_received{};
wil::unique_event_nothrow acceptex_overlapped_event{};
acceptex_overlapped_event.create(wil::EventOptions::ManualReset);
OVERLAPPED acceptex_overlapped{};
acceptex_overlapped.hEvent = acceptex_overlapped_event.get();
// must pass an OVERLAPPED*, cannot pass null
auto acceptex_return = extension_functions->AcceptEx(
listeningSocket.get(),
acceptSocket.get(),
acceptex_output_buffer,
0,
singleAddressOutputBufferSize,
singleAddressOutputBufferSize,
&acceptex_bytes_received,
&acceptex_overlapped);
if (!acceptex_return)
{
gle = ::WSAGetLastError();
if (gle != ERROR_IO_PENDING)
{
return gle;
}
}
// this scope guard is a guarantee against OVERLAPPED -sourced heap/stack corruption
// the scope guard ensures that we wait for this async (overlapped) call to complete
// before we exit this function, regardless of how we exit (return, exception, etc)
const auto ensure_acceptex_overlapped_completes = wil::scope_exit([&] {
// close the sockets to cancel any pended IO
acceptSocket.reset();
listeningSocket.reset();
// now wait for our async call
acceptex_overlapped_event.wait();
});
// must waiting for the overlapped AcceptEx to complete
// i.e., for the acceptex_overlapped_event to be signaled
}