/*
Copyright (C) 2011 Srivats P.

This file is part of "Ostinato"

This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

#include "linuxport.h"

#include "interfaceinfo.h"
#include "linuxutils.h"

#ifdef Q_OS_LINUX

#include "../common/qtport.h"

#include <QByteArray>
#include <QHash>
#include <QTime>

#include <errno.h>
#include <fcntl.h>
#include <netlink/route/addr.h>
#include <netlink/route/link.h>
#include <netlink/route/route.h>
#if (LIBNL_VER_NUM > 0x0302) || ((LIBNL_VER_NUM == 0x0302) && (LIBNL_VER_MIC >= 26))
#include <net/if.h>
#endif
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <linux/rtnetlink.h>

QList<LinuxPort*> LinuxPort::allPorts_;
LinuxPort::StatsMonitor *LinuxPort::monitor_;

const quint32 kMaxValue32 = 0xffffffff;
const quint64 kMaxValue64 = 0xffffffffffffffffULL;

#ifdef HAVE_IFLA_STATS64
#define X_IFLA_STATS IFLA_STATS64
typedef struct rtnl_link_stats64 x_rtnl_link_stats;
#else
#define X_IFLA_STATS IFLA_STATS
typedef struct rtnl_link_stats x_rtnl_link_stats;
#endif

nl_sock  *LinuxPort::netSock_{nullptr};
nl_cache *LinuxPort::linkCache_{nullptr};
nl_cache *LinuxPort::addressCache_{nullptr};
nl_cache *LinuxPort::routeCache_{nullptr};

LinuxPort::LinuxPort(int id, const char *device)
    : PcapPort(id, device) 
{
    isPromisc_ = true;
    clearPromisc_ = false;

    populateInterfaceInfo();

    // We don't need per port Rx/Tx monitors for Linux
    // No need to stop them because we start them only in
    // PcapPort::init which has not yet been called
    delete monitorRx_;
    delete monitorTx_;
    monitorRx_ = monitorTx_ = NULL;

    // We have one monitor for both Rx/Tx of all ports
    if (!monitor_)
        monitor_ = new StatsMonitor();

    data_.set_is_exclusive_control(hasExclusiveControl());
    minPacketSetSize_ = 16;

    qDebug("adding dev to all ports list <%s>", device);
    allPorts_.append(this);

    // A port can support either 32 or 64 bit stats - we will attempt
    // to guess this for each port and initialize this variable at 
    // run time when the counter wraps around
    maxStatsValue_ = 0;
}

LinuxPort::~LinuxPort()
{
    qDebug("In %s", __FUNCTION__);

    allPorts_.removeAll(this);

    if (monitor_->isRunning())
    {
        monitor_->stop();
        monitor_->wait();
    }

    if (clearPromisc_)
    {
        int sd = socket(AF_INET, SOCK_DGRAM, 0);
        struct ifreq ifr;

        memset(&ifr, 0, sizeof(ifr));
        strncpy(ifr.ifr_name, name(), sizeof(ifr.ifr_name));

        if (ioctl(sd, SIOCGIFFLAGS, &ifr) != -1) 
        {
            if (ifr.ifr_flags & IFF_PROMISC)
            {
                ifr.ifr_flags &= ~IFF_PROMISC;
                if (ioctl(sd, SIOCSIFFLAGS, &ifr) == -1)
                    qDebug("Failed clearing promisc flag. SIOCSIFFLAGS failed: %s",
                        strerror(errno));
            }
        }
        else
            qDebug("Failed clearing promisc flag. SIOCGIFFLAGS failed: %s",
                    strerror(errno));

        close(sd);
    }
}

void LinuxPort::fetchHostNetworkInfo()
{
    netSock_ = nl_socket_alloc();
    if (!netSock_) {
        qWarning("Failed to open netlink socket");
        return;
    }

    if (nl_connect(netSock_, NETLINK_ROUTE) < 0) {
        qWarning("Failed to connect netlink socket");
        return;
    }

    if (rtnl_link_alloc_cache(netSock_, AF_UNSPEC, &linkCache_) < 0) {
        qWarning("Failed to populate link cache");
        return;
    }

    if (rtnl_addr_alloc_cache(netSock_, &addressCache_) < 0) {
        qWarning("Failed to populate addr cache");
        return;
    }

    if (rtnl_route_alloc_cache(netSock_, AF_UNSPEC, 0, &routeCache_) < 0) {
        qWarning("Failed to populate addr cache");
        return;
    }
}

void LinuxPort::freeHostNetworkInfo()
{
    nl_cache_put(routeCache_);
    nl_cache_put(addressCache_);
    nl_cache_put(linkCache_);
    nl_socket_free(netSock_);
}

void LinuxPort::init()
{
    if (!monitor_->isRunning())
        monitor_->start();

    monitor_->waitForSetupFinished();

    if (!isPromisc_)
        addNote("Non Promiscuous Mode");

    AbstractPort::init();
}

OstProto::LinkState LinuxPort::linkState()
{
    return linkState_; 
}

bool LinuxPort::hasExclusiveControl() 
{
    // TODO
    return false;
}

bool LinuxPort::setExclusiveControl(bool /*exclusive*/) 
{
    // TODO
    return false;
}

