root/src/kits/network/libnetapi/NetworkRoute.cpp
/*
 * Copyright 2013-2015 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 */

#include <NetworkRoute.h>

#include <errno.h>
#include <net/if.h>
#include <string.h>
#include <sys/sockio.h>

#include <AutoDeleter.h>


BNetworkRoute::BNetworkRoute()
{
        memset(&fRouteEntry, 0, sizeof(route_entry));
}


BNetworkRoute::~BNetworkRoute()
{
        UnsetDestination();
        UnsetMask();
        UnsetGateway();
        UnsetSource();
}


status_t
BNetworkRoute::SetTo(const BNetworkRoute& other)
{
        return SetTo(other.RouteEntry());
}


status_t
BNetworkRoute::SetTo(const route_entry& routeEntry)
{
        #define SET_ADDRESS(address, setFunction) \
                if (routeEntry.address != NULL) { \
                        result = setFunction(*routeEntry.address); \
                        if (result != B_OK) \
                                return result; \
                }

        status_t result;
        SET_ADDRESS(destination, SetDestination)
        SET_ADDRESS(mask, SetMask)
        SET_ADDRESS(gateway, SetGateway)
        SET_ADDRESS(source, SetSource)

        SetFlags(routeEntry.flags);
        SetMTU(routeEntry.mtu);
        return B_OK;
}


void
BNetworkRoute::Adopt(BNetworkRoute& other)
{
        memcpy(&fRouteEntry, &other.fRouteEntry, sizeof(route_entry));
        memset(&other.fRouteEntry, 0, sizeof(route_entry));
}


const route_entry&
BNetworkRoute::RouteEntry() const
{
        return fRouteEntry;
}


const sockaddr*
BNetworkRoute::Destination() const
{
        return fRouteEntry.destination;
}


status_t
BNetworkRoute::SetDestination(const sockaddr& destination)
{
        return _AllocateAndSetAddress(destination, fRouteEntry.destination);
}


void
BNetworkRoute::UnsetDestination()
{
        _FreeAndUnsetAddress(fRouteEntry.destination);
}


const sockaddr*
BNetworkRoute::Mask() const
{
        return fRouteEntry.mask;
}


status_t
BNetworkRoute::SetMask(const sockaddr& mask)
{
        return _AllocateAndSetAddress(mask, fRouteEntry.mask);
}


void
BNetworkRoute::UnsetMask()
{
        _FreeAndUnsetAddress(fRouteEntry.mask);
}


const sockaddr*
BNetworkRoute::Gateway() const
{
        return fRouteEntry.gateway;
}


status_t
BNetworkRoute::SetGateway(const sockaddr& gateway)
{
        return _AllocateAndSetAddress(gateway, fRouteEntry.gateway);
}


void
BNetworkRoute::UnsetGateway()
{
        _FreeAndUnsetAddress(fRouteEntry.gateway);
}


const sockaddr*
BNetworkRoute::Source() const
{
        return fRouteEntry.source;
}


status_t
BNetworkRoute::SetSource(const sockaddr& source)
{
        return _AllocateAndSetAddress(source, fRouteEntry.source);
}


void
BNetworkRoute::UnsetSource()
{
        _FreeAndUnsetAddress(fRouteEntry.source);
}


uint32
BNetworkRoute::Flags() const
{
        return fRouteEntry.flags;
}


void
BNetworkRoute::SetFlags(uint32 flags)
{
        fRouteEntry.flags = flags;
}


uint32
BNetworkRoute::MTU() const
{
        return fRouteEntry.mtu;
}


void
BNetworkRoute::SetMTU(uint32 mtu)
{
        fRouteEntry.mtu = mtu;
}


int
BNetworkRoute::AddressFamily() const
{
        #define RETURN_FAMILY_IF_SET(address) \
                if (fRouteEntry.address != NULL \
                        && fRouteEntry.address->sa_family != AF_UNSPEC) { \
                        return fRouteEntry.address->sa_family; \
                }

        RETURN_FAMILY_IF_SET(destination)
        RETURN_FAMILY_IF_SET(mask)
        RETURN_FAMILY_IF_SET(gateway)
        RETURN_FAMILY_IF_SET(source)

        return AF_UNSPEC;
}


