/*
* Copyright (C) 2005-2013 Team XBMC
* http://xbmc.org
*
* This Program 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 2, 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 XBMC; see the file COPYING. If not, see
* .
*
*/
#include
#include
#include
#include "Network.h"
#include "ApplicationMessenger.h"
#include "network/NetworkServices.h"
#include "utils/log.h"
#ifdef TARGET_WINDOWS
#include "utils/SystemInfo.h"
#include "win32/WIN32Util.h"
#include "utils/CharsetConverter.h"
#endif
#include "utils/StringUtils.h"
/* slightly modified in_ether taken from the etherboot project (http://sourceforge.net/projects/etherboot) */
bool in_ether (const char *bufp, unsigned char *addr)
{
if (strlen(bufp) != 17)
return false;
char c;
const char *orig;
unsigned char *ptr = addr;
unsigned val;
int i = 0;
orig = bufp;
while ((*bufp != '\0') && (i < 6))
{
val = 0;
c = *bufp++;
if (isdigit(c))
val = c - '0';
else if (c >= 'a' && c <= 'f')
val = c - 'a' + 10;
else if (c >= 'A' && c <= 'F')
val = c - 'A' + 10;
else
return false;
val <<= 4;
c = *bufp;
if (isdigit(c))
val |= c - '0';
else if (c >= 'a' && c <= 'f')
val |= c - 'a' + 10;
else if (c >= 'A' && c <= 'F')
val |= c - 'A' + 10;
else if (c == ':' || c == '-' || c == 0)
val >>= 4;
else
return false;
if (c != 0)
bufp++;
*ptr++ = (unsigned char) (val & 0377);
i++;
if (*bufp == ':' || *bufp == '-')
bufp++;
}
if (bufp - orig != 17)
return false;
return true;
}
int NetworkAccessPoint::getQuality() const
{
// Cisco dBm lookup table (partially nonlinear)
// Source: "Converting Signal Strength Percentage to dBm Values, 2002"
int quality;
if (m_dBm >= -10) quality = 100;
else if (m_dBm >= -20) quality = 85 + (m_dBm + 20);
else if (m_dBm >= -30) quality = 77 + (m_dBm + 30);
else if (m_dBm >= -60) quality = 48 + (m_dBm + 60);
else if (m_dBm >= -98) quality = 13 + (m_dBm + 98);
else if (m_dBm >= -112) quality = 1 + (m_dBm + 112);
else quality = 0;
return quality;
}
int NetworkAccessPoint::FreqToChannel(float frequency)
{
int IEEE80211Freq[] = {2412, 2417, 2422, 2427, 2432,
2437, 2442, 2447, 2452, 2457,
2462, 2467, 2472, 2484,
5180, 5200, 5210, 5220, 5240, 5250,
5260, 5280, 5290, 5300, 5320,
5745, 5760, 5765, 5785, 5800, 5805, 5825};
int IEEE80211Ch[] = { 1, 2, 3, 4, 5,
6, 7, 8, 9, 10,
11, 12, 13, 14,
36, 40, 42, 44, 48, 50,
52, 56, 58, 60, 64,
149, 152, 153, 157, 160, 161, 165};
// Round frequency to the nearest MHz
int mod_chan = (int)(frequency / 1000000 + 0.5f);
for (unsigned int i = 0; i < sizeof(IEEE80211Freq) / sizeof(int); ++i)
{
if (IEEE80211Freq[i] == mod_chan)
return IEEE80211Ch[i];
}
return 0; // unknown
}
CNetwork::CNetwork()
{
CApplicationMessenger::Get().NetworkMessage(SERVICES_UP, 0);
}
CNetwork::~CNetwork()
{
CApplicationMessenger::Get().NetworkMessage(SERVICES_DOWN, 0);
}
int CNetwork::ParseHex(char *str, unsigned char *addr)
{
int len = 0;
while (*str)
{
int tmp;
if (str[1] == 0)
return -1;
if (sscanf(str, "%02x", (unsigned int *)&tmp) != 1)
return -1;
addr[len] = tmp;
len++;
str += 2;
}
return len;
}
std::string CNetwork::GetHostName(void)
{
char hostName[128];
if (gethostname(hostName, sizeof(hostName)))
return "unknown";
std::string hostStr;
#ifdef TARGET_WINDOWS
g_charsetConverter.systemToUtf8(hostName, hostStr);
#else
hostStr = hostName;
#endif
return hostStr;
}
CNetworkInterface* CNetwork::GetFirstConnectedInterface()
{
std::vector& ifaces = GetInterfaceList();
std::vector::const_iterator iter = ifaces.begin();
while (iter != ifaces.end())
{
CNetworkInterface* iface = *iter;
if (iface && iface->IsConnected())
return iface;
++iter;
}
return NULL;
}
bool CNetwork::HasInterfaceForIP(unsigned long address)
{
unsigned long subnet;
unsigned long local;
std::vector& ifaces = GetInterfaceList();
std::vector::const_iterator iter = ifaces.begin();
while (iter != ifaces.end())
{
CNetworkInterface* iface = *iter;
if (iface && iface->IsConnected())
{
subnet = ntohl(inet_addr(iface->GetCurrentNetmask().c_str()));
local = ntohl(inet_addr(iface->GetCurrentIPAddress().c_str()));
if( (address & subnet) == (local & subnet) )
return true;
}
++iter;
}
return false;
}
bool CNetwork::IsAvailable(bool wait /*= false*/)
{
if (wait)
{
// NOTE: Not implemented in linuxport branch as 99.9% of the time
// we have the network setup already. Trunk code has a busy
// wait for 5 seconds here.
}
std::vector& ifaces = GetInterfaceList();
return (ifaces.size() != 0);
}
bool CNetwork::IsConnected()
{
return GetFirstConnectedInterface() != NULL;
}
CNetworkInterface* CNetwork::GetInterfaceByName(const std::string& name)
{
std::vector& ifaces = GetInterfaceList();
std::vector::const_iterator iter = ifaces.begin();
while (iter != ifaces.end())
{
CNetworkInterface* iface = *iter;
if (iface && iface->GetName() == name)
return iface;
++iter;
}
return NULL;
}
void CNetwork::NetworkMessage(EMESSAGE message, int param)
{
switch( message )
{
case SERVICES_UP:
CLog::Log(LOGDEBUG, "%s - Starting network services",__FUNCTION__);
CNetworkServices::Get().Start();
break;
case SERVICES_DOWN:
CLog::Log(LOGDEBUG, "%s - Signaling network services to stop",__FUNCTION__);
CNetworkServices::Get().Stop(false); // tell network services to stop, but don't wait for them yet
CLog::Log(LOGDEBUG, "%s - Waiting for network services to stop",__FUNCTION__);
CNetworkServices::Get().Stop(true); // wait for network services to stop
break;
}
}
bool CNetwork::WakeOnLan(const char* mac)
{
int i, j, packet;
unsigned char ethaddr[8];
unsigned char buf [128];
unsigned char *ptr;
// Fetch the hardware address
if (!in_ether(mac, ethaddr))
{
CLog::Log(LOGERROR, "%s - Invalid hardware address specified (%s)", __FUNCTION__, mac);
return false;
}
// Setup the socket
if ((packet = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
{
CLog::Log(LOGERROR, "%s - Unable to create socket (%s)", __FUNCTION__, strerror (errno));
return false;
}
// Set socket options
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
saddr.sin_port = htons(9);
unsigned int value = 1;
if (setsockopt (packet, SOL_SOCKET, SO_BROADCAST, (char*) &value, sizeof( unsigned int ) ) == SOCKET_ERROR)
{
CLog::Log(LOGERROR, "%s - Unable to set socket options (%s)", __FUNCTION__, strerror (errno));
closesocket(packet);
return false;
}
// Build the magic packet (6 x 0xff + 16 x MAC address)
ptr = buf;
for (i = 0; i < 6; i++)
*ptr++ = 0xff;
for (j = 0; j < 16; j++)
for (i = 0; i < 6; i++)
*ptr++ = ethaddr[i];
// Send the magic packet
if (sendto (packet, (char *)buf, 102, 0, (struct sockaddr *)&saddr, sizeof (saddr)) < 0)
{
CLog::Log(LOGERROR, "%s - Unable to send magic packet (%s)", __FUNCTION__, strerror (errno));
closesocket(packet);
return false;
}
closesocket(packet);
CLog::Log(LOGINFO, "%s - Magic packet send to '%s'", __FUNCTION__, mac);
return true;
}
// ping helper
static const char* ConnectHostPort(SOCKET soc, const struct sockaddr_in& addr, struct timeval& timeOut, bool tryRead)
{
// set non-blocking
#ifdef TARGET_WINDOWS
u_long nonblocking = 1;
int result = ioctlsocket(soc, FIONBIO, &nonblocking);
#else
int result = fcntl(soc, F_SETFL, fcntl(soc, F_GETFL) | O_NONBLOCK);
#endif
if (result != 0)
return "set non-blocking option failed";
result = connect(soc, (struct sockaddr *)&addr, sizeof(addr)); // non-blocking connect, will fail ..
if (result < 0)
{
#ifdef TARGET_WINDOWS
if (WSAGetLastError() != WSAEWOULDBLOCK)
#else
if (errno != EINPROGRESS)
#endif
return "unexpected connect fail";
{ // wait for connect to complete
fd_set wset;
FD_ZERO(&wset);
FD_SET(soc, &wset);
result = select(FD_SETSIZE, 0, &wset, 0, &timeOut);
}
if (result < 0)
return "select fail";
if (result == 0) // timeout
return ""; // no error
{ // verify socket connection state
int err_code = -1;
socklen_t code_len = sizeof (err_code);
result = getsockopt(soc, SOL_SOCKET, SO_ERROR, (char*) &err_code, &code_len);
if (result != 0)
return "getsockopt fail";
if (err_code != 0)
return ""; // no error, just not connected
}
}
if (tryRead)
{
fd_set rset;
FD_ZERO(&rset);
FD_SET(soc, &rset);
result = select(FD_SETSIZE, &rset, 0, 0, &timeOut);
if (result > 0)
{
char message [32];
result = recv(soc, message, sizeof(message), 0);
}
if (result == 0)
return ""; // no reply yet
if (result < 0)
return "recv fail";
}
return 0; // success
}
bool CNetwork::PingHost(unsigned long ipaddr, unsigned short port, unsigned int timeOutMs, bool readability_check)
{
if (port == 0) // use icmp ping
return PingHost (ipaddr, timeOutMs);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = ipaddr;
SOCKET soc = socket(AF_INET, SOCK_STREAM, 0);
const char* err_msg = "invalid socket";
if (soc != INVALID_SOCKET)
{
struct timeval tmout;
tmout.tv_sec = timeOutMs / 1000;
tmout.tv_usec = (timeOutMs % 1000) * 1000;
err_msg = ConnectHostPort (soc, addr, tmout, readability_check);
(void) closesocket (soc);
}
if (err_msg && *err_msg)
{
#ifdef TARGET_WINDOWS
std::string sock_err = CWIN32Util::WUSysMsg(WSAGetLastError());
#else
std::string sock_err = strerror(errno);
#endif
CLog::Log(LOGERROR, "%s(%s:%d) - %s (%s)", __FUNCTION__, inet_ntoa(addr.sin_addr), port, err_msg, sock_err.c_str());
}
return err_msg == 0;
}
//creates, binds and listens a tcp socket on the desired port. Set bindLocal to
//true to bind to localhost only. The socket will listen over ipv6 if possible
//and fall back to ipv4 if ipv6 is not available on the platform.
int CreateTCPServerSocket(const int port, const bool bindLocal, const int backlog, const char *callerName)
{
struct sockaddr_storage addr;
int sock = -1;
#ifdef WINSOCK_VERSION
int yes = 1;
int no = 0;
#else
unsigned int yes = 1;
unsigned int no = 0;
#endif
// first try ipv6
if ((sock = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP)) >= 0)
{
// in case we're on ipv6, make sure the socket is dual stacked
if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&no, sizeof(no)) < 0)
{
#ifdef _MSC_VER
std::string sock_err = CWIN32Util::WUSysMsg(WSAGetLastError());
#else
std::string sock_err = strerror(errno);
#endif
CLog::Log(LOGWARNING, "%s Server: Only IPv6 supported (%s)", callerName, sock_err.c_str());
}
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&yes, sizeof(yes));
struct sockaddr_in6 *s6;
memset(&addr, 0, sizeof(addr));
addr.ss_family = AF_INET6;
s6 = (struct sockaddr_in6 *) &addr;
s6->sin6_port = htons(port);
if (bindLocal)
s6->sin6_addr = in6addr_loopback;
else
s6->sin6_addr = in6addr_any;
if (bind( sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in6)) < 0)
{
closesocket(sock);
sock = -1;
CLog::Log(LOGDEBUG, "%s Server: Failed to bind ipv6 serversocket, trying ipv4", callerName);
}
}
// ipv4 fallback
if (sock < 0 && (sock = socket(PF_INET, SOCK_STREAM, 0)) >= 0)
{
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&yes, sizeof(yes));
struct sockaddr_in *s4;
memset(&addr, 0, sizeof(addr));
addr.ss_family = AF_INET;
s4 = (struct sockaddr_in *) &addr;
s4->sin_port = htons(port);
if (bindLocal)
s4->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
else
s4->sin_addr.s_addr = htonl(INADDR_ANY);
if (bind( sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) < 0)
{
closesocket(sock);
CLog::Log(LOGERROR, "%s Server: Failed to bind ipv4 serversocket", callerName);
return INVALID_SOCKET;
}
}
else if (sock < 0)
{
CLog::Log(LOGERROR, "%s Server: Failed to create serversocket", callerName);
return INVALID_SOCKET;
}
if (listen(sock, backlog) < 0)
{
closesocket(sock);
CLog::Log(LOGERROR, "%s Server: Failed to set listen", callerName);
return INVALID_SOCKET;
}
return sock;
}