Merge changes I3b6ee1db,I70e13303,I6fb7dfe4 into main

* changes:
  Simplify addRoutesToLocalNetwork calls
  Add LocalNetworkConfig
  Tell netd about local networks
This commit is contained in:
Jean Chalard
2023-10-13 09:57:18 +00:00
committed by Gerrit Code Review
20 changed files with 919 additions and 61 deletions

View File

@@ -16,8 +16,6 @@
package android.net.ip;
import static android.net.RouteInfo.RTN_UNICAST;
import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_MTU;
@@ -42,12 +40,13 @@ import android.content.Context;
import android.net.INetd;
import android.net.IpPrefix;
import android.net.MacAddress;
import android.net.RouteInfo;
import android.net.ip.RouterAdvertisementDaemon.RaParams;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -55,7 +54,6 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.Ipv6Utils;
import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.Struct;
import com.android.net.module.util.structs.EthernetHeader;
import com.android.net.module.util.structs.Icmpv6Header;
@@ -80,7 +78,6 @@ import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.List;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -332,10 +329,12 @@ public final class RouterAdvertisementDaemonTest {
// Add a default route "fe80::/64 -> ::" to local network, otherwise, device will fail to
// send the unicast RA out due to the ENETUNREACH error(No route to the peer's link-local
// address is present).
final String iface = mTetheredParams.name;
final RouteInfo linkLocalRoute =
new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RTN_UNICAST);
NetdUtils.addRoutesToLocalNetwork(sNetd, iface, List.of(linkLocalRoute));
try {
sNetd.networkAddRoute(INetd.LOCAL_NET_ID, mTetheredParams.name,
"fe80::/64", INetd.NEXTHOP_NONE);
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
final ByteBuffer rs = createRsPacket("fe80::1122:3344:5566:7788");
mTetheredPacketReader.sendResponse(rs);

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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,

View File

@@ -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);

View File

@@ -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<LocalNetworkConfig> 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);
}
}
}

View File

@@ -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<Inet6Address> mListeningAddresses;
private MulticastRoutingConfig(@MulticastForwardingMode final int mode, final int scope,
@Nullable final Set<Inet6Address> 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<Inet6Address> getMulticastListeningAddresses() {
return mListeningAddresses;
}
private MulticastRoutingConfig(Parcel in) {
mForwardingMode = in.readInt();
mMinScope = in.readInt();
final int count = in.readInt();
final ArraySet<Inet6Address> 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<MulticastRoutingConfig> 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<Inet6Address> 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;
}
}
}

View File

@@ -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<NetworkAgentInfo, LocalNetworkConfig>}
* @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.
*

View File

