Move IPv4 address setting to IpManager

Bug: 24837343
Bug: 27605330
Change-Id: I19ac80e45b3e9200f81d1166ac6094fd19aee963
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 6d0808f..2b6c916 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -32,8 +32,6 @@
 import android.net.NetworkUtils;
 import android.net.metrics.DhcpClientEvent;
 import android.net.metrics.DhcpErrorEvent;
-import android.os.IBinder;
-import android.os.INetworkManagementService;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -121,6 +119,13 @@
      * after pre DHCP action is complete */
     public static final int CMD_PRE_DHCP_ACTION_COMPLETE    = PUBLIC_BASE + 7;
 
+    /* Command and event notification to/from IpManager requesting the setting
+     * (or clearing) of an IPv4 LinkAddress.
+     */
+    public static final int CMD_CLEAR_LINKADDRESS           = PUBLIC_BASE + 8;
+    public static final int CMD_CONFIGURE_LINKADDRESS       = PUBLIC_BASE + 9;
+    public static final int EVENT_LINKADDRESS_CONFIGURED    = PUBLIC_BASE + 10;
+
     /* Message.arg1 arguments to CMD_POST_DHCP notification */
     public static final int DHCP_SUCCESS = 1;
     public static final int DHCP_FAILURE = 2;
@@ -157,7 +162,6 @@
     // System services / libraries we use.
     private final Context mContext;
     private final Random mRandom;
-    private final INetworkManagementService mNMService;
 
     // Sockets.
     // - We use a packet socket to receive, because servers send us packets bound for IP addresses
@@ -192,7 +196,8 @@
     private State mDhcpInitState = new DhcpInitState();
     private State mDhcpSelectingState = new DhcpSelectingState();
     private State mDhcpRequestingState = new DhcpRequestingState();
-    private State mDhcpHaveAddressState = new DhcpHaveAddressState();
+    private State mDhcpHaveLeaseState = new DhcpHaveLeaseState();
+    private State mConfiguringInterfaceState = new ConfiguringInterfaceState();
     private State mDhcpBoundState = new DhcpBoundState();
     private State mDhcpRenewingState = new DhcpRenewingState();
     private State mDhcpRebindingState = new DhcpRebindingState();
@@ -219,19 +224,17 @@
             addState(mWaitBeforeStartState, mDhcpState);
             addState(mDhcpSelectingState, mDhcpState);
             addState(mDhcpRequestingState, mDhcpState);
-            addState(mDhcpHaveAddressState, mDhcpState);
-                addState(mDhcpBoundState, mDhcpHaveAddressState);
-                addState(mWaitBeforeRenewalState, mDhcpHaveAddressState);
-                addState(mDhcpRenewingState, mDhcpHaveAddressState);
-                addState(mDhcpRebindingState, mDhcpHaveAddressState);
+            addState(mDhcpHaveLeaseState, mDhcpState);
+                addState(mConfiguringInterfaceState, mDhcpHaveLeaseState);
+                addState(mDhcpBoundState, mDhcpHaveLeaseState);
+                addState(mWaitBeforeRenewalState, mDhcpHaveLeaseState);
+                addState(mDhcpRenewingState, mDhcpHaveLeaseState);
+                addState(mDhcpRebindingState, mDhcpHaveLeaseState);
             addState(mDhcpInitRebootState, mDhcpState);
             addState(mDhcpRebootingState, mDhcpState);
 
         setInitialState(mStoppedState);
 
-        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
-        mNMService = INetworkManagementService.Stub.asInterface(b);
-
         mRandom = new Random();
 
         // Used to schedule packet retransmissions.
@@ -321,18 +324,6 @@
         closeQuietly(mPacketSock);
     }
 
-    private boolean setIpAddress(LinkAddress address) {
-        InterfaceConfiguration ifcg = new InterfaceConfiguration();
-        ifcg.setLinkAddress(address);
-        try {
-            mNMService.setInterfaceConfig(mIfaceName, ifcg);
-        } catch (RemoteException|IllegalStateException e) {
-            Log.e(TAG, "Error configuring IP address " + address + ": ", e);
-            return false;
-        }
-        return true;
-    }
-
     class ReceiveThread extends Thread {
 
         private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH];
@@ -382,7 +373,8 @@
                 Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr);
             } else {
                 // It's safe to call getpeername here, because we only send unicast packets if we
-                // have an IP address, and we connect the UDP socket in DhcpHaveAddressState#enter.
+                // have an IP address, and we connect the UDP socket before
+                // ConfiguringInterfaceState#exit.
                 if (DBG) Log.d(TAG, "Unicasting " + description + " to " + Os.getpeername(mUdpSock));
                 Os.write(mUdpSock, buf);
             }
@@ -460,6 +452,7 @@
     }
 
     abstract class LoggingState extends State {
+        @Override
         public void enter() {
             if (STATE_DBG) Log.d(TAG, "Entering state " + getName());
             DhcpClientEvent.logStateEvent(mIfaceName, getName());
@@ -759,7 +752,7 @@
                     mOffer = null;
                     Log.d(TAG, "Confirmed lease: " + mDhcpLease);
                     setDhcpLeaseExpiry(packet);
-                    transitionTo(mDhcpBoundState);
+                    transitionTo(mConfiguringInterfaceState);
                 }
             } else if (packet instanceof DhcpNakPacket) {
                 // TODO: Wait a while before returning into INIT state.
@@ -776,24 +769,52 @@
         }
     }
 
