From fa21cafc1c1f375b99b685087eae0920bfee7acf Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Wed, 30 Aug 2023 20:31:43 +0900 Subject: [PATCH] Add LocalNetworkConfig This doesn't do anything at this patchset, but the whole pipe from the agent to ConnectivityService is built. LocalNetworkInfo will be the name of the public information sent in callbacks to clients. Test: CSLocalAgentTests Change-Id: I70e133031ef3b0aaf6c3e59ccc2ad895c66d339c --- .../android/net/LocalNetworkConfig.aidl | 20 ++ .../src/android/net/ConnectivityManager.java | 25 +- .../src/android/net/IConnectivityManager.aidl | 4 +- .../android/net/INetworkAgentRegistry.aidl | 2 + .../src/android/net/LocalNetworkConfig.java | 168 +++++++++++ .../android/net/MulticastRoutingConfig.java | 264 ++++++++++++++++++ framework/src/android/net/NetworkAgent.java | 75 ++++- .../android/server/ConnectivityService.java | 49 +++- .../server/connectivity/NetworkAgentInfo.java | 10 + .../src/android/net/cts/NetworkAgentTest.kt | 1 + .../server/ConnectivityServiceTest.java | 3 +- .../connectivity/LingerMonitorTest.java | 7 +- .../android/server/connectivity/VpnTest.java | 26 +- .../CSKeepConnectedTest.kt | 6 +- .../CSLocalAgentCreationTests.kt | 21 +- .../connectivityservice/CSLocalAgentTests.kt | 111 ++++++++ .../base/CSAgentWrapper.kt | 6 +- .../server/connectivityservice/base/CSTest.kt | 6 +- 18 files changed, 760 insertions(+), 44 deletions(-) create mode 100644 framework/aidl-export/android/net/LocalNetworkConfig.aidl create mode 100644 framework/src/android/net/LocalNetworkConfig.java create mode 100644 framework/src/android/net/MulticastRoutingConfig.java create mode 100644 tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt diff --git a/framework/aidl-export/android/net/LocalNetworkConfig.aidl b/framework/aidl-export/android/net/LocalNetworkConfig.aidl new file mode 100644 index 0000000000..e2829a5a41 --- /dev/null +++ b/framework/aidl-export/android/net/LocalNetworkConfig.aidl @@ -0,0 +1,20 @@ +/** + * + * Copyright (C) 2023 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; + +@JavaOnlyStableParcelable parcelable LocalNetworkConfig; diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 23155210be..915c20da5f 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -3811,11 +3811,28 @@ public class ConnectivityManager { @RequiresPermission(anyOf = { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) - public Network registerNetworkAgent(INetworkAgent na, NetworkInfo ni, LinkProperties lp, - NetworkCapabilities nc, @NonNull NetworkScore score, NetworkAgentConfig config, - int providerId) { + public Network registerNetworkAgent(@NonNull INetworkAgent na, @NonNull NetworkInfo ni, + @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc, + @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, int providerId) { + return registerNetworkAgent(na, ni, lp, nc, null /* localNetworkConfig */, score, config, + providerId); + } + + /** + * @hide + * Register a NetworkAgent with ConnectivityService. + * @return Network corresponding to NetworkAgent. + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public Network registerNetworkAgent(@NonNull INetworkAgent na, @NonNull NetworkInfo ni, + @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc, + @Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score, + @NonNull NetworkAgentConfig config, int providerId) { try { - return mService.registerNetworkAgent(na, ni, lp, nc, score, config, providerId); + return mService.registerNetworkAgent(na, ni, lp, nc, score, localNetworkConfig, config, + providerId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index ebe8bca77e..fe277732b0 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -27,6 +27,7 @@ import android.net.INetworkOfferCallback; import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; import android.net.LinkProperties; +import android.net.LocalNetworkConfig; import android.net.Network; import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; @@ -146,7 +147,8 @@ interface IConnectivityManager void declareNetworkRequestUnfulfillable(in NetworkRequest request); Network registerNetworkAgent(in INetworkAgent na, in NetworkInfo ni, in LinkProperties lp, - in NetworkCapabilities nc, in NetworkScore score, in NetworkAgentConfig config, + in NetworkCapabilities nc, in NetworkScore score, + in LocalNetworkConfig localNetworkConfig, in NetworkAgentConfig config, in int factorySerialNumber); NetworkRequest requestNetwork(int uid, in NetworkCapabilities networkCapabilities, int reqType, diff --git a/framework/src/android/net/INetworkAgentRegistry.aidl b/framework/src/android/net/INetworkAgentRegistry.aidl index b375b7b649..61b27b5f0e 100644 --- a/framework/src/android/net/INetworkAgentRegistry.aidl +++ b/framework/src/android/net/INetworkAgentRegistry.aidl @@ -17,6 +17,7 @@ package android.net; import android.net.DscpPolicy; import android.net.LinkProperties; +import android.net.LocalNetworkConfig; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; @@ -34,6 +35,7 @@ oneway interface INetworkAgentRegistry { void sendLinkProperties(in LinkProperties lp); // TODO: consider replacing this by "markConnected()" and removing void sendNetworkInfo(in NetworkInfo info); + void sendLocalNetworkConfig(in LocalNetworkConfig config); void sendScore(in NetworkScore score); void sendExplicitlySelected(boolean explicitlySelected, boolean acceptPartial); void sendSocketKeepaliveEvent(int slot, int reason); diff --git a/framework/src/android/net/LocalNetworkConfig.java b/framework/src/android/net/LocalNetworkConfig.java new file mode 100644 index 0000000000..fca7fd125a --- /dev/null +++ b/framework/src/android/net/LocalNetworkConfig.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2023 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.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A class to communicate configuration info about a local network through {@link NetworkAgent}. + * @hide + */ +// TODO : @SystemApi +public final class LocalNetworkConfig implements Parcelable { + @Nullable + private final NetworkRequest mUpstreamSelector; + + @NonNull + private final MulticastRoutingConfig mUpstreamMulticastRoutingConfig; + + @NonNull + private final MulticastRoutingConfig mDownstreamMulticastRoutingConfig; + + private LocalNetworkConfig(@Nullable final NetworkRequest upstreamSelector, + @Nullable final MulticastRoutingConfig upstreamConfig, + @Nullable final MulticastRoutingConfig downstreamConfig) { + mUpstreamSelector = upstreamSelector; + if (null != upstreamConfig) { + mUpstreamMulticastRoutingConfig = upstreamConfig; + } else { + mUpstreamMulticastRoutingConfig = MulticastRoutingConfig.CONFIG_FORWARD_NONE; + } + if (null != downstreamConfig) { + mDownstreamMulticastRoutingConfig = downstreamConfig; + } else { + mDownstreamMulticastRoutingConfig = MulticastRoutingConfig.CONFIG_FORWARD_NONE; + } + } + + /** + * Get the request choosing which network traffic from this network is forwarded to and from. + * + * This may be null if the local network doesn't forward the traffic anywhere. + */ + @Nullable + public NetworkRequest getUpstreamSelector() { + return mUpstreamSelector; + } + + public @NonNull MulticastRoutingConfig getUpstreamMulticastRoutingConfig() { + return mUpstreamMulticastRoutingConfig; + } + + public @NonNull MulticastRoutingConfig getDownstreamMulticastRoutingConfig() { + return mDownstreamMulticastRoutingConfig; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull final Parcel dest, final int flags) { + dest.writeParcelable(mUpstreamSelector, flags); + dest.writeParcelable(mUpstreamMulticastRoutingConfig, flags); + dest.writeParcelable(mDownstreamMulticastRoutingConfig, flags); + } + + public static final @NonNull Creator CREATOR = new Creator<>() { + public LocalNetworkConfig createFromParcel(Parcel in) { + final NetworkRequest upstreamSelector = in.readParcelable(null); + final MulticastRoutingConfig upstreamConfig = in.readParcelable(null); + final MulticastRoutingConfig downstreamConfig = in.readParcelable(null); + return new LocalNetworkConfig( + upstreamSelector, upstreamConfig, downstreamConfig); + } + + @Override + public LocalNetworkConfig[] newArray(final int size) { + return new LocalNetworkConfig[size]; + } + }; + + + public static final class Builder { + @Nullable + NetworkRequest mUpstreamSelector; + + @Nullable + MulticastRoutingConfig mUpstreamMulticastRoutingConfig; + + @Nullable + MulticastRoutingConfig mDownstreamMulticastRoutingConfig; + + /** + * Create a Builder + */ + public Builder() { + } + + /** + * Set to choose where this local network should forward its traffic to. + * + * The system will automatically choose the best network matching the request as an + * upstream, and set up forwarding between this local network and the chosen upstream. + * If no network matches the request, there is no upstream and the traffic is not forwarded. + * The caller can know when this changes by listening to link properties changes of + * this network with the {@link android.net.LinkProperties#getForwardedNetwork()} getter. + * + * Set this to null if the local network shouldn't be forwarded. Default is null. + */ + @NonNull + public Builder setUpstreamSelector(@Nullable NetworkRequest upstreamSelector) { + mUpstreamSelector = upstreamSelector; + return this; + } + + /** + * Set the upstream multicast routing config. + * + * If null, don't route multicast packets upstream. This is equivalent to a + * MulticastRoutingConfig in mode FORWARD_NONE. The default is null. + */ + @NonNull + public Builder setUpstreamMulticastRoutingConfig(@Nullable MulticastRoutingConfig cfg) { + mUpstreamMulticastRoutingConfig = cfg; + return this; + } + + /** + * Set the downstream multicast routing config. + * + * If null, don't route multicast packets downstream. This is equivalent to a + * MulticastRoutingConfig in mode FORWARD_NONE. The default is null. + */ + @NonNull + public Builder setDownstreamMulticastRoutingConfig(@Nullable MulticastRoutingConfig cfg) { + mDownstreamMulticastRoutingConfig = cfg; + return this; + } + + /** + * Build the LocalNetworkConfig object. + */ + @NonNull + public LocalNetworkConfig build() { + return new LocalNetworkConfig(mUpstreamSelector, + mUpstreamMulticastRoutingConfig, + mDownstreamMulticastRoutingConfig); + } + } +} diff --git a/framework/src/android/net/MulticastRoutingConfig.java b/framework/src/android/net/MulticastRoutingConfig.java new file mode 100644 index 0000000000..ebd9fc58c7 --- /dev/null +++ b/framework/src/android/net/MulticastRoutingConfig.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2023 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.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.ArraySet; +import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.Inet6Address; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; + +/** + * A class representing a configuration for multicast routing. + * + * Internal usage to Connectivity + * @hide + */ +// TODO : @SystemApi +public class MulticastRoutingConfig implements Parcelable { + private static final String TAG = MulticastRoutingConfig.class.getSimpleName(); + + /** Do not forward any multicast packets. */ + public static final int FORWARD_NONE = 0; + /** + * Forward only multicast packets with destination in the list of listening addresses. + * Ignore the min scope. + */ + public static final int FORWARD_SELECTED = 1; + /** + * Forward all multicast packets with scope greater or equal than the min scope. + * Ignore the list of listening addresses. + */ + public static final int FORWARD_WITH_MIN_SCOPE = 2; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "FORWARD_" }, value = { + FORWARD_NONE, + FORWARD_SELECTED, + FORWARD_WITH_MIN_SCOPE + }) + public @interface MulticastForwardingMode {} + + /** + * Not a multicast scope, for configurations that do not use the min scope. + */ + public static final int MULTICAST_SCOPE_NONE = -1; + + public static final MulticastRoutingConfig CONFIG_FORWARD_NONE = + new MulticastRoutingConfig(FORWARD_NONE, MULTICAST_SCOPE_NONE, null); + + @MulticastForwardingMode + private final int mForwardingMode; + + private final int mMinScope; + + @NonNull + private final Set mListeningAddresses; + + private MulticastRoutingConfig(@MulticastForwardingMode final int mode, final int scope, + @Nullable final Set addresses) { + mForwardingMode = mode; + mMinScope = scope; + if (null != addresses) { + mListeningAddresses = Collections.unmodifiableSet(new ArraySet<>(addresses)); + } else { + mListeningAddresses = Collections.emptySet(); + } + } + + /** + * Returns the forwarding mode. + */ + @MulticastForwardingMode + public int getForwardingMode() { + return mForwardingMode; + } + + /** + * Returns the minimal group address scope that is allowed for forwarding. + * If the forwarding mode is not FORWARD_WITH_MIN_SCOPE, will be MULTICAST_SCOPE_NONE. + */ + public int getMinScope() { + return mMinScope; + } + + /** + * Returns the list of group addresses listened by the outgoing interface. + * The list will be empty if the forwarding mode is not FORWARD_SELECTED. + */ + @NonNull + public Set getMulticastListeningAddresses() { + return mListeningAddresses; + } + + private MulticastRoutingConfig(Parcel in) { + mForwardingMode = in.readInt(); + mMinScope = in.readInt(); + final int count = in.readInt(); + final ArraySet listeningAddresses = new ArraySet<>(count); + final byte[] buffer = new byte[16]; // Size of an Inet6Address + for (int i = 0; i < count; ++i) { + in.readByteArray(buffer); + try { + listeningAddresses.add((Inet6Address) Inet6Address.getByAddress(buffer)); + } catch (UnknownHostException e) { + Log.wtf(TAG, "Can't read inet6address : " + Arrays.toString(buffer)); + } + } + mListeningAddresses = Collections.unmodifiableSet(listeningAddresses); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mForwardingMode); + dest.writeInt(mMinScope); + dest.writeInt(mListeningAddresses.size()); + for (final Inet6Address addr : mListeningAddresses) { + dest.writeByteArray(addr.getAddress()); + } + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = new Creator<>() { + @Override + public MulticastRoutingConfig createFromParcel(Parcel in) { + return new MulticastRoutingConfig(in); + } + + @Override + public MulticastRoutingConfig[] newArray(int size) { + return new MulticastRoutingConfig[size]; + } + }; + + public static class Builder { + @MulticastForwardingMode + private final int mForwardingMode; + private int mMinScope; + private final ArraySet mListeningAddresses; + + private Builder(@MulticastForwardingMode final int mode, int scope) { + mForwardingMode = mode; + mMinScope = scope; + mListeningAddresses = new ArraySet<>(); + } + + /** + * Create a builder that forwards nothing. + * No properties can be set on such a builder. + */ + public static Builder newBuilderForwardingNone() { + return new Builder(FORWARD_NONE, MULTICAST_SCOPE_NONE); + } + + /** + * Create a builder that forwards packets above a certain scope + * + * The scope can be changed on this builder, but not the listening addresses. + * @param scope the initial scope + */ + public static Builder newBuilderWithMinScope(final int scope) { + return new Builder(FORWARD_WITH_MIN_SCOPE, scope); + } + + /** + * Create a builder that forwards a specified list of listening addresses. + * + * Addresses can be added and removed from this builder, but the scope can't be set. + */ + public static Builder newBuilderWithListeningAddresses() { + return new Builder(FORWARD_SELECTED, MULTICAST_SCOPE_NONE); + } + + /** + * Sets the minimum scope for this multicast routing config. + * This is only meaningful (indeed, allowed) for configs in FORWARD_WITH_MIN_SCOPE mode. + * @return this builder + */ + public Builder setMinimumScope(final int scope) { + if (FORWARD_WITH_MIN_SCOPE != mForwardingMode) { + throw new IllegalArgumentException("Can't set the scope on a builder in mode " + + modeToString(mForwardingMode)); + } + mMinScope = scope; + return this; + } + + /** + * Add an address to the set of listening addresses. + * + * This is only meaningful (indeed, allowed) for configs in FORWARD_SELECTED mode. + * If this address was already added, this is a no-op. + * @return this builder + */ + public Builder addListeningAddress(@NonNull final Inet6Address address) { + if (FORWARD_SELECTED != mForwardingMode) { + throw new IllegalArgumentException("Can't add an address on a builder in mode " + + modeToString(mForwardingMode)); + } + // TODO : should we check that this is a multicast address ? + mListeningAddresses.add(address); + return this; + } + + /** + * Remove an address from the set of listening addresses. + * + * This is only meaningful (indeed, allowed) for configs in FORWARD_SELECTED mode. + * If this address was not added, or was already removed, this is a no-op. + * @return this builder + */ + public Builder removeListeningAddress(@NonNull final Inet6Address address) { + if (FORWARD_SELECTED != mForwardingMode) { + throw new IllegalArgumentException("Can't remove an address on a builder in mode " + + modeToString(mForwardingMode)); + } + mListeningAddresses.remove(address); + return this; + } + + /** + * Build the config. + */ + public MulticastRoutingConfig build() { + return new MulticastRoutingConfig(mForwardingMode, mMinScope, mListeningAddresses); + } + } + + private static String modeToString(@MulticastForwardingMode final int mode) { + switch (mode) { + case FORWARD_NONE: return "FORWARD_NONE"; + case FORWARD_SELECTED: return "FORWARD_SELECTED"; + case FORWARD_WITH_MIN_SCOPE: return "FORWARD_WITH_MIN_SCOPE"; + default: return "unknown multicast routing mode " + mode; + } + } +} diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java index 177f7e3d17..4e9087cbd3 100644 --- a/framework/src/android/net/NetworkAgent.java +++ b/framework/src/android/net/NetworkAgent.java @@ -151,7 +151,7 @@ public abstract class NetworkAgent { /** * Sent by the NetworkAgent to ConnectivityService to pass the current - * NetworkCapabilties. + * NetworkCapabilities. * obj = NetworkCapabilities * @hide */ @@ -442,6 +442,14 @@ public abstract class NetworkAgent { */ public static final int EVENT_UNREGISTER_AFTER_REPLACEMENT = BASE + 29; + /** + * Sent by the NetworkAgent to ConnectivityService to pass the new value of the local + * network agent config. + * obj = {@code Pair} + * @hide + */ + public static final int EVENT_LOCAL_NETWORK_CONFIG_CHANGED = BASE + 30; + /** * DSCP policy was successfully added. */ @@ -517,20 +525,47 @@ public abstract class NetworkAgent { @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) { - this(looper, context, logTag, nc, lp, score, config, + this(context, looper, logTag, nc, lp, null /* localNetworkConfig */, score, config, + provider); + } + + /** + * Create a new network agent. + * @param context a {@link Context} to get system services from. + * @param looper the {@link Looper} on which to invoke the callbacks. + * @param logTag the tag for logs + * @param nc the initial {@link NetworkCapabilities} of this network. Update with + * sendNetworkCapabilities. + * @param lp the initial {@link LinkProperties} of this network. Update with sendLinkProperties. + * @param localNetworkConfig the initial {@link LocalNetworkConfig} of this + * network. Update with sendLocalNetworkConfig. Must be + * non-null iff the nc have NET_CAPABILITY_LOCAL_NETWORK. + * @param score the initial score of this network. Update with sendNetworkScore. + * @param config an immutable {@link NetworkAgentConfig} for this agent. + * @param provider the {@link NetworkProvider} managing this agent. + * @hide + */ + // TODO : expose + public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag, + @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, + @Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score, + @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) { + this(looper, context, logTag, nc, lp, localNetworkConfig, score, config, provider == null ? NetworkProvider.ID_NONE : provider.getProviderId(), getLegacyNetworkInfo(config)); } private static class InitialConfiguration { - public final Context context; - public final NetworkCapabilities capabilities; - public final LinkProperties properties; - public final NetworkScore score; - public final NetworkAgentConfig config; - public final NetworkInfo info; + @NonNull public final Context context; + @NonNull public final NetworkCapabilities capabilities; + @NonNull public final LinkProperties properties; + @NonNull public final NetworkScore score; + @NonNull public final NetworkAgentConfig config; + @NonNull public final NetworkInfo info; + @Nullable public final LocalNetworkConfig localNetworkConfig; InitialConfiguration(@NonNull Context context, @NonNull NetworkCapabilities capabilities, - @NonNull LinkProperties properties, @NonNull NetworkScore score, + @NonNull LinkProperties properties, + @Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, @NonNull NetworkInfo info) { this.context = context; this.capabilities = capabilities; @@ -538,14 +573,15 @@ public abstract class NetworkAgent { this.score = score; this.config = config; this.info = info; + this.localNetworkConfig = localNetworkConfig; } } private volatile InitialConfiguration mInitialConfiguration; private NetworkAgent(@NonNull Looper looper, @NonNull Context context, @NonNull String logTag, @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, - @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, int providerId, - @NonNull NetworkInfo ni) { + @Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score, + @NonNull NetworkAgentConfig config, int providerId, @NonNull NetworkInfo ni) { mHandler = new NetworkAgentHandler(looper); LOG_TAG = logTag; mNetworkInfo = new NetworkInfo(ni); @@ -556,7 +592,7 @@ public abstract class NetworkAgent { mInitialConfiguration = new InitialConfiguration(context, new NetworkCapabilities(nc, NetworkCapabilities.REDACT_NONE), - new LinkProperties(lp), score, config, ni); + new LinkProperties(lp), localNetworkConfig, score, config, ni); } private class NetworkAgentHandler extends Handler { @@ -723,7 +759,8 @@ public abstract class NetworkAgent { mNetwork = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler), new NetworkInfo(mInitialConfiguration.info), mInitialConfiguration.properties, mInitialConfiguration.capabilities, - mInitialConfiguration.score, mInitialConfiguration.config, providerId); + mInitialConfiguration.localNetworkConfig, mInitialConfiguration.score, + mInitialConfiguration.config, providerId); mInitialConfiguration = null; // All this memory can now be GC'd } return mNetwork; @@ -1098,6 +1135,18 @@ public abstract class NetworkAgent { queueOrSendMessage(reg -> reg.sendNetworkCapabilities(nc)); } + /** + * Must be called by the agent when the network's {@link LocalNetworkConfig} changes. + * @param config the new LocalNetworkConfig + * @hide + */ + public void sendLocalNetworkConfig(@NonNull LocalNetworkConfig config) { + Objects.requireNonNull(config); + // If the agent doesn't have NET_CAPABILITY_LOCAL_NETWORK, this will be ignored by + // ConnectivityService with a Log.wtf. + queueOrSendMessage(reg -> reg.sendLocalNetworkConfig(config)); + } + /** * Must be called by the agent to update the score of this network. * diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index ba0cad3425..a50404adb2 100755 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -161,6 +161,7 @@ import android.net.InetAddresses; import android.net.IpMemoryStore; import android.net.IpPrefix; import android.net.LinkProperties; +import android.net.LocalNetworkConfig; import android.net.MatchAllNetworkSpecifier; import android.net.NativeNetworkConfig; import android.net.NativeNetworkType; @@ -1775,7 +1776,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mNoServiceNetwork = new NetworkAgentInfo(null, new Network(INetd.UNREACHABLE_NET_ID), new NetworkInfo(TYPE_NONE, 0, "", ""), - new LinkProperties(), new NetworkCapabilities(), + new LinkProperties(), new NetworkCapabilities(), null /* localNetworkConfig */, new NetworkScore.Builder().setLegacyInt(0).build(), mContext, null, new NetworkAgentConfig(), this, null, null, 0, INVALID_UID, mLingerDelayMs, mQosCallbackTracker, mDeps); @@ -4123,6 +4124,11 @@ public class ConnectivityService extends IConnectivityManager.Stub updateNetworkInfo(nai, info); break; } + case NetworkAgent.EVENT_LOCAL_NETWORK_CONFIG_CHANGED: { + final LocalNetworkConfig config = (LocalNetworkConfig) arg.second; + updateLocalNetworkConfig(nai, config); + break; + } case NetworkAgent.EVENT_NETWORK_SCORE_CHANGED: { updateNetworkScore(nai, (NetworkScore) arg.second); break; @@ -8083,13 +8089,18 @@ public class ConnectivityService extends IConnectivityManager.Stub * @param networkCapabilities the initial capabilites of this network. They can be updated * later : see {@link #updateCapabilities}. * @param initialScore the initial score of the network. See {@link NetworkAgentInfo#getScore}. + * @param localNetworkConfig config about this local network, or null if not a local network * @param networkAgentConfig metadata about the network. This is never updated. * @param providerId the ID of the provider owning this NetworkAgent. * @return the network created for this agent. */ - public Network registerNetworkAgent(INetworkAgent na, NetworkInfo networkInfo, - LinkProperties linkProperties, NetworkCapabilities networkCapabilities, - @NonNull NetworkScore initialScore, NetworkAgentConfig networkAgentConfig, + public Network registerNetworkAgent(INetworkAgent na, + NetworkInfo networkInfo, + LinkProperties linkProperties, + NetworkCapabilities networkCapabilities, + @NonNull NetworkScore initialScore, + @Nullable LocalNetworkConfig localNetworkConfig, + NetworkAgentConfig networkAgentConfig, int providerId) { Objects.requireNonNull(networkInfo, "networkInfo must not be null"); Objects.requireNonNull(linkProperties, "linkProperties must not be null"); @@ -8107,12 +8118,20 @@ public class ConnectivityService extends IConnectivityManager.Stub // Before U, netd doesn't support PHYSICAL_LOCAL networks so this can't work. throw new IllegalArgumentException("Local agents are not supported in this version"); } + final boolean hasLocalNetworkConfig = null != localNetworkConfig; + if (hasLocalCap != hasLocalNetworkConfig) { + throw new IllegalArgumentException(null != localNetworkConfig + ? "Only local network agents can have a LocalNetworkConfig" + : "Local network agents must have a LocalNetworkConfig" + ); + } final int uid = mDeps.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { return registerNetworkAgentInternal(na, networkInfo, linkProperties, - networkCapabilities, initialScore, networkAgentConfig, providerId, uid); + networkCapabilities, initialScore, networkAgentConfig, localNetworkConfig, + providerId, uid); } finally { Binder.restoreCallingIdentity(token); } @@ -8120,7 +8139,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private Network registerNetworkAgentInternal(INetworkAgent na, NetworkInfo networkInfo, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, - NetworkScore currentScore, NetworkAgentConfig networkAgentConfig, int providerId, + NetworkScore currentScore, NetworkAgentConfig networkAgentConfig, + @Nullable LocalNetworkConfig localNetworkConfig, int providerId, int uid) { // Make a copy of the passed NI, LP, NC as the caller may hold a reference to them @@ -8128,6 +8148,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkInfo niCopy = new NetworkInfo(networkInfo); final NetworkCapabilities ncCopy = new NetworkCapabilities(networkCapabilities); final LinkProperties lpCopy = new LinkProperties(linkProperties); + // No need to copy |localNetworkConfiguration| as it is immutable. // At this point the capabilities/properties are untrusted and unverified, e.g. checks that // the capabilities' access UIDs comply with security limitations. They will be sanitized @@ -8135,9 +8156,9 @@ public class ConnectivityService extends IConnectivityManager.Stub // because some of the checks must happen on the handler thread. final NetworkAgentInfo nai = new NetworkAgentInfo(na, new Network(mNetIdManager.reserveNetId()), niCopy, lpCopy, ncCopy, - currentScore, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig), - this, mNetd, mDnsResolver, providerId, uid, mLingerDelayMs, - mQosCallbackTracker, mDeps); + localNetworkConfig, currentScore, mContext, mTrackerHandler, + new NetworkAgentConfig(networkAgentConfig), this, mNetd, mDnsResolver, providerId, + uid, mLingerDelayMs, mQosCallbackTracker, mDeps); final String extraInfo = niCopy.getExtraInfo(); final String name = TextUtils.isEmpty(extraInfo) @@ -8872,6 +8893,16 @@ public class ConnectivityService extends IConnectivityManager.Stub updateCapabilities(nai.getScore(), nai, nai.networkCapabilities); } + private void updateLocalNetworkConfig(@NonNull final NetworkAgentInfo nai, + @NonNull final LocalNetworkConfig config) { + if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_LOCAL_NETWORK)) { + Log.wtf(TAG, "Ignoring update of a local network info on non-local network " + nai); + return; + } + // TODO : actually apply the diff. + nai.localNetworkConfig = config; + } + /** * Returns the interface which requires VPN isolation (ingress interface filtering). * diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java index 8d0d7116c9..b0ad978170 100644 --- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java +++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java @@ -35,6 +35,7 @@ import android.net.INetworkAgent; import android.net.INetworkAgentRegistry; import android.net.INetworkMonitor; import android.net.LinkProperties; +import android.net.LocalNetworkConfig; import android.net.NattKeepalivePacketData; import android.net.Network; import android.net.NetworkAgent; @@ -173,6 +174,7 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable { // TODO: make this private with a getter. @NonNull public NetworkCapabilities networkCapabilities; @NonNull public final NetworkAgentConfig networkAgentConfig; + @Nullable public LocalNetworkConfig localNetworkConfig; // Underlying networks declared by the agent. // The networks in this list might be declared by a VPN using setUnderlyingNetworks and are @@ -609,6 +611,7 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable { public NetworkAgentInfo(INetworkAgent na, Network net, NetworkInfo info, @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc, + @Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score, Context context, Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd, IDnsResolver dnsResolver, int factorySerialNumber, int creatorUid, @@ -626,6 +629,7 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable { networkInfo = info; linkProperties = lp; networkCapabilities = nc; + this.localNetworkConfig = localNetworkConfig; networkAgentConfig = config; mConnService = connService; mConnServiceDeps = deps; @@ -904,6 +908,12 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable { new Pair<>(NetworkAgentInfo.this, info)).sendToTarget(); } + @Override + public void sendLocalNetworkConfig(@NonNull final LocalNetworkConfig config) { + mHandler.obtainMessage(NetworkAgent.EVENT_LOCAL_NETWORK_CONFIG_CHANGED, + new Pair<>(NetworkAgentInfo.this, config)).sendToTarget(); + } + @Override public void sendScore(@NonNull final NetworkScore score) { mHandler.obtainMessage(NetworkAgent.EVENT_NETWORK_SCORE_CHANGED, diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt index 5937655925..392cba9bb8 100644 --- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt +++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt @@ -704,6 +704,7 @@ class NetworkAgentTest { argThat { it.detailedState == NetworkInfo.DetailedState.CONNECTING }, any(LinkProperties::class.java), any(NetworkCapabilities::class.java), + any(), // LocalNetworkConfig TODO : specify when it's public any(NetworkScore::class.java), any(NetworkAgentConfig::class.java), eq(NetworkProvider.ID_NONE)) diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index 16f0c44b50..04fc9ef43a 100755 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -12747,7 +12747,8 @@ public class ConnectivityServiceTest { private NetworkAgentInfo fakeNai(NetworkCapabilities nc, NetworkInfo networkInfo) { return new NetworkAgentInfo(null, new Network(NET_ID), networkInfo, new LinkProperties(), - nc, new NetworkScore.Builder().setLegacyInt(0).build(), + nc, null /* localNetworkConfig */, + new NetworkScore.Builder().setLegacyInt(0).build(), mServiceContext, null, new NetworkAgentConfig(), mService, null, null, 0, INVALID_UID, TEST_LINGER_DELAY_MS, mQosCallbackTracker, new ConnectivityService.Dependencies()); diff --git a/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java index e6c0c8348d..07883ff8f8 100644 --- a/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java +++ b/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java @@ -372,9 +372,10 @@ public class LingerMonitorTest { caps.addCapability(0); caps.addTransportType(transport); NetworkAgentInfo nai = new NetworkAgentInfo(null, new Network(netId), info, - new LinkProperties(), caps, new NetworkScore.Builder().setLegacyInt(50).build(), - mCtx, null, new NetworkAgentConfig.Builder().build(), mConnService, mNetd, - mDnsResolver, NetworkProvider.ID_NONE, Binder.getCallingUid(), TEST_LINGER_DELAY_MS, + new LinkProperties(), caps, null /* localNetworkConfiguration */, + new NetworkScore.Builder().setLegacyInt(50).build(), mCtx, null, + new NetworkAgentConfig.Builder().build(), mConnService, mNetd, mDnsResolver, + NetworkProvider.ID_NONE, Binder.getCallingUid(), TEST_LINGER_DELAY_MS, mQosCallbackTracker, new ConnectivityService.Dependencies()); if (setEverValidated) { // As tests in this class deal with testing lingering, most tests are interested diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java index d67476787a..478ff61c0a 100644 --- a/tests/unit/java/com/android/server/connectivity/VpnTest.java +++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java @@ -3004,8 +3004,15 @@ public class VpnTest extends VpnTestBase { profile.mppe = useMppe; doReturn(new Network[] { new Network(101) }).when(mConnectivityManager).getAllNetworks(); - doReturn(new Network(102)).when(mConnectivityManager).registerNetworkAgent(any(), any(), - any(), any(), any(), any(), anyInt()); + doReturn(new Network(102)).when(mConnectivityManager).registerNetworkAgent( + any(), // INetworkAgent + any(), // NetworkInfo + any(), // LinkProperties + any(), // NetworkCapabilities + any(), // LocalNetworkConfig + any(), // NetworkScore + any(), // NetworkAgentConfig + anyInt()); // provider ID final Vpn vpn = startLegacyVpn(createVpn(PRIMARY_USER.id), profile); final TestDeps deps = (TestDeps) vpn.mDeps; @@ -3027,8 +3034,15 @@ public class VpnTest extends VpnTestBase { assertEquals("nomppe", mtpdArgs[argsPrefix.length]); } - verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(any(), any(), - any(), any(), any(), any(), anyInt()); + verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent( + any(), // INetworkAgent + any(), // NetworkInfo + any(), // LinkProperties + any(), // NetworkCapabilities + any(), // LocalNetworkConfig + any(), // NetworkScore + any(), // NetworkAgentConfig + anyInt()); // provider ID }, () -> { // Cleanup vpn.mVpnRunner.exitVpnRunner(); deps.getStateFile().delete(); // set to delete on exit, but this deletes it earlier @@ -3053,7 +3067,7 @@ public class VpnTest extends VpnTestBase { .thenReturn(new Network[] { new Network(101) }); when(mConnectivityManager.registerNetworkAgent(any(), any(), any(), any(), - any(), any(), anyInt())).thenAnswer(invocation -> { + any(), any(), any(), anyInt())).thenAnswer(invocation -> { // The runner has registered an agent and is now ready. legacyRunnerReady.open(); return new Network(102); @@ -3079,7 +3093,7 @@ public class VpnTest extends VpnTestBase { ArgumentCaptor ncCaptor = ArgumentCaptor.forClass(NetworkCapabilities.class); verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(any(), any(), - lpCaptor.capture(), ncCaptor.capture(), any(), any(), anyInt()); + lpCaptor.capture(), ncCaptor.capture(), any(), any(), any(), anyInt()); // In this test the expected address is always v4 so /32. // Note that the interface needs to be specified because RouteInfo objects stored in diff --git a/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt index 86426c2066..6220e76409 100644 --- a/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt +++ b/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt @@ -16,6 +16,7 @@ package com.android.server +import android.net.LocalNetworkConfig import android.net.NetworkCapabilities import android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK import android.net.NetworkCapabilities.TRANSPORT_WIFI @@ -45,8 +46,9 @@ class CSKeepConnectedTest : CSTest() { .build() val keepConnectedAgent = Agent(nc = nc, score = FromS(NetworkScore.Builder() .setKeepConnectedReason(KEEP_CONNECTED_DOWNSTREAM_NETWORK) - .build())) - val dontKeepConnectedAgent = Agent(nc = nc) + .build()), + lnc = LocalNetworkConfig.Builder().build()) + val dontKeepConnectedAgent = Agent(nc = nc, lnc = LocalNetworkConfig.Builder().build()) doTestKeepConnected(keepConnectedAgent, dontKeepConnectedAgent) } diff --git a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentCreationTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentCreationTests.kt index 7914e04958..cfc3a3d8e0 100644 --- a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentCreationTests.kt +++ b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentCreationTests.kt @@ -18,6 +18,7 @@ package com.android.server import android.content.pm.PackageManager.FEATURE_LEANBACK import android.net.INetd +import android.net.LocalNetworkConfig import android.net.NativeNetworkConfig import android.net.NativeNetworkType import android.net.NetworkCapabilities @@ -48,6 +49,8 @@ private const val NO_CALLBACK_TIMEOUT_MS = 200L private fun keepConnectedScore() = FromS(NetworkScore.Builder().setKeepConnectedReason(KEEP_CONNECTED_FOR_TEST).build()) +private fun defaultLnc() = LocalNetworkConfig.Builder().build() + @RunWith(DevSdkIgnoreRunner::class) @SmallTest @IgnoreUpTo(Build.VERSION_CODES.R) @@ -93,9 +96,9 @@ class CSLocalAgentCreationTests( addCapability(NET_CAPABILITY_LOCAL_NETWORK) }.build() val localAgent = if (sdkLevel >= VERSION_V || sdkLevel == VERSION_U && isTv) { - Agent(nc = ncTemplate, score = keepConnectedScore()) + Agent(nc = ncTemplate, score = keepConnectedScore(), lnc = defaultLnc()) } else { - assertFailsWith { Agent(nc = ncTemplate) } + assertFailsWith { Agent(nc = ncTemplate, lnc = defaultLnc()) } netdInOrder.verify(netd, never()).networkCreate(any()) return } @@ -111,4 +114,18 @@ class CSLocalAgentCreationTests( localAgent.disconnect() netdInOrder.verify(netd, timeout(TIMEOUT_MS)).networkDestroy(localAgent.network.netId) } + + @Test + fun testBadAgents() { + assertFailsWith { + Agent(nc = NetworkCapabilities.Builder() + .addCapability(NET_CAPABILITY_LOCAL_NETWORK) + .build(), + lnc = null) + } + assertFailsWith { + Agent(nc = NetworkCapabilities.Builder().build(), + lnc = LocalNetworkConfig.Builder().build()) + } + } } diff --git a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt new file mode 100644 index 0000000000..bd3efa91ba --- /dev/null +++ b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2023 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 android.net.IpPrefix +import android.net.LinkAddress +import android.net.LinkProperties +import android.net.LocalNetworkConfig +import android.net.NetworkCapabilities +import android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK +import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED +import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING +import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED +import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED +import android.net.NetworkCapabilities.TRANSPORT_WIFI +import android.net.NetworkRequest +import android.net.RouteInfo +import android.os.Build +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRunner +import com.android.testutils.RecorderCallback +import com.android.testutils.RecorderCallback.CallbackEntry.Available +import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus +import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged +import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged +import com.android.testutils.TestableNetworkCallback +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertFailsWith + +private fun nc(transport: Int, vararg caps: Int) = NetworkCapabilities.Builder().apply { + addTransportType(transport) + caps.forEach { + addCapability(it) + } + // Useful capabilities for everybody + addCapability(NET_CAPABILITY_NOT_RESTRICTED) + addCapability(NET_CAPABILITY_NOT_SUSPENDED) + addCapability(NET_CAPABILITY_NOT_ROAMING) + addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) +}.build() + +private fun lp(iface: String) = LinkProperties().apply { + interfaceName = iface + addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 32)) + addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null)) +} + +@RunWith(DevSdkIgnoreRunner::class) +@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) +class CSLocalAgentTests : CSTest() { + @Test + fun testBadAgents() { + assertFailsWith { + Agent(nc = NetworkCapabilities.Builder() + .addCapability(NET_CAPABILITY_LOCAL_NETWORK) + .build(), + lnc = null) + } + assertFailsWith { + Agent(nc = NetworkCapabilities.Builder().build(), + lnc = LocalNetworkConfig.Builder().build()) + } + } + + @Test + fun testUpdateLocalAgentConfig() { + deps.setBuildSdk(VERSION_V) + + val cb = TestableNetworkCallback() + cm.requestNetwork(NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_LOCAL_NETWORK) + .build(), + cb) + + // Set up a local agent that should forward its traffic to the best DUN upstream. + val localAgent = Agent(nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_LOCAL_NETWORK), + lp = lp("local0"), + lnc = LocalNetworkConfig.Builder().build(), + ) + localAgent.connect() + + cb.expect(localAgent.network) + cb.expect(localAgent.network) + cb.expect(localAgent.network) + cb.expect(localAgent.network) + + val newLnc = LocalNetworkConfig.Builder() + .setUpstreamSelector(NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI) + .build()) + .build() + localAgent.sendLocalNetworkConfig(newLnc) + + localAgent.disconnect() + } +} diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt index 1d0d5df3fd..094ded3ae0 100644 --- a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt +++ b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt @@ -21,6 +21,7 @@ import android.net.ConnectivityManager import android.net.INetworkMonitor import android.net.INetworkMonitorCallbacks import android.net.LinkProperties +import android.net.LocalNetworkConfig import android.net.Network import android.net.NetworkAgent import android.net.NetworkAgentConfig @@ -69,6 +70,7 @@ class CSAgentWrapper( nac: NetworkAgentConfig, val nc: NetworkCapabilities, val lp: LinkProperties, + val lnc: LocalNetworkConfig?, val score: FromS, val provider: NetworkProvider? ) : TestableNetworkCallback.HasNetwork { @@ -100,7 +102,7 @@ class CSAgentWrapper( // Create the actual agent. NetworkAgent is abstract, so make an anonymous subclass. if (deps.isAtLeastS()) { agent = object : NetworkAgent(context, csHandlerThread.looper, TAG, - nc, lp, score.value, nac, provider) {} + nc, lp, lnc, score.value, nac, provider) {} } else { agent = object : NetworkAgent(context, csHandlerThread.looper, TAG, nc, lp, 50 /* score */, nac, provider) {} @@ -165,4 +167,6 @@ class CSAgentWrapper( agent.unregister() cb.eventuallyExpect { it.network == agent.network } } + + fun sendLocalNetworkConfig(lnc: LocalNetworkConfig) = agent.sendLocalNetworkConfig(lnc) } diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt index 2f78212768..0ccbfc3e80 100644 --- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt +++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt @@ -27,6 +27,7 @@ import android.net.ConnectivityManager import android.net.INetd import android.net.InetAddresses import android.net.LinkProperties +import android.net.LocalNetworkConfig import android.net.NetworkAgentConfig import android.net.NetworkCapabilities import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED @@ -292,10 +293,11 @@ open class CSTest { nc: NetworkCapabilities = defaultNc(), nac: NetworkAgentConfig = emptyAgentConfig(nc.getLegacyType()), lp: LinkProperties = defaultLp(), + lnc: LocalNetworkConfig? = null, score: FromS = defaultScore(), provider: NetworkProvider? = null - ) = CSAgentWrapper(context, deps, csHandlerThread, networkStack, nac, nc, lp, score, provider) - + ) = CSAgentWrapper(context, deps, csHandlerThread, networkStack, + nac, nc, lp, lnc, score, provider) fun Agent(vararg transports: Int, lp: LinkProperties = defaultLp()): CSAgentWrapper { val nc = NetworkCapabilities.Builder().apply { transports.forEach {