[Tether09] Add RouteUtils and NetdUtils

Add RouteUtils and NetdUtils file to NetworkStack module shared
folder. And it would statically linked into framework and
tethering module.

Bug: 136040414
Test: -build, flash, boot
      -atest TetheringTest

Change-Id: I8c2781597b410d12407ddb743f1621bf92d2a852
diff --git a/common/moduleutils/Android.bp b/common/moduleutils/Android.bp
index 6117149..afd82e5 100644
--- a/common/moduleutils/Android.bp
+++ b/common/moduleutils/Android.bp
@@ -18,4 +18,17 @@
 filegroup {
     name: "net-module-utils-srcs",
     srcs: ["src/**/*.java"],
-}
\ No newline at end of file
+}
+
+// Shared utility sources to be used by tethering modules
+filegroup {
+    name: "tethering-module-utils-srcs",
+    srcs: [
+        "src/android/net/ip/InterfaceController.java",
+        "src/android/net/netlink/*.java",
+        "src/android/net/shared/NetdUtils.java",
+        "src/android/net/shared/RouteUtils.java",
+        "src/android/net/util/InterfaceParams.java",
+        "src/android/net/util/SharedLog.java"
+    ],
+}
diff --git a/common/moduleutils/src/android/net/ip/InterfaceController.java b/common/moduleutils/src/android/net/ip/InterfaceController.java
index f32d143..e8fc72c 100644
--- a/common/moduleutils/src/android/net/ip/InterfaceController.java
+++ b/common/moduleutils/src/android/net/ip/InterfaceController.java
@@ -16,6 +16,9 @@
 
 package android.net.ip;
 
+import static android.net.INetd.IF_STATE_DOWN;
+import static android.net.INetd.IF_STATE_UP;
+
 import android.net.INetd;
 import android.net.InterfaceConfigurationParcel;
 import android.net.LinkAddress;
@@ -48,13 +51,31 @@
         mLog = log;
     }
 