-    class DhcpHaveAddressState extends LoggingState {
+    class DhcpHaveLeaseState extends LoggingState {
         @Override
         public void enter() {
             super.enter();
-            if (!setIpAddress(mDhcpLease.ipAddress) ||
-                    (mDhcpLease.serverAddress != null &&
-                            !connectUdpSock((mDhcpLease.serverAddress)))) {
-                notifyFailure();
-                // There's likely no point in going into DhcpInitState here, we'll probably just
-                // repeat the transaction, get the same IP address as before, and fail.
-                transitionTo(mStoppedState);
-            }
         }
 
         @Override
         public void exit() {
-            if (DBG) Log.d(TAG, "Clearing IP address");
-            setIpAddress(new LinkAddress("0.0.0.0/0"));
+            // Tell IpManager to clear the IPv4 address. There is no need to
+            // wait for confirmation since any subsequent packets are sent from
+            // INADDR_ANY anyway (DISCOVER, REQUEST).
+            mController.sendMessage(CMD_CLEAR_LINKADDRESS);
+        }
+    }
+
+    class ConfiguringInterfaceState extends LoggingState {
+        @Override
+        public void enter() {
+            super.enter();
+            mController.sendMessage(CMD_CONFIGURE_LINKADDRESS, mDhcpLease.ipAddress);
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            super.processMessage(message);
+            switch (message.what) {
+                case EVENT_LINKADDRESS_CONFIGURED:
+                    if (mDhcpLease.serverAddress != null &&
+                            !connectUdpSock(mDhcpLease.serverAddress)) {
+                        // There's likely no point in going into DhcpInitState here, we'll probably
+                        // just repeat the transaction, get the same IP address as before, and fail.
+                        //
+                        // NOTE: It is observed that connectUdpSock() basically never fails, due to
+                        // SO_BINDTODEVICE. Examining the local socket address shows it will happily
+                        // return an IPv4 address from another interface, or even return "0.0.0.0".
+                        //
+                        // TODO: Consider deleting this check, following testing on several kernels.
+                        notifyFailure();
+                        transitionTo(mStoppedState);
+                    } else {
+                        transitionTo(mDhcpBoundState);
+                    }
+                    return HANDLED;
+                default:
+                    return NOT_HANDLED;
+            }
         }
     }
 
@@ -803,8 +824,8 @@
             super.enter();
             mOneshotTimeoutAlarm.cancel();
             notifySuccess();
-            // TODO: DhcpStateMachine only supported renewing at 50% of the lease time, and did not
-            // support rebinding. Once the legacy DHCP client is gone, fix this.
+            // TODO: DhcpStateMachine only supported renewing at 50% of the lease time,
+            // and did not support rebinding. Now that the legacy DHCP client is gone, fix this.
             scheduleRenew();
         }
 
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index e39f1bc..fd98f46 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -770,6 +770,19 @@
         return (delta != ProvisioningChange.LOST_PROVISIONING);
     }
 
+    private boolean setIPv4Address(LinkAddress address) {
+        final InterfaceConfiguration ifcg = new InterfaceConfiguration();
+        ifcg.setLinkAddress(address);
+        try {
+            mNwService.setInterfaceConfig(mInterfaceName, ifcg);
+            if (VDBG) Log.d(mTag, "IPv4 configuration succeeded");
+        } catch (IllegalStateException | RemoteException e) {
+            Log.e(mTag, "IPv4 configuration failed: ", e);
+            return false;
+        }
+        return true;
+    }
+
     private void clearIPv4Address() {
         try {
             final InterfaceConfiguration ifcg = new InterfaceConfiguration();
@@ -794,7 +807,6 @@
     }
 
     private void handleIPv4Failure() {
-        // TODO: Figure out to de-dup this and the same code in DhcpClient.
         clearIPv4Address();
         mDhcpResults = null;
         final LinkProperties newLp = assembleLinkProperties();
@@ -944,7 +956,7 @@
             // If we have a StaticIpConfiguration attempt to apply it and
             // handle the result accordingly.
             if (mConfiguration.mStaticIpConfig != null) {
-                if (applyStaticIpConfig()) {
+                if (setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
                     handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
                 } else {
                     if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); }
@@ -1050,6 +1062,21 @@
                     }
                     break;
 
+                case DhcpClient.CMD_CLEAR_LINKADDRESS:
+                    clearIPv4Address();
+                    break;
+
+                case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
+                    final LinkAddress ipAddress = (LinkAddress) msg.obj;
+                    if (setIPv4Address(ipAddress)) {
+                        mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
+                    } else {
+                        Log.e(mTag, "Failed to set IPv4 address!");
+                        transitionTo(mStoppingState);
+                    }
+                    break;
+                }
+
                 case DhcpClient.CMD_POST_DHCP_ACTION: {
                     // Note that onPostDhcpAction() is likely to be
                     // asynchronous, and thus there is no guarantee that we
@@ -1082,20 +1109,5 @@
             }
             return HANDLED;
         }
-
-        private boolean applyStaticIpConfig() {
-            final InterfaceConfiguration ifcg = new InterfaceConfiguration();
-            ifcg.setLinkAddress(mConfiguration.mStaticIpConfig.ipAddress);
-            ifcg.setInterfaceUp();
-            try {
-                mNwService.setInterfaceConfig(mInterfaceName, ifcg);
-                if (DBG) Log.d(mTag, "Static IP configuration succeeded");
-            } catch (IllegalStateException | RemoteException e) {
-                Log.e(mTag, "Static IP configuration failed: ", e);
-                return false;
-            }
-
-            return true;
-        }
     }
 }