OffloadUtils: move existing ethernet device check to isEthernet() helper

Preparation for tethering bpf offload

Test: manual clatd test
1. Connect to IPv6-Only WiFi hotspot
2. Browse 172.217.0.46 (google.com) successfully
3. Disconnect from WiFi
Repeat the above steps three times.

Change-Id: Ib2fdc644c6048acaacf51c46c6960e8cd6cbc445
diff --git a/server/ClatdController.cpp b/server/ClatdController.cpp
index 0668de3..0e8fdb8 100644
--- a/server/ClatdController.cpp
+++ b/server/ClatdController.cpp
@@ -19,7 +19,6 @@
 
 #include <arpa/inet.h>
 #include <errno.h>
-#include <linux/if_arp.h>
 #include <linux/if_tun.h>
 #include <linux/ioctl.h>
 #include <net/if.h>
@@ -256,39 +255,24 @@
 void ClatdController::maybeStartBpf(const ClatdTracker& tracker) {
     if (mClatEbpfMode == ClatEbpfDisabled) return;
 
-    int rv = hardwareAddressType(tracker.iface);
-    if (rv < 0) {
-        ALOGE("hardwareAddressType(%s[%d]) failure: %s", tracker.iface, tracker.ifIndex,
-              strerror(-rv));
+    auto isEthernet = android::net::isEthernet(tracker.iface);
+    if (!isEthernet.ok()) {
+        ALOGE("isEthernet(%s[%d]) failure: %s", tracker.iface, tracker.ifIndex,
+              isEthernet.error().message().c_str());
         return;
     }
 
-    bool isEthernet;
-    switch (rv) {
-        case ARPHRD_ETHER:
-            isEthernet = true;
-            break;
-        case ARPHRD_RAWIP:  // in Linux 4.14+ rmnet support was upstreamed and this is 519
-        case 530:           // this is ARPHRD_RAWIP on some Android 4.9 kernels with rmnet
-            isEthernet = false;
-            break;
-        default:
-            ALOGE("hardwareAddressType(%s[%d]) returned unknown type %d.", tracker.iface,
-                  tracker.ifIndex, rv);
-            return;
-    }
-
     // This program will be attached to the v4-* interface which is a TUN and thus always rawip.
-    rv = getClatEgressProgFd(RAWIP);
+    int rv = getClatEgressProgFd(RAWIP);
     if (rv < 0) {
         ALOGE("getClatEgressProgFd(RAWIP) failure: %s", strerror(-rv));
         return;
     }
     unique_fd txRawIpProgFd(rv);
 
-    rv = getClatIngressProgFd(isEthernet);
+    rv = getClatIngressProgFd(isEthernet.value());
     if (rv < 0) {
-        ALOGE("getClatIngressProgFd(%d) failure: %s", isEthernet, strerror(-rv));
+        ALOGE("getClatIngressProgFd(%d) failure: %s", isEthernet.value(), strerror(-rv));
         return;
     }
     unique_fd rxProgFd(rv);
@@ -301,7 +285,7 @@
             .oif = tracker.ifIndex,
             .local6 = tracker.v6,
             .pfx96 = tracker.pfx96,
-            .oifIsEthernet = isEthernet,
+            .oifIsEthernet = isEthernet.value(),
     };
 
     auto ret = mClatEgressMap.writeValue(txKey, txValue, BPF_ANY);
@@ -373,14 +357,14 @@
         return;
     }
 
-    rv = tcFilterAddDevIngressClatIpv6(tracker.ifIndex, rxProgFd, isEthernet);
+    rv = tcFilterAddDevIngressClatIpv6(tracker.ifIndex, rxProgFd, isEthernet.value());
     if (rv) {
         if ((rv == -ENOENT) && (mClatEbpfMode == ClatEbpfMaybe)) {
             ALOGI("tcFilterAddDevIngressClatIpv6(%d[%s], %d): %s", tracker.ifIndex, tracker.iface,
-                  isEthernet, strerror(-rv));
+                  isEthernet.value(), strerror(-rv));
         } else {
             ALOGE("tcFilterAddDevIngressClatIpv6(%d[%s], %d) failure: %s", tracker.ifIndex,
-                  tracker.iface, isEthernet, strerror(-rv));
+                  tracker.iface, isEthernet.value(), strerror(-rv));
         }
         rv = tcFilterDelDevEgressClatIpv4(tracker.v4ifIndex);
         if (rv) {
diff --git a/server/OffloadUtils.cpp b/server/OffloadUtils.cpp
index b374722..53665f6 100644
--- a/server/OffloadUtils.cpp
+++ b/server/OffloadUtils.cpp
@@ -18,6 +18,7 @@
 
 #include <arpa/inet.h>
 #include <linux/if.h>
+#include <linux/if_arp.h>
 #include <linux/netlink.h>
 #include <linux/pkt_cls.h>
 #include <linux/pkt_sched.h>
@@ -60,6 +61,25 @@
     return ifr.ifr_hwaddr.sa_family;
 }
 