void LinuxPort::populateInterfaceInfo()
{
    //
    // Find Mac
    //
    if (!linkCache_) {
        qWarning("rtnetlink link cache empty for %s", name());
        return;
    }

    rtnl_link *link = rtnl_link_get_by_name(linkCache_, name());
    if (!link) {
        qWarning("rtnetlink link not found for %s", name());
        return;
    }

    nl_addr *addr = rtnl_link_get_addr(link);
    if (!addr) {
        qWarning("rtnetlink mac addr not found for %s", name());
        return;
    }

    if (nl_addr_get_family(addr) != AF_LLC) {
        qWarning("unexpected mac family found for %s:%d",
                name(), nl_addr_get_family(addr));
        rtnl_link_put(link);
        return;
    }

    if (nl_addr_get_prefixlen(addr) != 48) {
        qWarning("unexpected mac length for %s:%d",
                name(), nl_addr_get_prefixlen(addr));
        rtnl_link_put(link);
        return;
    }

    quint64 mac = qFromBigEndian<quint64>(nl_addr_get_binary_addr(addr)) >> 16;
    if (!mac) {
        qWarning("zero mac for %s - skipping", name());
        rtnl_link_put(link);
        return;
    }

    interfaceInfo_ = new InterfaceInfo;
    interfaceInfo_->speed = sysfsAttrib(name(), "speed").toDouble();
    interfaceInfo_->mtu = rtnl_link_get_mtu(link);

    int ifIndex = rtnl_link_get_ifindex(link);
    rtnl_link_put(link);

    interfaceInfo_->mac = mac;

    //
    // Find gateways
    //
    quint32 gw4 = 0;
    UInt128 gw6 = 0;
    for (rtnl_route *rt = routeCache_ ? (rtnl_route*) nl_cache_get_first(routeCache_) : 0;
            rt && (!gw4 || !gw6);
            rt = (rtnl_route*) nl_cache_get_next(OBJ_CAST(rt))) {
        if (rtnl_route_get_table(rt) != RT_TABLE_MAIN) // we want only main RTT
            continue;

        nl_addr *pfx = rtnl_route_get_dst(rt);
        if (nl_addr_get_len(pfx)) // default route has len = 0
            continue;

        if (!rtnl_route_get_nnexthops(rt)) // at least one nh is required
            continue;

        rtnl_nexthop *nh = rtnl_route_nexthop_n(rt, 0);
        if (rtnl_route_nh_get_ifindex(nh) != ifIndex) // ignore gw on other links
            continue;

        if (!gw4 && rtnl_route_get_family(rt) == AF_INET) {
            nl_addr *gwa = rtnl_route_nh_get_gateway(nh);
            if (gwa)
                gw4 = qFromBigEndian<quint32>(nl_addr_get_binary_addr(gwa));
        }
        else if (!gw6 && rtnl_route_get_family(rt) == AF_INET6) {
            nl_addr *gwa = rtnl_route_nh_get_gateway(nh);
            if (gwa)
                gw6 = UInt128((quint8*) nl_addr_get_binary_addr(gwa));
        }
    }

    //
    // Find self IP
    //
    if (!addressCache_) {
        qWarning("rtnetlink address cache empty for %s", name());
        return;
    }
    rtnl_addr *l3addr = (rtnl_addr*) nl_cache_get_first(addressCache_);
    while (l3addr) {
        if (rtnl_addr_get_ifindex(l3addr) == ifIndex) {
            if (rtnl_addr_get_family(l3addr) == AF_INET) {
                Ip4Config ip;
                ip.address = qFromBigEndian<quint32>(
                                nl_addr_get_binary_addr(
                                    rtnl_addr_get_local(l3addr)));
                ip.prefixLength = rtnl_addr_get_prefixlen(l3addr);
                ip.gateway = gw4;
                interfaceInfo_->ip4.append(ip);
            }
            else if (rtnl_addr_get_family(l3addr) == AF_INET6) {
                Ip6Config ip;
                ip.address = UInt128((quint8*)nl_addr_get_binary_addr(
                                                    rtnl_addr_get_local(l3addr)));
                ip.prefixLength = rtnl_addr_get_prefixlen(l3addr);
                ip.gateway = gw6;
                interfaceInfo_->ip6.append(ip);
            }
        }
        l3addr = (rtnl_addr*) nl_cache_get_next((nl_object*)l3addr);
    }
}

