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;
}