Using the BSD socket API from C++

Setting the stage

A lot of the POSIX API functions come in pairs: getifaddrs and freeifaddrs or getaddrinfo and freeaddrinfo. Both take the address of a pointer as an out parameter and, if successful, make that point to a linked-list allocated on the free-store. This memory resources must be freed via the free-functions.

void demo() { ifaddrs *result; int status{0}; if ((status = getifaddrs(&result)) != 0) throw error{gai_strerror(status)}; // use result freeifaddrs(result); }

This is not a good solution, because it's hard to make sure that the free-function is called and called only once. In C++ smart-pointers provide a handle to a memory allocation and thus can guarantee the release of the resource.

Using a smart-pointer for the out parameter

void demo() { auto deleter = [](ifaddrs *ia){ freeifaddrs(ia); }; std::unique_ptr<ifaddrs, decltype(deleter)> result; { ifaddrs *temp; int status{0}; if ((status = getifaddrs(&temp)) != 0) throw error{gai_strerror(status)}; result.reset(temp); } // use result }

This is really nice. The smart-pointer handles the release of the allocated memory for us, now.

Using C++ algorithms

Since these POSIX functions provide linked-lists it would be very nice to provide an iterator that would make them usable with the STL algorithms.

Linked-List-Iterator

#include <cstddef> #include <iterator> #include <functional> template <typename T> struct linked_list_iterator { using value_type = T; using difference_type = std::ptrdiff_t; using iterator_category = std::forward_iterator_tag; using pointer = T*; using refernce = T&; using Next = std::function<T*(T*)>; explicit linked_list_iterator(T* current, Next next) : m_current{current}, m_next{next} {} refernce operator*() const { return *m_current; } pointer operator->() const { return &(operator*()); } linked_list_iterator operator++() { if (m_current) m_current = m_next(m_current); return *this; } linked_list_iterator operator++(int) { linked_list_iterator temp{*this}; if (m_current) m_current = m_next(m_current); return temp; } bool operator==(const linked_list_iterator& other) const { return m_current == other.m_current; } bool operator!=(const linked_list_iterator& other) const { return !(*this == other); } private: Next m_next; T *m_current = nullptr; };

Using count_if example

int count_addresses(int family) { auto deleter = [](ifaddrs *ia) { freeifaddrs(ia); }; std::unique_ptr<ifaddrs, decltype(deleter)> result; { ifaddrs *temp; int status{0}; if ((status = getifaddrs(&temp)) != 0) throw error{gai_strerror(status)}; result.reset(temp); } // use result auto begin = linked_list_iterator<ifaddrs>(result.get(), [](ifaddrs *ia) { return ia->ifa_next; }); auto end = linked_list_iterator<ifaddrs>{}; return std::count_if(begin, end, [family](auto &ia) { return ia.ifa_addr->sa_family == family; }); }

Complete Demo

Chris Tietze made me realize that wrapping things up in a handle class IfAddrs makes things even nicer!

IfAddrs

#include "linked_list_iterator.h" #include <ifaddrs.h> #include <netdb.h> struct IfAddrs { static constexpr auto deleter = [](ifaddrs *ia){ freeifaddrs(ia); }; explicit IfAddrs() : m_list{nullptr} { ifaddrs *list; int status; if ((status = getifaddrs(&list)) != 0) throw std::runtime_error{gai_strerror(status)}; m_list.reset(list); } linked_list_iterator<ifaddrs> begin() { return linked_list_iterator<ifaddrs>(m_list.get(), [](ifaddrs *ia){ return ia->ifa_next; }); } linked_list_iterator<ifaddrs> end() { return linked_list_iterator<ifaddrs>(nullptr, nullptr); } private: std::unique_ptr<ifaddrs, decltype(deleter)> m_list; };

main

auto main() -> int { const auto convert = [](ifaddrs &ia) { switch (ia.ifa_addr->sa_family) { case AF_INET: { auto address = reinterpret_cast<sockaddr_in *>(ia.ifa_addr); char ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &(address->sin_addr), ip, INET_ADDRSTRLEN); return std::string{ip}; } case AF_INET6: { auto address = reinterpret_cast<sockaddr_in6 *>(ia.ifa_addr); char ip[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, &(address->sin6_addr), ip, INET6_ADDRSTRLEN); return std::string{ip}; } default: { return std::string{"Unknown"}; } } }; auto ips = std::views::all(IfAddrs{}) | std::views::filter([](const auto& ia){ return ia.ifa_addr->sa_family == AF_INET6; }) | std::views::transform([&convert](auto& ia){ return convert(ia); }); for (const auto& ip : ips) std::cout << ip << std::endl; return EXIT_SUCCESS; }