root/src/kits/network/libnetapi/NetworkInterface.cpp
/*
 * Copyright 2010, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 */

#include <NetworkInterface.h>

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

#include <AutoDeleter.h>
#include <Messenger.h>
#include <NetServer.h>
#include <NetworkRoute.h>


static int
family_from_interface_address(const BNetworkInterfaceAddress& address)
{
        if (address.Address().Family() != AF_UNSPEC)
                return address.Address().Family();
        if (address.Mask().Family() != AF_UNSPEC)
                return address.Mask().Family();
        if (address.Destination().Family() != AF_UNSPEC)
                return address.Destination().Family();

        return AF_INET;
}


static status_t
do_ifaliasreq(const char* name, int32 option, BNetworkInterfaceAddress& address,
        bool readBack = false)
{
        int family = AF_INET;
        if (!readBack)
                family = family_from_interface_address(address);

        FileDescriptorCloser socket(::socket(family, SOCK_DGRAM, 0));
        if (!socket.IsSet())
                return errno;

        ifaliasreq request;
        strlcpy(request.ifra_name, name, IF_NAMESIZE);
        request.ifra_index = address.Index();
        request.ifra_flags = address.Flags();

        memcpy(&request.ifra_addr, &address.Address().SockAddr(),
                address.Address().Length());
        memcpy(&request.ifra_mask, &address.Mask().SockAddr(),
                address.Mask().Length());
        memcpy(&request.ifra_broadaddr, &address.Broadcast().SockAddr(),
                address.Broadcast().Length());

        if (ioctl(socket.Get(), option, &request, sizeof(struct ifaliasreq)) < 0)
                return errno;

        if (readBack) {
                address.SetFlags(request.ifra_flags);
                address.Address().SetTo(request.ifra_addr);
                address.Mask().SetTo(request.ifra_mask);
                address.Broadcast().SetTo(request.ifra_broadaddr);
        }

        return B_OK;
}


static status_t
do_ifaliasreq(const char* name, int32 option,
        const BNetworkInterfaceAddress& address)
{
        return do_ifaliasreq(name, option,
                const_cast<BNetworkInterfaceAddress&>(address));
}


template<typename T> status_t
do_request(int family, T& request, const char* name, int option)
{
        FileDescriptorCloser socket(::socket(family, SOCK_DGRAM, 0));
        if (!socket.IsSet())
                return errno;

        strlcpy(((struct ifreq&)request).ifr_name, name, IF_NAMESIZE);

        if (ioctl(socket.Get(), option, &request, sizeof(T)) < 0)
                return errno;

        return B_OK;
}


// #pragma mark -


BNetworkInterfaceAddress::BNetworkInterfaceAddress()
        :
        fIndex(-1),
        fFlags(0)
{
}


BNetworkInterfaceAddress::~BNetworkInterfaceAddress()
{
}


status_t
BNetworkInterfaceAddress::SetTo(const BNetworkInterface& interface, int32 index)
{
        fIndex = index;
        return do_ifaliasreq(interface.Name(), B_SOCKET_GET_ALIAS, *this, true);
}


void
BNetworkInterfaceAddress::SetAddress(const BNetworkAddress& address)
{
        fAddress = address;
}


void
BNetworkInterfaceAddress::SetMask(const BNetworkAddress& mask)
{
        fMask = mask;
}


void
BNetworkInterfaceAddress::SetBroadcast(const BNetworkAddress& broadcast)
{
        fBroadcast = broadcast;
}


void
BNetworkInterfaceAddress::SetDestination(const BNetworkAddress& destination)
{
        fBroadcast = destination;
}


void
BNetworkInterfaceAddress::SetFlags(uint32 flags)
{
        fFlags = flags;
}


// #pragma mark -


BNetworkInterface::BNetworkInterface()
{
        Unset();
}


BNetworkInterface::BNetworkInterface(const char* name)
{
        SetTo(name);
}


BNetworkInterface::BNetworkInterface(uint32 index)
{
        SetTo(index);
}


BNetworkInterface::~BNetworkInterface()
{
}


void
BNetworkInterface::Unset()
{
        fName[0] = '\0';
}


void
BNetworkInterface::SetTo(const char* name)
{
        strlcpy(fName, name, IF_NAMESIZE);
}


status_t
BNetworkInterface::SetTo(uint32 index)
{
        ifreq request;
        request.ifr_index = index;

        status_t status = do_request(AF_INET, request, "", SIOCGIFNAME);
        if (status != B_OK)
                return status;

        strlcpy(fName, request.ifr_name, IF_NAMESIZE);
        return B_OK;
}


