Home | History | Annotate | Download | only in base
      1 /*
      2  *  Copyright 2004 The WebRTC Project Authors. All rights reserved.
      3  *
      4  *  Use of this source code is governed by a BSD-style license
      5  *  that can be found in the LICENSE file in the root of the source
      6  *  tree. An additional intellectual property rights grant can be found
      7  *  in the file PATENTS.  All contributing project authors may
      8  *  be found in the AUTHORS file in the root of the source tree.
      9  */
     10 
     11 #ifdef HAVE_CONFIG_H
     12 #include "config.h"
     13 #endif
     14 
     15 #include "webrtc/base/network.h"
     16 
     17 #if defined(WEBRTC_POSIX)
     18 // linux/if.h can't be included at the same time as the posix sys/if.h, and
     19 // it's transitively required by linux/route.h, so include that version on
     20 // linux instead of the standard posix one.
     21 #if defined(WEBRTC_LINUX)
     22 #include <linux/if.h>
     23 #include <linux/route.h>
     24 #elif !defined(__native_client__)
     25 #include <net/if.h>
     26 #endif
     27 #include <sys/socket.h>
     28 #include <sys/utsname.h>
     29 #include <sys/ioctl.h>
     30 #include <unistd.h>
     31 #include <errno.h>
     32 
     33 #if defined(WEBRTC_ANDROID)
     34 #include "webrtc/base/ifaddrs-android.h"
     35 #elif !defined(__native_client__)
     36 #include <ifaddrs.h>
     37 #endif
     38 
     39 #endif  // WEBRTC_POSIX
     40 
     41 #if defined(WEBRTC_WIN)
     42 #include "webrtc/base/win32.h"
     43 #include <Iphlpapi.h>
     44 #endif
     45 
     46 #include <stdio.h>
     47 
     48 #include <algorithm>
     49 
     50 #include "webrtc/base/logging.h"
     51 #include "webrtc/base/scoped_ptr.h"
     52 #include "webrtc/base/socket.h"  // includes something that makes windows happy
     53 #include "webrtc/base/stream.h"
     54 #include "webrtc/base/stringencode.h"
     55 #include "webrtc/base/thread.h"
     56 
     57 namespace rtc {
     58 namespace {
     59 
     60 const uint32 kUpdateNetworksMessage = 1;
     61 const uint32 kSignalNetworksMessage = 2;
     62 
     63 // Fetch list of networks every two seconds.
     64 const int kNetworksUpdateIntervalMs = 2000;
     65 
     66 const int kHighestNetworkPreference = 127;
     67 
     68 bool CompareNetworks(const Network* a, const Network* b) {
     69   if (a->prefix_length() == b->prefix_length()) {
     70     if (a->name() == b->name()) {
     71       return a->prefix() < b->prefix();
     72     }
     73   }
     74   return a->name() < b->name();
     75 }
     76 
     77 bool SortNetworks(const Network* a, const Network* b) {
     78   // Network types will be preferred above everything else while sorting
     79   // Networks.
     80 
     81   // Networks are sorted first by type.
     82   if (a->type() != b->type()) {
     83     return a->type() < b->type();
     84   }
     85 
     86   // After type, networks are sorted by IP address precedence values
     87   // from RFC 3484-bis
     88   if (IPAddressPrecedence(a->ip()) != IPAddressPrecedence(b->ip())) {
     89     return IPAddressPrecedence(a->ip()) > IPAddressPrecedence(b->ip());
     90   }
     91 
     92   // TODO(mallinath) - Add VPN and Link speed conditions while sorting.
     93 
     94   // Networks are sorted last by key.
     95   return a->key() > b->key();
     96 }
     97 
     98 std::string AdapterTypeToString(AdapterType type) {
     99   switch (type) {
    100     case ADAPTER_TYPE_UNKNOWN:
    101       return "Unknown";
    102     case ADAPTER_TYPE_ETHERNET:
    103       return "Ethernet";
    104     case ADAPTER_TYPE_WIFI:
    105       return "Wifi";
    106     case ADAPTER_TYPE_CELLULAR:
    107       return "Cellular";
    108     case ADAPTER_TYPE_VPN:
    109       return "VPN";
    110     default:
    111       ASSERT(false);
    112       return std::string();
    113   }
    114 }
    115 
    116 }  // namespace
    117 
    118 std::string MakeNetworkKey(const std::string& name, const IPAddress& prefix,
    119                            int prefix_length) {
    120   std::ostringstream ost;
    121   ost << name << "%" << prefix.ToString() << "/" << prefix_length;
    122   return ost.str();
    123 }
    124 
    125 NetworkManager::NetworkManager() {
    126 }
    127 
    128 NetworkManager::~NetworkManager() {
    129 }
    130 
    131 NetworkManagerBase::NetworkManagerBase() : ipv6_enabled_(true) {
    132 }
    133 
    134 NetworkManagerBase::~NetworkManagerBase() {
    135   for (NetworkMap::iterator i = networks_map_.begin();
    136        i != networks_map_.end(); ++i) {
    137     delete i->second;
    138   }
    139 }
    140 
    141 void NetworkManagerBase::GetNetworks(NetworkList* result) const {
    142   *result = networks_;
    143 }
    144 
    145 void NetworkManagerBase::MergeNetworkList(const NetworkList& new_networks,
    146                                           bool* changed) {
    147   // Sort the list so that we can detect when it changes.
    148   typedef std::pair<Network*, std::vector<IPAddress> > address_list;
    149   std::map<std::string, address_list> address_map;
    150   NetworkList list(new_networks);
    151   NetworkList merged_list;
    152   std::sort(list.begin(), list.end(), CompareNetworks);
    153 
    154   *changed = false;
    155 
    156   if (networks_.size() != list.size())
    157     *changed = true;
    158 
    159   // First, build a set of network-keys to the ipaddresses.
    160   for (uint32 i = 0; i < list.size(); ++i) {
    161     bool might_add_to_merged_list = false;
    162     std::string key = MakeNetworkKey(list[i]->name(),
    163                                      list[i]->prefix(),
    164                                      list[i]->prefix_length());
    165     if (address_map.find(key) == address_map.end()) {
    166       address_map[key] = address_list(list[i], std::vector<IPAddress>());
    167       might_add_to_merged_list = true;
    168     }
    169     const std::vector<IPAddress>& addresses = list[i]->GetIPs();
    170     address_list& current_list = address_map[key];
    171     for (std::vector<IPAddress>::const_iterator it = addresses.begin();
    172          it != addresses.end();
    173          ++it) {
    174       current_list.second.push_back(*it);
    175     }
    176     if (!might_add_to_merged_list) {
    177       delete list[i];
    178     }
    179   }
    180 
    181   // Next, look for existing network objects to re-use.
    182   for (std::map<std::string, address_list >::iterator it = address_map.begin();
    183        it != address_map.end();
    184        ++it) {
    185     const std::string& key = it->first;
    186     Network* net = it->second.first;
    187     NetworkMap::iterator existing = networks_map_.find(key);
    188     if (existing == networks_map_.end()) {
    189       // This network is new. Place it in the network map.
    190       merged_list.push_back(net);
    191       networks_map_[key] = net;
    192       *changed = true;
    193     } else {
    194       // This network exists in the map already. Reset its IP addresses.
    195       *changed = existing->second->SetIPs(it->second.second, *changed);
    196       merged_list.push_back(existing->second);
    197       if (existing->second != net) {
    198         delete net;
    199       }
    200     }
    201   }
    202   networks_ = merged_list;
    203 
    204   // If the network lists changes, we resort it.
    205   if (changed) {
    206     std::sort(networks_.begin(), networks_.end(), SortNetworks);
    207     // Now network interfaces are sorted, we should set the preference value
    208     // for each of the interfaces we are planning to use.
    209     // Preference order of network interfaces might have changed from previous
    210     // sorting due to addition of higher preference network interface.
    211     // Since we have already sorted the network interfaces based on our
    212     // requirements, we will just assign a preference value starting with 127,
    213     // in decreasing order.
    214     int pref = kHighestNetworkPreference;
    215     for (NetworkList::const_iterator iter = networks_.begin();
    216          iter != networks_.end(); ++iter) {
    217       (*iter)->set_preference(pref);
    218       if (pref > 0) {
    219         --pref;
    220       } else {
    221         LOG(LS_ERROR) << "Too many network interfaces to handle!";
    222         break;
    223       }
    224     }
    225   }
    226 }
    227 
    228 BasicNetworkManager::BasicNetworkManager()
    229     : thread_(NULL), sent_first_update_(false), start_count_(0),
    230       ignore_non_default_routes_(false) {
    231 }
    232 
    233 BasicNetworkManager::~BasicNetworkManager() {
    234 }
    235 
    236 #if defined(__native_client__)
    237 
    238 bool BasicNetworkManager::CreateNetworks(bool include_ignored,
    239                                          NetworkList* networks) const {
    240   ASSERT(false);
    241   LOG(LS_WARNING) << "BasicNetworkManager doesn't work on NaCl yet";
    242   return false;
    243 }
    244 
    245 #elif defined(WEBRTC_POSIX)
    246 void BasicNetworkManager::ConvertIfAddrs(struct ifaddrs* interfaces,
    247                                          bool include_ignored,
    248                                          NetworkList* networks) const {
    249   NetworkMap current_networks;
    250   for (struct ifaddrs* cursor = interfaces;
    251        cursor != NULL; cursor = cursor->ifa_next) {
    252     IPAddress prefix;
    253     IPAddress mask;
    254     IPAddress ip;
    255     int scope_id = 0;
    256 
    257     // Some interfaces may not have address assigned.
    258     if (!cursor->ifa_addr || !cursor->ifa_netmask)
    259       continue;
    260 
    261     switch (cursor->ifa_addr->sa_family) {
    262       case AF_INET: {
    263         ip = IPAddress(
    264             reinterpret_cast<sockaddr_in*>(cursor->ifa_addr)->sin_addr);
    265         mask = IPAddress(
    266             reinterpret_cast<sockaddr_in*>(cursor->ifa_netmask)->sin_addr);
    267         break;
    268       }
    269       case AF_INET6: {
    270         if (ipv6_enabled()) {
    271           ip = IPAddress(
    272               reinterpret_cast<sockaddr_in6*>(cursor->ifa_addr)->sin6_addr);
    273           mask = IPAddress(
    274               reinterpret_cast<sockaddr_in6*>(cursor->ifa_netmask)->sin6_addr);
    275           scope_id =
    276               reinterpret_cast<sockaddr_in6*>(cursor->ifa_addr)->sin6_scope_id;
    277           break;
    278         } else {
    279           continue;
    280         }
    281       }
    282       default: {
    283         continue;
    284       }
    285     }
    286 
    287     int prefix_length = CountIPMaskBits(mask);
    288     prefix = TruncateIP(ip, prefix_length);
    289     std::string key = MakeNetworkKey(std::string(cursor->ifa_name),
    290                                      prefix, prefix_length);
    291     NetworkMap::iterator existing_network = current_networks.find(key);
    292     if (existing_network == current_networks.end()) {
    293       scoped_ptr<Network> network(new Network(cursor->ifa_name,
    294                                               cursor->ifa_name,
    295                                               prefix,
    296                                               prefix_length));
    297       network->set_scope_id(scope_id);
    298       network->AddIP(ip);
    299       bool ignored = ((cursor->ifa_flags & IFF_LOOPBACK) ||
    300                       IsIgnoredNetwork(*network));
    301       network->set_ignored(ignored);
    302       if (include_ignored || !network->ignored()) {
    303         networks->push_back(network.release());
    304       }
    305     } else {
    306       (*existing_network).second->AddIP(ip);
    307     }
    308   }
    309 }
    310 
    311 bool BasicNetworkManager::CreateNetworks(bool include_ignored,
    312                                          NetworkList* networks) const {
    313   struct ifaddrs* interfaces;
    314   int error = getifaddrs(&interfaces);
    315   if (error != 0) {
    316     LOG_ERR(LERROR) << "getifaddrs failed to gather interface data: " << error;
    317     return false;
    318   }
    319 
    320   ConvertIfAddrs(interfaces, include_ignored, networks);
    321 
    322   freeifaddrs(interfaces);
    323   return true;
    324 }
    325 
    326 #elif defined(WEBRTC_WIN)
    327 
    328 unsigned int GetPrefix(PIP_ADAPTER_PREFIX prefixlist,
    329               const IPAddress& ip, IPAddress* prefix) {
    330   IPAddress current_prefix;
    331   IPAddress best_prefix;
    332   unsigned int best_length = 0;
    333   while (prefixlist) {
    334     // Look for the longest matching prefix in the prefixlist.
    335     if (prefixlist->Address.lpSockaddr == NULL ||
    336         prefixlist->Address.lpSockaddr->sa_family != ip.family()) {
    337       prefixlist = prefixlist->Next;
    338       continue;
    339     }
    340     switch (prefixlist->Address.lpSockaddr->sa_family) {
    341       case AF_INET: {
    342         sockaddr_in* v4_addr =
    343             reinterpret_cast<sockaddr_in*>(prefixlist->Address.lpSockaddr);
    344         current_prefix = IPAddress(v4_addr->sin_addr);
    345         break;
    346       }
    347       case AF_INET6: {
    348           sockaddr_in6* v6_addr =
    349               reinterpret_cast<sockaddr_in6*>(prefixlist->Address.lpSockaddr);
    350           current_prefix = IPAddress(v6_addr->sin6_addr);
    351           break;
    352       }
    353       default: {
    354         prefixlist = prefixlist->Next;
    355         continue;
    356       }
    357     }
    358     if (TruncateIP(ip, prefixlist->PrefixLength) == current_prefix &&
    359         prefixlist->PrefixLength > best_length) {
    360       best_prefix = current_prefix;
    361       best_length = prefixlist->PrefixLength;
    362     }
    363     prefixlist = prefixlist->Next;
    364   }
    365   *prefix = best_prefix;
    366   return best_length;
    367 }
    368 
    369 bool BasicNetworkManager::CreateNetworks(bool include_ignored,
    370                                          NetworkList* networks) const {
    371   NetworkMap current_networks;
    372   // MSDN recommends a 15KB buffer for the first try at GetAdaptersAddresses.
    373   size_t buffer_size = 16384;
    374   scoped_ptr<char[]> adapter_info(new char[buffer_size]);
    375   PIP_ADAPTER_ADDRESSES adapter_addrs =
    376       reinterpret_cast<PIP_ADAPTER_ADDRESSES>(adapter_info.get());
    377   int adapter_flags = (GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_ANYCAST |
    378                        GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_INCLUDE_PREFIX);
    379   int ret = 0;
    380   do {
    381     adapter_info.reset(new char[buffer_size]);
    382     adapter_addrs = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(adapter_info.get());
    383     ret = GetAdaptersAddresses(AF_UNSPEC, adapter_flags,
    384                                0, adapter_addrs,
    385                                reinterpret_cast<PULONG>(&buffer_size));
    386   } while (ret == ERROR_BUFFER_OVERFLOW);
    387   if (ret != ERROR_SUCCESS) {
    388     return false;
    389   }
    390   int count = 0;
    391   while (adapter_addrs) {
    392     if (adapter_addrs->OperStatus == IfOperStatusUp) {
    393       PIP_ADAPTER_UNICAST_ADDRESS address = adapter_addrs->FirstUnicastAddress;
    394       PIP_ADAPTER_PREFIX prefixlist = adapter_addrs->FirstPrefix;
    395       std::string name;
    396       std::string description;
    397 #ifdef _DEBUG
    398       name = ToUtf8(adapter_addrs->FriendlyName,
    399                     wcslen(adapter_addrs->FriendlyName));
    400 #endif
    401       description = ToUtf8(adapter_addrs->Description,
    402                            wcslen(adapter_addrs->Description));
    403       for (; address; address = address->Next) {
    404 #ifndef _DEBUG
    405         name = rtc::ToString(count);
    406 #endif
    407 
    408         IPAddress ip;
    409         int scope_id = 0;
    410         scoped_ptr<Network> network;
    411         switch (address->Address.lpSockaddr->sa_family) {
    412           case AF_INET: {
    413             sockaddr_in* v4_addr =
    414                 reinterpret_cast<sockaddr_in*>(address->Address.lpSockaddr);
    415             ip = IPAddress(v4_addr->sin_addr);
    416             break;
    417           }
    418           case AF_INET6: {
    419             if (ipv6_enabled()) {
    420               sockaddr_in6* v6_addr =
    421                   reinterpret_cast<sockaddr_in6*>(address->Address.lpSockaddr);
    422               scope_id = v6_addr->sin6_scope_id;
    423               ip = IPAddress(v6_addr->sin6_addr);
    424               break;
    425             } else {
    426               continue;
    427             }
    428           }
    429           default: {
    430             continue;
    431           }
    432         }
    433 
    434         IPAddress prefix;
    435         int prefix_length = GetPrefix(prefixlist, ip, &prefix);
    436         std::string key = MakeNetworkKey(name, prefix, prefix_length);
    437         NetworkMap::iterator existing_network = current_networks.find(key);
    438         if (existing_network == current_networks.end()) {
    439           scoped_ptr<Network> network(new Network(name,
    440                                                   description,
    441                                                   prefix,
    442                                                   prefix_length));
    443           network->set_scope_id(scope_id);
    444           network->AddIP(ip);
    445           bool ignore = ((adapter_addrs->IfType == IF_TYPE_SOFTWARE_LOOPBACK) ||
    446                          IsIgnoredNetwork(*network));
    447           network->set_ignored(ignore);
    448           if (include_ignored || !network->ignored()) {
    449             networks->push_back(network.release());
    450           }
    451         } else {
    452           (*existing_network).second->AddIP(ip);
    453         }
    454       }
    455       // Count is per-adapter - all 'Networks' created from the same
    456       // adapter need to have the same name.
    457       ++count;
    458     }
    459     adapter_addrs = adapter_addrs->Next;
    460   }
    461   return true;
    462 }
    463 #endif  // WEBRTC_WIN
    464 
    465 #if defined(WEBRTC_LINUX)
    466 bool IsDefaultRoute(const std::string& network_name) {
    467   FileStream fs;
    468   if (!fs.Open("/proc/net/route", "r", NULL)) {
    469     LOG(LS_WARNING) << "Couldn't read /proc/net/route, skipping default "
    470                     << "route check (assuming everything is a default route).";
    471     return true;
    472   } else {
    473     std::string line;
    474     while (fs.ReadLine(&line) == SR_SUCCESS) {
    475       char iface_name[256];
    476       unsigned int iface_ip, iface_gw, iface_mask, iface_flags;
    477       if (sscanf(line.c_str(),
    478                  "%255s %8X %8X %4X %*d %*u %*d %8X",
    479                  iface_name, &iface_ip, &iface_gw,
    480                  &iface_flags, &iface_mask) == 5 &&
    481           network_name == iface_name &&
    482           iface_mask == 0 &&
    483           (iface_flags & (RTF_UP | RTF_HOST)) == RTF_UP) {
    484         return true;
    485       }
    486     }
    487   }
    488   return false;
    489 }
    490 #endif
    491 
    492 bool BasicNetworkManager::IsIgnoredNetwork(const Network& network) const {
    493   // Ignore networks on the explicit ignore list.
    494   for (size_t i = 0; i < network_ignore_list_.size(); ++i) {
    495     if (network.name() == network_ignore_list_[i]) {
    496       return true;
    497     }
    498   }
    499 #if defined(WEBRTC_POSIX)
    500   // Filter out VMware interfaces, typically named vmnet1 and vmnet8
    501   if (strncmp(network.name().c_str(), "vmnet", 5) == 0 ||
    502       strncmp(network.name().c_str(), "vnic", 4) == 0) {
    503     return true;
    504   }
    505 #if defined(WEBRTC_LINUX)
    506   // Make sure this is a default route, if we're ignoring non-defaults.
    507   if (ignore_non_default_routes_ && !IsDefaultRoute(network.name())) {
    508     return true;
    509   }
    510 #endif
    511 #elif defined(WEBRTC_WIN)
    512   // Ignore any HOST side vmware adapters with a description like:
    513   // VMware Virtual Ethernet Adapter for VMnet1
    514   // but don't ignore any GUEST side adapters with a description like:
    515   // VMware Accelerated AMD PCNet Adapter #2
    516   if (strstr(network.description().c_str(), "VMnet") != NULL) {
    517     return true;
    518   }
    519 #endif
    520 
    521   // Ignore any networks with a 0.x.y.z IP
    522   if (network.prefix().family() == AF_INET) {
    523     return (network.prefix().v4AddressAsHostOrderInteger() < 0x01000000);
    524   }
    525   return false;
    526 }
    527 
    528 void BasicNetworkManager::StartUpdating() {
    529   thread_ = Thread::Current();
    530   if (start_count_) {
    531     // If network interfaces are already discovered and signal is sent,
    532     // we should trigger network signal immediately for the new clients
    533     // to start allocating ports.
    534     if (sent_first_update_)
    535       thread_->Post(this, kSignalNetworksMessage);
    536   } else {
    537     thread_->Post(this, kUpdateNetworksMessage);
    538   }
    539   ++start_count_;
    540 }
    541 
    542 void BasicNetworkManager::StopUpdating() {
    543   ASSERT(Thread::Current() == thread_);
    544   if (!start_count_)
    545     return;
    546 
    547   --start_count_;
    548   if (!start_count_) {
    549     thread_->Clear(this);
    550     sent_first_update_ = false;
    551   }
    552 }
    553 
    554 void BasicNetworkManager::OnMessage(Message* msg) {
    555   switch (msg->message_id) {
    556     case kUpdateNetworksMessage:  {
    557       DoUpdateNetworks();
    558       break;
    559     }
    560     case kSignalNetworksMessage:  {
    561       SignalNetworksChanged();
    562       break;
    563     }
    564     default:
    565       ASSERT(false);
    566   }
    567 }
    568 
    569 void BasicNetworkManager::DoUpdateNetworks() {
    570   if (!start_count_)
    571     return;
    572 
    573   ASSERT(Thread::Current() == thread_);
    574 
    575   NetworkList list;
    576   if (!CreateNetworks(false, &list)) {
    577     SignalError();
    578   } else {
    579     bool changed;
    580     MergeNetworkList(list, &changed);
    581     if (changed || !sent_first_update_) {
    582       SignalNetworksChanged();
    583       sent_first_update_ = true;
    584     }
    585   }
    586 
    587   thread_->PostDelayed(kNetworksUpdateIntervalMs, this, kUpdateNetworksMessage);
    588 }
    589 
    590 void BasicNetworkManager::DumpNetworks(bool include_ignored) {
    591   NetworkList list;
    592   CreateNetworks(include_ignored, &list);
    593   LOG(LS_INFO) << "NetworkManager detected " << list.size() << " networks:";
    594   for (size_t i = 0; i < list.size(); ++i) {
    595     const Network* network = list[i];
    596     if (!network->ignored() || include_ignored) {
    597       LOG(LS_INFO) << network->ToString() << ": "
    598                    << network->description()
    599                    << ((network->ignored()) ? ", Ignored" : "");
    600     }
    601   }
    602   // Release the network list created previously.
    603   // Do this in a seperated for loop for better readability.
    604   for (size_t i = 0; i < list.size(); ++i) {
    605     delete list[i];
    606   }
    607 }
    608 
    609 Network::Network(const std::string& name, const std::string& desc,
    610                  const IPAddress& prefix, int prefix_length)
    611     : name_(name), description_(desc), prefix_(prefix),
    612       prefix_length_(prefix_length),
    613       key_(MakeNetworkKey(name, prefix, prefix_length)), scope_id_(0),
    614       ignored_(false), type_(ADAPTER_TYPE_UNKNOWN), preference_(0) {
    615 }
    616 
    617 Network::Network(const std::string& name, const std::string& desc,
    618                  const IPAddress& prefix, int prefix_length, AdapterType type)
    619     : name_(name), description_(desc), prefix_(prefix),
    620       prefix_length_(prefix_length),
    621       key_(MakeNetworkKey(name, prefix, prefix_length)), scope_id_(0),
    622       ignored_(false), type_(type), preference_(0) {
    623 }
    624 
    625 std::string Network::ToString() const {
    626   std::stringstream ss;
    627   // Print out the first space-terminated token of the network desc, plus
    628   // the IP address.
    629   ss << "Net[" << description_.substr(0, description_.find(' '))
    630      << ":" << prefix_.ToSensitiveString() << "/" << prefix_length_
    631      << ":" << AdapterTypeToString(type_) << "]";
    632   return ss.str();
    633 }
    634 
    635 // Sets the addresses of this network. Returns true if the address set changed.
    636 // Change detection is short circuited if the changed argument is true.
    637 bool Network::SetIPs(const std::vector<IPAddress>& ips, bool changed) {
    638   changed = changed || ips.size() != ips_.size();
    639   // Detect changes with a nested loop; n-squared but we expect on the order
    640   // of 2-3 addresses per network.
    641   for (std::vector<IPAddress>::const_iterator it = ips.begin();
    642       !changed && it != ips.end();
    643       ++it) {
    644     bool found = false;
    645     for (std::vector<IPAddress>::iterator inner_it = ips_.begin();
    646          !found && inner_it != ips_.end();
    647          ++inner_it) {
    648       if (*it == *inner_it) {
    649         found = true;
    650       }
    651     }
    652     changed = !found;
    653   }
    654   ips_ = ips;
    655   return changed;
    656 }
    657 
    658 }  // namespace rtc
    659