1/*
2 * Copyright (C) 2012 Intel Corporation. All rights reserved.
3 * Copyright (C) 2013 Samsung Electronics. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24 * THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "NetworkStateNotifier.h"
29
30#include "Logging.h"
31#include <Ecore.h>
32#include <Eeze.h>
33#include <Eeze_Net.h>
34#include <asm/types.h>
35#include <string.h>
36#include <sys/socket.h>
37#include <unistd.h>
38
39// Must come at the end so that sys/socket.h is included first.
40#include <linux/netlink.h>
41#include <linux/rtnetlink.h>
42
43static const char udevLoopBackInterfaceSysPath[] = "lo";
44static const char udevOperstateAttribute[] = "operstate";
45static const char udevOperstateUp[] = "up";
46static const size_t bufferSize = 4096;
47
48namespace WebCore {
49
50void NetworkStateNotifier::updateState()
51{
52    // Assume that we're offline until proven otherwise.
53    m_isOnLine = false;
54
55    LOG(Network, "Checking online state...");
56
57    Eina_List* networkInterfaces = eeze_net_list();
58
59    Eina_List* list;
60    void* data;
61    EINA_LIST_FOREACH(networkInterfaces, list, data) {
62        Eeze_Net* networkInterface = static_cast<Eeze_Net*>(data);
63
64        // Skip Loopback interface.
65        const char* syspath = eeze_net_syspath_get(networkInterface);
66        if (!syspath || !strcmp(syspath, udevLoopBackInterfaceSysPath))
67            continue;
68
69        // Skip interfaces that are not up.
70        const char* state = eeze_net_attribute_get(networkInterface, udevOperstateAttribute);
71        LOG(Network, "Found network interface \"%s\" with state: \"%s\"", syspath, state);
72        if (!state || strcmp(state, udevOperstateUp))
73            continue;
74
75        // Check if the interface has an IP address.
76        eeze_net_scan(networkInterface);
77        if (eeze_net_addr_get(networkInterface, EEZE_NET_ADDR_TYPE_IP) || eeze_net_addr_get(networkInterface, EEZE_NET_ADDR_TYPE_IP6)) {
78#if !LOG_DISABLED
79            const char* ipAddress = eeze_net_addr_get(networkInterface, EEZE_NET_ADDR_TYPE_IP);
80            if (!ipAddress)
81                ipAddress = eeze_net_addr_get(networkInterface, EEZE_NET_ADDR_TYPE_IP6);
82            LOG(Network, "Network interface at %s has the following IP address: %s", syspath, ipAddress);
83#endif
84            m_isOnLine = true;
85            break;
86        }
87    }
88
89    LOG(Network, "Detected online state is \"%s\"", m_isOnLine ? "online" : "offline");
90
91    EINA_LIST_FREE(networkInterfaces, data)
92        eeze_net_free(static_cast<Eeze_Net*>(data));
93}
94
95void NetworkStateNotifier::networkInterfaceChanged()
96{
97    bool wasOnline = m_isOnLine;
98    updateState();
99
100    if (wasOnline != m_isOnLine && m_networkStateChangedFunction)
101        m_networkStateChangedFunction();
102}
103
104Eina_Bool NetworkStateNotifier::readSocketCallback(void* userData, Ecore_Fd_Handler* handler)
105{
106    NetworkStateNotifier* notifier = static_cast<NetworkStateNotifier*>(userData);
107
108    int sock = ecore_main_fd_handler_fd_get(handler);
109    char buffer[bufferSize];
110
111    bool detectedChange = false;
112
113    nlmsghdr* nlh = reinterpret_cast<nlmsghdr*>(buffer);
114    while (true) {
115        ssize_t length = recv(sock, nlh, bufferSize, MSG_DONTWAIT);
116        if (!length) {
117            LOG_ERROR("NETLINK socket was closed unexpectedly.");
118            notifier->m_fdHandler = 0;
119            return ECORE_CALLBACK_CANCEL;
120        }
121        if (length == -1) {
122            if (errno == EINTR)
123                continue;
124            if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
125                break;
126            LOG_ERROR("recv on NETLINK socket failed.");
127            notifier->m_fdHandler = 0;
128            return ECORE_CALLBACK_CANCEL;
129        }
130        while ((NLMSG_OK(nlh, static_cast<unsigned>(length))) && (nlh->nlmsg_type != NLMSG_DONE)) {
131            if (nlh->nlmsg_type == NLMSG_ERROR) {
132                LOG_ERROR("Unexpected NETLINK error %d.", reinterpret_cast<struct nlmsgerr*>(NLMSG_DATA(nlh))->error);
133                notifier->m_fdHandler = 0;
134                return ECORE_CALLBACK_CANCEL;
135            }
136            if ((nlh->nlmsg_type == RTM_NEWADDR && !notifier->m_isOnLine) || (nlh->nlmsg_type == RTM_DELADDR && notifier->m_isOnLine))
137                detectedChange = true;
138            nlh = NLMSG_NEXT(nlh, length);
139        }
140    }
141
142    if (detectedChange)
143        notifier->networkInterfaceChanged();
144
145    return ECORE_CALLBACK_RENEW;
146}
147
148NetworkStateNotifier::~NetworkStateNotifier()
149{
150    if (m_fdHandler)
151        ecore_main_fd_handler_del(m_fdHandler);
152    if (m_netlinkSocket != -1) {
153        int rv = 0;
154        do {
155            rv = close(m_netlinkSocket);
156        } while (rv == -1 && errno == EINTR);
157    }
158    eeze_shutdown();
159}
160
161NetworkStateNotifier::NetworkStateNotifier()
162    : m_isOnLine(false)
163    , m_networkStateChangedFunction(0)
164    , m_netlinkSocket(-1)
165    , m_fdHandler(0)
166{
167    if (eeze_init() < 0) {
168        LOG_ERROR("Failed to initialize eeze library.");
169        return;
170    }
171
172    updateState();
173
174    // Watch for network address changes to keep online state up-to-date.
175    m_netlinkSocket = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
176    if (m_netlinkSocket == -1) {
177        LOG_ERROR("Couldn't create NETLINK socket.");
178        return;
179    }
180
181    sockaddr_nl addr;
182    memset(&addr, 0, sizeof(addr));
183    addr.nl_family = AF_NETLINK;
184    addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
185
186    if (bind(m_netlinkSocket, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == -1) {
187        LOG_ERROR("Couldn't bind to NETLINK socket.");
188        return;
189    }
190
191    m_fdHandler = ecore_main_fd_handler_add(m_netlinkSocket, ECORE_FD_READ, readSocketCallback, this, 0, 0);
192}
193
194} // namespace WebCore
195