LinuxPort::StatsMonitor::StatsMonitor()
    : QThread()
{
    setObjectName("StatsMon");
    stop_ = false;
    setupDone_ = false;
    ioctlSocket_ = socket(AF_INET, SOCK_DGRAM, 0);
    Q_ASSERT(ioctlSocket_ >= 0);
}

LinuxPort::StatsMonitor::~StatsMonitor()
{
    close(ioctlSocket_);
}

void LinuxPort::StatsMonitor::run()
{
    if (netlinkStats() < 0)
    {
        qDebug("netlink stats not available - using /proc stats");
        procStats();
    }
}

void LinuxPort::StatsMonitor::procStats()
{
    PortStats **portStats;
    int fd;
    QByteArray buf;
    int len;
    char *p, *end;
    int count, index;
    const char* fmtopt[] = {
        "%llu%llu%llu%llu%llu%llu%u%u%llu%llu%u%u%u%u%u%u\n",
        "%llu%llu%llu%llu%llu%llu%n%n%llu%llu%u%u%u%u%u%n\n",
    };
    const char *fmt;

    //
    // We first setup stuff before we start polling for stats
    //
    fd = open("/proc/net/dev", O_RDONLY);
    if (fd < 0)
    {
        qWarning("Unable to open /proc/net/dev - no stats will be available");
        return;
    }

    buf.fill('\0', 8192);
    len = read(fd, (void*) buf.data(), buf.size());
    if (len < 0)
    {
        qWarning("initial buffer size is too small. no stats will be available");
        return;
    }

    p = buf.data();
    end = p + len;

    // Select scanf format
    if (strstr(buf, "compressed"))
        fmt = fmtopt[0];
    else 
        fmt = fmtopt[1];

    // Count number of lines - number of ports is 2 less than number of lines
    count = 0;
    while (p < end)
    {
        if (*p == '\n')
            count++;
        p++;
    }
    count -= 2;

    if (count <= 0)
    {
        qWarning("no ports in /proc/dev/net - no stats will be available");
        return;
    }

    portStats = (PortStats**) calloc(count, sizeof(PortStats));
    Q_ASSERT(portStats != NULL);

    //
    // Populate the port stats array
    //
    p = buf.data();

    // Skip first two lines
    while (*p != '\n')
        p++;
    p++;
    while (*p != '\n')
        p++;
    p++;

    index = 0;
    while (p < end)
    {
        char* q;

        // Skip whitespace
        while ((p < end) && (*p == ' '))
            p++;

        q = p;

        // Get interface name
        while ((q < end) && (*q != ':') && (*q != '\n'))
            q++;

        if ((q < end) && (*q == ':'))
        {
            foreach(LinuxPort* port, allPorts_)
            {
                if (strncmp(port->name(), p, int(q-p)) == 0)
                {
                    portStats[index] = &(port->stats_);

                    if (setPromisc(port->name()))
                        port->clearPromisc_ = true;
                    else
                        port->isPromisc_ = false;

                    break;
                }
            }
        }
        index++;

        // Skip till newline
        p = q;
        while (*p != '\n')
            p++;
        p++;
    }
    Q_ASSERT(index == count);

    qDebug("stats for %d ports setup", count);
    setupDone_ = true;

    //
    // We are all set - Let's start polling for stats!
    //
    while (!stop_)
    {
        lseek(fd, 0, SEEK_SET);
        len = read(fd, (void*) buf.data(), buf.size());
        if (len < 0)
        {
            if (buf.size() > 1*1024*1024)
            {
                qWarning("buffer size hit limit. no more stats");
                return;
            }
            qDebug("doubling buffer size. curr = %d", buf.size());
            buf.resize(buf.size() * 2);
            continue;
        }

        p = buf.data();
        end = p + len;

        // Skip first two lines
        while (*p != '\n')
            p++;
        p++;
        while (*p != '\n')
            p++;
        p++;

        index = 0;
        while (p < end)
        {
            uint dummy;
            quint64 rxBytes, rxPkts;
            quint64 rxErrors, rxDrops, rxFifo, rxFrame;
            quint64 txBytes, txPkts;

            // Skip interface name - we assume the number and order of ports
            // won't change since we parsed the output before we started polling
            while ((p < end) && (*p != ':') && (*p != '\n'))
                p++;
            if (p >= end)
                break;
            if (*p == '\n')
            {
                index++;
                continue;
            }
            p++;

            sscanf(p, fmt,
                    &rxBytes, &rxPkts, &rxErrors, &rxDrops, &rxFifo, &rxFrame, 
                        &dummy, &dummy,
                    &txBytes, &txPkts, &dummy, &dummy, &dummy, &dummy, &dummy, 
                        &dummy);

            if (index < count)
            {
                AbstractPort::PortStats *stats = portStats[index];
                if (stats)
                {
                    // TODO: fix the pps/Bps calc similar to netlink stats
                    stats->rxPps = 
                        ((rxPkts >= stats->rxPkts) ? 
                                rxPkts - stats->rxPkts : 
                                rxPkts + (kMaxValue32 - stats->rxPkts))
                        / kRefreshFreq_;
                    stats->rxBps = 
                        ((rxBytes >= stats->rxBytes) ? 
                                rxBytes - stats->rxBytes : 
                                rxBytes + (kMaxValue32 - stats->rxBytes))
                        / kRefreshFreq_;
                    stats->rxPkts  = rxPkts;
                    stats->rxBytes = rxBytes;
                    stats->txPps = 
                        ((txPkts >= stats->txPkts) ? 
                                txPkts - stats->txPkts : 
                                txPkts + (kMaxValue32 - stats->txPkts))
                        / kRefreshFreq_;
                    stats->txBps = 
                        ((txBytes >= stats->txBytes) ? 
                                txBytes - stats->txBytes : 
                                txBytes + (kMaxValue32 - stats->txBytes))
                        / kRefreshFreq_;
                    stats->txPkts  = txPkts;
                    stats->txBytes = txBytes;

                    stats->rxDrops = rxDrops;
                    stats->rxErrors = rxErrors;
                    stats->rxFifoErrors = rxFifo;
                    stats->rxFrameErrors = rxFrame;
                }
            }

            while (*p != '\n')
                p++;
            p++;
            index++;
        }
        QThread::sleep(kRefreshFreq_);
    }

    free(portStats);
}

