From a4ca3c6312cf42049a7a85689d79e026f8fdf6d9 Mon Sep 17 00:00:00 2001 From: Benedict Wong Date: Wed, 14 Nov 2018 17:50:13 -0800 Subject: [PATCH 1/2] Add shell TestNetworkManager and Service This adds the scaffolding for TestNetworkManager and TestNetworkService. These are separate from the ConnectivityManager/ConnectivityService in order to prevent polluting the API space, and making them harder to maintain. Bug: 72950854 Test: Compiles, CTS tests relying on this pass Change-Id: Ic42c09a33d962471e2587ec27f3fab2d72bbcd42 --- .../android/net/TestNetworkInterface.java | 74 +++++++++++++++ core/java/android/net/TestNetworkManager.java | 91 +++++++++++++++++++ .../android/server/TestNetworkService.java | 85 +++++++++++++++++ 3 files changed, 250 insertions(+) create mode 100644 core/java/android/net/TestNetworkInterface.java create mode 100644 core/java/android/net/TestNetworkManager.java create mode 100644 services/core/java/com/android/server/TestNetworkService.java diff --git a/core/java/android/net/TestNetworkInterface.java b/core/java/android/net/TestNetworkInterface.java new file mode 100644 index 0000000000..30e68f5b98 --- /dev/null +++ b/core/java/android/net/TestNetworkInterface.java @@ -0,0 +1,74 @@ +/* + * 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; + +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; + +/** + * This class is used to return the interface name and fd of the test interface + * + * @hide + */ +@TestApi +public final class TestNetworkInterface implements Parcelable { + private static final String TAG = "TestNetworkInterface"; + + private final ParcelFileDescriptor mFileDescriptor; + private final String mInterfaceName; + + @Override + public int describeContents() { + return (mFileDescriptor != null) ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeParcelable(mFileDescriptor, PARCELABLE_WRITE_RETURN_VALUE); + out.writeString(mInterfaceName); + } + + public TestNetworkInterface(ParcelFileDescriptor pfd, String intf) { + mFileDescriptor = pfd; + mInterfaceName = intf; + } + + private TestNetworkInterface(Parcel in) { + mFileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader()); + mInterfaceName = in.readString(); + } + + public ParcelFileDescriptor getFileDescriptor() { + return mFileDescriptor; + } + + public String getInterfaceName() { + return mInterfaceName; + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public TestNetworkInterface createFromParcel(Parcel in) { + return new TestNetworkInterface(in); + } + + public TestNetworkInterface[] newArray(int size) { + return new TestNetworkInterface[size]; + } + }; +} diff --git a/core/java/android/net/TestNetworkManager.java b/core/java/android/net/TestNetworkManager.java new file mode 100644 index 0000000000..cd58e6641e --- /dev/null +++ b/core/java/android/net/TestNetworkManager.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2018 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; + +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; + +import com.android.internal.util.Preconditions; + +/** + * Class that allows creation and management of per-app, test-only networks + * + * @hide + */ +@TestApi +public class TestNetworkManager { + @NonNull private static final String TAG = TestNetworkManager.class.getSimpleName(); + + @NonNull private final ITestNetworkManager mService; + @NonNull private final Context mContext; + + /** @hide */ + public TestNetworkManager(@NonNull Context context, @NonNull ITestNetworkManager service) { + mContext = Preconditions.checkNotNull(context, "missing Context"); + mService = Preconditions.checkNotNull(service, "missing ITestNetworkManager"); + } + + /** + * Teardown the capability-limited, testing-only network for a given interface + * + * @param network The test network that should be torn down + * @hide + */ + @TestApi + public void teardownTestNetwork(@NonNull Network network) { + try { + mService.teardownTestNetwork(network.netId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sets up a capability-limited, testing-only network for a given interface + * + * @param iface the name of the interface to be used for the Network LinkProperties. + * @param binder A binder object guarding the lifecycle of this test network. + * @hide + */ + @TestApi + public void setupTestNetwork(@NonNull String iface, @NonNull IBinder binder) { + try { + mService.setupTestNetwork(iface, binder); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Create a tun interface for testing purposes + * + * @param linkAddrs an array of LinkAddresses to assign to the TUN interface + * @return A ParcelFileDescriptor of the underlying TUN interface. Close this to tear down the + * TUN interface. + * @hide + */ + @TestApi + public TestNetworkInterface createTunInterface(@NonNull LinkAddress[] linkAddrs) { + try { + return mService.createTunInterface(linkAddrs); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java new file mode 100644 index 0000000000..013e9cc063 --- /dev/null +++ b/services/core/java/com/android/server/TestNetworkService.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2018 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 com.android.server; + +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.NonNull; +import android.content.Context; +import android.net.INetd; +import android.net.ITestNetworkManager; +import android.net.LinkAddress; +import android.net.TestNetworkInterface; +import android.net.util.NetdService; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.INetworkManagementService; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +/** @hide */ +class TestNetworkService extends ITestNetworkManager.Stub { + @NonNull private static final String TAG = TestNetworkService.class.getSimpleName(); + @NonNull private static final String TEST_NETWORK_TYPE = "TEST_NETWORK"; + + @NonNull private final Context mContext; + @NonNull private final INetworkManagementService mNMS; + @NonNull private final INetd mNetd; + + @NonNull private final HandlerThread mHandlerThread; + @NonNull private final Handler mHandler; + + @VisibleForTesting + protected TestNetworkService( + @NonNull Context context, @NonNull INetworkManagementService netManager) { + Log.d(TAG, "TestNetworkService starting up"); + + mHandlerThread = new HandlerThread("TestNetworkServiceThread"); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + + mContext = checkNotNull(context, "missing Context"); + mNMS = checkNotNull(netManager, "missing INetworkManagementService"); + mNetd = NetdService.getInstance(); + } + + /** + * Create a TUN interface with the given interface name and link addresses + * + *

This method will return the FileDescriptor to the TUN interface. Close it to tear down the + * TUN interface. + */ + @Override + public synchronized TestNetworkInterface createTunInterface(@NonNull LinkAddress[] linkAddrs) { + return null; + } + + /** + * Sets up a Network with extremely limited privileges, guarded by the MANAGE_TEST_NETWORKS + * permission. + * + *

This method provides a Network that is useful only for testing. + */ + @Override + public synchronized void setupTestNetwork(@NonNull String iface, @NonNull IBinder binder) {} + + /** Teardown a test network */ + @Override + public synchronized void teardownTestNetwork(int netId) {} +} From 2ffe4b89985499ab386d7c2f2996bb67309f84e7 Mon Sep 17 00:00:00 2001 From: Benedict Wong Date: Wed, 14 Nov 2018 17:53:19 -0800 Subject: [PATCH 2/2] Implement TestNetworkService This change adds the relevant JNI implementations, as well as the implementations and NetworkAgent tracking code in the TestNetworkService. TestNetworkService (And Manager) is designed for use exclusively in testing code, allowing for the creation of TUN and IPsec backed networks in test code. Specifically, this allows for testing of components such as IP, DHCP, or DNS clients, as well as kernel features such as IPsec. Access to the TestNetworkService will be conditioned upon the MANAGE_TEST_NETWORKS permission that will be granted only to the Shell. CTS will use UiAutomation.adoptShellPermissionIdentity() to gain the ability to use this service. Bug: 72950854 Test: CTS tests passing Change-Id: Ie66ba631a548b5f9c6b5ed0797582f86688debe5 --- .../android/server/TestNetworkService.java | 269 +++++++++++++++++- .../com_android_server_TestNetworkService.cpp | 107 +++++++ 2 files changed, 368 insertions(+), 8 deletions(-) create mode 100644 services/core/jni/com_android_server_TestNetworkService.cpp diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java index 013e9cc063..e64ab78d1e 100644 --- a/services/core/java/com/android/server/TestNetworkService.java +++ b/services/core/java/com/android/server/TestNetworkService.java @@ -20,23 +20,47 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.NonNull; import android.content.Context; +import android.net.ConnectivityManager; import android.net.INetd; import android.net.ITestNetworkManager; +import android.net.IpPrefix; import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.NetworkAgent; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; +import android.net.RouteInfo; +import android.net.StringNetworkSpecifier; import android.net.TestNetworkInterface; import android.net.util.NetdService; +import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.INetworkManagementService; -import android.util.Log; +import android.os.Looper; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import java.io.UncheckedIOException; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InterfaceAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.concurrent.atomic.AtomicInteger; + /** @hide */ class TestNetworkService extends ITestNetworkManager.Stub { @NonNull private static final String TAG = TestNetworkService.class.getSimpleName(); @NonNull private static final String TEST_NETWORK_TYPE = "TEST_NETWORK"; + @NonNull private static final String TEST_TUN_PREFIX = "testtun"; + @NonNull private static final AtomicInteger sTestTunIndex = new AtomicInteger(); @NonNull private final Context mContext; @NonNull private final INetworkManagementService mNMS; @@ -45,18 +69,19 @@ class TestNetworkService extends ITestNetworkManager.Stub { @NonNull private final HandlerThread mHandlerThread; @NonNull private final Handler mHandler; + // Native method stubs + private static native int jniCreateTun(@NonNull String iface); + @VisibleForTesting protected TestNetworkService( @NonNull Context context, @NonNull INetworkManagementService netManager) { - Log.d(TAG, "TestNetworkService starting up"); - mHandlerThread = new HandlerThread("TestNetworkServiceThread"); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); mContext = checkNotNull(context, "missing Context"); mNMS = checkNotNull(netManager, "missing INetworkManagementService"); - mNetd = NetdService.getInstance(); + mNetd = checkNotNull(NetdService.getInstance(), "could not get netd instance"); } /** @@ -66,8 +91,167 @@ class TestNetworkService extends ITestNetworkManager.Stub { * TUN interface. */ @Override - public synchronized TestNetworkInterface createTunInterface(@NonNull LinkAddress[] linkAddrs) { - return null; + public TestNetworkInterface createTunInterface(@NonNull LinkAddress[] linkAddrs) { + enforceTestNetworkPermissions(mContext); + + checkNotNull(linkAddrs, "missing linkAddrs"); + + String iface = TEST_TUN_PREFIX + sTestTunIndex.getAndIncrement(); + return Binder.withCleanCallingIdentity( + () -> { + try { + ParcelFileDescriptor tunIntf = + ParcelFileDescriptor.adoptFd(jniCreateTun(iface)); + for (LinkAddress addr : linkAddrs) { + mNetd.interfaceAddAddress( + iface, + addr.getAddress().getHostAddress(), + addr.getPrefixLength()); + } + + return new TestNetworkInterface(tunIntf, iface); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + }); + } + + // Tracker for TestNetworkAgents + @GuardedBy("mTestNetworkTracker") + @NonNull + private final SparseArray mTestNetworkTracker = new SparseArray<>(); + + public class TestNetworkAgent extends NetworkAgent implements IBinder.DeathRecipient { + private static final int NETWORK_SCORE = 1; // Use a low, non-zero score. + + private final int mUid; + @NonNull private final NetworkInfo mNi; + @NonNull private final NetworkCapabilities mNc; + @NonNull private final LinkProperties mLp; + + @GuardedBy("mBinderLock") + @NonNull + private IBinder mBinder; + + @NonNull private final Object mBinderLock = new Object(); + + private TestNetworkAgent( + @NonNull Looper looper, + @NonNull Context context, + @NonNull NetworkInfo ni, + @NonNull NetworkCapabilities nc, + @NonNull LinkProperties lp, + int uid, + @NonNull IBinder binder) + throws RemoteException { + super(looper, context, TEST_NETWORK_TYPE, ni, nc, lp, NETWORK_SCORE); + + mUid = uid; + mNi = ni; + mNc = nc; + mLp = lp; + + synchronized (mBinderLock) { + mBinder = binder; // Binder null-checks in create() + + try { + mBinder.linkToDeath(this, 0); + } catch (RemoteException e) { + binderDied(); + throw e; // Abort, signal failure up the stack. + } + } + } + + /** + * If the Binder object dies, this function is called to free the resources of this + * TestNetworkAgent + */ + @Override + public void binderDied() { + teardown(); + } + + @Override + protected void unwanted() { + teardown(); + } + + private void teardown() { + mNi.setDetailedState(DetailedState.DISCONNECTED, null, null); + mNi.setIsAvailable(false); + sendNetworkInfo(mNi); + + // Synchronize on mBinderLock to ensure that unlinkToDeath is never called more than + // once (otherwise it could throw an exception) + synchronized (mBinderLock) { + // If mBinder is null, this Test Network has already been cleaned up. + if (mBinder == null) return; + mBinder.unlinkToDeath(this, 0); + mBinder = null; + } + + // Has to be in TestNetworkAgent to ensure all teardown codepaths properly clean up + // resources, even for binder death or unwanted calls. + synchronized (mTestNetworkTracker) { + mTestNetworkTracker.remove(netId); + } + } + } + + private TestNetworkAgent registerTestNetworkAgent( + @NonNull Looper looper, + @NonNull Context context, + @NonNull String iface, + int callingUid, + @NonNull IBinder binder) + throws RemoteException, SocketException { + checkNotNull(looper, "missing Looper"); + checkNotNull(context, "missing Context"); + // iface and binder validity checked by caller + + // Build network info with special testing type + NetworkInfo ni = new NetworkInfo(ConnectivityManager.TYPE_TEST, 0, TEST_NETWORK_TYPE, ""); + ni.setDetailedState(DetailedState.CONNECTED, null, null); + ni.setIsAvailable(true); + + // Build narrow set of NetworkCapabilities, useful only for testing + NetworkCapabilities nc = new NetworkCapabilities(); + nc.clearAll(); // Remove default capabilities. + nc.addTransportType(NetworkCapabilities.TRANSPORT_TEST); + nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED); + nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED); + nc.setNetworkSpecifier(new StringNetworkSpecifier(iface)); + + // Build LinkProperties + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(iface); + + // Find the currently assigned addresses, and add them to LinkProperties + boolean allowIPv4 = false, allowIPv6 = false; + NetworkInterface netIntf = NetworkInterface.getByName(iface); + checkNotNull(netIntf, "No such network interface found: " + netIntf); + + for (InterfaceAddress intfAddr : netIntf.getInterfaceAddresses()) { + lp.addLinkAddress( + new LinkAddress(intfAddr.getAddress(), intfAddr.getNetworkPrefixLength())); + + if (intfAddr.getAddress() instanceof Inet6Address) { + allowIPv6 |= !intfAddr.getAddress().isLinkLocalAddress(); + } else if (intfAddr.getAddress() instanceof Inet4Address) { + allowIPv4 = true; + } + } + + // Add global routes (but as non-default, non-internet providing network) + if (allowIPv4) { + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null, iface)); + } + if (allowIPv6) { + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null, iface)); + } + + return new TestNetworkAgent(looper, context, ni, nc, lp, callingUid, binder); } /** @@ -77,9 +261,78 @@ class TestNetworkService extends ITestNetworkManager.Stub { *

This method provides a Network that is useful only for testing. */ @Override - public synchronized void setupTestNetwork(@NonNull String iface, @NonNull IBinder binder) {} + public void setupTestNetwork(@NonNull String iface, @NonNull IBinder binder) { + enforceTestNetworkPermissions(mContext); + + checkNotNull(iface, "missing Iface"); + checkNotNull(binder, "missing IBinder"); + + if (!(iface.startsWith(INetd.IPSEC_INTERFACE_PREFIX) + || iface.startsWith(TEST_TUN_PREFIX))) { + throw new IllegalArgumentException( + "Cannot create network for non ipsec, non-testtun interface"); + } + + // Setup needs to be done with NETWORK_STACK privileges. + int callingUid = Binder.getCallingUid(); + Binder.withCleanCallingIdentity( + () -> { + try { + mNMS.setInterfaceUp(iface); + + // Synchronize all accesses to mTestNetworkTracker to prevent the case + // where: + // 1. TestNetworkAgent successfully binds to death of binder + // 2. Before it is added to the mTestNetworkTracker, binder dies, + // binderDied() is called (on a different thread) + // 3. This thread is pre-empted, put() is called after remove() + synchronized (mTestNetworkTracker) { + TestNetworkAgent agent = + registerTestNetworkAgent( + mHandler.getLooper(), + mContext, + iface, + callingUid, + binder); + + mTestNetworkTracker.put(agent.netId, agent); + } + } catch (SocketException e) { + throw new UncheckedIOException(e); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + }); + } /** Teardown a test network */ @Override - public synchronized void teardownTestNetwork(int netId) {} + public void teardownTestNetwork(int netId) { + enforceTestNetworkPermissions(mContext); + + TestNetworkAgent agent; + synchronized (mTestNetworkTracker) { + agent = mTestNetworkTracker.get(netId); + } + + if (agent == null) { + return; // Already torn down + } else if (agent.mUid != Binder.getCallingUid()) { + throw new SecurityException("Attempted to modify other user's test networks"); + } + + // Safe to be called multiple times. + agent.teardown(); + } + + // STOPSHIP: Change this back to android.Manifest.permission.MANAGE_TEST_NETWORKS + private static final String PERMISSION_NAME = "dummy"; + + public static void enforceTestNetworkPermissions(@NonNull Context context) { + // STOPSHIP: Re-enable these checks. Disabled until adoptShellPermissionIdentity() can be + // called from CTS test code. + if (false) { + context.enforceCallingOrSelfPermission(PERMISSION_NAME, "TestNetworkService"); + } + } } diff --git a/services/core/jni/com_android_server_TestNetworkService.cpp b/services/core/jni/com_android_server_TestNetworkService.cpp new file mode 100644 index 0000000000..b90ff233c1 --- /dev/null +++ b/services/core/jni/com_android_server_TestNetworkService.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2018 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. + */ + +#define LOG_NDEBUG 0 + +#define LOG_TAG "TestNetworkServiceJni" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "netutils/ifc.h" + +#include "jni.h" +#include +#include +#include +#include + +namespace android { + +//------------------------------------------------------------------------------ + +static void throwException(JNIEnv* env, int error, const char* action, const char* iface) { + const std::string& msg = + android::base::StringPrintf("Error %s %s: %s", action, iface, strerror(error)); + + jniThrowException(env, "java/lang/IllegalStateException", msg.c_str()); +} + +static int createTunInterface(JNIEnv* env, const char* iface) { + base::unique_fd tun(open("/dev/tun", O_RDWR | O_NONBLOCK)); + ifreq ifr{}; + + // Allocate interface. + ifr.ifr_flags = IFF_TUN | IFF_NO_PI; + strlcpy(ifr.ifr_name, iface, IFNAMSIZ); + if (ioctl(tun.get(), TUNSETIFF, &ifr)) { + throwException(env, errno, "allocating", ifr.ifr_name); + return -1; + } + + // Activate interface using an unconnected datagram socket. + base::unique_fd inet6CtrlSock(socket(AF_INET6, SOCK_DGRAM, 0)); + ifr.ifr_flags = IFF_UP; + + if (ioctl(inet6CtrlSock.get(), SIOCSIFFLAGS, &ifr)) { + throwException(env, errno, "activating", ifr.ifr_name); + return -1; + } + + return tun.release(); +} + +//------------------------------------------------------------------------------ + +static jint create(JNIEnv* env, jobject /* thiz */, jstring jIface) { + ScopedUtfChars iface(env, jIface); + if (!iface.c_str()) { + jniThrowNullPointerException(env, "iface"); + return -1; + } + + int tun = createTunInterface(env, iface.c_str()); + + // Any exceptions will be thrown from the createTunInterface call + return tun; +} + +//------------------------------------------------------------------------------ + +static const JNINativeMethod gMethods[] = { + {"jniCreateTun", "(Ljava/lang/String;)I", (void*)create}, +}; + +int register_android_server_TestNetworkService(JNIEnv* env) { + return jniRegisterNativeMethods(env, "com/android/server/TestNetworkService", gMethods, + NELEM(gMethods)); +} + +}; // namespace android