bool
BNetworkInterface::Exists() const
{
        ifreq request;
        return do_request(AF_INET, request, Name(), SIOCGIFINDEX) == B_OK;
}


const char*
BNetworkInterface::Name() const
{
        return fName;
}


uint32
BNetworkInterface::Index() const
{
        ifreq request;
        if (do_request(AF_INET, request, Name(), SIOCGIFINDEX) != B_OK)
                return 0;

        return request.ifr_index;
}


uint32
BNetworkInterface::Flags() const
{
        ifreq request;
        if (do_request(AF_INET, request, Name(), SIOCGIFFLAGS) != B_OK)
                return 0;

        return request.ifr_flags;
}


uint32
BNetworkInterface::MTU() const
{
        ifreq request;
        if (do_request(AF_INET, request, Name(), SIOCGIFMTU) != B_OK)
                return 0;

        return request.ifr_mtu;
}


int32
BNetworkInterface::Media() const
{
        ifreq request;
        if (do_request(AF_INET, request, Name(), SIOCGIFMEDIA) != B_OK)
                return -1;

        return request.ifr_media;
}


uint32
BNetworkInterface::Metric() const
{
        ifreq request;
        if (do_request(AF_INET, request, Name(), SIOCGIFMETRIC) != B_OK)
                return 0;

        return request.ifr_metric;
}


uint32
BNetworkInterface::Type() const
{
        ifreq request;
        if (do_request(AF_INET, request, Name(), SIOCGIFTYPE) != B_OK)
                return 0;

        return request.ifr_type;
}


status_t
BNetworkInterface::GetStats(ifreq_stats& stats)
{
        ifreq request;
        status_t status = do_request(AF_INET, request, Name(), SIOCGIFSTATS);
        if (status != B_OK)
                return status;

        memcpy(&stats, &request.ifr_stats, sizeof(ifreq_stats));
        return B_OK;
}


bool
BNetworkInterface::HasLink() const
{
        return (Flags() & IFF_LINK) != 0;
}


status_t
BNetworkInterface::SetFlags(uint32 flags)
{
        ifreq request;
        request.ifr_flags = flags;
        return do_request(AF_INET, request, Name(), SIOCSIFFLAGS);
}


status_t
BNetworkInterface::SetMTU(uint32 mtu)
{
        ifreq request;
        request.ifr_mtu = mtu;
        return do_request(AF_INET, request, Name(), SIOCSIFMTU);
}


status_t
BNetworkInterface::SetMedia(int32 media)
{
        ifreq request;
        request.ifr_media = media;
        return do_request(AF_INET, request, Name(), SIOCSIFMEDIA);
}


status_t
BNetworkInterface::SetMetric(uint32 metric)
{
        ifreq request;
        request.ifr_metric = metric;
        return do_request(AF_INET, request, Name(), SIOCSIFMETRIC);
}


int32
BNetworkInterface::CountAddresses() const
{
        ifreq request;
        if (do_request(AF_INET, request, Name(), B_SOCKET_COUNT_ALIASES) != B_OK)
                return 0;

        return request.ifr_count;
}


status_t
BNetworkInterface::GetAddressAt(int32 index, BNetworkInterfaceAddress& address)
{
        return address.SetTo(*this, index);
}


int32
BNetworkInterface::FindAddress(const BNetworkAddress& address)
{
        FileDescriptorCloser socket(::socket(address.Family(), SOCK_DGRAM, 0));
        if (!socket.IsSet())
                return -1;

        ifaliasreq request;
        memset(&request, 0, sizeof(ifaliasreq));

        strlcpy(request.ifra_name, Name(), IF_NAMESIZE);
        request.ifra_index = -1;
        memcpy(&request.ifra_addr, &address.SockAddr(), address.Length());

        if (ioctl(socket.Get(), B_SOCKET_GET_ALIAS, &request,
                sizeof(struct ifaliasreq)) < 0) {
                return -1;
        }

        return request.ifra_index;
}


int32
BNetworkInterface::FindFirstAddress(int family)
{
        FileDescriptorCloser socket(::socket(family, SOCK_DGRAM, 0));
        if (!socket.IsSet())
                return -1;

        ifaliasreq request;
        memset(&request, 0, sizeof(ifaliasreq));

        strlcpy(request.ifra_name, Name(), IF_NAMESIZE);
        request.ifra_index = -1;
        request.ifra_addr.ss_family = AF_UNSPEC;

        if (ioctl(socket.Get(), B_SOCKET_GET_ALIAS, &request,
                sizeof(struct ifaliasreq)) < 0) {
                return -1;
        }

        return request.ifra_index;
}


