Switch DHCP server based on global setting

The newer implementation is disabled by default with this CL. Ultimately
the intention is to enable it by default.

Bug: b/109584964
Test: set tether_enable_legacy_dhcp_server to 0, ran DhcpServerTest.py,
      observed new behavior. Added tests in CL also pass.

Change-Id: I0f830b9804b8956c127057e66ab75a21ca29dc57
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 84bdbba..d16c277 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -1126,7 +1126,9 @@
     }
 
     public String[] getTetheredDhcpRanges() {
-        return mConfig.dhcpRanges;
+        // TODO: this is only valid for the old DHCP server. Latest search suggests it is only used
+        // by WifiP2pServiceImpl to start dnsmasq: remove/deprecate after migrating callers.
+        return mConfig.legacyDhcpRanges;
     }
 
     public String[] getErroredIfaces() {
@@ -1297,13 +1299,17 @@
                 return false;
             }
             // TODO: Randomize DHCPv4 ranges, especially in hotspot mode.
+            // Legacy DHCP server is disabled if passed an empty ranges array
+            final String[] dhcpRanges = cfg.enableLegacyDhcpServer
+                    ? cfg.legacyDhcpRanges
+                    : new String[0];
             try {
                 // TODO: Find a more accurate method name (startDHCPv4()?).
-                mNMService.startTethering(cfg.dhcpRanges);
+                mNMService.startTethering(dhcpRanges);
             } catch (Exception e) {
                 try {
                     mNMService.stopTethering();
-                    mNMService.startTethering(cfg.dhcpRanges);
+                    mNMService.startTethering(dhcpRanges);
                 } catch (Exception ee) {
                     mLog.e(ee);
                     transitionTo(mStartTetheringErrorState);
@@ -1972,7 +1978,7 @@
         final TetherState tetherState = new TetherState(
                 new TetherInterfaceStateMachine(
                     iface, mLooper, interfaceType, mLog, mNMService, mStatsService,
-                    makeControlCallback(iface), mDeps));
+                    makeControlCallback(iface), mConfig.enableLegacyDhcpServer, mDeps));
         mTetherStates.put(iface, tetherState);
         tetherState.stateMachine.start();
     }
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
index 9e6b659..1a4119c 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -16,6 +16,7 @@
 
 package com.android.server.connectivity.tethering;
 
+import static android.net.NetworkUtils.numericToInetAddress;
 import static android.net.util.NetworkConstants.asByte;
 import static android.net.util.NetworkConstants.FF;
 import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
@@ -27,8 +28,9 @@
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
-import android.net.NetworkUtils;
 import android.net.RouteInfo;
+import android.net.dhcp.DhcpServer;
+import android.net.dhcp.DhcpServingParams;
 import android.net.ip.InterfaceController;
 import android.net.ip.RouterAdvertisementDaemon;
 import android.net.ip.RouterAdvertisementDaemon.RaParams;
@@ -49,6 +51,7 @@
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 
+import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
@@ -73,6 +76,13 @@
     private static final String WIFI_HOST_IFACE_ADDR = "192.168.43.1";
     private static final int WIFI_HOST_IFACE_PREFIX_LENGTH = 24;
 
+    // TODO: have PanService use some visible version of this constant
+    private static final String BLUETOOTH_IFACE_ADDR = "192.168.44.1";
+    private static final int BLUETOOTH_DHCP_PREFIX_LENGTH = 24;
+
+    // TODO: have this configurable
+    private static final int DHCP_LEASE_TIME_SECS = 3600;
+
     private final static String TAG = "TetherInterfaceSM";
     private final static boolean DBG = false;
     private final static boolean VDBG = false;
@@ -119,6 +129,7 @@
     private final String mIfaceName;
     private final int mInterfaceType;
     private final LinkProperties mLinkProperties;
+    private final boolean mUsingLegacyDhcp;
 
     private final TetheringDependencies mDeps;
 
@@ -134,12 +145,13 @@
     // Advertisements (otherwise, we do not add them to mLinkProperties at all).
     private LinkProperties mLastIPv6LinkProperties;
     private RouterAdvertisementDaemon mRaDaemon;