status_t
BNetworkRoute::GetDefaultRoute(int family, const char* interfaceName,
        BNetworkRoute& route)
{
        BObjectList<BNetworkRoute, true> routes(1);
        status_t result = GetRoutes(family, interfaceName, RTF_DEFAULT, routes);
        if (result != B_OK)
                return result;

        if (routes.CountItems() == 0)
                return B_ENTRY_NOT_FOUND;

        route.Adopt(*routes.ItemAt(0));
        return B_OK;
}


status_t
BNetworkRoute::GetDefaultGateway(int family, const char* interfaceName,
        sockaddr& gateway)
{
        BNetworkRoute route;
        status_t result = GetDefaultRoute(family, interfaceName, route);
        if (result != B_OK)
                return result;

        const sockaddr* defaultGateway = route.Gateway();
        if (defaultGateway == NULL)
                return B_ENTRY_NOT_FOUND;

        memcpy(&gateway, defaultGateway, defaultGateway->sa_len);
        return B_OK;
}


status_t
BNetworkRoute::GetRoutes(int family, BObjectList<BNetworkRoute, true>& routes)
{
        return GetRoutes(family, NULL, 0, routes);
}


status_t
BNetworkRoute::GetRoutes(int family, const char* interfaceName,
        BObjectList<BNetworkRoute, true>& routes)
{
        return GetRoutes(family, interfaceName, 0, routes);
}


status_t
BNetworkRoute::GetRoutes(int family, const char* interfaceName,
        uint32 filterFlags, BObjectList<BNetworkRoute, true>& routes)
{
        FileDescriptorCloser socket(::socket(family, SOCK_DGRAM, 0));
        if (!socket.IsSet())
                return errno;

        ifconf config;
        config.ifc_len = sizeof(config.ifc_value);
        if (ioctl(socket.Get(), SIOCGRTSIZE, &config, sizeof(struct ifconf)) < 0)
                return errno;

        uint32 size = (uint32)config.ifc_value;
        if (size == 0)
                return B_OK;

        void* buffer = malloc(size);
        if (buffer == NULL)
                return B_NO_MEMORY;

        MemoryDeleter bufferDeleter(buffer);
        config.ifc_len = size;
        config.ifc_buf = buffer;

        if (ioctl(socket.Get(), SIOCGRTTABLE, &config, sizeof(struct ifconf)) < 0)
                return errno;

        ifreq* interface = (ifreq*)buffer;
        ifreq* end = (ifreq*)((uint8*)buffer + size);

        while (interface < end) {
                route_entry& routeEntry = interface->ifr_route;

                if ((interfaceName == NULL
                                || strcmp(interface->ifr_name, interfaceName) == 0)
                        && (filterFlags == 0 || (routeEntry.flags & filterFlags) != 0)) {

                        BNetworkRoute* route = new(std::nothrow) BNetworkRoute;
                        if (route == NULL)
                                return B_NO_MEMORY;

                        // Note that source is not provided in the buffer.
                        routeEntry.source = NULL;

                        status_t result = route->SetTo(routeEntry);
                        if (result != B_OK) {
                                delete route;
                                return result;
                        }

                        if (!routes.AddItem(route)) {
                                delete route;
                                return B_NO_MEMORY;
                        }
                }

                size_t addressSize = 0;
                if (routeEntry.destination != NULL)
                        addressSize += routeEntry.destination->sa_len;
                if (routeEntry.mask != NULL)
                        addressSize += routeEntry.mask->sa_len;
                if (routeEntry.gateway != NULL)
                        addressSize += routeEntry.gateway->sa_len;

                interface = (ifreq *)((addr_t)interface + IF_NAMESIZE
                        + sizeof(route_entry) + addressSize);
        }

        return B_OK;
}


status_t
BNetworkRoute::_AllocateAndSetAddress(const sockaddr& from,
        sockaddr*& to)
{
        if (from.sa_len > sizeof(sockaddr_storage))
                return B_BAD_VALUE;

        if (to == NULL) {
                to = (sockaddr*)malloc(sizeof(sockaddr_storage));
                if (to == NULL)
                        return B_NO_MEMORY;
        }

        memcpy(to, &from, from.sa_len);
        return B_OK;
}


void
BNetworkRoute::_FreeAndUnsetAddress(sockaddr*& address)
{
        free(address);
        address = NULL;
}