status_t
BNetworkInterface::AddAddress(const BNetworkInterfaceAddress& address)
{
        return do_ifaliasreq(Name(), B_SOCKET_ADD_ALIAS, address);
}


status_t
BNetworkInterface::AddAddress(const BNetworkAddress& local)
{
        BNetworkInterfaceAddress address;
        address.SetAddress(local);

        return do_ifaliasreq(Name(), B_SOCKET_ADD_ALIAS, address);
}


status_t
BNetworkInterface::SetAddress(const BNetworkInterfaceAddress& address)
{
        return do_ifaliasreq(Name(), B_SOCKET_SET_ALIAS, address);
}


status_t
BNetworkInterface::RemoveAddress(const BNetworkInterfaceAddress& address)
{
        ifreq request;
        memcpy(&request.ifr_addr, &address.Address().SockAddr(),
                address.Address().Length());

        return do_request(family_from_interface_address(address), request, Name(),
                B_SOCKET_REMOVE_ALIAS);
}


status_t
BNetworkInterface::RemoveAddress(const BNetworkAddress& address)
{
        ifreq request;
        memcpy(&request.ifr_addr, &address.SockAddr(), address.Length());

        return do_request(address.Family(), request, Name(), B_SOCKET_REMOVE_ALIAS);
}


status_t
BNetworkInterface::RemoveAddressAt(int32 index)
{
        BNetworkInterfaceAddress address;
        status_t status = GetAddressAt(index, address);
        if (status != B_OK)
                return status;

        return RemoveAddress(address);
}


status_t
BNetworkInterface::GetHardwareAddress(BNetworkAddress& address)
{
        FileDescriptorCloser socket(::socket(AF_LINK, SOCK_DGRAM, 0));
        if (!socket.IsSet())
                return errno;

        ifreq request;
        strlcpy(request.ifr_name, Name(), IF_NAMESIZE);

        if (ioctl(socket.Get(), SIOCGIFADDR, &request, sizeof(struct ifreq)) < 0)
                return errno;

        address.SetTo(request.ifr_addr);
        return B_OK;
}


status_t
BNetworkInterface::AddRoute(const BNetworkRoute& route)
{
        int family = route.AddressFamily();
        if (family == AF_UNSPEC)
                return B_BAD_VALUE;

        ifreq request;
        request.ifr_route = route.RouteEntry();
        return do_request(family, request, Name(), SIOCADDRT);
}


status_t
BNetworkInterface::AddDefaultRoute(const BNetworkAddress& gateway)
{
        BNetworkRoute route;
        status_t result = route.SetGateway(gateway);
        if (result != B_OK)
                return result;

        route.SetFlags(RTF_STATIC | RTF_DEFAULT | RTF_GATEWAY);
        return AddRoute(route);
}


status_t
BNetworkInterface::RemoveRoute(const BNetworkRoute& route)
{
        int family = route.AddressFamily();
        if (family == AF_UNSPEC)
                return B_BAD_VALUE;

        return RemoveRoute(family, route);
}


status_t
BNetworkInterface::RemoveRoute(int family, const BNetworkRoute& route)
{
        ifreq request;
        request.ifr_route = route.RouteEntry();
        return do_request(family, request, Name(), SIOCDELRT);
}


status_t
BNetworkInterface::RemoveDefaultRoute(int family)
{
        BNetworkRoute route;
        route.SetFlags(RTF_STATIC | RTF_DEFAULT);
        return RemoveRoute(family, route);
}


status_t
BNetworkInterface::GetRoutes(int family,
        BObjectList<BNetworkRoute, true>& routes) const
{
        return BNetworkRoute::GetRoutes(family, Name(), routes);
}


status_t
BNetworkInterface::GetDefaultRoute(int family, BNetworkRoute& route) const
{
        return BNetworkRoute::GetDefaultRoute(family, Name(), route);
}


status_t
BNetworkInterface::GetDefaultGateway(int family, BNetworkAddress& gateway) const
{
        return BNetworkRoute::GetDefaultGateway(family, Name(), gateway);
}


status_t
BNetworkInterface::AutoConfigure(int family)
{
        BMessage message(kMsgConfigureInterface);
        message.AddString("device", Name());

        BMessage address;
        address.AddInt32("family", family);
        address.AddBool("auto_config", true);
        message.AddMessage("address", &address);

        BMessenger networkServer(kNetServerSignature);
        BMessage reply;
        status_t status = networkServer.SendMessage(&message, &reply);
        if (status == B_OK)
                reply.FindInt32("status", &status);

        return status;
}