+    private DhcpServer mDhcpServer;
     private RaParams mLastRaParams;
 
     public TetherInterfaceStateMachine(
             String ifaceName, Looper looper, int interfaceType, SharedLog log,
             INetworkManagementService nMService, INetworkStatsService statsService,
-            IControlsTethering tetherController,
+            IControlsTethering tetherController, boolean usingLegacyDhcp,
             TetheringDependencies deps) {
         super(ifaceName, looper);
         mLog = log.forSubComponent(ifaceName);
@@ -151,6 +163,7 @@
         mIfaceName = ifaceName;
         mInterfaceType = interfaceType;
         mLinkProperties = new LinkProperties();
+        mUsingLegacyDhcp = usingLegacyDhcp;
         mDeps = deps;
         resetLinkProperties();
         mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
@@ -188,6 +201,52 @@
 
     private boolean startIPv4() { return configureIPv4(true); }
 
+    private boolean startDhcp(Inet4Address addr, int prefixLen) {
+        if (mUsingLegacyDhcp) {
+            return true;
+        }
+
+        final InterfaceParams ifaceParams = mDeps.getInterfaceParams(mIfaceName);
+        if (ifaceParams == null) {
+            Log.e(TAG, "Failed to find interface params for DHCPv4");
+            return false;
+        }
+        final DhcpServingParams params;
+        try {
+            params = new DhcpServingParams.Builder()
+                    .setDefaultRouters(addr)
+                    .setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS)
+                    .setDnsServers(addr)
+                    .setServerAddr(new LinkAddress(addr, prefixLen))
+                    .build();
+            // TODO: also advertise link MTU
+        } catch (DhcpServingParams.InvalidParameterException e) {
+            Log.e(TAG, "Invalid DHCP parameters", e);
+            return false;
+        }
+
+        mDhcpServer = mDeps.makeDhcpServer(getHandler().getLooper(), ifaceParams, params,
+                mLog.forSubComponent("DHCP"));
+        mDhcpServer.start();
+        return true;
+    }
+
+    private void stopDhcp() {
+        if (mDhcpServer != null) {
+            mDhcpServer.stop();
+            mDhcpServer = null;
+        }
+    }
+
+    private boolean configureDhcp(boolean enable, Inet4Address addr, int prefixLen) {
+        if (enable) {
+            return startDhcp(addr, prefixLen);
+        } else {
+            stopDhcp();
+            return true;
+        }
+    }
+
     private void stopIPv4() {
         configureIPv4(false);
         // NOTE: All of configureIPv4() will be refactored out of existence
@@ -210,8 +269,9 @@
             ipAsString = getRandomWifiIPv4Address();
             prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH;
         } else {
-            // Nothing to do, BT does this elsewhere.
-            return true;
+            // BT configures the interface elsewhere: only start DHCP.
+            final Inet4Address srvAddr = (Inet4Address) numericToInetAddress(BLUETOOTH_IFACE_ADDR);
+            return configureDhcp(enabled, srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH);
         }
 
         final LinkAddress linkAddr;
@@ -222,7 +282,7 @@
                 return false;
             }
 
-            InetAddress addr = NetworkUtils.numericToInetAddress(ipAsString);
+            InetAddress addr = numericToInetAddress(ipAsString);
             linkAddr = new LinkAddress(addr, prefixLen);
             ifcg.setLinkAddress(linkAddr);
             if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) {
@@ -239,6 +299,10 @@
             }
             ifcg.clearFlag("running");
             mNMService.setInterfaceConfig(mIfaceName, ifcg);