-    private boolean setInterfaceAddress(LinkAddress addr) {
+    /**
+     * Set the IPv4 address and also optionally bring the interface up or down.
+     */
+    public boolean setInterfaceConfiguration(final LinkAddress ipv4Addr,
+            final Boolean setIfaceUp) {
+        if (!(ipv4Addr.getAddress() instanceof Inet4Address)) {
+            throw new IllegalArgumentException("Invalid or mismatched Inet4Address");
+        }
+        // Note: currently netd only support INetd#IF_STATE_UP and #IF_STATE_DOWN.
+        // Other flags would be ignored.
+
         final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
         ifConfig.ifName = mIfName;
-        ifConfig.ipv4Addr = addr.getAddress().getHostAddress();
-        ifConfig.prefixLength = addr.getPrefixLength();
+        ifConfig.ipv4Addr = ipv4Addr.getAddress().getHostAddress();
+        ifConfig.prefixLength = ipv4Addr.getPrefixLength();
+        // Netd ignores hwaddr in interfaceSetCfg.
         ifConfig.hwAddr = "";
-        ifConfig.flags = new String[0];
+        if (setIfaceUp == null) {
+            // Empty array means no change.
+            ifConfig.flags = new String[0];
+        } else {
+            // Netd ignores any flag that's not IF_STATE_UP or IF_STATE_DOWN in interfaceSetCfg.
+            ifConfig.flags = setIfaceUp.booleanValue()
+                    ? new String[] {IF_STATE_UP} : new String[] {IF_STATE_DOWN};
+        }
         try {
             mNetd.interfaceSetCfg(ifConfig);
         } catch (RemoteException | ServiceSpecificException e) {
@@ -68,18 +89,15 @@
     /**
      * Set the IPv4 address of the interface.
      */
-    public boolean setIPv4Address(LinkAddress address) {
-        if (!(address.getAddress() instanceof Inet4Address)) {
-            return false;
-        }
-        return setInterfaceAddress(address);
+    public boolean setIPv4Address(final LinkAddress address) {
+        return setInterfaceConfiguration(address, null);
     }
 
     /**
      * Clear the IPv4Address of the interface.
      */
     public boolean clearIPv4Address() {
-        return setInterfaceAddress(new LinkAddress("0.0.0.0/0"));
+        return setIPv4Address(new LinkAddress("0.0.0.0/0"));
     }
 
     private boolean setEnableIPv6(boolean enabled) {
diff --git a/common/moduleutils/src/android/net/shared/NetdUtils.java b/common/moduleutils/src/android/net/shared/NetdUtils.java
new file mode 100644
index 0000000..0137bb7
--- /dev/null
+++ b/common/moduleutils/src/android/net/shared/NetdUtils.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.shared;
+
+import static android.net.RouteInfo.RTN_UNICAST;
+
+import android.net.INetd;
+import android.net.IpPrefix;
+import android.net.RouteInfo;
+import android.net.TetherConfigParcel;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implements common operations on INetd
+ * @hide
+ */
+public class NetdUtils {
+    /** Start tethering. */
+    public static void tetherStart(final INetd netd, final boolean usingLegacyDnsProxy,
+            final String[] dhcpRange) throws RemoteException, ServiceSpecificException {
+        final TetherConfigParcel config = new TetherConfigParcel();
+        config.usingLegacyDnsProxy = usingLegacyDnsProxy;
+        config.dhcpRanges = dhcpRange;
+        netd.tetherStartWithConfiguration(config);
+    }
+
+    /** Setup interface for tethering. */
+    public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest)
+            throws RemoteException, ServiceSpecificException {
+        netd.tetherInterfaceAdd(iface);
+
+        netd.networkAddInterface(INetd.LOCAL_NET_ID, iface);
+        List<RouteInfo> routes = new ArrayList<>();
+        routes.add(new RouteInfo(dest, null, iface, RTN_UNICAST));
+        RouteUtils.addRoutesToLocalNetwork(netd, iface, routes);
+    }
+
+    /** Reset interface for tethering. */
+    public static void untetherInterface(final INetd netd, String iface)
+            throws RemoteException, ServiceSpecificException {
+        try {
+            netd.tetherInterfaceRemove(iface);
+        } finally {
+            netd.networkRemoveInterface(INetd.LOCAL_NET_ID, iface);
+        }
+    }
+}
diff --git a/common/moduleutils/src/android/net/shared/RouteUtils.java b/common/moduleutils/src/android/net/shared/RouteUtils.java
new file mode 100644
index 0000000..54ff675
--- /dev/null
+++ b/common/moduleutils/src/android/net/shared/RouteUtils.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.shared;
+
+import static android.net.RouteInfo.RTN_THROW;
+import static android.net.RouteInfo.RTN_UNICAST;
+import static android.net.RouteInfo.RTN_UNREACHABLE;
+
+import android.net.INetd;
+import android.net.IpPrefix;
+import android.net.RouteInfo;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+
+import java.util.List;
+
+/** @hide*/
+public class RouteUtils {
+
+    /** Used to modify the specified route. */
+    public enum ModifyOperation {
+        ADD,
+        REMOVE,
+    }
+
+    private static String findNextHop(final RouteInfo route) {
+        final String nextHop;
+        switch (route.getType()) {
+            case RTN_UNICAST:
+                if (route.hasGateway()) {
+                    nextHop = route.getGateway().getHostAddress();
+                } else {
+                    nextHop = INetd.NEXTHOP_NONE;
+                }
+                break;
+            case RTN_UNREACHABLE:
+                nextHop = INetd.NEXTHOP_UNREACHABLE;
+                break;
+            case RTN_THROW:
+                nextHop = INetd.NEXTHOP_THROW;
+                break;
+            default:
+                nextHop = INetd.NEXTHOP_NONE;
+                break;
+        }
+        return nextHop;
+    }
+
+    /** Add |routes| to local network. */
+    public static void addRoutesToLocalNetwork(final INetd netd, final String iface,
+            final List<RouteInfo> routes) {
+
+        for (RouteInfo route : routes) {
+            if (!route.isDefaultRoute()) {
+                modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID, route);
+            }
+        }
+
+        // IPv6 link local should be activated always.
+        modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID,
+                new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RTN_UNICAST));
+    }
+
+    /** Remove routes from local network. */
+    public static int removeRoutesFromLocalNetwork(final INetd netd, final List<RouteInfo> routes) {
+        int failures = 0;
+
+        for (RouteInfo route : routes) {
+            try {
+                modifyRoute(netd, ModifyOperation.REMOVE, INetd.LOCAL_NET_ID, route);
+            } catch (IllegalStateException e) {
+                failures++;
+            }
+        }
+
+        return failures;
+    }
+
+    /** Add or remove |route|. */
+    public static void modifyRoute(final INetd netd, final ModifyOperation op, final int netId,
+            final RouteInfo route) {
+        final String ifName = route.getInterface();
+        final String dst = route.getDestination().toString();
+        final String nextHop = findNextHop(route);
+
+        try {
+            switch(op) {
+                case ADD:
+                    netd.networkAddRoute(netId, ifName, dst, nextHop);
+                    break;
+                case REMOVE:
+                    netd.networkRemoveRoute(netId, ifName, dst, nextHop);
+                    break;
+                default:
+                    throw new IllegalStateException("Unsupported modify operation:" + op);
+            }
+        } catch (RemoteException | ServiceSpecificException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+}