Implement waiting for IPv4 address for an interface

Bug: 239577572
Test: build, flash, check Internet
Change-Id: I339d6ae595fac13690a90d98ef82a6659819406c
diff --git a/automotive/can/1.0/default/libnetdevice/libnetdevice.cpp b/automotive/can/1.0/default/libnetdevice/libnetdevice.cpp
index 4c5b309..17f1cbc 100644
--- a/automotive/can/1.0/default/libnetdevice/libnetdevice.cpp
+++ b/automotive/can/1.0/default/libnetdevice/libnetdevice.cpp
@@ -100,23 +100,36 @@
     return ifr.ifr_flags & IFF_UP;
 }
 
+static bool hasIpv4(std::string ifname) {
+    auto ifr = ifreqs::fromName(ifname);
+    switch (const auto status = ifreqs::trySend(SIOCGIFADDR, ifr)) {
+        case 0:
+            return true;
+        case EADDRNOTAVAIL:
+        case ENODEV:
+            return false;
+        default:
+            PLOG(WARNING) << "Failed checking IPv4 address";
+            return false;
+    }
+}
+
 struct WaitState {
     bool present;
     bool up;
+    bool hasIpv4Addr;
 
     bool satisfied(WaitCondition cnd) const {
         switch (cnd) {
             case WaitCondition::PRESENT:
-                if (present) return true;
-                break;
+                return present;
             case WaitCondition::PRESENT_AND_UP:
-                if (present && up) return true;
-                break;
+                return present && up;
+            case WaitCondition::PRESENT_AND_IPV4:
+                return present && up && hasIpv4Addr;
             case WaitCondition::DOWN_OR_GONE:
-                if (!present || !up) return true;
-                break;
+                return !present || !up;
         }
-        return false;
     }
 };
 
@@ -126,11 +139,22 @@
             return "become present";
         case WaitCondition::PRESENT_AND_UP:
             return "come up";
+        case WaitCondition::PRESENT_AND_IPV4:
+            return "get IPv4 address";
         case WaitCondition::DOWN_OR_GONE:
             return "go down";
     }
 }
 
+static std::string toString(Quantifier quant) {
+    switch (quant) {
+        case Quantifier::ALL_OF:
+            return "all of";
+        case Quantifier::ANY_OF:
+            return "any of";
+    }
+}
+
 static std::string toString(const std::set<std::string>& ifnames) {
     std::stringstream ss;
     std::copy(ifnames.begin(), ifnames.end(), std::ostream_iterator<std::string>(ss, ","));
@@ -139,50 +163,73 @@
     return str;
 }
 
-void waitFor(std::set<std::string> ifnames, WaitCondition cnd, bool allOf) {
-    nl::Socket sock(NETLINK_ROUTE, 0, RTMGRP_LINK);
+std::optional<std::string> waitFor(std::set<std::string> ifnames, WaitCondition cnd,
+                                   Quantifier quant) {
+    nl::Socket sock(NETLINK_ROUTE, 0, RTMGRP_LINK | RTMGRP_IPV4_IFADDR);
 
     using StatesMap = std::map<std::string, WaitState>;
     StatesMap states = {};
     for (const auto ifname : ifnames) {
         const auto present = exists(ifname);
         const auto up = present && isUp(ifname).value_or(false);
-        states[ifname] = {present, up};
+        const auto hasIpv4Addr = present && hasIpv4(ifname);
+        states[ifname] = {present, up, hasIpv4Addr};
     }
 
     const auto mapConditionChecker = [cnd](const StatesMap::iterator::value_type& it) {
         return it.second.satisfied(cnd);
     };
-    const auto isFullySatisfied = [&states, allOf, mapConditionChecker]() {
-        if (allOf) {
-            return std::all_of(states.begin(), states.end(), mapConditionChecker);
-        } else {
-            return std::any_of(states.begin(), states.end(), mapConditionChecker);
+    const auto isFullySatisfied = [&states, quant,
+                                   mapConditionChecker]() -> std::optional<std::string> {
+        if (quant == Quantifier::ALL_OF) {
+            if (!std::all_of(states.begin(), states.end(), mapConditionChecker)) return {};
+            return states.begin()->first;
+        } else {  // Quantifier::ANY_OF
+            const auto it = std::find_if(states.begin(), states.end(), mapConditionChecker);
+            if (it == states.end()) return {};
+            return it->first;
         }
     };
 
-    if (isFullySatisfied()) return;
+    if (const auto iface = isFullySatisfied()) return iface;
 
-    LOG(DEBUG) << "Waiting for " << (allOf ? "" : "any of ") << toString(ifnames) << " to "
+    LOG(DEBUG) << "Waiting for " << toString(quant) << " " << toString(ifnames) << " to "
                << toString(cnd);
     for (const auto rawMsg : sock) {
-        const auto msg = nl::Message<ifinfomsg>::parse(rawMsg, {RTM_NEWLINK, RTM_DELLINK});
-        if (!msg.has_value()) continue;
+        if (const auto msg = nl::Message<ifinfomsg>::parse(rawMsg, {RTM_NEWLINK, RTM_DELLINK});
+            msg.has_value()) {
+            // Interface added / removed
+            const auto ifname = msg->attributes.get<std::string>(IFLA_IFNAME);
+            if (ifnames.count(ifname) == 0) continue;
 
-        const auto ifname = msg->attributes.get<std::string>(IFLA_IFNAME);
-        if (ifnames.count(ifname) == 0) continue;
+            auto& state = states[ifname];
+            state.present = (msg->header.nlmsg_type != RTM_DELLINK);
+            state.up = state.present && (msg->data.ifi_flags & IFF_UP) != 0;
+            if (!state.present) state.hasIpv4Addr = false;
 
-        const bool present = (msg->header.nlmsg_type != RTM_DELLINK);
-        const bool up = present && (msg->data.ifi_flags & IFF_UP) != 0;
-        states[ifname] = {present, up};
+        } else if (const auto msg =
+                           nl::Message<ifaddrmsg>::parse(rawMsg, {RTM_NEWADDR, RTM_DELADDR});
+                   msg.has_value()) {
+            // Address added / removed
+            const auto ifname = msg->attributes.get<std::string>(IFLA_IFNAME);
+            if (ifnames.count(ifname) == 0) continue;
 
-        if (isFullySatisfied()) {
-            LOG(DEBUG) << "Finished waiting for " << (allOf ? "" : "some of ") << toString(ifnames)
+            if (msg->header.nlmsg_type == RTM_NEWADDR) {
+                states[ifname].hasIpv4Addr = true;
+            } else {
+                // instead of tracking which one got deleted, let's just ask
+                states[ifname].hasIpv4Addr = hasIpv4(ifname);
+            }
+        }
+
+        if (const auto iface = isFullySatisfied()) {
+            LOG(DEBUG) << "Finished waiting for " << toString(quant) << " " << toString(ifnames)
                        << " to " << toString(cnd);
-            return;
+            return iface;
         }
     }
     LOG(FATAL) << "Can't read Netlink socket";
+    return {};
 }
 
 }  // namespace android::netdevice