int LinuxPort::StatsMonitor::netlinkStats()
{
    QHash<uint, PortStats*> portStats;
    QHash<uint, quint64*> portMaxStatsValue;
    QHash<uint, OstProto::LinkState*> linkState;
    int fd;
    struct sockaddr_nl local;
    struct sockaddr_nl kernel;
    QByteArray buf;
    int len, count;
    struct {
        struct nlmsghdr nlh;
        struct rtgenmsg rtg;
    } ifListReq;
    struct iovec iov;
    struct msghdr msg;
    struct nlmsghdr *nlm;
    bool done = false;

    //
    // We first setup stuff before we start polling for stats
    //
    fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    if (fd < 0)
    {
        qWarning("Unable to open netlink socket (errno %d)", errno);
        return -1;
    }

    memset(&local, 0, sizeof(local));
    local.nl_family = AF_NETLINK;

    if (bind(fd, (struct sockaddr*) &local, sizeof(local)) < 0)
    {
        qWarning("Unable to bind netlink socket (errno %d)", errno);
        return -1;
    }

    memset(&ifListReq, 0, sizeof(ifListReq));
    ifListReq.nlh.nlmsg_len = sizeof(ifListReq);
    ifListReq.nlh.nlmsg_type = RTM_GETLINK;
    ifListReq.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
    ifListReq.nlh.nlmsg_pid = 0;
    ifListReq.rtg.rtgen_family = AF_PACKET;

    buf.fill('\0', 1024);

    msg.msg_name = &kernel;
    msg.msg_namelen = sizeof(kernel);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_control = NULL;
    msg.msg_controllen = 0;
    msg.msg_flags = 0;

    qDebug("nlmsg_flags = %x", ifListReq.nlh.nlmsg_flags);

    if (send(fd, (void*)&ifListReq, sizeof(ifListReq), 0) < 0)
    {
        qWarning("Unable to send GETLINK request (errno %d)", errno);
        return -1;
    }

    count = 0;

_retry:

    // Find required size of buffer and resize accordingly
    while (1)
    {
        iov.iov_base = buf.data();
        iov.iov_len = buf.size();
        msg.msg_flags = 0;

        // Peek at reply to check buffer size required
        len = recvmsg(fd, &msg, MSG_PEEK|MSG_TRUNC);

        if (len < 0)
        {
            if (errno == EINTR || errno == EAGAIN)
                continue;

            qWarning("netlink recv error %d", errno);
            return -1;
        }
        else if (len == 0)
        {
            qWarning("netlink closed the socket on my face!");
            return -1;
        }
        else 
        {
            if (msg.msg_flags & MSG_TRUNC)
            {
                if (len == buf.size()) // Older Kernel returns truncated size
                {
                    qDebug("netlink buffer size %d not enough", buf.size());
                    qDebug("retrying with double the size");
                    // Double the size and retry
                    buf.resize(buf.size()*2);
                    continue;
                }
                else // Newer Kernel returns actual size required
                {
                    qDebug("netlink required buffer size = %d", len);
                    buf.resize(len);
                    continue;
                }
            }
            else
                qDebug("buffer size %d enough for netlink", buf.size());

            break;
        }
    }

    msg.msg_flags = 0;

    // Actually receive the reply now
    len = recvmsg(fd, &msg, 0);

    if (len < 0)
    {
        if (errno == EINTR || errno == EAGAIN)
            goto _retry;
        qWarning("netlink recv error %d", errno);
        return -1;
    }
    else if (len == 0)
    {
        qWarning("netlink socket closed unexpectedly");
        return -1;
    }

    //
    // Populate the port stats hash table
    //
    nlm = (struct nlmsghdr*) buf.data();
    while (NLMSG_OK(nlm, (uint)len))
    {
        struct ifinfomsg *ifi;
        struct rtattr *rta;
        int rtaLen;
        char ifname[64] = "";

        if (nlm->nlmsg_type == NLMSG_DONE)
        {
            done = true;
            break;
        }

        if (nlm->nlmsg_type == NLMSG_ERROR)
        {
            struct nlmsgerr *err = (struct nlmsgerr*) NLMSG_DATA(nlm);
            qDebug("RTNETLINK error %d", err->error);
            done = true;
            break;
        }

        Q_ASSERT(nlm->nlmsg_type == RTM_NEWLINK);

        ifi = (struct ifinfomsg*) NLMSG_DATA(nlm);
        rta = IFLA_RTA(ifi);
        rtaLen = len - NLMSG_LENGTH(sizeof(*ifi));
        while (RTA_OK(rta, rtaLen))
        {
            if (rta->rta_type == IFLA_IFNAME)
            {
                strncpy(ifname, (char*)RTA_DATA(rta), RTA_PAYLOAD(rta));
                ifname[RTA_PAYLOAD(rta)] = 0;
                break;
            }
            rta = RTA_NEXT(rta, rtaLen);
        }

        qDebug("if: %s(%d)", ifname, ifi->ifi_index);
        foreach(LinuxPort* port, allPorts_)
        {
            if (strcmp(port->name(), ifname) == 0)
            {
                portStats[uint(ifi->ifi_index)] = &(port->stats_);
                portMaxStatsValue[uint(ifi->ifi_index)] = 
                        &(port->maxStatsValue_);
                linkState[uint(ifi->ifi_index)] = &(port->linkState_);

                if (setPromisc(port->name()))
                    port->clearPromisc_ = true;
                else
                    port->isPromisc_ = false;

                count++;
                break;
            }
        }
        nlm = NLMSG_NEXT(nlm, len);
    }

    if (!done)
        goto _retry;

    qDebug("port count = %d\n", count);
    if (count <= 0)
    {
        qWarning("no ports in RTNETLINK GET_LINK - no stats will be available");
        return - 1;
    }

    qDebug("stats for %d ports setup", count);
    setupDone_ = true;

    //
    // We are all set - Let's start polling for stats!
    //
    while (!stop_)
    {
        if (send(fd, (void*)&ifListReq, sizeof(ifListReq), 0) < 0)
        {
            qWarning("Unable to send GETLINK request (errno %d)", errno);
            goto _try_later;
        }

        done = false;

_retry_recv:
        msg.msg_flags = 0;
        len = recvmsg(fd, &msg, 0);

        if (len < 0)
        {
            if (errno == EINTR || errno == EAGAIN)
                goto _retry_recv;
            qWarning("netlink recv error %d", errno);
            break;
        }
        else if (len == 0)
        {
            qWarning("netlink socket closed unexpectedly");
            break;
        }

        nlm = (struct nlmsghdr*) buf.data();
        while (NLMSG_OK(nlm, (uint)len))
        {
            struct ifinfomsg *ifi;
            struct rtattr *rta;
            int rtaLen;

            if (nlm->nlmsg_type == NLMSG_DONE) 
            {
                done = true;
                break;
            }

            if (nlm->nlmsg_type == NLMSG_ERROR)
            {
                struct nlmsgerr *err = (struct nlmsgerr*) NLMSG_DATA(nlm);
                qDebug("RTNETLINK error: %s", strerror(-err->error));
                done = true;
                break;
            }

            Q_ASSERT(nlm->nlmsg_type == RTM_NEWLINK);

            ifi = (struct ifinfomsg*) NLMSG_DATA(nlm);
            rta = IFLA_RTA(ifi);
            rtaLen = len - NLMSG_LENGTH(sizeof(*ifi));
            while (RTA_OK(rta, rtaLen))
            {
                if (rta->rta_type == X_IFLA_STATS)
                {
                    x_rtnl_link_stats *rtnlStats = 
                            (x_rtnl_link_stats*) RTA_DATA(rta);
                    AbstractPort::PortStats *stats = portStats[ifi->ifi_index];
                    quint64 *maxStatsValue = portMaxStatsValue[ifi->ifi_index];
                    OstProto::LinkState *state = linkState[ifi->ifi_index];

                    if (!stats)
                        break;

                    if (rtnlStats->rx_packets >= stats->rxPkts) {
                        stats->rxPps = (rtnlStats->rx_packets - stats->rxPkts)
                                            / kRefreshFreq_;
                    }
                    else {
                        if (*maxStatsValue == 0) {
                            *maxStatsValue = stats->rxPkts > kMaxValue32 ?
                                kMaxValue64 : kMaxValue32;
                        }
                        stats->rxPps = ((*maxStatsValue - stats->rxPkts)
                                            + rtnlStats->rx_packets)
                                        / kRefreshFreq_;
                    }

                    if (rtnlStats->rx_bytes >= stats->rxBytes) {
                        stats->rxBps = (rtnlStats->rx_bytes - stats->rxBytes)
                                            / kRefreshFreq_;
                    }
                    else {
                        if (*maxStatsValue == 0) {
                            *maxStatsValue = stats->rxBytes > kMaxValue32 ?
                                kMaxValue64 : kMaxValue32;
                        }
                        stats->rxBps = ((*maxStatsValue - stats->rxBytes)
                                            + rtnlStats->rx_bytes)
                                        / kRefreshFreq_;
                    }

                    stats->rxPkts  = rtnlStats->rx_packets;
                    stats->rxBytes = rtnlStats->rx_bytes;

                    if (rtnlStats->tx_packets >= stats->txPkts) {
                        stats->txPps = (rtnlStats->tx_packets - stats->txPkts)
                                            / kRefreshFreq_;
                    }
                    else {
                        if (*maxStatsValue == 0) {
                            *maxStatsValue = stats->txPkts > kMaxValue32 ?
                                kMaxValue64 : kMaxValue32;
                        }
                        stats->txPps = ((*maxStatsValue - stats->txPkts)
                                            + rtnlStats->tx_packets)
                                        / kRefreshFreq_;
                    }

                    if (rtnlStats->tx_bytes >= stats->txBytes) {
                        stats->txBps = (rtnlStats->tx_bytes - stats->txBytes)
                                            / kRefreshFreq_;
                    }
                    else {
                        if (*maxStatsValue == 0) {
                            *maxStatsValue = stats->txBytes > kMaxValue32 ?
                                kMaxValue64 : kMaxValue32;
                        }
                        stats->txBps = ((*maxStatsValue - stats->txBytes)
                                            + rtnlStats->tx_bytes)
                                        / kRefreshFreq_;
                    }

                    stats->txPkts  = rtnlStats->tx_packets;
                    stats->txBytes = rtnlStats->tx_bytes;

                    // TODO: export detailed error stats
                    stats->rxDrops =   rtnlStats->rx_dropped 
                                     + rtnlStats->rx_missed_errors;
                    stats->rxErrors = rtnlStats->rx_errors;
                    stats->rxFifoErrors = rtnlStats->rx_fifo_errors;
                    stats->rxFrameErrors =   rtnlStats->rx_crc_errors
                                           + rtnlStats->rx_length_errors
                                           + rtnlStats->rx_over_errors
                                           + rtnlStats->rx_frame_errors;

                    Q_ASSERT(state);  
                    *state = ifi->ifi_flags & IFF_RUNNING ?
                        OstProto::LinkStateUp : OstProto::LinkStateDown;

                    break;
                }
                rta = RTA_NEXT(rta, rtaLen);
            }
            nlm = NLMSG_NEXT(nlm, len);
        }

        if (!done)
            goto _retry_recv;

_try_later:
        QThread::sleep(kRefreshFreq_);
    }

    portStats.clear();
    linkState.clear();

    return 0;
}

int LinuxPort::StatsMonitor::setPromisc(const char * portName)
{ 
    struct ifreq ifr;

    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, portName, sizeof(ifr.ifr_name));

    if (ioctl(ioctlSocket_, SIOCGIFFLAGS, &ifr) != -1)
    {
        if ((ifr.ifr_flags & IFF_PROMISC) == 0)
        {
            ifr.ifr_flags |= IFF_PROMISC;
            if (ioctl(ioctlSocket_, SIOCSIFFLAGS, &ifr) != -1)
            {
                return 1;
            }
            else
            {
                qDebug("%s: failed to set promisc; "
                        "SIOCSIFFLAGS failed (%s)", 
                        portName, strerror(errno));
            }
        }
    }
    else
    {
        qDebug("%s: failed to set promisc; SIOCGIFFLAGS failed (%s)",
                portName, strerror(errno));
    }

    return 0;
}

void LinuxPort::StatsMonitor::stop()
{
    stop_ = true;
}

bool LinuxPort::StatsMonitor::waitForSetupFinished(int msecs)
{
    QTime t;

    t.start();
    while (!setupDone_)
    {
        if (t.elapsed() > msecs)
            return false;

        QThread::msleep(10);
    }

    return true;
}
#endif