| /* |
| * libjingle |
| * Copyright 2004--2005, Google Inc. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO |
| * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
| * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
| * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "talk/base/network.h" |
| #include "talk/base/stream.h" |
| |
| #ifdef POSIX |
| #include <sys/socket.h> |
| #include <sys/utsname.h> |
| #include <sys/ioctl.h> |
| #include <net/if.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #endif // POSIX |
| |
| #ifdef WIN32 |
| #include "talk/base/win32.h" |
| #include <Iphlpapi.h> |
| #endif |
| |
| #include <algorithm> |
| #include <cassert> |
| #include <cfloat> |
| #include <cmath> |
| #include <cstdio> |
| #include <cstring> |
| #include <sstream> |
| |
| #include "talk/base/host.h" |
| #include "talk/base/logging.h" |
| #include "talk/base/scoped_ptr.h" |
| #include "talk/base/socket.h" // includes something that makes windows happy |
| #include "talk/base/stringencode.h" |
| #include "talk/base/time.h" |
| |
| namespace { |
| |
| const double kAlpha = 0.5; // weight for data infinitely far in the past |
| const double kHalfLife = 2000; // half life of exponential decay (in ms) |
| const double kLog2 = 0.693147180559945309417; |
| const double kLambda = kLog2 / kHalfLife; |
| |
| // assume so-so quality unless data says otherwise |
| const double kDefaultQuality = talk_base::QUALITY_FAIR; |
| |
| typedef std::map<std::string, std::string> StrMap; |
| |
| void BuildMap(const StrMap& map, std::string& str) { |
| str.append("{"); |
| bool first = true; |
| for (StrMap::const_iterator i = map.begin(); i != map.end(); ++i) { |
| if (!first) str.append(","); |
| str.append(i->first); |
| str.append("="); |
| str.append(i->second); |
| first = false; |
| } |
| str.append("}"); |
| } |
| |
| void ParseCheck(std::istringstream& ist, char ch) { |
| if (ist.get() != ch) |
| LOG(LERROR) << "Expecting '" << ch << "'"; |
| } |
| |
| std::string ParseString(std::istringstream& ist) { |
| std::string str; |
| int count = 0; |
| while (ist) { |
| char ch = ist.peek(); |
| if ((count == 0) && ((ch == '=') || (ch == ',') || (ch == '}'))) { |
| break; |
| } else if (ch == '{') { |
| count += 1; |
| } else if (ch == '}') { |
| count -= 1; |
| if (count < 0) |
| LOG(LERROR) << "mismatched '{' and '}'"; |
| } |
| str.append(1, static_cast<char>(ist.get())); |
| } |
| return str; |
| } |
| |
| void ParseMap(const std::string& str, StrMap& map) { |
| if (str.size() == 0) |
| return; |
| std::istringstream ist(str); |
| ParseCheck(ist, '{'); |
| for (;;) { |
| std::string key = ParseString(ist); |
| ParseCheck(ist, '='); |
| std::string val = ParseString(ist); |
| map[key] = val; |
| if (ist.peek() == ',') |
| ist.get(); |
| else |
| break; |
| } |
| ParseCheck(ist, '}'); |
| if (ist.rdbuf()->in_avail() != 0) |
| LOG(LERROR) << "Unexpected characters at end"; |
| } |
| |
| } // namespace |
| |
| namespace talk_base { |
| |
| NetworkManager::~NetworkManager() { |
| for (NetworkMap::iterator i = networks_.begin(); i != networks_.end(); ++i) |
| delete i->second; |
| } |
| |
| bool NetworkManager::GetNetworks(std::vector<Network*>* result) { |
| std::vector<Network*> list; |
| if (!EnumNetworks(false, &list)) { |
| return false; |
| } |
| |
| for (uint32 i = 0; i < list.size(); ++i) { |
| NetworkMap::iterator iter = networks_.find(list[i]->name()); |
| |
| Network* network; |
| if (iter == networks_.end()) { |
| network = list[i]; |
| } else { |
| network = iter->second; |
| network->set_ip(list[i]->ip()); |
| network->set_gateway_ip(list[i]->gateway_ip()); |
| delete list[i]; |
| } |
| |
| networks_[network->name()] = network; |
| result->push_back(network); |
| } |
| return true; |
| } |
| |
| void NetworkManager::DumpNetworks(bool include_ignored) { |
| std::vector<Network*> list; |
| EnumNetworks(include_ignored, &list); |
| LOG(LS_INFO) << "NetworkManager detected " << list.size() << " networks:"; |
| for (size_t i = 0; i < list.size(); ++i) { |
| const Network* network = list[i]; |
| if (!network->ignored() || include_ignored) { |
| LOG(LS_INFO) << network->ToString() << ": " << network->description() |
| << ", Gateway=" |
| << SocketAddress::IPToString(network->gateway_ip()) |
| << ((network->ignored()) ? ", Ignored" : ""); |
| } |
| } |
| } |
| |
| std::string NetworkManager::GetState() const { |
| StrMap map; |
| for (NetworkMap::const_iterator i = networks_.begin(); |
| i != networks_.end(); ++i) |
| map[i->first] = i->second->GetState(); |
| |
| std::string str; |
| BuildMap(map, str); |
| return str; |
| } |
| |
| void NetworkManager::SetState(const std::string& str) { |
| StrMap map; |
| ParseMap(str, map); |
| |
| for (StrMap::iterator i = map.begin(); i != map.end(); ++i) { |
| std::string name = i->first; |
| std::string state = i->second; |
| |
| Network* network = new Network(name, "", 0, 0); |
| network->SetState(state); |
| networks_[name] = network; |
| } |
| } |
| |
| #ifdef POSIX |
| // Gets the default gateway for the specified interface. |
| uint32 GetDefaultGateway(const std::string& name) { |
| #ifdef OSX |
| // TODO: /proc/net/route doesn't exist, |
| // Use ioctl to get the routing table |
| return 0xFFFFFFFF; |
| #endif |
| |
| uint32 gateway_ip = 0; |
| |
| FileStream fs; |
| if (fs.Open("/proc/net/route", "r", NULL)) { |
| std::string line; |
| while (fs.ReadLine(&line) == SR_SUCCESS && gateway_ip == 0) { |
| char iface[16]; |
| unsigned int ip, gw; |
| if (sscanf(line.c_str(), "%7s %8X %8X", iface, &ip, &gw) == 3 && |
| name == iface && ip == 0) { |
| gateway_ip = ntohl(gw); |
| } |
| } |
| } |
| |
| return gateway_ip; |
| } |
| |
| |
| bool NetworkManager::CreateNetworks(bool include_ignored, |
| std::vector<Network*>* networks) { |
| int fd; |
| if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { |
| LOG_ERR(LERROR) << "socket"; |
| return false; |
| } |
| |
| struct ifconf ifc; |
| ifc.ifc_len = 64 * sizeof(struct ifreq); |
| ifc.ifc_buf = new char[ifc.ifc_len]; |
| |
| if (ioctl(fd, SIOCGIFCONF, &ifc) < 0) { |
| LOG_ERR(LERROR) << "ioctl"; |
| return false; |
| } |
| assert(ifc.ifc_len < static_cast<int>(64 * sizeof(struct ifreq))); |
| |
| struct ifreq* ptr = reinterpret_cast<struct ifreq*>(ifc.ifc_buf); |
| struct ifreq* end = |
| reinterpret_cast<struct ifreq*>(ifc.ifc_buf + ifc.ifc_len); |
| |
| while (ptr < end) { |
| struct sockaddr_in* inaddr = |
| reinterpret_cast<struct sockaddr_in*>(&ptr->ifr_ifru.ifru_addr); |
| if (inaddr->sin_family == AF_INET) { |
| uint32 ip = ntohl(inaddr->sin_addr.s_addr); |
| scoped_ptr<Network> network( |
| new Network(ptr->ifr_name, ptr->ifr_name, ip, |
| GetDefaultGateway(ptr->ifr_name))); |
| network->set_ignored(IsIgnoredNetwork(*network)); |
| if (include_ignored || !network->ignored()) { |
| networks->push_back(network.release()); |
| } |
| } |
| |
| #ifdef _SIZEOF_ADDR_IFREQ |
| ptr = reinterpret_cast<struct ifreq*>( |
| reinterpret_cast<char*>(ptr) + _SIZEOF_ADDR_IFREQ(*ptr)); |
| #else |
| ptr++; |
| #endif |
| } |
| |
| delete [] ifc.ifc_buf; |
| close(fd); |
| return true; |
| } |
| #endif // POSIX |
| |
| #ifdef WIN32 |
| bool NetworkManager::CreateNetworks(bool include_ignored, |
| std::vector<Network*>* networks) { |
| IP_ADAPTER_INFO info_temp; |
| ULONG len = 0; |
| |
| if (GetAdaptersInfo(&info_temp, &len) != ERROR_BUFFER_OVERFLOW) |
| // This just means there's zero networks, which is not an error. |
| return true; |
| |
| scoped_array<char> buf(new char[len]); |
| IP_ADAPTER_INFO *infos = reinterpret_cast<IP_ADAPTER_INFO *>(buf.get()); |
| DWORD ret = GetAdaptersInfo(infos, &len); |
| if (ret != NO_ERROR) { |
| LOG_ERR_EX(LS_ERROR, ret) << "GetAdaptersInfo failed"; |
| return false; |
| } |
| |
| int count = 0; |
| for (IP_ADAPTER_INFO *info = infos; info != NULL; info = info->Next) { |
| // Ignore the loopback device. |
| if (info->Type == MIB_IF_TYPE_LOOPBACK) { |
| continue; |
| } |
| |
| // In non-debug builds, don't transmit the network name because of |
| // privacy concerns. Transmit a number instead. |
| std::string name; |
| #ifdef _DEBUG |
| name = info->Description; |
| #else // !_DEBUG |
| std::ostringstream ost; |
| ost << count; |
| name = ost.str(); |
| count++; |
| #endif // !_DEBUG |
| |
| scoped_ptr<Network> network(new Network(name, info->Description, |
| SocketAddress::StringToIP(info->IpAddressList.IpAddress.String), |
| SocketAddress::StringToIP(info->GatewayList.IpAddress.String))); |
| network->set_ignored(IsIgnoredNetwork(*network)); |
| if (include_ignored || !network->ignored()) { |
| networks->push_back(network.release()); |
| } |
| } |
| |
| return true; |
| } |
| #endif // WIN32 |
| |
| bool NetworkManager::IsIgnoredNetwork(const Network& network) { |
| #ifdef POSIX |
| // Ignore local networks (lo, lo0, etc) |
| // Also filter out VMware interfaces, typically named vmnet1 and vmnet8 |
| if (strncmp(network.name().c_str(), "lo", 2) == 0 || |
| strncmp(network.name().c_str(), "vmnet", 5) == 0) { |
| return true; |
| } |
| #elif defined(WIN32) |
| // Ignore any HOST side vmware adapters with a description like: |
| // VMware Virtual Ethernet Adapter for VMnet1 |
| // but don't ignore any GUEST side adapters with a description like: |
| // VMware Accelerated AMD PCNet Adapter #2 |
| if (strstr(network.description().c_str(), "VMnet") != NULL) { |
| return true; |
| } |
| #endif |
| |
| // Ignore any networks with a 0.x.y.z IP |
| return (network.ip() < 0x01000000); |
| } |
| |
| bool NetworkManager::EnumNetworks(bool include_ignored, |
| std::vector<Network*>* result) { |
| return CreateNetworks(include_ignored, result); |
| } |
| |
| |
| Network::Network(const std::string& name, const std::string& desc, |
| uint32 ip, uint32 gateway_ip) |
| : name_(name), description_(desc), ip_(ip), gateway_ip_(gateway_ip), |
| ignored_(false), uniform_numerator_(0), uniform_denominator_(0), |
| exponential_numerator_(0), exponential_denominator_(0), |
| quality_(kDefaultQuality) { |
| last_data_time_ = Time(); |
| |
| // TODO: seed the historical data with one data point based |
| // on the link speed metric from XP (4.0 if < 50, 3.0 otherwise). |
| } |
| |
| void Network::StartSession(NetworkSession* session) { |
| assert(std::find(sessions_.begin(), sessions_.end(), session) == |
| sessions_.end()); |
| sessions_.push_back(session); |
| } |
| |
| void Network::StopSession(NetworkSession* session) { |
| SessionList::iterator iter = |
| std::find(sessions_.begin(), sessions_.end(), session); |
| if (iter != sessions_.end()) |
| sessions_.erase(iter); |
| } |
| |
| void Network::EstimateQuality() { |
| uint32 now = Time(); |
| |
| // Add new data points for the current time. |
| for (uint32 i = 0; i < sessions_.size(); ++i) { |
| if (sessions_[i]->HasQuality()) |
| AddDataPoint(now, sessions_[i]->GetCurrentQuality()); |
| } |
| |
| // Construct the weighted average using both uniform and exponential weights. |
| |
| double exp_shift = exp(-kLambda * (now - last_data_time_)); |
| double numerator = uniform_numerator_ + exp_shift * exponential_numerator_; |
| double denominator = uniform_denominator_ + exp_shift * |
| exponential_denominator_; |
| |
| if (denominator < DBL_EPSILON) |
| quality_ = kDefaultQuality; |
| else |
| quality_ = numerator / denominator; |
| } |
| |
| std::string Network::ToString() const { |
| std::stringstream ss; |
| // Print out the first space-terminated token of the network desc, plus |
| // the IP address. |
| ss << "Net[" << description_.substr(0, description_.find(' ')) |
| << ":" << SocketAddress::IPToString(ip_) << "]"; |
| return ss.str(); |
| } |
| |
| void Network::AddDataPoint(uint32 time, double quality) { |
| uniform_numerator_ += kAlpha * quality; |
| uniform_denominator_ += kAlpha; |
| |
| double exp_shift = exp(-kLambda * (time - last_data_time_)); |
| exponential_numerator_ = (1 - kAlpha) * quality + exp_shift * |
| exponential_numerator_; |
| exponential_denominator_ = (1 - kAlpha) + exp_shift * |
| exponential_denominator_; |
| |
| last_data_time_ = time; |
| } |
| |
| std::string Network::GetState() const { |
| StrMap map; |
| map["lt"] = talk_base::ToString<uint32>(last_data_time_); |
| map["un"] = talk_base::ToString<double>(uniform_numerator_); |
| map["ud"] = talk_base::ToString<double>(uniform_denominator_); |
| map["en"] = talk_base::ToString<double>(exponential_numerator_); |
| map["ed"] = talk_base::ToString<double>(exponential_denominator_); |
| |
| std::string str; |
| BuildMap(map, str); |
| return str; |
| } |
| |
| void Network::SetState(const std::string& str) { |
| StrMap map; |
| ParseMap(str, map); |
| |
| last_data_time_ = FromString<uint32>(map["lt"]); |
| uniform_numerator_ = FromString<double>(map["un"]); |
| uniform_denominator_ = FromString<double>(map["ud"]); |
| exponential_numerator_ = FromString<double>(map["en"]); |
| exponential_denominator_ = FromString<double>(map["ed"]); |
| } |
| |
| } // namespace talk_base |