+base::Result<bool> isEthernet(const std::string& interface) {
+    int rv = hardwareAddressType(interface);
+    if (rv < 0) {
+        errno = -rv;
+        return ErrnoErrorf("Get hardware address type of interface {} failed", interface);
+    }
+
+    switch (rv) {
+        case ARPHRD_ETHER:
+            return true;
+        case ARPHRD_RAWIP:  // in Linux 4.14+ rmnet support was upstreamed and this is 519
+        case 530:           // this is ARPHRD_RAWIP on some Android 4.9 kernels with rmnet
+            return false;
+        default:
+            errno = EAFNOSUPPORT;  // Address family not supported
+            return ErrnoErrorf("Unknown hardware address type {} on interface {}", rv, interface);
+    }
+}
+
 // TODO: use //system/netd/server/NetlinkCommands.cpp:openNetlinkSocket(protocol)
 // and //system/netd/server/SockDiag.cpp:checkError(fd)
 static int sendAndProcessNetlinkResponse(const void* req, int len) {
diff --git a/server/OffloadUtils.h b/server/OffloadUtils.h
index b44ffdf..cdb0d33 100644
--- a/server/OffloadUtils.h
+++ b/server/OffloadUtils.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <android-base/result.h>
 #include <errno.h>
 #include <linux/if_ether.h>
 #include <linux/rtnetlink.h>
@@ -28,9 +29,6 @@
 namespace android {
 namespace net {
 
-// this returns an ARPHRD_* constant or a -errno
-int hardwareAddressType(const std::string& interface);
-
 // For better code clarity - do not change values - used for booleans like
 // with_ethernet_header or isEthernet.
 constexpr bool RAWIP = false;
@@ -44,6 +42,11 @@
 constexpr uint16_t PRIO_CLAT = 1;
 constexpr uint16_t PRIO_TETHER = 2;
 
+// this returns an ARPHRD_* constant or a -errno
+int hardwareAddressType(const std::string& interface);
+
+base::Result<bool> isEthernet(const std::string& interface);
+
 inline int getClatEgressMapFd(void) {
     const int fd = bpf::bpfFdGet(CLAT_EGRESS_MAP_PATH, 0);
     return (fd == -1) ? -errno : fd;
diff --git a/server/OffloadUtilsTest.cpp b/server/OffloadUtilsTest.cpp
index b404bc8..a2c3982 100644
--- a/server/OffloadUtilsTest.cpp
+++ b/server/OffloadUtilsTest.cpp
@@ -65,6 +65,39 @@
     ASSERT_EQ(ARPHRD_RAWIP, type);
 }
 
+TEST_F(OffloadUtilsTest, IsEthernetOfNonExistingIf) {
+    auto res = isEthernet("not_existing_if");
+    ASSERT_FALSE(res.ok());
+    ASSERT_EQ(ENODEV, res.error().code());
+}
+
+TEST_F(OffloadUtilsTest, IsEthernetOfLoopback) {
+    auto res = isEthernet("lo");
+    ASSERT_FALSE(res.ok());
+    ASSERT_EQ(EAFNOSUPPORT, res.error().code());
+}
+
+// If wireless 'wlan0' interface exists it should be Ethernet.
+// See also HardwareAddressTypeOfWireless.
+TEST_F(OffloadUtilsTest, IsEthernetOfWireless) {
+    auto res = isEthernet("wlan0");
+    if (!res.ok() && res.error().code() == ENODEV) return;
+
+    ASSERT_RESULT_OK(res);
+    ASSERT_TRUE(res.value());
+}
+
+// If cellular 'rmnet_data0' interface exists it should
+// *probably* not be Ethernet and instead be RawIp.
+// See also HardwareAddressTypeOfCellular.
+TEST_F(OffloadUtilsTest, IsEthernetOfCellular) {
+    auto res = isEthernet("rmnet_data0");
+    if (!res.ok() && res.error().code() == ENODEV) return;
+
+    ASSERT_RESULT_OK(res);
+    ASSERT_FALSE(res.value());
+}
+
 TEST_F(OffloadUtilsTest, GetClatEgressMapFd) {
     SKIP_IF_BPF_NOT_SUPPORTED;