@@ -19,6 +19,7 @@ package com.android.server;
import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.content.pm.PackageManager.FEATURE_WIFI;
import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT;
@@ -67,6 +68,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
@@ -159,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;
@@ -1773,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);
@@ -4121,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;
@@ -5006,7 +5014,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
!nai.networkAgentConfig.allowBypass /* secure */,
getVpnType(nai), nai.networkAgentConfig.excludeLocalRouteVpn);
} else {
config = new NativeNetworkConfig(nai.network.getNetId(), NativeNetworkType.PHYSICAL,
final boolean hasLocalCap =
nai.networkCapabilities.hasCapability(NET_CAPABILITY_LOCAL_NETWORK);
config = new NativeNetworkConfig(nai.network.getNetId(),
hasLocalCap ? NativeNetworkType.PHYSICAL_LOCAL : NativeNetworkType.PHYSICAL,
getNetworkPermission(nai.networkCapabilities),
false /* secure */,
VpnManager.TYPE_VPN_NONE,
@@ -8056,6 +8067,18 @@ public class ConnectivityService extends IConnectivityManager.Stub
return nai == getDefaultNetwork();
}
/**
* Returns whether local agents are supported on this device.
*
* Local agents are supported from U on TVs, and from V on all devices.
*/
@VisibleForTesting
public boolean areLocalAgentsSupported() {
final PackageManager pm = mContext.getPackageManager();
// Local agents are supported starting on U on TVs and on V on everything else.
return mDeps.isAtLeastV() || (mDeps.isAtLeastU() && pm.hasSystemFeature(FEATURE_LEANBACK));
}
/**
* Register a new agent with ConnectivityService to handle a network.
*
@@ -8067,13 +8090,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");
@@ -8085,12 +8113,26 @@ public class ConnectivityService extends IConnectivityManager.Stub
} else {
enforceNetworkFactoryPermission();
}
final boolean hasLocalCap =
networkCapabilities.hasCapability(NET_CAPABILITY_LOCAL_NETWORK);
if (hasLocalCap && !areLocalAgentsSupported()) {
// 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);
}
@@ -8098,7 +8140,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
@@ -8106,6 +8149,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
@@ -8113,9 +8157,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)
@@ -8850,6 +8894,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).
*
@@ -9190,7 +9244,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
// are Type.LISTEN, but should not have NetworkCallbacks invoked.
return;
}
Bundle bundle = new Bundle();
final Bundle bundle = new Bundle();
// TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects.
// TODO: check if defensive copies of data is needed.
final NetworkRequest nrForCallback = nri.getNetworkRequestForCallback();

View File

@@ -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;
@@ -64,7 +65,6 @@ import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.WakeupMessage;
import com.android.modules.utils.build.SdkLevel;
import com.android.server.ConnectivityService;
import java.io.PrintWriter;
@@ -174,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
@@ -470,8 +471,8 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
+ networkCapabilities.getOwnerUid() + " to " + nc.getOwnerUid());
nc.setOwnerUid(networkCapabilities.getOwnerUid());
}
restrictCapabilitiesFromNetworkAgent(
nc, creatorUid, mHasAutomotiveFeature, carrierPrivilegeAuthenticator);
restrictCapabilitiesFromNetworkAgent(nc, creatorUid, mHasAutomotiveFeature,
mConnServiceDeps, carrierPrivilegeAuthenticator);
return nc;
}
@@ -601,6 +602,7 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
private static final String TAG = ConnectivityService.class.getSimpleName();
private static final boolean VDBG = false;
private final ConnectivityService mConnService;
private final ConnectivityService.Dependencies mConnServiceDeps;
private final Context mContext;
private final Handler mHandler;
private final QosCallbackTracker mQosCallbackTracker;
@@ -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,8 +629,10 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
networkInfo = info;
linkProperties = lp;
networkCapabilities = nc;
this.localNetworkConfig = localNetworkConfig;
networkAgentConfig = config;
mConnService = connService;
mConnServiceDeps = deps;
setScore(score); // uses members connService, networkCapabilities and networkAgentConfig
clatd = new Nat464Xlat(this, netd, dnsResolver, deps);
mContext = context;
@@ -903,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,
@@ -1518,23 +1529,26 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
*/
public static void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
final int creatorUid, final boolean hasAutomotiveFeature,
@NonNull final ConnectivityService.Dependencies deps,
@Nullable final CarrierPrivilegeAuthenticator authenticator) {
if (nc.hasTransport(TRANSPORT_TEST)) {
nc.restrictCapabilitiesForTestNetwork(creatorUid);
}
if (!areAllowedUidsAcceptableFromNetworkAgent(nc, hasAutomotiveFeature, authenticator)) {
if (!areAllowedUidsAcceptableFromNetworkAgent(
nc, hasAutomotiveFeature, deps, authenticator)) {
nc.setAllowedUids(new ArraySet<>());
}
}
private static boolean areAllowedUidsAcceptableFromNetworkAgent(
@NonNull final NetworkCapabilities nc, final boolean hasAutomotiveFeature,
@NonNull final ConnectivityService.Dependencies deps,
@Nullable final CarrierPrivilegeAuthenticator carrierPrivilegeAuthenticator) {
// NCs without access UIDs are fine.
if (!nc.hasAllowedUids()) return true;
// S and below must never accept access UIDs, even if an agent sends them, because netd
// didn't support the required feature in S.
if (!SdkLevel.isAtLeastT()) return false;
if (!deps.isAtLeastT()) return false;
// On a non-restricted network, access UIDs make no sense
if (nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) return false;

View File

@@ -159,9 +159,11 @@ public class NetdUtils {
throws RemoteException, ServiceSpecificException {
netd.tetherInterfaceAdd(iface);
networkAddInterface(netd, iface, maxAttempts, pollingIntervalMs);
List<RouteInfo> routes = new ArrayList<>();
routes.add(new RouteInfo(dest, null, iface, RTN_UNICAST));
addRoutesToLocalNetwork(netd, iface, routes);
// Activate a route to dest and IPv6 link local.
modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID,
new RouteInfo(dest, null, iface, RTN_UNICAST));
modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID,
new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RTN_UNICAST));
}
/**

View File

@@ -704,6 +704,7 @@ class NetworkAgentTest {
argThat<NetworkInfo> { 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))

View File

@@ -12819,7 +12819,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());

View File

@@ -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

View File

@@ -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<NetworkCapabilities> 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

View File

@@ -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)
}

View File

@@ -0,0 +1,131 @@
/*
* 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.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
import android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK
import android.net.NetworkRequest
import android.net.NetworkScore
import android.net.NetworkScore.KEEP_CONNECTED_FOR_TEST
import android.net.VpnManager
import android.os.Build
import androidx.test.filters.SmallTest
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.RecorderCallback.CallbackEntry.Available
import com.android.testutils.TestableNetworkCallback
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.never
import org.mockito.Mockito.timeout
import kotlin.test.assertFailsWith
private const val TIMEOUT_MS = 2_000L
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)
class CSLocalAgentCreationTests(
private val sdkLevel: Int,
private val isTv: Boolean,
private val addLocalNetCapToRequest: Boolean
) : CSTest() {
companion object {
@JvmStatic
@Parameterized.Parameters
fun arguments() = listOf(
arrayOf(VERSION_V, false /* isTv */, true /* addLocalNetCapToRequest */),
arrayOf(VERSION_V, false /* isTv */, false /* addLocalNetCapToRequest */),
arrayOf(VERSION_V, true /* isTv */, true /* addLocalNetCapToRequest */),
arrayOf(VERSION_V, true /* isTv */, false /* addLocalNetCapToRequest */),
arrayOf(VERSION_U, false /* isTv */, true /* addLocalNetCapToRequest */),
arrayOf(VERSION_U, false /* isTv */, false /* addLocalNetCapToRequest */),
arrayOf(VERSION_U, true /* isTv */, true /* addLocalNetCapToRequest */),
arrayOf(VERSION_U, true /* isTv */, false /* addLocalNetCapToRequest */),
arrayOf(VERSION_T, false /* isTv */, false /* addLocalNetCapToRequest */),
arrayOf(VERSION_T, true /* isTv */, false /* addLocalNetCapToRequest */),
)
}
private fun makeNativeNetworkConfigLocal(netId: Int, permission: Int) =
NativeNetworkConfig(netId, NativeNetworkType.PHYSICAL_LOCAL, permission,
false /* secure */, VpnManager.TYPE_VPN_NONE, false /* excludeLocalRoutes */)
@Test
fun testLocalAgents() {
val netdInOrder = inOrder(netd)
deps.setBuildSdk(sdkLevel)
doReturn(isTv).`when`(packageManager).hasSystemFeature(FEATURE_LEANBACK)
val allNetworksCb = TestableNetworkCallback()
val request = NetworkRequest.Builder()
if (addLocalNetCapToRequest) {
request.addCapability(NET_CAPABILITY_LOCAL_NETWORK)
}
cm.registerNetworkCallback(request.build(), allNetworksCb)
val ncTemplate = NetworkCapabilities.Builder().run {
addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
addCapability(NET_CAPABILITY_LOCAL_NETWORK)
}.build()
val localAgent = if (sdkLevel >= VERSION_V || sdkLevel == VERSION_U && isTv) {
Agent(nc = ncTemplate, score = keepConnectedScore(), lnc = defaultLnc())
} else {
assertFailsWith<IllegalArgumentException> { Agent(nc = ncTemplate, lnc = defaultLnc()) }
netdInOrder.verify(netd, never()).networkCreate(any())
return
}
localAgent.connect()
netdInOrder.verify(netd).networkCreate(
makeNativeNetworkConfigLocal(localAgent.network.netId, INetd.PERMISSION_NONE))
if (addLocalNetCapToRequest) {
assertEquals(localAgent.network, allNetworksCb.expect<Available>().network)
} else {
allNetworksCb.assertNoCallback(NO_CALLBACK_TIMEOUT_MS)
}
cm.unregisterNetworkCallback(allNetworksCb)
localAgent.disconnect()
netdInOrder.verify(netd, timeout(TIMEOUT_MS)).networkDestroy(localAgent.network.netId)
}
@Test
fun testBadAgents() {
assertFailsWith<IllegalArgumentException> {
Agent(nc = NetworkCapabilities.Builder()
.addCapability(NET_CAPABILITY_LOCAL_NETWORK)
.build(),
lnc = null)
}
assertFailsWith<IllegalArgumentException> {
Agent(nc = NetworkCapabilities.Builder().build(),
lnc = LocalNetworkConfig.Builder().build())
}
}
}

View File

@@ -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<IllegalArgumentException> {
Agent(nc = NetworkCapabilities.Builder()
.addCapability(NET_CAPABILITY_LOCAL_NETWORK)
.build(),
lnc = null)
}
assertFailsWith<IllegalArgumentException> {
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<Available>(localAgent.network)
cb.expect<CapabilitiesChanged>(localAgent.network)
cb.expect<LinkPropertiesChanged>(localAgent.network)
cb.expect<BlockedStatus>(localAgent.network)
val newLnc = LocalNetworkConfig.Builder()
.setUpstreamSelector(NetworkRequest.Builder()
.addTransportType(TRANSPORT_WIFI)
.build())
.build()
localAgent.sendLocalNetworkConfig(newLnc)
localAgent.disconnect()
}
}

View File

@@ -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<NetworkScore>,
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<Lost> { it.network == agent.network }
}
fun sendLocalNetworkConfig(lnc: LocalNetworkConfig) = agent.sendLocalNetworkConfig(lnc)
}

View File

@@ -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<NetworkScore> = 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 {