diff options
Diffstat (limited to 'packages/Tethering/src')
25 files changed, 0 insertions, 8459 deletions
diff --git a/packages/Tethering/src/android/net/dhcp/DhcpServerCallbacks.java b/packages/Tethering/src/android/net/dhcp/DhcpServerCallbacks.java deleted file mode 100644 index 9fda1257b4c9..000000000000 --- a/packages/Tethering/src/android/net/dhcp/DhcpServerCallbacks.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.dhcp; - -/** - * Convenience wrapper around IDhcpServerCallbacks.Stub that implements getInterfaceVersion(). - * @hide - */ -public abstract class DhcpServerCallbacks extends IDhcpServerCallbacks.Stub { - /** - * Get the version of the aidl interface implemented by the callbacks. - */ - @Override - public int getInterfaceVersion() { - return IDhcpServerCallbacks.VERSION; - } - - @Override - public String getInterfaceHash() { - return IDhcpServerCallbacks.HASH; - } -} diff --git a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java b/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java deleted file mode 100644 index aaaec17bf922..000000000000 --- a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.dhcp; - -import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH; - -import android.net.LinkAddress; -import android.util.ArraySet; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.net.Inet4Address; -import java.util.Collection; -import java.util.Collections; -import java.util.Set; - -/** - * Subclass of {@link DhcpServingParamsParcel} with additional utility methods for building. - * - * <p>This utility class does not check for validity of the parameters: invalid parameters are - * reported by the receiving module when unparceling the parcel. - * - * @see DhcpServingParams - * @hide - */ -public class DhcpServingParamsParcelExt extends DhcpServingParamsParcel { - public static final int MTU_UNSET = 0; - - /** - * Set the server address and served prefix for the DHCP server. - * - * <p>This parameter is required. - */ - public DhcpServingParamsParcelExt setServerAddr(@NonNull LinkAddress serverAddr) { - this.serverAddr = inet4AddressToIntHTH((Inet4Address) serverAddr.getAddress()); - this.serverAddrPrefixLength = serverAddr.getPrefixLength(); - return this; - } - - /** - * Set the default routers to be advertised to DHCP clients. - * - * <p>Each router must be inside the served prefix. This may be an empty set, but it must - * always be set explicitly. - */ - public DhcpServingParamsParcelExt setDefaultRouters(@NonNull Set<Inet4Address> defaultRouters) { - this.defaultRouters = toIntArray(defaultRouters); - return this; - } - - /** - * Set the default routers to be advertised to DHCP clients. - * - * <p>Each router must be inside the served prefix. This may be an empty list of routers, - * but it must always be set explicitly. - */ - public DhcpServingParamsParcelExt setDefaultRouters(@NonNull Inet4Address... defaultRouters) { - return setDefaultRouters(newArraySet(defaultRouters)); - } - - /** - * Convenience method to build the parameters with no default router. - * - * <p>Equivalent to calling {@link #setDefaultRouters(Inet4Address...)} with no address. - */ - public DhcpServingParamsParcelExt setNoDefaultRouter() { - return setDefaultRouters(); - } - - /** - * Set the DNS servers to be advertised to DHCP clients. - * - * <p>This may be an empty set, but it must always be set explicitly. - */ - public DhcpServingParamsParcelExt setDnsServers(@NonNull Set<Inet4Address> dnsServers) { - this.dnsServers = toIntArray(dnsServers); - return this; - } - - /** - * Set the DNS servers to be advertised to DHCP clients. - * - * <p>This may be an empty list of servers, but it must always be set explicitly. - */ - public DhcpServingParamsParcelExt setDnsServers(@NonNull Inet4Address... dnsServers) { - return setDnsServers(newArraySet(dnsServers)); - } - - /** - * Convenience method to build the parameters with no DNS server. - * - * <p>Equivalent to calling {@link #setDnsServers(Inet4Address...)} with no address. - */ - public DhcpServingParamsParcelExt setNoDnsServer() { - return setDnsServers(); - } - - /** - * Set excluded addresses that the DHCP server is not allowed to assign to clients. - * - * <p>This parameter is optional. DNS servers and default routers are always excluded - * and do not need to be set here. - */ - public DhcpServingParamsParcelExt setExcludedAddrs(@NonNull Set<Inet4Address> excludedAddrs) { - this.excludedAddrs = toIntArray(excludedAddrs); - return this; - } - - /** - * Set excluded addresses that the DHCP server is not allowed to assign to clients. - * - * <p>This parameter is optional. DNS servers and default routers are always excluded - * and do not need to be set here. - */ - public DhcpServingParamsParcelExt setExcludedAddrs(@NonNull Inet4Address... excludedAddrs) { - return setExcludedAddrs(newArraySet(excludedAddrs)); - } - - /** - * Set the lease time for leases assigned by the DHCP server. - * - * <p>This parameter is required. - */ - public DhcpServingParamsParcelExt setDhcpLeaseTimeSecs(long dhcpLeaseTimeSecs) { - this.dhcpLeaseTimeSecs = dhcpLeaseTimeSecs; - return this; - } - - /** - * Set the link MTU to be advertised to DHCP clients. - * - * <p>If set to {@link #MTU_UNSET}, no MTU will be advertised to clients. This parameter - * is optional and defaults to {@link #MTU_UNSET}. - */ - public DhcpServingParamsParcelExt setLinkMtu(int linkMtu) { - this.linkMtu = linkMtu; - return this; - } - - /** - * Set whether the DHCP server should send the ANDROID_METERED vendor-specific option. - * - * <p>If not set, the default value is false. - */ - public DhcpServingParamsParcelExt setMetered(boolean metered) { - this.metered = metered; - return this; - } - - /** - * Set the client address to tell DHCP server only offer this address. - * The client's prefix length is the same as server's. - * - * <p>If not set, the default value is null. - */ - public DhcpServingParamsParcelExt setSingleClientAddr(@Nullable Inet4Address clientAddr) { - this.singleClientAddr = clientAddr == null ? 0 : inet4AddressToIntHTH(clientAddr); - return this; - } - - /** - * Set whether the DHCP server should request a new prefix from IpServer when receiving - * DHCPDECLINE message in certain particular link (e.g. there is only one downstream USB - * tethering client). If it's false, process DHCPDECLINE message as RFC2131#4.3.3 suggests. - * - * <p>If not set, the default value is false. - */ - public DhcpServingParamsParcelExt setChangePrefixOnDecline(boolean changePrefixOnDecline) { - this.changePrefixOnDecline = changePrefixOnDecline; - return this; - } - - private static int[] toIntArray(@NonNull Collection<Inet4Address> addrs) { - int[] res = new int[addrs.size()]; - int i = 0; - for (Inet4Address addr : addrs) { - res[i] = inet4AddressToIntHTH(addr); - i++; - } - return res; - } - - private static ArraySet<Inet4Address> newArraySet(Inet4Address... addrs) { - ArraySet<Inet4Address> addrSet = new ArraySet<>(addrs.length); - Collections.addAll(addrSet, addrs); - return addrSet; - } -} diff --git a/packages/Tethering/src/android/net/ip/DadProxy.java b/packages/Tethering/src/android/net/ip/DadProxy.java deleted file mode 100644 index e2976b78908c..000000000000 --- a/packages/Tethering/src/android/net/ip/DadProxy.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2020 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.ip; - -import android.net.util.InterfaceParams; -import android.os.Handler; - -import androidx.annotation.VisibleForTesting; - -/** - * Basic Duplicate address detection proxy. - * - * @hide - */ -public class DadProxy { - private static final String TAG = DadProxy.class.getSimpleName(); - - @VisibleForTesting - public static NeighborPacketForwarder naForwarder; - public static NeighborPacketForwarder nsForwarder; - - public DadProxy(Handler h, InterfaceParams tetheredIface) { - naForwarder = new NeighborPacketForwarder(h, tetheredIface, - NeighborPacketForwarder.ICMPV6_NEIGHBOR_ADVERTISEMENT); - nsForwarder = new NeighborPacketForwarder(h, tetheredIface, - NeighborPacketForwarder.ICMPV6_NEIGHBOR_SOLICITATION); - } - - /** Stop NS/NA Forwarders. */ - public void stop() { - naForwarder.stop(); - nsForwarder.stop(); - } - - /** Set upstream iface on both forwarders. */ - public void setUpstreamIface(InterfaceParams upstreamIface) { - naForwarder.setUpstreamIface(upstreamIface); - nsForwarder.setUpstreamIface(upstreamIface); - } -} diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java deleted file mode 100644 index 52d59fcdc19b..000000000000 --- a/packages/Tethering/src/android/net/ip/IpServer.java +++ /dev/null @@ -1,1422 +0,0 @@ -/* - * Copyright (C) 2016 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.ip; - -import static android.net.RouteInfo.RTN_UNICAST; -import static android.net.TetheringManager.TetheringRequest.checkStaticAddressConfiguration; -import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; -import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH; -import static android.net.util.NetworkConstants.asByte; -import static android.net.util.PrefixUtils.asIpPrefix; -import static android.net.util.TetheringMessageBase.BASE_IPSERVER; -import static android.system.OsConstants.RT_SCOPE_UNIVERSE; - -import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH; - -import android.net.INetd; -import android.net.INetworkStackStatusCallback; -import android.net.IpPrefix; -import android.net.LinkAddress; -import android.net.LinkProperties; -import android.net.MacAddress; -import android.net.RouteInfo; -import android.net.TetheredClient; -import android.net.TetheringManager; -import android.net.TetheringRequestParcel; -import android.net.dhcp.DhcpLeaseParcelable; -import android.net.dhcp.DhcpServerCallbacks; -import android.net.dhcp.DhcpServingParamsParcel; -import android.net.dhcp.DhcpServingParamsParcelExt; -import android.net.dhcp.IDhcpEventCallbacks; -import android.net.dhcp.IDhcpServer; -import android.net.ip.IpNeighborMonitor.NeighborEvent; -import android.net.ip.RouterAdvertisementDaemon.RaParams; -import android.net.shared.NetdUtils; -import android.net.shared.RouteUtils; -import android.net.util.InterfaceParams; -import android.net.util.InterfaceSet; -import android.net.util.PrefixUtils; -import android.net.util.SharedLog; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.RemoteException; -import android.os.ServiceSpecificException; -import android.util.Log; -import android.util.SparseArray; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.internal.util.MessageUtils; -import com.android.internal.util.State; -import com.android.internal.util.StateMachine; -import com.android.networkstack.tethering.BpfCoordinator; -import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; -import com.android.networkstack.tethering.PrivateAddressCoordinator; - -import java.io.IOException; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.NetworkInterface; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Random; -import java.util.Set; - -/** - * Provides the interface to IP-layer serving functionality for a given network - * interface, e.g. for tethering or "local-only hotspot" mode. - * - * @hide - */ -public class IpServer extends StateMachine { - public static final int STATE_UNAVAILABLE = 0; - public static final int STATE_AVAILABLE = 1; - public static final int STATE_TETHERED = 2; - public static final int STATE_LOCAL_ONLY = 3; - - /** Get string name of |state|.*/ - public static String getStateString(int state) { - switch (state) { - case STATE_UNAVAILABLE: return "UNAVAILABLE"; - case STATE_AVAILABLE: return "AVAILABLE"; - case STATE_TETHERED: return "TETHERED"; - case STATE_LOCAL_ONLY: return "LOCAL_ONLY"; - } - return "UNKNOWN: " + state; - } - - private static final byte DOUG_ADAMS = (byte) 42; - - // TODO: have PanService use some visible version of this constant - private static final String BLUETOOTH_IFACE_ADDR = "192.168.44.1/24"; - - // TODO: have this configurable - private static final int DHCP_LEASE_TIME_SECS = 3600; - - private static final MacAddress NULL_MAC_ADDRESS = MacAddress.fromString("00:00:00:00:00:00"); - - private static final String TAG = "IpServer"; - private static final boolean DBG = false; - private static final boolean VDBG = false; - private static final Class[] sMessageClasses = { - IpServer.class - }; - private static final SparseArray<String> sMagicDecoderRing = - MessageUtils.findMessageNames(sMessageClasses); - - /** IpServer callback. */ - public static class Callback { - /** - * Notify that |who| has changed its tethering state. - * - * @param who the calling instance of IpServer - * @param state one of STATE_* - * @param lastError one of TetheringManager.TETHER_ERROR_* - */ - public void updateInterfaceState(IpServer who, int state, int lastError) { } - - /** - * Notify that |who| has new LinkProperties. - * - * @param who the calling instance of IpServer - * @param newLp the new LinkProperties to report - */ - public void updateLinkProperties(IpServer who, LinkProperties newLp) { } - - /** - * Notify that the DHCP leases changed in one of the IpServers. - */ - public void dhcpLeasesChanged() { } - - /** - * Request Tethering change. - * - * @param tetheringType the downstream type of this IpServer. - * @param enabled enable or disable tethering. - */ - public void requestEnableTethering(int tetheringType, boolean enabled) { } - } - - /** Capture IpServer dependencies, for injection. */ - public abstract static class Dependencies { - /** - * Create a DadProxy instance to be used by IpServer. - * To support multiple tethered interfaces concurrently DAD Proxy - * needs to be supported per IpServer instead of per upstream. - */ - public DadProxy getDadProxy(Handler handler, InterfaceParams ifParams) { - return new DadProxy(handler, ifParams); - } - - /** Create an IpNeighborMonitor to be used by this IpServer */ - public IpNeighborMonitor getIpNeighborMonitor(Handler handler, SharedLog log, - IpNeighborMonitor.NeighborEventConsumer consumer) { - return new IpNeighborMonitor(handler, log, consumer); - } - - /** Create a RouterAdvertisementDaemon instance to be used by IpServer.*/ - public RouterAdvertisementDaemon getRouterAdvertisementDaemon(InterfaceParams ifParams) { - return new RouterAdvertisementDaemon(ifParams); - } - - /** Get |ifName|'s interface information.*/ - public InterfaceParams getInterfaceParams(String ifName) { - return InterfaceParams.getByName(ifName); - } - - /** Get |ifName|'s interface index. */ - public int getIfindex(String ifName) { - try { - return NetworkInterface.getByName(ifName).getIndex(); - } catch (IOException | NullPointerException e) { - Log.e(TAG, "Can't determine interface index for interface " + ifName); - return 0; - } - } - - /** Create a DhcpServer instance to be used by IpServer. */ - public abstract void makeDhcpServer(String ifName, DhcpServingParamsParcel params, - DhcpServerCallbacks cb); - } - - // request from the user that it wants to tether - public static final int CMD_TETHER_REQUESTED = BASE_IPSERVER + 1; - // request from the user that it wants to untether - public static final int CMD_TETHER_UNREQUESTED = BASE_IPSERVER + 2; - // notification that this interface is down - public static final int CMD_INTERFACE_DOWN = BASE_IPSERVER + 3; - // notification from the {@link Tethering.TetherMainSM} that it had trouble enabling IP - // Forwarding - public static final int CMD_IP_FORWARDING_ENABLE_ERROR = BASE_IPSERVER + 4; - // notification from the {@link Tethering.TetherMainSM} SM that it had trouble disabling IP - // Forwarding - public static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IPSERVER + 5; - // notification from the {@link Tethering.TetherMainSM} SM that it had trouble starting - // tethering - public static final int CMD_START_TETHERING_ERROR = BASE_IPSERVER + 6; - // notification from the {@link Tethering.TetherMainSM} that it had trouble stopping tethering - public static final int CMD_STOP_TETHERING_ERROR = BASE_IPSERVER + 7; - // notification from the {@link Tethering.TetherMainSM} that it had trouble setting the DNS - // forwarders - public static final int CMD_SET_DNS_FORWARDERS_ERROR = BASE_IPSERVER + 8; - // the upstream connection has changed - public static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IPSERVER + 9; - // new IPv6 tethering parameters need to be processed - public static final int CMD_IPV6_TETHER_UPDATE = BASE_IPSERVER + 10; - // new neighbor cache entry on our interface - public static final int CMD_NEIGHBOR_EVENT = BASE_IPSERVER + 11; - // request from DHCP server that it wants to have a new prefix - public static final int CMD_NEW_PREFIX_REQUEST = BASE_IPSERVER + 12; - // request from PrivateAddressCoordinator to restart tethering. - public static final int CMD_NOTIFY_PREFIX_CONFLICT = BASE_IPSERVER + 13; - - private final State mInitialState; - private final State mLocalHotspotState; - private final State mTetheredState; - private final State mUnavailableState; - private final State mWaitingForRestartState; - - private final SharedLog mLog; - private final INetd mNetd; - @NonNull - private final BpfCoordinator mBpfCoordinator; - private final Callback mCallback; - private final InterfaceController mInterfaceCtrl; - private final PrivateAddressCoordinator mPrivateAddressCoordinator; - - private final String mIfaceName; - private final int mInterfaceType; - private final LinkProperties mLinkProperties; - private final boolean mUsingLegacyDhcp; - private final boolean mUsingBpfOffload; - - private final Dependencies mDeps; - - private int mLastError; - private int mServingMode; - private InterfaceSet mUpstreamIfaceSet; // may change over time - private InterfaceParams mInterfaceParams; - // TODO: De-duplicate this with mLinkProperties above. Currently, these link - // properties are those selected by the IPv6TetheringCoordinator and relayed - // to us. By comparison, mLinkProperties contains the addresses and directly - // connected routes that have been formed from these properties iff. we have - // succeeded in configuring them and are able to announce them within Router - // Advertisements (otherwise, we do not add them to mLinkProperties at all). - private LinkProperties mLastIPv6LinkProperties; - private RouterAdvertisementDaemon mRaDaemon; - private DadProxy mDadProxy; - - // To be accessed only on the handler thread - private int mDhcpServerStartIndex = 0; - private IDhcpServer mDhcpServer; - private RaParams mLastRaParams; - - private LinkAddress mStaticIpv4ServerAddr; - private LinkAddress mStaticIpv4ClientAddr; - - @NonNull - private List<TetheredClient> mDhcpLeases = Collections.emptyList(); - - private int mLastIPv6UpstreamIfindex = 0; - - private class MyNeighborEventConsumer implements IpNeighborMonitor.NeighborEventConsumer { - public void accept(NeighborEvent e) { - sendMessage(CMD_NEIGHBOR_EVENT, e); - } - } - - private final IpNeighborMonitor mIpNeighborMonitor; - - private LinkAddress mIpv4Address; - - // TODO: Add a dependency object to pass the data members or variables from the tethering - // object. It helps to reduce the arguments of the constructor. - public IpServer( - String ifaceName, Looper looper, int interfaceType, SharedLog log, - INetd netd, @NonNull BpfCoordinator coordinator, Callback callback, - boolean usingLegacyDhcp, boolean usingBpfOffload, - PrivateAddressCoordinator addressCoordinator, Dependencies deps) { - super(ifaceName, looper); - mLog = log.forSubComponent(ifaceName); - mNetd = netd; - mBpfCoordinator = coordinator; - mCallback = callback; - mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog); - mIfaceName = ifaceName; - mInterfaceType = interfaceType; - mLinkProperties = new LinkProperties(); - mUsingLegacyDhcp = usingLegacyDhcp; - mUsingBpfOffload = usingBpfOffload; - mPrivateAddressCoordinator = addressCoordinator; - mDeps = deps; - resetLinkProperties(); - mLastError = TetheringManager.TETHER_ERROR_NO_ERROR; - mServingMode = STATE_AVAILABLE; - - mIpNeighborMonitor = mDeps.getIpNeighborMonitor(getHandler(), mLog, - new MyNeighborEventConsumer()); - - // IP neighbor monitor monitors the neighbor events for adding/removing offload - // forwarding rules per client. If BPF offload is not supported, don't start listening - // for neighbor events. See updateIpv6ForwardingRules, addIpv6ForwardingRule, - // removeIpv6ForwardingRule. - if (mUsingBpfOffload && !mIpNeighborMonitor.start()) { - mLog.e("Failed to create IpNeighborMonitor on " + mIfaceName); - } - - mInitialState = new InitialState(); - mLocalHotspotState = new LocalHotspotState(); - mTetheredState = new TetheredState(); - mUnavailableState = new UnavailableState(); - mWaitingForRestartState = new WaitingForRestartState(); - addState(mInitialState); - addState(mLocalHotspotState); - addState(mTetheredState); - addState(mWaitingForRestartState, mTetheredState); - addState(mUnavailableState); - - setInitialState(mInitialState); - } - - /** Interface name which IpServer served.*/ - public String interfaceName() { - return mIfaceName; - } - - /** - * Tethering downstream type. It would be one of TetheringManager#TETHERING_*. - */ - public int interfaceType() { - return mInterfaceType; - } - - /** Last error from this IpServer. */ - public int lastError() { - return mLastError; - } - - /** Serving mode is the current state of IpServer state machine. */ - public int servingMode() { - return mServingMode; - } - - /** The properties of the network link which IpServer is serving. */ - public LinkProperties linkProperties() { - return new LinkProperties(mLinkProperties); - } - - /** The address which IpServer is using. */ - public LinkAddress getAddress() { - return mIpv4Address; - } - - /** - * Get the latest list of DHCP leases that was reported. Must be called on the IpServer looper - * thread. - */ - public List<TetheredClient> getAllLeases() { - return Collections.unmodifiableList(mDhcpLeases); - } - - /** Stop this IpServer. After this is called this IpServer should not be used any more. */ - public void stop() { - sendMessage(CMD_INTERFACE_DOWN); - } - - /** - * Tethering is canceled. IpServer state machine will be available and wait for - * next tethering request. - */ - public void unwanted() { - sendMessage(CMD_TETHER_UNREQUESTED); - } - - /** Internals. */ - - private boolean startIPv4() { - return configureIPv4(true); - } - - /** - * Convenience wrapper around INetworkStackStatusCallback to run callbacks on the IpServer - * handler. - * - * <p>Different instances of this class can be created for each call to IDhcpServer methods, - * with different implementations of the callback, to differentiate handling of success/error in - * each call. - */ - private abstract class OnHandlerStatusCallback extends INetworkStackStatusCallback.Stub { - @Override - public void onStatusAvailable(int statusCode) { - getHandler().post(() -> callback(statusCode)); - } - - public abstract void callback(int statusCode); - - @Override - public int getInterfaceVersion() { - return this.VERSION; - } - - @Override - public String getInterfaceHash() { - return this.HASH; - } - } - - private class DhcpServerCallbacksImpl extends DhcpServerCallbacks { - private final int mStartIndex; - - private DhcpServerCallbacksImpl(int startIndex) { - mStartIndex = startIndex; - } - - @Override - public void onDhcpServerCreated(int statusCode, IDhcpServer server) throws RemoteException { - getHandler().post(() -> { - // We are on the handler thread: mDhcpServerStartIndex can be read safely. - if (mStartIndex != mDhcpServerStartIndex) { - // This start request is obsolete. Explicitly stop the DHCP server to shut - // down its thread. When the |server| binder token goes out of scope, the - // garbage collector will finalize it, which causes the network stack process - // garbage collector to collect the server itself. - try { - server.stop(null); - } catch (RemoteException e) { } - return; - } - - if (statusCode != STATUS_SUCCESS) { - mLog.e("Error obtaining DHCP server: " + statusCode); - handleError(); - return; - } - - mDhcpServer = server; - try { - mDhcpServer.startWithCallbacks(new OnHandlerStatusCallback() { - @Override - public void callback(int startStatusCode) { - if (startStatusCode != STATUS_SUCCESS) { - mLog.e("Error starting DHCP server: " + startStatusCode); - handleError(); - } - } - }, new DhcpEventCallback()); - } catch (RemoteException e) { - throw new IllegalStateException(e); - } - }); - } - - private void handleError() { - mLastError = TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR; - transitionTo(mInitialState); - } - } - - private class DhcpEventCallback extends IDhcpEventCallbacks.Stub { - @Override - public void onLeasesChanged(List<DhcpLeaseParcelable> leaseParcelables) { - final ArrayList<TetheredClient> leases = new ArrayList<>(); - for (DhcpLeaseParcelable lease : leaseParcelables) { - final LinkAddress address = new LinkAddress( - intToInet4AddressHTH(lease.netAddr), lease.prefixLength, - 0 /* flags */, RT_SCOPE_UNIVERSE /* as per RFC6724#3.2 */, - lease.expTime /* deprecationTime */, lease.expTime /* expirationTime */); - - final MacAddress macAddress; - try { - macAddress = MacAddress.fromBytes(lease.hwAddr); - } catch (IllegalArgumentException e) { - Log.wtf(TAG, "Invalid address received from DhcpServer: " - + Arrays.toString(lease.hwAddr)); - return; - } - - final TetheredClient.AddressInfo addressInfo = new TetheredClient.AddressInfo( - address, lease.hostname); - leases.add(new TetheredClient( - macAddress, - Collections.singletonList(addressInfo), - mInterfaceType)); - } - - getHandler().post(() -> { - mDhcpLeases = leases; - mCallback.dhcpLeasesChanged(); - }); - } - - @Override - public void onNewPrefixRequest(@NonNull final IpPrefix currentPrefix) { - Objects.requireNonNull(currentPrefix); - sendMessage(CMD_NEW_PREFIX_REQUEST, currentPrefix); - } - - @Override - public int getInterfaceVersion() { - return this.VERSION; - } - - @Override - public String getInterfaceHash() throws RemoteException { - return this.HASH; - } - } - - private RouteInfo getDirectConnectedRoute(@NonNull final LinkAddress ipv4Address) { - Objects.requireNonNull(ipv4Address); - return new RouteInfo(PrefixUtils.asIpPrefix(ipv4Address), null, mIfaceName, RTN_UNICAST); - } - - private DhcpServingParamsParcel makeServingParams(@NonNull final Inet4Address defaultRouter, - @NonNull final Inet4Address dnsServer, @NonNull LinkAddress serverAddr, - @Nullable Inet4Address clientAddr) { - final boolean changePrefixOnDecline = - (mInterfaceType == TetheringManager.TETHERING_NCM && clientAddr == null); - return new DhcpServingParamsParcelExt() - .setDefaultRouters(defaultRouter) - .setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS) - .setDnsServers(dnsServer) - .setServerAddr(serverAddr) - .setMetered(true) - .setSingleClientAddr(clientAddr) - .setChangePrefixOnDecline(changePrefixOnDecline); - // TODO: also advertise link MTU - } - - private boolean startDhcp(final LinkAddress serverLinkAddr, final LinkAddress clientLinkAddr) { - if (mUsingLegacyDhcp) { - return true; - } - - final Inet4Address addr = (Inet4Address) serverLinkAddr.getAddress(); - final Inet4Address clientAddr = clientLinkAddr == null ? null : - (Inet4Address) clientLinkAddr.getAddress(); - - final DhcpServingParamsParcel params = makeServingParams(addr /* defaultRouter */, - addr /* dnsServer */, serverLinkAddr, clientAddr); - mDhcpServerStartIndex++; - mDeps.makeDhcpServer( - mIfaceName, params, new DhcpServerCallbacksImpl(mDhcpServerStartIndex)); - return true; - } - - private void stopDhcp() { - // Make all previous start requests obsolete so servers are not started later - mDhcpServerStartIndex++; - - if (mDhcpServer != null) { - try { - mDhcpServer.stop(new OnHandlerStatusCallback() { - @Override - public void callback(int statusCode) { - if (statusCode != STATUS_SUCCESS) { - mLog.e("Error stopping DHCP server: " + statusCode); - mLastError = TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR; - // Not much more we can do here - } - mDhcpLeases.clear(); - getHandler().post(mCallback::dhcpLeasesChanged); - } - }); - mDhcpServer = null; - } catch (RemoteException e) { - mLog.e("Error stopping DHCP server", e); - // Not much more we can do here - } - } - } - - private boolean configureDhcp(boolean enable, final LinkAddress serverAddr, - final LinkAddress clientAddr) { - if (enable) { - return startDhcp(serverAddr, clientAddr); - } else { - stopDhcp(); - return true; - } - } - - private void stopIPv4() { - configureIPv4(false); - // NOTE: All of configureIPv4() will be refactored out of existence - // into calls to InterfaceController, shared with startIPv4(). - mInterfaceCtrl.clearIPv4Address(); - mPrivateAddressCoordinator.releaseDownstream(this); - mIpv4Address = null; - mStaticIpv4ServerAddr = null; - mStaticIpv4ClientAddr = null; - } - - private boolean configureIPv4(boolean enabled) { - if (VDBG) Log.d(TAG, "configureIPv4(" + enabled + ")"); - - if (enabled) { - mIpv4Address = requestIpv4Address(true /* useLastAddress */); - } - - if (mIpv4Address == null) { - mLog.e("No available ipv4 address"); - return false; - } - - if (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) { - // BT configures the interface elsewhere: only start DHCP. - // TODO: make all tethering types behave the same way, and delete the bluetooth - // code that calls into NetworkManagementService directly. - return configureDhcp(enabled, mIpv4Address, null /* clientAddress */); - } - - final IpPrefix ipv4Prefix = asIpPrefix(mIpv4Address); - - final Boolean setIfaceUp; - if (mInterfaceType == TetheringManager.TETHERING_WIFI - || mInterfaceType == TetheringManager.TETHERING_WIFI_P2P - || mInterfaceType == TetheringManager.TETHERING_ETHERNET - || mInterfaceType == TetheringManager.TETHERING_WIGIG) { - // The WiFi and Ethernet stack has ownership of the interface up/down state. - // It is unclear whether the Bluetooth or USB stacks will manage their own - // state. - setIfaceUp = null; - } else { - setIfaceUp = enabled; - } - if (!mInterfaceCtrl.setInterfaceConfiguration(mIpv4Address, setIfaceUp)) { - mLog.e("Error configuring interface"); - if (!enabled) stopDhcp(); - return false; - } - - if (enabled) { - mLinkProperties.addLinkAddress(mIpv4Address); - mLinkProperties.addRoute(getDirectConnectedRoute(mIpv4Address)); - } else { - mLinkProperties.removeLinkAddress(mIpv4Address); - mLinkProperties.removeRoute(getDirectConnectedRoute(mIpv4Address)); - } - return configureDhcp(enabled, mIpv4Address, mStaticIpv4ClientAddr); - } - - private LinkAddress requestIpv4Address(final boolean useLastAddress) { - if (mStaticIpv4ServerAddr != null) return mStaticIpv4ServerAddr; - - if (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) { - return new LinkAddress(BLUETOOTH_IFACE_ADDR); - } - - return mPrivateAddressCoordinator.requestDownstreamAddress(this, useLastAddress); - } - - private boolean startIPv6() { - mInterfaceParams = mDeps.getInterfaceParams(mIfaceName); - if (mInterfaceParams == null) { - mLog.e("Failed to find InterfaceParams"); - stopIPv6(); - return false; - } - - mRaDaemon = mDeps.getRouterAdvertisementDaemon(mInterfaceParams); - if (!mRaDaemon.start()) { - stopIPv6(); - return false; - } - - // TODO: use ShimUtils instead of explicitly checking the version here. - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R || "S".equals(Build.VERSION.CODENAME) - || "T".equals(Build.VERSION.CODENAME)) { - // DAD Proxy starts forwarding packets after IPv6 upstream is present. - mDadProxy = mDeps.getDadProxy(getHandler(), mInterfaceParams); - } - - return true; - } - - private void stopIPv6() { - mInterfaceParams = null; - setRaParams(null); - - if (mRaDaemon != null) { - mRaDaemon.stop(); - mRaDaemon = null; - } - - if (mDadProxy != null) { - mDadProxy.stop(); - mDadProxy = null; - } - } - - // IPv6TetheringCoordinator sends updates with carefully curated IPv6-only - // LinkProperties. These have extraneous data filtered out and only the - // necessary prefixes included (per its prefix distribution policy). - // - // TODO: Evaluate using a data structure than is more directly suited to - // communicating only the relevant information. - private void updateUpstreamIPv6LinkProperties(LinkProperties v6only, int ttlAdjustment) { - if (mRaDaemon == null) return; - - // Avoid unnecessary work on spurious updates. - if (Objects.equals(mLastIPv6LinkProperties, v6only)) { - return; - } - - RaParams params = null; - String upstreamIface = null; - InterfaceParams upstreamIfaceParams = null; - int upstreamIfIndex = 0; - - if (v6only != null) { - upstreamIface = v6only.getInterfaceName(); - upstreamIfaceParams = mDeps.getInterfaceParams(upstreamIface); - if (upstreamIfaceParams != null) { - upstreamIfIndex = upstreamIfaceParams.index; - } - params = new RaParams(); - params.mtu = v6only.getMtu(); - params.hasDefaultRoute = v6only.hasIpv6DefaultRoute(); - - if (params.hasDefaultRoute) params.hopLimit = getHopLimit(upstreamIface, ttlAdjustment); - - for (LinkAddress linkAddr : v6only.getLinkAddresses()) { - if (linkAddr.getPrefixLength() != RFC7421_PREFIX_LENGTH) continue; - - final IpPrefix prefix = new IpPrefix( - linkAddr.getAddress(), linkAddr.getPrefixLength()); - params.prefixes.add(prefix); - - final Inet6Address dnsServer = getLocalDnsIpFor(prefix); - if (dnsServer != null) { - params.dnses.add(dnsServer); - } - } - - // Add upstream index to name mapping for the tether stats usage in the coordinator. - // Although this mapping could be added by both class Tethering and IpServer, adding - // mapping from IpServer guarantees that the mapping is added before the adding - // forwarding rules. That is because there are different state machines in both - // classes. It is hard to guarantee the link property update order between multiple - // state machines. - mBpfCoordinator.addUpstreamNameToLookupTable(upstreamIfIndex, upstreamIface); - } - - // If v6only is null, we pass in null to setRaParams(), which handles - // deprecation of any existing RA data. - - setRaParams(params); - mLastIPv6LinkProperties = v6only; - - updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, upstreamIfIndex, null); - mLastIPv6UpstreamIfindex = upstreamIfIndex; - if (mDadProxy != null) { - mDadProxy.setUpstreamIface(upstreamIfaceParams); - } - } - - private void removeRoutesFromLocalNetwork(@NonNull final List<RouteInfo> toBeRemoved) { - final int removalFailures = RouteUtils.removeRoutesFromLocalNetwork( - mNetd, toBeRemoved); - if (removalFailures > 0) { - mLog.e(String.format("Failed to remove %d IPv6 routes from local table.", - removalFailures)); - } - - for (RouteInfo route : toBeRemoved) mLinkProperties.removeRoute(route); - } - - private void addRoutesToLocalNetwork(@NonNull final List<RouteInfo> toBeAdded) { - try { - // It's safe to call networkAddInterface() even if - // the interface is already in the local_network. - mNetd.networkAddInterface(INetd.LOCAL_NET_ID, mIfaceName); - try { - // Add routes from local network. Note that adding routes that - // already exist does not cause an error (EEXIST is silently ignored). - RouteUtils.addRoutesToLocalNetwork(mNetd, mIfaceName, toBeAdded); - } catch (IllegalStateException e) { - mLog.e("Failed to add IPv4/v6 routes to local table: " + e); - return; - } - } catch (ServiceSpecificException | RemoteException e) { - mLog.e("Failed to add " + mIfaceName + " to local table: ", e); - return; - } - - for (RouteInfo route : toBeAdded) mLinkProperties.addRoute(route); - } - - private void configureLocalIPv6Routes( - HashSet<IpPrefix> deprecatedPrefixes, HashSet<IpPrefix> newPrefixes) { - // [1] Remove the routes that are deprecated. - if (!deprecatedPrefixes.isEmpty()) { - removeRoutesFromLocalNetwork(getLocalRoutesFor(mIfaceName, deprecatedPrefixes)); - } - - // [2] Add only the routes that have not previously been added. - if (newPrefixes != null && !newPrefixes.isEmpty()) { - HashSet<IpPrefix> addedPrefixes = (HashSet) newPrefixes.clone(); - if (mLastRaParams != null) { - addedPrefixes.removeAll(mLastRaParams.prefixes); - } - - if (!addedPrefixes.isEmpty()) { - addRoutesToLocalNetwork(getLocalRoutesFor(mIfaceName, addedPrefixes)); - } - } - } - - private void configureLocalIPv6Dns( - HashSet<Inet6Address> deprecatedDnses, HashSet<Inet6Address> newDnses) { - // TODO: Is this really necessary? Can we not fail earlier if INetd cannot be located? - if (mNetd == null) { - if (newDnses != null) newDnses.clear(); - mLog.e("No netd service instance available; not setting local IPv6 addresses"); - return; - } - - // [1] Remove deprecated local DNS IP addresses. - if (!deprecatedDnses.isEmpty()) { - for (Inet6Address dns : deprecatedDnses) { - if (!mInterfaceCtrl.removeAddress(dns, RFC7421_PREFIX_LENGTH)) { - mLog.e("Failed to remove local dns IP " + dns); - } - - mLinkProperties.removeLinkAddress(new LinkAddress(dns, RFC7421_PREFIX_LENGTH)); - } - } - - // [2] Add only the local DNS IP addresses that have not previously been added. - if (newDnses != null && !newDnses.isEmpty()) { - final HashSet<Inet6Address> addedDnses = (HashSet) newDnses.clone(); - if (mLastRaParams != null) { - addedDnses.removeAll(mLastRaParams.dnses); - } - - for (Inet6Address dns : addedDnses) { - if (!mInterfaceCtrl.addAddress(dns, RFC7421_PREFIX_LENGTH)) { - mLog.e("Failed to add local dns IP " + dns); - newDnses.remove(dns); - } - - mLinkProperties.addLinkAddress(new LinkAddress(dns, RFC7421_PREFIX_LENGTH)); - } - } - - try { - mNetd.tetherApplyDnsInterfaces(); - } catch (ServiceSpecificException | RemoteException e) { - mLog.e("Failed to update local DNS caching server"); - if (newDnses != null) newDnses.clear(); - } - } - - private void addIpv6ForwardingRule(Ipv6ForwardingRule rule) { - // Theoretically, we don't need this check because IP neighbor monitor doesn't start if BPF - // offload is disabled. Add this check just in case. - // TODO: Perhaps remove this protection check. - if (!mUsingBpfOffload) return; - - mBpfCoordinator.tetherOffloadRuleAdd(this, rule); - } - - private void removeIpv6ForwardingRule(Ipv6ForwardingRule rule) { - // TODO: Perhaps remove this protection check. - // See the related comment in #addIpv6ForwardingRule. - if (!mUsingBpfOffload) return; - - mBpfCoordinator.tetherOffloadRuleRemove(this, rule); - } - - private void clearIpv6ForwardingRules() { - if (!mUsingBpfOffload) return; - - mBpfCoordinator.tetherOffloadRuleClear(this); - } - - private void updateIpv6ForwardingRule(int newIfindex) { - // TODO: Perhaps remove this protection check. - // See the related comment in #addIpv6ForwardingRule. - if (!mUsingBpfOffload) return; - - mBpfCoordinator.tetherOffloadRuleUpdate(this, newIfindex); - } - - // Handles all updates to IPv6 forwarding rules. These can currently change only if the upstream - // changes or if a neighbor event is received. - private void updateIpv6ForwardingRules(int prevUpstreamIfindex, int upstreamIfindex, - NeighborEvent e) { - // If we no longer have an upstream, clear forwarding rules and do nothing else. - if (upstreamIfindex == 0) { - clearIpv6ForwardingRules(); - return; - } - - // If the upstream interface has changed, remove all rules and re-add them with the new - // upstream interface. - if (prevUpstreamIfindex != upstreamIfindex) { - updateIpv6ForwardingRule(upstreamIfindex); - } - - // If we're here to process a NeighborEvent, do so now. - // mInterfaceParams must be non-null or the event would not have arrived. - if (e == null) return; - if (!(e.ip instanceof Inet6Address) || e.ip.isMulticastAddress() - || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) { - return; - } - - // When deleting rules, we still need to pass a non-null MAC, even though it's ignored. - // Do this here instead of in the Ipv6ForwardingRule constructor to ensure that we never - // add rules with a null MAC, only delete them. - MacAddress dstMac = e.isValid() ? e.macAddr : NULL_MAC_ADDRESS; - Ipv6ForwardingRule rule = new Ipv6ForwardingRule(upstreamIfindex, - mInterfaceParams.index, (Inet6Address) e.ip, mInterfaceParams.macAddr, dstMac); - if (e.isValid()) { - addIpv6ForwardingRule(rule); - } else { - removeIpv6ForwardingRule(rule); - } - } - - private void handleNeighborEvent(NeighborEvent e) { - if (mInterfaceParams != null - && mInterfaceParams.index == e.ifindex - && mInterfaceParams.hasMacAddress) { - updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, mLastIPv6UpstreamIfindex, e); - } - } - - private void handleNewPrefixRequest(@NonNull final IpPrefix currentPrefix) { - if (!currentPrefix.contains(mIpv4Address.getAddress()) - || currentPrefix.getPrefixLength() != mIpv4Address.getPrefixLength()) { - Log.e(TAG, "Invalid prefix: " + currentPrefix); - return; - } - - final LinkAddress deprecatedLinkAddress = mIpv4Address; - mIpv4Address = requestIpv4Address(false); - if (mIpv4Address == null) { - mLog.e("Fail to request a new downstream prefix"); - return; - } - final Inet4Address srvAddr = (Inet4Address) mIpv4Address.getAddress(); - - // Add new IPv4 address on the interface. - if (!mInterfaceCtrl.addAddress(srvAddr, currentPrefix.getPrefixLength())) { - mLog.e("Failed to add new IP " + srvAddr); - return; - } - - // Remove deprecated routes from local network. - removeRoutesFromLocalNetwork( - Collections.singletonList(getDirectConnectedRoute(deprecatedLinkAddress))); - mLinkProperties.removeLinkAddress(deprecatedLinkAddress); - - // Add new routes to local network. - addRoutesToLocalNetwork( - Collections.singletonList(getDirectConnectedRoute(mIpv4Address))); - mLinkProperties.addLinkAddress(mIpv4Address); - - // Update local DNS caching server with new IPv4 address, otherwise, dnsmasq doesn't - // listen on the interface configured with new IPv4 address, that results DNS validation - // failure of downstream client even if appropriate routes have been configured. - try { - mNetd.tetherApplyDnsInterfaces(); - } catch (ServiceSpecificException | RemoteException e) { - mLog.e("Failed to update local DNS caching server"); - return; - } - sendLinkProperties(); - - // Notify DHCP server that new prefix/route has been applied on IpServer. - final Inet4Address clientAddr = mStaticIpv4ClientAddr == null ? null : - (Inet4Address) mStaticIpv4ClientAddr.getAddress(); - final DhcpServingParamsParcel params = makeServingParams(srvAddr /* defaultRouter */, - srvAddr /* dnsServer */, mIpv4Address /* serverLinkAddress */, clientAddr); - try { - mDhcpServer.updateParams(params, new OnHandlerStatusCallback() { - @Override - public void callback(int statusCode) { - if (statusCode != STATUS_SUCCESS) { - mLog.e("Error updating DHCP serving params: " + statusCode); - } - } - }); - } catch (RemoteException e) { - mLog.e("Error updating DHCP serving params", e); - } - } - - private byte getHopLimit(String upstreamIface, int adjustTTL) { - try { - int upstreamHopLimit = Integer.parseUnsignedInt( - mNetd.getProcSysNet(INetd.IPV6, INetd.CONF, upstreamIface, "hop_limit")); - upstreamHopLimit = upstreamHopLimit + adjustTTL; - // Cap the hop limit to 255. - return (byte) Integer.min(upstreamHopLimit, 255); - } catch (Exception e) { - mLog.e("Failed to find upstream interface hop limit", e); - } - return RaParams.DEFAULT_HOPLIMIT; - } - - private void setRaParams(RaParams newParams) { - if (mRaDaemon != null) { - final RaParams deprecatedParams = - RaParams.getDeprecatedRaParams(mLastRaParams, newParams); - - configureLocalIPv6Routes(deprecatedParams.prefixes, - (newParams != null) ? newParams.prefixes : null); - - configureLocalIPv6Dns(deprecatedParams.dnses, - (newParams != null) ? newParams.dnses : null); - - mRaDaemon.buildNewRa(deprecatedParams, newParams); - } - - mLastRaParams = newParams; - } - - private void logMessage(State state, int what) { - mLog.log(state.getName() + " got " + sMagicDecoderRing.get(what, Integer.toString(what))); - } - - private void sendInterfaceState(int newInterfaceState) { - mServingMode = newInterfaceState; - mCallback.updateInterfaceState(this, newInterfaceState, mLastError); - sendLinkProperties(); - } - - private void sendLinkProperties() { - mCallback.updateLinkProperties(this, new LinkProperties(mLinkProperties)); - } - - private void resetLinkProperties() { - mLinkProperties.clear(); - mLinkProperties.setInterfaceName(mIfaceName); - } - - private void maybeConfigureStaticIp(final TetheringRequestParcel request) { - // Ignore static address configuration if they are invalid or null. In theory, static - // addresses should not be invalid here because TetheringManager do not allow caller to - // specify invalid static address configuration. - if (request == null || request.localIPv4Address == null - || request.staticClientAddress == null || !checkStaticAddressConfiguration( - request.localIPv4Address, request.staticClientAddress)) { - return; - } - - mStaticIpv4ServerAddr = request.localIPv4Address; - mStaticIpv4ClientAddr = request.staticClientAddress; - } - - class InitialState extends State { - @Override - public void enter() { - sendInterfaceState(STATE_AVAILABLE); - } - - @Override - public boolean processMessage(Message message) { - logMessage(this, message.what); - switch (message.what) { - case CMD_TETHER_REQUESTED: - mLastError = TetheringManager.TETHER_ERROR_NO_ERROR; - switch (message.arg1) { - case STATE_LOCAL_ONLY: - maybeConfigureStaticIp((TetheringRequestParcel) message.obj); - transitionTo(mLocalHotspotState); - break; - case STATE_TETHERED: - maybeConfigureStaticIp((TetheringRequestParcel) message.obj); - transitionTo(mTetheredState); - break; - default: - mLog.e("Invalid tethering interface serving state specified."); - } - break; - case CMD_INTERFACE_DOWN: - transitionTo(mUnavailableState); - break; - case CMD_IPV6_TETHER_UPDATE: - updateUpstreamIPv6LinkProperties((LinkProperties) message.obj, message.arg1); - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - class BaseServingState extends State { - @Override - public void enter() { - if (!startIPv4()) { - mLastError = TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR; - return; - } - - try { - NetdUtils.tetherInterface(mNetd, mIfaceName, asIpPrefix(mIpv4Address)); - } catch (RemoteException | ServiceSpecificException | IllegalStateException e) { - mLog.e("Error Tethering", e); - mLastError = TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR; - return; - } - - if (!startIPv6()) { - mLog.e("Failed to startIPv6"); - // TODO: Make this a fatal error once Bluetooth IPv6 is sorted. - return; - } - } - - @Override - public void exit() { - // Note that at this point, we're leaving the tethered state. We can fail any - // of these operations, but it doesn't really change that we have to try them - // all in sequence. - stopIPv6(); - - try { - NetdUtils.untetherInterface(mNetd, mIfaceName); - } catch (RemoteException | ServiceSpecificException e) { - mLastError = TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR; - mLog.e("Failed to untether interface: " + e); - } - - stopIPv4(); - - resetLinkProperties(); - } - - @Override - public boolean processMessage(Message message) { - logMessage(this, message.what); - switch (message.what) { - case CMD_TETHER_UNREQUESTED: - transitionTo(mInitialState); - if (DBG) Log.d(TAG, "Untethered (unrequested)" + mIfaceName); - break; - case CMD_INTERFACE_DOWN: - transitionTo(mUnavailableState); - if (DBG) Log.d(TAG, "Untethered (ifdown)" + mIfaceName); - break; - case CMD_IPV6_TETHER_UPDATE: - updateUpstreamIPv6LinkProperties((LinkProperties) message.obj, message.arg1); - sendLinkProperties(); - break; - case CMD_IP_FORWARDING_ENABLE_ERROR: - case CMD_IP_FORWARDING_DISABLE_ERROR: - case CMD_START_TETHERING_ERROR: - case CMD_STOP_TETHERING_ERROR: - case CMD_SET_DNS_FORWARDERS_ERROR: - mLastError = TetheringManager.TETHER_ERROR_INTERNAL_ERROR; - transitionTo(mInitialState); - break; - case CMD_NEW_PREFIX_REQUEST: - handleNewPrefixRequest((IpPrefix) message.obj); - break; - case CMD_NOTIFY_PREFIX_CONFLICT: - mLog.i("restart tethering: " + mInterfaceType); - mCallback.requestEnableTethering(mInterfaceType, false /* enabled */); - transitionTo(mWaitingForRestartState); - break; - default: - return false; - } - return true; - } - } - - // Handling errors in BaseServingState.enter() by transitioning is - // problematic because transitioning during a multi-state jump yields - // a Log.wtf(). Ultimately, there should be only one ServingState, - // and forwarding and NAT rules should be handled by a coordinating - // functional element outside of IpServer. - class LocalHotspotState extends BaseServingState { - @Override - public void enter() { - super.enter(); - if (mLastError != TetheringManager.TETHER_ERROR_NO_ERROR) { - transitionTo(mInitialState); - } - - if (DBG) Log.d(TAG, "Local hotspot " + mIfaceName); - sendInterfaceState(STATE_LOCAL_ONLY); - } - - @Override - public boolean processMessage(Message message) { - if (super.processMessage(message)) return true; - - logMessage(this, message.what); - switch (message.what) { - case CMD_TETHER_REQUESTED: - mLog.e("CMD_TETHER_REQUESTED while in local-only hotspot mode."); - break; - case CMD_TETHER_CONNECTION_CHANGED: - // Ignored in local hotspot state. - break; - default: - return false; - } - return true; - } - } - - // Handling errors in BaseServingState.enter() by transitioning is - // problematic because transitioning during a multi-state jump yields - // a Log.wtf(). Ultimately, there should be only one ServingState, - // and forwarding and NAT rules should be handled by a coordinating - // functional element outside of IpServer. - class TetheredState extends BaseServingState { - @Override - public void enter() { - super.enter(); - if (mLastError != TetheringManager.TETHER_ERROR_NO_ERROR) { - transitionTo(mInitialState); - } - - if (DBG) Log.d(TAG, "Tethered " + mIfaceName); - sendInterfaceState(STATE_TETHERED); - } - - @Override - public void exit() { - cleanupUpstream(); - super.exit(); - } - - private void cleanupUpstream() { - if (mUpstreamIfaceSet == null) return; - - for (String ifname : mUpstreamIfaceSet.ifnames) cleanupUpstreamInterface(ifname); - mUpstreamIfaceSet = null; - clearIpv6ForwardingRules(); - } - - private void cleanupUpstreamInterface(String upstreamIface) { - // Note that we don't care about errors here. - // Sometimes interfaces are gone before we get - // to remove their rules, which generates errors. - // Just do the best we can. - try { - mNetd.ipfwdRemoveInterfaceForward(mIfaceName, upstreamIface); - } catch (RemoteException | ServiceSpecificException e) { - mLog.e("Exception in ipfwdRemoveInterfaceForward: " + e.toString()); - } - try { - mNetd.tetherRemoveForward(mIfaceName, upstreamIface); - } catch (RemoteException | ServiceSpecificException e) { - mLog.e("Exception in disableNat: " + e.toString()); - } - } - - @Override - public boolean processMessage(Message message) { - if (super.processMessage(message)) return true; - - logMessage(this, message.what); - switch (message.what) { - case CMD_TETHER_REQUESTED: - mLog.e("CMD_TETHER_REQUESTED while already tethering."); - break; - case CMD_TETHER_CONNECTION_CHANGED: - final InterfaceSet newUpstreamIfaceSet = (InterfaceSet) message.obj; - if (noChangeInUpstreamIfaceSet(newUpstreamIfaceSet)) { - if (VDBG) Log.d(TAG, "Connection changed noop - dropping"); - break; - } - - if (newUpstreamIfaceSet == null) { - cleanupUpstream(); - break; - } - - for (String removed : upstreamInterfacesRemoved(newUpstreamIfaceSet)) { - cleanupUpstreamInterface(removed); - } - - final Set<String> added = upstreamInterfacesAdd(newUpstreamIfaceSet); - // This makes the call to cleanupUpstream() in the error - // path for any interface neatly cleanup all the interfaces. - mUpstreamIfaceSet = newUpstreamIfaceSet; - - for (String ifname : added) { - try { - mNetd.tetherAddForward(mIfaceName, ifname); - mNetd.ipfwdAddInterfaceForward(mIfaceName, ifname); - } catch (RemoteException | ServiceSpecificException e) { - mLog.e("Exception enabling NAT: " + e.toString()); - cleanupUpstream(); - mLastError = TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR; - transitionTo(mInitialState); - return true; - } - } - break; - case CMD_NEIGHBOR_EVENT: - handleNeighborEvent((NeighborEvent) message.obj); - break; - default: - return false; - } - return true; - } - - private boolean noChangeInUpstreamIfaceSet(InterfaceSet newIfaces) { - if (mUpstreamIfaceSet == null && newIfaces == null) return true; - if (mUpstreamIfaceSet != null && newIfaces != null) { - return mUpstreamIfaceSet.equals(newIfaces); - } - return false; - } - - private Set<String> upstreamInterfacesRemoved(InterfaceSet newIfaces) { - if (mUpstreamIfaceSet == null) return new HashSet<>(); - - final HashSet<String> removed = new HashSet<>(mUpstreamIfaceSet.ifnames); - removed.removeAll(newIfaces.ifnames); - return removed; - } - - private Set<String> upstreamInterfacesAdd(InterfaceSet newIfaces) { - final HashSet<String> added = new HashSet<>(newIfaces.ifnames); - if (mUpstreamIfaceSet != null) added.removeAll(mUpstreamIfaceSet.ifnames); - return added; - } - } - - /** - * This state is terminal for the per interface state machine. At this - * point, the tethering main state machine should have removed this interface - * specific state machine from its list of possible recipients of - * tethering requests. The state machine itself will hang around until - * the garbage collector finds it. - */ - class UnavailableState extends State { - @Override - public void enter() { - mIpNeighborMonitor.stop(); - mLastError = TetheringManager.TETHER_ERROR_NO_ERROR; - sendInterfaceState(STATE_UNAVAILABLE); - } - } - - class WaitingForRestartState extends State { - @Override - public boolean processMessage(Message message) { - logMessage(this, message.what); - switch (message.what) { - case CMD_TETHER_UNREQUESTED: - transitionTo(mInitialState); - mLog.i("Untethered (unrequested) and restarting " + mIfaceName); - mCallback.requestEnableTethering(mInterfaceType, true /* enabled */); - break; - case CMD_INTERFACE_DOWN: - transitionTo(mUnavailableState); - mLog.i("Untethered (interface down) and restarting" + mIfaceName); - mCallback.requestEnableTethering(mInterfaceType, true /* enabled */); - break; - default: - return false; - } - return true; - } - } - - // Accumulate routes representing "prefixes to be assigned to the local - // interface", for subsequent modification of local_network routing. - private static ArrayList<RouteInfo> getLocalRoutesFor( - String ifname, HashSet<IpPrefix> prefixes) { - final ArrayList<RouteInfo> localRoutes = new ArrayList<RouteInfo>(); - for (IpPrefix ipp : prefixes) { - localRoutes.add(new RouteInfo(ipp, null, ifname, RTN_UNICAST)); - } - return localRoutes; - } - - // Given a prefix like 2001:db8::/64 return an address like 2001:db8::1. - private static Inet6Address getLocalDnsIpFor(IpPrefix localPrefix) { - final byte[] dnsBytes = localPrefix.getRawAddress(); - dnsBytes[dnsBytes.length - 1] = getRandomSanitizedByte(DOUG_ADAMS, asByte(0), asByte(1)); - try { - return Inet6Address.getByAddress(null, dnsBytes, 0); - } catch (UnknownHostException e) { - Log.wtf(TAG, "Failed to construct Inet6Address from: " + localPrefix); - return null; - } - } - - private static byte getRandomSanitizedByte(byte dflt, byte... excluded) { - final byte random = (byte) (new Random()).nextInt(); - for (int value : excluded) { - if (random == value) return dflt; - } - return random; - } -} diff --git a/packages/Tethering/src/android/net/ip/NeighborPacketForwarder.java b/packages/Tethering/src/android/net/ip/NeighborPacketForwarder.java deleted file mode 100644 index 73fc833fabf5..000000000000 --- a/packages/Tethering/src/android/net/ip/NeighborPacketForwarder.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) 2020 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.ip; - -import static android.system.OsConstants.AF_INET6; -import static android.system.OsConstants.AF_PACKET; -import static android.system.OsConstants.ETH_P_IPV6; -import static android.system.OsConstants.IPPROTO_RAW; -import static android.system.OsConstants.SOCK_DGRAM; -import static android.system.OsConstants.SOCK_NONBLOCK; -import static android.system.OsConstants.SOCK_RAW; - -import android.net.util.InterfaceParams; -import android.net.util.PacketReader; -import android.net.util.SocketUtils; -import android.net.util.TetheringUtils; -import android.os.Handler; -import android.system.ErrnoException; -import android.system.Os; -import android.util.Log; - -import java.io.FileDescriptor; -import java.io.IOException; -import java.net.Inet6Address; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.util.Arrays; - -/** - * Basic IPv6 Neighbor Advertisement Forwarder. - * - * Forward NA packets from upstream iface to tethered iface - * and NS packets from tethered iface to upstream iface. - * - * @hide - */ -public class NeighborPacketForwarder extends PacketReader { - private final String mTag; - - private FileDescriptor mFd; - - // TODO: get these from NetworkStackConstants. - private static final int IPV6_ADDR_LEN = 16; - private static final int IPV6_DST_ADDR_OFFSET = 24; - private static final int IPV6_HEADER_LEN = 40; - private static final int ETH_HEADER_LEN = 14; - - private InterfaceParams mListenIfaceParams, mSendIfaceParams; - - private final int mType; - public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136; - public static final int ICMPV6_NEIGHBOR_SOLICITATION = 135; - - public NeighborPacketForwarder(Handler h, InterfaceParams tetheredInterface, int type) { - super(h); - mTag = NeighborPacketForwarder.class.getSimpleName() + "-" - + tetheredInterface.name + "-" + type; - mType = type; - - if (mType == ICMPV6_NEIGHBOR_ADVERTISEMENT) { - mSendIfaceParams = tetheredInterface; - } else { - mListenIfaceParams = tetheredInterface; - } - } - - /** Set new upstream iface and start/stop based on new params. */ - public void setUpstreamIface(InterfaceParams upstreamParams) { - final InterfaceParams oldUpstreamParams; - - if (mType == ICMPV6_NEIGHBOR_ADVERTISEMENT) { - oldUpstreamParams = mListenIfaceParams; - mListenIfaceParams = upstreamParams; - } else { - oldUpstreamParams = mSendIfaceParams; - mSendIfaceParams = upstreamParams; - } - - if (oldUpstreamParams == null && upstreamParams != null) { - start(); - } else if (oldUpstreamParams != null && upstreamParams == null) { - stop(); - } else if (oldUpstreamParams != null && upstreamParams != null - && oldUpstreamParams.index != upstreamParams.index) { - stop(); - start(); - } - } - - // TODO: move NetworkStackUtils.closeSocketQuietly to - // frameworks/libs/net/common/device/com/android/net/module/util/[someclass]. - private void closeSocketQuietly(FileDescriptor fd) { - try { - SocketUtils.closeSocket(fd); - } catch (IOException ignored) { - } - } - - @Override - protected FileDescriptor createFd() { - try { - // ICMPv6 packets from modem do not have eth header, so RAW socket cannot be used. - // To keep uniformity in both directions PACKET socket can be used. - mFd = Os.socket(AF_PACKET, SOCK_DGRAM | SOCK_NONBLOCK, 0); - - // TODO: convert setup*Socket to setupICMPv6BpfFilter with filter type? - if (mType == ICMPV6_NEIGHBOR_ADVERTISEMENT) { - TetheringUtils.setupNaSocket(mFd); - } else if (mType == ICMPV6_NEIGHBOR_SOLICITATION) { - TetheringUtils.setupNsSocket(mFd); - } - - SocketAddress bindAddress = SocketUtils.makePacketSocketAddress( - ETH_P_IPV6, mListenIfaceParams.index); - Os.bind(mFd, bindAddress); - } catch (ErrnoException | SocketException e) { - Log.wtf(mTag, "Failed to create socket", e); - closeSocketQuietly(mFd); - return null; - } - - return mFd; - } - - private Inet6Address getIpv6DestinationAddress(byte[] recvbuf) { - Inet6Address dstAddr; - try { - dstAddr = (Inet6Address) Inet6Address.getByAddress(Arrays.copyOfRange(recvbuf, - IPV6_DST_ADDR_OFFSET, IPV6_DST_ADDR_OFFSET + IPV6_ADDR_LEN)); - } catch (UnknownHostException | ClassCastException impossible) { - throw new AssertionError("16-byte array not valid IPv6 address?"); - } - return dstAddr; - } - - @Override - protected void handlePacket(byte[] recvbuf, int length) { - if (mSendIfaceParams == null) { - return; - } - - // The BPF filter should already have checked the length of the packet, but... - if (length < IPV6_HEADER_LEN) { - return; - } - Inet6Address destv6 = getIpv6DestinationAddress(recvbuf); - if (!destv6.isMulticastAddress()) { - return; - } - InetSocketAddress dest = new InetSocketAddress(destv6, 0); - - FileDescriptor fd = null; - try { - fd = Os.socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_RAW); - SocketUtils.bindSocketToInterface(fd, mSendIfaceParams.name); - - int ret = Os.sendto(fd, recvbuf, 0, length, 0, dest); - } catch (ErrnoException | SocketException e) { - Log.e(mTag, "handlePacket error: " + e); - } finally { - closeSocketQuietly(fd); - } - } -} diff --git a/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java deleted file mode 100644 index 7c0b7cc7515c..000000000000 --- a/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java +++ /dev/null @@ -1,744 +0,0 @@ -/* - * Copyright (C) 2016 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.ip; - -import static android.net.util.NetworkConstants.IPV6_MIN_MTU; -import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH; -import static android.net.util.TetheringUtils.getAllNodesForScopeId; -import static android.system.OsConstants.AF_INET6; -import static android.system.OsConstants.IPPROTO_ICMPV6; -import static android.system.OsConstants.SOCK_RAW; -import static android.system.OsConstants.SOL_SOCKET; -import static android.system.OsConstants.SO_SNDTIMEO; - -import android.net.IpPrefix; -import android.net.LinkAddress; -import android.net.TrafficStats; -import android.net.util.InterfaceParams; -import android.net.util.SocketUtils; -import android.net.util.TetheringUtils; -import android.system.ErrnoException; -import android.system.Os; -import android.system.StructTimeval; -import android.util.Log; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.TrafficStatsConstants; - -import java.io.FileDescriptor; -import java.io.IOException; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.SocketException; -import java.nio.BufferOverflowException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Random; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; - - -/** - * Basic IPv6 Router Advertisement Daemon. - * - * TODO: - * - * - Rewrite using Handler (and friends) so that AlarmManager can deliver - * "kick" messages when it's time to send a multicast RA. - * - * @hide - */ -public class RouterAdvertisementDaemon { - private static final String TAG = RouterAdvertisementDaemon.class.getSimpleName(); - private static final byte ICMPV6_ND_ROUTER_SOLICIT = asByte(133); - private static final byte ICMPV6_ND_ROUTER_ADVERT = asByte(134); - private static final int MIN_RA_HEADER_SIZE = 16; - - // Summary of various timers and lifetimes. - private static final int MIN_RTR_ADV_INTERVAL_SEC = 300; - private static final int MAX_RTR_ADV_INTERVAL_SEC = 600; - // In general, router, prefix, and DNS lifetimes are all advised to be - // greater than or equal to 3 * MAX_RTR_ADV_INTERVAL. Here, we double - // that to allow for multicast packet loss. - // - // This MAX_RTR_ADV_INTERVAL_SEC and DEFAULT_LIFETIME are also consistent - // with the https://tools.ietf.org/html/rfc7772#section-4 discussion of - // "approximately 7 RAs per hour". - private static final int DEFAULT_LIFETIME = 6 * MAX_RTR_ADV_INTERVAL_SEC; - // From https://tools.ietf.org/html/rfc4861#section-10 . - private static final int MIN_DELAY_BETWEEN_RAS_SEC = 3; - // Both initial and final RAs, but also for changes in RA contents. - // From https://tools.ietf.org/html/rfc4861#section-10 . - private static final int MAX_URGENT_RTR_ADVERTISEMENTS = 5; - - private static final int DAY_IN_SECONDS = 86_400; - - private final InterfaceParams mInterface; - private final InetSocketAddress mAllNodes; - - // This lock is to protect the RA from being updated while being - // transmitted on another thread (multicast or unicast). - // - // TODO: This should be handled with a more RCU-like approach. - private final Object mLock = new Object(); - @GuardedBy("mLock") - private final byte[] mRA = new byte[IPV6_MIN_MTU]; - @GuardedBy("mLock") - private int mRaLength; - @GuardedBy("mLock") - private final DeprecatedInfoTracker mDeprecatedInfoTracker; - @GuardedBy("mLock") - private RaParams mRaParams; - - private volatile FileDescriptor mSocket; - private volatile MulticastTransmitter mMulticastTransmitter; - private volatile UnicastResponder mUnicastResponder; - - /** Encapsulate the RA parameters for RouterAdvertisementDaemon.*/ - public static class RaParams { - // Tethered traffic will have the hop limit properly decremented. - // Consequently, set the hoplimit greater by one than the upstream - // unicast hop limit. - // - // TODO: Dynamically pass down the IPV6_UNICAST_HOPS value from the - // upstream interface for more correct behaviour. - static final byte DEFAULT_HOPLIMIT = 65; - - public boolean hasDefaultRoute; - public byte hopLimit; - public int mtu; - public HashSet<IpPrefix> prefixes; - public HashSet<Inet6Address> dnses; - - public RaParams() { - hasDefaultRoute = false; - hopLimit = DEFAULT_HOPLIMIT; - mtu = IPV6_MIN_MTU; - prefixes = new HashSet<IpPrefix>(); - dnses = new HashSet<Inet6Address>(); - } - - public RaParams(RaParams other) { - hasDefaultRoute = other.hasDefaultRoute; - hopLimit = other.hopLimit; - mtu = other.mtu; - prefixes = (HashSet) other.prefixes.clone(); - dnses = (HashSet) other.dnses.clone(); - } - - /** - * Returns the subset of RA parameters that become deprecated when - * moving from announcing oldRa to announcing newRa. - * - * Currently only tracks differences in |prefixes| and |dnses|. - */ - public static RaParams getDeprecatedRaParams(RaParams oldRa, RaParams newRa) { - RaParams newlyDeprecated = new RaParams(); - - if (oldRa != null) { - for (IpPrefix ipp : oldRa.prefixes) { - if (newRa == null || !newRa.prefixes.contains(ipp)) { - newlyDeprecated.prefixes.add(ipp); - } - } - - for (Inet6Address dns : oldRa.dnses) { - if (newRa == null || !newRa.dnses.contains(dns)) { - newlyDeprecated.dnses.add(dns); - } - } - } - - return newlyDeprecated; - } - } - - private static class DeprecatedInfoTracker { - private final HashMap<IpPrefix, Integer> mPrefixes = new HashMap<>(); - private final HashMap<Inet6Address, Integer> mDnses = new HashMap<>(); - - Set<IpPrefix> getPrefixes() { - return mPrefixes.keySet(); - } - - void putPrefixes(Set<IpPrefix> prefixes) { - for (IpPrefix ipp : prefixes) { - mPrefixes.put(ipp, MAX_URGENT_RTR_ADVERTISEMENTS); - } - } - - void removePrefixes(Set<IpPrefix> prefixes) { - for (IpPrefix ipp : prefixes) { - mPrefixes.remove(ipp); - } - } - - Set<Inet6Address> getDnses() { - return mDnses.keySet(); - } - - void putDnses(Set<Inet6Address> dnses) { - for (Inet6Address dns : dnses) { - mDnses.put(dns, MAX_URGENT_RTR_ADVERTISEMENTS); - } - } - - void removeDnses(Set<Inet6Address> dnses) { - for (Inet6Address dns : dnses) { - mDnses.remove(dns); - } - } - - boolean isEmpty() { - return mPrefixes.isEmpty() && mDnses.isEmpty(); - } - - private boolean decrementCounters() { - boolean removed = decrementCounter(mPrefixes); - removed |= decrementCounter(mDnses); - return removed; - } - - private <T> boolean decrementCounter(HashMap<T, Integer> map) { - boolean removed = false; - - for (Iterator<Map.Entry<T, Integer>> it = map.entrySet().iterator(); - it.hasNext();) { - Map.Entry<T, Integer> kv = it.next(); - if (kv.getValue() == 0) { - it.remove(); - removed = true; - } else { - kv.setValue(kv.getValue() - 1); - } - } - - return removed; - } - } - - public RouterAdvertisementDaemon(InterfaceParams ifParams) { - mInterface = ifParams; - mAllNodes = new InetSocketAddress(getAllNodesForScopeId(mInterface.index), 0); - mDeprecatedInfoTracker = new DeprecatedInfoTracker(); - } - - /** Build new RA.*/ - public void buildNewRa(RaParams deprecatedParams, RaParams newParams) { - synchronized (mLock) { - if (deprecatedParams != null) { - mDeprecatedInfoTracker.putPrefixes(deprecatedParams.prefixes); - mDeprecatedInfoTracker.putDnses(deprecatedParams.dnses); - } - - if (newParams != null) { - // Process information that is no longer deprecated. - mDeprecatedInfoTracker.removePrefixes(newParams.prefixes); - mDeprecatedInfoTracker.removeDnses(newParams.dnses); - } - - mRaParams = newParams; - assembleRaLocked(); - } - - maybeNotifyMulticastTransmitter(); - } - - /** Start router advertisement daemon. */ - public boolean start() { - if (!createSocket()) { - return false; - } - - mMulticastTransmitter = new MulticastTransmitter(); - mMulticastTransmitter.start(); - - mUnicastResponder = new UnicastResponder(); - mUnicastResponder.start(); - - return true; - } - - /** Stop router advertisement daemon. */ - public void stop() { - closeSocket(); - // Wake up mMulticastTransmitter thread to interrupt a potential 1 day sleep before - // the thread's termination. - maybeNotifyMulticastTransmitter(); - mMulticastTransmitter = null; - mUnicastResponder = null; - } - - @GuardedBy("mLock") - private void assembleRaLocked() { - final ByteBuffer ra = ByteBuffer.wrap(mRA); - ra.order(ByteOrder.BIG_ENDIAN); - - final boolean haveRaParams = (mRaParams != null); - boolean shouldSendRA = false; - - try { - putHeader(ra, haveRaParams && mRaParams.hasDefaultRoute, - haveRaParams ? mRaParams.hopLimit : RaParams.DEFAULT_HOPLIMIT); - putSlla(ra, mInterface.macAddr.toByteArray()); - mRaLength = ra.position(); - - // https://tools.ietf.org/html/rfc5175#section-4 says: - // - // "MUST NOT be added to a Router Advertisement message - // if no flags in the option are set." - // - // putExpandedFlagsOption(ra); - - if (haveRaParams) { - putMtu(ra, mRaParams.mtu); - mRaLength = ra.position(); - - for (IpPrefix ipp : mRaParams.prefixes) { - putPio(ra, ipp, DEFAULT_LIFETIME, DEFAULT_LIFETIME); - mRaLength = ra.position(); - shouldSendRA = true; - } - - if (mRaParams.dnses.size() > 0) { - putRdnss(ra, mRaParams.dnses, DEFAULT_LIFETIME); - mRaLength = ra.position(); - shouldSendRA = true; - } - } - - for (IpPrefix ipp : mDeprecatedInfoTracker.getPrefixes()) { - putPio(ra, ipp, 0, 0); - mRaLength = ra.position(); - shouldSendRA = true; - } - - final Set<Inet6Address> deprecatedDnses = mDeprecatedInfoTracker.getDnses(); - if (!deprecatedDnses.isEmpty()) { - putRdnss(ra, deprecatedDnses, 0); - mRaLength = ra.position(); - shouldSendRA = true; - } - } catch (BufferOverflowException e) { - // The packet up to mRaLength is valid, since it has been updated - // progressively as the RA was built. Log an error, and continue - // on as best as possible. - Log.e(TAG, "Could not construct new RA: " + e); - } - - // We have nothing worth announcing; indicate as much to maybeSendRA(). - if (!shouldSendRA) { - mRaLength = 0; - } - } - - private void maybeNotifyMulticastTransmitter() { - final MulticastTransmitter m = mMulticastTransmitter; - if (m != null) { - m.hup(); - } - } - - private static byte asByte(int value) { - return (byte) value; - } - private static short asShort(int value) { - return (short) value; - } - - private static void putHeader(ByteBuffer ra, boolean hasDefaultRoute, byte hopLimit) { - /** - Router Advertisement Message Format - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type | Code | Checksum | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Cur Hop Limit |M|O|H|Prf|P|R|R| Router Lifetime | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Reachable Time | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Retrans Timer | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Options ... - +-+-+-+-+-+-+-+-+-+-+-+- - */ - ra.put(ICMPV6_ND_ROUTER_ADVERT) - .put(asByte(0)) - .putShort(asShort(0)) - .put(hopLimit) - // RFC 4191 "high" preference, iff. advertising a default route. - .put(hasDefaultRoute ? asByte(0x08) : asByte(0)) - .putShort(hasDefaultRoute ? asShort(DEFAULT_LIFETIME) : asShort(0)) - .putInt(0) - .putInt(0); - } - - private static void putSlla(ByteBuffer ra, byte[] slla) { - /** - Source/Target Link-layer Address - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type | Length | Link-Layer Address ... - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ - if (slla == null || slla.length != 6) { - // Only IEEE 802.3 6-byte addresses are supported. - return; - } - - final byte nd_option_slla = 1; - final byte slla_num_8octets = 1; - ra.put(nd_option_slla) - .put(slla_num_8octets) - .put(slla); - } - - private static void putExpandedFlagsOption(ByteBuffer ra) { - /** - Router Advertisement Expanded Flags Option - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type | Length | Bit fields available .. - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - ... for assignment | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ - - final byte nd_option__efo = 26; - final byte efo_num_8octets = 1; - - ra.put(nd_option__efo) - .put(efo_num_8octets) - .putShort(asShort(0)) - .putInt(0); - } - - private static void putMtu(ByteBuffer ra, int mtu) { - /** - MTU - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type | Length | Reserved | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | MTU | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ - final byte nd_option_mtu = 5; - final byte mtu_num_8octs = 1; - ra.put(nd_option_mtu) - .put(mtu_num_8octs) - .putShort(asShort(0)) - .putInt((mtu < IPV6_MIN_MTU) ? IPV6_MIN_MTU : mtu); - } - - private static void putPio(ByteBuffer ra, IpPrefix ipp, - int validTime, int preferredTime) { - /** - Prefix Information - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type | Length | Prefix Length |L|A| Reserved1 | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Valid Lifetime | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Preferred Lifetime | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Reserved2 | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | | - + + - | | - + Prefix + - | | - + + - | | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ - final int prefixLength = ipp.getPrefixLength(); - if (prefixLength != 64) { - return; - } - final byte nd_option_pio = 3; - final byte pio_num_8octets = 4; - - if (validTime < 0) validTime = 0; - if (preferredTime < 0) preferredTime = 0; - if (preferredTime > validTime) preferredTime = validTime; - - final byte[] addr = ipp.getAddress().getAddress(); - ra.put(nd_option_pio) - .put(pio_num_8octets) - .put(asByte(prefixLength)) - .put(asByte(0xc0)) /* L & A set */ - .putInt(validTime) - .putInt(preferredTime) - .putInt(0) - .put(addr); - } - - private static void putRio(ByteBuffer ra, IpPrefix ipp) { - /** - Route Information Option - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type | Length | Prefix Length |Resvd|Prf|Resvd| - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Route Lifetime | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Prefix (Variable Length) | - . . - . . - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ - final int prefixLength = ipp.getPrefixLength(); - if (prefixLength > 64) { - return; - } - final byte nd_option_rio = 24; - final byte rio_num_8octets = asByte( - (prefixLength == 0) ? 1 : (prefixLength <= 8) ? 2 : 3); - - final byte[] addr = ipp.getAddress().getAddress(); - ra.put(nd_option_rio) - .put(rio_num_8octets) - .put(asByte(prefixLength)) - .put(asByte(0x18)) - .putInt(DEFAULT_LIFETIME); - - // Rely upon an IpPrefix's address being properly zeroed. - if (prefixLength > 0) { - ra.put(addr, 0, (prefixLength <= 64) ? 8 : 16); - } - } - - private static void putRdnss(ByteBuffer ra, Set<Inet6Address> dnses, int lifetime) { - /** - Recursive DNS Server (RDNSS) Option - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type | Length | Reserved | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Lifetime | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | | - : Addresses of IPv6 Recursive DNS Servers : - | | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ - - final HashSet<Inet6Address> filteredDnses = new HashSet<>(); - for (Inet6Address dns : dnses) { - if ((new LinkAddress(dns, RFC7421_PREFIX_LENGTH)).isGlobalPreferred()) { - filteredDnses.add(dns); - } - } - if (filteredDnses.isEmpty()) return; - - final byte nd_option_rdnss = 25; - final byte rdnss_num_8octets = asByte(dnses.size() * 2 + 1); - ra.put(nd_option_rdnss) - .put(rdnss_num_8octets) - .putShort(asShort(0)) - .putInt(lifetime); - - for (Inet6Address dns : filteredDnses) { - // NOTE: If the full of list DNS servers doesn't fit in the packet, - // this code will cause a buffer overflow and the RA won't include - // this instance of the option at all. - // - // TODO: Consider looking at ra.remaining() to determine how many - // DNS servers will fit, and adding only those. - ra.put(dns.getAddress()); - } - } - - private boolean createSocket() { - final int send_timout_ms = 300; - - final int oldTag = TrafficStats.getAndSetThreadStatsTag( - TrafficStatsConstants.TAG_SYSTEM_NEIGHBOR); - try { - mSocket = Os.socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); - // Setting SNDTIMEO is purely for defensive purposes. - Os.setsockoptTimeval( - mSocket, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(send_timout_ms)); - SocketUtils.bindSocketToInterface(mSocket, mInterface.name); - TetheringUtils.setupRaSocket(mSocket, mInterface.index); - } catch (ErrnoException | IOException e) { - Log.e(TAG, "Failed to create RA daemon socket: " + e); - return false; - } finally { - TrafficStats.setThreadStatsTag(oldTag); - } - - return true; - } - - private void closeSocket() { - if (mSocket != null) { - try { - SocketUtils.closeSocket(mSocket); - } catch (IOException ignored) { } - } - mSocket = null; - } - - private boolean isSocketValid() { - final FileDescriptor s = mSocket; - return (s != null) && s.valid(); - } - - private boolean isSuitableDestination(InetSocketAddress dest) { - if (mAllNodes.equals(dest)) { - return true; - } - - final InetAddress destip = dest.getAddress(); - return (destip instanceof Inet6Address) - && destip.isLinkLocalAddress() - && (((Inet6Address) destip).getScopeId() == mInterface.index); - } - - private void maybeSendRA(InetSocketAddress dest) { - if (dest == null || !isSuitableDestination(dest)) { - dest = mAllNodes; - } - - try { - synchronized (mLock) { - if (mRaLength < MIN_RA_HEADER_SIZE) { - // No actual RA to send. - return; - } - Os.sendto(mSocket, mRA, 0, mRaLength, 0, dest); - } - Log.d(TAG, "RA sendto " + dest.getAddress().getHostAddress()); - } catch (ErrnoException | SocketException e) { - if (isSocketValid()) { - Log.e(TAG, "sendto error: " + e); - } - } - } - - private final class UnicastResponder extends Thread { - private final InetSocketAddress mSolicitor = new InetSocketAddress(0); - // The recycled buffer for receiving Router Solicitations from clients. - // If the RS is larger than IPV6_MIN_MTU the packets are truncated. - // This is fine since currently only byte 0 is examined anyway. - private final byte[] mSolicitation = new byte[IPV6_MIN_MTU]; - - @Override - public void run() { - while (isSocketValid()) { - try { - // Blocking receive. - final int rval = Os.recvfrom( - mSocket, mSolicitation, 0, mSolicitation.length, 0, mSolicitor); - // Do the least possible amount of validation. - if (rval < 1 || mSolicitation[0] != ICMPV6_ND_ROUTER_SOLICIT) { - continue; - } - } catch (ErrnoException | SocketException e) { - if (isSocketValid()) { - Log.e(TAG, "recvfrom error: " + e); - } - continue; - } - - maybeSendRA(mSolicitor); - } - } - } - - // TODO: Consider moving this to run on a provided Looper as a Handler, - // with WakeupMessage-style messages providing the timer driven input. - private final class MulticastTransmitter extends Thread { - private final Random mRandom = new Random(); - private final AtomicInteger mUrgentAnnouncements = new AtomicInteger(0); - - @Override - public void run() { - while (isSocketValid()) { - try { - Thread.sleep(getNextMulticastTransmitDelayMs()); - } catch (InterruptedException ignored) { - // Stop sleeping, immediately send an RA, and continue. - } - - maybeSendRA(mAllNodes); - synchronized (mLock) { - if (mDeprecatedInfoTracker.decrementCounters()) { - // At least one deprecated PIO has been removed; - // reassemble the RA. - assembleRaLocked(); - } - } - } - } - - public void hup() { - // Set to one fewer that the desired number, because as soon as - // the thread interrupt is processed we immediately send an RA - // and mUrgentAnnouncements is not examined until the subsequent - // sleep interval computation (i.e. this way we send 3 and not 4). - mUrgentAnnouncements.set(MAX_URGENT_RTR_ADVERTISEMENTS - 1); - interrupt(); - } - - private int getNextMulticastTransmitDelaySec() { - boolean deprecationInProgress = false; - synchronized (mLock) { - if (mRaLength < MIN_RA_HEADER_SIZE) { - // No actual RA to send; just sleep for 1 day. - return DAY_IN_SECONDS; - } - deprecationInProgress = !mDeprecatedInfoTracker.isEmpty(); - } - - final int urgentPending = mUrgentAnnouncements.getAndDecrement(); - if ((urgentPending > 0) || deprecationInProgress) { - return MIN_DELAY_BETWEEN_RAS_SEC; - } - - return MIN_RTR_ADV_INTERVAL_SEC + mRandom.nextInt( - MAX_RTR_ADV_INTERVAL_SEC - MIN_RTR_ADV_INTERVAL_SEC); - } - - private long getNextMulticastTransmitDelayMs() { - return 1000 * (long) getNextMulticastTransmitDelaySec(); - } - } -} diff --git a/packages/Tethering/src/android/net/util/BaseNetdUnsolicitedEventListener.java b/packages/Tethering/src/android/net/util/BaseNetdUnsolicitedEventListener.java deleted file mode 100644 index b1ffdb01f5f3..000000000000 --- a/packages/Tethering/src/android/net/util/BaseNetdUnsolicitedEventListener.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.net.util; - -import android.net.INetdUnsolicitedEventListener; - -import androidx.annotation.NonNull; - -/** - * Base {@link INetdUnsolicitedEventListener} that provides no-op implementations which can be - * overridden. - */ -public class BaseNetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub { - - @Override - public void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, - int uid) { } - - @Override - public void onQuotaLimitReached(@NonNull String alertName, @NonNull String ifName) { } - - @Override - public void onInterfaceDnsServerInfo(@NonNull String ifName, long lifetimeS, - @NonNull String[] servers) { } - - @Override - public void onInterfaceAddressUpdated(@NonNull String addr, String ifName, int flags, - int scope) { } - - @Override - public void onInterfaceAddressRemoved(@NonNull String addr, @NonNull String ifName, int flags, - int scope) { } - - @Override - public void onInterfaceAdded(@NonNull String ifName) { } - - @Override - public void onInterfaceRemoved(@NonNull String ifName) { } - - @Override - public void onInterfaceChanged(@NonNull String ifName, boolean up) { } - - @Override - public void onInterfaceLinkStateChanged(@NonNull String ifName, boolean up) { } - - @Override - public void onRouteChanged(boolean updated, @NonNull String route, @NonNull String gateway, - @NonNull String ifName) { } - - @Override - public void onStrictCleartextDetected(int uid, @NonNull String hex) { } - - @Override - public int getInterfaceVersion() { - return INetdUnsolicitedEventListener.VERSION; - } - - @Override - public String getInterfaceHash() { - return INetdUnsolicitedEventListener.HASH; - } -} diff --git a/packages/Tethering/src/android/net/util/InterfaceSet.java b/packages/Tethering/src/android/net/util/InterfaceSet.java deleted file mode 100644 index 758978711bd4..000000000000 --- a/packages/Tethering/src/android/net/util/InterfaceSet.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net.util; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.StringJoiner; - - -/** - * @hide - */ -public class InterfaceSet { - public final Set<String> ifnames; - - public InterfaceSet(String... names) { - final Set<String> nameSet = new HashSet<>(); - for (String name : names) { - if (name != null) nameSet.add(name); - } - ifnames = Collections.unmodifiableSet(nameSet); - } - - @Override - public String toString() { - final StringJoiner sj = new StringJoiner(",", "[", "]"); - for (String ifname : ifnames) sj.add(ifname); - return sj.toString(); - } - - @Override - public boolean equals(Object obj) { - return obj != null - && obj instanceof InterfaceSet - && ifnames.equals(((InterfaceSet) obj).ifnames); - } -} diff --git a/packages/Tethering/src/android/net/util/PrefixUtils.java b/packages/Tethering/src/android/net/util/PrefixUtils.java deleted file mode 100644 index f203e9995f3d..000000000000 --- a/packages/Tethering/src/android/net/util/PrefixUtils.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2017 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.util; - -import android.net.IpPrefix; -import android.net.LinkAddress; -import android.net.LinkProperties; - -import java.net.Inet4Address; -import java.net.InetAddress; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - - -/** - * @hide - */ -public class PrefixUtils { - private static final IpPrefix[] MIN_NON_FORWARDABLE_PREFIXES = { - pfx("127.0.0.0/8"), // IPv4 loopback - pfx("169.254.0.0/16"), // IPv4 link-local, RFC3927#section-8 - pfx("::/3"), - pfx("fe80::/64"), // IPv6 link-local - pfx("fc00::/7"), // IPv6 ULA - pfx("ff02::/8"), // IPv6 link-local multicast - }; - - public static final IpPrefix DEFAULT_WIFI_P2P_PREFIX = pfx("192.168.49.0/24"); - - /** Get non forwardable prefixes. */ - public static Set<IpPrefix> getNonForwardablePrefixes() { - final HashSet<IpPrefix> prefixes = new HashSet<>(); - addNonForwardablePrefixes(prefixes); - return prefixes; - } - - /** Add non forwardable prefixes. */ - public static void addNonForwardablePrefixes(Set<IpPrefix> prefixes) { - Collections.addAll(prefixes, MIN_NON_FORWARDABLE_PREFIXES); - } - - /** Get local prefixes from |lp|. */ - public static Set<IpPrefix> localPrefixesFrom(LinkProperties lp) { - final HashSet<IpPrefix> localPrefixes = new HashSet<>(); - if (lp == null) return localPrefixes; - - for (LinkAddress addr : lp.getAllLinkAddresses()) { - if (addr.getAddress().isLinkLocalAddress()) continue; - localPrefixes.add(asIpPrefix(addr)); - } - // TODO: Add directly-connected routes as well (ones from which we did - // not also form a LinkAddress)? - - return localPrefixes; - } - - /** Convert LinkAddress |addr| to IpPrefix. */ - public static IpPrefix asIpPrefix(LinkAddress addr) { - return new IpPrefix(addr.getAddress(), addr.getPrefixLength()); - } - - /** Convert InetAddress |ip| to IpPrefix. */ - public static IpPrefix ipAddressAsPrefix(InetAddress ip) { - final int bitLength = (ip instanceof Inet4Address) - ? NetworkConstants.IPV4_ADDR_BITS - : NetworkConstants.IPV6_ADDR_BITS; - return new IpPrefix(ip, bitLength); - } - - private static IpPrefix pfx(String prefixStr) { - return new IpPrefix(prefixStr); - } -} diff --git a/packages/Tethering/src/android/net/util/TetheringMessageBase.java b/packages/Tethering/src/android/net/util/TetheringMessageBase.java deleted file mode 100644 index 29c0a817b6f4..000000000000 --- a/packages/Tethering/src/android/net/util/TetheringMessageBase.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2020 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.util; - -/** - * This class defines Message.what base addresses for various state machine. - */ -public class TetheringMessageBase { - public static final int BASE_MAIN_SM = 0; - public static final int BASE_IPSERVER = 100; - -} diff --git a/packages/Tethering/src/android/net/util/TetheringUtils.java b/packages/Tethering/src/android/net/util/TetheringUtils.java deleted file mode 100644 index 53b54f7de05d..000000000000 --- a/packages/Tethering/src/android/net/util/TetheringUtils.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.net.util; - -import android.net.TetherStatsParcel; -import android.net.TetheringRequestParcel; -import android.util.Log; - -import androidx.annotation.NonNull; - -import java.io.FileDescriptor; -import java.net.Inet6Address; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.util.Arrays; -import java.util.Objects; - -/** - * The classes and the methods for tethering utilization. - * - * {@hide} - */ -public class TetheringUtils { - public static final byte[] ALL_NODES = new byte[] { - (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 - }; - - /** - * Configures a socket for receiving and sending ICMPv6 neighbor advertisments. - * @param fd the socket's {@link FileDescriptor}. - */ - public static native void setupNaSocket(FileDescriptor fd) - throws SocketException; - - /** - * Configures a socket for receiving and sending ICMPv6 neighbor solicitations. - * @param fd the socket's {@link FileDescriptor}. - */ - public static native void setupNsSocket(FileDescriptor fd) - throws SocketException; - - /** - * The object which records offload Tx/Rx forwarded bytes/packets. - * TODO: Replace the inner class ForwardedStats of class OffloadHardwareInterface with - * this class as well. - */ - public static class ForwardedStats { - public final long rxBytes; - public final long rxPackets; - public final long txBytes; - public final long txPackets; - - public ForwardedStats() { - rxBytes = 0; - rxPackets = 0; - txBytes = 0; - txPackets = 0; - } - - public ForwardedStats(long rxBytes, long txBytes) { - this.rxBytes = rxBytes; - this.rxPackets = 0; - this.txBytes = txBytes; - this.txPackets = 0; - } - - public ForwardedStats(long rxBytes, long rxPackets, long txBytes, long txPackets) { - this.rxBytes = rxBytes; - this.rxPackets = rxPackets; - this.txBytes = txBytes; - this.txPackets = txPackets; - } - - public ForwardedStats(@NonNull TetherStatsParcel tetherStats) { - rxBytes = tetherStats.rxBytes; - rxPackets = tetherStats.rxPackets; - txBytes = tetherStats.txBytes; - txPackets = tetherStats.txPackets; - } - - public ForwardedStats(@NonNull ForwardedStats other) { - rxBytes = other.rxBytes; - rxPackets = other.rxPackets; - txBytes = other.txBytes; - txPackets = other.txPackets; - } - - /** Add Tx/Rx bytes/packets and return the result as a new object. */ - @NonNull - public ForwardedStats add(@NonNull ForwardedStats other) { - return new ForwardedStats(rxBytes + other.rxBytes, rxPackets + other.rxPackets, - txBytes + other.txBytes, txPackets + other.txPackets); - } - - /** Subtract Tx/Rx bytes/packets and return the result as a new object. */ - @NonNull - public ForwardedStats subtract(@NonNull ForwardedStats other) { - // TODO: Perhaps throw an exception if any negative difference value just in case. - final long rxBytesDiff = Math.max(rxBytes - other.rxBytes, 0); - final long rxPacketsDiff = Math.max(rxPackets - other.rxPackets, 0); - final long txBytesDiff = Math.max(txBytes - other.txBytes, 0); - final long txPacketsDiff = Math.max(txPackets - other.txPackets, 0); - return new ForwardedStats(rxBytesDiff, rxPacketsDiff, txBytesDiff, txPacketsDiff); - } - - /** Returns the string representation of this object. */ - @NonNull - public String toString() { - return String.format("ForwardedStats(rxb: %d, rxp: %d, txb: %d, txp: %d)", rxBytes, - rxPackets, txBytes, txPackets); - } - } - - /** - * Configures a socket for receiving ICMPv6 router solicitations and sending advertisements. - * @param fd the socket's {@link FileDescriptor}. - * @param ifIndex the interface index. - */ - public static native void setupRaSocket(FileDescriptor fd, int ifIndex) - throws SocketException; - - /** - * Read s as an unsigned 16-bit integer. - */ - public static int uint16(short s) { - return s & 0xffff; - } - - /** Check whether two TetheringRequestParcels are the same. */ - public static boolean isTetheringRequestEquals(final TetheringRequestParcel request, - final TetheringRequestParcel otherRequest) { - if (request == otherRequest) return true; - - return request != null && otherRequest != null - && request.tetheringType == otherRequest.tetheringType - && Objects.equals(request.localIPv4Address, otherRequest.localIPv4Address) - && Objects.equals(request.staticClientAddress, otherRequest.staticClientAddress) - && request.exemptFromEntitlementCheck == otherRequest.exemptFromEntitlementCheck - && request.showProvisioningUi == otherRequest.showProvisioningUi; - } - - /** Get inet6 address for all nodes given scope ID. */ - public static Inet6Address getAllNodesForScopeId(int scopeId) { - try { - return Inet6Address.getByAddress("ff02::1", ALL_NODES, scopeId); - } catch (UnknownHostException uhe) { - Log.wtf("TetheringUtils", "Failed to construct Inet6Address from " - + Arrays.toString(ALL_NODES) + " and scopedId " + scopeId); - return null; - } - } -} diff --git a/packages/Tethering/src/android/net/util/VersionedBroadcastListener.java b/packages/Tethering/src/android/net/util/VersionedBroadcastListener.java deleted file mode 100644 index e2804abd752b..000000000000 --- a/packages/Tethering/src/android/net/util/VersionedBroadcastListener.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2017 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.util; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Handler; -import android.util.Log; - -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; - - -/** - * A utility class that runs the provided callback on the provided handler when - * intents matching the provided filter arrive. Intents received by a stale - * receiver are safely ignored. - * - * Calls to startListening() and stopListening() must happen on the same thread. - * - * @hide - */ -public class VersionedBroadcastListener { - private static final boolean DBG = false; - - private final String mTag; - private final Context mContext; - private final Handler mHandler; - private final IntentFilter mFilter; - private final Consumer<Intent> mCallback; - private final AtomicInteger mGenerationNumber; - private BroadcastReceiver mReceiver; - - public VersionedBroadcastListener(String tag, Context ctx, Handler handler, - IntentFilter filter, Consumer<Intent> callback) { - mTag = tag; - mContext = ctx; - mHandler = handler; - mFilter = filter; - mCallback = callback; - mGenerationNumber = new AtomicInteger(0); - } - - /** Start listening to intent broadcast. */ - public void startListening() { - if (DBG) Log.d(mTag, "startListening"); - if (mReceiver != null) return; - - mReceiver = new Receiver(mTag, mGenerationNumber, mCallback); - mContext.registerReceiver(mReceiver, mFilter, null, mHandler); - } - - /** Stop listening to intent broadcast. */ - public void stopListening() { - if (DBG) Log.d(mTag, "stopListening"); - if (mReceiver == null) return; - - mGenerationNumber.incrementAndGet(); - mContext.unregisterReceiver(mReceiver); - mReceiver = null; - } - - private static class Receiver extends BroadcastReceiver { - public final String tag; - public final AtomicInteger atomicGenerationNumber; - public final Consumer<Intent> callback; - // Used to verify this receiver is still current. - public final int generationNumber; - - Receiver(String tag, AtomicInteger atomicGenerationNumber, Consumer<Intent> callback) { - this.tag = tag; - this.atomicGenerationNumber = atomicGenerationNumber; - this.callback = callback; - generationNumber = atomicGenerationNumber.incrementAndGet(); - } - - @Override - public void onReceive(Context context, Intent intent) { - final int currentGenerationNumber = atomicGenerationNumber.get(); - - if (DBG) { - Log.d(tag, "receiver generationNumber=" + generationNumber - + ", current generationNumber=" + currentGenerationNumber); - } - if (generationNumber != currentGenerationNumber) return; - - callback.accept(intent); - } - } -} diff --git a/packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java deleted file mode 100644 index 20f30ea7a460..000000000000 --- a/packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java +++ /dev/null @@ -1,778 +0,0 @@ -/* - * Copyright (C) 2020 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.networkstack.tethering; - -import static android.net.NetworkStats.DEFAULT_NETWORK_NO; -import static android.net.NetworkStats.METERED_NO; -import static android.net.NetworkStats.ROAMING_NO; -import static android.net.NetworkStats.SET_DEFAULT; -import static android.net.NetworkStats.TAG_NONE; -import static android.net.NetworkStats.UID_ALL; -import static android.net.NetworkStats.UID_TETHERING; -import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED; - -import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS; - -import android.app.usage.NetworkStatsManager; -import android.net.INetd; -import android.net.MacAddress; -import android.net.NetworkStats; -import android.net.NetworkStats.Entry; -import android.net.TetherOffloadRuleParcel; -import android.net.TetherStatsParcel; -import android.net.ip.IpServer; -import android.net.netstats.provider.NetworkStatsProvider; -import android.net.util.SharedLog; -import android.net.util.TetheringUtils.ForwardedStats; -import android.os.ConditionVariable; -import android.os.Handler; -import android.os.RemoteException; -import android.os.ServiceSpecificException; -import android.text.TextUtils; -import android.util.Log; -import android.util.SparseArray; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.IndentingPrintWriter; - -import java.net.Inet6Address; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Objects; - -/** - * This coordinator is responsible for providing BPF offload relevant functionality. - * - Get tethering stats. - * - Set data limit. - * - Set global alert. - * - Add/remove forwarding rules. - * - * @hide - */ -public class BpfCoordinator { - private static final String TAG = BpfCoordinator.class.getSimpleName(); - private static final int DUMP_TIMEOUT_MS = 10_000; - - @VisibleForTesting - enum StatsType { - STATS_PER_IFACE, - STATS_PER_UID, - } - - @NonNull - private final Handler mHandler; - @NonNull - private final INetd mNetd; - @NonNull - private final SharedLog mLog; - @NonNull - private final Dependencies mDeps; - @Nullable - private final BpfTetherStatsProvider mStatsProvider; - - // True if BPF offload is supported, false otherwise. The BPF offload could be disabled by - // a runtime resource overlay package or device configuration. This flag is only initialized - // in the constructor because it is hard to unwind all existing change once device - // configuration is changed. Especially the forwarding rules. Keep the same setting - // to make it simpler. See also TetheringConfiguration. - private final boolean mIsBpfEnabled; - - // Tracks whether BPF tethering is started or not. This is set by tethering before it - // starts the first IpServer and is cleared by tethering shortly before the last IpServer - // is stopped. Note that rule updates (especially deletions, but sometimes additions as - // well) may arrive when this is false. If they do, they must be communicated to netd. - // Changes in data limits may also arrive when this is false, and if they do, they must - // also be communicated to netd. - private boolean mPollingStarted = false; - - // Tracking remaining alert quota. Unlike limit quota is subject to interface, the alert - // quota is interface independent and global for tether offload. - private long mRemainingAlertQuota = QUOTA_UNLIMITED; - - // Maps upstream interface index to offloaded traffic statistics. - // Always contains the latest total bytes/packets, since each upstream was started, received - // from the BPF maps for each interface. - private final SparseArray<ForwardedStats> mStats = new SparseArray<>(); - - // Maps upstream interface names to interface quotas. - // Always contains the latest value received from the framework for each interface, regardless - // of whether offload is currently running (or is even supported) on that interface. Only - // includes interfaces that have a quota set. Note that this map is used for storing the quota - // which is set from the service. Because the service uses the interface name to present the - // interface, this map uses the interface name to be the mapping index. - private final HashMap<String, Long> mInterfaceQuotas = new HashMap<>(); - - // Maps upstream interface index to interface names. - // Store all interface name since boot. Used for lookup what interface name it is from the - // tether stats got from netd because netd reports interface index to present an interface. - // TODO: Remove the unused interface name. - private final SparseArray<String> mInterfaceNames = new SparseArray<>(); - - // Map of downstream rule maps. Each of these maps represents the IPv6 forwarding rules for a - // given downstream. Each map: - // - Is owned by the IpServer that is responsible for that downstream. - // - Must only be modified by that IpServer. - // - Is created when the IpServer adds its first rule, and deleted when the IpServer deletes - // its last rule (or clears its rules). - // TODO: Perhaps seal the map and rule operations which communicates with netd into a class. - // TODO: Does this need to be a LinkedHashMap or can it just be a HashMap? Also, could it be - // a ConcurrentHashMap, in order to avoid the copies in tetherOffloadRuleClear - // and tetherOffloadRuleUpdate? - // TODO: Perhaps use one-dimensional map and access specific downstream rules via downstream - // index. For doing that, IpServer must guarantee that it always has a valid IPv6 downstream - // interface index while calling function to clear all rules. IpServer may be calling clear - // rules function without a valid IPv6 downstream interface index even if it may have one - // before. IpServer would need to call getInterfaceParams() in the constructor instead of when - // startIpv6() is called, and make mInterfaceParams final. - private final HashMap<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>> - mIpv6ForwardingRules = new LinkedHashMap<>(); - - // Runnable that used by scheduling next polling of stats. - private final Runnable mScheduledPollingTask = () -> { - updateForwardedStatsFromNetd(); - maybeSchedulePollingStats(); - }; - - @VisibleForTesting - public abstract static class Dependencies { - /** Get handler. */ - @NonNull public abstract Handler getHandler(); - - /** Get netd. */ - @NonNull public abstract INetd getNetd(); - - /** Get network stats manager. */ - @NonNull public abstract NetworkStatsManager getNetworkStatsManager(); - - /** Get shared log. */ - @NonNull public abstract SharedLog getSharedLog(); - - /** Get tethering configuration. */ - @Nullable public abstract TetheringConfiguration getTetherConfig(); - } - - @VisibleForTesting - public BpfCoordinator(@NonNull Dependencies deps) { - mDeps = deps; - mHandler = mDeps.getHandler(); - mNetd = mDeps.getNetd(); - mLog = mDeps.getSharedLog().forSubComponent(TAG); - mIsBpfEnabled = isBpfEnabled(); - BpfTetherStatsProvider provider = new BpfTetherStatsProvider(); - try { - mDeps.getNetworkStatsManager().registerNetworkStatsProvider( - getClass().getSimpleName(), provider); - } catch (RuntimeException e) { - // TODO: Perhaps not allow to use BPF offload because the reregistration failure - // implied that no data limit could be applies on a metered upstream if any. - Log.wtf(TAG, "Cannot register offload stats provider: " + e); - provider = null; - } - mStatsProvider = provider; - } - - /** - * Start BPF tethering offload stats polling when the first upstream is started. - * Note that this can be only called on handler thread. - * TODO: Perhaps check BPF support before starting. - * TODO: Start the stats polling only if there is any client on the downstream. - */ - public void startPolling() { - if (mPollingStarted) return; - - if (!mIsBpfEnabled) { - mLog.i("Offload disabled"); - return; - } - - mPollingStarted = true; - maybeSchedulePollingStats(); - - mLog.i("Polling started"); - } - - /** - * Stop BPF tethering offload stats polling. - * The data limit cleanup and the tether stats maps cleanup are not implemented here. - * These cleanups rely on all IpServers calling #tetherOffloadRuleRemove. After the - * last rule is removed from the upstream, #tetherOffloadRuleRemove does the cleanup - * functionality. - * Note that this can be only called on handler thread. - */ - public void stopPolling() { - if (!mPollingStarted) return; - - // Stop scheduled polling tasks and poll the latest stats from BPF maps. - if (mHandler.hasCallbacks(mScheduledPollingTask)) { - mHandler.removeCallbacks(mScheduledPollingTask); - } - updateForwardedStatsFromNetd(); - mPollingStarted = false; - - mLog.i("Polling stopped"); - } - - /** - * Add forwarding rule. After adding the first rule on a given upstream, must add the data - * limit on the given upstream. - * Note that this can be only called on handler thread. - */ - public void tetherOffloadRuleAdd( - @NonNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule) { - if (!mIsBpfEnabled) return; - - try { - // TODO: Perhaps avoid to add a duplicate rule. - mNetd.tetherOffloadRuleAdd(rule.toTetherOffloadRuleParcel()); - } catch (RemoteException | ServiceSpecificException e) { - mLog.e("Could not add IPv6 forwarding rule: ", e); - return; - } - - if (!mIpv6ForwardingRules.containsKey(ipServer)) { - mIpv6ForwardingRules.put(ipServer, new LinkedHashMap<Inet6Address, - Ipv6ForwardingRule>()); - } - LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(ipServer); - - // Setup the data limit on the given upstream if the first rule is added. - final int upstreamIfindex = rule.upstreamIfindex; - if (!isAnyRuleOnUpstream(upstreamIfindex)) { - // If failed to set a data limit, probably should not use this upstream, because - // the upstream may not want to blow through the data limit that was told to apply. - // TODO: Perhaps stop the coordinator. - boolean success = updateDataLimit(upstreamIfindex); - if (!success) { - final String iface = mInterfaceNames.get(upstreamIfindex); - mLog.e("Setting data limit for " + iface + " failed."); - } - } - - // Must update the adding rule after calling #isAnyRuleOnUpstream because it needs to - // check if it is about adding a first rule for a given upstream. - rules.put(rule.address, rule); - } - - /** - * Remove forwarding rule. After removing the last rule on a given upstream, must clear - * data limit, update the last tether stats and remove the tether stats in the BPF maps. - * Note that this can be only called on handler thread. - */ - public void tetherOffloadRuleRemove( - @NonNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule) { - if (!mIsBpfEnabled) return; - - try { - // TODO: Perhaps avoid to remove a non-existent rule. - mNetd.tetherOffloadRuleRemove(rule.toTetherOffloadRuleParcel()); - } catch (RemoteException | ServiceSpecificException e) { - mLog.e("Could not remove IPv6 forwarding rule: ", e); - return; - } - - LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(ipServer); - if (rules == null) return; - - // Must remove rules before calling #isAnyRuleOnUpstream because it needs to check if - // the last rule is removed for a given upstream. If no rule is removed, return early. - // Avoid unnecessary work on a non-existent rule which may have never been added or - // removed already. - if (rules.remove(rule.address) == null) return; - - // Remove the downstream entry if it has no more rule. - if (rules.isEmpty()) { - mIpv6ForwardingRules.remove(ipServer); - } - - // Do cleanup functionality if there is no more rule on the given upstream. - final int upstreamIfindex = rule.upstreamIfindex; - if (!isAnyRuleOnUpstream(upstreamIfindex)) { - try { - final TetherStatsParcel stats = - mNetd.tetherOffloadGetAndClearStats(upstreamIfindex); - // Update the last stats delta and delete the local cache for a given upstream. - updateQuotaAndStatsFromSnapshot(new TetherStatsParcel[] {stats}); - mStats.remove(upstreamIfindex); - } catch (RemoteException | ServiceSpecificException e) { - Log.wtf(TAG, "Exception when cleanup tether stats for upstream index " - + upstreamIfindex + ": ", e); - } - } - } - - /** - * Clear all forwarding rules for a given downstream. - * Note that this can be only called on handler thread. - */ - public void tetherOffloadRuleClear(@NonNull final IpServer ipServer) { - if (!mIsBpfEnabled) return; - - final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get( - ipServer); - if (rules == null) return; - - // Need to build a rule list because the rule map may be changed in the iteration. - for (final Ipv6ForwardingRule rule : new ArrayList<Ipv6ForwardingRule>(rules.values())) { - tetherOffloadRuleRemove(ipServer, rule); - } - } - - /** - * Update existing forwarding rules to new upstream for a given downstream. - * Note that this can be only called on handler thread. - */ - public void tetherOffloadRuleUpdate(@NonNull final IpServer ipServer, int newUpstreamIfindex) { - if (!mIsBpfEnabled) return; - - final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get( - ipServer); - if (rules == null) return; - - // Need to build a rule list because the rule map may be changed in the iteration. - for (final Ipv6ForwardingRule rule : new ArrayList<Ipv6ForwardingRule>(rules.values())) { - // Remove the old rule before adding the new one because the map uses the same key for - // both rules. Reversing the processing order causes that the new rule is removed as - // unexpected. - // TODO: Add new rule first to reduce the latency which has no rule. - tetherOffloadRuleRemove(ipServer, rule); - tetherOffloadRuleAdd(ipServer, rule.onNewUpstream(newUpstreamIfindex)); - } - } - - /** - * Add upstream name to lookup table. The lookup table is used for tether stats interface name - * lookup because the netd only reports interface index in BPF tether stats but the service - * expects the interface name in NetworkStats object. - * Note that this can be only called on handler thread. - */ - public void addUpstreamNameToLookupTable(int upstreamIfindex, @NonNull String upstreamIface) { - if (!mIsBpfEnabled) return; - - if (upstreamIfindex == 0 || TextUtils.isEmpty(upstreamIface)) return; - - // The same interface index to name mapping may be added by different IpServer objects or - // re-added by reconnection on the same upstream interface. Ignore the duplicate one. - final String iface = mInterfaceNames.get(upstreamIfindex); - if (iface == null) { - mInterfaceNames.put(upstreamIfindex, upstreamIface); - } else if (!TextUtils.equals(iface, upstreamIface)) { - Log.wtf(TAG, "The upstream interface name " + upstreamIface - + " is different from the existing interface name " - + iface + " for index " + upstreamIfindex); - } - } - - /** - * Dump information. - * Block the function until all the data are dumped on the handler thread or timed-out. The - * reason is that dumpsys invokes this function on the thread of caller and the data may only - * be allowed to be accessed on the handler thread. - */ - public void dump(@NonNull IndentingPrintWriter pw) { - final ConditionVariable dumpDone = new ConditionVariable(); - mHandler.post(() -> { - pw.println("mIsBpfEnabled: " + mIsBpfEnabled); - pw.println("Polling " + (mPollingStarted ? "started" : "not started")); - pw.println("Stats provider " + (mStatsProvider != null - ? "registered" : "not registered")); - pw.println("Upstream quota: " + mInterfaceQuotas.toString()); - pw.println("Polling interval: " + getPollingInterval() + " ms"); - - pw.println("Forwarding stats:"); - pw.increaseIndent(); - if (mStats.size() == 0) { - pw.println("<empty>"); - } else { - dumpStats(pw); - } - pw.decreaseIndent(); - - pw.println("Forwarding rules:"); - pw.increaseIndent(); - if (mIpv6ForwardingRules.size() == 0) { - pw.println("<empty>"); - } else { - dumpIpv6ForwardingRules(pw); - } - pw.decreaseIndent(); - - dumpDone.open(); - }); - if (!dumpDone.block(DUMP_TIMEOUT_MS)) { - pw.println("... dump timed-out after " + DUMP_TIMEOUT_MS + "ms"); - } - } - - private void dumpStats(@NonNull IndentingPrintWriter pw) { - for (int i = 0; i < mStats.size(); i++) { - final int upstreamIfindex = mStats.keyAt(i); - final ForwardedStats stats = mStats.get(upstreamIfindex); - pw.println(String.format("%d(%s) - %s", upstreamIfindex, mInterfaceNames.get( - upstreamIfindex), stats.toString())); - } - } - - private void dumpIpv6ForwardingRules(@NonNull IndentingPrintWriter pw) { - for (Map.Entry<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>> entry : - mIpv6ForwardingRules.entrySet()) { - IpServer ipServer = entry.getKey(); - // The rule downstream interface index is paired with the interface name from - // IpServer#interfaceName. See #startIPv6, #updateIpv6ForwardingRules in IpServer. - final String downstreamIface = ipServer.interfaceName(); - pw.println("[" + downstreamIface + "]: iif(iface) oif(iface) v6addr srcmac dstmac"); - - pw.increaseIndent(); - LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = entry.getValue(); - for (Ipv6ForwardingRule rule : rules.values()) { - final int upstreamIfindex = rule.upstreamIfindex; - pw.println(String.format("%d(%s) %d(%s) %s %s %s", upstreamIfindex, - mInterfaceNames.get(upstreamIfindex), rule.downstreamIfindex, - downstreamIface, rule.address, rule.srcMac, rule.dstMac)); - } - pw.decreaseIndent(); - } - } - - /** IPv6 forwarding rule class. */ - public static class Ipv6ForwardingRule { - public final int upstreamIfindex; - public final int downstreamIfindex; - - @NonNull - public final Inet6Address address; - @NonNull - public final MacAddress srcMac; - @NonNull - public final MacAddress dstMac; - - public Ipv6ForwardingRule(int upstreamIfindex, int downstreamIfIndex, - @NonNull Inet6Address address, @NonNull MacAddress srcMac, - @NonNull MacAddress dstMac) { - this.upstreamIfindex = upstreamIfindex; - this.downstreamIfindex = downstreamIfIndex; - this.address = address; - this.srcMac = srcMac; - this.dstMac = dstMac; - } - - /** Return a new rule object which updates with new upstream index. */ - @NonNull - public Ipv6ForwardingRule onNewUpstream(int newUpstreamIfindex) { - return new Ipv6ForwardingRule(newUpstreamIfindex, downstreamIfindex, address, srcMac, - dstMac); - } - - /** - * Don't manipulate TetherOffloadRuleParcel directly because implementing onNewUpstream() - * would be error-prone due to generated stable AIDL classes not having a copy constructor. - */ - @NonNull - public TetherOffloadRuleParcel toTetherOffloadRuleParcel() { - final TetherOffloadRuleParcel parcel = new TetherOffloadRuleParcel(); - parcel.inputInterfaceIndex = upstreamIfindex; - parcel.outputInterfaceIndex = downstreamIfindex; - parcel.destination = address.getAddress(); - parcel.prefixLength = 128; - parcel.srcL2Address = srcMac.toByteArray(); - parcel.dstL2Address = dstMac.toByteArray(); - return parcel; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof Ipv6ForwardingRule)) return false; - Ipv6ForwardingRule that = (Ipv6ForwardingRule) o; - return this.upstreamIfindex == that.upstreamIfindex - && this.downstreamIfindex == that.downstreamIfindex - && Objects.equals(this.address, that.address) - && Objects.equals(this.srcMac, that.srcMac) - && Objects.equals(this.dstMac, that.dstMac); - } - - @Override - public int hashCode() { - // TODO: if this is ever used in production code, don't pass ifindices - // to Objects.hash() to avoid autoboxing overhead. - return Objects.hash(upstreamIfindex, downstreamIfindex, address, srcMac, dstMac); - } - } - - /** - * A BPF tethering stats provider to provide network statistics to the system. - * Note that this class' data may only be accessed on the handler thread. - */ - @VisibleForTesting - class BpfTetherStatsProvider extends NetworkStatsProvider { - // The offloaded traffic statistics per interface that has not been reported since the - // last call to pushTetherStats. Only the interfaces that were ever tethering upstreams - // and has pending tether stats delta are included in this NetworkStats object. - private NetworkStats mIfaceStats = new NetworkStats(0L, 0); - - // The same stats as above, but counts network stats per uid. - private NetworkStats mUidStats = new NetworkStats(0L, 0); - - @Override - public void onRequestStatsUpdate(int token) { - mHandler.post(() -> pushTetherStats()); - } - - @Override - public void onSetAlert(long quotaBytes) { - mHandler.post(() -> updateAlertQuota(quotaBytes)); - } - - @Override - public void onSetLimit(@NonNull String iface, long quotaBytes) { - if (quotaBytes < QUOTA_UNLIMITED) { - throw new IllegalArgumentException("invalid quota value " + quotaBytes); - } - - mHandler.post(() -> { - final Long curIfaceQuota = mInterfaceQuotas.get(iface); - - if (null == curIfaceQuota && QUOTA_UNLIMITED == quotaBytes) return; - - if (quotaBytes == QUOTA_UNLIMITED) { - mInterfaceQuotas.remove(iface); - } else { - mInterfaceQuotas.put(iface, quotaBytes); - } - maybeUpdateDataLimit(iface); - }); - } - - @VisibleForTesting - void pushTetherStats() { - try { - // The token is not used for now. See b/153606961. - notifyStatsUpdated(0 /* token */, mIfaceStats, mUidStats); - - // Clear the accumulated tether stats delta after reported. Note that create a new - // empty object because NetworkStats#clear is @hide. - mIfaceStats = new NetworkStats(0L, 0); - mUidStats = new NetworkStats(0L, 0); - } catch (RuntimeException e) { - mLog.e("Cannot report network stats: ", e); - } - } - - private void accumulateDiff(@NonNull NetworkStats ifaceDiff, - @NonNull NetworkStats uidDiff) { - mIfaceStats = mIfaceStats.add(ifaceDiff); - mUidStats = mUidStats.add(uidDiff); - } - } - - private boolean isBpfEnabled() { - final TetheringConfiguration config = mDeps.getTetherConfig(); - return (config != null) ? config.isBpfOffloadEnabled() : true /* default value */; - } - - private int getInterfaceIndexFromRules(@NonNull String ifName) { - for (LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules : mIpv6ForwardingRules - .values()) { - for (Ipv6ForwardingRule rule : rules.values()) { - final int upstreamIfindex = rule.upstreamIfindex; - if (TextUtils.equals(ifName, mInterfaceNames.get(upstreamIfindex))) { - return upstreamIfindex; - } - } - } - return 0; - } - - private long getQuotaBytes(@NonNull String iface) { - final Long limit = mInterfaceQuotas.get(iface); - final long quotaBytes = (limit != null) ? limit : QUOTA_UNLIMITED; - - return quotaBytes; - } - - private boolean sendDataLimitToNetd(int ifIndex, long quotaBytes) { - if (ifIndex == 0) { - Log.wtf(TAG, "Invalid interface index."); - return false; - } - - try { - mNetd.tetherOffloadSetInterfaceQuota(ifIndex, quotaBytes); - } catch (RemoteException | ServiceSpecificException e) { - mLog.e("Exception when updating quota " + quotaBytes + ": ", e); - return false; - } - - return true; - } - - // Handle the data limit update from the service which is the stats provider registered for. - private void maybeUpdateDataLimit(@NonNull String iface) { - // Set data limit only on a given upstream which has at least one rule. If we can't get - // an interface index for a given interface name, it means either there is no rule for - // a given upstream or the interface name is not an upstream which is monitored by the - // coordinator. - final int ifIndex = getInterfaceIndexFromRules(iface); - if (ifIndex == 0) return; - - final long quotaBytes = getQuotaBytes(iface); - sendDataLimitToNetd(ifIndex, quotaBytes); - } - - // Handle the data limit update while adding forwarding rules. - private boolean updateDataLimit(int ifIndex) { - final String iface = mInterfaceNames.get(ifIndex); - if (iface == null) { - mLog.e("Fail to get the interface name for index " + ifIndex); - return false; - } - final long quotaBytes = getQuotaBytes(iface); - return sendDataLimitToNetd(ifIndex, quotaBytes); - } - - private boolean isAnyRuleOnUpstream(int upstreamIfindex) { - for (LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules : mIpv6ForwardingRules - .values()) { - for (Ipv6ForwardingRule rule : rules.values()) { - if (upstreamIfindex == rule.upstreamIfindex) return true; - } - } - return false; - } - - @NonNull - private NetworkStats buildNetworkStats(@NonNull StatsType type, int ifIndex, - @NonNull final ForwardedStats diff) { - NetworkStats stats = new NetworkStats(0L, 0); - final String iface = mInterfaceNames.get(ifIndex); - if (iface == null) { - // TODO: Use Log.wtf once the coordinator owns full control of tether stats from netd. - // For now, netd may add the empty stats for the upstream which is not monitored by - // the coordinator. Silently ignore it. - return stats; - } - final int uid = (type == StatsType.STATS_PER_UID) ? UID_TETHERING : UID_ALL; - // Note that the argument 'metered', 'roaming' and 'defaultNetwork' are not recorded for - // network stats snapshot. See NetworkStatsRecorder#recordSnapshotLocked. - return stats.addEntry(new Entry(iface, uid, SET_DEFAULT, TAG_NONE, METERED_NO, - ROAMING_NO, DEFAULT_NETWORK_NO, diff.rxBytes, diff.rxPackets, - diff.txBytes, diff.txPackets, 0L /* operations */)); - } - - private void updateAlertQuota(long newQuota) { - if (newQuota < QUOTA_UNLIMITED) { - throw new IllegalArgumentException("invalid quota value " + newQuota); - } - if (mRemainingAlertQuota == newQuota) return; - - mRemainingAlertQuota = newQuota; - if (mRemainingAlertQuota == 0) { - mLog.i("onAlertReached"); - if (mStatsProvider != null) mStatsProvider.notifyAlertReached(); - } - } - - private void updateQuotaAndStatsFromSnapshot( - @NonNull final TetherStatsParcel[] tetherStatsList) { - long usedAlertQuota = 0; - for (TetherStatsParcel tetherStats : tetherStatsList) { - final Integer ifIndex = tetherStats.ifIndex; - final ForwardedStats curr = new ForwardedStats(tetherStats); - final ForwardedStats base = mStats.get(ifIndex); - final ForwardedStats diff = (base != null) ? curr.subtract(base) : curr; - usedAlertQuota += diff.rxBytes + diff.txBytes; - - // Update the local cache for counting tether stats delta. - mStats.put(ifIndex, curr); - - // Update the accumulated tether stats delta to the stats provider for the service - // querying. - if (mStatsProvider != null) { - try { - mStatsProvider.accumulateDiff( - buildNetworkStats(StatsType.STATS_PER_IFACE, ifIndex, diff), - buildNetworkStats(StatsType.STATS_PER_UID, ifIndex, diff)); - } catch (ArrayIndexOutOfBoundsException e) { - Log.wtf(TAG, "Fail to update the accumulated stats delta for interface index " - + ifIndex + " : ", e); - } - } - } - - if (mRemainingAlertQuota > 0 && usedAlertQuota > 0) { - // Trim to zero if overshoot. - final long newQuota = Math.max(mRemainingAlertQuota - usedAlertQuota, 0); - updateAlertQuota(newQuota); - } - - // TODO: Count the used limit quota for notifying data limit reached. - } - - private void updateForwardedStatsFromNetd() { - final TetherStatsParcel[] tetherStatsList; - try { - // The reported tether stats are total data usage for all currently-active upstream - // interfaces since tethering start. - tetherStatsList = mNetd.tetherOffloadGetStats(); - } catch (RemoteException | ServiceSpecificException e) { - mLog.e("Problem fetching tethering stats: ", e); - return; - } - updateQuotaAndStatsFromSnapshot(tetherStatsList); - } - - @VisibleForTesting - int getPollingInterval() { - // The valid range of interval is DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS..max_long. - // Ignore the config value is less than the minimum polling interval. Note that the - // minimum interval definition is invoked as OffloadController#isPollingStatsNeeded does. - // TODO: Perhaps define a minimum polling interval constant. - final TetheringConfiguration config = mDeps.getTetherConfig(); - final int configInterval = (config != null) ? config.getOffloadPollInterval() : 0; - return Math.max(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS, configInterval); - } - - private void maybeSchedulePollingStats() { - if (!mPollingStarted) return; - - if (mHandler.hasCallbacks(mScheduledPollingTask)) { - mHandler.removeCallbacks(mScheduledPollingTask); - } - - mHandler.postDelayed(mScheduledPollingTask, getPollingInterval()); - } - - // Return forwarding rule map. This is used for testing only. - // Note that this can be only called on handler thread. - @NonNull - @VisibleForTesting - final HashMap<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>> - getForwardingRulesForTesting() { - return mIpv6ForwardingRules; - } - - // Return upstream interface name map. This is used for testing only. - // Note that this can be only called on handler thread. - @NonNull - @VisibleForTesting - final SparseArray<String> getInterfaceNamesForTesting() { - return mInterfaceNames; - } -} diff --git a/packages/Tethering/src/com/android/networkstack/tethering/ConnectedClientsTracker.java b/packages/Tethering/src/com/android/networkstack/tethering/ConnectedClientsTracker.java deleted file mode 100644 index 8a96988ae1d1..000000000000 --- a/packages/Tethering/src/com/android/networkstack/tethering/ConnectedClientsTracker.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2020 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.networkstack.tethering; - -import static android.net.TetheringManager.TETHERING_WIFI; - -import android.net.MacAddress; -import android.net.TetheredClient; -import android.net.TetheredClient.AddressInfo; -import android.net.ip.IpServer; -import android.net.wifi.WifiClient; -import android.os.SystemClock; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Tracker for clients connected to downstreams. - * - * <p>This class is not thread safe, it is intended to be used only from the tethering handler - * thread. - */ -public class ConnectedClientsTracker { - private final Clock mClock; - - @NonNull - private List<WifiClient> mLastWifiClients = Collections.emptyList(); - @NonNull - private List<TetheredClient> mLastTetheredClients = Collections.emptyList(); - - @VisibleForTesting - static class Clock { - public long elapsedRealtime() { - return SystemClock.elapsedRealtime(); - } - } - - public ConnectedClientsTracker() { - this(new Clock()); - } - - @VisibleForTesting - ConnectedClientsTracker(Clock clock) { - mClock = clock; - } - - /** - * Update the tracker with new connected clients. - * - * <p>The new list can be obtained through {@link #getLastTetheredClients()}. - * @param ipServers The IpServers used to assign addresses to clients. - * @param wifiClients The list of L2-connected WiFi clients. Null for no change since last - * update. - * @return True if the list of clients changed since the last calculation. - */ - public boolean updateConnectedClients( - Iterable<IpServer> ipServers, @Nullable List<WifiClient> wifiClients) { - final long now = mClock.elapsedRealtime(); - - if (wifiClients != null) { - mLastWifiClients = wifiClients; - } - final Set<MacAddress> wifiClientMacs = getClientMacs(mLastWifiClients); - - // Build the list of non-expired leases from all IpServers, grouped by mac address - final Map<MacAddress, TetheredClient> clientsMap = new HashMap<>(); - for (IpServer server : ipServers) { - for (TetheredClient client : server.getAllLeases()) { - if (client.getTetheringType() == TETHERING_WIFI - && !wifiClientMacs.contains(client.getMacAddress())) { - // Skip leases of WiFi clients that are not (or no longer) L2-connected - continue; - } - final TetheredClient prunedClient = pruneExpired(client, now); - if (prunedClient == null) continue; // All addresses expired - - addLease(clientsMap, prunedClient); - } - } - - // TODO: add IPv6 addresses from netlink - - // Add connected WiFi clients that do not have any known address - for (MacAddress client : wifiClientMacs) { - if (clientsMap.containsKey(client)) continue; - clientsMap.put(client, new TetheredClient( - client, Collections.emptyList() /* addresses */, TETHERING_WIFI)); - } - - final HashSet<TetheredClient> clients = new HashSet<>(clientsMap.values()); - final boolean clientsChanged = clients.size() != mLastTetheredClients.size() - || !clients.containsAll(mLastTetheredClients); - mLastTetheredClients = Collections.unmodifiableList(new ArrayList<>(clients)); - return clientsChanged; - } - - private static void addLease(Map<MacAddress, TetheredClient> clientsMap, TetheredClient lease) { - final TetheredClient aggregateClient = clientsMap.getOrDefault( - lease.getMacAddress(), lease); - if (aggregateClient == lease) { - // This is the first lease with this mac address - clientsMap.put(lease.getMacAddress(), lease); - return; - } - - // Only add the address info; this assumes that the tethering type is the same when the mac - // address is the same. If a client is connected through different tethering types with the - // same mac address, connected clients callbacks will report all of its addresses under only - // one of these tethering types. This keeps the API simple considering that such a scenario - // would really be a rare edge case. - clientsMap.put(lease.getMacAddress(), aggregateClient.addAddresses(lease)); - } - - /** - * Get the last list of tethered clients, as calculated in {@link #updateConnectedClients}. - * - * <p>The returned list is immutable. - */ - @NonNull - public List<TetheredClient> getLastTetheredClients() { - return mLastTetheredClients; - } - - private static boolean hasExpiredAddress(List<AddressInfo> addresses, long now) { - for (AddressInfo info : addresses) { - if (info.getExpirationTime() <= now) { - return true; - } - } - return false; - } - - @Nullable - private static TetheredClient pruneExpired(TetheredClient client, long now) { - final List<AddressInfo> addresses = client.getAddresses(); - if (addresses.size() == 0) return null; - if (!hasExpiredAddress(addresses, now)) return client; - - final ArrayList<AddressInfo> newAddrs = new ArrayList<>(addresses.size() - 1); - for (AddressInfo info : addresses) { - if (info.getExpirationTime() > now) { - newAddrs.add(info); - } - } - - if (newAddrs.size() == 0) { - return null; - } - return new TetheredClient(client.getMacAddress(), newAddrs, client.getTetheringType()); - } - - @NonNull - private static Set<MacAddress> getClientMacs(@NonNull List<WifiClient> clients) { - final Set<MacAddress> macs = new HashSet<>(clients.size()); - for (WifiClient c : clients) { - macs.add(c.getMacAddress()); - } - return macs; - } -} diff --git a/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java deleted file mode 100644 index bb7322f2a0d2..000000000000 --- a/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java +++ /dev/null @@ -1,646 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.networkstack.tethering; - -import static android.net.TetheringConstants.EXTRA_ADD_TETHER_TYPE; -import static android.net.TetheringConstants.EXTRA_PROVISION_CALLBACK; -import static android.net.TetheringConstants.EXTRA_RUN_PROVISION; -import static android.net.TetheringConstants.EXTRA_TETHER_PROVISIONING_RESPONSE; -import static android.net.TetheringConstants.EXTRA_TETHER_SILENT_PROVISIONING_ACTION; -import static android.net.TetheringConstants.EXTRA_TETHER_SUBID; -import static android.net.TetheringConstants.EXTRA_TETHER_UI_PROVISIONING_APP_NAME; -import static android.net.TetheringManager.TETHERING_BLUETOOTH; -import static android.net.TetheringManager.TETHERING_ETHERNET; -import static android.net.TetheringManager.TETHERING_INVALID; -import static android.net.TetheringManager.TETHERING_USB; -import static android.net.TetheringManager.TETHERING_WIFI; -import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN; -import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; -import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.util.SharedLog; -import android.os.Bundle; -import android.os.ConditionVariable; -import android.os.Handler; -import android.os.Parcel; -import android.os.PersistableBundle; -import android.os.ResultReceiver; -import android.os.SystemClock; -import android.os.SystemProperties; -import android.provider.Settings; -import android.telephony.CarrierConfigManager; -import android.util.SparseIntArray; - -import com.android.internal.annotations.VisibleForTesting; - -import java.io.PrintWriter; -import java.util.BitSet; - -/** - * Re-check tethering provisioning for enabled downstream tether types. - * Reference TetheringManager.TETHERING_{@code *} for each tether type. - * - * All methods of this class must be accessed from the thread of tethering - * state machine. - * @hide - */ -public class EntitlementManager { - private static final String TAG = EntitlementManager.class.getSimpleName(); - private static final boolean DBG = false; - - @VisibleForTesting - protected static final String DISABLE_PROVISIONING_SYSPROP_KEY = "net.tethering.noprovisioning"; - private static final String ACTION_PROVISIONING_ALARM = - "com.android.networkstack.tethering.PROVISIONING_RECHECK_ALARM"; - - private final ComponentName mSilentProvisioningService; - private static final int MS_PER_HOUR = 60 * 60 * 1000; - private static final int DUMP_TIMEOUT = 10_000; - - // The BitSet is the bit map of each enabled downstream types, ex: - // {@link TetheringManager.TETHERING_WIFI} - // {@link TetheringManager.TETHERING_USB} - // {@link TetheringManager.TETHERING_BLUETOOTH} - private final BitSet mCurrentDownstreams; - private final BitSet mExemptedDownstreams; - private final Context mContext; - private final SharedLog mLog; - private final SparseIntArray mEntitlementCacheValue; - private final Handler mHandler; - // Key: TetheringManager.TETHERING_*(downstream). - // Value: TetheringManager.TETHER_ERROR_{NO_ERROR or PROVISION_FAILED}(provisioning result). - private final SparseIntArray mCurrentEntitlementResults; - private final Runnable mPermissionChangeCallback; - private PendingIntent mProvisioningRecheckAlarm; - private boolean mLastCellularUpstreamPermitted = true; - private boolean mUsingCellularAsUpstream = false; - private boolean mNeedReRunProvisioningUi = false; - private OnUiEntitlementFailedListener mListener; - private TetheringConfigurationFetcher mFetcher; - - public EntitlementManager(Context ctx, Handler h, SharedLog log, - Runnable callback) { - mContext = ctx; - mLog = log.forSubComponent(TAG); - mCurrentDownstreams = new BitSet(); - mExemptedDownstreams = new BitSet(); - mCurrentEntitlementResults = new SparseIntArray(); - mEntitlementCacheValue = new SparseIntArray(); - mPermissionChangeCallback = callback; - mHandler = h; - mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PROVISIONING_ALARM), - null, mHandler); - mSilentProvisioningService = ComponentName.unflattenFromString( - mContext.getResources().getString(R.string.config_wifi_tether_enable)); - } - - public void setOnUiEntitlementFailedListener(final OnUiEntitlementFailedListener listener) { - mListener = listener; - } - - /** Callback fired when UI entitlement failed. */ - public interface OnUiEntitlementFailedListener { - /** - * Ui entitlement check fails in |downstream|. - * - * @param downstream tethering type from TetheringManager.TETHERING_{@code *}. - */ - void onUiEntitlementFailed(int downstream); - } - - public void setTetheringConfigurationFetcher(final TetheringConfigurationFetcher fetcher) { - mFetcher = fetcher; - } - - /** Interface to fetch TetheringConfiguration. */ - public interface TetheringConfigurationFetcher { - /** - * Fetch current tethering configuration. This will be called to ensure whether entitlement - * check is needed. - * @return TetheringConfiguration instance. - */ - TetheringConfiguration fetchTetheringConfiguration(); - } - - /** - * Check if cellular upstream is permitted. - */ - public boolean isCellularUpstreamPermitted() { - final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); - - return isCellularUpstreamPermitted(config); - } - - private boolean isCellularUpstreamPermitted(final TetheringConfiguration config) { - if (!isTetherProvisioningRequired(config)) return true; - - // If provisioning is required and EntitlementManager doesn't know any downstreams, cellular - // upstream should not be enabled. Enable cellular upstream for exempted downstreams only - // when there is no non-exempted downstream. - if (mCurrentDownstreams.isEmpty()) return !mExemptedDownstreams.isEmpty(); - - return mCurrentEntitlementResults.indexOfValue(TETHER_ERROR_NO_ERROR) > -1; - } - - /** - * Set exempted downstream type. If there is only exempted downstream type active, - * corresponding entitlement check will not be run and cellular upstream will be permitted - * by default. If a privileged app enables tethering without a provisioning check, and then - * another app enables tethering of the same type but does not disable the provisioning check, - * then the downstream immediately loses exempt status and a provisioning check is run. - * If any non-exempted downstream type is active, the cellular upstream will be gated by the - * result of entitlement check from non-exempted downstreams. If entitlement check is still - * in progress on non-exempt downstreams, ceullar upstream would default be disabled. When any - * non-exempted downstream gets positive entitlement result, ceullar upstream will be enabled. - */ - public void setExemptedDownstreamType(final int type) { - mExemptedDownstreams.set(type, true); - } - - /** - * This is called when tethering starts. - * Launch provisioning app if upstream is cellular. - * - * @param downstreamType tethering type from TetheringManager.TETHERING_{@code *} - * @param showProvisioningUi a boolean indicating whether to show the - * provisioning app UI if there is one. - */ - public void startProvisioningIfNeeded(int downstreamType, boolean showProvisioningUi) { - if (!isValidDownstreamType(downstreamType)) return; - - mCurrentDownstreams.set(downstreamType, true); - - mExemptedDownstreams.set(downstreamType, false); - - final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); - if (!isTetherProvisioningRequired(config)) return; - - // If upstream is not cellular, provisioning app would not be launched - // till upstream change to cellular. - if (mUsingCellularAsUpstream) { - if (showProvisioningUi) { - runUiTetherProvisioning(downstreamType, config); - } else { - runSilentTetherProvisioning(downstreamType, config); - } - mNeedReRunProvisioningUi = false; - } else { - mNeedReRunProvisioningUi |= showProvisioningUi; - } - } - - /** - * Tell EntitlementManager that a given type of tethering has been disabled - * - * @param type tethering type from TetheringManager.TETHERING_{@code *} - */ - public void stopProvisioningIfNeeded(int downstreamType) { - if (!isValidDownstreamType(downstreamType)) return; - - mCurrentDownstreams.set(downstreamType, false); - // There are lurking bugs where the notion of "provisioning required" or - // "tethering supported" may change without without tethering being notified properly. - // Remove the mapping all the time no matter provisioning is required or not. - removeDownstreamMapping(downstreamType); - mExemptedDownstreams.set(downstreamType, false); - } - - /** - * Notify EntitlementManager if upstream is cellular or not. - * - * @param isCellular whether tethering upstream is cellular. - */ - public void notifyUpstream(boolean isCellular) { - if (DBG) { - mLog.i("notifyUpstream: " + isCellular - + ", mLastCellularUpstreamPermitted: " + mLastCellularUpstreamPermitted - + ", mNeedReRunProvisioningUi: " + mNeedReRunProvisioningUi); - } - mUsingCellularAsUpstream = isCellular; - - if (mUsingCellularAsUpstream) { - final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); - maybeRunProvisioning(config); - } - } - - /** Run provisioning if needed */ - public void maybeRunProvisioning() { - final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); - maybeRunProvisioning(config); - } - - private void maybeRunProvisioning(final TetheringConfiguration config) { - if (mCurrentDownstreams.isEmpty() || !isTetherProvisioningRequired(config)) { - return; - } - - // Whenever any entitlement value changes, all downstreams will re-evaluate whether they - // are allowed. Therefore even if the silent check here ends in a failure and the UI later - // yields success, then the downstream that got a failure will re-evaluate as a result of - // the change and get the new correct value. - for (int downstream = mCurrentDownstreams.nextSetBit(0); downstream >= 0; - downstream = mCurrentDownstreams.nextSetBit(downstream + 1)) { - if (mCurrentEntitlementResults.indexOfKey(downstream) < 0) { - if (mNeedReRunProvisioningUi) { - mNeedReRunProvisioningUi = false; - runUiTetherProvisioning(downstream, config); - } else { - runSilentTetherProvisioning(downstream, config); - } - } - } - } - - /** - * Check if the device requires a provisioning check in order to enable tethering. - * - * @param config an object that encapsulates the various tethering configuration elements. - * @return a boolean - {@code true} indicating tether provisioning is required by the carrier. - */ - @VisibleForTesting - protected boolean isTetherProvisioningRequired(final TetheringConfiguration config) { - if (SystemProperties.getBoolean(DISABLE_PROVISIONING_SYSPROP_KEY, false) - || config.provisioningApp.length == 0) { - return false; - } - if (carrierConfigAffirmsEntitlementCheckNotRequired(config)) { - return false; - } - return (config.provisioningApp.length == 2); - } - - /** - * Re-check tethering provisioning for all enabled tether types. - * Reference TetheringManager.TETHERING_{@code *} for each tether type. - * - * @param config an object that encapsulates the various tethering configuration elements. - * Note: this method is only called from @{link Tethering.TetherMainSM} on the handler thread. - * If there are new callers from different threads, the logic should move to - * @{link Tethering.TetherMainSM} handler to avoid race conditions. - */ - public void reevaluateSimCardProvisioning(final TetheringConfiguration config) { - if (DBG) mLog.i("reevaluateSimCardProvisioning"); - - if (!mHandler.getLooper().isCurrentThread()) { - // Except for test, this log should not appear in normal flow. - mLog.log("reevaluateSimCardProvisioning() don't run in TetherMainSM thread"); - } - mEntitlementCacheValue.clear(); - mCurrentEntitlementResults.clear(); - - // TODO: refine provisioning check to isTetherProvisioningRequired() ?? - if (!config.hasMobileHotspotProvisionApp() - || carrierConfigAffirmsEntitlementCheckNotRequired(config)) { - evaluateCellularPermission(config); - return; - } - - if (mUsingCellularAsUpstream) { - maybeRunProvisioning(config); - } - } - - /** - * Get carrier configuration bundle. - * @param config an object that encapsulates the various tethering configuration elements. - * */ - public PersistableBundle getCarrierConfig(final TetheringConfiguration config) { - final CarrierConfigManager configManager = (CarrierConfigManager) mContext - .getSystemService(Context.CARRIER_CONFIG_SERVICE); - if (configManager == null) return null; - - final PersistableBundle carrierConfig = configManager.getConfigForSubId( - config.activeDataSubId); - - if (CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfig)) { - return carrierConfig; - } - - return null; - } - - // The logic here is aimed solely at confirming that a CarrierConfig exists - // and affirms that entitlement checks are not required. - // - // TODO: find a better way to express this, or alter the checking process - // entirely so that this is more intuitive. - private boolean carrierConfigAffirmsEntitlementCheckNotRequired( - final TetheringConfiguration config) { - // Check carrier config for entitlement checks - final PersistableBundle carrierConfig = getCarrierConfig(config); - if (carrierConfig == null) return false; - - // A CarrierConfigManager was found and it has a config. - final boolean isEntitlementCheckRequired = carrierConfig.getBoolean( - CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL); - return !isEntitlementCheckRequired; - } - - /** - * Run no UI tethering provisioning check. - * @param type tethering type from TetheringManager.TETHERING_{@code *} - * @param subId default data subscription ID. - */ - @VisibleForTesting - protected Intent runSilentTetherProvisioning(int type, final TetheringConfiguration config) { - if (DBG) mLog.i("runSilentTetherProvisioning: " + type); - // For silent provisioning, settings would stop tethering when entitlement fail. - ResultReceiver receiver = buildProxyReceiver(type, false/* notifyFail */, null); - - Intent intent = new Intent(); - intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); - intent.putExtra(EXTRA_RUN_PROVISION, true); - intent.putExtra(EXTRA_TETHER_SILENT_PROVISIONING_ACTION, config.provisioningAppNoUi); - intent.putExtra(EXTRA_TETHER_PROVISIONING_RESPONSE, config.provisioningResponse); - intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); - intent.putExtra(EXTRA_TETHER_SUBID, config.activeDataSubId); - intent.setComponent(mSilentProvisioningService); - // Only admin user can change tethering and SilentTetherProvisioning don't need to - // show UI, it is fine to always start setting's background service as system user. - mContext.startService(intent); - return intent; - } - - private void runUiTetherProvisioning(int type, final TetheringConfiguration config) { - ResultReceiver receiver = buildProxyReceiver(type, true/* notifyFail */, null); - runUiTetherProvisioning(type, config, receiver); - } - - /** - * Run the UI-enabled tethering provisioning check. - * @param type tethering type from TetheringManager.TETHERING_{@code *} - * @param subId default data subscription ID. - * @param receiver to receive entitlement check result. - */ - @VisibleForTesting - protected Intent runUiTetherProvisioning(int type, final TetheringConfiguration config, - ResultReceiver receiver) { - if (DBG) mLog.i("runUiTetherProvisioning: " + type); - - Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING_UI); - intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); - intent.putExtra(EXTRA_TETHER_UI_PROVISIONING_APP_NAME, config.provisioningApp); - intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); - intent.putExtra(EXTRA_TETHER_SUBID, config.activeDataSubId); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - // Only launch entitlement UI for system user. Entitlement UI should not appear for other - // user because only admin user is allowed to change tethering. - mContext.startActivity(intent); - return intent; - } - - // Not needed to check if this don't run on the handler thread because it's private. - private void scheduleProvisioningRechecks(final TetheringConfiguration config) { - if (mProvisioningRecheckAlarm == null) { - final int period = config.provisioningCheckPeriod; - if (period <= 0) return; - - Intent intent = new Intent(ACTION_PROVISIONING_ALARM); - mProvisioningRecheckAlarm = PendingIntent.getBroadcast(mContext, 0, intent, 0); - AlarmManager alarmManager = (AlarmManager) mContext.getSystemService( - Context.ALARM_SERVICE); - long periodMs = period * MS_PER_HOUR; - long firstAlarmTime = SystemClock.elapsedRealtime() + periodMs; - alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, firstAlarmTime, periodMs, - mProvisioningRecheckAlarm); - } - } - - private void cancelTetherProvisioningRechecks() { - if (mProvisioningRecheckAlarm != null) { - AlarmManager alarmManager = (AlarmManager) mContext.getSystemService( - Context.ALARM_SERVICE); - alarmManager.cancel(mProvisioningRecheckAlarm); - mProvisioningRecheckAlarm = null; - } - } - - private void evaluateCellularPermission(final TetheringConfiguration config) { - final boolean permitted = isCellularUpstreamPermitted(config); - - if (DBG) { - mLog.i("Cellular permission change from " + mLastCellularUpstreamPermitted - + " to " + permitted); - } - - if (mLastCellularUpstreamPermitted != permitted) { - mLog.log("Cellular permission change: " + permitted); - mPermissionChangeCallback.run(); - } - // Only schedule periodic re-check when tether is provisioned - // and the result is ok. - if (permitted && mCurrentEntitlementResults.size() > 0) { - scheduleProvisioningRechecks(config); - } else { - cancelTetherProvisioningRechecks(); - } - mLastCellularUpstreamPermitted = permitted; - } - - /** - * Add the mapping between provisioning result and tethering type. - * Notify UpstreamNetworkMonitor if Cellular permission changes. - * - * @param type tethering type from TetheringManager.TETHERING_{@code *} - * @param resultCode Provisioning result - */ - protected void addDownstreamMapping(int type, int resultCode) { - mLog.i("addDownstreamMapping: " + type + ", result: " + resultCode - + " ,TetherTypeRequested: " + mCurrentDownstreams.get(type)); - if (!mCurrentDownstreams.get(type)) return; - - mCurrentEntitlementResults.put(type, resultCode); - final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); - evaluateCellularPermission(config); - } - - /** - * Remove the mapping for input tethering type. - * @param type tethering type from TetheringManager.TETHERING_{@code *} - */ - protected void removeDownstreamMapping(int type) { - mLog.i("removeDownstreamMapping: " + type); - mCurrentEntitlementResults.delete(type); - final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); - evaluateCellularPermission(config); - } - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (ACTION_PROVISIONING_ALARM.equals(intent.getAction())) { - mLog.log("Received provisioning alarm"); - final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); - reevaluateSimCardProvisioning(config); - } - } - }; - - private static boolean isValidDownstreamType(int type) { - switch (type) { - case TETHERING_BLUETOOTH: - case TETHERING_ETHERNET: - case TETHERING_USB: - case TETHERING_WIFI: - return true; - default: - return false; - } - } - - /** - * Dump the infromation of EntitlementManager. - * @param pw {@link PrintWriter} is used to print formatted - */ - public void dump(PrintWriter pw) { - final ConditionVariable mWaiting = new ConditionVariable(); - mHandler.post(() -> { - pw.print("isCellularUpstreamPermitted: "); - pw.println(isCellularUpstreamPermitted()); - for (int type = mCurrentDownstreams.nextSetBit(0); type >= 0; - type = mCurrentDownstreams.nextSetBit(type + 1)) { - pw.print("Type: "); - pw.print(typeString(type)); - if (mCurrentEntitlementResults.indexOfKey(type) > -1) { - pw.print(", Value: "); - pw.println(errorString(mCurrentEntitlementResults.get(type))); - } else { - pw.println(", Value: empty"); - } - } - mWaiting.open(); - }); - if (!mWaiting.block(DUMP_TIMEOUT)) { - pw.println("... dump timed out after " + DUMP_TIMEOUT + "ms"); - } - pw.print("Exempted: ["); - for (int type = mExemptedDownstreams.nextSetBit(0); type >= 0; - type = mExemptedDownstreams.nextSetBit(type + 1)) { - pw.print(typeString(type)); - pw.print(", "); - } - pw.println("]"); - } - - private static String typeString(int type) { - switch (type) { - case TETHERING_BLUETOOTH: return "TETHERING_BLUETOOTH"; - case TETHERING_INVALID: return "TETHERING_INVALID"; - case TETHERING_USB: return "TETHERING_USB"; - case TETHERING_WIFI: return "TETHERING_WIFI"; - default: - return String.format("TETHERING UNKNOWN TYPE (%d)", type); - } - } - - private static String errorString(int value) { - switch (value) { - case TETHER_ERROR_ENTITLEMENT_UNKNOWN: return "TETHER_ERROR_ENTITLEMENT_UNKONWN"; - case TETHER_ERROR_NO_ERROR: return "TETHER_ERROR_NO_ERROR"; - case TETHER_ERROR_PROVISIONING_FAILED: return "TETHER_ERROR_PROVISIONING_FAILED"; - default: - return String.format("UNKNOWN ERROR (%d)", value); - } - } - - private ResultReceiver buildProxyReceiver(int type, boolean notifyFail, - final ResultReceiver receiver) { - ResultReceiver rr = new ResultReceiver(mHandler) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - int updatedCacheValue = updateEntitlementCacheValue(type, resultCode); - addDownstreamMapping(type, updatedCacheValue); - if (updatedCacheValue == TETHER_ERROR_PROVISIONING_FAILED && notifyFail) { - mListener.onUiEntitlementFailed(type); - } - if (receiver != null) receiver.send(updatedCacheValue, null); - } - }; - - return writeToParcel(rr); - } - - // Instances of ResultReceiver need to be public classes for remote processes to be able - // to load them (otherwise, ClassNotFoundException). For private classes, this method - // performs a trick : round-trip parceling any instance of ResultReceiver will return a - // vanilla instance of ResultReceiver sharing the binder token with the original receiver. - // The binder token has a reference to the original instance of the private class and will - // still call its methods, and can be sent over. However it cannot be used for anything - // else than sending over a Binder call. - // While round-trip parceling is not great, there is currently no other way of generating - // a vanilla instance of ResultReceiver because all its fields are private. - private ResultReceiver writeToParcel(final ResultReceiver receiver) { - Parcel parcel = Parcel.obtain(); - receiver.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - ResultReceiver receiverForSending = ResultReceiver.CREATOR.createFromParcel(parcel); - parcel.recycle(); - return receiverForSending; - } - - /** - * Update the last entitlement value to internal cache - * - * @param type tethering type from TetheringManager.TETHERING_{@code *} - * @param resultCode last entitlement value - * @return the last updated entitlement value - */ - private int updateEntitlementCacheValue(int type, int resultCode) { - if (DBG) { - mLog.i("updateEntitlementCacheValue: " + type + ", result: " + resultCode); - } - if (resultCode == TETHER_ERROR_NO_ERROR) { - mEntitlementCacheValue.put(type, resultCode); - return resultCode; - } else { - mEntitlementCacheValue.put(type, TETHER_ERROR_PROVISIONING_FAILED); - return TETHER_ERROR_PROVISIONING_FAILED; - } - } - - /** Get the last value of the tethering entitlement check. */ - public void requestLatestTetheringEntitlementResult(int downstream, ResultReceiver receiver, - boolean showEntitlementUi) { - if (!isValidDownstreamType(downstream)) { - receiver.send(TETHER_ERROR_ENTITLEMENT_UNKNOWN, null); - return; - } - - final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); - if (!isTetherProvisioningRequired(config)) { - receiver.send(TETHER_ERROR_NO_ERROR, null); - return; - } - - final int cacheValue = mEntitlementCacheValue.get( - downstream, TETHER_ERROR_ENTITLEMENT_UNKNOWN); - if (cacheValue == TETHER_ERROR_NO_ERROR || !showEntitlementUi) { - receiver.send(cacheValue, null); - } else { - ResultReceiver proxy = buildProxyReceiver(downstream, false/* notifyFail */, receiver); - runUiTetherProvisioning(downstream, config, proxy); - } - } -} diff --git a/packages/Tethering/src/com/android/networkstack/tethering/IPv6TetheringCoordinator.java b/packages/Tethering/src/com/android/networkstack/tethering/IPv6TetheringCoordinator.java deleted file mode 100644 index f3dcaa2529e7..000000000000 --- a/packages/Tethering/src/com/android/networkstack/tethering/IPv6TetheringCoordinator.java +++ /dev/null @@ -1,328 +0,0 @@ -/* - * Copyright (C) 2016 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.networkstack.tethering; - -import android.net.IpPrefix; -import android.net.LinkAddress; -import android.net.LinkProperties; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.RouteInfo; -import android.net.ip.IpServer; -import android.net.util.NetworkConstants; -import android.net.util.SharedLog; -import android.util.Log; - -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.Random; - - -/** - * IPv6 tethering is rather different from IPv4 owing to the absence of NAT. - * This coordinator is responsible for evaluating the dedicated prefixes - * assigned to the device and deciding how to divvy them up among downstream - * interfaces. - * - * @hide - */ -public class IPv6TetheringCoordinator { - private static final String TAG = IPv6TetheringCoordinator.class.getSimpleName(); - private static final boolean DBG = false; - private static final boolean VDBG = false; - - private static class Downstream { - public final IpServer ipServer; - public final int mode; // IpServer.STATE_* - // Used to append to a ULA /48, constructing a ULA /64 for local use. - public final short subnetId; - - Downstream(IpServer ipServer, int mode, short subnetId) { - this.ipServer = ipServer; - this.mode = mode; - this.subnetId = subnetId; - } - } - - private final ArrayList<IpServer> mNotifyList; - private final SharedLog mLog; - // NOTE: mActiveDownstreams is a list and not a hash data structure because - // we keep active downstreams in arrival order. This is done so /64s can - // be parceled out on a "first come, first served" basis and a /64 used by - // a downstream that is no longer active can be redistributed to any next - // waiting active downstream (again, in arrival order). - private final LinkedList<Downstream> mActiveDownstreams; - private final byte[] mUniqueLocalPrefix; - private short mNextSubnetId; - private UpstreamNetworkState mUpstreamNetworkState; - - public IPv6TetheringCoordinator(ArrayList<IpServer> notifyList, SharedLog log) { - mNotifyList = notifyList; - mLog = log.forSubComponent(TAG); - mActiveDownstreams = new LinkedList<>(); - mUniqueLocalPrefix = generateUniqueLocalPrefix(); - mNextSubnetId = 0; - } - - /** Add active downstream to ipv6 tethering candidate list. */ - public void addActiveDownstream(IpServer downstream, int mode) { - if (findDownstream(downstream) == null) { - // Adding a new downstream appends it to the list. Adding a - // downstream a second time without first removing it has no effect. - // We never change the mode of a downstream except by first removing - // it and then re-adding it (with its new mode specified); - if (mActiveDownstreams.offer(new Downstream(downstream, mode, mNextSubnetId))) { - // Make sure subnet IDs are always positive. They are appended - // to a ULA /48 to make a ULA /64 for local use. - mNextSubnetId = (short) Math.max(0, mNextSubnetId + 1); - } - updateIPv6TetheringInterfaces(); - } - } - - /** Remove downstream from ipv6 tethering candidate list. */ - public void removeActiveDownstream(IpServer downstream) { - stopIPv6TetheringOn(downstream); - if (mActiveDownstreams.remove(findDownstream(downstream))) { - updateIPv6TetheringInterfaces(); - } - - // When tethering is stopping we can reset the subnet counter. - if (mNotifyList.isEmpty()) { - if (!mActiveDownstreams.isEmpty()) { - Log.wtf(TAG, "Tethering notify list empty, IPv6 downstreams non-empty."); - } - mNextSubnetId = 0; - } - } - - /** - * Call when UpstreamNetworkState may be changed. - * If upstream has ipv6 for tethering, update this new UpstreamNetworkState - * to IpServer. Otherwise stop ipv6 tethering on downstream interfaces. - */ - public void updateUpstreamNetworkState(UpstreamNetworkState ns) { - if (VDBG) { - Log.d(TAG, "updateUpstreamNetworkState: " + toDebugString(ns)); - } - if (TetheringInterfaceUtils.getIPv6Interface(ns) == null) { - stopIPv6TetheringOnAllInterfaces(); - setUpstreamNetworkState(null); - return; - } - - if (mUpstreamNetworkState != null - && !ns.network.equals(mUpstreamNetworkState.network)) { - stopIPv6TetheringOnAllInterfaces(); - } - - setUpstreamNetworkState(ns); - updateIPv6TetheringInterfaces(); - } - - private void stopIPv6TetheringOnAllInterfaces() { - for (IpServer ipServer : mNotifyList) { - stopIPv6TetheringOn(ipServer); - } - } - - private void setUpstreamNetworkState(UpstreamNetworkState ns) { - if (ns == null) { - mUpstreamNetworkState = null; - } else { - // Make a deep copy of the parts we need. - mUpstreamNetworkState = new UpstreamNetworkState( - new LinkProperties(ns.linkProperties), - new NetworkCapabilities(ns.networkCapabilities), - new Network(ns.network)); - } - - mLog.log("setUpstreamNetworkState: " + toDebugString(mUpstreamNetworkState)); - } - - private void updateIPv6TetheringInterfaces() { - for (IpServer ipServer : mNotifyList) { - final LinkProperties lp = getInterfaceIPv6LinkProperties(ipServer); - ipServer.sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, getTtlAdjustment(), 0, lp); - break; - } - } - - private int getTtlAdjustment() { - if (mUpstreamNetworkState == null || mUpstreamNetworkState.networkCapabilities == null) { - return 0; - } - - // If upstream is cellular, set the TTL in Router Advertisements to "network-set TTL" - 1 - // for carrier requirement. - if (mUpstreamNetworkState.networkCapabilities.hasTransport( - NetworkCapabilities.TRANSPORT_CELLULAR)) { - return -1; - } - - // For other non-cellular upstream, set TTL as "network-set TTL" + 1 to preventing arbitrary - // distinction between tethered and untethered traffic. - return 1; - } - - private LinkProperties getInterfaceIPv6LinkProperties(IpServer ipServer) { - final Downstream ds = findDownstream(ipServer); - if (ds == null) return null; - - if (ds.mode == IpServer.STATE_LOCAL_ONLY) { - // Build a Unique Locally-assigned Prefix configuration. - return getUniqueLocalConfig(mUniqueLocalPrefix, ds.subnetId); - } - - // This downstream is in IpServer.STATE_TETHERED mode. - if (mUpstreamNetworkState == null || mUpstreamNetworkState.linkProperties == null) { - return null; - } - - // NOTE: Here, in future, we would have policies to decide how to divvy - // up the available dedicated prefixes among downstream interfaces. - // At this time we have no such mechanism--we only support tethering - // IPv6 toward the oldest (first requested) active downstream. - - final Downstream currentActive = mActiveDownstreams.peek(); - if (currentActive != null && currentActive.ipServer == ipServer) { - final LinkProperties lp = getIPv6OnlyLinkProperties( - mUpstreamNetworkState.linkProperties); - if (lp.hasIpv6DefaultRoute() && lp.hasGlobalIpv6Address()) { - return lp; - } - } - - return null; - } - - Downstream findDownstream(IpServer ipServer) { - for (Downstream ds : mActiveDownstreams) { - if (ds.ipServer == ipServer) return ds; - } - return null; - } - - private static LinkProperties getIPv6OnlyLinkProperties(LinkProperties lp) { - final LinkProperties v6only = new LinkProperties(); - if (lp == null) { - return v6only; - } - - // NOTE: At this time we don't copy over any information about any - // stacked links. No current stacked link configuration has IPv6. - - v6only.setInterfaceName(lp.getInterfaceName()); - - v6only.setMtu(lp.getMtu()); - - for (LinkAddress linkAddr : lp.getLinkAddresses()) { - if (linkAddr.isGlobalPreferred() && linkAddr.getPrefixLength() == 64) { - v6only.addLinkAddress(linkAddr); - } - } - - for (RouteInfo routeInfo : lp.getRoutes()) { - final IpPrefix destination = routeInfo.getDestination(); - if ((destination.getAddress() instanceof Inet6Address) - && (destination.getPrefixLength() <= 64)) { - v6only.addRoute(routeInfo); - } - } - - for (InetAddress dnsServer : lp.getDnsServers()) { - if (isIPv6GlobalAddress(dnsServer)) { - // For now we include ULAs. - v6only.addDnsServer(dnsServer); - } - } - - v6only.setDomains(lp.getDomains()); - - return v6only; - } - - // TODO: Delete this and switch to LinkAddress#isGlobalPreferred once we - // announce our own IPv6 address as DNS server. - private static boolean isIPv6GlobalAddress(InetAddress ip) { - return (ip instanceof Inet6Address) - && !ip.isAnyLocalAddress() - && !ip.isLoopbackAddress() - && !ip.isLinkLocalAddress() - && !ip.isSiteLocalAddress() - && !ip.isMulticastAddress(); - } - - private static LinkProperties getUniqueLocalConfig(byte[] ulp, short subnetId) { - final LinkProperties lp = new LinkProperties(); - - final IpPrefix local48 = makeUniqueLocalPrefix(ulp, (short) 0, 48); - lp.addRoute(new RouteInfo(local48, null, null, RouteInfo.RTN_UNICAST)); - - final IpPrefix local64 = makeUniqueLocalPrefix(ulp, subnetId, 64); - // Because this is a locally-generated ULA, we don't have an upstream - // address. But because the downstream IP address management code gets - // its prefix from the upstream's IP address, we create a fake one here. - lp.addLinkAddress(new LinkAddress(local64.getAddress(), 64)); - - lp.setMtu(NetworkConstants.ETHER_MTU); - return lp; - } - - private static IpPrefix makeUniqueLocalPrefix(byte[] in6addr, short subnetId, int prefixlen) { - final byte[] bytes = Arrays.copyOf(in6addr, in6addr.length); - bytes[7] = (byte) (subnetId >> 8); - bytes[8] = (byte) subnetId; - final InetAddress addr; - try { - addr = InetAddress.getByAddress(bytes); - } catch (UnknownHostException e) { - throw new IllegalStateException("Invalid address length: " + bytes.length, e); - } - return new IpPrefix(addr, prefixlen); - } - - // Generates a Unique Locally-assigned Prefix: - // - // https://tools.ietf.org/html/rfc4193#section-3.1 - // - // The result is a /48 that can be used for local-only communications. - private static byte[] generateUniqueLocalPrefix() { - final byte[] ulp = new byte[6]; // 6 = 48bits / 8bits/byte - (new Random()).nextBytes(ulp); - - final byte[] in6addr = Arrays.copyOf(ulp, NetworkConstants.IPV6_ADDR_LEN); - in6addr[0] = (byte) 0xfd; // fc00::/7 and L=1 - - return in6addr; - } - - private static String toDebugString(UpstreamNetworkState ns) { - if (ns == null) { - return "UpstreamNetworkState{null}"; - } - return ns.toString(); - } - - private static void stopIPv6TetheringOn(IpServer ipServer) { - ipServer.sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, 0, 0, null); - } -} diff --git a/packages/Tethering/src/com/android/networkstack/tethering/OffloadController.java b/packages/Tethering/src/com/android/networkstack/tethering/OffloadController.java deleted file mode 100644 index 88c77b07e7e3..000000000000 --- a/packages/Tethering/src/com/android/networkstack/tethering/OffloadController.java +++ /dev/null @@ -1,811 +0,0 @@ -/* - * Copyright (C) 2017 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.networkstack.tethering; - -import static android.net.NetworkStats.DEFAULT_NETWORK_NO; -import static android.net.NetworkStats.METERED_NO; -import static android.net.NetworkStats.ROAMING_NO; -import static android.net.NetworkStats.SET_DEFAULT; -import static android.net.NetworkStats.TAG_NONE; -import static android.net.NetworkStats.UID_ALL; -import static android.net.NetworkStats.UID_TETHERING; -import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED; -import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED; - -import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.usage.NetworkStatsManager; -import android.content.ContentResolver; -import android.net.InetAddresses; -import android.net.IpPrefix; -import android.net.LinkAddress; -import android.net.LinkProperties; -import android.net.NetworkStats; -import android.net.NetworkStats.Entry; -import android.net.RouteInfo; -import android.net.netlink.ConntrackMessage; -import android.net.netlink.NetlinkConstants; -import android.net.netlink.NetlinkSocket; -import android.net.netstats.provider.NetworkStatsProvider; -import android.net.util.SharedLog; -import android.os.Handler; -import android.provider.Settings; -import android.system.ErrnoException; -import android.system.OsConstants; -import android.text.TextUtils; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.IndentingPrintWriter; -import com.android.networkstack.tethering.OffloadHardwareInterface.ForwardedStats; - -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -/** - * A class to encapsulate the business logic of programming the tethering - * hardware offload interface. - * - * @hide - */ -public class OffloadController { - private static final String TAG = OffloadController.class.getSimpleName(); - private static final boolean DBG = false; - private static final String ANYIP = "0.0.0.0"; - private static final ForwardedStats EMPTY_STATS = new ForwardedStats(); - - @VisibleForTesting - enum StatsType { - STATS_PER_IFACE, - STATS_PER_UID, - } - - private enum UpdateType { IF_NEEDED, FORCE }; - - private final Handler mHandler; - private final OffloadHardwareInterface mHwInterface; - private final ContentResolver mContentResolver; - @Nullable - private final OffloadTetheringStatsProvider mStatsProvider; - private final SharedLog mLog; - private final HashMap<String, LinkProperties> mDownstreams; - private boolean mConfigInitialized; - private boolean mControlInitialized; - private LinkProperties mUpstreamLinkProperties; - // The complete set of offload-exempt prefixes passed in via Tethering from - // all upstream and downstream sources. - private Set<IpPrefix> mExemptPrefixes; - // A strictly "smaller" set of prefixes, wherein offload-approved prefixes - // (e.g. downstream on-link prefixes) have been removed and replaced with - // prefixes representing only the locally-assigned IP addresses. - private Set<String> mLastLocalPrefixStrs; - - // Maps upstream interface names to offloaded traffic statistics. - // Always contains the latest value received from the hardware for each interface, regardless of - // whether offload is currently running on that interface. - private ConcurrentHashMap<String, ForwardedStats> mForwardedStats = - new ConcurrentHashMap<>(16, 0.75F, 1); - - // Maps upstream interface names to interface quotas. - // Always contains the latest value received from the framework for each interface, regardless - // of whether offload is currently running (or is even supported) on that interface. Only - // includes upstream interfaces that have a quota set. - private HashMap<String, Long> mInterfaceQuotas = new HashMap<>(); - - // Tracking remaining alert quota. Unlike limit quota is subject to interface, the alert - // quota is interface independent and global for tether offload. Note that this is only - // accessed on the handler thread and in the constructor. - private long mRemainingAlertQuota = QUOTA_UNLIMITED; - // Runnable that used to schedule the next stats poll. - private final Runnable mScheduledPollingTask = () -> { - updateStatsForCurrentUpstream(); - maybeSchedulePollingStats(); - }; - - private int mNatUpdateCallbacksReceived; - private int mNatUpdateNetlinkErrors; - - @NonNull - private final Dependencies mDeps; - - // TODO: Put more parameters in constructor into dependency object. - interface Dependencies { - @NonNull - TetheringConfiguration getTetherConfig(); - } - - public OffloadController(Handler h, OffloadHardwareInterface hwi, - ContentResolver contentResolver, NetworkStatsManager nsm, SharedLog log, - @NonNull Dependencies deps) { - mHandler = h; - mHwInterface = hwi; - mContentResolver = contentResolver; - mLog = log.forSubComponent(TAG); - mDownstreams = new HashMap<>(); - mExemptPrefixes = new HashSet<>(); - mLastLocalPrefixStrs = new HashSet<>(); - OffloadTetheringStatsProvider provider = new OffloadTetheringStatsProvider(); - try { - nsm.registerNetworkStatsProvider(getClass().getSimpleName(), provider); - } catch (RuntimeException e) { - Log.wtf(TAG, "Cannot register offload stats provider: " + e); - provider = null; - } - mStatsProvider = provider; - mDeps = deps; - } - - /** Start hardware offload. */ - public boolean start() { - if (started()) return true; - - if (isOffloadDisabled()) { - mLog.i("tethering offload disabled"); - return false; - } - - if (!mConfigInitialized) { - mConfigInitialized = mHwInterface.initOffloadConfig(); - if (!mConfigInitialized) { - mLog.i("tethering offload config not supported"); - stop(); - return false; - } - } - - mControlInitialized = mHwInterface.initOffloadControl( - // OffloadHardwareInterface guarantees that these callback - // methods are called on the handler passed to it, which is the - // same as mHandler, as coordinated by the setup in Tethering. - new OffloadHardwareInterface.ControlCallback() { - @Override - public void onStarted() { - if (!started()) return; - mLog.log("onStarted"); - } - - @Override - public void onStoppedError() { - if (!started()) return; - mLog.log("onStoppedError"); - } - - @Override - public void onStoppedUnsupported() { - if (!started()) return; - mLog.log("onStoppedUnsupported"); - // Poll for statistics and trigger a sweep of tethering - // stats by observers. This might not succeed, but it's - // worth trying anyway. We need to do this because from - // this point on we continue with software forwarding, - // and we need to synchronize stats and limits between - // software and hardware forwarding. - updateStatsForAllUpstreams(); - if (mStatsProvider != null) mStatsProvider.pushTetherStats(); - } - - @Override - public void onSupportAvailable() { - if (!started()) return; - mLog.log("onSupportAvailable"); - - // [1] Poll for statistics and trigger a sweep of stats - // by observers. We need to do this to ensure that any - // limits set take into account any software tethering - // traffic that has been happening in the meantime. - updateStatsForAllUpstreams(); - if (mStatsProvider != null) mStatsProvider.pushTetherStats(); - // [2] (Re)Push all state. - computeAndPushLocalPrefixes(UpdateType.FORCE); - pushAllDownstreamState(); - pushUpstreamParameters(null); - } - - @Override - public void onStoppedLimitReached() { - if (!started()) return; - mLog.log("onStoppedLimitReached"); - - // We cannot reliably determine on which interface the limit was reached, - // because the HAL interface does not specify it. We cannot just use the - // current upstream, because that might have changed since the time that - // the HAL queued the callback. - // TODO: rev the HAL so that it provides an interface name. - - updateStatsForCurrentUpstream(); - if (mStatsProvider != null) { - mStatsProvider.pushTetherStats(); - // Push stats to service does not cause the service react to it - // immediately. Inform the service about limit reached. - mStatsProvider.notifyLimitReached(); - } - } - - @Override - public void onNatTimeoutUpdate(int proto, - String srcAddr, int srcPort, - String dstAddr, int dstPort) { - if (!started()) return; - updateNatTimeout(proto, srcAddr, srcPort, dstAddr, dstPort); - } - }); - - final boolean isStarted = started(); - if (!isStarted) { - mLog.i("tethering offload control not supported"); - stop(); - } else { - mLog.log("tethering offload started"); - mNatUpdateCallbacksReceived = 0; - mNatUpdateNetlinkErrors = 0; - maybeSchedulePollingStats(); - } - return isStarted; - } - - /** Stop hardware offload. */ - public void stop() { - // Completely stops tethering offload. After this method is called, it is no longer safe to - // call any HAL method, no callbacks from the hardware will be delivered, and any in-flight - // callbacks must be ignored. Offload may be started again by calling start(). - final boolean wasStarted = started(); - updateStatsForCurrentUpstream(); - mUpstreamLinkProperties = null; - mHwInterface.stopOffloadControl(); - mControlInitialized = false; - mConfigInitialized = false; - if (mHandler.hasCallbacks(mScheduledPollingTask)) { - mHandler.removeCallbacks(mScheduledPollingTask); - } - if (wasStarted) mLog.log("tethering offload stopped"); - } - - private boolean started() { - return mConfigInitialized && mControlInitialized; - } - - @VisibleForTesting - class OffloadTetheringStatsProvider extends NetworkStatsProvider { - // These stats must only ever be touched on the handler thread. - @NonNull - private NetworkStats mIfaceStats = new NetworkStats(0L, 0); - @NonNull - private NetworkStats mUidStats = new NetworkStats(0L, 0); - - /** - * A helper function that collect tether stats from local hashmap. Note that this does not - * invoke binder call. - */ - @VisibleForTesting - @NonNull - NetworkStats getTetherStats(@NonNull StatsType how) { - NetworkStats stats = new NetworkStats(0L, 0); - final int uid = (how == StatsType.STATS_PER_UID) ? UID_TETHERING : UID_ALL; - - for (final Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) { - final ForwardedStats value = kv.getValue(); - final Entry entry = new Entry(kv.getKey(), uid, SET_DEFAULT, TAG_NONE, METERED_NO, - ROAMING_NO, DEFAULT_NETWORK_NO, value.rxBytes, 0L, value.txBytes, 0L, 0L); - stats = stats.addEntry(entry); - } - - return stats; - } - - @Override - public void onSetLimit(String iface, long quotaBytes) { - // Listen for all iface is necessary since upstream might be changed after limit - // is set. - mHandler.post(() -> { - final Long curIfaceQuota = mInterfaceQuotas.get(iface); - - // If the quota is set to unlimited, the value set to HAL is Long.MAX_VALUE, - // which is ~8.4 x 10^6 TiB, no one can actually reach it. Thus, it is not - // useful to set it multiple times. - // Otherwise, the quota needs to be updated to tell HAL to re-count from now even - // if the quota is the same as the existing one. - if (null == curIfaceQuota && QUOTA_UNLIMITED == quotaBytes) return; - - if (quotaBytes == QUOTA_UNLIMITED) { - mInterfaceQuotas.remove(iface); - } else { - mInterfaceQuotas.put(iface, quotaBytes); - } - maybeUpdateDataLimit(iface); - }); - } - - /** - * Push stats to service, but does not cause a force polling. Note that this can only be - * called on the handler thread. - */ - public void pushTetherStats() { - // TODO: remove the accumulated stats and report the diff from HAL directly. - final NetworkStats ifaceDiff = - getTetherStats(StatsType.STATS_PER_IFACE).subtract(mIfaceStats); - final NetworkStats uidDiff = - getTetherStats(StatsType.STATS_PER_UID).subtract(mUidStats); - try { - notifyStatsUpdated(0 /* token */, ifaceDiff, uidDiff); - mIfaceStats = mIfaceStats.add(ifaceDiff); - mUidStats = mUidStats.add(uidDiff); - } catch (RuntimeException e) { - mLog.e("Cannot report network stats: ", e); - } - } - - @Override - public void onRequestStatsUpdate(int token) { - // Do not attempt to update stats by querying the offload HAL - // synchronously from a different thread than the Handler thread. http://b/64771555. - mHandler.post(() -> { - updateStatsForCurrentUpstream(); - pushTetherStats(); - }); - } - - @Override - public void onSetAlert(long quotaBytes) { - // TODO: Ask offload HAL to notify alert without stopping traffic. - // Post it to handler thread since it access remaining quota bytes. - mHandler.post(() -> { - updateAlertQuota(quotaBytes); - maybeSchedulePollingStats(); - }); - } - } - - private String currentUpstreamInterface() { - return (mUpstreamLinkProperties != null) - ? mUpstreamLinkProperties.getInterfaceName() : null; - } - - private void maybeUpdateStats(String iface) { - if (TextUtils.isEmpty(iface)) { - return; - } - - // Always called on the handler thread. - // - // Use get()/put() instead of updating ForwardedStats in place because we can be called - // concurrently with getTetherStats. In combination with the guarantees provided by - // ConcurrentHashMap, this ensures that getTetherStats always gets the most recent copy of - // the stats for each interface, and does not observe partial writes where rxBytes is - // updated and txBytes is not. - ForwardedStats diff = mHwInterface.getForwardedStats(iface); - final long usedAlertQuota = diff.rxBytes + diff.txBytes; - ForwardedStats base = mForwardedStats.get(iface); - if (base != null) { - diff.add(base); - } - - // Update remaining alert quota if it is still positive. - if (mRemainingAlertQuota > 0 && usedAlertQuota > 0) { - // Trim to zero if overshoot. - final long newQuota = Math.max(mRemainingAlertQuota - usedAlertQuota, 0); - updateAlertQuota(newQuota); - } - - mForwardedStats.put(iface, diff); - // diff is a new object, just created by getForwardedStats(). Therefore, anyone reading from - // mForwardedStats (i.e., any caller of getTetherStats) will see the new stats immediately. - } - - /** - * Update remaining alert quota, fire the {@link NetworkStatsProvider#notifyAlertReached()} - * callback when it reaches zero. This can be invoked either from service setting the alert, or - * {@code maybeUpdateStats} when updating stats. Note that this can be only called on - * handler thread. - * - * @param newQuota non-negative value to indicate the new quota, or - * {@link NetworkStatsProvider#QUOTA_UNLIMITED} to indicate there is no - * quota. - */ - private void updateAlertQuota(long newQuota) { - if (newQuota < QUOTA_UNLIMITED) { - throw new IllegalArgumentException("invalid quota value " + newQuota); - } - if (mRemainingAlertQuota == newQuota) return; - - mRemainingAlertQuota = newQuota; - if (mRemainingAlertQuota == 0) { - mLog.i("notifyAlertReached"); - if (mStatsProvider != null) mStatsProvider.notifyAlertReached(); - } - } - - /** - * Schedule polling if needed, this will be stopped if offload has been - * stopped or remaining quota reaches zero or upstream is empty. - * Note that this can be only called on handler thread. - */ - private void maybeSchedulePollingStats() { - if (!isPollingStatsNeeded()) return; - - if (mHandler.hasCallbacks(mScheduledPollingTask)) { - mHandler.removeCallbacks(mScheduledPollingTask); - } - mHandler.postDelayed(mScheduledPollingTask, - mDeps.getTetherConfig().getOffloadPollInterval()); - } - - private boolean isPollingStatsNeeded() { - return started() && mRemainingAlertQuota > 0 - && !TextUtils.isEmpty(currentUpstreamInterface()) - && mDeps.getTetherConfig() != null - && mDeps.getTetherConfig().getOffloadPollInterval() - >= DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS; - } - - private boolean maybeUpdateDataLimit(String iface) { - // setDataLimit may only be called while offload is occurring on this upstream. - if (!started() || !TextUtils.equals(iface, currentUpstreamInterface())) { - return true; - } - - Long limit = mInterfaceQuotas.get(iface); - if (limit == null) { - limit = Long.MAX_VALUE; - } - - return mHwInterface.setDataLimit(iface, limit); - } - - private void updateStatsForCurrentUpstream() { - maybeUpdateStats(currentUpstreamInterface()); - } - - private void updateStatsForAllUpstreams() { - // In practice, there should only ever be a single digit number of - // upstream interfaces over the lifetime of an active tethering session. - // Roughly speaking, imagine a very ambitious one or two of each of the - // following interface types: [ "rmnet_data", "wlan", "eth", "rndis" ]. - for (Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) { - maybeUpdateStats(kv.getKey()); - } - } - - /** Set current tethering upstream LinkProperties. */ - public void setUpstreamLinkProperties(LinkProperties lp) { - if (!started() || Objects.equals(mUpstreamLinkProperties, lp)) return; - - final String prevUpstream = currentUpstreamInterface(); - - mUpstreamLinkProperties = (lp != null) ? new LinkProperties(lp) : null; - // Make sure we record this interface in the ForwardedStats map. - final String iface = currentUpstreamInterface(); - if (!TextUtils.isEmpty(iface)) mForwardedStats.putIfAbsent(iface, EMPTY_STATS); - - maybeSchedulePollingStats(); - - // TODO: examine return code and decide what to do if programming - // upstream parameters fails (probably just wait for a subsequent - // onOffloadEvent() callback to tell us offload is available again and - // then reapply all state). - computeAndPushLocalPrefixes(UpdateType.IF_NEEDED); - pushUpstreamParameters(prevUpstream); - } - - /** Set local prefixes. */ - public void setLocalPrefixes(Set<IpPrefix> localPrefixes) { - mExemptPrefixes = localPrefixes; - - if (!started()) return; - computeAndPushLocalPrefixes(UpdateType.IF_NEEDED); - } - - /** Update current downstream LinkProperties. */ - public void notifyDownstreamLinkProperties(LinkProperties lp) { - final String ifname = lp.getInterfaceName(); - final LinkProperties oldLp = mDownstreams.put(ifname, new LinkProperties(lp)); - if (Objects.equals(oldLp, lp)) return; - - if (!started()) return; - pushDownstreamState(oldLp, lp); - } - - private void pushDownstreamState(LinkProperties oldLp, LinkProperties newLp) { - final String ifname = newLp.getInterfaceName(); - final List<RouteInfo> oldRoutes = - (oldLp != null) ? oldLp.getRoutes() : Collections.EMPTY_LIST; - final List<RouteInfo> newRoutes = newLp.getRoutes(); - - // For each old route, if not in new routes: remove. - for (RouteInfo ri : oldRoutes) { - if (shouldIgnoreDownstreamRoute(ri)) continue; - if (!newRoutes.contains(ri)) { - mHwInterface.removeDownstreamPrefix(ifname, ri.getDestination().toString()); - } - } - - // For each new route, if not in old routes: add. - for (RouteInfo ri : newRoutes) { - if (shouldIgnoreDownstreamRoute(ri)) continue; - if (!oldRoutes.contains(ri)) { - mHwInterface.addDownstreamPrefix(ifname, ri.getDestination().toString()); - } - } - } - - private void pushAllDownstreamState() { - for (LinkProperties lp : mDownstreams.values()) { - pushDownstreamState(null, lp); - } - } - - /** Remove downstream interface from offload hardware. */ - public void removeDownstreamInterface(String ifname) { - final LinkProperties lp = mDownstreams.remove(ifname); - if (lp == null) return; - - if (!started()) return; - - for (RouteInfo route : lp.getRoutes()) { - if (shouldIgnoreDownstreamRoute(route)) continue; - mHwInterface.removeDownstreamPrefix(ifname, route.getDestination().toString()); - } - } - - private boolean isOffloadDisabled() { - final int defaultDisposition = mHwInterface.getDefaultTetherOffloadDisabled(); - return (Settings.Global.getInt( - mContentResolver, TETHER_OFFLOAD_DISABLED, defaultDisposition) != 0); - } - - private boolean pushUpstreamParameters(String prevUpstream) { - final String iface = currentUpstreamInterface(); - - if (TextUtils.isEmpty(iface)) { - final boolean rval = mHwInterface.setUpstreamParameters("", ANYIP, ANYIP, null); - // Update stats after we've told the hardware to stop forwarding so - // we don't miss packets. - maybeUpdateStats(prevUpstream); - return rval; - } - - // A stacked interface cannot be an upstream for hardware offload. - // Consequently, we examine only the primary interface name, look at - // getAddresses() rather than getAllAddresses(), and check getRoutes() - // rather than getAllRoutes(). - final ArrayList<String> v6gateways = new ArrayList<>(); - String v4addr = null; - String v4gateway = null; - - for (InetAddress ip : mUpstreamLinkProperties.getAddresses()) { - if (ip instanceof Inet4Address) { - v4addr = ip.getHostAddress(); - break; - } - } - - // Find the gateway addresses of all default routes of either address family. - for (RouteInfo ri : mUpstreamLinkProperties.getRoutes()) { - if (!ri.hasGateway()) continue; - - final String gateway = ri.getGateway().getHostAddress(); - final InetAddress address = ri.getDestination().getAddress(); - if (ri.isDefaultRoute() && address instanceof Inet4Address) { - v4gateway = gateway; - } else if (ri.isDefaultRoute() && address instanceof Inet6Address) { - v6gateways.add(gateway); - } - } - - boolean success = mHwInterface.setUpstreamParameters( - iface, v4addr, v4gateway, (v6gateways.isEmpty() ? null : v6gateways)); - - if (!success) { - return success; - } - - // Update stats after we've told the hardware to change routing so we don't miss packets. - maybeUpdateStats(prevUpstream); - - // Data limits can only be set once offload is running on the upstream. - success = maybeUpdateDataLimit(iface); - if (!success) { - // If we failed to set a data limit, don't use this upstream, because we don't want to - // blow through the data limit that we were told to apply. - mLog.log("Setting data limit for " + iface + " failed, disabling offload."); - stop(); - } - - return success; - } - - private boolean computeAndPushLocalPrefixes(UpdateType how) { - final boolean force = (how == UpdateType.FORCE); - final Set<String> localPrefixStrs = computeLocalPrefixStrings( - mExemptPrefixes, mUpstreamLinkProperties); - if (!force && mLastLocalPrefixStrs.equals(localPrefixStrs)) return true; - - mLastLocalPrefixStrs = localPrefixStrs; - return mHwInterface.setLocalPrefixes(new ArrayList<>(localPrefixStrs)); - } - - // TODO: Factor in downstream LinkProperties once that information is available. - private static Set<String> computeLocalPrefixStrings( - Set<IpPrefix> localPrefixes, LinkProperties upstreamLinkProperties) { - // Create an editable copy. - final Set<IpPrefix> prefixSet = new HashSet<>(localPrefixes); - - // TODO: If a downstream interface (not currently passed in) is reusing - // the /64 of the upstream (64share) then: - // - // [a] remove that /64 from the local prefixes - // [b] add in /128s for IP addresses on the downstream interface - // [c] add in /128s for IP addresses on the upstream interface - // - // Until downstream information is available here, simply add /128s from - // the upstream network; they'll just be redundant with their /64. - if (upstreamLinkProperties != null) { - for (LinkAddress linkAddr : upstreamLinkProperties.getLinkAddresses()) { - if (!linkAddr.isGlobalPreferred()) continue; - final InetAddress ip = linkAddr.getAddress(); - if (!(ip instanceof Inet6Address)) continue; - prefixSet.add(new IpPrefix(ip, 128)); - } - } - - final HashSet<String> localPrefixStrs = new HashSet<>(); - for (IpPrefix pfx : prefixSet) localPrefixStrs.add(pfx.toString()); - return localPrefixStrs; - } - - private static boolean shouldIgnoreDownstreamRoute(RouteInfo route) { - // Ignore any link-local routes. - final IpPrefix destination = route.getDestination(); - final LinkAddress linkAddr = new LinkAddress(destination.getAddress(), - destination.getPrefixLength()); - if (!linkAddr.isGlobalPreferred()) return true; - - return false; - } - - /** Dump information. */ - public void dump(IndentingPrintWriter pw) { - if (isOffloadDisabled()) { - pw.println("Offload disabled"); - return; - } - final boolean isStarted = started(); - pw.println("Offload HALs " + (isStarted ? "started" : "not started")); - LinkProperties lp = mUpstreamLinkProperties; - String upstream = (lp != null) ? lp.getInterfaceName() : null; - pw.println("Current upstream: " + upstream); - pw.println("Exempt prefixes: " + mLastLocalPrefixStrs); - pw.println("NAT timeout update callbacks received during the " - + (isStarted ? "current" : "last") - + " offload session: " - + mNatUpdateCallbacksReceived); - pw.println("NAT timeout update netlink errors during the " - + (isStarted ? "current" : "last") - + " offload session: " - + mNatUpdateNetlinkErrors); - } - - private void updateNatTimeout( - int proto, String srcAddr, int srcPort, String dstAddr, int dstPort) { - final String protoName = protoNameFor(proto); - if (protoName == null) { - mLog.e("Unknown NAT update callback protocol: " + proto); - return; - } - - final Inet4Address src = parseIPv4Address(srcAddr); - if (src == null) { - mLog.e("Failed to parse IPv4 address: " + srcAddr); - return; - } - - if (!isValidUdpOrTcpPort(srcPort)) { - mLog.e("Invalid src port: " + srcPort); - return; - } - - final Inet4Address dst = parseIPv4Address(dstAddr); - if (dst == null) { - mLog.e("Failed to parse IPv4 address: " + dstAddr); - return; - } - - if (!isValidUdpOrTcpPort(dstPort)) { - mLog.e("Invalid dst port: " + dstPort); - return; - } - - mNatUpdateCallbacksReceived++; - final String natDescription = String.format("%s (%s, %s) -> (%s, %s)", - protoName, srcAddr, srcPort, dstAddr, dstPort); - if (DBG) { - mLog.log("NAT timeout update: " + natDescription); - } - - final int timeoutSec = connectionTimeoutUpdateSecondsFor(proto); - final byte[] msg = ConntrackMessage.newIPv4TimeoutUpdateRequest( - proto, src, srcPort, dst, dstPort, timeoutSec); - - try { - NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_NETFILTER, msg); - } catch (ErrnoException e) { - mNatUpdateNetlinkErrors++; - mLog.e("Error updating NAT conntrack entry >" + natDescription + "<: " + e - + ", msg: " + NetlinkConstants.hexify(msg)); - mLog.log("NAT timeout update callbacks received: " + mNatUpdateCallbacksReceived); - mLog.log("NAT timeout update netlink errors: " + mNatUpdateNetlinkErrors); - } - } - - private static Inet4Address parseIPv4Address(String addrString) { - try { - final InetAddress ip = InetAddresses.parseNumericAddress(addrString); - // TODO: Consider other sanitization steps here, including perhaps: - // not eql to 0.0.0.0 - // not within 169.254.0.0/16 - // not within ::ffff:0.0.0.0/96 - // not within ::/96 - // et cetera. - if (ip instanceof Inet4Address) { - return (Inet4Address) ip; - } - } catch (IllegalArgumentException iae) { } - return null; - } - - private static String protoNameFor(int proto) { - // OsConstants values are not constant expressions; no switch statement. - if (proto == OsConstants.IPPROTO_UDP) { - return "UDP"; - } else if (proto == OsConstants.IPPROTO_TCP) { - return "TCP"; - } - return null; - } - - private static int connectionTimeoutUpdateSecondsFor(int proto) { - // TODO: Replace this with more thoughtful work, perhaps reading from - // and maybe writing to any required - // - // /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_* - // /proc/sys/net/netfilter/nf_conntrack_udp_timeout{,_stream} - // - // entries. TBD. - if (proto == OsConstants.IPPROTO_TCP) { - // Cf. /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established - return 432000; - } else { - // Cf. /proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream - return 180; - } - } - - private static boolean isValidUdpOrTcpPort(int port) { - return port > 0 && port < 65536; - } -} diff --git a/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java deleted file mode 100644 index da5f25b2a596..000000000000 --- a/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java +++ /dev/null @@ -1,574 +0,0 @@ -/* - * Copyright (C) 2017 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.networkstack.tethering; - -import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP; -import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST; -import static android.net.util.TetheringUtils.uint16; - -import android.annotation.NonNull; -import android.hardware.tetheroffload.config.V1_0.IOffloadConfig; -import android.hardware.tetheroffload.control.V1_0.IOffloadControl; -import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback; -import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate; -import android.hardware.tetheroffload.control.V1_0.NetworkProtocol; -import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent; -import android.net.netlink.NetlinkSocket; -import android.net.netlink.StructNfGenMsg; -import android.net.netlink.StructNlMsgHdr; -import android.net.util.SharedLog; -import android.net.util.SocketUtils; -import android.os.Handler; -import android.os.NativeHandle; -import android.os.RemoteException; -import android.system.ErrnoException; -import android.system.Os; -import android.system.OsConstants; - -import com.android.internal.annotations.VisibleForTesting; - -import java.io.FileDescriptor; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.net.SocketAddress; -import java.net.SocketException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.NoSuchElementException; - - -/** - * Capture tethering dependencies, for injection. - * - * @hide - */ -public class OffloadHardwareInterface { - private static final String TAG = OffloadHardwareInterface.class.getSimpleName(); - private static final String YIELDS = " -> "; - // Change this value to control whether tether offload is enabled or - // disabled by default in the absence of an explicit Settings value. - // See accompanying unittest to distinguish 0 from non-0 values. - private static final int DEFAULT_TETHER_OFFLOAD_DISABLED = 0; - private static final String NO_INTERFACE_NAME = ""; - private static final String NO_IPV4_ADDRESS = ""; - private static final String NO_IPV4_GATEWAY = ""; - // Reference kernel/uapi/linux/netfilter/nfnetlink_compat.h - public static final int NF_NETLINK_CONNTRACK_NEW = 1; - public static final int NF_NETLINK_CONNTRACK_UPDATE = 2; - public static final int NF_NETLINK_CONNTRACK_DESTROY = 4; - // Reference libnetfilter_conntrack/linux_nfnetlink_conntrack.h - public static final short NFNL_SUBSYS_CTNETLINK = 1; - public static final short IPCTNL_MSG_CT_NEW = 0; - public static final short IPCTNL_MSG_CT_GET = 1; - - private final long NETLINK_MESSAGE_TIMEOUT_MS = 500; - - private final Handler mHandler; - private final SharedLog mLog; - private final Dependencies mDeps; - private IOffloadControl mOffloadControl; - private TetheringOffloadCallback mTetheringOffloadCallback; - private ControlCallback mControlCallback; - - /** The callback to notify status of offload management process. */ - public static class ControlCallback { - /** Offload started. */ - public void onStarted() {} - /** - * Offload stopped because an error has occurred in lower layer. - */ - public void onStoppedError() {} - /** - * Offload stopped because the device has moved to a bearer on which hardware offload is - * not supported. Subsequent calls to setUpstreamParameters and add/removeDownstream will - * likely fail and cannot be presumed to be saved inside of the hardware management process. - * Upon receiving #onSupportAvailable(), the caller should reprogram the hardware to begin - * offload again. - */ - public void onStoppedUnsupported() {} - /** Indicate that offload is able to proivde support for this time. */ - public void onSupportAvailable() {} - /** Offload stopped because of usage limit reached. */ - public void onStoppedLimitReached() {} - - /** Indicate to update NAT timeout. */ - public void onNatTimeoutUpdate(int proto, - String srcAddr, int srcPort, - String dstAddr, int dstPort) {} - } - - /** The object which records Tx/Rx forwarded bytes. */ - public static class ForwardedStats { - public long rxBytes; - public long txBytes; - - public ForwardedStats() { - rxBytes = 0; - txBytes = 0; - } - - @VisibleForTesting - public ForwardedStats(long rxBytes, long txBytes) { - this.rxBytes = rxBytes; - this.txBytes = txBytes; - } - - /** Add Tx/Rx bytes. */ - public void add(ForwardedStats other) { - rxBytes += other.rxBytes; - txBytes += other.txBytes; - } - - /** Returns the string representation of this object. */ - public String toString() { - return String.format("rx:%s tx:%s", rxBytes, txBytes); - } - } - - public OffloadHardwareInterface(Handler h, SharedLog log) { - this(h, log, new Dependencies(log)); - } - - OffloadHardwareInterface(Handler h, SharedLog log, Dependencies deps) { - mHandler = h; - mLog = log.forSubComponent(TAG); - mDeps = deps; - } - - /** Capture OffloadHardwareInterface dependencies, for injection. */ - static class Dependencies { - private final SharedLog mLog; - - Dependencies(SharedLog log) { - mLog = log; - } - - public IOffloadConfig getOffloadConfig() { - try { - return IOffloadConfig.getService(true /*retry*/); - } catch (RemoteException | NoSuchElementException e) { - mLog.e("getIOffloadConfig error " + e); - return null; - } - } - - public IOffloadControl getOffloadControl() { - try { - return IOffloadControl.getService(true /*retry*/); - } catch (RemoteException | NoSuchElementException e) { - mLog.e("tethering offload control not supported: " + e); - return null; - } - } - - public NativeHandle createConntrackSocket(final int groups) { - final FileDescriptor fd; - try { - fd = NetlinkSocket.forProto(OsConstants.NETLINK_NETFILTER); - } catch (ErrnoException e) { - mLog.e("Unable to create conntrack socket " + e); - return null; - } - - final SocketAddress sockAddr = SocketUtils.makeNetlinkSocketAddress(0, groups); - try { - Os.bind(fd, sockAddr); - } catch (ErrnoException | SocketException e) { - mLog.e("Unable to bind conntrack socket for groups " + groups + " error: " + e); - try { - SocketUtils.closeSocket(fd); - } catch (IOException ie) { - // Nothing we can do here - } - return null; - } - try { - Os.connect(fd, sockAddr); - } catch (ErrnoException | SocketException e) { - mLog.e("connect to kernel fail for groups " + groups + " error: " + e); - try { - SocketUtils.closeSocket(fd); - } catch (IOException ie) { - // Nothing we can do here - } - return null; - } - - return new NativeHandle(fd, true); - } - } - - /** Get default value indicating whether offload is supported. */ - public int getDefaultTetherOffloadDisabled() { - return DEFAULT_TETHER_OFFLOAD_DISABLED; - } - - /** - * Offload management process need to know conntrack rules to support NAT, but it may not have - * permission to create netlink netfilter sockets. Create two netlink netfilter sockets and - * share them with offload management process. - */ - public boolean initOffloadConfig() { - final IOffloadConfig offloadConfig = mDeps.getOffloadConfig(); - if (offloadConfig == null) { - mLog.e("Could not find IOffloadConfig service"); - return false; - } - // Per the IConfigOffload definition: - // - // h1 provides a file descriptor bound to the following netlink groups - // (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY). - // - // h2 provides a file descriptor bound to the following netlink groups - // (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY). - final NativeHandle h1 = mDeps.createConntrackSocket( - NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY); - if (h1 == null) return false; - - sendIpv4NfGenMsg(h1, (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET), - (short) (NLM_F_REQUEST | NLM_F_DUMP)); - - final NativeHandle h2 = mDeps.createConntrackSocket( - NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY); - if (h2 == null) { - closeFdInNativeHandle(h1); - return false; - } - - final CbResults results = new CbResults(); - try { - offloadConfig.setHandles(h1, h2, - (boolean success, String errMsg) -> { - results.mSuccess = success; - results.mErrMsg = errMsg; - }); - } catch (RemoteException e) { - record("initOffloadConfig, setHandles fail", e); - return false; - } - // Explicitly close FDs. - closeFdInNativeHandle(h1); - closeFdInNativeHandle(h2); - - record("initOffloadConfig, setHandles results:", results); - return results.mSuccess; - } - - @VisibleForTesting - public void sendIpv4NfGenMsg(@NonNull NativeHandle handle, short type, short flags) { - final int length = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE; - final byte[] msg = new byte[length]; - final ByteBuffer byteBuffer = ByteBuffer.wrap(msg); - byteBuffer.order(ByteOrder.nativeOrder()); - - final StructNlMsgHdr nlh = new StructNlMsgHdr(); - nlh.nlmsg_len = length; - nlh.nlmsg_type = type; - nlh.nlmsg_flags = flags; - nlh.nlmsg_seq = 0; - nlh.pack(byteBuffer); - - // Header needs to be added to buffer since a generic netlink request is being sent. - final StructNfGenMsg nfh = new StructNfGenMsg((byte) OsConstants.AF_INET); - nfh.pack(byteBuffer); - - try { - NetlinkSocket.sendMessage(handle.getFileDescriptor(), msg, 0 /* offset */, length, - NETLINK_MESSAGE_TIMEOUT_MS); - } catch (ErrnoException | InterruptedIOException e) { - mLog.e("Unable to send netfilter message, error: " + e); - } - } - - private void closeFdInNativeHandle(final NativeHandle h) { - try { - h.close(); - } catch (IOException | IllegalStateException e) { - // IllegalStateException means fd is already closed, do nothing here. - // Also nothing we can do if IOException. - } - } - - /** Initialize the tethering offload HAL. */ - public boolean initOffloadControl(ControlCallback controlCb) { - mControlCallback = controlCb; - - if (mOffloadControl == null) { - mOffloadControl = mDeps.getOffloadControl(); - if (mOffloadControl == null) { - mLog.e("tethering IOffloadControl.getService() returned null"); - return false; - } - } - - final String logmsg = String.format("initOffloadControl(%s)", - (controlCb == null) ? "null" - : "0x" + Integer.toHexString(System.identityHashCode(controlCb))); - - mTetheringOffloadCallback = new TetheringOffloadCallback(mHandler, mControlCallback, mLog); - final CbResults results = new CbResults(); - try { - mOffloadControl.initOffload( - mTetheringOffloadCallback, - (boolean success, String errMsg) -> { - results.mSuccess = success; - results.mErrMsg = errMsg; - }); - } catch (RemoteException e) { - record(logmsg, e); - return false; - } - - record(logmsg, results); - return results.mSuccess; - } - - /** Stop IOffloadControl. */ - public void stopOffloadControl() { - if (mOffloadControl != null) { - try { - mOffloadControl.stopOffload( - (boolean success, String errMsg) -> { - if (!success) mLog.e("stopOffload failed: " + errMsg); - }); - } catch (RemoteException e) { - mLog.e("failed to stopOffload: " + e); - } - } - mOffloadControl = null; - mTetheringOffloadCallback = null; - mControlCallback = null; - mLog.log("stopOffloadControl()"); - } - - /** Get Tx/Rx usage from last query. */ - public ForwardedStats getForwardedStats(String upstream) { - final String logmsg = String.format("getForwardedStats(%s)", upstream); - - final ForwardedStats stats = new ForwardedStats(); - try { - mOffloadControl.getForwardedStats( - upstream, - (long rxBytes, long txBytes) -> { - stats.rxBytes = (rxBytes > 0) ? rxBytes : 0; - stats.txBytes = (txBytes > 0) ? txBytes : 0; - }); - } catch (RemoteException e) { - record(logmsg, e); - return stats; - } - - return stats; - } - - /** Set local prefixes to offload management process. */ - public boolean setLocalPrefixes(ArrayList<String> localPrefixes) { - final String logmsg = String.format("setLocalPrefixes([%s])", - String.join(",", localPrefixes)); - - final CbResults results = new CbResults(); - try { - mOffloadControl.setLocalPrefixes(localPrefixes, - (boolean success, String errMsg) -> { - results.mSuccess = success; - results.mErrMsg = errMsg; - }); - } catch (RemoteException e) { - record(logmsg, e); - return false; - } - - record(logmsg, results); - return results.mSuccess; - } - - /** Set data limit value to offload management process. */ - public boolean setDataLimit(String iface, long limit) { - - final String logmsg = String.format("setDataLimit(%s, %d)", iface, limit); - - final CbResults results = new CbResults(); - try { - mOffloadControl.setDataLimit( - iface, limit, - (boolean success, String errMsg) -> { - results.mSuccess = success; - results.mErrMsg = errMsg; - }); - } catch (RemoteException e) { - record(logmsg, e); - return false; - } - - record(logmsg, results); - return results.mSuccess; - } - - /** Set upstream parameters to offload management process. */ - public boolean setUpstreamParameters( - String iface, String v4addr, String v4gateway, ArrayList<String> v6gws) { - iface = (iface != null) ? iface : NO_INTERFACE_NAME; - v4addr = (v4addr != null) ? v4addr : NO_IPV4_ADDRESS; - v4gateway = (v4gateway != null) ? v4gateway : NO_IPV4_GATEWAY; - v6gws = (v6gws != null) ? v6gws : new ArrayList<>(); - - final String logmsg = String.format("setUpstreamParameters(%s, %s, %s, [%s])", - iface, v4addr, v4gateway, String.join(",", v6gws)); - - final CbResults results = new CbResults(); - try { - mOffloadControl.setUpstreamParameters( - iface, v4addr, v4gateway, v6gws, - (boolean success, String errMsg) -> { - results.mSuccess = success; - results.mErrMsg = errMsg; - }); - } catch (RemoteException e) { - record(logmsg, e); - return false; - } - - record(logmsg, results); - return results.mSuccess; - } - - /** Add downstream prefix to offload management process. */ - public boolean addDownstreamPrefix(String ifname, String prefix) { - final String logmsg = String.format("addDownstreamPrefix(%s, %s)", ifname, prefix); - - final CbResults results = new CbResults(); - try { - mOffloadControl.addDownstream(ifname, prefix, - (boolean success, String errMsg) -> { - results.mSuccess = success; - results.mErrMsg = errMsg; - }); - } catch (RemoteException e) { - record(logmsg, e); - return false; - } - - record(logmsg, results); - return results.mSuccess; - } - - /** Remove downstream prefix from offload management process. */ - public boolean removeDownstreamPrefix(String ifname, String prefix) { - final String logmsg = String.format("removeDownstreamPrefix(%s, %s)", ifname, prefix); - - final CbResults results = new CbResults(); - try { - mOffloadControl.removeDownstream(ifname, prefix, - (boolean success, String errMsg) -> { - results.mSuccess = success; - results.mErrMsg = errMsg; - }); - } catch (RemoteException e) { - record(logmsg, e); - return false; - } - - record(logmsg, results); - return results.mSuccess; - } - - private void record(String msg, Throwable t) { - mLog.e(msg + YIELDS + "exception: " + t); - } - - private void record(String msg, CbResults results) { - final String logmsg = msg + YIELDS + results; - if (!results.mSuccess) { - mLog.e(logmsg); - } else { - mLog.log(logmsg); - } - } - - private static class TetheringOffloadCallback extends ITetheringOffloadCallback.Stub { - public final Handler handler; - public final ControlCallback controlCb; - public final SharedLog log; - - TetheringOffloadCallback(Handler h, ControlCallback cb, SharedLog sharedLog) { - handler = h; - controlCb = cb; - log = sharedLog; - } - - @Override - public void onEvent(int event) { - handler.post(() -> { - switch (event) { - case OffloadCallbackEvent.OFFLOAD_STARTED: - controlCb.onStarted(); - break; - case OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR: - controlCb.onStoppedError(); - break; - case OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED: - controlCb.onStoppedUnsupported(); - break; - case OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE: - controlCb.onSupportAvailable(); - break; - case OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED: - controlCb.onStoppedLimitReached(); - break; - default: - log.e("Unsupported OffloadCallbackEvent: " + event); - } - }); - } - - @Override - public void updateTimeout(NatTimeoutUpdate params) { - handler.post(() -> { - controlCb.onNatTimeoutUpdate( - networkProtocolToOsConstant(params.proto), - params.src.addr, uint16(params.src.port), - params.dst.addr, uint16(params.dst.port)); - }); - } - } - - private static int networkProtocolToOsConstant(int proto) { - switch (proto) { - case NetworkProtocol.TCP: return OsConstants.IPPROTO_TCP; - case NetworkProtocol.UDP: return OsConstants.IPPROTO_UDP; - default: - // The caller checks this value and will log an error. Just make - // sure it won't collide with valid OsContants.IPPROTO_* values. - return -Math.abs(proto); - } - } - - private static class CbResults { - boolean mSuccess; - String mErrMsg; - - @Override - public String toString() { - if (mSuccess) { - return "ok"; - } else { - return "fail: " + mErrMsg; - } - } - } -} diff --git a/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java deleted file mode 100644 index 0cf14e3f868c..000000000000 --- a/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java +++ /dev/null @@ -1,417 +0,0 @@ -/* - * Copyright (C) 2020 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.networkstack.tethering; - -import static android.net.NetworkCapabilities.TRANSPORT_VPN; -import static android.net.TetheringManager.TETHERING_BLUETOOTH; -import static android.net.TetheringManager.TETHERING_WIFI_P2P; -import static android.net.util.PrefixUtils.asIpPrefix; - -import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH; -import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH; -import static com.android.net.module.util.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH; - -import static java.util.Arrays.asList; - -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.IpPrefix; -import android.net.LinkAddress; -import android.net.Network; -import android.net.ip.IpServer; -import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.SparseArray; - -import androidx.annotation.Nullable; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.IndentingPrintWriter; - -import java.net.Inet4Address; -import java.net.InetAddress; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Random; -import java.util.Set; - -/** - * This class coordinate IP addresses conflict problem. - * - * Tethering downstream IP addresses may conflict with network assigned addresses. This - * coordinator is responsible for recording all of network assigned addresses and dispatched - * free address to downstream interfaces. - * - * This class is not thread-safe and should be accessed on the same tethering internal thread. - * @hide - */ -public class PrivateAddressCoordinator { - public static final int PREFIX_LENGTH = 24; - - // Upstream monitor would be stopped when tethering is down. When tethering restart, downstream - // address may be requested before coordinator get current upstream notification. To ensure - // coordinator do not select conflict downstream prefix, mUpstreamPrefixMap would not be cleared - // when tethering is down. Instead tethering would remove all deprecated upstreams from - // mUpstreamPrefixMap when tethering is starting. See #maybeRemoveDeprecatedUpstreams(). - private final ArrayMap<Network, List<IpPrefix>> mUpstreamPrefixMap; - private final ArraySet<IpServer> mDownstreams; - private static final String LEGACY_WIFI_P2P_IFACE_ADDRESS = "192.168.49.1/24"; - private static final String LEGACY_BLUETOOTH_IFACE_ADDRESS = "192.168.44.1/24"; - private final List<IpPrefix> mTetheringPrefixes; - private final ConnectivityManager mConnectivityMgr; - private final TetheringConfiguration mConfig; - // keyed by downstream type(TetheringManager.TETHERING_*). - private final SparseArray<LinkAddress> mCachedAddresses; - - public PrivateAddressCoordinator(Context context, TetheringConfiguration config) { - this(context, config, new ArrayList<>(Arrays.asList(new IpPrefix("192.168.0.0/16")))); - } - - public PrivateAddressCoordinator(Context context, TetheringConfiguration config, - List<IpPrefix> prefixPools) { - mDownstreams = new ArraySet<>(); - mUpstreamPrefixMap = new ArrayMap<>(); - mConnectivityMgr = (ConnectivityManager) context.getSystemService( - Context.CONNECTIVITY_SERVICE); - mConfig = config; - mCachedAddresses = new SparseArray<>(); - // Reserved static addresses for bluetooth and wifi p2p. - mCachedAddresses.put(TETHERING_BLUETOOTH, new LinkAddress(LEGACY_BLUETOOTH_IFACE_ADDRESS)); - mCachedAddresses.put(TETHERING_WIFI_P2P, new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS)); - - mTetheringPrefixes = prefixPools; - } - - /** - * Record a new upstream IpPrefix which may conflict with tethering downstreams. - * The downstreams will be notified if a conflict is found. When updateUpstreamPrefix is called, - * UpstreamNetworkState must have an already populated LinkProperties. - */ - public void updateUpstreamPrefix(final UpstreamNetworkState ns) { - // Do not support VPN as upstream. Normally, networkCapabilities is not expected to be null, - // but just checking to be sure. - if (ns.networkCapabilities != null && ns.networkCapabilities.hasTransport(TRANSPORT_VPN)) { - removeUpstreamPrefix(ns.network); - return; - } - - final ArrayList<IpPrefix> ipv4Prefixes = getIpv4Prefixes( - ns.linkProperties.getAllLinkAddresses()); - if (ipv4Prefixes.isEmpty()) { - removeUpstreamPrefix(ns.network); - return; - } - - mUpstreamPrefixMap.put(ns.network, ipv4Prefixes); - handleMaybePrefixConflict(ipv4Prefixes); - } - - private ArrayList<IpPrefix> getIpv4Prefixes(final List<LinkAddress> linkAddresses) { - final ArrayList<IpPrefix> list = new ArrayList<>(); - for (LinkAddress address : linkAddresses) { - if (!address.isIpv4()) continue; - - list.add(asIpPrefix(address)); - } - - return list; - } - - private void handleMaybePrefixConflict(final List<IpPrefix> prefixes) { - for (IpServer downstream : mDownstreams) { - final IpPrefix target = getDownstreamPrefix(downstream); - if (target == null) continue; - - for (IpPrefix source : prefixes) { - if (isConflictPrefix(source, target)) { - downstream.sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT); - break; - } - } - } - } - - /** Remove IpPrefix records corresponding to input network. */ - public void removeUpstreamPrefix(final Network network) { - mUpstreamPrefixMap.remove(network); - } - - /** - * Maybe remove deprecated upstream records, this would be called once tethering started without - * any exiting tethered downstream. - */ - public void maybeRemoveDeprecatedUpstreams() { - if (mUpstreamPrefixMap.isEmpty()) return; - - // Remove all upstreams that are no longer valid networks - final Set<Network> toBeRemoved = new HashSet<>(mUpstreamPrefixMap.keySet()); - toBeRemoved.removeAll(asList(mConnectivityMgr.getAllNetworks())); - - mUpstreamPrefixMap.removeAll(toBeRemoved); - } - - /** - * Pick a random available address and mark its prefix as in use for the provided IpServer, - * returns null if there is no available address. - */ - @Nullable - public LinkAddress requestDownstreamAddress(final IpServer ipServer, boolean useLastAddress) { - if (mConfig.shouldEnableWifiP2pDedicatedIp() - && ipServer.interfaceType() == TETHERING_WIFI_P2P) { - return new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS); - } - - final LinkAddress cachedAddress = mCachedAddresses.get(ipServer.interfaceType()); - if (useLastAddress && cachedAddress != null - && !isConflictWithUpstream(asIpPrefix(cachedAddress))) { - return cachedAddress; - } - - for (IpPrefix prefixRange : mTetheringPrefixes) { - final LinkAddress newAddress = chooseDownstreamAddress(prefixRange); - if (newAddress != null) { - mDownstreams.add(ipServer); - mCachedAddresses.put(ipServer.interfaceType(), newAddress); - return newAddress; - } - } - - // No available address. - return null; - } - - private int getPrefixBaseAddress(final IpPrefix prefix) { - return inet4AddressToIntHTH((Inet4Address) prefix.getAddress()); - } - - /** - * Check whether input prefix conflict with upstream prefixes or in-use downstream prefixes. - * If yes, return one of them. - */ - private IpPrefix getConflictPrefix(final IpPrefix prefix) { - final IpPrefix upstream = getConflictWithUpstream(prefix); - if (upstream != null) return upstream; - - return getInUseDownstreamPrefix(prefix); - } - - // Get the next non-conflict sub prefix. E.g: To get next sub prefix from 10.0.0.0/8, if the - // previously selected prefix is 10.20.42.0/24(subPrefix: 0.20.42.0) and the conflicting prefix - // is 10.16.0.0/20 (10.16.0.0 ~ 10.16.15.255), then the max address under subPrefix is - // 0.16.15.255 and the next subPrefix is 0.16.16.255/24 (0.16.15.255 + 0.0.1.0). - // Note: the sub address 0.0.0.255 here is fine to be any value that it will be replaced as - // selected random sub address later. - private int getNextSubPrefix(final IpPrefix conflictPrefix, final int prefixRangeMask) { - final int suffixMask = ~prefixLengthToV4NetmaskIntHTH(conflictPrefix.getPrefixLength()); - // The largest offset within the prefix assignment block that still conflicts with - // conflictPrefix. - final int maxConflict = - (getPrefixBaseAddress(conflictPrefix) | suffixMask) & ~prefixRangeMask; - - final int prefixMask = prefixLengthToV4NetmaskIntHTH(PREFIX_LENGTH); - // Pick a sub prefix a full prefix (1 << (32 - PREFIX_LENGTH) addresses) greater than - // maxConflict. This ensures that the selected prefix never overlaps with conflictPrefix. - // There is no need to mask the result with PREFIX_LENGTH bits because this is done by - // findAvailablePrefixFromRange when it constructs the prefix. - return maxConflict + (1 << (32 - PREFIX_LENGTH)); - } - - private LinkAddress chooseDownstreamAddress(final IpPrefix prefixRange) { - // The netmask of the prefix assignment block (e.g., 0xfff00000 for 172.16.0.0/12). - final int prefixRangeMask = prefixLengthToV4NetmaskIntHTH(prefixRange.getPrefixLength()); - - // The zero address in the block (e.g., 0xac100000 for 172.16.0.0/12). - final int baseAddress = getPrefixBaseAddress(prefixRange); - - // The subnet mask corresponding to PREFIX_LENGTH. - final int prefixMask = prefixLengthToV4NetmaskIntHTH(PREFIX_LENGTH); - - // The offset within prefixRange of a randomly-selected prefix of length PREFIX_LENGTH. - // This may not be the prefix of the address returned by this method: - // - If it is already in use, the method will return an address in another prefix. - // - If all prefixes within prefixRange are in use, the method will return null. For - // example, for a /24 prefix within 172.26.0.0/12, this will be a multiple of 256 in - // [0, 1048576). In other words, a random 32-bit number with mask 0x000fff00. - // - // prefixRangeMask is required to ensure no wrapping. For example, consider: - // - prefixRange 127.0.0.0/8 - // - randomPrefixStart 127.255.255.0 - // - A conflicting prefix of 127.255.254.0/23 - // In this case without prefixRangeMask, getNextSubPrefix would return 128.0.0.0, which - // means the "start < end" check in findAvailablePrefixFromRange would not reject the prefix - // because Java doesn't have unsigned integers, so 128.0.0.0 = 0x80000000 = -2147483648 - // is less than 127.0.0.0 = 0x7f000000 = 2130706432. - // - // Additionally, it makes debug output easier to read by making the numbers smaller. - final int randomPrefixStart = getRandomInt() & ~prefixRangeMask & prefixMask; - - // A random offset within the prefix. Used to determine the local address once the prefix - // is selected. It does not result in an IPv4 address ending in .0, .1, or .255 - // For a PREFIX_LENGTH of 255, this is a number between 2 and 254. - final int subAddress = getSanitizedSubAddr(~prefixMask); - - // Find a prefix length PREFIX_LENGTH between randomPrefixStart and the end of the block, - // such that the prefix does not conflict with any upstream. - IpPrefix downstreamPrefix = findAvailablePrefixFromRange( - randomPrefixStart, (~prefixRangeMask) + 1, baseAddress, prefixRangeMask); - if (downstreamPrefix != null) return getLinkAddress(downstreamPrefix, subAddress); - - // If that failed, do the same, but between 0 and randomPrefixStart. - downstreamPrefix = findAvailablePrefixFromRange( - 0, randomPrefixStart, baseAddress, prefixRangeMask); - - return getLinkAddress(downstreamPrefix, subAddress); - } - - private LinkAddress getLinkAddress(final IpPrefix prefix, final int subAddress) { - if (prefix == null) return null; - - final InetAddress address = intToInet4AddressHTH(getPrefixBaseAddress(prefix) | subAddress); - return new LinkAddress(address, PREFIX_LENGTH); - } - - private IpPrefix findAvailablePrefixFromRange(final int start, final int end, - final int baseAddress, final int prefixRangeMask) { - int newSubPrefix = start; - while (newSubPrefix < end) { - final InetAddress address = intToInet4AddressHTH(baseAddress | newSubPrefix); - final IpPrefix prefix = new IpPrefix(address, PREFIX_LENGTH); - - final IpPrefix conflictPrefix = getConflictPrefix(prefix); - - if (conflictPrefix == null) return prefix; - - newSubPrefix = getNextSubPrefix(conflictPrefix, prefixRangeMask); - } - - return null; - } - - /** Get random int which could be used to generate random address. */ - @VisibleForTesting - public int getRandomInt() { - return (new Random()).nextInt(); - } - - /** Get random subAddress and avoid selecting x.x.x.0, x.x.x.1 and x.x.x.255 address. */ - private int getSanitizedSubAddr(final int subAddrMask) { - final int randomSubAddr = getRandomInt() & subAddrMask; - // If prefix length > 30, the selecting speace would be less than 4 which may be hard to - // avoid 3 consecutive address. - if (PREFIX_LENGTH > 30) return randomSubAddr; - - // TODO: maybe it is not necessary to avoid .0, .1 and .255 address because tethering - // address would not be conflicted. This code only works because PREFIX_LENGTH is not longer - // than 24 - final int candidate = randomSubAddr & 0xff; - if (candidate == 0 || candidate == 1 || candidate == 255) { - return (randomSubAddr & 0xfffffffc) + 2; - } - - return randomSubAddr; - } - - /** Release downstream record for IpServer. */ - public void releaseDownstream(final IpServer ipServer) { - mDownstreams.remove(ipServer); - } - - /** Clear current upstream prefixes records. */ - public void clearUpstreamPrefixes() { - mUpstreamPrefixMap.clear(); - } - - private IpPrefix getConflictWithUpstream(final IpPrefix prefix) { - for (int i = 0; i < mUpstreamPrefixMap.size(); i++) { - final List<IpPrefix> list = mUpstreamPrefixMap.valueAt(i); - for (IpPrefix upstream : list) { - if (isConflictPrefix(prefix, upstream)) return upstream; - } - } - return null; - } - - private boolean isConflictWithUpstream(final IpPrefix prefix) { - return getConflictWithUpstream(prefix) != null; - } - - private boolean isConflictPrefix(final IpPrefix prefix1, final IpPrefix prefix2) { - if (prefix2.getPrefixLength() < prefix1.getPrefixLength()) { - return prefix2.contains(prefix1.getAddress()); - } - - return prefix1.contains(prefix2.getAddress()); - } - - // InUse Prefixes are prefixes of mCachedAddresses which are active downstream addresses, last - // downstream addresses(reserved for next time) and static addresses(e.g. bluetooth, wifi p2p). - private IpPrefix getInUseDownstreamPrefix(final IpPrefix prefix) { - for (int i = 0; i < mCachedAddresses.size(); i++) { - final IpPrefix downstream = asIpPrefix(mCachedAddresses.valueAt(i)); - if (isConflictPrefix(prefix, downstream)) return downstream; - } - - // IpServer may use manually-defined address (mStaticIpv4ServerAddr) which does not include - // in mCachedAddresses. - for (IpServer downstream : mDownstreams) { - final IpPrefix target = getDownstreamPrefix(downstream); - if (target == null) continue; - - if (isConflictPrefix(prefix, target)) return target; - } - - return null; - } - - private IpPrefix getDownstreamPrefix(final IpServer downstream) { - final LinkAddress address = downstream.getAddress(); - if (address == null) return null; - - return asIpPrefix(address); - } - - void dump(final IndentingPrintWriter pw) { - pw.println("mTetheringPrefixes:"); - pw.increaseIndent(); - for (IpPrefix prefix : mTetheringPrefixes) { - pw.println(prefix); - } - pw.decreaseIndent(); - - pw.println("mUpstreamPrefixMap:"); - pw.increaseIndent(); - for (int i = 0; i < mUpstreamPrefixMap.size(); i++) { - pw.println(mUpstreamPrefixMap.keyAt(i) + " - " + mUpstreamPrefixMap.valueAt(i)); - } - pw.decreaseIndent(); - - pw.println("mDownstreams:"); - pw.increaseIndent(); - for (IpServer ipServer : mDownstreams) { - pw.println(ipServer.interfaceType() + " - " + ipServer.getAddress()); - } - pw.decreaseIndent(); - - pw.println("mCachedAddresses:"); - pw.increaseIndent(); - for (int i = 0; i < mCachedAddresses.size(); i++) { - pw.println(mCachedAddresses.keyAt(i) + " - " + mCachedAddresses.valueAt(i)); - } - pw.decreaseIndent(); - } -} diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java deleted file mode 100644 index 5783805861a3..000000000000 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java +++ /dev/null @@ -1,501 +0,0 @@ -/* - * Copyright (C) 2017 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.networkstack.tethering; - -import static android.content.Context.TELEPHONY_SERVICE; -import static android.net.ConnectivityManager.TYPE_ETHERNET; -import static android.net.ConnectivityManager.TYPE_MOBILE; -import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; -import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; -import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; - -import android.content.Context; -import android.content.res.Resources; -import android.net.TetheringConfigurationParcel; -import android.net.util.SharedLog; -import android.provider.DeviceConfig; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; -import android.text.TextUtils; - -import com.android.internal.annotations.VisibleForTesting; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.StringJoiner; - - -/** - * A utility class to encapsulate the various tethering configuration elements. - * - * This configuration data includes elements describing upstream properties - * (preferred and required types of upstream connectivity as well as default - * DNS servers to use if none are available) and downstream properties (such - * as regular expressions use to match suitable downstream interfaces and the - * DHCPv4 ranges to use). - * - * @hide - */ -public class TetheringConfiguration { - private static final String TAG = TetheringConfiguration.class.getSimpleName(); - - private static final String[] EMPTY_STRING_ARRAY = new String[0]; - - // Default ranges used for the legacy DHCP server. - // USB is 192.168.42.1 and 255.255.255.0 - // Wifi is 192.168.43.1 and 255.255.255.0 - // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1 - // with 255.255.255.0 - // P2P is 192.168.49.1 and 255.255.255.0 - private static final String[] LEGACY_DHCP_DEFAULT_RANGE = { - "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254", - "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254", - "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254", - "192.168.48.2", "192.168.48.254", "192.168.49.2", "192.168.49.254", - }; - - private static final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"}; - - /** - * Override enabling BPF offload configuration for tethering. - */ - public static final String OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD = - "override_tether_enable_bpf_offload"; - - /** - * Use the old dnsmasq DHCP server for tethering instead of the framework implementation. - */ - public static final String TETHER_ENABLE_LEGACY_DHCP_SERVER = - "tether_enable_legacy_dhcp_server"; - - public static final String USE_LEGACY_WIFI_P2P_DEDICATED_IP = - "use_legacy_wifi_p2p_dedicated_ip"; - - /** - * Default value that used to periodic polls tether offload stats from tethering offload HAL - * to make the data warnings work. - */ - public static final int DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS = 5000; - - public final String[] tetherableUsbRegexs; - public final String[] tetherableWifiRegexs; - public final String[] tetherableWigigRegexs; - public final String[] tetherableWifiP2pRegexs; - public final String[] tetherableBluetoothRegexs; - public final String[] tetherableNcmRegexs; - public final boolean isDunRequired; - public final boolean chooseUpstreamAutomatically; - public final Collection<Integer> preferredUpstreamIfaceTypes; - public final String[] legacyDhcpRanges; - public final String[] defaultIPv4DNS; - public final boolean enableLegacyDhcpServer; - - public final String[] provisioningApp; - public final String provisioningAppNoUi; - public final int provisioningCheckPeriod; - public final String provisioningResponse; - - public final int activeDataSubId; - - private final int mOffloadPollInterval; - // TODO: Add to TetheringConfigurationParcel if required. - private final boolean mEnableBpfOffload; - private final boolean mEnableWifiP2pDedicatedIp; - - public TetheringConfiguration(Context ctx, SharedLog log, int id) { - final SharedLog configLog = log.forSubComponent("config"); - - activeDataSubId = id; - Resources res = getResources(ctx, activeDataSubId); - - tetherableUsbRegexs = getResourceStringArray(res, R.array.config_tether_usb_regexs); - tetherableNcmRegexs = getResourceStringArray(res, R.array.config_tether_ncm_regexs); - // TODO: Evaluate deleting this altogether now that Wi-Fi always passes - // us an interface name. Careful consideration needs to be given to - // implications for Settings and for provisioning checks. - tetherableWifiRegexs = getResourceStringArray(res, R.array.config_tether_wifi_regexs); - tetherableWigigRegexs = getResourceStringArray(res, R.array.config_tether_wigig_regexs); - tetherableWifiP2pRegexs = getResourceStringArray( - res, R.array.config_tether_wifi_p2p_regexs); - tetherableBluetoothRegexs = getResourceStringArray( - res, R.array.config_tether_bluetooth_regexs); - - isDunRequired = checkDunRequired(ctx); - - chooseUpstreamAutomatically = getResourceBoolean( - res, R.bool.config_tether_upstream_automatic, false /** defaultValue */); - preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, isDunRequired); - - legacyDhcpRanges = getLegacyDhcpRanges(res); - defaultIPv4DNS = copy(DEFAULT_IPV4_DNS); - mEnableBpfOffload = getEnableBpfOffload(res); - enableLegacyDhcpServer = getEnableLegacyDhcpServer(res); - - provisioningApp = getResourceStringArray(res, R.array.config_mobile_hotspot_provision_app); - provisioningAppNoUi = getResourceString(res, - R.string.config_mobile_hotspot_provision_app_no_ui); - provisioningCheckPeriod = getResourceInteger(res, - R.integer.config_mobile_hotspot_provision_check_period, - 0 /* No periodic re-check */); - provisioningResponse = getResourceString(res, - R.string.config_mobile_hotspot_provision_response); - - mOffloadPollInterval = getResourceInteger(res, - R.integer.config_tether_offload_poll_interval, - DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS); - - mEnableWifiP2pDedicatedIp = getResourceBoolean(res, - R.bool.config_tether_enable_legacy_wifi_p2p_dedicated_ip, - false /* defaultValue */); - - configLog.log(toString()); - } - - /** Check whether input interface belong to usb.*/ - public boolean isUsb(String iface) { - return matchesDownstreamRegexs(iface, tetherableUsbRegexs); - } - - /** Check whether input interface belong to wifi.*/ - public boolean isWifi(String iface) { - return matchesDownstreamRegexs(iface, tetherableWifiRegexs); - } - - /** Check whether input interface belong to wigig.*/ - public boolean isWigig(String iface) { - return matchesDownstreamRegexs(iface, tetherableWigigRegexs); - } - - /** Check whether this interface is Wifi P2P interface. */ - public boolean isWifiP2p(String iface) { - return matchesDownstreamRegexs(iface, tetherableWifiP2pRegexs); - } - - /** Check whether using legacy mode for wifi P2P. */ - public boolean isWifiP2pLegacyTetheringMode() { - return (tetherableWifiP2pRegexs == null || tetherableWifiP2pRegexs.length == 0); - } - - /** Check whether input interface belong to bluetooth.*/ - public boolean isBluetooth(String iface) { - return matchesDownstreamRegexs(iface, tetherableBluetoothRegexs); - } - - /** Check if interface is ncm */ - public boolean isNcm(String iface) { - return matchesDownstreamRegexs(iface, tetherableNcmRegexs); - } - - /** Check whether no ui entitlement application is available.*/ - public boolean hasMobileHotspotProvisionApp() { - return !TextUtils.isEmpty(provisioningAppNoUi); - } - - /** Check whether dedicated wifi p2p address is enabled. */ - public boolean shouldEnableWifiP2pDedicatedIp() { - return mEnableWifiP2pDedicatedIp; - } - - /** Does the dumping.*/ - public void dump(PrintWriter pw) { - pw.print("activeDataSubId: "); - pw.println(activeDataSubId); - - dumpStringArray(pw, "tetherableUsbRegexs", tetherableUsbRegexs); - dumpStringArray(pw, "tetherableWifiRegexs", tetherableWifiRegexs); - dumpStringArray(pw, "tetherableWifiP2pRegexs", tetherableWifiP2pRegexs); - dumpStringArray(pw, "tetherableBluetoothRegexs", tetherableBluetoothRegexs); - dumpStringArray(pw, "tetherableNcmRegexs", tetherableNcmRegexs); - - pw.print("isDunRequired: "); - pw.println(isDunRequired); - - pw.print("chooseUpstreamAutomatically: "); - pw.println(chooseUpstreamAutomatically); - pw.print("legacyPreredUpstreamIfaceTypes: "); - pw.println(Arrays.toString(toIntArray(preferredUpstreamIfaceTypes))); - - dumpStringArray(pw, "legacyDhcpRanges", legacyDhcpRanges); - dumpStringArray(pw, "defaultIPv4DNS", defaultIPv4DNS); - - pw.print("offloadPollInterval: "); - pw.println(mOffloadPollInterval); - - dumpStringArray(pw, "provisioningApp", provisioningApp); - pw.print("provisioningAppNoUi: "); - pw.println(provisioningAppNoUi); - - pw.print("enableBpfOffload: "); - pw.println(mEnableBpfOffload); - - pw.print("enableLegacyDhcpServer: "); - pw.println(enableLegacyDhcpServer); - - pw.print("enableWifiP2pDedicatedIp: "); - pw.println(mEnableWifiP2pDedicatedIp); - } - - /** Returns the string representation of this object.*/ - public String toString() { - final StringJoiner sj = new StringJoiner(" "); - sj.add(String.format("activeDataSubId:%d", activeDataSubId)); - sj.add(String.format("tetherableUsbRegexs:%s", makeString(tetherableUsbRegexs))); - sj.add(String.format("tetherableWifiRegexs:%s", makeString(tetherableWifiRegexs))); - sj.add(String.format("tetherableWifiP2pRegexs:%s", makeString(tetherableWifiP2pRegexs))); - sj.add(String.format("tetherableBluetoothRegexs:%s", - makeString(tetherableBluetoothRegexs))); - sj.add(String.format("isDunRequired:%s", isDunRequired)); - sj.add(String.format("chooseUpstreamAutomatically:%s", chooseUpstreamAutomatically)); - sj.add(String.format("offloadPollInterval:%d", mOffloadPollInterval)); - sj.add(String.format("preferredUpstreamIfaceTypes:%s", - toIntArray(preferredUpstreamIfaceTypes))); - sj.add(String.format("provisioningApp:%s", makeString(provisioningApp))); - sj.add(String.format("provisioningAppNoUi:%s", provisioningAppNoUi)); - sj.add(String.format("enableBpfOffload:%s", mEnableBpfOffload)); - sj.add(String.format("enableLegacyDhcpServer:%s", enableLegacyDhcpServer)); - return String.format("TetheringConfiguration{%s}", sj.toString()); - } - - private static void dumpStringArray(PrintWriter pw, String label, String[] values) { - pw.print(label); - pw.print(": "); - - if (values != null) { - final StringJoiner sj = new StringJoiner(", ", "[", "]"); - for (String value : values) sj.add(value); - pw.print(sj.toString()); - } else { - pw.print("null"); - } - - pw.println(); - } - - private static String makeString(String[] strings) { - if (strings == null) return "null"; - final StringJoiner sj = new StringJoiner(",", "[", "]"); - for (String s : strings) sj.add(s); - return sj.toString(); - } - - /** Check whether dun is required. */ - public static boolean checkDunRequired(Context ctx) { - final TelephonyManager tm = (TelephonyManager) ctx.getSystemService(TELEPHONY_SERVICE); - // TelephonyManager would uses the active data subscription, which should be the one used - // by tethering. - return (tm != null) ? tm.isTetheringApnRequired() : false; - } - - public int getOffloadPollInterval() { - return mOffloadPollInterval; - } - - public boolean isBpfOffloadEnabled() { - return mEnableBpfOffload; - } - - private static Collection<Integer> getUpstreamIfaceTypes(Resources res, boolean dunRequired) { - final int[] ifaceTypes = res.getIntArray(R.array.config_tether_upstream_types); - final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length); - for (int i : ifaceTypes) { - switch (i) { - case TYPE_MOBILE: - case TYPE_MOBILE_HIPRI: - if (dunRequired) continue; - break; - case TYPE_MOBILE_DUN: - if (!dunRequired) continue; - break; - } - upstreamIfaceTypes.add(i); - } - - // Fix up upstream interface types for DUN or mobile. NOTE: independent - // of the value of |dunRequired|, cell data of one form or another is - // *always* an upstream, regardless of the upstream interface types - // specified by configuration resources. - if (dunRequired) { - appendIfNotPresent(upstreamIfaceTypes, TYPE_MOBILE_DUN); - } else { - // Do not modify if a cellular interface type is already present in the - // upstream interface types. Add TYPE_MOBILE and TYPE_MOBILE_HIPRI if no - // cellular interface types are found in the upstream interface types. - // This preserves backwards compatibility and prevents the DUN and default - // mobile types incorrectly appearing together, which could happen on - // previous releases in the common case where checkDunRequired returned - // DUN_UNSPECIFIED. - if (!containsOneOf(upstreamIfaceTypes, TYPE_MOBILE, TYPE_MOBILE_HIPRI)) { - upstreamIfaceTypes.add(TYPE_MOBILE); - upstreamIfaceTypes.add(TYPE_MOBILE_HIPRI); - } - } - - // Always make sure our good friend Ethernet is present. - // TODO: consider unilaterally forcing this at the front. - prependIfNotPresent(upstreamIfaceTypes, TYPE_ETHERNET); - - return upstreamIfaceTypes; - } - - private static boolean matchesDownstreamRegexs(String iface, String[] regexs) { - for (String regex : regexs) { - if (iface.matches(regex)) return true; - } - return false; - } - - private static String[] getLegacyDhcpRanges(Resources res) { - final String[] fromResource = getResourceStringArray(res, R.array.config_tether_dhcp_range); - if ((fromResource.length > 0) && (fromResource.length % 2 == 0)) { - return fromResource; - } - return copy(LEGACY_DHCP_DEFAULT_RANGE); - } - - private static String getResourceString(Resources res, final int resId) { - try { - return res.getString(resId); - } catch (Resources.NotFoundException e) { - return ""; - } - } - - private static boolean getResourceBoolean(Resources res, int resId, boolean defaultValue) { - try { - return res.getBoolean(resId); - } catch (Resources.NotFoundException e404) { - return defaultValue; - } - } - - private static String[] getResourceStringArray(Resources res, int resId) { - try { - final String[] strArray = res.getStringArray(resId); - return (strArray != null) ? strArray : EMPTY_STRING_ARRAY; - } catch (Resources.NotFoundException e404) { - return EMPTY_STRING_ARRAY; - } - } - - private static int getResourceInteger(Resources res, int resId, int defaultValue) { - try { - return res.getInteger(resId); - } catch (Resources.NotFoundException e404) { - return defaultValue; - } - } - - private boolean getEnableBpfOffload(final Resources res) { - // Get BPF offload config - // Priority 1: Device config - // Priority 2: Resource config - // Priority 3: Default value - final boolean defaultValue = getResourceBoolean( - res, R.bool.config_tether_enable_bpf_offload, true /** default value */); - - return getDeviceConfigBoolean(OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD, defaultValue); - } - - private boolean getEnableLegacyDhcpServer(final Resources res) { - return getResourceBoolean( - res, R.bool.config_tether_enable_legacy_dhcp_server, false /** defaultValue */) - || getDeviceConfigBoolean( - TETHER_ENABLE_LEGACY_DHCP_SERVER, false /** defaultValue */); - } - - private boolean getDeviceConfigBoolean(final String name, final boolean defaultValue) { - // Due to the limitation of static mock for testing, using #getDeviceConfigProperty instead - // of DeviceConfig#getBoolean. If using #getBoolean here, the test can't know that the - // returned boolean value comes from device config or default value (because of null - // property string). See the test case testBpfOffload{*} in TetheringConfigurationTest.java. - final String value = getDeviceConfigProperty(name); - return value != null ? Boolean.parseBoolean(value) : defaultValue; - } - - @VisibleForTesting - protected String getDeviceConfigProperty(String name) { - return DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY, name); - } - - private Resources getResources(Context ctx, int subId) { - if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { - return getResourcesForSubIdWrapper(ctx, subId); - } else { - return ctx.getResources(); - } - } - - @VisibleForTesting - protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) { - return SubscriptionManager.getResourcesForSubId(ctx, subId); - } - - private static String[] copy(String[] strarray) { - return Arrays.copyOf(strarray, strarray.length); - } - - private static void prependIfNotPresent(ArrayList<Integer> list, int value) { - if (list.contains(value)) return; - list.add(0, value); - } - - private static void appendIfNotPresent(ArrayList<Integer> list, int value) { - if (list.contains(value)) return; - list.add(value); - } - - private static boolean containsOneOf(ArrayList<Integer> list, Integer... values) { - for (Integer value : values) { - if (list.contains(value)) return true; - } - return false; - } - - private static int[] toIntArray(Collection<Integer> values) { - final int[] result = new int[values.size()]; - int index = 0; - for (Integer value : values) { - result[index++] = value; - } - return result; - } - - /** - * Convert this TetheringConfiguration to a TetheringConfigurationParcel. - */ - public TetheringConfigurationParcel toStableParcelable() { - final TetheringConfigurationParcel parcel = new TetheringConfigurationParcel(); - parcel.subId = activeDataSubId; - parcel.tetherableUsbRegexs = tetherableUsbRegexs; - parcel.tetherableWifiRegexs = tetherableWifiRegexs; - parcel.tetherableBluetoothRegexs = tetherableBluetoothRegexs; - parcel.isDunRequired = isDunRequired; - parcel.chooseUpstreamAutomatically = chooseUpstreamAutomatically; - - parcel.preferredUpstreamIfaceTypes = toIntArray(preferredUpstreamIfaceTypes); - - parcel.legacyDhcpRanges = legacyDhcpRanges; - parcel.defaultIPv4DNS = defaultIPv4DNS; - parcel.enableLegacyDhcpServer = enableLegacyDhcpServer; - parcel.provisioningApp = provisioningApp; - parcel.provisioningAppNoUi = provisioningAppNoUi; - parcel.provisioningCheckPeriod = provisioningCheckPeriod; - return parcel; - } -} diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java deleted file mode 100644 index 45b914178e97..000000000000 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2017 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.networkstack.tethering; - -import android.app.usage.NetworkStatsManager; -import android.bluetooth.BluetoothAdapter; -import android.content.Context; -import android.net.INetd; -import android.net.NetworkRequest; -import android.net.ip.IpServer; -import android.net.util.SharedLog; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.SystemProperties; -import android.text.TextUtils; - -import androidx.annotation.NonNull; - -import com.android.internal.util.StateMachine; - -import java.util.ArrayList; - - -/** - * Capture tethering dependencies, for injection. - * - * @hide - */ -public abstract class TetheringDependencies { - /** - * Get a reference to the BpfCoordinator to be used by tethering. - */ - public @NonNull BpfCoordinator getBpfCoordinator( - @NonNull BpfCoordinator.Dependencies deps) { - return new BpfCoordinator(deps); - } - - /** - * Get a reference to the offload hardware interface to be used by tethering. - */ - public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) { - return new OffloadHardwareInterface(h, log); - } - - /** - * Get a reference to the offload controller to be used by tethering. - */ - @NonNull - public OffloadController getOffloadController(@NonNull Handler h, - @NonNull SharedLog log, @NonNull OffloadController.Dependencies deps) { - final NetworkStatsManager statsManager = - (NetworkStatsManager) getContext().getSystemService(Context.NETWORK_STATS_SERVICE); - return new OffloadController(h, getOffloadHardwareInterface(h, log), - getContext().getContentResolver(), statsManager, log, deps); - } - - - /** - * Get a reference to the UpstreamNetworkMonitor to be used by tethering. - */ - public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, StateMachine target, - SharedLog log, int what) { - return new UpstreamNetworkMonitor(ctx, target, log, what); - } - - /** - * Get a reference to the IPv6TetheringCoordinator to be used by tethering. - */ - public IPv6TetheringCoordinator getIPv6TetheringCoordinator( - ArrayList<IpServer> notifyList, SharedLog log) { - return new IPv6TetheringCoordinator(notifyList, log); - } - - /** - * Get dependencies to be used by IpServer. - */ - public abstract IpServer.Dependencies getIpServerDependencies(); - - /** - * Indicates whether tethering is supported on the device. - */ - public boolean isTetheringSupported() { - return true; - } - - /** - * Get the NetworkRequest that should be fulfilled by the default network. - */ - public abstract NetworkRequest getDefaultNetworkRequest(); - - /** - * Get a reference to the EntitlementManager to be used by tethering. - */ - public EntitlementManager getEntitlementManager(Context ctx, Handler h, SharedLog log, - Runnable callback) { - return new EntitlementManager(ctx, h, log, callback); - } - - /** - * Generate a new TetheringConfiguration according to input sub Id. - */ - public TetheringConfiguration generateTetheringConfiguration(Context ctx, SharedLog log, - int subId) { - return new TetheringConfiguration(ctx, log, subId); - } - - /** - * Get a reference to INetd to be used by tethering. - */ - public INetd getINetd(Context context) { - return INetd.Stub.asInterface( - (IBinder) context.getSystemService(Context.NETD_SERVICE)); - } - - /** - * Get a reference to the TetheringNotificationUpdater to be used by tethering. - */ - public TetheringNotificationUpdater getNotificationUpdater(@NonNull final Context ctx, - @NonNull final Looper looper) { - return new TetheringNotificationUpdater(ctx, looper); - } - - /** - * Get tethering thread looper. - */ - public abstract Looper getTetheringLooper(); - - /** - * Get Context of TetheringSerice. - */ - public abstract Context getContext(); - - /** - * Get a reference to BluetoothAdapter to be used by tethering. - */ - public abstract BluetoothAdapter getBluetoothAdapter(); - - /** - * Get SystemProperties which indicate whether tethering is denied. - */ - public boolean isTetheringDenied() { - return TextUtils.equals(SystemProperties.get("ro.tether.denied"), "true"); - } - - /** - * Get a reference to PrivateAddressCoordinator to be used by Tethering. - */ - public PrivateAddressCoordinator getPrivateAddressCoordinator(Context ctx, - TetheringConfiguration cfg) { - return new PrivateAddressCoordinator(ctx, cfg); - } -} diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringInterfaceUtils.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringInterfaceUtils.java deleted file mode 100644 index ff38f717a121..000000000000 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringInterfaceUtils.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.networkstack.tethering; - -import android.annotation.Nullable; -import android.net.LinkProperties; -import android.net.NetworkCapabilities; -import android.net.RouteInfo; -import android.net.util.InterfaceSet; - -import com.android.net.module.util.NetUtils; - -import java.net.InetAddress; -import java.net.UnknownHostException; - -/** - * @hide - */ -public final class TetheringInterfaceUtils { - private static final InetAddress IN6ADDR_ANY = getByAddress( - new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); - private static final InetAddress INADDR_ANY = getByAddress(new byte[] {0, 0, 0, 0}); - - /** - * Get upstream interfaces for tethering based on default routes for IPv4/IPv6. - * @return null if there is no usable interface, or a set of at least one interface otherwise. - */ - public static @Nullable InterfaceSet getTetheringInterfaces(UpstreamNetworkState ns) { - if (ns == null) { - return null; - } - - final LinkProperties lp = ns.linkProperties; - final String if4 = getInterfaceForDestination(lp, INADDR_ANY); - final String if6 = getIPv6Interface(ns); - - return (if4 == null && if6 == null) ? null : new InterfaceSet(if4, if6); - } - - /** - * Get the upstream interface for IPv6 tethering. - * @return null if there is no usable interface, or the interface name otherwise. - */ - public static @Nullable String getIPv6Interface(UpstreamNetworkState ns) { - // Broadly speaking: - // - // [1] does the upstream have an IPv6 default route? - // - // and - // - // [2] does the upstream have one or more global IPv6 /64s - // dedicated to this device? - // - // In lieu of Prefix Delegation and other evaluation of whether a - // prefix may or may not be dedicated to this device, for now just - // check whether the upstream is TRANSPORT_CELLULAR. This works - // because "[t]he 3GPP network allocates each default bearer a unique - // /64 prefix", per RFC 6459, Section 5.2. - final boolean canTether = - (ns != null) && (ns.network != null) - && (ns.linkProperties != null) && (ns.networkCapabilities != null) - // At least one upstream DNS server: - && ns.linkProperties.hasIpv6DnsServer() - // Minimal amount of IPv6 provisioning: - && ns.linkProperties.hasGlobalIpv6Address() - // Temporary approximation of "dedicated prefix": - && ns.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR); - - return canTether - ? getInterfaceForDestination(ns.linkProperties, IN6ADDR_ANY) - : null; - } - - private static String getInterfaceForDestination(LinkProperties lp, InetAddress dst) { - final RouteInfo ri = (lp != null) - ? NetUtils.selectBestRoute(lp.getAllRoutes(), dst) - : null; - return (ri != null) ? ri.getInterface() : null; - } - - private static InetAddress getByAddress(final byte[] addr) { - try { - return InetAddress.getByAddress(null, addr); - } catch (UnknownHostException e) { - throw new AssertionError("illegal address length" + addr.length); - } - } -} diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java deleted file mode 100644 index a0198cc9c126..000000000000 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Copyright (C) 2020 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.networkstack.tethering; - -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; -import static android.text.TextUtils.isEmpty; - -import android.app.Notification; -import android.app.Notification.Action; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.net.NetworkCapabilities; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.UserHandle; -import android.provider.Settings; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; -import android.util.SparseArray; - -import androidx.annotation.DrawableRes; -import androidx.annotation.IntDef; -import androidx.annotation.IntRange; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.internal.annotations.VisibleForTesting; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * A class to display tethering-related notifications. - * - * <p>This class is not thread safe, it is intended to be used only from the tethering handler - * thread. However the constructor is an exception, as it is called on another thread ; - * therefore for thread safety all members of this class MUST either be final or initialized - * to their default value (0, false or null). - * - * @hide - */ -public class TetheringNotificationUpdater { - private static final String TAG = TetheringNotificationUpdater.class.getSimpleName(); - private static final String CHANNEL_ID = "TETHERING_STATUS"; - private static final String WIFI_DOWNSTREAM = "WIFI"; - private static final String USB_DOWNSTREAM = "USB"; - private static final String BLUETOOTH_DOWNSTREAM = "BT"; - @VisibleForTesting - static final String ACTION_DISABLE_TETHERING = - "com.android.server.connectivity.tethering.DISABLE_TETHERING"; - private static final boolean NOTIFY_DONE = true; - private static final boolean NO_NOTIFY = false; - @VisibleForTesting - static final int EVENT_SHOW_NO_UPSTREAM = 1; - // Id to update and cancel restricted notification. Must be unique within the tethering app. - @VisibleForTesting - static final int RESTRICTED_NOTIFICATION_ID = 1001; - // Id to update and cancel no upstream notification. Must be unique within the tethering app. - @VisibleForTesting - static final int NO_UPSTREAM_NOTIFICATION_ID = 1002; - // Id to update and cancel roaming notification. Must be unique within the tethering app. - @VisibleForTesting - static final int ROAMING_NOTIFICATION_ID = 1003; - @VisibleForTesting - static final int NO_ICON_ID = 0; - @VisibleForTesting - static final int DOWNSTREAM_NONE = 0; - // Refer to TelephonyManager#getSimCarrierId for more details about carrier id. - @VisibleForTesting - static final int VERIZON_CARRIER_ID = 1839; - private final Context mContext; - private final NotificationManager mNotificationManager; - private final NotificationChannel mChannel; - private final Handler mHandler; - - // WARNING : the constructor is called on a different thread. Thread safety therefore - // relies on these values being initialized to 0, false or null, and not any other value. If you - // need to change this, you will need to change the thread where the constructor is invoked, or - // to introduce synchronization. - // Downstream type is one of ConnectivityManager.TETHERING_* constants, 0 1 or 2. - // This value has to be made 1 2 and 4, and OR'd with the others. - private int mDownstreamTypesMask = DOWNSTREAM_NONE; - private boolean mNoUpstream = false; - private boolean mRoaming = false; - - // WARNING : this value is not able to being initialized to 0 and must have volatile because - // telephony service is not guaranteed that is up before tethering service starts. If telephony - // is up later than tethering, TetheringNotificationUpdater will use incorrect and valid - // subscription id(0) to query resources. Therefore, initialized subscription id must be - // INVALID_SUBSCRIPTION_ID. - private volatile int mActiveDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - RESTRICTED_NOTIFICATION_ID, - NO_UPSTREAM_NOTIFICATION_ID, - ROAMING_NOTIFICATION_ID - }) - @interface NotificationId {} - - private static final class MccMncOverrideInfo { - public final String visitedMccMnc; - public final int homeMcc; - public final int homeMnc; - MccMncOverrideInfo(String visitedMccMnc, int mcc, int mnc) { - this.visitedMccMnc = visitedMccMnc; - this.homeMcc = mcc; - this.homeMnc = mnc; - } - } - - private static final SparseArray<MccMncOverrideInfo> sCarrierIdToMccMnc = new SparseArray<>(); - - static { - sCarrierIdToMccMnc.put(VERIZON_CARRIER_ID, new MccMncOverrideInfo("20404", 311, 480)); - } - - public TetheringNotificationUpdater(@NonNull final Context context, - @NonNull final Looper looper) { - mContext = context; - mNotificationManager = (NotificationManager) context.createContextAsUser(UserHandle.ALL, 0) - .getSystemService(Context.NOTIFICATION_SERVICE); - mChannel = new NotificationChannel( - CHANNEL_ID, - context.getResources().getString(R.string.notification_channel_tethering_status), - NotificationManager.IMPORTANCE_LOW); - mNotificationManager.createNotificationChannel(mChannel); - mHandler = new NotificationHandler(looper); - } - - private class NotificationHandler extends Handler { - NotificationHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch(msg.what) { - case EVENT_SHOW_NO_UPSTREAM: - notifyTetheringNoUpstream(); - break; - } - } - } - - /** Called when downstream has changed */ - public void onDownstreamChanged(@IntRange(from = 0, to = 7) final int downstreamTypesMask) { - updateActiveNotifications( - mActiveDataSubId, downstreamTypesMask, mNoUpstream, mRoaming); - } - - /** Called when active data subscription id changed */ - public void onActiveDataSubscriptionIdChanged(final int subId) { - updateActiveNotifications(subId, mDownstreamTypesMask, mNoUpstream, mRoaming); - } - - /** Called when upstream network capabilities changed */ - public void onUpstreamCapabilitiesChanged(@Nullable final NetworkCapabilities capabilities) { - final boolean isNoUpstream = (capabilities == null); - final boolean isRoaming = capabilities != null - && !capabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING); - updateActiveNotifications( - mActiveDataSubId, mDownstreamTypesMask, isNoUpstream, isRoaming); - } - - @NonNull - @VisibleForTesting - final Handler getHandler() { - return mHandler; - } - - @NonNull - @VisibleForTesting - Resources getResourcesForSubId(@NonNull final Context context, final int subId) { - final Resources res = SubscriptionManager.getResourcesForSubId(context, subId); - final TelephonyManager tm = - ((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE)) - .createForSubscriptionId(mActiveDataSubId); - final int carrierId = tm.getSimCarrierId(); - final String mccmnc = tm.getSimOperator(); - final MccMncOverrideInfo overrideInfo = sCarrierIdToMccMnc.get(carrierId); - if (overrideInfo != null && overrideInfo.visitedMccMnc.equals(mccmnc)) { - // Re-configure MCC/MNC value to specific carrier to get right resources. - final Configuration config = res.getConfiguration(); - config.mcc = overrideInfo.homeMcc; - config.mnc = overrideInfo.homeMnc; - return context.createConfigurationContext(config).getResources(); - } - return res; - } - - private void updateActiveNotifications(final int subId, final int downstreamTypes, - final boolean noUpstream, final boolean isRoaming) { - final boolean tetheringActiveChanged = - (downstreamTypes == DOWNSTREAM_NONE) != (mDownstreamTypesMask == DOWNSTREAM_NONE); - final boolean subIdChanged = subId != mActiveDataSubId; - final boolean upstreamChanged = noUpstream != mNoUpstream; - final boolean roamingChanged = isRoaming != mRoaming; - final boolean updateAll = tetheringActiveChanged || subIdChanged; - mActiveDataSubId = subId; - mDownstreamTypesMask = downstreamTypes; - mNoUpstream = noUpstream; - mRoaming = isRoaming; - - if (updateAll || upstreamChanged) updateNoUpstreamNotification(); - if (updateAll || roamingChanged) updateRoamingNotification(); - } - - private void updateNoUpstreamNotification() { - final boolean tetheringInactive = mDownstreamTypesMask == DOWNSTREAM_NONE; - - if (tetheringInactive || !mNoUpstream || setupNoUpstreamNotification() == NO_NOTIFY) { - clearNotification(NO_UPSTREAM_NOTIFICATION_ID); - mHandler.removeMessages(EVENT_SHOW_NO_UPSTREAM); - } - } - - private void updateRoamingNotification() { - final boolean tetheringInactive = mDownstreamTypesMask == DOWNSTREAM_NONE; - - if (tetheringInactive || !mRoaming || setupRoamingNotification() == NO_NOTIFY) { - clearNotification(ROAMING_NOTIFICATION_ID); - } - } - - @VisibleForTesting - void tetheringRestrictionLifted() { - clearNotification(RESTRICTED_NOTIFICATION_ID); - } - - private void clearNotification(@NotificationId final int id) { - mNotificationManager.cancel(null /* tag */, id); - } - - @VisibleForTesting - static String getSettingsPackageName(@NonNull final PackageManager pm) { - final Intent settingsIntent = new Intent(Settings.ACTION_SETTINGS); - final ComponentName settingsComponent = settingsIntent.resolveActivity(pm); - return settingsComponent != null - ? settingsComponent.getPackageName() : "com.android.settings"; - } - - @VisibleForTesting - void notifyTetheringDisabledByRestriction() { - final Resources res = getResourcesForSubId(mContext, mActiveDataSubId); - final String title = res.getString(R.string.disable_tether_notification_title); - final String message = res.getString(R.string.disable_tether_notification_message); - if (isEmpty(title) || isEmpty(message)) return; - - final PendingIntent pi = PendingIntent.getActivity( - mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */), - 0 /* requestCode */, - new Intent(Settings.ACTION_TETHER_SETTINGS) - .setPackage(getSettingsPackageName(mContext.getPackageManager())) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), - PendingIntent.FLAG_IMMUTABLE, - null /* options */); - - showNotification(R.drawable.stat_sys_tether_general, title, message, - RESTRICTED_NOTIFICATION_ID, false /* ongoing */, pi, new Action[0]); - } - - private void notifyTetheringNoUpstream() { - final Resources res = getResourcesForSubId(mContext, mActiveDataSubId); - final String title = res.getString(R.string.no_upstream_notification_title); - final String message = res.getString(R.string.no_upstream_notification_message); - final String disableButton = - res.getString(R.string.no_upstream_notification_disable_button); - if (isEmpty(title) || isEmpty(message) || isEmpty(disableButton)) return; - - final Intent intent = new Intent(ACTION_DISABLE_TETHERING); - intent.setPackage(mContext.getPackageName()); - final PendingIntent pi = PendingIntent.getBroadcast( - mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */), - 0 /* requestCode */, - intent, - PendingIntent.FLAG_IMMUTABLE); - final Action action = new Action.Builder(NO_ICON_ID, disableButton, pi).build(); - - showNotification(R.drawable.stat_sys_tether_general, title, message, - NO_UPSTREAM_NOTIFICATION_ID, true /* ongoing */, null /* pendingIntent */, action); - } - - private boolean setupRoamingNotification() { - final Resources res = getResourcesForSubId(mContext, mActiveDataSubId); - final boolean upstreamRoamingNotification = - res.getBoolean(R.bool.config_upstream_roaming_notification); - - if (!upstreamRoamingNotification) return NO_NOTIFY; - - final String title = res.getString(R.string.upstream_roaming_notification_title); - final String message = res.getString(R.string.upstream_roaming_notification_message); - if (isEmpty(title) || isEmpty(message)) return NO_NOTIFY; - - final PendingIntent pi = PendingIntent.getActivity( - mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */), - 0 /* requestCode */, - new Intent(Settings.ACTION_TETHER_SETTINGS) - .setPackage(getSettingsPackageName(mContext.getPackageManager())) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), - PendingIntent.FLAG_IMMUTABLE, - null /* options */); - - showNotification(R.drawable.stat_sys_tether_general, title, message, - ROAMING_NOTIFICATION_ID, true /* ongoing */, pi, new Action[0]); - return NOTIFY_DONE; - } - - private boolean setupNoUpstreamNotification() { - final Resources res = getResourcesForSubId(mContext, mActiveDataSubId); - final int delayToShowUpstreamNotification = - res.getInteger(R.integer.delay_to_show_no_upstream_after_no_backhaul); - - if (delayToShowUpstreamNotification < 0) return NO_NOTIFY; - - mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_SHOW_NO_UPSTREAM), - delayToShowUpstreamNotification); - return NOTIFY_DONE; - } - - private void showNotification(@DrawableRes final int iconId, @NonNull final String title, - @NonNull final String message, @NotificationId final int id, final boolean ongoing, - @Nullable PendingIntent pi, @NonNull final Action... actions) { - final Notification notification = - new Notification.Builder(mContext, mChannel.getId()) - .setSmallIcon(iconId) - .setContentTitle(title) - .setContentText(message) - .setOngoing(ongoing) - .setColor(mContext.getColor( - android.R.color.system_notification_accent_color)) - .setVisibility(Notification.VISIBILITY_PUBLIC) - .setCategory(Notification.CATEGORY_STATUS) - .setContentIntent(pi) - .setActions(actions) - .build(); - - mNotificationManager.notify(null /* tag */, id, notification); - } -} diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringService.java deleted file mode 100644 index d637ba7557fa..000000000000 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringService.java +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.networkstack.tethering; - -import static android.Manifest.permission.ACCESS_NETWORK_STATE; -import static android.Manifest.permission.NETWORK_STACK; -import static android.Manifest.permission.TETHER_PRIVILEGED; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; -import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION; -import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION; -import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; -import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED; -import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR; - -import android.app.Service; -import android.bluetooth.BluetoothAdapter; -import android.content.Context; -import android.content.Intent; -import android.net.IIntResultListener; -import android.net.INetworkStackConnector; -import android.net.ITetheringConnector; -import android.net.ITetheringEventCallback; -import android.net.NetworkCapabilities; -import android.net.NetworkRequest; -import android.net.NetworkStack; -import android.net.TetheringRequestParcel; -import android.net.dhcp.DhcpServerCallbacks; -import android.net.dhcp.DhcpServingParamsParcel; -import android.net.ip.IpServer; -import android.os.Binder; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.Looper; -import android.os.RemoteException; -import android.os.ResultReceiver; -import android.provider.Settings; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.internal.annotations.VisibleForTesting; - -import java.io.FileDescriptor; -import java.io.PrintWriter; - -/** - * Android service used to manage tethering. - * - * <p>The service returns a binder for the system server to communicate with the tethering. - */ -public class TetheringService extends Service { - private static final String TAG = TetheringService.class.getSimpleName(); - - private TetheringConnector mConnector; - - @Override - public void onCreate() { - final TetheringDependencies deps = makeTetheringDependencies(); - // The Tethering object needs a fully functional context to start, so this can't be done - // in the constructor. - mConnector = new TetheringConnector(makeTethering(deps), TetheringService.this); - } - - /** - * Make a reference to Tethering object. - */ - @VisibleForTesting - public Tethering makeTethering(TetheringDependencies deps) { - System.loadLibrary("tetherutilsjni"); - return new Tethering(deps); - } - - @NonNull - @Override - public IBinder onBind(Intent intent) { - return mConnector; - } - - private static class TetheringConnector extends ITetheringConnector.Stub { - private final TetheringService mService; - private final Tethering mTethering; - - TetheringConnector(Tethering tether, TetheringService service) { - mTethering = tether; - mService = service; - } - - @Override - public void tether(String iface, String callerPkg, String callingAttributionTag, - IIntResultListener listener) { - if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; - - try { - listener.onResult(mTethering.tether(iface)); - } catch (RemoteException e) { } - } - - @Override - public void untether(String iface, String callerPkg, String callingAttributionTag, - IIntResultListener listener) { - if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; - - try { - listener.onResult(mTethering.untether(iface)); - } catch (RemoteException e) { } - } - - @Override - public void setUsbTethering(boolean enable, String callerPkg, String callingAttributionTag, - IIntResultListener listener) { - if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; - - try { - listener.onResult(mTethering.setUsbTethering(enable)); - } catch (RemoteException e) { } - } - - @Override - public void startTethering(TetheringRequestParcel request, String callerPkg, - String callingAttributionTag, IIntResultListener listener) { - if (checkAndNotifyCommonError(callerPkg, - callingAttributionTag, - request.exemptFromEntitlementCheck /* onlyAllowPrivileged */, - listener)) { - return; - } - - mTethering.startTethering(request, listener); - } - - @Override - public void stopTethering(int type, String callerPkg, String callingAttributionTag, - IIntResultListener listener) { - if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; - - try { - mTethering.stopTethering(type); - listener.onResult(TETHER_ERROR_NO_ERROR); - } catch (RemoteException e) { } - } - - @Override - public void requestLatestTetheringEntitlementResult(int type, ResultReceiver receiver, - boolean showEntitlementUi, String callerPkg, String callingAttributionTag) { - if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, receiver)) return; - - mTethering.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi); - } - - @Override - public void registerTetheringEventCallback(ITetheringEventCallback callback, - String callerPkg) { - try { - if (!hasTetherAccessPermission()) { - callback.onCallbackStopped(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION); - return; - } - mTethering.registerTetheringEventCallback(callback); - } catch (RemoteException e) { } - } - - @Override - public void unregisterTetheringEventCallback(ITetheringEventCallback callback, - String callerPkg) { - try { - if (!hasTetherAccessPermission()) { - callback.onCallbackStopped(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION); - return; - } - mTethering.unregisterTetheringEventCallback(callback); - } catch (RemoteException e) { } - } - - @Override - public void stopAllTethering(String callerPkg, String callingAttributionTag, - IIntResultListener listener) { - if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; - - try { - mTethering.untetherAll(); - listener.onResult(TETHER_ERROR_NO_ERROR); - } catch (RemoteException e) { } - } - - @Override - public void isTetheringSupported(String callerPkg, String callingAttributionTag, - IIntResultListener listener) { - if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; - - try { - listener.onResult(TETHER_ERROR_NO_ERROR); - } catch (RemoteException e) { } - } - - @Override - protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, - @Nullable String[] args) { - mTethering.dump(fd, writer, args); - } - - private boolean checkAndNotifyCommonError(final String callerPkg, - final String callingAttributionTag, final IIntResultListener listener) { - return checkAndNotifyCommonError(callerPkg, callingAttributionTag, - false /* onlyAllowPrivileged */, listener); - } - - private boolean checkAndNotifyCommonError(final String callerPkg, - final String callingAttributionTag, final boolean onlyAllowPrivileged, - final IIntResultListener listener) { - try { - if (!hasTetherChangePermission(callerPkg, callingAttributionTag, - onlyAllowPrivileged)) { - listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); - return true; - } - if (!mTethering.isTetheringSupported()) { - listener.onResult(TETHER_ERROR_UNSUPPORTED); - return true; - } - } catch (RemoteException e) { - return true; - } - - return false; - } - - private boolean checkAndNotifyCommonError(final String callerPkg, - final String callingAttributionTag, final ResultReceiver receiver) { - if (!hasTetherChangePermission(callerPkg, callingAttributionTag, - false /* onlyAllowPrivileged */)) { - receiver.send(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION, null); - return true; - } - if (!mTethering.isTetheringSupported()) { - receiver.send(TETHER_ERROR_UNSUPPORTED, null); - return true; - } - - return false; - } - - private boolean hasNetworkStackPermission() { - return checkCallingOrSelfPermission(NETWORK_STACK) - || checkCallingOrSelfPermission(PERMISSION_MAINLINE_NETWORK_STACK); - } - - private boolean hasTetherPrivilegedPermission() { - return checkCallingOrSelfPermission(TETHER_PRIVILEGED); - } - - private boolean checkCallingOrSelfPermission(final String permission) { - return mService.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED; - } - - private boolean hasTetherChangePermission(final String callerPkg, - final String callingAttributionTag, final boolean onlyAllowPrivileged) { - if (onlyAllowPrivileged && !hasNetworkStackPermission()) return false; - - if (hasTetherPrivilegedPermission()) return true; - - if (mTethering.isTetherProvisioningRequired()) return false; - - int uid = Binder.getCallingUid(); - - // If callerPkg's uid is not same as Binder.getCallingUid(), - // checkAndNoteWriteSettingsOperation will return false and the operation will be - // denied. - return mService.checkAndNoteWriteSettingsOperation(mService, uid, callerPkg, - callingAttributionTag, false /* throwException */); - } - - private boolean hasTetherAccessPermission() { - if (hasTetherPrivilegedPermission()) return true; - - return mService.checkCallingOrSelfPermission( - ACCESS_NETWORK_STATE) == PERMISSION_GRANTED; - } - } - - /** - * Check if the package is a allowed to write settings. This also accounts that such an access - * happened. - * - * @return {@code true} iff the package is allowed to write settings. - */ - @VisibleForTesting - boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid, - @NonNull String callingPackage, @Nullable String callingAttributionTag, - boolean throwException) { - return Settings.checkAndNoteWriteSettingsOperation(context, uid, callingPackage, - callingAttributionTag, throwException); - } - - /** - * An injection method for testing. - */ - @VisibleForTesting - public TetheringDependencies makeTetheringDependencies() { - return new TetheringDependencies() { - @Override - public NetworkRequest getDefaultNetworkRequest() { - // TODO: b/147280869, add a proper system API to replace this. - final NetworkRequest trackDefaultRequest = new NetworkRequest.Builder() - .clearCapabilities() - .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) - .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) - .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .build(); - return trackDefaultRequest; - } - - @Override - public Looper getTetheringLooper() { - final HandlerThread tetherThread = new HandlerThread("android.tethering"); - tetherThread.start(); - return tetherThread.getLooper(); - } - - @Override - public Context getContext() { - return TetheringService.this; - } - - @Override - public IpServer.Dependencies getIpServerDependencies() { - return new IpServer.Dependencies() { - @Override - public void makeDhcpServer(String ifName, DhcpServingParamsParcel params, - DhcpServerCallbacks cb) { - try { - final INetworkStackConnector service = getNetworkStackConnector(); - if (service == null) return; - - service.makeDhcpServer(ifName, params, cb); - } catch (RemoteException e) { - Log.e(TAG, "Fail to make dhcp server"); - try { - cb.onDhcpServerCreated(STATUS_UNKNOWN_ERROR, null); - } catch (RemoteException re) { } - } - } - }; - } - - // TODO: replace this by NetworkStackClient#getRemoteConnector after refactoring - // networkStackClient. - static final int NETWORKSTACK_TIMEOUT_MS = 60_000; - private INetworkStackConnector getNetworkStackConnector() { - IBinder connector; - try { - final long before = System.currentTimeMillis(); - while ((connector = NetworkStack.getService()) == null) { - if (System.currentTimeMillis() - before > NETWORKSTACK_TIMEOUT_MS) { - Log.wtf(TAG, "Timeout, fail to get INetworkStackConnector"); - return null; - } - Thread.sleep(200); - } - } catch (InterruptedException e) { - Log.wtf(TAG, "Interrupted, fail to get INetworkStackConnector"); - return null; - } - return INetworkStackConnector.Stub.asInterface(connector); - } - - @Override - public BluetoothAdapter getBluetoothAdapter() { - return BluetoothAdapter.getDefaultAdapter(); - } - }; - } -} diff --git a/packages/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkState.java b/packages/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkState.java deleted file mode 100644 index bab9f84cf762..000000000000 --- a/packages/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkState.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.networkstack.tethering; - -import android.net.LinkProperties; -import android.net.Network; -import android.net.NetworkCapabilities; - -import androidx.annotation.NonNull; - -/** - * Snapshot of tethering upstream network state. - */ -public class UpstreamNetworkState { - /** {@link LinkProperties}. */ - public final LinkProperties linkProperties; - /** {@link NetworkCapabilities}. */ - public final NetworkCapabilities networkCapabilities; - /** {@link Network}. */ - public final Network network; - - /** Constructs a new UpstreamNetworkState. */ - public UpstreamNetworkState(LinkProperties linkProperties, - NetworkCapabilities networkCapabilities, Network network) { - this.linkProperties = linkProperties; - this.networkCapabilities = networkCapabilities; - this.network = network; - } - - @NonNull - @Override - public String toString() { - return String.format("UpstreamNetworkState{%s, %s, %s}", - network == null ? "null" : network, - networkCapabilities == null ? "null" : networkCapabilities, - linkProperties == null ? "null" : linkProperties); - } -} |