+
+            if (!configureDhcp(enabled, (Inet4Address) addr, prefixLen)) {
+                return false;
+            }
         } catch (Exception e) {
             mLog.e("Error configuring interface " + e);
             return false;
@@ -258,7 +322,7 @@
 
     private String getRandomWifiIPv4Address() {
         try {
-            byte[] bytes = NetworkUtils.numericToInetAddress(WIFI_HOST_IFACE_ADDR).getAddress();
+            byte[] bytes = numericToInetAddress(WIFI_HOST_IFACE_ADDR).getAddress();
             bytes[3] = getRandomSanitizedByte(DOUG_ADAMS, asByte(0), asByte(1), FF);
             return InetAddress.getByAddress(bytes).getHostAddress();
         } catch (Exception e) {
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
index dd9fe05..6699444 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -21,6 +21,8 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
+import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
+
 import static com.android.internal.R.array.config_mobile_hotspot_provision_app;
 import static com.android.internal.R.array.config_tether_bluetooth_regexs;
 import static com.android.internal.R.array.config_tether_dhcp_range;
@@ -30,15 +32,16 @@
 import static com.android.internal.R.bool.config_tether_upstream_automatic;
 import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui;
 
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.util.SharedLog;
+import android.provider.Settings;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.R;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -68,12 +71,13 @@
     public static final int DUN_REQUIRED = 1;
     public static final int DUN_UNSPECIFIED = 2;
 
+    // Default ranges used for the legacy DHCP server.
     // USB is  192.168.42.1 and 255.255.255.0
     // Wifi is 192.168.43.1 and 255.255.255.0
     // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
     // with 255.255.255.0
     // P2P is 192.168.49.1 and 255.255.255.0
-    private static final String[] DHCP_DEFAULT_RANGE = {
+    private static final String[] LEGACY_DHCP_DEFAULT_RANGE = {
         "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254",
         "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254",
         "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254",
@@ -89,8 +93,9 @@
     public final boolean isDunRequired;
     public final boolean chooseUpstreamAutomatically;
     public final Collection<Integer> preferredUpstreamIfaceTypes;
-    public final String[] dhcpRanges;
+    public final String[] legacyDhcpRanges;
     public final String[] defaultIPv4DNS;
+    public final boolean enableLegacyDhcpServer;
 
     public final String[] provisioningApp;
     public final String provisioningAppNoUi;
@@ -112,8 +117,9 @@
         preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(ctx, dunCheck);
         isDunRequired = preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN);
 
-        dhcpRanges = getDhcpRanges(ctx);
+        legacyDhcpRanges = getLegacyDhcpRanges(ctx);
         defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
+        enableLegacyDhcpServer = getEnableLegacyDhcpServer(ctx);
 
         provisioningApp = getResourceStringArray(ctx, config_mobile_hotspot_provision_app);
         provisioningAppNoUi = getProvisioningAppNoUi(ctx);
@@ -150,7 +156,7 @@
         dumpStringArray(pw, "preferredUpstreamIfaceTypes",
                 preferredUpstreamNames(preferredUpstreamIfaceTypes));
 
-        dumpStringArray(pw, "dhcpRanges", dhcpRanges);
+        dumpStringArray(pw, "legacyDhcpRanges", legacyDhcpRanges);
         dumpStringArray(pw, "defaultIPv4DNS", defaultIPv4DNS);
 
         dumpStringArray(pw, "provisioningApp", provisioningApp);
@@ -276,12 +282,12 @@
         return false;
     }
 
-    private static String[] getDhcpRanges(Context ctx) {
+    private static String[] getLegacyDhcpRanges(Context ctx) {
         final String[] fromResource = getResourceStringArray(ctx, config_tether_dhcp_range);
         if ((fromResource.length > 0) && (fromResource.length % 2 == 0)) {
             return fromResource;
         }
-        return copy(DHCP_DEFAULT_RANGE);
+        return copy(LEGACY_DHCP_DEFAULT_RANGE);
     }
 
     private static String getProvisioningAppNoUi(Context ctx) {
@@ -309,6 +315,13 @@
         }
     }
 
+    private static boolean getEnableLegacyDhcpServer(Context ctx) {
+        // TODO: make the default false (0) and update javadoc in Settings.java
+        final ContentResolver cr = ctx.getContentResolver();
+        final int intVal = Settings.Global.getInt(cr, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1);
+        return intVal != 0;
+    }
+
     private static String[] copy(String[] strarray) {
         return Arrays.copyOf(strarray, strarray.length);
     }
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
index 605ee9c..caa867c 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
@@ -19,11 +19,14 @@
 import android.content.Context;
 import android.net.INetd;
 import android.net.NetworkRequest;
+import android.net.dhcp.DhcpServer;
+import android.net.dhcp.DhcpServingParams;
 import android.net.ip.RouterAdvertisementDaemon;
 import android.net.util.InterfaceParams;
 import android.net.util.NetdService;
 import android.os.Handler;
 import android.net.util.SharedLog;
+import android.os.Looper;
 
 import com.android.internal.util.StateMachine;
 
@@ -69,4 +72,9 @@
     public NetworkRequest getDefaultNetworkRequest() {
         return null;
     }
+
+    public DhcpServer makeDhcpServer(Looper looper, InterfaceParams iface, DhcpServingParams params,
+            SharedLog log) {
+        return new DhcpServer(looper, iface, params, log);
+    }
 }
diff --git a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
index 19d3a2e..5934653 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.connectivity.tethering;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
@@ -23,7 +24,9 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
@@ -40,9 +43,15 @@
 
 import android.net.INetworkStatsService;
 import android.net.InterfaceConfiguration;
+import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.MacAddress;
 import android.net.RouteInfo;
+import android.net.dhcp.DhcpServer;
+import android.net.dhcp.DhcpServingParams;
+import android.net.ip.RouterAdvertisementDaemon;
+import android.net.util.InterfaceParams;
 import android.net.util.InterfaceSet;
 import android.net.util.SharedLog;
 import android.os.INetworkManagementService;
@@ -58,6 +67,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -68,33 +78,58 @@
     private static final String IFACE_NAME = "testnet1";
     private static final String UPSTREAM_IFACE = "upstream0";
     private static final String UPSTREAM_IFACE2 = "upstream1";
+    private static final int DHCP_LEASE_TIME_SECS = 3600;
+
+    private static final InterfaceParams TEST_IFACE_PARAMS = new InterfaceParams(
+            IFACE_NAME, 42 /* index */, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */);
 
     @Mock private INetworkManagementService mNMService;
     @Mock private INetworkStatsService mStatsService;
     @Mock private IControlsTethering mTetherHelper;
     @Mock private InterfaceConfiguration mInterfaceConfiguration;
     @Mock private SharedLog mSharedLog;
+    @Mock private DhcpServer mDhcpServer;
+    @Mock private RouterAdvertisementDaemon mRaDaemon;
     @Mock private TetheringDependencies mTetheringDependencies;
 
+    @Captor private ArgumentCaptor<DhcpServingParams> mDhcpParamsCaptor;
+
     private final TestLooper mLooper = new TestLooper();
     private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor =
             ArgumentCaptor.forClass(LinkProperties.class);
     private TetherInterfaceStateMachine mTestedSm;
 
     private void initStateMachine(int interfaceType) throws Exception {
+        initStateMachine(interfaceType, false /* usingLegacyDhcp */);
+    }
+
+    private void initStateMachine(int interfaceType, boolean usingLegacyDhcp) throws Exception {
         mTestedSm = new TetherInterfaceStateMachine(
                 IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog,
-                mNMService, mStatsService, mTetherHelper, mTetheringDependencies);
+                mNMService, mStatsService, mTetherHelper, usingLegacyDhcp,
+                mTetheringDependencies);
         mTestedSm.start();
         // Starting the state machine always puts us in a consistent state and notifies
         // the rest of the world that we've changed from an unknown to available state.
         mLooper.dispatchAll();
         reset(mNMService, mStatsService, mTetherHelper);
         when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
+        when(mTetheringDependencies.makeDhcpServer(
+                any(), any(), mDhcpParamsCaptor.capture(), any())).thenReturn(mDhcpServer);
+        when(mTetheringDependencies.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon);
+        when(mTetheringDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS);
+
+        when(mRaDaemon.start()).thenReturn(true);
     }
 
-    private void initTetheredStateMachine(int interfaceType, String upstreamIface) throws Exception {
-        initStateMachine(interfaceType);
+    private void initTetheredStateMachine(int interfaceType, String upstreamIface)
+            throws Exception {
+        initTetheredStateMachine(interfaceType, upstreamIface, false);
+    }
+
+    private void initTetheredStateMachine(int interfaceType, String upstreamIface,
+            boolean usingLegacyDhcp) throws Exception {
+        initStateMachine(interfaceType, usingLegacyDhcp);
         dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, STATE_TETHERED);
         if (upstreamIface != null) {
             dispatchTetherConnectionChanged(upstreamIface);
@@ -112,7 +147,7 @@
     public void startsOutAvailable() {
         mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(),
                 TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mTetherHelper,
-                mTetheringDependencies);
+                false /* usingLegacyDhcp */, mTetheringDependencies);
         mTestedSm.start();
         mLooper.dispatchAll();
         verify(mTetherHelper).updateInterfaceState(
@@ -345,6 +380,45 @@
         }
     }
 
+    @Test
+    public void startsDhcpServer() throws Exception {
+        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+
+        assertDhcpStarted(new IpPrefix("192.168.43.0/24"));
+    }
+
+    @Test
+    public void startsDhcpServerOnBluetooth() throws Exception {
+        initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+
+        assertDhcpStarted(new IpPrefix("192.168.44.0/24"));
+    }
+
+    @Test
+    public void doesNotStartDhcpServerIfDisabled() throws Exception {
+        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, true /* usingLegacyDhcp */);
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+
+        verify(mTetheringDependencies, never()).makeDhcpServer(any(), any(), any(), any());
+    }
+
+    private void assertDhcpStarted(IpPrefix expectedPrefix) {
+        verify(mTetheringDependencies, times(1)).makeDhcpServer(
+                eq(mLooper.getLooper()), eq(TEST_IFACE_PARAMS), any(), eq(mSharedLog));
+        verify(mDhcpServer, times(1)).start();
+        final DhcpServingParams params = mDhcpParamsCaptor.getValue();
+        // Last address byte is random
+        assertTrue(expectedPrefix.contains(params.serverAddr.getAddress()));
+        assertEquals(expectedPrefix.getPrefixLength(), params.serverAddr.getPrefixLength());
+        assertEquals(1, params.defaultRouters.size());
+        assertEquals(params.serverAddr.getAddress(), params.defaultRouters.iterator().next());
+        assertEquals(1, params.dnsServers.size());
+        assertEquals(params.serverAddr.getAddress(), params.dnsServers.iterator().next());
+        assertEquals(DHCP_LEASE_TIME_SECS, params.dhcpLeaseTimeSecs);
+    }
+
     /**
      * Send a command to the state machine under test, and run the event loop to idle.
      *
diff --git a/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
index b68f203..bb31230 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
@@ -21,6 +21,8 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
 import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
+
 import static com.android.server.connectivity.tethering.TetheringConfiguration.DUN_NOT_REQUIRED;
 import static com.android.server.connectivity.tethering.TetheringConfiguration.DUN_REQUIRED;
 import static com.android.server.connectivity.tethering.TetheringConfiguration.DUN_UNSPECIFIED;
@@ -29,15 +31,18 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.when;
 
+import android.content.ContentResolver;
 import android.content.Context;
-import android.content.ContextWrapper;
 import android.content.res.Resources;
 import android.net.util.SharedLog;
+import android.provider.Settings;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.telephony.TelephonyManager;
+import android.test.mock.MockContentResolver;
 
 import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.internal.util.test.FakeSettingsProvider;
 
 import java.util.Iterator;
 
@@ -55,6 +60,7 @@
     @Mock private Context mContext;
     @Mock private TelephonyManager mTelephonyManager;
     @Mock private Resources mResources;
+    private MockContentResolver mContentResolver;
     private Context mMockContext;
     private boolean mHasTelephonyManager;
 
@@ -73,6 +79,11 @@
             }
             return super.getSystemService(name);
         }
+
+        @Override
+        public ContentResolver getContentResolver() {
+            return mContentResolver;
+        }
     }
 
     @Before
@@ -86,6 +97,10 @@
                 .thenReturn(new String[]{ "test_wlan\\d" });
         when(mResources.getStringArray(com.android.internal.R.array.config_tether_bluetooth_regexs))
                 .thenReturn(new String[0]);
+        when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types))
+                .thenReturn(new int[0]);
+        mContentResolver = new MockContentResolver();
+        mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         mMockContext = new MockContext(mContext);
     }
 
@@ -194,4 +209,29 @@
         assertEquals(TYPE_MOBILE_HIPRI, upstreamIterator.next().intValue());
         assertFalse(upstreamIterator.hasNext());
     }
+
+    @Test
+    public void testNewDhcpServerDisabled() {
+        Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1);
+
+        final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext, mLog);
+        assertTrue(cfg.enableLegacyDhcpServer);
+    }
+
+    @Test
+    public void testNewDhcpServerEnabled() {
+        Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
+
+        final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext, mLog);
+        assertFalse(cfg.enableLegacyDhcpServer);
+    }
+
+    @Test
+    public void testNewDhcpServerDefault() {
+        Settings.Global.putString(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, null);
+
+        final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext, mLog);
+        // TODO: change to false when new server is promoted to default
+        assertTrue(cfg.enableLegacyDhcpServer);
+    }
 }