getAddresses() {
+ return new ArrayList<>(mAddresses);
+ }
+
+ /**
+ * Get the type of tethering used by the client.
+ * @return one of the {@code TetheringManager#TETHERING_*} constants.
+ */
+ public int getTetheringType() {
+ return mTetheringType;
+ }
+
+ /**
+ * Return a new {@link TetheredClient} that has all the attributes of this instance, plus the
+ * {@link AddressInfo} of the provided {@link TetheredClient}.
+ *
+ * Duplicate addresses are removed.
+ * @hide
+ */
+ public TetheredClient addAddresses(@NonNull TetheredClient other) {
+ final LinkedHashSet newAddresses = new LinkedHashSet<>(
+ mAddresses.size() + other.mAddresses.size());
+ newAddresses.addAll(mAddresses);
+ newAddresses.addAll(other.mAddresses);
+ return new TetheredClient(mMacAddress, newAddresses, mTetheringType);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mMacAddress, mAddresses, mTetheringType);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof TetheredClient)) return false;
+ final TetheredClient other = (TetheredClient) obj;
+ return mMacAddress.equals(other.mMacAddress)
+ && mAddresses.equals(other.mAddresses)
+ && mTetheringType == other.mTetheringType;
+ }
+
+ /**
+ * Information on an lease assigned to a tethered client.
+ */
+ public static final class AddressInfo implements Parcelable {
+ @NonNull
+ private final LinkAddress mAddress;
+ @Nullable
+ private final String mHostname;
+
+ /** @hide */
+ public AddressInfo(@NonNull LinkAddress address, @Nullable String hostname) {
+ this.mAddress = address;
+ this.mHostname = hostname;
+ }
+
+ private AddressInfo(Parcel in) {
+ this(in.readParcelable(null), in.readString());
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mAddress, flags);
+ dest.writeString(mHostname);
+ }
+
+ /**
+ * Get the link address (including prefix length and lifetime) used by the client.
+ *
+ * This may be an IPv4 or IPv6 address.
+ */
+ @NonNull
+ public LinkAddress getAddress() {
+ return mAddress;
+ }
+
+ /**
+ * Get the hostname that was advertised by the client when obtaining its address, if any.
+ */
+ @Nullable
+ public String getHostname() {
+ return mHostname;
+ }
+
+ /**
+ * Get the expiration time of the address assigned to the client.
+ * @hide
+ */
+ public long getExpirationTime() {
+ return mAddress.getExpirationTime();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAddress, mHostname);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof AddressInfo)) return false;
+ final AddressInfo other = (AddressInfo) obj;
+ // Use .equals() for addresses as all changes, including address expiry changes,
+ // should be included.
+ return other.mAddress.equals(mAddress)
+ && Objects.equals(mHostname, other.mHostname);
+ }
+
+ @NonNull
+ public static final Creator CREATOR = new Creator() {
+ @NonNull
+ @Override
+ public AddressInfo createFromParcel(@NonNull Parcel in) {
+ return new AddressInfo(in);
+ }
+
+ @NonNull
+ @Override
+ public AddressInfo[] newArray(int size) {
+ return new AddressInfo[size];
+ }
+ };
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "AddressInfo {"
+ + mAddress
+ + (mHostname != null ? ", hostname " + mHostname : "")
+ + "}";
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Creator CREATOR = new Creator() {
+ @NonNull
+ @Override
+ public TetheredClient createFromParcel(@NonNull Parcel in) {
+ return new TetheredClient(in);
+ }
+
+ @NonNull
+ @Override
+ public TetheredClient[] newArray(int size) {
+ return new TetheredClient[size];
+ }
+ };
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "TetheredClient {hwAddr " + mMacAddress
+ + ", addresses " + mAddresses
+ + ", tetheringType " + mTetheringType
+ + "}";
+ }
+}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl b/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
new file mode 100644
index 0000000000..253eacbd23
--- /dev/null
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+import android.net.Network;
+import android.net.TetheredClient;
+import android.net.TetheringConfigurationParcel;
+import android.net.TetherStatesParcel;
+
+/**
+ * Initial information reported by tethering upon callback registration.
+ * @hide
+ */
+parcelable TetheringCallbackStartedParcel {
+ boolean tetheringSupported;
+ Network upstreamNetwork;
+ TetheringConfigurationParcel config;
+ TetherStatesParcel states;
+ List tetheredClients;
+ int offloadStatus;
+}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringConfigurationParcel.aidl b/Tethering/common/TetheringLib/src/android/net/TetheringConfigurationParcel.aidl
new file mode 100644
index 0000000000..89f38132ff
--- /dev/null
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringConfigurationParcel.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+/**
+ * Configuration details for tethering.
+ * @hide
+ */
+parcelable TetheringConfigurationParcel {
+ int subId;
+ String[] tetherableUsbRegexs;
+ String[] tetherableWifiRegexs;
+ String[] tetherableBluetoothRegexs;
+ boolean isDunRequired;
+ boolean chooseUpstreamAutomatically;
+ int[] preferredUpstreamIfaceTypes;
+ String[] legacyDhcpRanges;
+ String[] defaultIPv4DNS;
+ boolean enableLegacyDhcpServer;
+ String[] provisioningApp;
+ String provisioningAppNoUi;
+ int provisioningCheckPeriod;
+}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java b/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
new file mode 100644
index 0000000000..f14def6a3a
--- /dev/null
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java
@@ -0,0 +1,93 @@
+/*
+ * 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;
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.annotation.SystemApi;
+import android.os.ResultReceiver;
+
+/**
+ * Collections of constants for internal tethering usage.
+ *
+ * These hidden constants are not in TetheringManager as they are not part of the API stubs
+ * generated for TetheringManager, which prevents the tethering module from linking them at
+ * build time.
+ * TODO: investigate changing the tethering build rules so that Tethering can reference hidden
+ * symbols from framework-tethering even when they are in a non-hidden class.
+ * @hide
+ */
+@SystemApi(client = MODULE_LIBRARIES)
+public final class TetheringConstants {
+ /** An explicit private class to avoid exposing constructor.*/
+ private TetheringConstants() { }
+
+ /**
+ * Extra used for communicating with the TetherService and TetherProvisioningActivity.
+ * Includes the type of tethering to enable if any.
+ */
+ public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
+ /**
+ * Extra used for communicating with the TetherService. Includes the type of tethering for
+ * which to cancel provisioning.
+ */
+ public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
+ /**
+ * Extra used for communicating with the TetherService. True to schedule a recheck of tether
+ * provisioning.
+ */
+ public static final String EXTRA_SET_ALARM = "extraSetAlarm";
+ /**
+ * Tells the TetherService to run a provision check now.
+ */
+ public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
+ /**
+ * Extra used for communicating with the TetherService and TetherProvisioningActivity.
+ * Contains the {@link ResultReceiver} which will receive provisioning results.
+ * Can not be empty.
+ */
+ public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
+
+ /**
+ * Extra used for communicating with the TetherService and TetherProvisioningActivity.
+ * Contains the subId of current active cellular upstream.
+ * @hide
+ */
+ public static final String EXTRA_TETHER_SUBID = "android.net.extra.TETHER_SUBID";
+
+ /**
+ * Extra used for telling TetherProvisioningActivity the entitlement package name and class
+ * name to start UI entitlement check.
+ * @hide
+ */
+ public static final String EXTRA_TETHER_UI_PROVISIONING_APP_NAME =
+ "android.net.extra.TETHER_UI_PROVISIONING_APP_NAME";
+
+ /**
+ * Extra used for telling TetherService the intent action to start silent entitlement check.
+ * @hide
+ */
+ public static final String EXTRA_TETHER_SILENT_PROVISIONING_ACTION =
+ "android.net.extra.TETHER_SILENT_PROVISIONING_ACTION";
+
+ /**
+ * Extra used for TetherService to receive the response of provisioning check.
+ * @hide
+ */
+ public static final String EXTRA_TETHER_PROVISIONING_RESPONSE =
+ "android.net.extra.TETHER_PROVISIONING_RESPONSE";
+}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringInterface.aidl b/Tethering/common/TetheringLib/src/android/net/TetheringInterface.aidl
new file mode 100644
index 0000000000..715198447f
--- /dev/null
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringInterface.aidl
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+@JavaOnlyStableParcelable parcelable TetheringInterface;
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringInterface.java b/Tethering/common/TetheringLib/src/android/net/TetheringInterface.java
new file mode 100644
index 0000000000..84cdef1163
--- /dev/null
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringInterface.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.net.TetheringManager.TetheringType;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * The mapping of tethering interface and type.
+ * @hide
+ */
+@SystemApi
+public final class TetheringInterface implements Parcelable {
+ private final int mType;
+ private final String mInterface;
+
+ public TetheringInterface(@TetheringType int type, @NonNull String iface) {
+ Objects.requireNonNull(iface);
+ mType = type;
+ mInterface = iface;
+ }
+
+ private TetheringInterface(@NonNull Parcel in) {
+ this(in.readInt(), in.readString());
+ }
+
+ /** Get tethering type. */
+ public int getType() {
+ return mType;
+ }
+
+ /** Get tethering interface. */
+ @NonNull
+ public String getInterface() {
+ return mInterface;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mType);
+ dest.writeString(mInterface);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mType, mInterface);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof TetheringInterface)) return false;
+ final TetheringInterface other = (TetheringInterface) obj;
+ return mType == other.mType && mInterface.equals(other.mInterface);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Creator CREATOR = new Creator() {
+ @NonNull
+ @Override
+ public TetheringInterface createFromParcel(@NonNull Parcel in) {
+ return new TetheringInterface(in);
+ }
+
+ @NonNull
+ @Override
+ public TetheringInterface[] newArray(int size) {
+ return new TetheringInterface[size];
+ }
+ };
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "TetheringInterface {mType=" + mType
+ + ", mInterface=" + mInterface + "}";
+ }
+}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
new file mode 100644
index 0000000000..edd141d383
--- /dev/null
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -0,0 +1,1541 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.Supplier;
+
+/**
+ * This class provides the APIs to control the tethering service.
+ * The primary responsibilities of this class are to provide the APIs for applications to
+ * start tethering, stop tethering, query configuration and query status.
+ *
+ * @hide
+ */
+@SystemApi
+public class TetheringManager {
+ private static final String TAG = TetheringManager.class.getSimpleName();
+ private static final int DEFAULT_TIMEOUT_MS = 60_000;
+ private static final long CONNECTOR_POLL_INTERVAL_MILLIS = 200L;
+
+ @GuardedBy("mConnectorWaitQueue")
+ @Nullable
+ private ITetheringConnector mConnector;
+ @GuardedBy("mConnectorWaitQueue")
+ @NonNull
+ private final List mConnectorWaitQueue = new ArrayList<>();
+ private final Supplier mConnectorSupplier;
+
+ private final TetheringCallbackInternal mCallback;
+ private final Context mContext;
+ private final ArrayMap
+ mTetheringEventCallbacks = new ArrayMap<>();
+
+ private volatile TetheringConfigurationParcel mTetheringConfiguration;
+ private volatile TetherStatesParcel mTetherStatesParcel;
+
+ /**
+ * Broadcast Action: A tetherable connection has come or gone.
+ * Uses {@code TetheringManager.EXTRA_AVAILABLE_TETHER},
+ * {@code TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY},
+ * {@code TetheringManager.EXTRA_ACTIVE_TETHER}, and
+ * {@code TetheringManager.EXTRA_ERRORED_TETHER} to indicate
+ * the current state of tethering. Each include a list of
+ * interface names in that state (may be empty).
+ *
+ * @deprecated New client should use TetheringEventCallback instead.
+ */
+ @Deprecated
+ public static final String ACTION_TETHER_STATE_CHANGED =
+ "android.net.conn.TETHER_STATE_CHANGED";
+
+ /**
+ * gives a String[] listing all the interfaces configured for
+ * tethering and currently available for tethering.
+ */
+ public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
+
+ /**
+ * gives a String[] listing all the interfaces currently in local-only
+ * mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding)
+ */
+ public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY";
+
+ /**
+ * gives a String[] listing all the interfaces currently tethered
+ * (ie, has DHCPv4 support and packets potentially forwarded/NATed)
+ */
+ public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
+
+ /**
+ * gives a String[] listing all the interfaces we tried to tether and
+ * failed. Use {@link #getLastTetherError} to find the error code
+ * for any interfaces listed here.
+ */
+ public static final String EXTRA_ERRORED_TETHER = "erroredArray";
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = false, value = {
+ TETHERING_WIFI,
+ TETHERING_USB,
+ TETHERING_BLUETOOTH,
+ TETHERING_WIFI_P2P,
+ TETHERING_NCM,
+ TETHERING_ETHERNET,
+ })
+ public @interface TetheringType {
+ }
+
+ /**
+ * Invalid tethering type.
+ * @see #startTethering.
+ */
+ public static final int TETHERING_INVALID = -1;
+
+ /**
+ * Wifi tethering type.
+ * @see #startTethering.
+ */
+ public static final int TETHERING_WIFI = 0;
+
+ /**
+ * USB tethering type.
+ * @see #startTethering.
+ */
+ public static final int TETHERING_USB = 1;
+
+ /**
+ * Bluetooth tethering type.
+ * @see #startTethering.
+ */
+ public static final int TETHERING_BLUETOOTH = 2;
+
+ /**
+ * Wifi P2p tethering type.
+ * Wifi P2p tethering is set through events automatically, and don't
+ * need to start from #startTethering.
+ */
+ public static final int TETHERING_WIFI_P2P = 3;
+
+ /**
+ * Ncm local tethering type.
+ * @see #startTethering(TetheringRequest, Executor, StartTetheringCallback)
+ */
+ public static final int TETHERING_NCM = 4;
+
+ /**
+ * Ethernet tethering type.
+ * @see #startTethering(TetheringRequest, Executor, StartTetheringCallback)
+ */
+ public static final int TETHERING_ETHERNET = 5;
+
+ /**
+ * WIGIG tethering type. Use a separate type to prevent
+ * conflicts with TETHERING_WIFI
+ * This type is only used internally by the tethering module
+ * @hide
+ */
+ public static final int TETHERING_WIGIG = 6;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ TETHER_ERROR_NO_ERROR,
+ TETHER_ERROR_PROVISIONING_FAILED,
+ TETHER_ERROR_ENTITLEMENT_UNKNOWN,
+ })
+ public @interface EntitlementResult {
+ }
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ TETHER_ERROR_NO_ERROR,
+ TETHER_ERROR_UNKNOWN_IFACE,
+ TETHER_ERROR_SERVICE_UNAVAIL,
+ TETHER_ERROR_INTERNAL_ERROR,
+ TETHER_ERROR_TETHER_IFACE_ERROR,
+ TETHER_ERROR_ENABLE_FORWARDING_ERROR,
+ TETHER_ERROR_DISABLE_FORWARDING_ERROR,
+ TETHER_ERROR_IFACE_CFG_ERROR,
+ TETHER_ERROR_DHCPSERVER_ERROR,
+ })
+ public @interface TetheringIfaceError {
+ }
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ TETHER_ERROR_SERVICE_UNAVAIL,
+ TETHER_ERROR_INTERNAL_ERROR,
+ TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION,
+ TETHER_ERROR_UNKNOWN_TYPE,
+ })
+ public @interface StartTetheringError {
+ }
+
+ public static final int TETHER_ERROR_NO_ERROR = 0;
+ public static final int TETHER_ERROR_UNKNOWN_IFACE = 1;
+ public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2;
+ public static final int TETHER_ERROR_UNSUPPORTED = 3;
+ public static final int TETHER_ERROR_UNAVAIL_IFACE = 4;
+ public static final int TETHER_ERROR_INTERNAL_ERROR = 5;
+ public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6;
+ public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7;
+ public static final int TETHER_ERROR_ENABLE_FORWARDING_ERROR = 8;
+ public static final int TETHER_ERROR_DISABLE_FORWARDING_ERROR = 9;
+ public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10;
+ public static final int TETHER_ERROR_PROVISIONING_FAILED = 11;
+ public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12;
+ public static final int TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13;
+ public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14;
+ public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15;
+ public static final int TETHER_ERROR_UNKNOWN_TYPE = 16;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = false, value = {
+ TETHER_HARDWARE_OFFLOAD_STOPPED,
+ TETHER_HARDWARE_OFFLOAD_STARTED,
+ TETHER_HARDWARE_OFFLOAD_FAILED,
+ })
+ public @interface TetherOffloadStatus {
+ }
+
+ /** Tethering offload status is stopped. */
+ public static final int TETHER_HARDWARE_OFFLOAD_STOPPED = 0;
+ /** Tethering offload status is started. */
+ public static final int TETHER_HARDWARE_OFFLOAD_STARTED = 1;
+ /** Fail to start tethering offload. */
+ public static final int TETHER_HARDWARE_OFFLOAD_FAILED = 2;
+
+ /**
+ * Create a TetheringManager object for interacting with the tethering service.
+ *
+ * @param context Context for the manager.
+ * @param connectorSupplier Supplier for the manager connector; may return null while the
+ * service is not connected.
+ * {@hide}
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public TetheringManager(@NonNull final Context context,
+ @NonNull Supplier connectorSupplier) {
+ mContext = context;
+ mCallback = new TetheringCallbackInternal();
+ mConnectorSupplier = connectorSupplier;
+
+ final String pkgName = mContext.getOpPackageName();
+
+ final IBinder connector = mConnectorSupplier.get();
+ // If the connector is available on start, do not start a polling thread. This introduces
+ // differences in the thread that sends the oneway binder calls to the service between the
+ // first few seconds after boot and later, but it avoids always having differences between
+ // the first usage of TetheringManager from a process and subsequent usages (so the
+ // difference is only on boot). On boot binder calls may be queued until the service comes
+ // up and be sent from a worker thread; later, they are always sent from the caller thread.
+ // Considering that it's just oneway binder calls, and ordering is preserved, this seems
+ // better than inconsistent behavior persisting after boot.
+ if (connector != null) {
+ mConnector = ITetheringConnector.Stub.asInterface(connector);
+ } else {
+ startPollingForConnector();
+ }
+
+ Log.i(TAG, "registerTetheringEventCallback:" + pkgName);
+ getConnector(c -> c.registerTetheringEventCallback(mCallback, pkgName));
+ }
+
+ private void startPollingForConnector() {
+ new Thread(() -> {
+ while (true) {
+ try {
+ Thread.sleep(CONNECTOR_POLL_INTERVAL_MILLIS);
+ } catch (InterruptedException e) {
+ // Not much to do here, the system needs to wait for the connector
+ }
+
+ final IBinder connector = mConnectorSupplier.get();
+ if (connector != null) {
+ onTetheringConnected(ITetheringConnector.Stub.asInterface(connector));
+ return;
+ }
+ }
+ }).start();
+ }
+
+ private interface ConnectorConsumer {
+ void onConnectorAvailable(ITetheringConnector connector) throws RemoteException;
+ }
+
+ private void onTetheringConnected(ITetheringConnector connector) {
+ // Process the connector wait queue in order, including any items that are added
+ // while processing.
+ //
+ // 1. Copy the queue to a local variable under lock.
+ // 2. Drain the local queue with the lock released (otherwise, enqueuing future commands
+ // would block on the lock).
+ // 3. Acquire the lock again. If any new tasks were queued during step 2, goto 1.
+ // If not, set mConnector to non-null so future tasks are run immediately, not queued.
+ //
+ // For this to work, all calls to the tethering service must use getConnector(), which
+ // ensures that tasks are added to the queue with the lock held.
+ //
+ // Once mConnector is set to non-null, it will never be null again. If the network stack
+ // process crashes, no recovery is possible.
+ // TODO: evaluate whether it is possible to recover from network stack process crashes
+ // (though in most cases the system will have crashed when the network stack process
+ // crashes).
+ do {
+ final List localWaitQueue;
+ synchronized (mConnectorWaitQueue) {
+ localWaitQueue = new ArrayList<>(mConnectorWaitQueue);
+ mConnectorWaitQueue.clear();
+ }
+
+ // Allow more tasks to be added at the end without blocking while draining the queue.
+ for (ConnectorConsumer task : localWaitQueue) {
+ try {
+ task.onConnectorAvailable(connector);
+ } catch (RemoteException e) {
+ // Most likely the network stack process crashed, which is likely to crash the
+ // system. Keep processing other requests but report the error loudly.
+ Log.wtf(TAG, "Error processing request for the tethering connector", e);
+ }
+ }
+
+ synchronized (mConnectorWaitQueue) {
+ if (mConnectorWaitQueue.size() == 0) {
+ mConnector = connector;
+ return;
+ }
+ }
+ } while (true);
+ }
+
+ /**
+ * Asynchronously get the ITetheringConnector to execute some operation.
+ *
+ * If the connector is already available, the operation will be executed on the caller's
+ * thread. Otherwise it will be queued and executed on a worker thread. The operation should be
+ * limited to performing oneway binder calls to minimize differences due to threading.
+ */
+ private void getConnector(ConnectorConsumer consumer) {
+ final ITetheringConnector connector;
+ synchronized (mConnectorWaitQueue) {
+ connector = mConnector;
+ if (connector == null) {
+ mConnectorWaitQueue.add(consumer);
+ return;
+ }
+ }
+
+ try {
+ consumer.onConnectorAvailable(connector);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private interface RequestHelper {
+ void runRequest(ITetheringConnector connector, IIntResultListener listener);
+ }
+
+ // Used to dispatch legacy ConnectivityManager methods that expect tethering to be able to
+ // return results and perform operations synchronously.
+ // TODO: remove once there are no callers of these legacy methods.
+ private class RequestDispatcher {
+ private final ConditionVariable mWaiting;
+ public volatile int mRemoteResult;
+
+ private final IIntResultListener mListener = new IIntResultListener.Stub() {
+ @Override
+ public void onResult(final int resultCode) {
+ mRemoteResult = resultCode;
+ mWaiting.open();
+ }
+ };
+
+ RequestDispatcher() {
+ mWaiting = new ConditionVariable();
+ }
+
+ int waitForResult(final RequestHelper request) {
+ getConnector(c -> request.runRequest(c, mListener));
+ if (!mWaiting.block(DEFAULT_TIMEOUT_MS)) {
+ throw new IllegalStateException("Callback timeout");
+ }
+
+ throwIfPermissionFailure(mRemoteResult);
+
+ return mRemoteResult;
+ }
+ }
+
+ private void throwIfPermissionFailure(final int errorCode) {
+ switch (errorCode) {
+ case TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION:
+ throw new SecurityException("No android.permission.TETHER_PRIVILEGED"
+ + " or android.permission.WRITE_SETTINGS permission");
+ case TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION:
+ throw new SecurityException(
+ "No android.permission.ACCESS_NETWORK_STATE permission");
+ }
+ }
+
+ private class TetheringCallbackInternal extends ITetheringEventCallback.Stub {
+ private volatile int mError = TETHER_ERROR_NO_ERROR;
+ private final ConditionVariable mWaitForCallback = new ConditionVariable();
+
+ @Override
+ public void onCallbackStarted(TetheringCallbackStartedParcel parcel) {
+ mTetheringConfiguration = parcel.config;
+ mTetherStatesParcel = parcel.states;
+ mWaitForCallback.open();
+ }
+
+ @Override
+ public void onCallbackStopped(int errorCode) {
+ mError = errorCode;
+ mWaitForCallback.open();
+ }
+
+ @Override
+ public void onUpstreamChanged(Network network) { }
+
+ @Override
+ public void onConfigurationChanged(TetheringConfigurationParcel config) {
+ mTetheringConfiguration = config;
+ }
+
+ @Override
+ public void onTetherStatesChanged(TetherStatesParcel states) {
+ mTetherStatesParcel = states;
+ }
+
+ @Override
+ public void onTetherClientsChanged(List clients) { }
+
+ @Override
+ public void onOffloadStatusChanged(int status) { }
+
+ public void waitForStarted() {
+ mWaitForCallback.block(DEFAULT_TIMEOUT_MS);
+ throwIfPermissionFailure(mError);
+ }
+ }
+
+ /**
+ * Attempt to tether the named interface. This will setup a dhcp server
+ * on the interface, forward and NAT IP v4 packets and forward DNS requests
+ * to the best active upstream network interface. Note that if no upstream
+ * IP network interface is available, dhcp will still run and traffic will be
+ * allowed between the tethered devices and this device, though upstream net
+ * access will of course fail until an upstream network interface becomes
+ * active.
+ *
+ * @deprecated The only usages is PanService. It uses this for legacy reasons
+ * and will migrate away as soon as possible.
+ *
+ * @param iface the interface name to tether.
+ * @return error a {@code TETHER_ERROR} value indicating success or failure type
+ *
+ * {@hide}
+ */
+ @Deprecated
+ @SystemApi(client = MODULE_LIBRARIES)
+ public int tether(@NonNull final String iface) {
+ final String callerPkg = mContext.getOpPackageName();
+ Log.i(TAG, "tether caller:" + callerPkg);
+ final RequestDispatcher dispatcher = new RequestDispatcher();
+
+ return dispatcher.waitForResult((connector, listener) -> {
+ try {
+ connector.tether(iface, callerPkg, getAttributionTag(), listener);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ });
+ }
+
+ /**
+ * @return the context's attribution tag
+ */
+ private @Nullable String getAttributionTag() {
+ return mContext.getAttributionTag();
+ }
+
+ /**
+ * Stop tethering the named interface.
+ *
+ * @deprecated The only usages is PanService. It uses this for legacy reasons
+ * and will migrate away as soon as possible.
+ *
+ * {@hide}
+ */
+ @Deprecated
+ @SystemApi(client = MODULE_LIBRARIES)
+ public int untether(@NonNull final String iface) {
+ final String callerPkg = mContext.getOpPackageName();
+ Log.i(TAG, "untether caller:" + callerPkg);
+
+ final RequestDispatcher dispatcher = new RequestDispatcher();
+
+ return dispatcher.waitForResult((connector, listener) -> {
+ try {
+ connector.untether(iface, callerPkg, getAttributionTag(), listener);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ });
+ }
+
+ /**
+ * Attempt to both alter the mode of USB and Tethering of USB.
+ *
+ * @deprecated New clients should not use this API anymore. All clients should use
+ * #startTethering or #stopTethering which encapsulate proper entitlement logic. If the API is
+ * used and an entitlement check is needed, downstream USB tethering will be enabled but will
+ * not have any upstream.
+ *
+ * {@hide}
+ */
+ @Deprecated
+ @SystemApi(client = MODULE_LIBRARIES)
+ public int setUsbTethering(final boolean enable) {
+ final String callerPkg = mContext.getOpPackageName();
+ Log.i(TAG, "setUsbTethering caller:" + callerPkg);
+
+ final RequestDispatcher dispatcher = new RequestDispatcher();
+
+ return dispatcher.waitForResult((connector, listener) -> {
+ try {
+ connector.setUsbTethering(enable, callerPkg, getAttributionTag(),
+ listener);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ });
+ }
+
+ /**
+ * Indicates that this tethering connection will provide connectivity beyond this device (e.g.,
+ * global Internet access).
+ */
+ public static final int CONNECTIVITY_SCOPE_GLOBAL = 1;
+
+ /**
+ * Indicates that this tethering connection will only provide local connectivity.
+ */
+ public static final int CONNECTIVITY_SCOPE_LOCAL = 2;
+
+ /**
+ * Connectivity scopes for {@link TetheringRequest.Builder#setConnectivityScope}.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "CONNECTIVITY_SCOPE_", value = {
+ CONNECTIVITY_SCOPE_GLOBAL,
+ CONNECTIVITY_SCOPE_LOCAL,
+ })
+ public @interface ConnectivityScope {}
+
+ /**
+ * Use with {@link #startTethering} to specify additional parameters when starting tethering.
+ */
+ public static class TetheringRequest {
+ /** A configuration set for TetheringRequest. */
+ private final TetheringRequestParcel mRequestParcel;
+
+ private TetheringRequest(final TetheringRequestParcel request) {
+ mRequestParcel = request;
+ }
+
+ /** Builder used to create TetheringRequest. */
+ public static class Builder {
+ private final TetheringRequestParcel mBuilderParcel;
+
+ /** Default constructor of Builder. */
+ public Builder(@TetheringType final int type) {
+ mBuilderParcel = new TetheringRequestParcel();
+ mBuilderParcel.tetheringType = type;
+ mBuilderParcel.localIPv4Address = null;
+ mBuilderParcel.staticClientAddress = null;
+ mBuilderParcel.exemptFromEntitlementCheck = false;
+ mBuilderParcel.showProvisioningUi = true;
+ mBuilderParcel.connectivityScope = getDefaultConnectivityScope(type);
+ }
+
+ /**
+ * Configure tethering with static IPv4 assignment.
+ *
+ * A DHCP server will be started, but will only be able to offer the client address.
+ * The two addresses must be in the same prefix.
+ *
+ * @param localIPv4Address The preferred local IPv4 link address to use.
+ * @param clientAddress The static client address.
+ */
+ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
+ @NonNull
+ public Builder setStaticIpv4Addresses(@NonNull final LinkAddress localIPv4Address,
+ @NonNull final LinkAddress clientAddress) {
+ Objects.requireNonNull(localIPv4Address);
+ Objects.requireNonNull(clientAddress);
+ if (!checkStaticAddressConfiguration(localIPv4Address, clientAddress)) {
+ throw new IllegalArgumentException("Invalid server or client addresses");
+ }
+
+ mBuilderParcel.localIPv4Address = localIPv4Address;
+ mBuilderParcel.staticClientAddress = clientAddress;
+ return this;
+ }
+
+ /** Start tethering without entitlement checks. */
+ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
+ @NonNull
+ public Builder setExemptFromEntitlementCheck(boolean exempt) {
+ mBuilderParcel.exemptFromEntitlementCheck = exempt;
+ return this;
+ }
+
+ /**
+ * If an entitlement check is needed, sets whether to show the entitlement UI or to
+ * perform a silent entitlement check. By default, the entitlement UI is shown.
+ */
+ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
+ @NonNull
+ public Builder setShouldShowEntitlementUi(boolean showUi) {
+ mBuilderParcel.showProvisioningUi = showUi;
+ return this;
+ }
+
+ /**
+ * Sets the connectivity scope to be provided by this tethering downstream.
+ */
+ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
+ @NonNull
+ public Builder setConnectivityScope(@ConnectivityScope int scope) {
+ if (!checkConnectivityScope(mBuilderParcel.tetheringType, scope)) {
+ throw new IllegalArgumentException("Invalid connectivity scope " + scope);
+ }
+
+ mBuilderParcel.connectivityScope = scope;
+ return this;
+ }
+
+ /** Build {@link TetheringRequest} with the currently set configuration. */
+ @NonNull
+ public TetheringRequest build() {
+ return new TetheringRequest(mBuilderParcel);
+ }
+ }
+
+ /**
+ * Get the local IPv4 address, if one was configured with
+ * {@link Builder#setStaticIpv4Addresses}.
+ */
+ @Nullable
+ public LinkAddress getLocalIpv4Address() {
+ return mRequestParcel.localIPv4Address;
+ }
+
+ /**
+ * Get the static IPv4 address of the client, if one was configured with
+ * {@link Builder#setStaticIpv4Addresses}.
+ */
+ @Nullable
+ public LinkAddress getClientStaticIpv4Address() {
+ return mRequestParcel.staticClientAddress;
+ }
+
+ /** Get tethering type. */
+ @TetheringType
+ public int getTetheringType() {
+ return mRequestParcel.tetheringType;
+ }
+
+ /** Get connectivity type */
+ @ConnectivityScope
+ public int getConnectivityScope() {
+ return mRequestParcel.connectivityScope;
+ }
+
+ /** Check if exempt from entitlement check. */
+ public boolean isExemptFromEntitlementCheck() {
+ return mRequestParcel.exemptFromEntitlementCheck;
+ }
+
+ /** Check if show entitlement ui. */
+ public boolean getShouldShowEntitlementUi() {
+ return mRequestParcel.showProvisioningUi;
+ }
+
+ /**
+ * Check whether the two addresses are ipv4 and in the same prefix.
+ * @hide
+ */
+ public static boolean checkStaticAddressConfiguration(
+ @NonNull final LinkAddress localIPv4Address,
+ @NonNull final LinkAddress clientAddress) {
+ return localIPv4Address.getPrefixLength() == clientAddress.getPrefixLength()
+ && localIPv4Address.isIpv4() && clientAddress.isIpv4()
+ && new IpPrefix(localIPv4Address.toString()).equals(
+ new IpPrefix(clientAddress.toString()));
+ }
+
+ /**
+ * Returns the default connectivity scope for the given tethering type. Usually this is
+ * CONNECTIVITY_SCOPE_GLOBAL, except for NCM which for historical reasons defaults to local.
+ * @hide
+ */
+ public static @ConnectivityScope int getDefaultConnectivityScope(int tetheringType) {
+ return tetheringType != TETHERING_NCM
+ ? CONNECTIVITY_SCOPE_GLOBAL
+ : CONNECTIVITY_SCOPE_LOCAL;
+ }
+
+ /**
+ * Checks whether the requested connectivity scope is allowed.
+ * @hide
+ */
+ private static boolean checkConnectivityScope(int type, int scope) {
+ if (scope == CONNECTIVITY_SCOPE_GLOBAL) return true;
+ return type == TETHERING_USB || type == TETHERING_ETHERNET || type == TETHERING_NCM;
+ }
+
+ /**
+ * Get a TetheringRequestParcel from the configuration
+ * @hide
+ */
+ public TetheringRequestParcel getParcel() {
+ return mRequestParcel;
+ }
+
+ /** String of TetheringRequest detail. */
+ public String toString() {
+ return "TetheringRequest [ type= " + mRequestParcel.tetheringType
+ + ", localIPv4Address= " + mRequestParcel.localIPv4Address
+ + ", staticClientAddress= " + mRequestParcel.staticClientAddress
+ + ", exemptFromEntitlementCheck= "
+ + mRequestParcel.exemptFromEntitlementCheck + ", showProvisioningUi= "
+ + mRequestParcel.showProvisioningUi + " ]";
+ }
+ }
+
+ /**
+ * Callback for use with {@link #startTethering} to find out whether tethering succeeded.
+ */
+ public interface StartTetheringCallback {
+ /**
+ * Called when tethering has been successfully started.
+ */
+ default void onTetheringStarted() {}
+
+ /**
+ * Called when starting tethering failed.
+ *
+ * @param error The error that caused the failure.
+ */
+ default void onTetheringFailed(@StartTetheringError final int error) {}
+ }
+
+ /**
+ * Starts tethering and runs tether provisioning for the given type if needed. If provisioning
+ * fails, stopTethering will be called automatically.
+ *
+ * Without {@link android.Manifest.permission.TETHER_PRIVILEGED} permission, the call will
+ * fail if a tethering entitlement check is required.
+ *
+ * @param request a {@link TetheringRequest} which can specify the preferred configuration.
+ * @param executor {@link Executor} to specify the thread upon which the callback of
+ * TetheringRequest will be invoked.
+ * @param callback A callback that will be called to indicate the success status of the
+ * tethering start request.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.TETHER_PRIVILEGED,
+ android.Manifest.permission.WRITE_SETTINGS
+ })
+ public void startTethering(@NonNull final TetheringRequest request,
+ @NonNull final Executor executor, @NonNull final StartTetheringCallback callback) {
+ final String callerPkg = mContext.getOpPackageName();
+ Log.i(TAG, "startTethering caller:" + callerPkg);
+
+ final IIntResultListener listener = new IIntResultListener.Stub() {
+ @Override
+ public void onResult(final int resultCode) {
+ executor.execute(() -> {
+ if (resultCode == TETHER_ERROR_NO_ERROR) {
+ callback.onTetheringStarted();
+ } else {
+ callback.onTetheringFailed(resultCode);
+ }
+ });
+ }
+ };
+ getConnector(c -> c.startTethering(request.getParcel(), callerPkg,
+ getAttributionTag(), listener));
+ }
+
+ /**
+ * Starts tethering and runs tether provisioning for the given type if needed. If provisioning
+ * fails, stopTethering will be called automatically.
+ *
+ *
Without {@link android.Manifest.permission.TETHER_PRIVILEGED} permission, the call will
+ * fail if a tethering entitlement check is required.
+ *
+ * @param type The tethering type, on of the {@code TetheringManager#TETHERING_*} constants.
+ * @param executor {@link Executor} to specify the thread upon which the callback of
+ * TetheringRequest will be invoked.
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.TETHER_PRIVILEGED,
+ android.Manifest.permission.WRITE_SETTINGS
+ })
+ @SystemApi(client = MODULE_LIBRARIES)
+ public void startTethering(int type, @NonNull final Executor executor,
+ @NonNull final StartTetheringCallback callback) {
+ startTethering(new TetheringRequest.Builder(type).build(), executor, callback);
+ }
+
+ /**
+ * Stops tethering for the given type. Also cancels any provisioning rechecks for that type if
+ * applicable.
+ *
+ *
Without {@link android.Manifest.permission.TETHER_PRIVILEGED} permission, the call will
+ * fail if a tethering entitlement check is required.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.TETHER_PRIVILEGED,
+ android.Manifest.permission.WRITE_SETTINGS
+ })
+ public void stopTethering(@TetheringType final int type) {
+ final String callerPkg = mContext.getOpPackageName();
+ Log.i(TAG, "stopTethering caller:" + callerPkg);
+
+ getConnector(c -> c.stopTethering(type, callerPkg, getAttributionTag(),
+ new IIntResultListener.Stub() {
+ @Override
+ public void onResult(int resultCode) {
+ // TODO: provide an API to obtain result
+ // This has never been possible as stopTethering has always been void and never
+ // taken a callback object. The only indication that callers have is if the call
+ // results in a TETHER_STATE_CHANGE broadcast.
+ }
+ }));
+ }
+
+ /**
+ * Callback for use with {@link #getLatestTetheringEntitlementResult} to find out whether
+ * entitlement succeeded.
+ */
+ public interface OnTetheringEntitlementResultListener {
+ /**
+ * Called to notify entitlement result.
+ *
+ * @param resultCode an int value of entitlement result. It may be one of
+ * {@link #TETHER_ERROR_NO_ERROR},
+ * {@link #TETHER_ERROR_PROVISIONING_FAILED}, or
+ * {@link #TETHER_ERROR_ENTITLEMENT_UNKNOWN}.
+ */
+ void onTetheringEntitlementResult(@EntitlementResult int result);
+ }
+
+ /**
+ * Request the latest value of the tethering entitlement check.
+ *
+ *
This method will only return the latest entitlement result if it is available. If no
+ * cached entitlement result is available, and {@code showEntitlementUi} is false,
+ * {@link #TETHER_ERROR_ENTITLEMENT_UNKNOWN} will be returned. If {@code showEntitlementUi} is
+ * true, entitlement will be run.
+ *
+ *
Without {@link android.Manifest.permission.TETHER_PRIVILEGED} permission, the call will
+ * fail if a tethering entitlement check is required.
+ *
+ * @param type the downstream type of tethering. Must be one of {@code #TETHERING_*} constants.
+ * @param showEntitlementUi a boolean indicating whether to check result for the UI-based
+ * entitlement check or the silent entitlement check.
+ * @param executor the executor on which callback will be invoked.
+ * @param listener an {@link OnTetheringEntitlementResultListener} which will be called to
+ * notify the caller of the result of entitlement check. The listener may be called zero
+ * or one time.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.TETHER_PRIVILEGED,
+ android.Manifest.permission.WRITE_SETTINGS
+ })
+ public void requestLatestTetheringEntitlementResult(@TetheringType int type,
+ boolean showEntitlementUi,
+ @NonNull Executor executor,
+ @NonNull final OnTetheringEntitlementResultListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException(
+ "OnTetheringEntitlementResultListener cannot be null.");
+ }
+
+ ResultReceiver wrappedListener = new ResultReceiver(null /* handler */) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ executor.execute(() -> {
+ listener.onTetheringEntitlementResult(resultCode);
+ });
+ }
+ };
+
+ requestLatestTetheringEntitlementResult(type, wrappedListener,
+ showEntitlementUi);
+ }
+
+ /**
+ * Helper function of #requestLatestTetheringEntitlementResult to remain backwards compatible
+ * with ConnectivityManager#getLatestTetheringEntitlementResult
+ *
+ * {@hide}
+ */
+ // TODO: improve the usage of ResultReceiver, b/145096122
+ @SystemApi(client = MODULE_LIBRARIES)
+ public void requestLatestTetheringEntitlementResult(@TetheringType final int type,
+ @NonNull final ResultReceiver receiver, final boolean showEntitlementUi) {
+ final String callerPkg = mContext.getOpPackageName();
+ Log.i(TAG, "getLatestTetheringEntitlementResult caller:" + callerPkg);
+
+ getConnector(c -> c.requestLatestTetheringEntitlementResult(
+ type, receiver, showEntitlementUi, callerPkg, getAttributionTag()));
+ }
+
+ /**
+ * Callback for use with {@link registerTetheringEventCallback} to find out tethering
+ * upstream status.
+ */
+ public interface TetheringEventCallback {
+ /**
+ * Called when tethering supported status changed.
+ *
+ *
This will be called immediately after the callback is registered, and may be called
+ * multiple times later upon changes.
+ *
+ *
Tethering may be disabled via system properties, device configuration, or device
+ * policy restrictions.
+ *
+ * @param supported The new supported status
+ */
+ default void onTetheringSupported(boolean supported) {}
+
+ /**
+ * Called when tethering upstream changed.
+ *
+ *
This will be called immediately after the callback is registered, and may be called
+ * multiple times later upon changes.
+ *
+ * @param network the {@link Network} of tethering upstream. Null means tethering doesn't
+ * have any upstream.
+ */
+ default void onUpstreamChanged(@Nullable Network network) {}
+
+ /**
+ * Called when there was a change in tethering interface regular expressions.
+ *
+ *
This will be called immediately after the callback is registered, and may be called
+ * multiple times later upon changes.
+ * @param reg The new regular expressions.
+ *
+ * @deprecated New clients should use the callbacks with {@link TetheringInterface} which
+ * has the mapping between tethering type and interface. InterfaceRegex is no longer needed
+ * to determine the mapping of tethering type and interface.
+ *
+ * @hide
+ */
+ @Deprecated
+ @SystemApi(client = MODULE_LIBRARIES)
+ default void onTetherableInterfaceRegexpsChanged(@NonNull TetheringInterfaceRegexps reg) {}
+
+ /**
+ * Called when there was a change in the list of tetherable interfaces. Tetherable
+ * interface means this interface is available and can be used for tethering.
+ *
+ *
This will be called immediately after the callback is registered, and may be called
+ * multiple times later upon changes.
+ * @param interfaces The list of tetherable interface names.
+ */
+ default void onTetherableInterfacesChanged(@NonNull List interfaces) {}
+
+ /**
+ * Called when there was a change in the list of tetherable interfaces. Tetherable
+ * interface means this interface is available and can be used for tethering.
+ *
+ * This will be called immediately after the callback is registered, and may be called
+ * multiple times later upon changes.
+ * @param interfaces The set of TetheringInterface of currently tetherable interface.
+ */
+ default void onTetherableInterfacesChanged(@NonNull Set interfaces) {
+ // By default, the new callback calls the old callback, so apps
+ // implementing the old callback just work.
+ onTetherableInterfacesChanged(toIfaces(interfaces));
+ }
+
+ /**
+ * Called when there was a change in the list of tethered interfaces.
+ *
+ * This will be called immediately after the callback is registered, and may be called
+ * multiple times later upon changes.
+ * @param interfaces The lit of 0 or more String of currently tethered interface names.
+ */
+ default void onTetheredInterfacesChanged(@NonNull List interfaces) {}
+
+ /**
+ * Called when there was a change in the list of tethered interfaces.
+ *
+ * This will be called immediately after the callback is registered, and may be called
+ * multiple times later upon changes.
+ * @param interfaces The set of 0 or more TetheringInterface of currently tethered
+ * interface.
+ */
+ default void onTetheredInterfacesChanged(@NonNull Set interfaces) {
+ // By default, the new callback calls the old callback, so apps
+ // implementing the old callback just work.
+ onTetheredInterfacesChanged(toIfaces(interfaces));
+ }
+
+ /**
+ * Called when there was a change in the list of local-only interfaces.
+ *
+ * This will be called immediately after the callback is registered, and may be called
+ * multiple times later upon changes.
+ * @param interfaces The list of 0 or more String of active local-only interface names.
+ */
+ default void onLocalOnlyInterfacesChanged(@NonNull List interfaces) {}
+
+ /**
+ * Called when there was a change in the list of local-only interfaces.
+ *
+ * This will be called immediately after the callback is registered, and may be called
+ * multiple times later upon changes.
+ * @param interfaces The set of 0 or more TetheringInterface of active local-only
+ * interface.
+ */
+ default void onLocalOnlyInterfacesChanged(@NonNull Set interfaces) {
+ // By default, the new callback calls the old callback, so apps
+ // implementing the old callback just work.
+ onLocalOnlyInterfacesChanged(toIfaces(interfaces));
+ }
+
+ /**
+ * Called when an error occurred configuring tethering.
+ *
+ * This will be called immediately after the callback is registered if the latest status
+ * on the interface is an error, and may be called multiple times later upon changes.
+ * @param ifName Name of the interface.
+ * @param error One of {@code TetheringManager#TETHER_ERROR_*}.
+ */
+ default void onError(@NonNull String ifName, @TetheringIfaceError int error) {}
+
+ /**
+ * Called when an error occurred configuring tethering.
+ *
+ *
This will be called immediately after the callback is registered if the latest status
+ * on the interface is an error, and may be called multiple times later upon changes.
+ * @param iface The interface that experienced the error.
+ * @param error One of {@code TetheringManager#TETHER_ERROR_*}.
+ */
+ default void onError(@NonNull TetheringInterface iface, @TetheringIfaceError int error) {
+ // By default, the new callback calls the old callback, so apps
+ // implementing the old callback just work.
+ onError(iface.getInterface(), error);
+ }
+
+ /**
+ * Called when the list of tethered clients changes.
+ *
+ *
This callback provides best-effort information on connected clients based on state
+ * known to the system, however the list cannot be completely accurate (and should not be
+ * used for security purposes). For example, clients behind a bridge and using static IP
+ * assignments are not visible to the tethering device; or even when using DHCP, such
+ * clients may still be reported by this callback after disconnection as the system cannot
+ * determine if they are still connected.
+ * @param clients The new set of tethered clients; the collection is not ordered.
+ */
+ default void onClientsChanged(@NonNull Collection clients) {}
+
+ /**
+ * Called when tethering offload status changes.
+ *
+ * This will be called immediately after the callback is registered.
+ * @param status The offload status.
+ */
+ default void onOffloadStatusChanged(@TetherOffloadStatus int status) {}
+ }
+
+ /**
+ * Covert DownStreamInterface collection to interface String array list. Internal use only.
+ *
+ * @hide
+ */
+ public static ArrayList toIfaces(Collection tetherIfaces) {
+ final ArrayList ifaces = new ArrayList<>();
+ for (TetheringInterface tether : tetherIfaces) {
+ ifaces.add(tether.getInterface());
+ }
+
+ return ifaces;
+ }
+
+ private static String[] toIfaces(TetheringInterface[] tetherIfaces) {
+ final String[] ifaces = new String[tetherIfaces.length];
+ for (int i = 0; i < tetherIfaces.length; i++) {
+ ifaces[i] = tetherIfaces[i].getInterface();
+ }
+
+ return ifaces;
+ }
+
+
+ /**
+ * Regular expressions used to identify tethering interfaces.
+ *
+ * @deprecated Instead of using regex to determine tethering type. New client could use the
+ * callbacks with {@link TetheringInterface} which has the mapping of type and interface.
+ * @hide
+ */
+ @Deprecated
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static class TetheringInterfaceRegexps {
+ private final String[] mTetherableBluetoothRegexs;
+ private final String[] mTetherableUsbRegexs;
+ private final String[] mTetherableWifiRegexs;
+
+ /** @hide */
+ public TetheringInterfaceRegexps(@NonNull String[] tetherableBluetoothRegexs,
+ @NonNull String[] tetherableUsbRegexs, @NonNull String[] tetherableWifiRegexs) {
+ mTetherableBluetoothRegexs = tetherableBluetoothRegexs.clone();
+ mTetherableUsbRegexs = tetherableUsbRegexs.clone();
+ mTetherableWifiRegexs = tetherableWifiRegexs.clone();
+ }
+
+ @NonNull
+ public List getTetherableBluetoothRegexs() {
+ return Collections.unmodifiableList(Arrays.asList(mTetherableBluetoothRegexs));
+ }
+
+ @NonNull
+ public List getTetherableUsbRegexs() {
+ return Collections.unmodifiableList(Arrays.asList(mTetherableUsbRegexs));
+ }
+
+ @NonNull
+ public List getTetherableWifiRegexs() {
+ return Collections.unmodifiableList(Arrays.asList(mTetherableWifiRegexs));
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTetherableBluetoothRegexs, mTetherableUsbRegexs,
+ mTetherableWifiRegexs);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof TetheringInterfaceRegexps)) return false;
+ final TetheringInterfaceRegexps other = (TetheringInterfaceRegexps) obj;
+ return Arrays.equals(mTetherableBluetoothRegexs, other.mTetherableBluetoothRegexs)
+ && Arrays.equals(mTetherableUsbRegexs, other.mTetherableUsbRegexs)
+ && Arrays.equals(mTetherableWifiRegexs, other.mTetherableWifiRegexs);
+ }
+ }
+
+ /**
+ * Start listening to tethering change events. Any new added callback will receive the last
+ * tethering status right away. If callback is registered,
+ * {@link TetheringEventCallback#onUpstreamChanged} will immediately be called. If tethering
+ * has no upstream or disabled, the argument of callback will be null. The same callback object
+ * cannot be registered twice.
+ *
+ * @param executor the executor on which callback will be invoked.
+ * @param callback the callback to be called when tethering has change events.
+ */
+ @RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE)
+ public void registerTetheringEventCallback(@NonNull Executor executor,
+ @NonNull TetheringEventCallback callback) {
+ final String callerPkg = mContext.getOpPackageName();
+ Log.i(TAG, "registerTetheringEventCallback caller:" + callerPkg);
+
+ synchronized (mTetheringEventCallbacks) {
+ if (mTetheringEventCallbacks.containsKey(callback)) {
+ throw new IllegalArgumentException("callback was already registered.");
+ }
+ final ITetheringEventCallback remoteCallback = new ITetheringEventCallback.Stub() {
+ // Only accessed with a lock on this object
+ private final HashMap mErrorStates = new HashMap<>();
+ private TetheringInterface[] mLastTetherableInterfaces = null;
+ private TetheringInterface[] mLastTetheredInterfaces = null;
+ private TetheringInterface[] mLastLocalOnlyInterfaces = null;
+
+ @Override
+ public void onUpstreamChanged(Network network) throws RemoteException {
+ executor.execute(() -> {
+ callback.onUpstreamChanged(network);
+ });
+ }
+
+ private synchronized void sendErrorCallbacks(final TetherStatesParcel newStates) {
+ for (int i = 0; i < newStates.erroredIfaceList.length; i++) {
+ final TetheringInterface tetherIface = newStates.erroredIfaceList[i];
+ final Integer lastError = mErrorStates.get(tetherIface);
+ final int newError = newStates.lastErrorList[i];
+ if (newError != TETHER_ERROR_NO_ERROR
+ && !Objects.equals(lastError, newError)) {
+ callback.onError(tetherIface, newError);
+ }
+ mErrorStates.put(tetherIface, newError);
+ }
+ }
+
+ private synchronized void maybeSendTetherableIfacesChangedCallback(
+ final TetherStatesParcel newStates) {
+ if (Arrays.equals(mLastTetherableInterfaces, newStates.availableList)) return;
+ mLastTetherableInterfaces = newStates.availableList.clone();
+ callback.onTetherableInterfacesChanged(
+ Collections.unmodifiableSet((new ArraySet(mLastTetherableInterfaces))));
+ }
+
+ private synchronized void maybeSendTetheredIfacesChangedCallback(
+ final TetherStatesParcel newStates) {
+ if (Arrays.equals(mLastTetheredInterfaces, newStates.tetheredList)) return;
+ mLastTetheredInterfaces = newStates.tetheredList.clone();
+ callback.onTetheredInterfacesChanged(
+ Collections.unmodifiableSet((new ArraySet(mLastTetheredInterfaces))));
+ }
+
+ private synchronized void maybeSendLocalOnlyIfacesChangedCallback(
+ final TetherStatesParcel newStates) {
+ if (Arrays.equals(mLastLocalOnlyInterfaces, newStates.localOnlyList)) return;
+ mLastLocalOnlyInterfaces = newStates.localOnlyList.clone();
+ callback.onLocalOnlyInterfacesChanged(
+ Collections.unmodifiableSet((new ArraySet(mLastLocalOnlyInterfaces))));
+ }
+
+ // Called immediately after the callbacks are registered.
+ @Override
+ public void onCallbackStarted(TetheringCallbackStartedParcel parcel) {
+ executor.execute(() -> {
+ callback.onTetheringSupported(parcel.tetheringSupported);
+ callback.onUpstreamChanged(parcel.upstreamNetwork);
+ sendErrorCallbacks(parcel.states);
+ sendRegexpsChanged(parcel.config);
+ maybeSendTetherableIfacesChangedCallback(parcel.states);
+ maybeSendTetheredIfacesChangedCallback(parcel.states);
+ maybeSendLocalOnlyIfacesChangedCallback(parcel.states);
+ callback.onClientsChanged(parcel.tetheredClients);
+ callback.onOffloadStatusChanged(parcel.offloadStatus);
+ });
+ }
+
+ @Override
+ public void onCallbackStopped(int errorCode) {
+ executor.execute(() -> {
+ throwIfPermissionFailure(errorCode);
+ });
+ }
+
+ private void sendRegexpsChanged(TetheringConfigurationParcel parcel) {
+ callback.onTetherableInterfaceRegexpsChanged(new TetheringInterfaceRegexps(
+ parcel.tetherableBluetoothRegexs,
+ parcel.tetherableUsbRegexs,
+ parcel.tetherableWifiRegexs));
+ }
+
+ @Override
+ public void onConfigurationChanged(TetheringConfigurationParcel config) {
+ executor.execute(() -> sendRegexpsChanged(config));
+ }
+
+ @Override
+ public void onTetherStatesChanged(TetherStatesParcel states) {
+ executor.execute(() -> {
+ sendErrorCallbacks(states);
+ maybeSendTetherableIfacesChangedCallback(states);
+ maybeSendTetheredIfacesChangedCallback(states);
+ maybeSendLocalOnlyIfacesChangedCallback(states);
+ });
+ }
+
+ @Override
+ public void onTetherClientsChanged(final List clients) {
+ executor.execute(() -> callback.onClientsChanged(clients));
+ }
+
+ @Override
+ public void onOffloadStatusChanged(final int status) {
+ executor.execute(() -> callback.onOffloadStatusChanged(status));
+ }
+ };
+ getConnector(c -> c.registerTetheringEventCallback(remoteCallback, callerPkg));
+ mTetheringEventCallbacks.put(callback, remoteCallback);
+ }
+ }
+
+ /**
+ * Remove tethering event callback previously registered with
+ * {@link #registerTetheringEventCallback}.
+ *
+ * @param callback previously registered callback.
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.TETHER_PRIVILEGED,
+ Manifest.permission.ACCESS_NETWORK_STATE
+ })
+ public void unregisterTetheringEventCallback(@NonNull final TetheringEventCallback callback) {
+ final String callerPkg = mContext.getOpPackageName();
+ Log.i(TAG, "unregisterTetheringEventCallback caller:" + callerPkg);
+
+ synchronized (mTetheringEventCallbacks) {
+ ITetheringEventCallback remoteCallback = mTetheringEventCallbacks.remove(callback);
+ if (remoteCallback == null) {
+ throw new IllegalArgumentException("callback was not registered.");
+ }
+
+ getConnector(c -> c.unregisterTetheringEventCallback(remoteCallback, callerPkg));
+ }
+ }
+
+ /**
+ * Get a more detailed error code after a Tethering or Untethering
+ * request asynchronously failed.
+ *
+ * @param iface The name of the interface of interest
+ * @return error The error code of the last error tethering or untethering the named
+ * interface
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public int getLastTetherError(@NonNull final String iface) {
+ mCallback.waitForStarted();
+ if (mTetherStatesParcel == null) return TETHER_ERROR_NO_ERROR;
+
+ int i = 0;
+ for (TetheringInterface errored : mTetherStatesParcel.erroredIfaceList) {
+ if (iface.equals(errored.getInterface())) return mTetherStatesParcel.lastErrorList[i];
+
+ i++;
+ }
+ return TETHER_ERROR_NO_ERROR;
+ }
+
+ /**
+ * Get the list of regular expressions that define any tetherable
+ * USB network interfaces. If USB tethering is not supported by the
+ * device, this list should be empty.
+ *
+ * @return an array of 0 or more regular expression Strings defining
+ * what interfaces are considered tetherable usb interfaces.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public @NonNull String[] getTetherableUsbRegexs() {
+ mCallback.waitForStarted();
+ return mTetheringConfiguration.tetherableUsbRegexs;
+ }
+
+ /**
+ * Get the list of regular expressions that define any tetherable
+ * Wifi network interfaces. If Wifi tethering is not supported by the
+ * device, this list should be empty.
+ *
+ * @return an array of 0 or more regular expression Strings defining
+ * what interfaces are considered tetherable wifi interfaces.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public @NonNull String[] getTetherableWifiRegexs() {
+ mCallback.waitForStarted();
+ return mTetheringConfiguration.tetherableWifiRegexs;
+ }
+
+ /**
+ * Get the list of regular expressions that define any tetherable
+ * Bluetooth network interfaces. If Bluetooth tethering is not supported by the
+ * device, this list should be empty.
+ *
+ * @return an array of 0 or more regular expression Strings defining
+ * what interfaces are considered tetherable bluetooth interfaces.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public @NonNull String[] getTetherableBluetoothRegexs() {
+ mCallback.waitForStarted();
+ return mTetheringConfiguration.tetherableBluetoothRegexs;
+ }
+
+ /**
+ * Get the set of tetherable, available interfaces. This list is limited by
+ * device configuration and current interface existence.
+ *
+ * @return an array of 0 or more Strings of tetherable interface names.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public @NonNull String[] getTetherableIfaces() {
+ mCallback.waitForStarted();
+ if (mTetherStatesParcel == null) return new String[0];
+
+ return toIfaces(mTetherStatesParcel.availableList);
+ }
+
+ /**
+ * Get the set of tethered interfaces.
+ *
+ * @return an array of 0 or more String of currently tethered interface names.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public @NonNull String[] getTetheredIfaces() {
+ mCallback.waitForStarted();
+ if (mTetherStatesParcel == null) return new String[0];
+
+ return toIfaces(mTetherStatesParcel.tetheredList);
+ }
+
+ /**
+ * Get the set of interface names which attempted to tether but
+ * failed. Re-attempting to tether may cause them to reset to the Tethered
+ * state. Alternatively, causing the interface to be destroyed and recreated
+ * may cause them to reset to the available state.
+ * {@link TetheringManager#getLastTetherError} can be used to get more
+ * information on the cause of the errors.
+ *
+ * @return an array of 0 or more String indicating the interface names
+ * which failed to tether.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public @NonNull String[] getTetheringErroredIfaces() {
+ mCallback.waitForStarted();
+ if (mTetherStatesParcel == null) return new String[0];
+
+ return toIfaces(mTetherStatesParcel.erroredIfaceList);
+ }
+
+ /**
+ * Get the set of tethered dhcp ranges.
+ *
+ * @deprecated This API just return the default value which is not used in DhcpServer.
+ * @hide
+ */
+ @Deprecated
+ public @NonNull String[] getTetheredDhcpRanges() {
+ mCallback.waitForStarted();
+ return mTetheringConfiguration.legacyDhcpRanges;
+ }
+
+ /**
+ * Check if the device allows for tethering. It may be disabled via
+ * {@code ro.tether.denied} system property, Settings.TETHER_SUPPORTED or
+ * due to device configuration.
+ *
+ * @return a boolean - {@code true} indicating Tethering is supported.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public boolean isTetheringSupported() {
+ final String callerPkg = mContext.getOpPackageName();
+
+ return isTetheringSupported(callerPkg);
+ }
+
+ /**
+ * Check if the device allows for tethering. It may be disabled via {@code ro.tether.denied}
+ * system property, Settings.TETHER_SUPPORTED or due to device configuration. This is useful
+ * for system components that query this API on behalf of an app. In particular, Bluetooth
+ * has @UnsupportedAppUsage calls that will let apps turn on bluetooth tethering if they have
+ * the right permissions, but such an app needs to know whether it can (permissions as well
+ * as support from the device) turn on tethering in the first place to show the appropriate UI.
+ *
+ * @param callerPkg The caller package name, if it is not matching the calling uid,
+ * SecurityException would be thrown.
+ * @return a boolean - {@code true} indicating Tethering is supported.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public boolean isTetheringSupported(@NonNull final String callerPkg) {
+
+ final RequestDispatcher dispatcher = new RequestDispatcher();
+ final int ret = dispatcher.waitForResult((connector, listener) -> {
+ try {
+ connector.isTetheringSupported(callerPkg, getAttributionTag(), listener);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ });
+
+ return ret == TETHER_ERROR_NO_ERROR;
+ }
+
+ /**
+ * Stop all active tethering.
+ *
+ * Without {@link android.Manifest.permission.TETHER_PRIVILEGED} permission, the call will
+ * fail if a tethering entitlement check is required.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.TETHER_PRIVILEGED,
+ android.Manifest.permission.WRITE_SETTINGS
+ })
+ public void stopAllTethering() {
+ final String callerPkg = mContext.getOpPackageName();
+ Log.i(TAG, "stopAllTethering caller:" + callerPkg);
+
+ getConnector(c -> c.stopAllTethering(callerPkg, getAttributionTag(),
+ new IIntResultListener.Stub() {
+ @Override
+ public void onResult(int resultCode) {
+ // TODO: add an API parameter to send result to caller.
+ // This has never been possible as stopAllTethering has always been void
+ // and never taken a callback object. The only indication that callers have
+ // is if the call results in a TETHER_STATE_CHANGE broadcast.
+ }
+ }));
+ }
+}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl b/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl
new file mode 100644
index 0000000000..f13c970d28
--- /dev/null
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+import android.net.LinkAddress;
+
+/**
+ * Configuration details for requesting tethering.
+ * @hide
+ */
+parcelable TetheringRequestParcel {
+ int tetheringType;
+ LinkAddress localIPv4Address;
+ LinkAddress staticClientAddress;
+ boolean exemptFromEntitlementCheck;
+ boolean showProvisioningUi;
+ int connectivityScope;
+}
diff --git a/Tethering/jarjar-rules.txt b/Tethering/jarjar-rules.txt
new file mode 100644
index 0000000000..5de4b97b76
--- /dev/null
+++ b/Tethering/jarjar-rules.txt
@@ -0,0 +1,14 @@
+# These must be kept in sync with the framework-connectivity-shared-srcs filegroup.
+# Classes from the framework-connectivity-shared-srcs filegroup.
+# If there are files in that filegroup that are not covered below, the classes in the
+# module will be overwritten by the ones in the framework.
+rule com.android.internal.util.** com.android.networkstack.tethering.util.@1
+rule android.util.LocalLog* com.android.networkstack.tethering.util.LocalLog@1
+
+rule android.net.shared.Inet4AddressUtils* com.android.networkstack.tethering.shared.Inet4AddressUtils@1
+
+# Classes from net-utils-framework-common
+rule com.android.net.module.util.** com.android.networkstack.tethering.util.@1
+
+# Classes from net-utils-device-common
+rule com.android.net.module.util.Struct* com.android.networkstack.tethering.util.Struct@1
diff --git a/Tethering/jni/android_net_util_TetheringUtils.cpp b/Tethering/jni/android_net_util_TetheringUtils.cpp
new file mode 100644
index 0000000000..27c84cf280
--- /dev/null
+++ b/Tethering/jni/android_net_util_TetheringUtils.cpp
@@ -0,0 +1,184 @@
+/*
+ * 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.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace android {
+
+static const uint32_t kIPv6NextHeaderOffset = offsetof(ip6_hdr, ip6_nxt);
+static const uint32_t kIPv6PayloadStart = sizeof(ip6_hdr);
+static const uint32_t kICMPv6TypeOffset = kIPv6PayloadStart + offsetof(icmp6_hdr, icmp6_type);
+
+static void android_net_util_setupIcmpFilter(JNIEnv *env, jobject javaFd, uint32_t type) {
+ sock_filter filter_code[] = {
+ // Check header is ICMPv6.
+ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv6NextHeaderOffset),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3),
+
+ // Check ICMPv6 type.
+ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kICMPv6TypeOffset),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, type, 0, 1),
+
+ // Accept or reject.
+ BPF_STMT(BPF_RET | BPF_K, 0xffff),
+ BPF_STMT(BPF_RET | BPF_K, 0)
+ };
+
+ const sock_fprog filter = {
+ sizeof(filter_code) / sizeof(filter_code[0]),
+ filter_code,
+ };
+
+ int fd = netjniutils::GetNativeFileDescriptor(env, javaFd);
+ if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
+ }
+}
+
+static void android_net_util_setupNaSocket(JNIEnv *env, jobject clazz, jobject javaFd)
+{
+ android_net_util_setupIcmpFilter(env, javaFd, ND_NEIGHBOR_ADVERT);
+}
+
+static void android_net_util_setupNsSocket(JNIEnv *env, jobject clazz, jobject javaFd)
+{
+ android_net_util_setupIcmpFilter(env, javaFd, ND_NEIGHBOR_SOLICIT);
+}
+
+static void android_net_util_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd,
+ jint ifIndex)
+{
+ static const int kLinkLocalHopLimit = 255;
+
+ int fd = netjniutils::GetNativeFileDescriptor(env, javaFd);
+
+ // Set an ICMPv6 filter that only passes Router Solicitations.
+ struct icmp6_filter rs_only;
+ ICMP6_FILTER_SETBLOCKALL(&rs_only);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &rs_only);
+ socklen_t len = sizeof(rs_only);
+ if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &rs_only, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(ICMP6_FILTER): %s", strerror(errno));
+ return;
+ }
+
+ // Most/all of the rest of these options can be set via Java code, but
+ // because we're here on account of setting an icmp6_filter go ahead
+ // and do it all natively for now.
+
+ // Set the multicast hoplimit to 255 (link-local only).
+ int hops = kLinkLocalHopLimit;
+ len = sizeof(hops);
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(IPV6_MULTICAST_HOPS): %s", strerror(errno));
+ return;
+ }
+
+ // Set the unicast hoplimit to 255 (link-local only).
+ hops = kLinkLocalHopLimit;
+ len = sizeof(hops);
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hops, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(IPV6_UNICAST_HOPS): %s", strerror(errno));
+ return;
+ }
+
+ // Explicitly disable multicast loopback.
+ int off = 0;
+ len = sizeof(off);
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &off, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(IPV6_MULTICAST_LOOP): %s", strerror(errno));
+ return;
+ }
+
+ // Specify the IPv6 interface to use for outbound multicast.
+ len = sizeof(ifIndex);
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifIndex, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(IPV6_MULTICAST_IF): %s", strerror(errno));
+ return;
+ }
+
+ // Additional options to be considered:
+ // - IPV6_TCLASS
+ // - IPV6_RECVPKTINFO
+ // - IPV6_RECVHOPLIMIT
+
+ // Bind to [::].
+ const struct sockaddr_in6 sin6 = {
+ .sin6_family = AF_INET6,
+ .sin6_port = 0,
+ .sin6_flowinfo = 0,
+ .sin6_addr = IN6ADDR_ANY_INIT,
+ .sin6_scope_id = 0,
+ };
+ auto sa = reinterpret_cast(&sin6);
+ len = sizeof(sin6);
+ if (bind(fd, sa, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "bind(IN6ADDR_ANY): %s", strerror(errno));
+ return;
+ }
+
+ // Join the all-routers multicast group, ff02::2%index.
+ struct ipv6_mreq all_rtrs = {
+ .ipv6mr_multiaddr = {{{0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2}}},
+ .ipv6mr_interface = ifIndex,
+ };
+ len = sizeof(all_rtrs);
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &all_rtrs, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(IPV6_JOIN_GROUP): %s", strerror(errno));
+ return;
+ }
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ { "setupNaSocket", "(Ljava/io/FileDescriptor;)V",
+ (void*) android_net_util_setupNaSocket },
+ { "setupNsSocket", "(Ljava/io/FileDescriptor;)V",
+ (void*) android_net_util_setupNsSocket },
+ { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V",
+ (void*) android_net_util_setupRaSocket },
+};
+
+int register_android_net_util_TetheringUtils(JNIEnv* env) {
+ return jniRegisterNativeMethods(env,
+ "android/net/util/TetheringUtils",
+ gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/Tethering/jni/com_android_networkstack_tethering_BpfCoordinator.cpp b/Tethering/jni/com_android_networkstack_tethering_BpfCoordinator.cpp
new file mode 100644
index 0000000000..27357f88d6
--- /dev/null
+++ b/Tethering/jni/com_android_networkstack_tethering_BpfCoordinator.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include
+#include
+
+#include "bpf_tethering.h"
+
+namespace android {
+
+static jobjectArray getBpfCounterNames(JNIEnv *env) {
+ size_t size = BPF_TETHER_ERR__MAX;
+ jobjectArray ret = env->NewObjectArray(size, env->FindClass("java/lang/String"), nullptr);
+ for (int i = 0; i < size; i++) {
+ env->SetObjectArrayElement(ret, i, env->NewStringUTF(bpf_tether_errors[i]));
+ }
+ return ret;
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ { "getBpfCounterNames", "()[Ljava/lang/String;", (void*) getBpfCounterNames },
+};
+
+int register_com_android_networkstack_tethering_BpfCoordinator(JNIEnv* env) {
+ return jniRegisterNativeMethods(env,
+ "com/android/networkstack/tethering/BpfCoordinator",
+ gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/Tethering/jni/com_android_networkstack_tethering_BpfMap.cpp b/Tethering/jni/com_android_networkstack_tethering_BpfMap.cpp
new file mode 100644
index 0000000000..eadc210e31
--- /dev/null
+++ b/Tethering/jni/com_android_networkstack_tethering_BpfMap.cpp
@@ -0,0 +1,175 @@
+/*
+ * 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.
+ */
+
+#include
+#include
+#include
+#include
+
+#include "nativehelper/scoped_primitive_array.h"
+#include "nativehelper/scoped_utf_chars.h"
+
+#define BPF_FD_JUST_USE_INT
+#include "BpfSyscallWrappers.h"
+
+namespace android {
+
+static jclass sErrnoExceptionClass;
+static jmethodID sErrnoExceptionCtor2;
+static jmethodID sErrnoExceptionCtor3;
+
+static void throwErrnoException(JNIEnv* env, const char* functionName, int error) {
+ if (sErrnoExceptionClass == nullptr || sErrnoExceptionClass == nullptr) return;
+
+ jthrowable cause = nullptr;
+ if (env->ExceptionCheck()) {
+ cause = env->ExceptionOccurred();
+ env->ExceptionClear();
+ }
+
+ ScopedLocalRef msg(env, env->NewStringUTF(functionName));
+
+ // Not really much we can do here if msg is null, let's try to stumble on...
+ if (msg.get() == nullptr) env->ExceptionClear();
+
+ jobject errnoException;
+ if (cause != nullptr) {
+ errnoException = env->NewObject(sErrnoExceptionClass, sErrnoExceptionCtor3, msg.get(),
+ error, cause);
+ } else {
+ errnoException = env->NewObject(sErrnoExceptionClass, sErrnoExceptionCtor2, msg.get(),
+ error);
+ }
+ env->Throw(static_cast(errnoException));
+}
+
+static jint com_android_networkstack_tethering_BpfMap_closeMap(JNIEnv *env, jobject clazz,
+ jint fd) {
+ int ret = close(fd);
+
+ if (ret) throwErrnoException(env, "closeMap", errno);
+
+ return ret;
+}
+
+static jint com_android_networkstack_tethering_BpfMap_bpfFdGet(JNIEnv *env, jobject clazz,
+ jstring path, jint mode) {
+ ScopedUtfChars pathname(env, path);
+
+ jint fd = bpf::bpfFdGet(pathname.c_str(), static_cast(mode));
+
+ return fd;
+}
+
+static void com_android_networkstack_tethering_BpfMap_writeToMapEntry(JNIEnv *env, jobject clazz,
+ jint fd, jbyteArray key, jbyteArray value, jint flags) {
+ ScopedByteArrayRO keyRO(env, key);
+ ScopedByteArrayRO valueRO(env, value);
+
+ int ret = bpf::writeToMapEntry(static_cast(fd), keyRO.get(), valueRO.get(),
+ static_cast(flags));
+
+ if (ret) throwErrnoException(env, "writeToMapEntry", errno);
+}
+
+static jboolean throwIfNotEnoent(JNIEnv *env, const char* functionName, int ret, int err) {
+ if (ret == 0) return true;
+
+ if (err != ENOENT) throwErrnoException(env, functionName, err);
+ return false;
+}
+
+static jboolean com_android_networkstack_tethering_BpfMap_deleteMapEntry(JNIEnv *env, jobject clazz,
+ jint fd, jbyteArray key) {
+ ScopedByteArrayRO keyRO(env, key);
+
+ // On success, zero is returned. If the element is not found, -1 is returned and errno is set
+ // to ENOENT.
+ int ret = bpf::deleteMapEntry(static_cast(fd), keyRO.get());
+
+ return throwIfNotEnoent(env, "deleteMapEntry", ret, errno);
+}
+
+static jboolean com_android_networkstack_tethering_BpfMap_getNextMapKey(JNIEnv *env, jobject clazz,
+ jint fd, jbyteArray key, jbyteArray nextKey) {
+ // If key is found, the operation returns zero and sets the next key pointer to the key of the
+ // next element. If key is not found, the operation returns zero and sets the next key pointer
+ // to the key of the first element. If key is the last element, -1 is returned and errno is
+ // set to ENOENT. Other possible errno values are ENOMEM, EFAULT, EPERM, and EINVAL.
+ ScopedByteArrayRW nextKeyRW(env, nextKey);
+ int ret;
+ if (key == nullptr) {
+ // Called by getFirstKey. Find the first key in the map.
+ ret = bpf::getNextMapKey(static_cast(fd), nullptr, nextKeyRW.get());
+ } else {
+ ScopedByteArrayRO keyRO(env, key);
+ ret = bpf::getNextMapKey(static_cast(fd), keyRO.get(), nextKeyRW.get());
+ }
+
+ return throwIfNotEnoent(env, "getNextMapKey", ret, errno);
+}
+
+static jboolean com_android_networkstack_tethering_BpfMap_findMapEntry(JNIEnv *env, jobject clazz,
+ jint fd, jbyteArray key, jbyteArray value) {
+ ScopedByteArrayRO keyRO(env, key);
+ ScopedByteArrayRW valueRW(env, value);
+
+ // If an element is found, the operation returns zero and stores the element's value into
+ // "value". If no element is found, the operation returns -1 and sets errno to ENOENT.
+ int ret = bpf::findMapEntry(static_cast(fd), keyRO.get(), valueRW.get());
+
+ return throwIfNotEnoent(env, "findMapEntry", ret, errno);
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ { "closeMap", "(I)I",
+ (void*) com_android_networkstack_tethering_BpfMap_closeMap },
+ { "bpfFdGet", "(Ljava/lang/String;I)I",
+ (void*) com_android_networkstack_tethering_BpfMap_bpfFdGet },
+ { "writeToMapEntry", "(I[B[BI)V",
+ (void*) com_android_networkstack_tethering_BpfMap_writeToMapEntry },
+ { "deleteMapEntry", "(I[B)Z",
+ (void*) com_android_networkstack_tethering_BpfMap_deleteMapEntry },
+ { "getNextMapKey", "(I[B[B)Z",
+ (void*) com_android_networkstack_tethering_BpfMap_getNextMapKey },
+ { "findMapEntry", "(I[B[B)Z",
+ (void*) com_android_networkstack_tethering_BpfMap_findMapEntry },
+
+};
+
+int register_com_android_networkstack_tethering_BpfMap(JNIEnv* env) {
+ sErrnoExceptionClass = static_cast(env->NewGlobalRef(
+ env->FindClass("android/system/ErrnoException")));
+ if (sErrnoExceptionClass == nullptr) return JNI_ERR;
+
+ sErrnoExceptionCtor2 = env->GetMethodID(sErrnoExceptionClass, "",
+ "(Ljava/lang/String;I)V");
+ if (sErrnoExceptionCtor2 == nullptr) return JNI_ERR;
+
+ sErrnoExceptionCtor3 = env->GetMethodID(sErrnoExceptionClass, "",
+ "(Ljava/lang/String;ILjava/lang/Throwable;)V");
+ if (sErrnoExceptionCtor3 == nullptr) return JNI_ERR;
+
+ return jniRegisterNativeMethods(env,
+ "com/android/networkstack/tethering/BpfMap",
+ gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/Tethering/jni/com_android_networkstack_tethering_BpfUtils.cpp b/Tethering/jni/com_android_networkstack_tethering_BpfUtils.cpp
new file mode 100644
index 0000000000..1611f9d514
--- /dev/null
+++ b/Tethering/jni/com_android_networkstack_tethering_BpfUtils.cpp
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+// TODO: use unique_fd.
+#define BPF_FD_JUST_USE_INT
+#include "BpfSyscallWrappers.h"
+#include "bpf_tethering.h"
+#include "nativehelper/scoped_utf_chars.h"
+
+// The maximum length of TCA_BPF_NAME. Sync from net/sched/cls_bpf.c.
+#define CLS_BPF_NAME_LEN 256
+
+// Classifier name. See cls_bpf_ops in net/sched/cls_bpf.c.
+#define CLS_BPF_KIND_NAME "bpf"
+
+namespace android {
+// Sync from system/netd/server/NetlinkCommands.h
+const uint16_t NETLINK_REQUEST_FLAGS = NLM_F_REQUEST | NLM_F_ACK;
+const sockaddr_nl KERNEL_NLADDR = {AF_NETLINK, 0, 0, 0};
+
+// TODO: move to frameworks/libs/net/common/native for sharing with
+// system/netd/server/OffloadUtils.{c, h}.
+static void sendAndProcessNetlinkResponse(JNIEnv* env, const void* req, int len) {
+ int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); // TODO: use unique_fd
+ if (fd == -1) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE): %s",
+ strerror(errno));
+ return;
+ }
+
+ static constexpr int on = 1;
+ if (setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &on, sizeof(on))) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, %d)", on);
+ close(fd);
+ return;
+ }
+
+ // this is needed to get valid strace netlink parsing, it allocates the pid
+ if (bind(fd, (const struct sockaddr*)&KERNEL_NLADDR, sizeof(KERNEL_NLADDR))) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "bind(fd, {AF_NETLINK, 0, 0}): %s",
+ strerror(errno));
+ close(fd);
+ return;
+ }
+
+ // we do not want to receive messages from anyone besides the kernel
+ if (connect(fd, (const struct sockaddr*)&KERNEL_NLADDR, sizeof(KERNEL_NLADDR))) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "connect(fd, {AF_NETLINK, 0, 0}): %s",
+ strerror(errno));
+ close(fd);
+ return;
+ }
+
+ int rv = send(fd, req, len, 0);
+
+ if (rv == -1) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "send(fd, req, len, 0): %s",
+ strerror(errno));
+ close(fd);
+ return;
+ }
+
+ if (rv != len) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "send(fd, req, len, 0): %s",
+ strerror(EMSGSIZE));
+ close(fd);
+ return;
+ }
+
+ struct {
+ nlmsghdr h;
+ nlmsgerr e;
+ char buf[256];
+ } resp = {};
+
+ rv = recv(fd, &resp, sizeof(resp), MSG_TRUNC);
+
+ if (rv == -1) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "recv() failed: %s", strerror(errno));
+ close(fd);
+ return;
+ }
+
+ if (rv < (int)NLMSG_SPACE(sizeof(struct nlmsgerr))) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "recv() returned short packet: %d", rv);
+ close(fd);
+ return;
+ }
+
+ if (resp.h.nlmsg_len != (unsigned)rv) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "recv() returned invalid header length: %d != %d", resp.h.nlmsg_len,
+ rv);
+ close(fd);
+ return;
+ }
+
+ if (resp.h.nlmsg_type != NLMSG_ERROR) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "recv() did not return NLMSG_ERROR message: %d", resp.h.nlmsg_type);
+ close(fd);
+ return;
+ }
+
+ if (resp.e.error) { // returns 0 on success
+ jniThrowExceptionFmt(env, "java/io/IOException", "NLMSG_ERROR message return error: %s",
+ strerror(-resp.e.error));
+ }
+ close(fd);
+ return;
+}
+
+static int hardwareAddressType(const char* interface) {
+ int fd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (fd < 0) return -errno;
+
+ struct ifreq ifr = {};
+ // We use strncpy() instead of strlcpy() since kernel has to be able
+ // to handle non-zero terminated junk passed in by userspace anyway,
+ // and this way too long interface names (more than IFNAMSIZ-1 = 15
+ // characters plus terminating NULL) will not get truncated to 15
+ // characters and zero-terminated and thus potentially erroneously
+ // match a truncated interface if one were to exist.
+ strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));
+
+ int rv;
+ if (ioctl(fd, SIOCGIFHWADDR, &ifr, sizeof(ifr))) {
+ rv = -errno;
+ } else {
+ rv = ifr.ifr_hwaddr.sa_family;
+ }
+
+ close(fd);
+ return rv;
+}
+
+static jboolean com_android_networkstack_tethering_BpfUtils_isEthernet(JNIEnv* env, jobject clazz,
+ jstring iface) {
+ ScopedUtfChars interface(env, iface);
+
+ int rv = hardwareAddressType(interface.c_str());
+ if (rv < 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "Get hardware address type of interface %s failed: %s",
+ interface.c_str(), strerror(-rv));
+ return false;
+ }
+
+ switch (rv) {
+ case ARPHRD_ETHER:
+ return true;
+ case ARPHRD_NONE:
+ case ARPHRD_RAWIP: // in Linux 4.14+ rmnet support was upstreamed and this is 519
+ case 530: // this is ARPHRD_RAWIP on some Android 4.9 kernels with rmnet
+ return false;
+ default:
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "Unknown hardware address type %s on interface %s", rv,
+ interface.c_str());
+ return false;
+ }
+}
+
+// tc filter add dev .. in/egress prio 1 protocol ipv6/ip bpf object-pinned /sys/fs/bpf/...
+// direct-action
+static void com_android_networkstack_tethering_BpfUtils_tcFilterAddDevBpf(
+ JNIEnv* env, jobject clazz, jint ifIndex, jboolean ingress, jshort prio, jshort proto,
+ jstring bpfProgPath) {
+ ScopedUtfChars pathname(env, bpfProgPath);
+
+ const int bpfFd = bpf::retrieveProgram(pathname.c_str());
+ if (bpfFd == -1) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "retrieveProgram failed %s",
+ strerror(errno));
+ return;
+ }
+
+ struct {
+ nlmsghdr n;
+ tcmsg t;
+ struct {
+ nlattr attr;
+ // The maximum classifier name length is defined in
+ // tcf_proto_ops in include/net/sch_generic.h.
+ char str[NLMSG_ALIGN(sizeof(CLS_BPF_KIND_NAME))];
+ } kind;
+ struct {
+ nlattr attr;
+ struct {
+ nlattr attr;
+ __u32 u32;
+ } fd;
+ struct {
+ nlattr attr;
+ char str[NLMSG_ALIGN(CLS_BPF_NAME_LEN)];
+ } name;
+ struct {
+ nlattr attr;
+ __u32 u32;
+ } flags;
+ } options;
+ } req = {
+ .n =
+ {
+ .nlmsg_len = sizeof(req),
+ .nlmsg_type = RTM_NEWTFILTER,
+ .nlmsg_flags = NETLINK_REQUEST_FLAGS | NLM_F_EXCL | NLM_F_CREATE,
+ },
+ .t =
+ {
+ .tcm_family = AF_UNSPEC,
+ .tcm_ifindex = ifIndex,
+ .tcm_handle = TC_H_UNSPEC,
+ .tcm_parent = TC_H_MAKE(TC_H_CLSACT,
+ ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
+ .tcm_info = static_cast<__u32>((static_cast(prio) << 16) |
+ htons(static_cast(proto))),
+ },
+ .kind =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.kind),
+ .nla_type = TCA_KIND,
+ },
+ .str = CLS_BPF_KIND_NAME,
+ },
+ .options =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.options),
+ .nla_type = NLA_F_NESTED | TCA_OPTIONS,
+ },
+ .fd =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.options.fd),
+ .nla_type = TCA_BPF_FD,
+ },
+ .u32 = static_cast<__u32>(bpfFd),
+ },
+ .name =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.options.name),
+ .nla_type = TCA_BPF_NAME,
+ },
+ // Visible via 'tc filter show', but
+ // is overwritten by strncpy below
+ .str = "placeholder",
+ },
+ .flags =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.options.flags),
+ .nla_type = TCA_BPF_FLAGS,
+ },
+ .u32 = TCA_BPF_FLAG_ACT_DIRECT,
+ },
+ },
+ };
+
+ snprintf(req.options.name.str, sizeof(req.options.name.str), "%s:[*fsobj]",
+ basename(pathname.c_str()));
+
+ // The exception may be thrown from sendAndProcessNetlinkResponse. Close the file descriptor of
+ // BPF program before returning the function in any case.
+ sendAndProcessNetlinkResponse(env, &req, sizeof(req));
+ close(bpfFd);
+}
+
+// tc filter del dev .. in/egress prio .. protocol ..
+static void com_android_networkstack_tethering_BpfUtils_tcFilterDelDev(JNIEnv* env, jobject clazz,
+ jint ifIndex,
+ jboolean ingress,
+ jshort prio, jshort proto) {
+ const struct {
+ nlmsghdr n;
+ tcmsg t;
+ } req = {
+ .n =
+ {
+ .nlmsg_len = sizeof(req),
+ .nlmsg_type = RTM_DELTFILTER,
+ .nlmsg_flags = NETLINK_REQUEST_FLAGS,
+ },
+ .t =
+ {
+ .tcm_family = AF_UNSPEC,
+ .tcm_ifindex = ifIndex,
+ .tcm_handle = TC_H_UNSPEC,
+ .tcm_parent = TC_H_MAKE(TC_H_CLSACT,
+ ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
+ .tcm_info = static_cast<__u32>((static_cast(prio) << 16) |
+ htons(static_cast(proto))),
+ },
+ };
+
+ sendAndProcessNetlinkResponse(env, &req, sizeof(req));
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ {"isEthernet", "(Ljava/lang/String;)Z",
+ (void*)com_android_networkstack_tethering_BpfUtils_isEthernet},
+ {"tcFilterAddDevBpf", "(IZSSLjava/lang/String;)V",
+ (void*)com_android_networkstack_tethering_BpfUtils_tcFilterAddDevBpf},
+ {"tcFilterDelDev", "(IZSS)V",
+ (void*)com_android_networkstack_tethering_BpfUtils_tcFilterDelDev},
+};
+
+int register_com_android_networkstack_tethering_BpfUtils(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/networkstack/tethering/BpfUtils", gMethods,
+ NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/Tethering/jni/onload.cpp b/Tethering/jni/onload.cpp
new file mode 100644
index 0000000000..02e602d99e
--- /dev/null
+++ b/Tethering/jni/onload.cpp
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#include
+#include "jni.h"
+
+#define LOG_TAG "TetheringJni"
+#include
+
+namespace android {
+
+int register_android_net_util_TetheringUtils(JNIEnv* env);
+int register_com_android_networkstack_tethering_BpfMap(JNIEnv* env);
+int register_com_android_networkstack_tethering_BpfCoordinator(JNIEnv* env);
+int register_com_android_networkstack_tethering_BpfUtils(JNIEnv* env);
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
+ JNIEnv *env;
+ if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) {
+ __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, "ERROR: GetEnv failed");
+ return JNI_ERR;
+ }
+
+ if (register_android_net_util_TetheringUtils(env) < 0) return JNI_ERR;
+
+ if (register_com_android_networkstack_tethering_BpfMap(env) < 0) return JNI_ERR;
+
+ if (register_com_android_networkstack_tethering_BpfCoordinator(env) < 0) return JNI_ERR;
+
+ if (register_com_android_networkstack_tethering_BpfUtils(env) < 0) return JNI_ERR;
+
+ return JNI_VERSION_1_6;
+}
+
+}; // namespace android
diff --git a/Tethering/proguard.flags b/Tethering/proguard.flags
new file mode 100644
index 0000000000..75ecdce8d2
--- /dev/null
+++ b/Tethering/proguard.flags
@@ -0,0 +1,17 @@
+# Keep class's integer static field for MessageUtils to parsing their name.
+-keep class com.android.networkstack.tethering.Tethering$TetherMainSM {
+ static final int CMD_*;
+ static final int EVENT_*;
+}
+
+-keep class com.android.networkstack.tethering.BpfMap {
+ native ;
+}
+
+-keepclassmembers public class * extends com.android.networkstack.tethering.util.Struct {
+ *;
+}
+
+-keepclassmembers class android.net.ip.IpServer {
+ static final int CMD_*;
+}
diff --git a/Tethering/res/drawable-hdpi/stat_sys_tether_bluetooth.png b/Tethering/res/drawable-hdpi/stat_sys_tether_bluetooth.png
new file mode 100644
index 0000000000..9451174d65
Binary files /dev/null and b/Tethering/res/drawable-hdpi/stat_sys_tether_bluetooth.png differ
diff --git a/Tethering/res/drawable-hdpi/stat_sys_tether_general.png b/Tethering/res/drawable-hdpi/stat_sys_tether_general.png
new file mode 100644
index 0000000000..79d5756ae3
Binary files /dev/null and b/Tethering/res/drawable-hdpi/stat_sys_tether_general.png differ
diff --git a/Tethering/res/drawable-hdpi/stat_sys_tether_usb.png b/Tethering/res/drawable-hdpi/stat_sys_tether_usb.png
new file mode 100644
index 0000000000..cae1bd1b25
Binary files /dev/null and b/Tethering/res/drawable-hdpi/stat_sys_tether_usb.png differ
diff --git a/Tethering/res/drawable-ldpi/stat_sys_tether_bluetooth.png b/Tethering/res/drawable-ldpi/stat_sys_tether_bluetooth.png
new file mode 100644
index 0000000000..ffe8e8c982
Binary files /dev/null and b/Tethering/res/drawable-ldpi/stat_sys_tether_bluetooth.png differ
diff --git a/Tethering/res/drawable-ldpi/stat_sys_tether_general.png b/Tethering/res/drawable-ldpi/stat_sys_tether_general.png
new file mode 100644
index 0000000000..ca20f73520
Binary files /dev/null and b/Tethering/res/drawable-ldpi/stat_sys_tether_general.png differ
diff --git a/Tethering/res/drawable-ldpi/stat_sys_tether_usb.png b/Tethering/res/drawable-ldpi/stat_sys_tether_usb.png
new file mode 100644
index 0000000000..65e907565e
Binary files /dev/null and b/Tethering/res/drawable-ldpi/stat_sys_tether_usb.png differ
diff --git a/Tethering/res/drawable-mdpi/stat_sys_tether_bluetooth.png b/Tethering/res/drawable-mdpi/stat_sys_tether_bluetooth.png
new file mode 100644
index 0000000000..f42dae0fdc
Binary files /dev/null and b/Tethering/res/drawable-mdpi/stat_sys_tether_bluetooth.png differ
diff --git a/Tethering/res/drawable-mdpi/stat_sys_tether_general.png b/Tethering/res/drawable-mdpi/stat_sys_tether_general.png
new file mode 100644
index 0000000000..065516185a
Binary files /dev/null and b/Tethering/res/drawable-mdpi/stat_sys_tether_general.png differ
diff --git a/Tethering/res/drawable-mdpi/stat_sys_tether_usb.png b/Tethering/res/drawable-mdpi/stat_sys_tether_usb.png
new file mode 100644
index 0000000000..2e2b8ca2e9
Binary files /dev/null and b/Tethering/res/drawable-mdpi/stat_sys_tether_usb.png differ
diff --git a/Tethering/res/drawable-xhdpi/stat_sys_tether_bluetooth.png b/Tethering/res/drawable-xhdpi/stat_sys_tether_bluetooth.png
new file mode 100644
index 0000000000..3f57d1c76c
Binary files /dev/null and b/Tethering/res/drawable-xhdpi/stat_sys_tether_bluetooth.png differ
diff --git a/Tethering/res/drawable-xhdpi/stat_sys_tether_general.png b/Tethering/res/drawable-xhdpi/stat_sys_tether_general.png
new file mode 100644
index 0000000000..34b0cb3673
Binary files /dev/null and b/Tethering/res/drawable-xhdpi/stat_sys_tether_general.png differ
diff --git a/Tethering/res/drawable-xhdpi/stat_sys_tether_usb.png b/Tethering/res/drawable-xhdpi/stat_sys_tether_usb.png
new file mode 100644
index 0000000000..36afe485b5
Binary files /dev/null and b/Tethering/res/drawable-xhdpi/stat_sys_tether_usb.png differ
diff --git a/Tethering/res/drawable-xxhdpi/stat_sys_tether_bluetooth.png b/Tethering/res/drawable-xxhdpi/stat_sys_tether_bluetooth.png
new file mode 100644
index 0000000000..25acfbb01b
Binary files /dev/null and b/Tethering/res/drawable-xxhdpi/stat_sys_tether_bluetooth.png differ
diff --git a/Tethering/res/drawable-xxhdpi/stat_sys_tether_general.png b/Tethering/res/drawable-xxhdpi/stat_sys_tether_general.png
new file mode 100644
index 0000000000..5c656012e6
Binary files /dev/null and b/Tethering/res/drawable-xxhdpi/stat_sys_tether_general.png differ
diff --git a/Tethering/res/drawable-xxhdpi/stat_sys_tether_usb.png b/Tethering/res/drawable-xxhdpi/stat_sys_tether_usb.png
new file mode 100644
index 0000000000..28b4b5438e
Binary files /dev/null and b/Tethering/res/drawable-xxhdpi/stat_sys_tether_usb.png differ
diff --git a/Tethering/res/values-af/strings.xml b/Tethering/res/values-af/strings.xml
new file mode 100644
index 0000000000..056168b12e
--- /dev/null
+++ b/Tethering/res/values-af/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Verbinding of warmkol is aktief"
+ "Tik om op te stel."
+ "Verbinding is gedeaktiveer"
+ "Kontak jou administrateur vir besonderhede"
+ "Warmkol- en verbindingstatus"
+
+
+
+
+
+
diff --git a/Tethering/res/values-am/strings.xml b/Tethering/res/values-am/strings.xml
new file mode 100644
index 0000000000..ac468dd144
--- /dev/null
+++ b/Tethering/res/values-am/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "እንደ ሞደም መሰካት ወይም መገናኛ ነጥብ ገባሪ"
+ "ለማዋቀር መታ ያድርጉ።"
+ "እንደ ሞደም መሰካት ተሰናክሏል"
+ "ለዝርዝሮች የእርስዎን አስተዳዳሪ ያነጋግሩ"
+ "መገናኛ ነጥብ እና እንደ ሞደም የመሰካት ሁኔታ"
+
+
+
+
+
+
diff --git a/Tethering/res/values-ar/strings.xml b/Tethering/res/values-ar/strings.xml
new file mode 100644
index 0000000000..7d5bad34da
--- /dev/null
+++ b/Tethering/res/values-ar/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "النطاق نشط أو نقطة الاتصال نشطة"
+ "انقر للإعداد."
+ "التوصيل متوقف."
+ "تواصَل مع المشرف للحصول على التفاصيل."
+ "حالة نقطة الاتصال والتوصيل"
+
+
+
+
+
+
diff --git a/Tethering/res/values-as/strings.xml b/Tethering/res/values-as/strings.xml
new file mode 100644
index 0000000000..091350455b
--- /dev/null
+++ b/Tethering/res/values-as/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "টে\'ডাৰিং অথবা হ\'টস্প\'ট সক্ৰিয় অৱস্থাত আছে"
+ "ছেট আপ কৰিবলৈ টিপক।"
+ "টে\'ডাৰিঙৰ সুবিধাটো অক্ষম কৰি থোৱা হৈছে"
+ "সবিশেষ জানিবলৈ আপোনাৰ প্ৰশাসকৰ সৈতে যোগাযোগ কৰক"
+ "হ’টস্প\'ট আৰু টে\'ডাৰিঙৰ স্থিতি"
+
+
+
+
+
+
diff --git a/Tethering/res/values-az/strings.xml b/Tethering/res/values-az/strings.xml
new file mode 100644
index 0000000000..dce70da178
--- /dev/null
+++ b/Tethering/res/values-az/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Birləşmə və ya hotspot aktivdir"
+ "Ayarlamaq üçün toxunun."
+ "Birləşmə deaktivdir"
+ "Detallar üçün adminlə əlaqə saxlayın"
+ "Hotspot & birləşmə statusu"
+
+
+
+
+
+
diff --git a/Tethering/res/values-b+sr+Latn/strings.xml b/Tethering/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000000..b0774ec9a8
--- /dev/null
+++ b/Tethering/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Privezivanje ili hotspot je aktivan"
+ "Dodirnite da biste podesili."
+ "Privezivanje je onemogućeno"
+ "Potražite detalje od administratora"
+ "Status hotspota i privezivanja"
+
+
+
+
+
+
diff --git a/Tethering/res/values-be/strings.xml b/Tethering/res/values-be/strings.xml
new file mode 100644
index 0000000000..a8acebe2e9
--- /dev/null
+++ b/Tethering/res/values-be/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Мадэм або хот-спот актыўныя"
+ "Дакраніцеся, каб наладзіць."
+ "Рэжым мадэма выключаны"
+ "Звярніцеся да адміністратара па падрабязную інфармацыю"
+ "Стан \"Хот-спот і мадэм\""
+
+
+
+
+
+
diff --git a/Tethering/res/values-bg/strings.xml b/Tethering/res/values-bg/strings.xml
new file mode 100644
index 0000000000..94fb2d8f17
--- /dev/null
+++ b/Tethering/res/values-bg/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Има активна споделена връзка или точка за достъп"
+ "Докоснете, за да настроите."
+ "Функцията за тетъринг е деактивирана"
+ "Свържете се с администратора си за подробности"
+ "Състояние на функцията за точка за достъп и тетъринг"
+
+
+
+
+
+
diff --git a/Tethering/res/values-bn/strings.xml b/Tethering/res/values-bn/strings.xml
new file mode 100644
index 0000000000..aea02b9ddf
--- /dev/null
+++ b/Tethering/res/values-bn/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "টিথারিং বা হটস্পট চালু আছে"
+ "সেট-আপ করতে ট্যাপ করুন।"
+ "টিথারিং বন্ধ করা আছে"
+ "বিশদে জানতে অ্যাডমিনের সাথে যোগাযোগ করুন"
+ "হটস্পট ও টিথারিং স্ট্যাটাস"
+
+
+
+
+
+
diff --git a/Tethering/res/values-bs/strings.xml b/Tethering/res/values-bs/strings.xml
new file mode 100644
index 0000000000..de232724c5
--- /dev/null
+++ b/Tethering/res/values-bs/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Aktivno je povezivanje putem mobitela ili pristupna tačka"
+ "Dodirnite da postavite."
+ "Povezivanje putem mobitela je onemogućeno"
+ "Kontaktirajte svog administratora za detalje"
+ "Status pristupne tačke i povezivanja putem mobitela"
+
+
+
+
+
+
diff --git a/Tethering/res/values-ca/strings.xml b/Tethering/res/values-ca/strings.xml
new file mode 100644
index 0000000000..88b795c1f8
--- /dev/null
+++ b/Tethering/res/values-ca/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Compartició de xarxa o punt d\'accés Wi‑Fi actius"
+ "Toca per configurar."
+ "La compartició de xarxa està desactivada"
+ "Contacta amb el teu administrador per obtenir més informació"
+ "Estat del punt d\'accés Wi‑Fi i de la compartició de xarxa"
+
+
+
+
+
+
diff --git a/Tethering/res/values-cs/strings.xml b/Tethering/res/values-cs/strings.xml
new file mode 100644
index 0000000000..8c1b83bf3e
--- /dev/null
+++ b/Tethering/res/values-cs/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Tethering nebo hotspot je aktivní"
+ "Klepnutím zahájíte nastavení."
+ "Tethering je zakázán"
+ "O podrobnosti požádejte administrátora"
+ "Stav hotspotu a tetheringu"
+
+
+
+
+
+
diff --git a/Tethering/res/values-da/strings.xml b/Tethering/res/values-da/strings.xml
new file mode 100644
index 0000000000..f413e70548
--- /dev/null
+++ b/Tethering/res/values-da/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Netdeling eller hotspot er aktivt"
+ "Tryk for at konfigurere."
+ "Netdeling er deaktiveret"
+ "Kontakt din administrator for at få oplysninger"
+ "Status for hotspot og netdeling"
+
+
+
+
+
+
diff --git a/Tethering/res/values-de/strings.xml b/Tethering/res/values-de/strings.xml
new file mode 100644
index 0000000000..f057d7824e
--- /dev/null
+++ b/Tethering/res/values-de/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Tethering oder Hotspot aktiv"
+ "Zum Einrichten tippen."
+ "Tethering ist deaktiviert"
+ "Bitte wende dich für weitere Informationen an den Administrator"
+ "Hotspot- und Tethering-Status"
+
+
+
+
+
+
diff --git a/Tethering/res/values-el/strings.xml b/Tethering/res/values-el/strings.xml
new file mode 100644
index 0000000000..b3c986bdaf
--- /dev/null
+++ b/Tethering/res/values-el/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Πρόσδεση ή σύνδεση σημείου πρόσβασης ενεργή"
+ "Πατήστε για ρύθμιση."
+ "Η σύνδεση είναι απενεργοποιημένη"
+ "Επικοινωνήστε με τον διαχειριστή σας για λεπτομέρειες"
+ "Κατάσταση σημείου πρόσβασης Wi-Fi και σύνδεσης"
+
+
+
+
+
+
diff --git a/Tethering/res/values-en-rAU/strings.xml b/Tethering/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000000..769e01208a
--- /dev/null
+++ b/Tethering/res/values-en-rAU/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Tethering or hotspot active"
+ "Tap to set up."
+ "Tethering is disabled"
+ "Contact your admin for details"
+ "Hotspot and tethering status"
+
+
+
+
+
+
diff --git a/Tethering/res/values-en-rCA/strings.xml b/Tethering/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000000..769e01208a
--- /dev/null
+++ b/Tethering/res/values-en-rCA/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Tethering or hotspot active"
+ "Tap to set up."
+ "Tethering is disabled"
+ "Contact your admin for details"
+ "Hotspot and tethering status"
+
+
+
+
+
+
diff --git a/Tethering/res/values-en-rGB/strings.xml b/Tethering/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000000..769e01208a
--- /dev/null
+++ b/Tethering/res/values-en-rGB/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Tethering or hotspot active"
+ "Tap to set up."
+ "Tethering is disabled"
+ "Contact your admin for details"
+ "Hotspot and tethering status"
+
+
+
+
+
+
diff --git a/Tethering/res/values-en-rIN/strings.xml b/Tethering/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000000..769e01208a
--- /dev/null
+++ b/Tethering/res/values-en-rIN/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Tethering or hotspot active"
+ "Tap to set up."
+ "Tethering is disabled"
+ "Contact your admin for details"
+ "Hotspot and tethering status"
+
+
+
+
+
+
diff --git a/Tethering/res/values-en-rXC/strings.xml b/Tethering/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000000..f1674bed4e
--- /dev/null
+++ b/Tethering/res/values-en-rXC/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Tethering or hotspot active"
+ "Tap to set up."
+ "Tethering is disabled"
+ "Contact your admin for details"
+ "Hotspot & tethering status"
+
+
+
+
+
+
diff --git a/Tethering/res/values-es-rUS/strings.xml b/Tethering/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000000..63689f4399
--- /dev/null
+++ b/Tethering/res/values-es-rUS/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Conexión a red o hotspot conectados"
+ "Presiona para configurar esta opción."
+ "Se inhabilitó la conexión mediante dispositivo portátil"
+ "Para obtener más información, comunícate con el administrador"
+ "Estado del hotspot y la conexión mediante dispositivo portátil"
+
+
+
+
+
+
diff --git a/Tethering/res/values-es/strings.xml b/Tethering/res/values-es/strings.xml
new file mode 100644
index 0000000000..9a34ed5e38
--- /dev/null
+++ b/Tethering/res/values-es/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Conexión compartida o punto de acceso activos"
+ "Toca para configurar."
+ "La conexión compartida está inhabilitada"
+ "Solicita más información a tu administrador"
+ "Estado del punto de acceso y de la conexión compartida"
+
+
+
+
+
+
diff --git a/Tethering/res/values-et/strings.xml b/Tethering/res/values-et/strings.xml
new file mode 100644
index 0000000000..0970341ab0
--- /dev/null
+++ b/Tethering/res/values-et/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Jagamine või kuumkoht on aktiivne"
+ "Puudutage seadistamiseks."
+ "Jagamine on keelatud"
+ "Lisateabe saamiseks võtke ühendust oma administraatoriga"
+ "Kuumkoha ja jagamise olek"
+
+
+
+
+
+
diff --git a/Tethering/res/values-eu/strings.xml b/Tethering/res/values-eu/strings.xml
new file mode 100644
index 0000000000..632019e2ef
--- /dev/null
+++ b/Tethering/res/values-eu/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Konexioa partekatzea edo wifi-gunea aktibo dago"
+ "Sakatu konfiguratzeko."
+ "Desgaituta dago konexioa partekatzeko aukera"
+ "Xehetasunak lortzeko, jarri administratzailearekin harremanetan"
+ "Wifi-gunearen eta konexioa partekatzeko eginbidearen egoera"
+
+
+
+
+
+
diff --git a/Tethering/res/values-fa/strings.xml b/Tethering/res/values-fa/strings.xml
new file mode 100644
index 0000000000..2e21c85fa1
--- /dev/null
+++ b/Tethering/res/values-fa/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "اشتراکگذاری اینترنت یا نقطه اتصال فعال"
+ "برای راهاندازی ضربه بزنید."
+ "اشتراکگذاری اینترنت غیرفعال است"
+ "برای جزئیات، با سرپرستتان تماس بگیرید"
+ "وضعیت نقطه اتصال و اشتراکگذاری اینترنت"
+
+
+
+
+
+
diff --git a/Tethering/res/values-fi/strings.xml b/Tethering/res/values-fi/strings.xml
new file mode 100644
index 0000000000..413db3f0f8
--- /dev/null
+++ b/Tethering/res/values-fi/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Yhteyden jakaminen tai hotspot käytössä"
+ "Ota käyttöön napauttamalla."
+ "Yhteyden jakaminen on poistettu käytöstä"
+ "Pyydä lisätietoja järjestelmänvalvojalta"
+ "Hotspotin ja yhteyden jakamisen tila"
+
+
+
+
+
+
diff --git a/Tethering/res/values-fr-rCA/strings.xml b/Tethering/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000000..eb2e4ba540
--- /dev/null
+++ b/Tethering/res/values-fr-rCA/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Partage de connexion ou point d\'accès sans fil activé"
+ "Touchez pour configurer."
+ "Le partage de connexion est désactivé"
+ "Communiquez avec votre administrateur pour obtenir plus de détails"
+ "Point d\'accès et partage de connexion"
+
+
+
+
+
+
diff --git a/Tethering/res/values-fr/strings.xml b/Tethering/res/values-fr/strings.xml
new file mode 100644
index 0000000000..22259c52ab
--- /dev/null
+++ b/Tethering/res/values-fr/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Partage de connexion ou point d\'accès activé"
+ "Appuyez pour effectuer la configuration."
+ "Le partage de connexion est désactivé"
+ "Pour en savoir plus, contactez votre administrateur"
+ "État du point d\'accès et du partage de connexion"
+
+
+
+
+
+
diff --git a/Tethering/res/values-gl/strings.xml b/Tethering/res/values-gl/strings.xml
new file mode 100644
index 0000000000..ded82fcd54
--- /dev/null
+++ b/Tethering/res/values-gl/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Conexión compartida ou zona wifi activada"
+ "Toca para configurar."
+ "A conexión compartida está desactivada"
+ "Contacta co administrador para obter información"
+ "Estado da zona wifi e da conexión compartida"
+
+
+
+
+
+
diff --git a/Tethering/res/values-gu/strings.xml b/Tethering/res/values-gu/strings.xml
new file mode 100644
index 0000000000..7cbbc2de3d
--- /dev/null
+++ b/Tethering/res/values-gu/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "ઇન્ટરનેટ શેર કરવાની સુવિધા અથવા હૉટસ્પૉટ સક્રિય છે"
+ "સેટઅપ કરવા માટે ટૅપ કરો."
+ "ઇન્ટરનેટ શેર કરવાની સુવિધા બંધ કરી છે"
+ "વિગતો માટે તમારા વ્યવસ્થાપકનો સંપર્ક કરો"
+ "હૉટસ્પૉટ અને ઇન્ટરનેટ શેર કરવાની સુવિધાનું સ્ટેટસ"
+
+
+
+
+
+
diff --git a/Tethering/res/values-hi/strings.xml b/Tethering/res/values-hi/strings.xml
new file mode 100644
index 0000000000..08af81b826
--- /dev/null
+++ b/Tethering/res/values-hi/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "टेदरिंग या हॉटस्पॉट चालू है"
+ "सेट अप करने के लिए टैप करें."
+ "टेदरिंग बंद है"
+ "जानकारी के लिए अपने एडमिन से संपर्क करें"
+ "हॉटस्पॉट और टेदरिंग की स्थिति"
+
+
+
+
+
+
diff --git a/Tethering/res/values-hr/strings.xml b/Tethering/res/values-hr/strings.xml
new file mode 100644
index 0000000000..827c135f20
--- /dev/null
+++ b/Tethering/res/values-hr/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Modemsko povezivanje ili žarišna točka aktivni"
+ "Dodirnite da biste postavili."
+ "Modemsko je povezivanje onemogućeno"
+ "Obratite se administratoru da biste saznali pojedinosti"
+ "Status žarišne točke i modemskog povezivanja"
+
+
+
+
+
+
diff --git a/Tethering/res/values-hu/strings.xml b/Tethering/res/values-hu/strings.xml
new file mode 100644
index 0000000000..eb68d6babf
--- /dev/null
+++ b/Tethering/res/values-hu/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Megosztás vagy aktív hotspot"
+ "Koppintson a beállításhoz."
+ "Az internetmegosztás le van tiltva"
+ "A részletekért forduljon rendszergazdájához"
+ "Hotspot és internetmegosztás állapota"
+
+
+
+
+
+
diff --git a/Tethering/res/values-hy/strings.xml b/Tethering/res/values-hy/strings.xml
new file mode 100644
index 0000000000..912941e538
--- /dev/null
+++ b/Tethering/res/values-hy/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Մոդեմի ռեժիմը միացված է"
+ "Հպեք՝ կարգավորելու համար։"
+ "Մոդեմի ռեժիմն անջատված է"
+ "Մանրամասների համար դիմեք ձեր ադմինիստրատորին"
+ "Թեժ կետի և մոդեմի ռեժիմի կարգավիճակը"
+
+
+
+
+
+
diff --git a/Tethering/res/values-in/strings.xml b/Tethering/res/values-in/strings.xml
new file mode 100644
index 0000000000..a4e175a439
--- /dev/null
+++ b/Tethering/res/values-in/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Tethering atau hotspot aktif"
+ "Ketuk untuk menyiapkan."
+ "Tethering dinonaktifkan"
+ "Hubungi admin untuk mengetahui detailnya"
+ "Status hotspot & tethering"
+
+
+
+
+
+
diff --git a/Tethering/res/values-is/strings.xml b/Tethering/res/values-is/strings.xml
new file mode 100644
index 0000000000..e9f6670bcd
--- /dev/null
+++ b/Tethering/res/values-is/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Kveikt á tjóðrun eða aðgangsstað"
+ "Ýttu til að setja upp."
+ "Slökkt er á tjóðrun"
+ "Hafðu samband við kerfisstjórann til að fá upplýsingar"
+ "Staða heits reits og tjóðrunar"
+
+
+
+
+
+
diff --git a/Tethering/res/values-it/strings.xml b/Tethering/res/values-it/strings.xml
new file mode 100644
index 0000000000..ffb9196f5e
--- /dev/null
+++ b/Tethering/res/values-it/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Hotspot o tethering attivo"
+ "Tocca per impostare."
+ "Tethering disattivato"
+ "Contatta il tuo amministratore per avere informazioni dettagliate"
+ "Stato hotspot e tethering"
+
+
+
+
+
+
diff --git a/Tethering/res/values-iw/strings.xml b/Tethering/res/values-iw/strings.xml
new file mode 100644
index 0000000000..7adcb47350
--- /dev/null
+++ b/Tethering/res/values-iw/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "נקודה לשיתוף אינטרנט או שיתוף אינטרנט בין מכשירים: בסטטוס פעיל"
+ "יש להקיש כדי להגדיר."
+ "שיתוף האינטרנט בין מכשירים מושבת"
+ "לפרטים, יש לפנות למנהל המערכת"
+ "סטטוס של נקודה לשיתוף אינטרנט ושיתוף אינטרנט בין מכשירים"
+
+
+
+
+
+
diff --git a/Tethering/res/values-ja/strings.xml b/Tethering/res/values-ja/strings.xml
new file mode 100644
index 0000000000..f68a73010b
--- /dev/null
+++ b/Tethering/res/values-ja/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "テザリングまたはアクセス ポイントが有効です"
+ "タップしてセットアップします。"
+ "テザリングは無効に設定されています"
+ "詳しくは、管理者にお問い合わせください"
+ "アクセス ポイントとテザリングのステータス"
+
+
+
+
+
+
diff --git a/Tethering/res/values-ka/strings.xml b/Tethering/res/values-ka/strings.xml
new file mode 100644
index 0000000000..7c22e82bd3
--- /dev/null
+++ b/Tethering/res/values-ka/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "ტეტერინგი ან უსადენო ქსელი აქტიურია"
+ "შეეხეთ დასაყენებლად."
+ "ტეტერინგი გათიშულია"
+ "დამატებითი ინფორმაციისთვის დაუკავშირდით თქვენს ადმინისტრატორს"
+ "უსადენო ქსელის და ტეტერინგის სტატუსი"
+
+
+
+
+
+
diff --git a/Tethering/res/values-kk/strings.xml b/Tethering/res/values-kk/strings.xml
new file mode 100644
index 0000000000..0857d06de2
--- /dev/null
+++ b/Tethering/res/values-kk/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Тетеринг немесе хотспот қосулы"
+ "Реттеу үшін түртіңіз."
+ "Тетеринг өшірілді."
+ "Мәліметтерді әкімшіден алыңыз."
+ "Хотспот және тетеринг күйі"
+
+
+
+
+
+
diff --git a/Tethering/res/values-km/strings.xml b/Tethering/res/values-km/strings.xml
new file mode 100644
index 0000000000..536e3d1703
--- /dev/null
+++ b/Tethering/res/values-km/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "ការភ្ជាប់ ឬហតស្ប៉តកំពុងដំណើរការ"
+ "ចុចដើម្បីរៀបចំ។"
+ "ការភ្ជាប់ត្រូវបានបិទ"
+ "ទាក់ទងអ្នកគ្រប់គ្រងរបស់អ្នក ដើម្បីទទួលបានព័ត៌មានលម្អិត"
+ "ស្ថានភាពនៃការភ្ជាប់ និងហតស្ប៉ត"
+
+
+
+
+
+
diff --git a/Tethering/res/values-kn/strings.xml b/Tethering/res/values-kn/strings.xml
new file mode 100644
index 0000000000..32f54926f4
--- /dev/null
+++ b/Tethering/res/values-kn/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "ಟೆಥರಿಂಗ್ ಅಥವಾ ಹಾಟ್ಸ್ಪಾಟ್ ಸಕ್ರಿಯವಾಗಿದೆ"
+ "ಸೆಟಪ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ."
+ "ಟೆಥರಿಂಗ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"
+ "ವಿವರಗಳಿಗಾಗಿ ನಿಮ್ಮ ನಿರ್ವಾಹಕರನ್ನು ಸಂಪರ್ಕಿಸಿ"
+ "ಹಾಟ್ಸ್ಪಾಟ್ ಮತ್ತು ಟೆಥರಿಂಗ್ ಸ್ಥಿತಿ"
+
+
+
+
+
+
diff --git a/Tethering/res/values-ko/strings.xml b/Tethering/res/values-ko/strings.xml
new file mode 100644
index 0000000000..156b24786d
--- /dev/null
+++ b/Tethering/res/values-ko/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "테더링 또는 핫스팟 사용"
+ "설정하려면 탭하세요."
+ "테더링이 사용 중지됨"
+ "자세한 정보는 관리자에게 문의하세요."
+ "핫스팟 및 테더링 상태"
+
+
+
+
+
+
diff --git a/Tethering/res/values-ky/strings.xml b/Tethering/res/values-ky/strings.xml
new file mode 100644
index 0000000000..18ee5fd357
--- /dev/null
+++ b/Tethering/res/values-ky/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Модем режими күйүп турат"
+ "Жөндөө үчүн таптап коюңуз."
+ "Телефонду модем катары колдонууга болбойт"
+ "Кеңири маалымат үчүн администраторуңузга кайрылыңыз"
+ "Байланыш түйүнүнүн жана модем режиминин статусу"
+
+
+
+
+
+
diff --git a/Tethering/res/values-lo/strings.xml b/Tethering/res/values-lo/strings.xml
new file mode 100644
index 0000000000..b12767018c
--- /dev/null
+++ b/Tethering/res/values-lo/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "ເປີດການປ່ອຍສັນຍານ ຫຼື ຮັອດສະປອດແລ້ວ"
+ "ແຕະເພື່ອຕັ້ງຄ່າ."
+ "ການປ່ອຍສັນຍານຖືກປິດໄວ້"
+ "ຕິດຕໍ່ຜູ້ເບິ່ງແຍງລະບົບສຳລັບລາຍລະອຽດ"
+ "ສະຖານະຮັອດສະປອດ ແລະ ການປ່ອຍສັນຍານ"
+
+
+
+
+
+
diff --git a/Tethering/res/values-lt/strings.xml b/Tethering/res/values-lt/strings.xml
new file mode 100644
index 0000000000..8427baf39f
--- /dev/null
+++ b/Tethering/res/values-lt/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Įrenginys naudojamas kaip modemas arba įjungtas viešosios interneto prieigos taškas"
+ "Palieskite, kad nustatytumėte."
+ "Įrenginio kaip modemo naudojimas išjungtas"
+ "Jei reikia išsamios informacijos, susisiekite su administratoriumi"
+ "Viešosios interneto prieigos taško ir įrenginio kaip modemo naudojimo būsena"
+
+
+
+
+
+
diff --git a/Tethering/res/values-lv/strings.xml b/Tethering/res/values-lv/strings.xml
new file mode 100644
index 0000000000..aa2d6990e0
--- /dev/null
+++ b/Tethering/res/values-lv/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Piesaiste vai tīklājs ir aktīvs."
+ "Pieskarieties, lai to iestatītu."
+ "Piesaiste ir atspējota"
+ "Lai iegūtu detalizētu informāciju, sazinieties ar savu administratoru."
+ "Tīklāja un piesaistes statuss"
+
+
+
+
+
+
diff --git a/Tethering/res/values-mcc204-mnc04-af/strings.xml b/Tethering/res/values-mcc204-mnc04-af/strings.xml
new file mode 100644
index 0000000000..052ca091ac
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-af/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Warmkol het nie internet nie"
+ "Toestelle kan nie aan internet koppel nie"
+ "Skakel warmkol af"
+ "Warmkol is aan"
+ "Bykomende heffings kan geld terwyl jy swerf"
+ "Gaan voort"
+
diff --git a/Tethering/res/values-mcc204-mnc04-am/strings.xml b/Tethering/res/values-mcc204-mnc04-am/strings.xml
new file mode 100644
index 0000000000..0518c5a14f
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-am/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "መገናኛ ነጥቡ በይነመረብ የለውም"
+ "መሣሪያዎች ከበይነመረብ ጋር መገናኘት አይችሉም"
+ "መገናኛ ነጥብ ያጥፉ"
+ "የመገናኛ ነጥብ በርቷል"
+ "በሚያንዣብብበት ጊዜ ተጨማሪ ክፍያዎች ተፈጻሚ ሊሆኑ ይችላሉ"
+ "ቀጥል"
+
diff --git a/Tethering/res/values-mcc204-mnc04-ar/strings.xml b/Tethering/res/values-mcc204-mnc04-ar/strings.xml
new file mode 100644
index 0000000000..e6d8423f46
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ar/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "نقطة الاتصال غير متصلة بالإنترنت."
+ "لا يمكن للأجهزة الاتصال بالإنترنت."
+ "إيقاف نقطة الاتصال"
+ "نقطة الاتصال مفعّلة"
+ "قد يتم تطبيق رسوم إضافية أثناء التجوال."
+ "متابعة"
+
diff --git a/Tethering/res/values-mcc204-mnc04-as/strings.xml b/Tethering/res/values-mcc204-mnc04-as/strings.xml
new file mode 100644
index 0000000000..4c57f21eae
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-as/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "হটস্পটৰ কোনো ইণ্টাৰনেট নাই"
+ "ডিভাইচসমূহ ইণ্টাৰনেটৰ সৈতে সংযোগ কৰিব নোৱাৰি"
+ "হটস্পট অফ কৰক"
+ "হটস্পট অন হৈ আছে"
+ "ৰ\'মিঙত থাকিলে অতিৰিক্ত মাচুল প্ৰযোজ্য হ’ব পাৰে"
+ "অব্যাহত ৰাখক"
+
diff --git a/Tethering/res/values-mcc204-mnc04-az/strings.xml b/Tethering/res/values-mcc204-mnc04-az/strings.xml
new file mode 100644
index 0000000000..2610ab1bec
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-az/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Hotspotun internetə girişi yoxdur"
+ "Cihazlar internetə qoşula bilmir"
+ "Hotspot\'u deaktiv edin"
+ "Hotspot aktivdir"
+ "Rouminq zamanı əlavə ödənişlər tətbiq edilə bilər"
+ "Davam edin"
+
diff --git a/Tethering/res/values-mcc204-mnc04-b+sr+Latn/strings.xml b/Tethering/res/values-mcc204-mnc04-b+sr+Latn/strings.xml
new file mode 100644
index 0000000000..7b032badf0
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-b+sr+Latn/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Hotspot nema pristup internetu"
+ "Uređaji ne mogu da se povežu na internet"
+ "Isključi hotspot"
+ "Hotspot je uključen"
+ "Možda važe dodatni troškovi u romingu"
+ "Nastavi"
+
diff --git a/Tethering/res/values-mcc204-mnc04-be/strings.xml b/Tethering/res/values-mcc204-mnc04-be/strings.xml
new file mode 100644
index 0000000000..2362a1e6a5
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-be/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Хот-спот не падключаны да інтэрнэту"
+ "Прылады не могуць падключацца да інтэрнэту"
+ "Выключыць хот-спот"
+ "Хот-спот уключаны"
+ "Пры выкарыстанні роўмінгу можа спаганяцца дадатковая плата"
+ "Працягнуць"
+
diff --git a/Tethering/res/values-mcc204-mnc04-bg/strings.xml b/Tethering/res/values-mcc204-mnc04-bg/strings.xml
new file mode 100644
index 0000000000..6ef1b0bbaf
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-bg/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Точката за достъп няма връзка с интернет"
+ "Устройствата не могат да се свържат с интернет"
+ "Изключване на точката за достъп"
+ "Точката за достъп е включена"
+ "Възможно е да ви бъдат начислени допълнителни такси при роуминг"
+ "Напред"
+
diff --git a/Tethering/res/values-mcc204-mnc04-bn/strings.xml b/Tethering/res/values-mcc204-mnc04-bn/strings.xml
new file mode 100644
index 0000000000..9a3033c94d
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-bn/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "হটস্পটের সাথে ইন্টারনেট কানেক্ট করা নেই"
+ "ডিভাইস ইন্টারনেটের সাথে কানেক্ট করতে পারছে না"
+ "হটস্পট বন্ধ করুন"
+ "হটস্পট চালু আছে"
+ "রোমিংয়ের সময় অতিরিক্ত চার্জ করা হতে পারে"
+ "চালিয়ে যান"
+
diff --git a/Tethering/res/values-mcc204-mnc04-bs/strings.xml b/Tethering/res/values-mcc204-mnc04-bs/strings.xml
new file mode 100644
index 0000000000..57f6d88a4e
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-bs/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Pristupna tačka nema internet"
+ "Uređaji se ne mogu povezati na internet"
+ "Isključi pristupnu tačku"
+ "Pristupna tačka je uključena"
+ "Mogu nastati dodatni troškovi u romingu"
+ "Nastavi"
+
diff --git a/Tethering/res/values-mcc204-mnc04-ca/strings.xml b/Tethering/res/values-mcc204-mnc04-ca/strings.xml
new file mode 100644
index 0000000000..e3ad666c0b
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ca/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "El punt d\'accés Wi‑Fi no té accés a Internet"
+ "Els dispositius no es poden connectar a Internet"
+ "Desactiva el punt d\'accés Wi‑Fi"
+ "El punt d\'accés Wi‑Fi està activat"
+ "És possible que s\'apliquin costos addicionals en itinerància"
+ "Continua"
+
diff --git a/Tethering/res/values-mcc204-mnc04-cs/strings.xml b/Tethering/res/values-mcc204-mnc04-cs/strings.xml
new file mode 100644
index 0000000000..f0992814c1
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-cs/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Hotspot nemá připojení k internetu"
+ "Zařízení se nemohou připojit k internetu"
+ "Vypnout hotspot"
+ "Hotspot je aktivní"
+ "Při roamingu mohou být účtovány dodatečné poplatky"
+ "Pokračovat"
+
diff --git a/Tethering/res/values-mcc204-mnc04-da/strings.xml b/Tethering/res/values-mcc204-mnc04-da/strings.xml
new file mode 100644
index 0000000000..1fb2374487
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-da/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Hotspottet har intet internet"
+ "Enheder kan ikke oprette forbindelse til internettet"
+ "Deaktiver hotspot"
+ "Hotspottet er aktiveret"
+ "Der opkræves muligvis yderligere gebyrer ved roaming"
+ "Fortsæt"
+
diff --git a/Tethering/res/values-mcc204-mnc04-de/strings.xml b/Tethering/res/values-mcc204-mnc04-de/strings.xml
new file mode 100644
index 0000000000..56d1d1df58
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-de/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Hotspot ist nicht mit dem Internet verbunden"
+ "Geräte können nicht mit dem Internet verbunden werden"
+ "Hotspot deaktivieren"
+ "Hotspot aktiviert"
+ "Für das Roaming können zusätzliche Gebühren anfallen"
+ "Weiter"
+
diff --git a/Tethering/res/values-mcc204-mnc04-el/strings.xml b/Tethering/res/values-mcc204-mnc04-el/strings.xml
new file mode 100644
index 0000000000..674f1f6798
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-el/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Το σημείο πρόσβασης Wi-Fi δεν έχει πρόσβαση στο διαδίκτυο."
+ "Δεν είναι η δυνατή η σύνδεση των συσκευών στο διαδίκτυο."
+ "Απενεργοποίηση σημείου πρόσβασης Wi-Fi"
+ "Σημείο πρόσβασης Wi-Fi ενεργό"
+ "Ενδέχεται να ισχύουν επιπλέον χρεώσεις κατά την περιαγωγή."
+ "Συνέχεια"
+
diff --git a/Tethering/res/values-mcc204-mnc04-en-rAU/strings.xml b/Tethering/res/values-mcc204-mnc04-en-rAU/strings.xml
new file mode 100644
index 0000000000..3046a3725d
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-en-rAU/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Hotspot has no Internet"
+ "Devices can’t connect to Internet"
+ "Turn off hotspot"
+ "Hotspot is on"
+ "Additional charges may apply while roaming"
+ "Continue"
+
diff --git a/Tethering/res/values-mcc204-mnc04-en-rCA/strings.xml b/Tethering/res/values-mcc204-mnc04-en-rCA/strings.xml
new file mode 100644
index 0000000000..3046a3725d
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-en-rCA/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Hotspot has no Internet"
+ "Devices can’t connect to Internet"
+ "Turn off hotspot"
+ "Hotspot is on"
+ "Additional charges may apply while roaming"
+ "Continue"
+
diff --git a/Tethering/res/values-mcc204-mnc04-en-rGB/strings.xml b/Tethering/res/values-mcc204-mnc04-en-rGB/strings.xml
new file mode 100644
index 0000000000..3046a3725d
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-en-rGB/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Hotspot has no Internet"
+ "Devices can’t connect to Internet"
+ "Turn off hotspot"
+ "Hotspot is on"
+ "Additional charges may apply while roaming"
+ "Continue"
+
diff --git a/Tethering/res/values-mcc204-mnc04-en-rIN/strings.xml b/Tethering/res/values-mcc204-mnc04-en-rIN/strings.xml
new file mode 100644
index 0000000000..3046a3725d
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-en-rIN/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Hotspot has no Internet"
+ "Devices can’t connect to Internet"
+ "Turn off hotspot"
+ "Hotspot is on"
+ "Additional charges may apply while roaming"
+ "Continue"
+
diff --git a/Tethering/res/values-mcc204-mnc04-en-rXC/strings.xml b/Tethering/res/values-mcc204-mnc04-en-rXC/strings.xml
new file mode 100644
index 0000000000..20c9b94cd5
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-en-rXC/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Hotspot has no internet"
+ "Devices can’t connect to internet"
+ "Turn off hotspot"
+ "Hotspot is on"
+ "Additional charges may apply while roaming"
+ "Continue"
+
diff --git a/Tethering/res/values-mcc204-mnc04-es-rUS/strings.xml b/Tethering/res/values-mcc204-mnc04-es-rUS/strings.xml
new file mode 100644
index 0000000000..956547cc6d
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-es-rUS/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "El hotspot no tiene conexión a Internet"
+ "Los dispositivos no pueden conectarse a Internet"
+ "Desactiva el hotspot"
+ "El hotspot está activado"
+ "Es posible que se apliquen cargos adicionales por roaming"
+ "Continuar"
+
diff --git a/Tethering/res/values-mcc204-mnc04-es/strings.xml b/Tethering/res/values-mcc204-mnc04-es/strings.xml
new file mode 100644
index 0000000000..831ec1fb1e
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-es/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "El punto de acceso no tiene conexión a Internet"
+ "Los dispositivos no se pueden conectar a Internet"
+ "Desactivar punto de acceso"
+ "Punto de acceso activado"
+ "Puede que se apliquen cargos adicionales en itinerancia"
+ "Continuar"
+
diff --git a/Tethering/res/values-mcc204-mnc04-et/strings.xml b/Tethering/res/values-mcc204-mnc04-et/strings.xml
new file mode 100644
index 0000000000..ff8dde5422
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-et/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Kuumkohal puudub Interneti-ühendus"
+ "Seadmed ei saa Internetiga ühendust luua"
+ "Lülita kuumkoht välja"
+ "Kuumkoht on sees"
+ "Rändluse kasutamisega võivad kaasneda lisatasud"
+ "Jätka"
+
diff --git a/Tethering/res/values-mcc204-mnc04-eu/strings.xml b/Tethering/res/values-mcc204-mnc04-eu/strings.xml
new file mode 100644
index 0000000000..c4f70a3eb4
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-eu/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Sare publikoak ez du Interneteko konexiorik"
+ "Gailuak ezin dira konektatu Internetera"
+ "Desaktibatu sare publikoa"
+ "Sare publikoa aktibatuta dago"
+ "Baliteke kostu gehigarriak ordaindu behar izatea ibiltaritzan"
+ "Egin aurrera"
+
diff --git a/Tethering/res/values-mcc204-mnc04-fa/strings.xml b/Tethering/res/values-mcc204-mnc04-fa/strings.xml
new file mode 100644
index 0000000000..79e3ef11d6
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-fa/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "نقطه اتصال به اینترنت دسترسی ندارد"
+ "دستگاهها به اینترنت متصل نشدند"
+ "نقطه اتصال را خاموش کنید"
+ "نقطه اتصال روشن است"
+ "ممکن است درحین فراگردی تغییرات دیگر اعمال شود"
+ "ادامه"
+
diff --git a/Tethering/res/values-mcc204-mnc04-fi/strings.xml b/Tethering/res/values-mcc204-mnc04-fi/strings.xml
new file mode 100644
index 0000000000..64921bca9f
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-fi/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Hotspotilla ei ole internetyhteyttä"
+ "Laitteet eivät voi yhdistää internetiin"
+ "Laita hotspot pois päältä"
+ "Hotspot on päällä"
+ "Roaming voi aiheuttaa lisämaksuja"
+ "Jatka"
+
diff --git a/Tethering/res/values-mcc204-mnc04-fr-rCA/strings.xml b/Tethering/res/values-mcc204-mnc04-fr-rCA/strings.xml
new file mode 100644
index 0000000000..eda7b59761
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-fr-rCA/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Le point d\'accès n\'est pas connecté à Internet"
+ "Appareils non connectés à Internet"
+ "Désactiver le point d\'accès"
+ "Le point d\'accès est activé"
+ "En itinérance, des frais supplémentaires peuvent s\'appliquer"
+ "Continuer"
+
diff --git a/Tethering/res/values-mcc204-mnc04-fr/strings.xml b/Tethering/res/values-mcc204-mnc04-fr/strings.xml
new file mode 100644
index 0000000000..eda7b59761
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-fr/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Le point d\'accès n\'est pas connecté à Internet"
+ "Appareils non connectés à Internet"
+ "Désactiver le point d\'accès"
+ "Le point d\'accès est activé"
+ "En itinérance, des frais supplémentaires peuvent s\'appliquer"
+ "Continuer"
+
diff --git a/Tethering/res/values-mcc204-mnc04-gl/strings.xml b/Tethering/res/values-mcc204-mnc04-gl/strings.xml
new file mode 100644
index 0000000000..c163c61fbd
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-gl/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "A zona wifi non ten acceso a Internet"
+ "Os dispositivos non se poden conectar a Internet"
+ "Desactivar zona wifi"
+ "A zona wifi está activada"
+ "Pódense aplicar cargos adicionais en itinerancia"
+ "Continuar"
+
diff --git a/Tethering/res/values-mcc204-mnc04-gu/strings.xml b/Tethering/res/values-mcc204-mnc04-gu/strings.xml
new file mode 100644
index 0000000000..796d42ec52
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-gu/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "હૉટસ્પૉટથી ઇન્ટરનેટ ચાલી રહ્યું નથી"
+ "ડિવાઇસ, ઇન્ટરનેટ સાથે કનેક્ટ થઈ શકતા નથી"
+ "હૉટસ્પૉટ બંધ કરો"
+ "હૉટસ્પૉટ ચાલુ છે"
+ "રોમિંગમાં વધારાના શુલ્ક લાગી શકે છે"
+ "આગળ વધો"
+
diff --git a/Tethering/res/values-mcc204-mnc04-hi/strings.xml b/Tethering/res/values-mcc204-mnc04-hi/strings.xml
new file mode 100644
index 0000000000..a2442009b5
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-hi/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "हॉटस्पॉट से इंटरनेट नहीं चल रहा"
+ "डिवाइस इंटरनेट से कनेक्ट नहीं हो पा रहे"
+ "हॉटस्पॉट बंद करें"
+ "हॉटस्पॉट चालू है"
+ "रोमिंग के दौरान अतिरिक्त शुल्क लग सकता है"
+ "जारी रखें"
+
diff --git a/Tethering/res/values-mcc204-mnc04-hr/strings.xml b/Tethering/res/values-mcc204-mnc04-hr/strings.xml
new file mode 100644
index 0000000000..41618afb2e
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-hr/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Žarišna točka nema pristup internetu"
+ "Uređaji se ne mogu povezati s internetom"
+ "Isključi žarišnu točku"
+ "Žarišna je točka uključena"
+ "U roamingu su mogući dodatni troškovi"
+ "Nastavi"
+
diff --git a/Tethering/res/values-mcc204-mnc04-hu/strings.xml b/Tethering/res/values-mcc204-mnc04-hu/strings.xml
new file mode 100644
index 0000000000..39b7a6975b
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-hu/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "A hotspot nem csatlakozik az internethez"
+ "Az eszközök nem tudnak csatlakozni az internethez"
+ "Hotspot kikapcsolása"
+ "A hotspot be van kapcsolva"
+ "Roaming során további díjak léphetnek fel"
+ "Tovább"
+
diff --git a/Tethering/res/values-mcc204-mnc04-hy/strings.xml b/Tethering/res/values-mcc204-mnc04-hy/strings.xml
new file mode 100644
index 0000000000..c14ae10ad1
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-hy/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Թեժ կետը միացված չէ ինտերնետին"
+ "Սարքերը չեն կարողանում միանալ ինտերնետին"
+ "Անջատել թեժ կետը"
+ "Թեժ կետը միացված է"
+ "Ռոումինգում կարող են լրացուցիչ վճարներ գանձվել"
+ "Շարունակել"
+
diff --git a/Tethering/res/values-mcc204-mnc04-in/strings.xml b/Tethering/res/values-mcc204-mnc04-in/strings.xml
new file mode 100644
index 0000000000..1243d22d19
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-in/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Hotspot tidak memiliki koneksi internet"
+ "Perangkat tidak dapat tersambung ke internet"
+ "Nonaktifkan hotspot"
+ "Hotspot aktif"
+ "Biaya tambahan mungkin berlaku saat roaming"
+ "Lanjutkan"
+
diff --git a/Tethering/res/values-mcc204-mnc04-is/strings.xml b/Tethering/res/values-mcc204-mnc04-is/strings.xml
new file mode 100644
index 0000000000..82a7d01234
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-is/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Heitur reitur er ekki nettengdur"
+ "Tæki geta ekki tengst við internetið"
+ "Slökkva á heitum reit"
+ "Kveikt er á heitum reit"
+ "Viðbótargjöld kunna að eiga við í reiki"
+ "Halda áfram"
+
diff --git a/Tethering/res/values-mcc204-mnc04-it/strings.xml b/Tethering/res/values-mcc204-mnc04-it/strings.xml
new file mode 100644
index 0000000000..a0f52dc89b
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-it/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "L\'hotspot non ha accesso a Internet"
+ "I dispositivi non possono connettersi a Internet"
+ "Disattiva l\'hotspot"
+ "Hotspot attivo"
+ "Potrebbero essere applicati costi aggiuntivi durante il roaming"
+ "Continua"
+
diff --git a/Tethering/res/values-mcc204-mnc04-iw/strings.xml b/Tethering/res/values-mcc204-mnc04-iw/strings.xml
new file mode 100644
index 0000000000..80807bc232
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-iw/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "לנקודה לשיתוף אינטרנט אין חיבור לאינטרנט"
+ "המכשירים לא יכולים להתחבר לאינטרנט"
+ "כיבוי הנקודה לשיתוף אינטרנט"
+ "הנקודה לשיתוף אינטרנט פועלת"
+ "ייתכנו חיובים נוספים בעת נדידה"
+ "המשך"
+
diff --git a/Tethering/res/values-mcc204-mnc04-ja/strings.xml b/Tethering/res/values-mcc204-mnc04-ja/strings.xml
new file mode 100644
index 0000000000..0e21a7f322
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ja/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "アクセス ポイントがインターネットに接続されていません"
+ "デバイスをインターネットに接続できません"
+ "アクセス ポイントを OFF にする"
+ "アクセス ポイント: ON"
+ "ローミング時に追加料金が発生することがあります"
+ "続行"
+
diff --git a/Tethering/res/values-mcc204-mnc04-ka/strings.xml b/Tethering/res/values-mcc204-mnc04-ka/strings.xml
new file mode 100644
index 0000000000..6d3b548744
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ka/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "უსადენო ქსელს არ აქვს ინტერნეტზე წვდომა"
+ "მოწყობილობები ვერ უკავშირდება ინტერნეტს"
+ "გამორთეთ უსადენო ქსელი"
+ "უსადენო ქსელი ჩართულია"
+ "როუმინგის გამოყენებისას შეიძლება ჩამოგეჭრათ დამატებითი საფასური"
+ "გაგრძელება"
+
diff --git a/Tethering/res/values-mcc204-mnc04-kk/strings.xml b/Tethering/res/values-mcc204-mnc04-kk/strings.xml
new file mode 100644
index 0000000000..985fc3ff99
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-kk/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Хотспотта интернет жоқ"
+ "Құрылғылар интернетке қосылмайды"
+ "Хотспотты өшіру"
+ "Хотспот қосулы"
+ "Роуминг кезінде қосымша ақы алынуы мүмкін."
+ "Жалғастыру"
+
diff --git a/Tethering/res/values-mcc204-mnc04-km/strings.xml b/Tethering/res/values-mcc204-mnc04-km/strings.xml
new file mode 100644
index 0000000000..03b5cb6e4b
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-km/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "ហតស្ប៉តមិនមានអ៊ីនធឺណិតទេ"
+ "ឧបករណ៍មិនអាចភ្ជាប់អ៊ីនធឺណិតបានទេ"
+ "បិទហតស្ប៉ត"
+ "ហតស្ប៉តត្រូវបានបើក"
+ "អាចមានការគិតថ្លៃបន្ថែម នៅពេលរ៉ូមីង"
+ "បន្ត"
+
diff --git a/Tethering/res/values-mcc204-mnc04-kn/strings.xml b/Tethering/res/values-mcc204-mnc04-kn/strings.xml
new file mode 100644
index 0000000000..f0adad8e21
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-kn/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "ಹಾಟ್ಸ್ಪಾಟ್ ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಸಂಪರ್ಕವನ್ನು ಹೊಂದಿಲ್ಲ"
+ "ಇಂಟರ್ನೆಟ್ಗೆ ಸಂಪರ್ಕಗೊಳ್ಳಲು ಸಾಧನಗಳಿಗೆ ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ"
+ "ಹಾಟ್ಸ್ಪಾಟ್ ಆಫ್ ಮಾಡಿ"
+ "ಹಾಟ್ಸ್ಪಾಟ್ ಆನ್ ಆಗಿದೆ"
+ "ರೋಮಿಂಗ್ನಲ್ಲಿರುವಾಗ ಹೆಚ್ಚುವರಿ ಶುಲ್ಕಗಳು ಅನ್ವಯವಾಗಬಹುದು"
+ "ಮುಂದುವರಿಸಿ"
+
diff --git a/Tethering/res/values-mcc204-mnc04-ko/strings.xml b/Tethering/res/values-mcc204-mnc04-ko/strings.xml
new file mode 100644
index 0000000000..9218e9a09b
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ko/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "핫스팟이 인터넷에 연결되지 않음"
+ "기기를 인터넷에 연결할 수 없음"
+ "핫스팟 사용 중지"
+ "핫스팟 사용 중"
+ "로밍 중에는 추가 요금이 발생할 수 있습니다."
+ "계속"
+
diff --git a/Tethering/res/values-mcc204-mnc04-ky/strings.xml b/Tethering/res/values-mcc204-mnc04-ky/strings.xml
new file mode 100644
index 0000000000..35a060aa24
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ky/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Байланыш түйүнүндө Интернет жок"
+ "Түзмөктөр Интернетке туташпай жатат"
+ "Туташуу түйүнүн өчүрүү"
+ "Кошулуу түйүнү күйүк"
+ "Роумингде кошумча акы алынышы мүмкүн"
+ "Улантуу"
+
diff --git a/Tethering/res/values-mcc204-mnc04-lo/strings.xml b/Tethering/res/values-mcc204-mnc04-lo/strings.xml
new file mode 100644
index 0000000000..1d9203b369
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-lo/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "ຮັອດສະປອດບໍ່ມີອິນເຕີເນັດ"
+ "ອຸປະກອນບໍ່ສາມາດເຊື່ອມຕໍ່ອິນເຕີເນັດໄດ້"
+ "ປິດຮັອດສະປອດ"
+ "ຮັອດສະປອດເປີດຢູ່"
+ "ອາດມີຄ່າໃຊ້ຈ່າຍເພີ່ມເຕີມໃນລະຫວ່າງການໂຣມມິງ"
+ "ສືບຕໍ່"
+
diff --git a/Tethering/res/values-mcc204-mnc04-lt/strings.xml b/Tethering/res/values-mcc204-mnc04-lt/strings.xml
new file mode 100644
index 0000000000..db5178bf2d
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-lt/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Nėra viešosios interneto prieigos taško interneto ryšio"
+ "Įrenginiams nepavyksta prisijungti prie interneto"
+ "Išjungti viešosios interneto prieigos tašką"
+ "Viešosios interneto prieigos taškas įjungtas"
+ "Veikiant tarptinkliniam ryšiui gali būti taikomi papildomi mokesčiai"
+ "Tęsti"
+
diff --git a/Tethering/res/values-mcc204-mnc04-lv/strings.xml b/Tethering/res/values-mcc204-mnc04-lv/strings.xml
new file mode 100644
index 0000000000..c712173ca2
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-lv/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Tīklājam nav interneta savienojuma"
+ "Ierīces nevar izveidot savienojumu ar internetu"
+ "Izslēgt tīklāju"
+ "Tīklājs ir ieslēgts"
+ "Viesabonēšanas laikā var tikt piemērota papildu samaksa"
+ "Tālāk"
+
diff --git a/Tethering/res/values-mcc204-mnc04-mk/strings.xml b/Tethering/res/values-mcc204-mnc04-mk/strings.xml
new file mode 100644
index 0000000000..aa4490912b
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-mk/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Точката на пристап нема интернет"
+ "Уредите не може да се поврзат на интернет"
+ "Исклучи ја точката на пристап"
+ "Точката на пристап е вклучена"
+ "При роаминг може да се наплатат дополнителни трошоци"
+ "Продолжи"
+
diff --git a/Tethering/res/values-mcc204-mnc04-ml/strings.xml b/Tethering/res/values-mcc204-mnc04-ml/strings.xml
new file mode 100644
index 0000000000..d376fe5870
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ml/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "ഹോട്ട്സ്പോട്ടിൽ ഇന്റർനെറ്റ് ലഭ്യമല്ല"
+ "ഉപകരണങ്ങൾ ഇന്റർനെറ്റിലേക്ക് കണക്റ്റ് ചെയ്യാനാവില്ല"
+ "ഹോട്ട്സ്പോട്ട് ഓഫാക്കുക"
+ "ഹോട്ട്സ്പോട്ട് ഓണാണ്"
+ "റോമിംഗ് ചെയ്യുമ്പോൾ അധിക നിരക്കുകൾ ബാധകമായേക്കാം"
+ "തുടരുക"
+
diff --git a/Tethering/res/values-mcc204-mnc04-mn/strings.xml b/Tethering/res/values-mcc204-mnc04-mn/strings.xml
new file mode 100644
index 0000000000..417213f543
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-mn/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Сүлжээний цэг дээр интернэт алга байна"
+ "Төхөөрөмжүүд нь интернэтэд холбогдох боломжгүй байна"
+ "Сүлжээний цэгийг унтраах"
+ "Сүлжээний цэг асаалттай байна"
+ "Роумингийн үеэр нэмэлт төлбөр нэхэмжилж болзошгүй"
+ "Үргэлжлүүлэх"
+
diff --git a/Tethering/res/values-mcc204-mnc04-mr/strings.xml b/Tethering/res/values-mcc204-mnc04-mr/strings.xml
new file mode 100644
index 0000000000..2ed153fb17
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-mr/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "हॉटस्पॉटला इंटरनेट नाही"
+ "डिव्हाइस इंटरनेटला कनेक्ट करू शकत नाहीत"
+ "हॉटस्पॉट बंद करा"
+ "हॉटस्पॉट सुरू आहे"
+ "रोमिंगदरम्यान अतिरिक्त शुल्क लागू होऊ शकतात"
+ "सुरू ठेवा"
+
diff --git a/Tethering/res/values-mcc204-mnc04-ms/strings.xml b/Tethering/res/values-mcc204-mnc04-ms/strings.xml
new file mode 100644
index 0000000000..50817fd4a2
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ms/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Tempat liputan tiada Internet"
+ "Peranti tidak dapat menyambung kepada Internet"
+ "Matikan tempat liputan"
+ "Tempat liputan dihidupkan"
+ "Caj tambahan mungkin digunakan semasa perayauan"
+ "Teruskan"
+
diff --git a/Tethering/res/values-mcc204-mnc04-my/strings.xml b/Tethering/res/values-mcc204-mnc04-my/strings.xml
new file mode 100644
index 0000000000..c0d70e3d5f
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-my/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "ဟော့စပေါ့တွင် အင်တာနက်မရှိပါ"
+ "စက်များက အင်တာနက်ချိတ်ဆက်၍ မရပါ"
+ "ဟော့စပေါ့ ပိတ်ရန်"
+ "ဟော့စပေါ့ ဖွင့်ထားသည်"
+ "ပြင်ပကွန်ရက်နှင့် ချိတ်ဆက်သည့်အခါ နောက်ထပ်ကျသင့်မှုများ ရှိနိုင်သည်"
+ "ရှေ့ဆက်ရန်"
+
diff --git a/Tethering/res/values-mcc204-mnc04-nb/strings.xml b/Tethering/res/values-mcc204-mnc04-nb/strings.xml
new file mode 100644
index 0000000000..1e7f1c6d0a
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-nb/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Wi-Fi-sonen har ikke internettilgang"
+ "Enheter kan ikke koble til internett"
+ "Slå av Wi-Fi-sonen"
+ "Wi-Fi-sonen er på"
+ "Ytterligere kostnader kan påløpe under roaming"
+ "Fortsett"
+
diff --git a/Tethering/res/values-mcc204-mnc04-ne/strings.xml b/Tethering/res/values-mcc204-mnc04-ne/strings.xml
new file mode 100644
index 0000000000..63ce155034
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ne/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "हटस्पटमा इन्टरनेट छैन"
+ "यन्त्रहरू इन्टरनेटमा कनेक्ट गर्न सकिएन"
+ "हटस्पट निष्क्रिय पार्नुहोस्"
+ "हटस्पट सक्रिय छ"
+ "रोमिङ सेवा प्रयोग गर्दा अतिरिक्त शुल्क लाग्न सक्छ"
+ "जारी राख्नुहोस्"
+
diff --git a/Tethering/res/values-mcc204-mnc04-nl/strings.xml b/Tethering/res/values-mcc204-mnc04-nl/strings.xml
new file mode 100644
index 0000000000..bf14a0fced
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-nl/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Hotspot heeft geen internet"
+ "Apparaten kunnen geen verbinding maken met internet"
+ "Hotspot uitschakelen"
+ "Hotspot is ingeschakeld"
+ "Er kunnen extra kosten voor roaming in rekening worden gebracht."
+ "Doorgaan"
+
diff --git a/Tethering/res/values-mcc204-mnc04-or/strings.xml b/Tethering/res/values-mcc204-mnc04-or/strings.xml
new file mode 100644
index 0000000000..ab87b76caf
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-or/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "ହଟସ୍ପଟରେ କୌଣସି ଇଣ୍ଟର୍ନେଟ୍ ସଂଯୋଗ ନାହିଁ"
+ "ଡିଭାଇସଗୁଡ଼ିକ ଇଣ୍ଟର୍ନେଟ୍ ସହ ସଂଯୋଗ କରାଯାଇପାରିବ ନାହିଁ"
+ "ହଟସ୍ପଟ ବନ୍ଦ କରନ୍ତୁ"
+ "ହଟସ୍ପଟ ଚାଲୁ ଅଛି"
+ "ରୋମିଂରେ ଥିବା ସମୟରେ ଅତିରିକ୍ତ ଶୁଳ୍କ ଲାଗୁ ହୋଇପାରେ"
+ "ଜାରି ରଖନ୍ତୁ"
+
diff --git a/Tethering/res/values-mcc204-mnc04-pa/strings.xml b/Tethering/res/values-mcc204-mnc04-pa/strings.xml
new file mode 100644
index 0000000000..b09f285c2d
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-pa/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "ਹੌਟਸਪੌਟ ਕੋਲ ਇੰਟਰਨੈੱਟ ਪਹੁੰਚ ਨਹੀਂ ਹੈ"
+ "ਡੀਵਾਈਸ ਇੰਟਰਨੈੱਟ ਨਾਲ ਕਨੈਕਟ ਨਹੀਂ ਹੋ ਸਕਦੇ"
+ "ਹੌਟਸਪੌਟ ਬੰਦ ਕਰੋ"
+ "ਹੌਟਸਪੌਟ ਚਾਲੂ ਹੈ"
+ "ਰੋਮਿੰਗ ਦੌਰਾਨ ਵਧੀਕ ਖਰਚੇ ਲਾਗੂ ਹੋ ਸਕਦੇ ਹਨ"
+ "ਜਾਰੀ ਰੱਖੋ"
+
diff --git a/Tethering/res/values-mcc204-mnc04-pl/strings.xml b/Tethering/res/values-mcc204-mnc04-pl/strings.xml
new file mode 100644
index 0000000000..8becd0715f
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-pl/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Hotspot nie ma internetu"
+ "Urządzenia nie mogą połączyć się z internetem"
+ "Wyłącz hotspot"
+ "Hotspot jest włączony"
+ "Podczas korzystania z roamingu mogą zostać naliczone dodatkowe opłaty"
+ "Dalej"
+
diff --git a/Tethering/res/values-mcc204-mnc04-pt-rBR/strings.xml b/Tethering/res/values-mcc204-mnc04-pt-rBR/strings.xml
new file mode 100644
index 0000000000..8e01736f64
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-pt-rBR/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "O ponto de acesso não tem conexão com a Internet"
+ "Não foi possível conectar os dispositivos à Internet"
+ "Desativar ponto de acesso"
+ "O ponto de acesso está ativado"
+ "Pode haver cobranças extras durante o roaming"
+ "Continuar"
+
diff --git a/Tethering/res/values-mcc204-mnc04-pt-rPT/strings.xml b/Tethering/res/values-mcc204-mnc04-pt-rPT/strings.xml
new file mode 100644
index 0000000000..2356379e2f
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-pt-rPT/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "A zona Wi-Fi não tem Internet"
+ "Não é possível ligar os dispositivos à Internet"
+ "Desativar zona Wi-Fi"
+ "A zona Wi-Fi está ativada"
+ "Podem aplicar-se custos adicionais em roaming."
+ "Continuar"
+
diff --git a/Tethering/res/values-mcc204-mnc04-pt/strings.xml b/Tethering/res/values-mcc204-mnc04-pt/strings.xml
new file mode 100644
index 0000000000..8e01736f64
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-pt/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "O ponto de acesso não tem conexão com a Internet"
+ "Não foi possível conectar os dispositivos à Internet"
+ "Desativar ponto de acesso"
+ "O ponto de acesso está ativado"
+ "Pode haver cobranças extras durante o roaming"
+ "Continuar"
+
diff --git a/Tethering/res/values-mcc204-mnc04-ro/strings.xml b/Tethering/res/values-mcc204-mnc04-ro/strings.xml
new file mode 100644
index 0000000000..2e62bd611c
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ro/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Hotspotul nu are internet"
+ "Dispozitivele nu se pot conecta la internet"
+ "Dezactivați hotspotul"
+ "Hotspotul este activ"
+ "Se pot aplica taxe suplimentare pentru roaming"
+ "Continuați"
+
diff --git a/Tethering/res/values-mcc204-mnc04-ru/strings.xml b/Tethering/res/values-mcc204-mnc04-ru/strings.xml
new file mode 100644
index 0000000000..a2b1640cb2
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ru/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Точка доступа не подключена к Интернету"
+ "Устройства не могут подключаться к Интернету"
+ "Отключить точку доступа"
+ "Точка доступа включена"
+ "За использование услуг связи в роуминге может взиматься дополнительная плата."
+ "Продолжить"
+
diff --git a/Tethering/res/values-mcc204-mnc04-si/strings.xml b/Tethering/res/values-mcc204-mnc04-si/strings.xml
new file mode 100644
index 0000000000..632748a3e8
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-si/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "හොට්ස්පොට් හට අන්තර්ජාලය නැත"
+ "උපාංගවලට අන්තර්ජාලයට සම්බන්ධ විය නොහැකිය"
+ "හොට්ස්පොට් ක්රියාවිරහිත කරන්න"
+ "හොට්ස්පොට් ක්රියාත්මකයි"
+ "රෝමිං අතරතුර අමතර ගාස්තු අදාළ විය හැකිය"
+ "ඉදිරියට යන්න"
+
diff --git a/Tethering/res/values-mcc204-mnc04-sk/strings.xml b/Tethering/res/values-mcc204-mnc04-sk/strings.xml
new file mode 100644
index 0000000000..247fc1b0e7
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-sk/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Hotspot nemá internetové pripojenie"
+ "Zariadenia sa nedajú pripojiť k internetu"
+ "Vypnúť hotspot"
+ "Hotspot je zapnutý"
+ "Počas roamingu vám môžu byť účtované ďalšie poplatky"
+ "Pokračovať"
+
diff --git a/Tethering/res/values-mcc204-mnc04-sl/strings.xml b/Tethering/res/values-mcc204-mnc04-sl/strings.xml
new file mode 100644
index 0000000000..ed22372197
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-sl/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Dostopna točka nima internetne povezave"
+ "Naprave ne morejo vzpostaviti internetne povezave"
+ "Izklopi dostopno točko"
+ "Dostopna točka je vklopljena"
+ "Med gostovanjem lahko nastanejo dodatni stroški"
+ "Naprej"
+
diff --git a/Tethering/res/values-mcc204-mnc04-sq/strings.xml b/Tethering/res/values-mcc204-mnc04-sq/strings.xml
new file mode 100644
index 0000000000..4bfab6e474
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-sq/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Zona e qasjes për internet nuk ka internet"
+ "Pajisjet nuk mund të lidhen me internetin"
+ "Çaktivizo zonën e qasjes për internet"
+ "Zona e qasjes për internet është aktive"
+ "Mund të zbatohen tarifime shtesë kur je në roaming"
+ "Vazhdo"
+
diff --git a/Tethering/res/values-mcc204-mnc04-sr/strings.xml b/Tethering/res/values-mcc204-mnc04-sr/strings.xml
new file mode 100644
index 0000000000..478d53a255
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-sr/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Хотспот нема приступ интернету"
+ "Уређаји не могу да се повежу на интернет"
+ "Искључи хотспот"
+ "Хотспот је укључен"
+ "Можда важе додатни трошкови у ромингу"
+ "Настави"
+
diff --git a/Tethering/res/values-mcc204-mnc04-sv/strings.xml b/Tethering/res/values-mcc204-mnc04-sv/strings.xml
new file mode 100644
index 0000000000..a793ed6483
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-sv/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Surfzonen har ingen internetanslutning"
+ "Enheterna har ingen internetanslutning"
+ "Inaktivera surfzon"
+ "Surfzonen är aktiverad"
+ "Ytterligare avgifter kan tillkomma vid roaming"
+ "Fortsätt"
+
diff --git a/Tethering/res/values-mcc204-mnc04-sw/strings.xml b/Tethering/res/values-mcc204-mnc04-sw/strings.xml
new file mode 100644
index 0000000000..18ee457d03
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-sw/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Mtandao pepe hauna intaneti"
+ "Vifaa vimeshindwa kuunganisha kwenye intaneti"
+ "Zima mtandao pepe"
+ "Mtandao pepe umewashwa"
+ "Huenda ukatozwa gharama za ziada ukitumia mitandao ya ng\'ambo"
+ "Endelea"
+
diff --git a/Tethering/res/values-mcc204-mnc04-ta/strings.xml b/Tethering/res/values-mcc204-mnc04-ta/strings.xml
new file mode 100644
index 0000000000..7eebd6784a
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ta/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "ஹாட்ஸ்பாட்டில் இணையம் இல்லை"
+ "சாதனங்களால் இணையத்தில் இணைய இயலவில்லை"
+ "ஹாட்ஸ்பாட்டை ஆஃப் செய்"
+ "ஹாட்ஸ்பாட் ஆன் செய்யப்பட்டுள்ளது"
+ "ரோமிங்கின்போது கூடுதல் கட்டணங்கள் விதிக்கப்படக்கூடும்"
+ "தொடர்க"
+
diff --git a/Tethering/res/values-mcc204-mnc04-te/strings.xml b/Tethering/res/values-mcc204-mnc04-te/strings.xml
new file mode 100644
index 0000000000..0986534fc7
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-te/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "హాట్స్పాట్కు ఇంటర్నెట్ యాక్సెస్ లేదు"
+ "పరికరాలను ఇంటర్నెట్కి కనెక్ట్ చేయడం సాధ్యం కాదు"
+ "హాట్స్పాట్ని ఆఫ్ చేయండి"
+ "హాట్స్పాట్ ఆన్లో ఉంది"
+ "రోమింగ్లో ఉన్నప్పుడు అదనపు ఛార్జీలు వర్తించవచ్చు"
+ "కొనసాగించు"
+
diff --git a/Tethering/res/values-mcc204-mnc04-th/strings.xml b/Tethering/res/values-mcc204-mnc04-th/strings.xml
new file mode 100644
index 0000000000..3837002b29
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-th/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "ฮอตสปอตไม่ได้เชื่อมต่ออินเทอร์เน็ต"
+ "อุปกรณ์เชื่อมต่ออินเทอร์เน็ตไม่ได้"
+ "ปิดฮอตสปอต"
+ "ฮอตสปอตเปิดอยู่"
+ "อาจมีค่าใช้จ่ายเพิ่มเติมขณะโรมมิ่ง"
+ "ต่อไป"
+
diff --git a/Tethering/res/values-mcc204-mnc04-tl/strings.xml b/Tethering/res/values-mcc204-mnc04-tl/strings.xml
new file mode 100644
index 0000000000..208f893447
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-tl/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Walang internet ang hotspot"
+ "Hindi makakonekta sa internet ang mga device"
+ "I-off ang hotspot"
+ "Naka-on ang hotspot"
+ "Posibleng magkaroon ng mga karagdagang singil habang nagro-roam"
+ "Ituloy"
+
diff --git a/Tethering/res/values-mcc204-mnc04-tr/strings.xml b/Tethering/res/values-mcc204-mnc04-tr/strings.xml
new file mode 100644
index 0000000000..3482fafa2d
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-tr/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Hotspot\'un internet bağlantısı yok"
+ "Cihazlar internete bağlanamıyor"
+ "Hotspot\'u kapat"
+ "Hotspot açık"
+ "Dolaşım sırasında ek ücretler uygulanabilir"
+ "Devam"
+
diff --git a/Tethering/res/values-mcc204-mnc04-uk/strings.xml b/Tethering/res/values-mcc204-mnc04-uk/strings.xml
new file mode 100644
index 0000000000..dea311443f
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-uk/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Точка доступу не підключена до Інтернету"
+ "Не вдається підключити пристрої до Інтернету"
+ "Вимкнути точку доступу"
+ "Точку доступу ввімкнено"
+ "У роумінгу може стягуватися додаткова плата"
+ "Продовжити"
+
diff --git a/Tethering/res/values-mcc204-mnc04-ur/strings.xml b/Tethering/res/values-mcc204-mnc04-ur/strings.xml
new file mode 100644
index 0000000000..09bc0c9eab
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ur/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "ہاٹ اسپاٹ میں انٹرنیٹ نہیں ہے"
+ "آلات انٹرنیٹ سے منسلک نہیں ہو سکتے"
+ "ہاٹ اسپاٹ آف کریں"
+ "ہاٹ اسپاٹ آن ہے"
+ "رومنگ کے دوران اضافی چارجز لاگو ہو سکتے ہیں"
+ "جاری رکھیں"
+
diff --git a/Tethering/res/values-mcc204-mnc04-uz/strings.xml b/Tethering/res/values-mcc204-mnc04-uz/strings.xml
new file mode 100644
index 0000000000..715d34808b
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-uz/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Hotspot internetga ulanmagan"
+ "Qurilmalar internetga ulana olmayapti"
+ "Hotspotni faolsizlantirish"
+ "Hotspot yoniq"
+ "Rouming vaqtida qoʻshimcha haq olinishi mumkin"
+ "Davom etish"
+
diff --git a/Tethering/res/values-mcc204-mnc04-vi/strings.xml b/Tethering/res/values-mcc204-mnc04-vi/strings.xml
new file mode 100644
index 0000000000..bf4ee1011b
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-vi/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "Điểm phát sóng không có kết nối Internet"
+ "Các thiết bị không thể kết nối Internet"
+ "Tắt điểm phát sóng"
+ "Điểm phát sóng đang bật"
+ "Bạn có thể mất thêm phí dữ liệu khi chuyển vùng"
+ "Tiếp tục"
+
diff --git a/Tethering/res/values-mcc204-mnc04-zh-rCN/strings.xml b/Tethering/res/values-mcc204-mnc04-zh-rCN/strings.xml
new file mode 100644
index 0000000000..cdb4224bf3
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-zh-rCN/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "热点没有网络连接"
+ "设备无法连接到互联网"
+ "关闭热点"
+ "热点已开启"
+ "漫游时可能会产生额外的费用"
+ "继续"
+
diff --git a/Tethering/res/values-mcc204-mnc04-zh-rHK/strings.xml b/Tethering/res/values-mcc204-mnc04-zh-rHK/strings.xml
new file mode 100644
index 0000000000..3bb52e491f
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-zh-rHK/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "熱點沒有互聯網連線"
+ "裝置無法連線至互聯網"
+ "關閉熱點"
+ "已開啟熱點"
+ "漫遊時可能需要支付額外費用"
+ "繼續"
+
diff --git a/Tethering/res/values-mcc204-mnc04-zh-rTW/strings.xml b/Tethering/res/values-mcc204-mnc04-zh-rTW/strings.xml
new file mode 100644
index 0000000000..298c3eac70
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-zh-rTW/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "無線基地台沒有網際網路連線"
+ "裝置無法連上網際網路"
+ "關閉無線基地台"
+ "無線基地台已開啟"
+ "使用漫遊服務可能須支付額外費用"
+ "繼續"
+
diff --git a/Tethering/res/values-mcc204-mnc04-zu/strings.xml b/Tethering/res/values-mcc204-mnc04-zu/strings.xml
new file mode 100644
index 0000000000..3dc0078834
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-zu/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ "I-Hotspot ayina-inthanethi"
+ "Amadivayisi awakwazi ukuxhuma ku-inthanethi"
+ "Vala i-hotspot"
+ "I-Hotspot ivuliwe"
+ "Kungaba nezinkokhelo ezengeziwe uma uzula"
+ "Qhubeka"
+
diff --git a/Tethering/res/values-mcc310-mnc004-af/strings.xml b/Tethering/res/values-mcc310-mnc004-af/strings.xml
new file mode 100644
index 0000000000..19d659c6ce
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-af/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Verbinding het nie internet nie"
+ "Toestelle kan nie koppel nie"
+ "Skakel verbinding af"
+ "Warmkol of verbinding is aan"
+ "Bykomende heffings kan geld terwyl jy swerf"
+
diff --git a/Tethering/res/values-mcc310-mnc004-am/strings.xml b/Tethering/res/values-mcc310-mnc004-am/strings.xml
new file mode 100644
index 0000000000..8995430b4f
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-am/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ማስተሳሰር ምንም በይነመረብ የለውም"
+ "መሣሪያዎችን ማገናኘት አይቻልም"
+ "ማስተሳሰርን አጥፋ"
+ "መገናኛ ነጥብ ወይም ማስተሳሰር በርቷል"
+ "በሚያንዣብብበት ጊዜ ተጨማሪ ክፍያዎች ተፈጻሚ ሊሆኑ ይችላሉ"
+
diff --git a/Tethering/res/values-mcc310-mnc004-ar/strings.xml b/Tethering/res/values-mcc310-mnc004-ar/strings.xml
new file mode 100644
index 0000000000..54f3b5389a
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-ar/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ما مِن اتصال بالإنترنت خلال التوصيل"
+ "تعذّر اتصال الأجهزة"
+ "إيقاف التوصيل"
+ "نقطة الاتصال أو التوصيل مفعّلان"
+ "قد يتم تطبيق رسوم إضافية أثناء التجوال."
+
diff --git a/Tethering/res/values-mcc310-mnc004-as/strings.xml b/Tethering/res/values-mcc310-mnc004-as/strings.xml
new file mode 100644
index 0000000000..e215141c9e
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-as/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "টে\'ডাৰিঙৰ ইণ্টাৰনেট নাই"
+ "ডিভাইচসমূহ সংযোগ কৰিব নোৱাৰি"
+ "টে\'ডাৰিং অফ কৰক"
+ "হটস্পট অথবা টে\'ডাৰিং অন আছে"
+ "ৰ\'মিঙত থাকিলে অতিৰিক্ত মাচুল প্ৰযোজ্য হ’ব পাৰে"
+
diff --git a/Tethering/res/values-mcc310-mnc004-az/strings.xml b/Tethering/res/values-mcc310-mnc004-az/strings.xml
new file mode 100644
index 0000000000..1fd8e4c963
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-az/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Modemin internetə girişi yoxdur"
+ "Cihazları qoşmaq mümkün deyil"
+ "Modemi deaktiv edin"
+ "Hotspot və ya modem aktivdir"
+ "Rouminq zamanı əlavə ödənişlər tətbiq edilə bilər"
+
diff --git a/Tethering/res/values-mcc310-mnc004-b+sr+Latn/strings.xml b/Tethering/res/values-mcc310-mnc004-b+sr+Latn/strings.xml
new file mode 100644
index 0000000000..1abe4f3aa3
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-b+sr+Latn/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Privezivanje nema pristup internetu"
+ "Povezivanje uređaja nije uspelo"
+ "Isključi privezivanje"
+ "Uključen je hotspot ili privezivanje"
+ "Možda važe dodatni troškovi u romingu"
+
diff --git a/Tethering/res/values-mcc310-mnc004-be/strings.xml b/Tethering/res/values-mcc310-mnc004-be/strings.xml
new file mode 100644
index 0000000000..38dbd1e391
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-be/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Рэжым мадэма выкарыстоўваецца без доступу да інтэрнэту"
+ "Не ўдалося падключыць прылады"
+ "Выключыць рэжым мадэма"
+ "Хот-спот або рэжым мадэма ўключаны"
+ "Пры выкарыстанні роўмінгу можа спаганяцца дадатковая плата"
+
diff --git a/Tethering/res/values-mcc310-mnc004-bg/strings.xml b/Tethering/res/values-mcc310-mnc004-bg/strings.xml
new file mode 100644
index 0000000000..04b44db5c1
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-bg/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Тетърингът няма връзка с интернет"
+ "Устройствата не могат да установят връзка"
+ "Изключване на тетъринга"
+ "Точката за достъп или тетърингът са включени"
+ "Възможно е да ви бъдат начислени допълнителни такси при роуминг"
+
diff --git a/Tethering/res/values-mcc310-mnc004-bn/strings.xml b/Tethering/res/values-mcc310-mnc004-bn/strings.xml
new file mode 100644
index 0000000000..579d1be1c1
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-bn/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "টিথারিং করার জন্য কোনও ইন্টারনেট কানেকশন নেই"
+ "ডিভাইস কানেক্ট করতে পারছে না"
+ "টিথারিং বন্ধ করুন"
+ "হটস্পট বা টিথারিং চালু আছে"
+ "রোমিংয়ের সময় অতিরিক্ত চার্জ করা হতে পারে"
+
diff --git a/Tethering/res/values-mcc310-mnc004-bs/strings.xml b/Tethering/res/values-mcc310-mnc004-bs/strings.xml
new file mode 100644
index 0000000000..9ce3efe6c3
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-bs/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Povezivanje putem mobitela nema internet"
+ "Uređaji se ne mogu povezati"
+ "Isključi povezivanje putem mobitela"
+ "Pristupna tačka ili povezivanje putem mobitela je uključeno"
+ "Mogu nastati dodatni troškovi u romingu"
+
diff --git a/Tethering/res/values-mcc310-mnc004-ca/strings.xml b/Tethering/res/values-mcc310-mnc004-ca/strings.xml
new file mode 100644
index 0000000000..46d4c35b9b
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-ca/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "La compartició de xarxa no té accés a Internet"
+ "No es poden connectar els dispositius"
+ "Desactiva la compartició de xarxa"
+ "S\'ha activat el punt d\'accés Wi‑Fi o la compartició de xarxa"
+ "És possible que s\'apliquin costos addicionals en itinerància"
+
diff --git a/Tethering/res/values-mcc310-mnc004-cs/strings.xml b/Tethering/res/values-mcc310-mnc004-cs/strings.xml
new file mode 100644
index 0000000000..cc13860b3d
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-cs/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering nemá připojení k internetu"
+ "Zařízení se nemůžou připojit"
+ "Vypnout tethering"
+ "Je zapnutý hotspot nebo tethering"
+ "Při roamingu mohou být účtovány dodatečné poplatky"
+
diff --git a/Tethering/res/values-mcc310-mnc004-da/strings.xml b/Tethering/res/values-mcc310-mnc004-da/strings.xml
new file mode 100644
index 0000000000..92c3ae1156
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-da/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Netdeling har ingen internetforbindelse"
+ "Enheder kan ikke oprette forbindelse"
+ "Deaktiver netdeling"
+ "Hotspot eller netdeling er aktiveret"
+ "Der opkræves muligvis yderligere gebyrer ved roaming"
+
diff --git a/Tethering/res/values-mcc310-mnc004-de/strings.xml b/Tethering/res/values-mcc310-mnc004-de/strings.xml
new file mode 100644
index 0000000000..967eb4db2e
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-de/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering hat keinen Internetzugriff"
+ "Geräte können sich nicht verbinden"
+ "Tethering deaktivieren"
+ "Hotspot oder Tethering ist aktiviert"
+ "Für das Roaming können zusätzliche Gebühren anfallen"
+
diff --git a/Tethering/res/values-mcc310-mnc004-el/strings.xml b/Tethering/res/values-mcc310-mnc004-el/strings.xml
new file mode 100644
index 0000000000..5fb497451f
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-el/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Η σύνδεση δεν έχει πρόσβαση στο διαδίκτυο"
+ "Δεν είναι δυνατή η σύνδεση των συσκευών"
+ "Απενεργοποιήστε τη σύνδεση"
+ "Ενεργό σημείο πρόσβασης Wi-Fi ή ενεργή σύνδεση"
+ "Ενδέχεται να ισχύουν επιπλέον χρεώσεις κατά την περιαγωγή."
+
diff --git a/Tethering/res/values-mcc310-mnc004-en-rAU/strings.xml b/Tethering/res/values-mcc310-mnc004-en-rAU/strings.xml
new file mode 100644
index 0000000000..45647f93f2
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-en-rAU/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering has no Internet"
+ "Devices can’t connect"
+ "Turn off tethering"
+ "Hotspot or tethering is on"
+ "Additional charges may apply while roaming"
+
diff --git a/Tethering/res/values-mcc310-mnc004-en-rCA/strings.xml b/Tethering/res/values-mcc310-mnc004-en-rCA/strings.xml
new file mode 100644
index 0000000000..45647f93f2
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-en-rCA/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering has no Internet"
+ "Devices can’t connect"
+ "Turn off tethering"
+ "Hotspot or tethering is on"
+ "Additional charges may apply while roaming"
+
diff --git a/Tethering/res/values-mcc310-mnc004-en-rGB/strings.xml b/Tethering/res/values-mcc310-mnc004-en-rGB/strings.xml
new file mode 100644
index 0000000000..45647f93f2
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-en-rGB/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering has no Internet"
+ "Devices can’t connect"
+ "Turn off tethering"
+ "Hotspot or tethering is on"
+ "Additional charges may apply while roaming"
+
diff --git a/Tethering/res/values-mcc310-mnc004-en-rIN/strings.xml b/Tethering/res/values-mcc310-mnc004-en-rIN/strings.xml
new file mode 100644
index 0000000000..45647f93f2
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-en-rIN/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering has no Internet"
+ "Devices can’t connect"
+ "Turn off tethering"
+ "Hotspot or tethering is on"
+ "Additional charges may apply while roaming"
+
diff --git a/Tethering/res/values-mcc310-mnc004-en-rXC/strings.xml b/Tethering/res/values-mcc310-mnc004-en-rXC/strings.xml
new file mode 100644
index 0000000000..7877074afc
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-en-rXC/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering has no internet"
+ "Devices can’t connect"
+ "Turn off tethering"
+ "Hotspot or tethering is on"
+ "Additional charges may apply while roaming"
+
diff --git a/Tethering/res/values-mcc310-mnc004-es-rUS/strings.xml b/Tethering/res/values-mcc310-mnc004-es-rUS/strings.xml
new file mode 100644
index 0000000000..08edd81a6b
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-es-rUS/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "La conexión mediante dispositivo móvil no tiene Internet"
+ "No se pueden conectar los dispositivos"
+ "Desactivar conexión mediante dispositivo móvil"
+ "Se activó el hotspot o la conexión mediante dispositivo móvil"
+ "Es posible que se apliquen cargos adicionales por roaming"
+
diff --git a/Tethering/res/values-mcc310-mnc004-es/strings.xml b/Tethering/res/values-mcc310-mnc004-es/strings.xml
new file mode 100644
index 0000000000..79f51d00e2
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-es/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "La conexión no se puede compartir, porque no hay acceso a Internet"
+ "Los dispositivos no se pueden conectar"
+ "Desactivar conexión compartida"
+ "Punto de acceso o conexión compartida activados"
+ "Puede que se apliquen cargos adicionales en itinerancia"
+
diff --git a/Tethering/res/values-mcc310-mnc004-et/strings.xml b/Tethering/res/values-mcc310-mnc004-et/strings.xml
new file mode 100644
index 0000000000..2da5f8a6d6
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-et/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Jagamisel puudub internetiühendus"
+ "Seadmed ei saa ühendust luua"
+ "Lülita jagamine välja"
+ "Kuumkoht või jagamine on sisse lülitatud"
+ "Rändluse kasutamisega võivad kaasneda lisatasud"
+
diff --git a/Tethering/res/values-mcc310-mnc004-eu/strings.xml b/Tethering/res/values-mcc310-mnc004-eu/strings.xml
new file mode 100644
index 0000000000..2073f2806c
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-eu/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Konexioa partekatzeko aukerak ez du Interneteko konexiorik"
+ "Ezin dira konektatu gailuak"
+ "Desaktibatu konexioa partekatzeko aukera"
+ "Wifi-gunea edo konexioa partekatzeko aukera aktibatuta dago"
+ "Baliteke kostu gehigarriak ordaindu behar izatea ibiltaritzan"
+
diff --git a/Tethering/res/values-mcc310-mnc004-fa/strings.xml b/Tethering/res/values-mcc310-mnc004-fa/strings.xml
new file mode 100644
index 0000000000..e21b2a0852
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-fa/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "«اشتراکگذاری اینترنت» به اینترنت دسترسی ندارد"
+ "دستگاهها متصل نمیشوند"
+ "خاموش کردن «اشتراکگذاری اینترنت»"
+ "«نقطه اتصال» یا «اشتراکگذاری اینترنت» روشن است"
+ "ممکن است درحین فراگردی تغییرات دیگر اعمال شود"
+
diff --git a/Tethering/res/values-mcc310-mnc004-fi/strings.xml b/Tethering/res/values-mcc310-mnc004-fi/strings.xml
new file mode 100644
index 0000000000..88b0b13eb4
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-fi/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Ei jaettavaa internetyhteyttä"
+ "Laitteet eivät voi muodostaa yhteyttä"
+ "Laita yhteyden jakaminen pois päältä"
+ "Hotspot tai yhteyden jakaminen on päällä"
+ "Roaming voi aiheuttaa lisämaksuja"
+
diff --git a/Tethering/res/values-mcc310-mnc004-fr-rCA/strings.xml b/Tethering/res/values-mcc310-mnc004-fr-rCA/strings.xml
new file mode 100644
index 0000000000..3b781bc8db
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-fr-rCA/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Le partage de connexion n\'est pas connecté à Internet"
+ "Impossible de connecter les appareils"
+ "Désactiver le partage de connexion"
+ "Le point d\'accès ou le partage de connexion est activé"
+ "En itinérance, des frais supplémentaires peuvent s\'appliquer"
+
diff --git a/Tethering/res/values-mcc310-mnc004-fr/strings.xml b/Tethering/res/values-mcc310-mnc004-fr/strings.xml
new file mode 100644
index 0000000000..51d7203c36
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-fr/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Aucune connexion à Internet n\'est disponible pour le partage de connexion"
+ "Impossible de connecter les appareils"
+ "Désactiver le partage de connexion"
+ "Le point d\'accès ou le partage de connexion est activé"
+ "En itinérance, des frais supplémentaires peuvent s\'appliquer"
+
diff --git a/Tethering/res/values-mcc310-mnc004-gl/strings.xml b/Tethering/res/values-mcc310-mnc004-gl/strings.xml
new file mode 100644
index 0000000000..008ccb475d
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-gl/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "A conexión compartida non ten Internet"
+ "Non se puideron conectar os dispositivos"
+ "Desactivar conexión compartida"
+ "Está activada a zona wifi ou a conexión compartida"
+ "Pódense aplicar cargos adicionais en itinerancia"
+
diff --git a/Tethering/res/values-mcc310-mnc004-gu/strings.xml b/Tethering/res/values-mcc310-mnc004-gu/strings.xml
new file mode 100644
index 0000000000..f2e3b4df78
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-gu/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ઇન્ટરનેટ શેર કરવાની સુવિધામાં ઇન્ટરનેટ નથી"
+ "ડિવાઇસ કનેક્ટ કરી શકાતા નથી"
+ "ઇન્ટરનેટ શેર કરવાની સુવિધા બંધ કરો"
+ "હૉટસ્પૉટ અથવા ઇન્ટરનેટ શેર કરવાની સુવિધા ચાલુ છે"
+ "રોમિંગમાં વધારાના શુલ્ક લાગી શકે છે"
+
diff --git a/Tethering/res/values-mcc310-mnc004-hi/strings.xml b/Tethering/res/values-mcc310-mnc004-hi/strings.xml
new file mode 100644
index 0000000000..b11839d760
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-hi/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "टेदरिंग से इंटरनेट नहीं चल रहा"
+ "डिवाइस कनेक्ट नहीं हो पा रहे"
+ "टेदरिंग बंद करें"
+ "हॉटस्पॉट या टेदरिंग चालू है"
+ "रोमिंग के दौरान अतिरिक्त शुल्क लग सकता है"
+
diff --git a/Tethering/res/values-mcc310-mnc004-hr/strings.xml b/Tethering/res/values-mcc310-mnc004-hr/strings.xml
new file mode 100644
index 0000000000..0a5aca25b1
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-hr/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Modemsko povezivanje nema internet"
+ "Uređaji se ne mogu povezati"
+ "Isključivanje modemskog povezivanja"
+ "Uključena je žarišna točka ili modemsko povezivanje"
+ "U roamingu su mogući dodatni troškovi"
+
diff --git a/Tethering/res/values-mcc310-mnc004-hu/strings.xml b/Tethering/res/values-mcc310-mnc004-hu/strings.xml
new file mode 100644
index 0000000000..21c689a44e
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-hu/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Nincs internetkapcsolat az internet megosztásához"
+ "Az eszközök nem tudnak csatlakozni"
+ "Internetmegosztás kikapcsolása"
+ "A hotspot vagy az internetmegosztás be van kapcsolva"
+ "Roaming során további díjak léphetnek fel"
+
diff --git a/Tethering/res/values-mcc310-mnc004-hy/strings.xml b/Tethering/res/values-mcc310-mnc004-hy/strings.xml
new file mode 100644
index 0000000000..689d92870e
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-hy/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Մոդեմի ռեժիմի կապը բացակայում է"
+ "Չհաջողվեց միացնել սարքը"
+ "Անջատել մոդեմի ռեժիմը"
+ "Թեժ կետը կամ մոդեմի ռեժիմը միացված է"
+ "Ռոումինգում կարող են լրացուցիչ վճարներ գանձվել"
+
diff --git a/Tethering/res/values-mcc310-mnc004-in/strings.xml b/Tethering/res/values-mcc310-mnc004-in/strings.xml
new file mode 100644
index 0000000000..a5f4d19abf
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-in/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tidak ada koneksi internet di tethering"
+ "Perangkat tidak dapat terhubung"
+ "Nonaktifkan tethering"
+ "Hotspot atau tethering aktif"
+ "Biaya tambahan mungkin berlaku saat roaming"
+
diff --git a/Tethering/res/values-mcc310-mnc004-is/strings.xml b/Tethering/res/values-mcc310-mnc004-is/strings.xml
new file mode 100644
index 0000000000..fc7e8aaf4e
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-is/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tjóðrun er ekki með internettengingu"
+ "Tæki geta ekki tengst"
+ "Slökkva á tjóðrun"
+ "Kveikt er á heitum reit eða tjóðrun"
+ "Viðbótargjöld kunna að eiga við í reiki"
+
diff --git a/Tethering/res/values-mcc310-mnc004-it/strings.xml b/Tethering/res/values-mcc310-mnc004-it/strings.xml
new file mode 100644
index 0000000000..6456dd1b80
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-it/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Nessuna connessione a Internet per il tethering"
+ "Impossibile connettere i dispositivi"
+ "Disattiva il tethering"
+ "Hotspot o tethering attivi"
+ "Potrebbero essere applicati costi aggiuntivi durante il roaming"
+
diff --git a/Tethering/res/values-mcc310-mnc004-iw/strings.xml b/Tethering/res/values-mcc310-mnc004-iw/strings.xml
new file mode 100644
index 0000000000..46b24bd3c5
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-iw/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "אי אפשר להפעיל את תכונת שיתוף האינטרנט בין מכשירים כי אין חיבור לאינטרנט"
+ "למכשירים אין אפשרות להתחבר"
+ "השבתה של שיתוף האינטרנט בין מכשירים"
+ "תכונת הנקודה לשיתוף אינטרנט או תכונת שיתוף האינטרנט בין מכשירים פועלת"
+ "ייתכנו חיובים נוספים בעת נדידה"
+
diff --git a/Tethering/res/values-mcc310-mnc004-ja/strings.xml b/Tethering/res/values-mcc310-mnc004-ja/strings.xml
new file mode 100644
index 0000000000..e6eb277b90
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-ja/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "テザリングがインターネットに接続されていません"
+ "デバイスを接続できません"
+ "テザリングを OFF にする"
+ "アクセス ポイントまたはテザリングが ON です"
+ "ローミング時に追加料金が発生することがあります"
+
diff --git a/Tethering/res/values-mcc310-mnc004-ka/strings.xml b/Tethering/res/values-mcc310-mnc004-ka/strings.xml
new file mode 100644
index 0000000000..aeddd7101d
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-ka/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ტეტერინგს არ აქვს ინტერნეტზე წვდომა"
+ "მოწყობილობები ვერ ახერხებენ დაკავშირებას"
+ "ტეტერინგის გამორთვა"
+ "ჩართულია უსადენო ქსელი ან ტეტერინგი"
+ "როუმინგის გამოყენებისას შეიძლება ჩამოგეჭრათ დამატებითი საფასური"
+
diff --git a/Tethering/res/values-mcc310-mnc004-kk/strings.xml b/Tethering/res/values-mcc310-mnc004-kk/strings.xml
new file mode 100644
index 0000000000..255f0a276f
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-kk/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Тетеринг режимі интернет байланысынсыз пайдаланылуда"
+ "Құрылғыларды байланыстыру мүмкін емес"
+ "Тетерингіні өшіру"
+ "Хотспот немесе тетеринг қосулы"
+ "Роуминг кезінде қосымша ақы алынуы мүмкін."
+
diff --git a/Tethering/res/values-mcc310-mnc004-km/strings.xml b/Tethering/res/values-mcc310-mnc004-km/strings.xml
new file mode 100644
index 0000000000..2bceb1cf77
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-km/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ការភ្ជាប់មិនមានអ៊ីនធឺណិតទេ"
+ "មិនអាចភ្ជាប់ឧបករណ៍បានទេ"
+ "បិទការភ្ជាប់"
+ "ហតស្ប៉ត ឬការភ្ជាប់ត្រូវបានបើក"
+ "អាចមានការគិតថ្លៃបន្ថែម នៅពេលរ៉ូមីង"
+
diff --git a/Tethering/res/values-mcc310-mnc004-kn/strings.xml b/Tethering/res/values-mcc310-mnc004-kn/strings.xml
new file mode 100644
index 0000000000..ed769305a6
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-kn/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ಟೆಥರಿಂಗ್ ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಕನೆಕ್ಷನ್ ಹೊಂದಿಲ್ಲ"
+ "ಸಾಧನಗಳನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"
+ "ಟೆಥರಿಂಗ್ ಆಫ್ ಮಾಡಿ"
+ "ಹಾಟ್ಸ್ಪಾಟ್ ಅಥವಾ ಟೆಥರಿಂಗ್ ಆನ್ ಆಗಿದೆ"
+ "ರೋಮಿಂಗ್ನಲ್ಲಿರುವಾಗ ಹೆಚ್ಚುವರಿ ಶುಲ್ಕಗಳು ಅನ್ವಯವಾಗಬಹುದು"
+
diff --git a/Tethering/res/values-mcc310-mnc004-ko/strings.xml b/Tethering/res/values-mcc310-mnc004-ko/strings.xml
new file mode 100644
index 0000000000..6e504941eb
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-ko/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "테더링으로 인터넷을 사용할 수 없음"
+ "기기에서 연결할 수 없음"
+ "테더링 사용 중지"
+ "핫스팟 또는 테더링 켜짐"
+ "로밍 중에는 추가 요금이 발생할 수 있습니다."
+
diff --git a/Tethering/res/values-mcc310-mnc004-ky/strings.xml b/Tethering/res/values-mcc310-mnc004-ky/strings.xml
new file mode 100644
index 0000000000..d68128b9a5
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-ky/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Модем режими Интернети жок колдонулууда"
+ "Түзмөктөр туташпай жатат"
+ "Модем режимин өчүрүү"
+ "Байланыш түйүнү же модем режими күйүк"
+ "Роумингде кошумча акы алынышы мүмкүн"
+
diff --git a/Tethering/res/values-mcc310-mnc004-lo/strings.xml b/Tethering/res/values-mcc310-mnc004-lo/strings.xml
new file mode 100644
index 0000000000..03e134a0fc
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-lo/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ການປ່ອຍສັນຍານບໍ່ມີອິນເຕີເນັດ"
+ "ອຸປະກອນບໍ່ສາມາດເຊື່ອມຕໍ່ໄດ້"
+ "ປິດການປ່ອຍສັນຍານ"
+ "ເປີດໃຊ້ຮັອດສະປອດ ຫຼື ການປ່ອຍສັນຍານຢູ່"
+ "ອາດມີຄ່າໃຊ້ຈ່າຍເພີ່ມເຕີມໃນລະຫວ່າງການໂຣມມິງ"
+
diff --git a/Tethering/res/values-mcc310-mnc004-lt/strings.xml b/Tethering/res/values-mcc310-mnc004-lt/strings.xml
new file mode 100644
index 0000000000..652cedc6e6
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-lt/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Nėra įrenginio kaip modemo naudojimo interneto ryšio"
+ "Nepavyko susieti įrenginių"
+ "Išjungti įrenginio kaip modemo naudojimą"
+ "Įjungtas viešosios interneto prieigos taškas arba įrenginio kaip modemo naudojimas"
+ "Veikiant tarptinkliniam ryšiui gali būti taikomi papildomi mokesčiai"
+
diff --git a/Tethering/res/values-mcc310-mnc004-lv/strings.xml b/Tethering/res/values-mcc310-mnc004-lv/strings.xml
new file mode 100644
index 0000000000..221972298c
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-lv/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Piesaistei nav interneta savienojuma"
+ "Nevar savienot ierīces"
+ "Izslēgt piesaisti"
+ "Ir ieslēgts tīklājs vai piesaiste"
+ "Viesabonēšanas laikā var tikt piemērota papildu samaksa"
+
diff --git a/Tethering/res/values-mcc310-mnc004-mk/strings.xml b/Tethering/res/values-mcc310-mnc004-mk/strings.xml
new file mode 100644
index 0000000000..227f9e3466
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-mk/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Нема интернет преку мобилен"
+ "Уредите не може да се поврзат"
+ "Исклучи интернет преку мобилен"
+ "Точката на пристап или интернетот преку мобилен е вклучен"
+ "При роаминг може да се наплатат дополнителни трошоци"
+
diff --git a/Tethering/res/values-mcc310-mnc004-ml/strings.xml b/Tethering/res/values-mcc310-mnc004-ml/strings.xml
new file mode 100644
index 0000000000..ec43885126
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-ml/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ടെതറിംഗിന് ഇന്റർനെറ്റ് ഇല്ല"
+ "ഉപകരണങ്ങൾ കണക്റ്റ് ചെയ്യാനാവില്ല"
+ "ടെതറിംഗ് ഓഫാക്കുക"
+ "ഹോട്ട്സ്പോട്ട് അല്ലെങ്കിൽ ടെതറിംഗ് ഓണാണ്"
+ "റോമിംഗ് ചെയ്യുമ്പോൾ അധിക നിരക്കുകൾ ബാധകമായേക്കാം"
+
diff --git a/Tethering/res/values-mcc310-mnc004-mn/strings.xml b/Tethering/res/values-mcc310-mnc004-mn/strings.xml
new file mode 100644
index 0000000000..e263573799
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-mn/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Модемд интернэт алга байна"
+ "Төхөөрөмжүүд холбогдох боломжгүй байна"
+ "Модем болгохыг унтраах"
+ "Сүлжээний цэг эсвэл модем болгох асаалттай байна"
+ "Роумингийн үеэр нэмэлт төлбөр нэхэмжилж болзошгүй"
+
diff --git a/Tethering/res/values-mcc310-mnc004-mr/strings.xml b/Tethering/res/values-mcc310-mnc004-mr/strings.xml
new file mode 100644
index 0000000000..adf845d078
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-mr/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "टेदरिंगला इंटरनेट नाही"
+ "डिव्हाइस कनेक्ट होऊ शकत नाहीत"
+ "टेदरिंग बंद करा"
+ "हॉटस्पॉट किंवा टेदरिंग सुरू आहे"
+ "रोमिंगदरम्यान अतिरिक्त शुल्क लागू होऊ शकतात"
+
diff --git a/Tethering/res/values-mcc310-mnc004-ms/strings.xml b/Tethering/res/values-mcc310-mnc004-ms/strings.xml
new file mode 100644
index 0000000000..f65c451e4c
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-ms/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Penambatan tiada Internet"
+ "Peranti tidak dapat disambungkan"
+ "Matikan penambatan"
+ "Tempat liputan atau penambatan dihidupkan"
+ "Caj tambahan mungkin digunakan semasa perayauan"
+
diff --git a/Tethering/res/values-mcc310-mnc004-my/strings.xml b/Tethering/res/values-mcc310-mnc004-my/strings.xml
new file mode 100644
index 0000000000..4118e775cd
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-my/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်းတွင် အင်တာနက် မရှိပါ"
+ "စက်များ ချိတ်ဆက်၍ မရပါ"
+ "မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်း ပိတ်ရန်"
+ "ဟော့စပေါ့ (သို့) မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်း ဖွင့်ထားသည်"
+ "ပြင်ပကွန်ရက်နှင့် ချိတ်ဆက်သည့်အခါ နောက်ထပ်ကျသင့်မှုများ ရှိနိုင်သည်"
+
diff --git a/Tethering/res/values-mcc310-mnc004-nb/strings.xml b/Tethering/res/values-mcc310-mnc004-nb/strings.xml
new file mode 100644
index 0000000000..36853583ce
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-nb/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Internettdeling har ikke internettilgang"
+ "Enhetene kan ikke koble til"
+ "Slå av internettdeling"
+ "Wi-Fi-sone eller internettdeling er på"
+ "Ytterligere kostnader kan påløpe under roaming"
+
diff --git a/Tethering/res/values-mcc310-mnc004-ne/strings.xml b/Tethering/res/values-mcc310-mnc004-ne/strings.xml
new file mode 100644
index 0000000000..d074f15699
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-ne/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "टेदरिङमार्फत इन्टरनेट कनेक्सन प्राप्त हुन सकेन"
+ "यन्त्रहरू कनेक्ट गर्न सकिएन"
+ "टेदरिङ निष्क्रिय पार्नुहोस्"
+ "हटस्पट वा टेदरिङ सक्रिय छ"
+ "रोमिङ सेवा प्रयोग गर्दा अतिरिक्त शुल्क लाग्न सक्छ"
+
diff --git a/Tethering/res/values-mcc310-mnc004-nl/strings.xml b/Tethering/res/values-mcc310-mnc004-nl/strings.xml
new file mode 100644
index 0000000000..1d888942f4
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-nl/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering heeft geen internet"
+ "Apparaten kunnen niet worden verbonden"
+ "Tethering uitschakelen"
+ "Hotspot of tethering is ingeschakeld"
+ "Er kunnen extra kosten voor roaming in rekening worden gebracht."
+
diff --git a/Tethering/res/values-mcc310-mnc004-or/strings.xml b/Tethering/res/values-mcc310-mnc004-or/strings.xml
new file mode 100644
index 0000000000..8038815fe8
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-or/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ଟିଥରିଂ ପାଇଁ କୌଣସି ଇଣ୍ଟର୍ନେଟ୍ ସଂଯୋଗ ନାହିଁ"
+ "ଡିଭାଇସଗୁଡ଼ିକ ସଂଯୋଗ କରାଯାଇପାରିବ ନାହିଁ"
+ "ଟିଥରିଂ ବନ୍ଦ କରନ୍ତୁ"
+ "ହଟସ୍ପଟ୍ କିମ୍ବା ଟିଥରିଂ ଚାଲୁ ଅଛି"
+ "ରୋମିଂରେ ଥିବା ସମୟରେ ଅତିରିକ୍ତ ଶୁଳ୍କ ଲାଗୁ ହୋଇପାରେ"
+
diff --git a/Tethering/res/values-mcc310-mnc004-pa/strings.xml b/Tethering/res/values-mcc310-mnc004-pa/strings.xml
new file mode 100644
index 0000000000..819833eab0
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-pa/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ਟੈਦਰਿੰਗ ਕੋਲ ਇੰਟਰਨੈੱਟ ਪਹੁੰਚ ਨਹੀਂ ਹੈ"
+ "ਡੀਵਾਈਸ ਕਨੈਕਟ ਨਹੀਂ ਕੀਤੇ ਜਾ ਸਕਦੇ"
+ "ਟੈਦਰਿੰਗ ਬੰਦ ਕਰੋ"
+ "ਹੌਟਸਪੌਟ ਜਾਂ ਟੈਦਰਿੰਗ ਚਾਲੂ ਹੈ"
+ "ਰੋਮਿੰਗ ਦੌਰਾਨ ਵਧੀਕ ਖਰਚੇ ਲਾਗੂ ਹੋ ਸਕਦੇ ਹਨ"
+
diff --git a/Tethering/res/values-mcc310-mnc004-pl/strings.xml b/Tethering/res/values-mcc310-mnc004-pl/strings.xml
new file mode 100644
index 0000000000..65e4380e39
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-pl/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering nie ma internetu"
+ "Urządzenia nie mogą się połączyć"
+ "Wyłącz tethering"
+ "Hotspot lub tethering jest włączony"
+ "Podczas korzystania z roamingu mogą zostać naliczone dodatkowe opłaty"
+
diff --git a/Tethering/res/values-mcc310-mnc004-pt-rBR/strings.xml b/Tethering/res/values-mcc310-mnc004-pt-rBR/strings.xml
new file mode 100644
index 0000000000..d8866170c1
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-pt-rBR/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "O tethering não tem Internet"
+ "Não é possível conectar os dispositivos"
+ "Desativar o tethering"
+ "Ponto de acesso ou tethering ativado"
+ "Pode haver cobranças extras durante o roaming"
+
diff --git a/Tethering/res/values-mcc310-mnc004-pt-rPT/strings.xml b/Tethering/res/values-mcc310-mnc004-pt-rPT/strings.xml
new file mode 100644
index 0000000000..bfd45ca0a3
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-pt-rPT/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "A ligação (à Internet) via telemóvel não tem Internet"
+ "Não é possível ligar os dispositivos"
+ "Desativar ligação (à Internet) via telemóvel"
+ "A zona Wi-Fi ou a ligação (à Internet) via telemóvel está ativada"
+ "Podem aplicar-se custos adicionais em roaming."
+
diff --git a/Tethering/res/values-mcc310-mnc004-pt/strings.xml b/Tethering/res/values-mcc310-mnc004-pt/strings.xml
new file mode 100644
index 0000000000..d8866170c1
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-pt/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "O tethering não tem Internet"
+ "Não é possível conectar os dispositivos"
+ "Desativar o tethering"
+ "Ponto de acesso ou tethering ativado"
+ "Pode haver cobranças extras durante o roaming"
+
diff --git a/Tethering/res/values-mcc310-mnc004-ro/strings.xml b/Tethering/res/values-mcc310-mnc004-ro/strings.xml
new file mode 100644
index 0000000000..8d87a9e516
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-ro/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Procesul de tethering nu are internet"
+ "Dispozitivele nu se pot conecta"
+ "Dezactivați procesul de tethering"
+ "S-a activat hotspotul sau tethering"
+ "Se pot aplica taxe suplimentare pentru roaming"
+
diff --git a/Tethering/res/values-mcc310-mnc004-ru/strings.xml b/Tethering/res/values-mcc310-mnc004-ru/strings.xml
new file mode 100644
index 0000000000..dbdb9ebe49
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-ru/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Режим модема используется без доступа к Интернету"
+ "Невозможно подключить устройства."
+ "Отключить режим модема"
+ "Включены точка доступа или режим модема"
+ "За использование услуг связи в роуминге может взиматься дополнительная плата."
+
diff --git a/Tethering/res/values-mcc310-mnc004-si/strings.xml b/Tethering/res/values-mcc310-mnc004-si/strings.xml
new file mode 100644
index 0000000000..d8301e41c2
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-si/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ටෙදරින් හට අන්තර්ජාලය නැත"
+ "උපාංගවලට සම්බන්ධ විය නොහැකිය"
+ "ටෙදරින් ක්රියාවිරහිත කරන්න"
+ "හොට්ස්පොට් හෝ ටෙදරින් ක්රියාත්මකයි"
+ "රෝමිං අතරතුර අමතර ගාස්තු අදාළ විය හැකිය"
+
diff --git a/Tethering/res/values-mcc310-mnc004-sk/strings.xml b/Tethering/res/values-mcc310-mnc004-sk/strings.xml
new file mode 100644
index 0000000000..bef71363f4
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-sk/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering nemá internetové pripojenie"
+ "Zariadenia sa nemôžu pripojiť"
+ "Vypnúť tethering"
+ "Je zapnutý hotspot alebo tethering"
+ "Počas roamingu vám môžu byť účtované ďalšie poplatky"
+
diff --git a/Tethering/res/values-mcc310-mnc004-sl/strings.xml b/Tethering/res/values-mcc310-mnc004-sl/strings.xml
new file mode 100644
index 0000000000..3202c62e8a
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-sl/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Internetna povezava prek mobilnega telefona ni vzpostavljena"
+ "Napravi se ne moreta povezati"
+ "Izklopi internetno povezavo prek mobilnega telefona"
+ "Dostopna točka ali internetna povezava prek mobilnega telefona je vklopljena"
+ "Med gostovanjem lahko nastanejo dodatni stroški"
+
diff --git a/Tethering/res/values-mcc310-mnc004-sq/strings.xml b/Tethering/res/values-mcc310-mnc004-sq/strings.xml
new file mode 100644
index 0000000000..37f6ad2868
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-sq/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Ndarja e internetit nuk ka internet"
+ "Pajisjet nuk mund të lidhen"
+ "Çaktivizo ndarjen e internetit"
+ "Zona e qasjes për internet ose ndarja e internetit është aktive"
+ "Mund të zbatohen tarifime shtesë kur je në roaming"
+
diff --git a/Tethering/res/values-mcc310-mnc004-sr/strings.xml b/Tethering/res/values-mcc310-mnc004-sr/strings.xml
new file mode 100644
index 0000000000..5566d03ed1
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-sr/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Привезивање нема приступ интернету"
+ "Повезивање уређаја није успело"
+ "Искључи привезивање"
+ "Укључен је хотспот или привезивање"
+ "Можда важе додатни трошкови у ромингу"
+
diff --git a/Tethering/res/values-mcc310-mnc004-sv/strings.xml b/Tethering/res/values-mcc310-mnc004-sv/strings.xml
new file mode 100644
index 0000000000..9765acd0cf
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-sv/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Det finns ingen internetanslutning för internetdelningen"
+ "Enheterna kan inte anslutas"
+ "Inaktivera internetdelning"
+ "Surfzon eller internetdelning har aktiverats"
+ "Ytterligare avgifter kan tillkomma vid roaming"
+
diff --git a/Tethering/res/values-mcc310-mnc004-sw/strings.xml b/Tethering/res/values-mcc310-mnc004-sw/strings.xml
new file mode 100644
index 0000000000..cf850c9cd2
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-sw/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Kipengele cha kusambaza mtandao hakina intaneti"
+ "Imeshindwa kuunganisha vifaa"
+ "Zima kipengele cha kusambaza mtandao"
+ "Umewasha kipengele cha kusambaza mtandao au mtandao pepe"
+ "Huenda ukatozwa gharama za ziada ukitumia mitandao ya ng\'ambo"
+
diff --git a/Tethering/res/values-mcc310-mnc004-ta/strings.xml b/Tethering/res/values-mcc310-mnc004-ta/strings.xml
new file mode 100644
index 0000000000..f4b15aab19
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-ta/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "இணைப்பு முறைக்கு இணைய இணைப்பு இல்லை"
+ "சாதனங்களால் இணைய முடியவில்லை"
+ "இணைப்பு முறையை ஆஃப் செய்"
+ "ஹாட்ஸ்பாட் அல்லது இணைப்பு முறை ஆன் செய்யப்பட்டுள்ளது"
+ "ரோமிங்கின்போது கூடுதல் கட்டணங்கள் விதிக்கப்படக்கூடும்"
+
diff --git a/Tethering/res/values-mcc310-mnc004-te/strings.xml b/Tethering/res/values-mcc310-mnc004-te/strings.xml
new file mode 100644
index 0000000000..937d34d520
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-te/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "టెథరింగ్ చేయడానికి ఇంటర్నెట్ కనెక్షన్ లేదు"
+ "పరికరాలు కనెక్ట్ అవ్వడం లేదు"
+ "టెథరింగ్ను ఆఫ్ చేయండి"
+ "హాట్స్పాట్ లేదా టెథరింగ్ ఆన్లో ఉంది"
+ "రోమింగ్లో ఉన్నప్పుడు అదనపు ఛార్జీలు వర్తించవచ్చు"
+
diff --git a/Tethering/res/values-mcc310-mnc004-th/strings.xml b/Tethering/res/values-mcc310-mnc004-th/strings.xml
new file mode 100644
index 0000000000..f781fae525
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-th/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "การเชื่อมต่ออินเทอร์เน็ตผ่านมือถือไม่มีอินเทอร์เน็ต"
+ "อุปกรณ์เชื่อมต่อไม่ได้"
+ "ปิดการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือ"
+ "ฮอตสปอตหรือการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือเปิดอยู่"
+ "อาจมีค่าใช้จ่ายเพิ่มเติมขณะโรมมิ่ง"
+
diff --git a/Tethering/res/values-mcc310-mnc004-tl/strings.xml b/Tethering/res/values-mcc310-mnc004-tl/strings.xml
new file mode 100644
index 0000000000..8d5d465373
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-tl/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Walang internet ang pag-tether"
+ "Hindi makakonekta ang mga device"
+ "I-off ang pag-tether"
+ "Naka-on ang Hotspot o pag-tether"
+ "Posibleng magkaroon ng mga karagdagang singil habang nagro-roam"
+
diff --git a/Tethering/res/values-mcc310-mnc004-tr/strings.xml b/Tethering/res/values-mcc310-mnc004-tr/strings.xml
new file mode 100644
index 0000000000..80cab33ac0
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-tr/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering\'in internet bağlantısı yok"
+ "Cihazlar bağlanamıyor"
+ "Tethering\'i kapat"
+ "Hotspot veya tethering açık"
+ "Dolaşım sırasında ek ücretler uygulanabilir"
+
diff --git a/Tethering/res/values-mcc310-mnc004-uk/strings.xml b/Tethering/res/values-mcc310-mnc004-uk/strings.xml
new file mode 100644
index 0000000000..c05932a5ae
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-uk/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Телефон, який використовується як модем, не підключений до Інтернету"
+ "Не вдається підключити пристрої"
+ "Вимкнути використання телефона як модема"
+ "Увімкнено точку доступу або використання телефона як модема"
+ "У роумінгу може стягуватися додаткова плата"
+
diff --git a/Tethering/res/values-mcc310-mnc004-ur/strings.xml b/Tethering/res/values-mcc310-mnc004-ur/strings.xml
new file mode 100644
index 0000000000..d820eee8ba
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-ur/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ٹیدرنگ میں انٹرنیٹ نہیں ہے"
+ "آلات منسلک نہیں ہو سکتے"
+ "ٹیدرنگ آف کریں"
+ "ہاٹ اسپاٹ یا ٹیدرنگ آن ہے"
+ "رومنگ کے دوران اضافی چارجز لاگو ہو سکتے ہیں"
+
diff --git a/Tethering/res/values-mcc310-mnc004-uz/strings.xml b/Tethering/res/values-mcc310-mnc004-uz/strings.xml
new file mode 100644
index 0000000000..726148aaee
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-uz/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Modem internetga ulanmagan"
+ "Qurilmalar ulanmadi"
+ "Modem rejimini faolsizlantirish"
+ "Hotspot yoki modem rejimi yoniq"
+ "Rouming vaqtida qoʻshimcha haq olinishi mumkin"
+
diff --git a/Tethering/res/values-mcc310-mnc004-vi/strings.xml b/Tethering/res/values-mcc310-mnc004-vi/strings.xml
new file mode 100644
index 0000000000..b7cb0456b6
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-vi/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Không có Internet để chia sẻ kết Internet"
+ "Các thiết bị không thể kết nối"
+ "Tắt tính năng chia sẻ Internet"
+ "Điểm phát sóng hoặc tính năng chia sẻ Internet đang bật"
+ "Bạn có thể mất thêm phí dữ liệu khi chuyển vùng"
+
diff --git a/Tethering/res/values-mcc310-mnc004-zh-rCN/strings.xml b/Tethering/res/values-mcc310-mnc004-zh-rCN/strings.xml
new file mode 100644
index 0000000000..af91afff9a
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-zh-rCN/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "共享网络未连接到互联网"
+ "设备无法连接"
+ "关闭网络共享"
+ "热点或网络共享已开启"
+ "漫游时可能会产生额外的费用"
+
diff --git a/Tethering/res/values-mcc310-mnc004-zh-rHK/strings.xml b/Tethering/res/values-mcc310-mnc004-zh-rHK/strings.xml
new file mode 100644
index 0000000000..28e6b80c01
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-zh-rHK/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "無法透過網絡共享連線至互聯網"
+ "裝置無法連接"
+ "關閉網絡共享"
+ "熱點或網絡共享已開啟"
+ "漫遊時可能需要支付額外費用"
+
diff --git a/Tethering/res/values-mcc310-mnc004-zh-rTW/strings.xml b/Tethering/res/values-mcc310-mnc004-zh-rTW/strings.xml
new file mode 100644
index 0000000000..528a1e5292
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-zh-rTW/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "無法透過網路共用連上網際網路"
+ "裝置無法連線"
+ "關閉網路共用"
+ "無線基地台或網路共用已開啟"
+ "使用漫遊服務可能須支付額外費用"
+
diff --git a/Tethering/res/values-mcc310-mnc004-zu/strings.xml b/Tethering/res/values-mcc310-mnc004-zu/strings.xml
new file mode 100644
index 0000000000..11eb666219
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004-zu/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Ukusebenzisa ifoni njengemodemu akunayo i-inthanethi"
+ "Amadivayisi awakwazi ukuxhumeka"
+ "Vala ukusebenzisa ifoni njengemodemu"
+ "I-hotspot noma ukusebenzisa ifoni njengemodemu kuvuliwe"
+ "Kungaba nezinkokhelo ezengeziwe uma uzula"
+
diff --git a/Tethering/res/values-mcc310-mnc004/config.xml b/Tethering/res/values-mcc310-mnc004/config.xml
new file mode 100644
index 0000000000..5c5be0466a
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004/config.xml
@@ -0,0 +1,23 @@
+
+
+
+
+ 5000
+
+
+ true
+
\ No newline at end of file
diff --git a/Tethering/res/values-mcc310-mnc004/strings.xml b/Tethering/res/values-mcc310-mnc004/strings.xml
new file mode 100644
index 0000000000..ce9ff60807
--- /dev/null
+++ b/Tethering/res/values-mcc310-mnc004/strings.xml
@@ -0,0 +1,28 @@
+
+
+
+
+ Tethering has no internet
+
+ Devices can\u2019t connect
+
+ Turn off tethering
+
+
+ Hotspot or tethering is on
+
+ Additional charges may apply while roaming
+
diff --git a/Tethering/res/values-mcc311-mnc480-af/strings.xml b/Tethering/res/values-mcc311-mnc480-af/strings.xml
new file mode 100644
index 0000000000..9bfa5317a9
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-af/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Verbinding het nie internet nie"
+ "Toestelle kan nie koppel nie"
+ "Skakel verbinding af"
+ "Warmkol of verbinding is aan"
+ "Bykomende heffings kan geld terwyl jy swerf"
+
diff --git a/Tethering/res/values-mcc311-mnc480-am/strings.xml b/Tethering/res/values-mcc311-mnc480-am/strings.xml
new file mode 100644
index 0000000000..5949dfa776
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-am/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ማስተሳሰር ምንም በይነመረብ የለውም"
+ "መሣሪያዎችን ማገናኘት አይቻልም"
+ "ማስተሳሰርን አጥፋ"
+ "መገናኛ ነጥብ ወይም ማስተሳሰር በርቷል"
+ "በሚያንዣብብበት ጊዜ ተጨማሪ ክፍያዎች ተፈጻሚ ሊሆኑ ይችላሉ"
+
diff --git a/Tethering/res/values-mcc311-mnc480-ar/strings.xml b/Tethering/res/values-mcc311-mnc480-ar/strings.xml
new file mode 100644
index 0000000000..8467f9b1f5
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-ar/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ما مِن اتصال بالإنترنت خلال التوصيل"
+ "تعذّر اتصال الأجهزة"
+ "إيقاف التوصيل"
+ "نقطة الاتصال أو التوصيل مفعّلان"
+ "قد يتم تطبيق رسوم إضافية أثناء التجوال."
+
diff --git a/Tethering/res/values-mcc311-mnc480-as/strings.xml b/Tethering/res/values-mcc311-mnc480-as/strings.xml
new file mode 100644
index 0000000000..9776bd89da
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-as/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "টে\'ডাৰিঙৰ ইণ্টাৰনেট নাই"
+ "ডিভাইচসমূহ সংযোগ কৰিব নোৱাৰি"
+ "টে\'ডাৰিং অফ কৰক"
+ "হটস্পট অথবা টে\'ডাৰিং অন আছে"
+ "ৰ\'মিঙত থাকিলে অতিৰিক্ত মাচুল প্ৰযোজ্য হ’ব পাৰে"
+
diff --git a/Tethering/res/values-mcc311-mnc480-az/strings.xml b/Tethering/res/values-mcc311-mnc480-az/strings.xml
new file mode 100644
index 0000000000..e6d3eaf9f0
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-az/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Modemin internetə girişi yoxdur"
+ "Cihazları qoşmaq mümkün deyil"
+ "Modemi deaktiv edin"
+ "Hotspot və ya modem aktivdir"
+ "Rouminq zamanı əlavə ödənişlər tətbiq edilə bilər"
+
diff --git a/Tethering/res/values-mcc311-mnc480-b+sr+Latn/strings.xml b/Tethering/res/values-mcc311-mnc480-b+sr+Latn/strings.xml
new file mode 100644
index 0000000000..4c8a1df8ee
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-b+sr+Latn/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Privezivanje nema pristup internetu"
+ "Povezivanje uređaja nije uspelo"
+ "Isključi privezivanje"
+ "Uključen je hotspot ili privezivanje"
+ "Možda važe dodatni troškovi u romingu"
+
diff --git a/Tethering/res/values-mcc311-mnc480-be/strings.xml b/Tethering/res/values-mcc311-mnc480-be/strings.xml
new file mode 100644
index 0000000000..edfa41e1ff
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-be/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Рэжым мадэма выкарыстоўваецца без доступу да інтэрнэту"
+ "Не ўдалося падключыць прылады"
+ "Выключыць рэжым мадэма"
+ "Хот-спот або рэжым мадэма ўключаны"
+ "Пры выкарыстанні роўмінгу можа спаганяцца дадатковая плата"
+
diff --git a/Tethering/res/values-mcc311-mnc480-bg/strings.xml b/Tethering/res/values-mcc311-mnc480-bg/strings.xml
new file mode 100644
index 0000000000..f56398196f
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-bg/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Тетърингът няма връзка с интернет"
+ "Устройствата не могат да установят връзка"
+ "Изключване на тетъринга"
+ "Точката за достъп или тетърингът са включени"
+ "Възможно е да ви бъдат начислени допълнителни такси при роуминг"
+
diff --git a/Tethering/res/values-mcc311-mnc480-bn/strings.xml b/Tethering/res/values-mcc311-mnc480-bn/strings.xml
new file mode 100644
index 0000000000..d8ecd2e988
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-bn/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "টিথারিং করার জন্য কোনও ইন্টারনেট কানেকশন নেই"
+ "ডিভাইস কানেক্ট করতে পারছে না"
+ "টিথারিং বন্ধ করুন"
+ "হটস্পট বা টিথারিং চালু আছে"
+ "রোমিংয়ের সময় অতিরিক্ত চার্জ করা হতে পারে"
+
diff --git a/Tethering/res/values-mcc311-mnc480-bs/strings.xml b/Tethering/res/values-mcc311-mnc480-bs/strings.xml
new file mode 100644
index 0000000000..b85fd5e285
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-bs/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Povezivanje putem mobitela nema internet"
+ "Uređaji se ne mogu povezati"
+ "Isključi povezivanje putem mobitela"
+ "Pristupna tačka ili povezivanje putem mobitela je uključeno"
+ "Mogu nastati dodatni troškovi u romingu"
+
diff --git a/Tethering/res/values-mcc311-mnc480-ca/strings.xml b/Tethering/res/values-mcc311-mnc480-ca/strings.xml
new file mode 100644
index 0000000000..a3572151be
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-ca/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "La compartició de xarxa no té accés a Internet"
+ "No es poden connectar els dispositius"
+ "Desactiva la compartició de xarxa"
+ "S\'ha activat el punt d\'accés Wi‑Fi o la compartició de xarxa"
+ "És possible que s\'apliquin costos addicionals en itinerància"
+
diff --git a/Tethering/res/values-mcc311-mnc480-cs/strings.xml b/Tethering/res/values-mcc311-mnc480-cs/strings.xml
new file mode 100644
index 0000000000..91196be9e5
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-cs/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering nemá připojení k internetu"
+ "Zařízení se nemůžou připojit"
+ "Vypnout tethering"
+ "Je zapnutý hotspot nebo tethering"
+ "Při roamingu mohou být účtovány dodatečné poplatky"
+
diff --git a/Tethering/res/values-mcc311-mnc480-da/strings.xml b/Tethering/res/values-mcc311-mnc480-da/strings.xml
new file mode 100644
index 0000000000..196890011d
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-da/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Netdeling har ingen internetforbindelse"
+ "Enheder kan ikke oprette forbindelse"
+ "Deaktiver netdeling"
+ "Hotspot eller netdeling er aktiveret"
+ "Der opkræves muligvis yderligere gebyrer ved roaming"
+
diff --git a/Tethering/res/values-mcc311-mnc480-de/strings.xml b/Tethering/res/values-mcc311-mnc480-de/strings.xml
new file mode 100644
index 0000000000..eb3f8c52c0
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-de/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering hat keinen Internetzugriff"
+ "Geräte können sich nicht verbinden"
+ "Tethering deaktivieren"
+ "Hotspot oder Tethering ist aktiviert"
+ "Für das Roaming können zusätzliche Gebühren anfallen"
+
diff --git a/Tethering/res/values-mcc311-mnc480-el/strings.xml b/Tethering/res/values-mcc311-mnc480-el/strings.xml
new file mode 100644
index 0000000000..56c3d81b63
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-el/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Η σύνδεση δεν έχει πρόσβαση στο διαδίκτυο"
+ "Δεν είναι δυνατή η σύνδεση των συσκευών"
+ "Απενεργοποιήστε τη σύνδεση"
+ "Ενεργό σημείο πρόσβασης Wi-Fi ή ενεργή σύνδεση"
+ "Ενδέχεται να ισχύουν επιπλέον χρεώσεις κατά την περιαγωγή."
+
diff --git a/Tethering/res/values-mcc311-mnc480-en-rAU/strings.xml b/Tethering/res/values-mcc311-mnc480-en-rAU/strings.xml
new file mode 100644
index 0000000000..dd1a1971cd
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-en-rAU/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering has no Internet"
+ "Devices can’t connect"
+ "Turn off tethering"
+ "Hotspot or tethering is on"
+ "Additional charges may apply while roaming"
+
diff --git a/Tethering/res/values-mcc311-mnc480-en-rCA/strings.xml b/Tethering/res/values-mcc311-mnc480-en-rCA/strings.xml
new file mode 100644
index 0000000000..dd1a1971cd
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-en-rCA/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering has no Internet"
+ "Devices can’t connect"
+ "Turn off tethering"
+ "Hotspot or tethering is on"
+ "Additional charges may apply while roaming"
+
diff --git a/Tethering/res/values-mcc311-mnc480-en-rGB/strings.xml b/Tethering/res/values-mcc311-mnc480-en-rGB/strings.xml
new file mode 100644
index 0000000000..dd1a1971cd
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-en-rGB/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering has no Internet"
+ "Devices can’t connect"
+ "Turn off tethering"
+ "Hotspot or tethering is on"
+ "Additional charges may apply while roaming"
+
diff --git a/Tethering/res/values-mcc311-mnc480-en-rIN/strings.xml b/Tethering/res/values-mcc311-mnc480-en-rIN/strings.xml
new file mode 100644
index 0000000000..dd1a1971cd
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-en-rIN/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering has no Internet"
+ "Devices can’t connect"
+ "Turn off tethering"
+ "Hotspot or tethering is on"
+ "Additional charges may apply while roaming"
+
diff --git a/Tethering/res/values-mcc311-mnc480-en-rXC/strings.xml b/Tethering/res/values-mcc311-mnc480-en-rXC/strings.xml
new file mode 100644
index 0000000000..d3347aae20
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-en-rXC/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering has no internet"
+ "Devices can’t connect"
+ "Turn off tethering"
+ "Hotspot or tethering is on"
+ "Additional charges may apply while roaming"
+
diff --git a/Tethering/res/values-mcc311-mnc480-es-rUS/strings.xml b/Tethering/res/values-mcc311-mnc480-es-rUS/strings.xml
new file mode 100644
index 0000000000..2f0504f07d
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-es-rUS/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "La conexión mediante dispositivo móvil no tiene Internet"
+ "No se pueden conectar los dispositivos"
+ "Desactivar conexión mediante dispositivo móvil"
+ "Se activó el hotspot o la conexión mediante dispositivo móvil"
+ "Es posible que se apliquen cargos adicionales por roaming"
+
diff --git a/Tethering/res/values-mcc311-mnc480-es/strings.xml b/Tethering/res/values-mcc311-mnc480-es/strings.xml
new file mode 100644
index 0000000000..2d8f882425
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-es/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "La conexión no se puede compartir, porque no hay acceso a Internet"
+ "Los dispositivos no se pueden conectar"
+ "Desactivar conexión compartida"
+ "Punto de acceso o conexión compartida activados"
+ "Puede que se apliquen cargos adicionales en itinerancia"
+
diff --git a/Tethering/res/values-mcc311-mnc480-et/strings.xml b/Tethering/res/values-mcc311-mnc480-et/strings.xml
new file mode 100644
index 0000000000..8493c47071
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-et/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Jagamisel puudub internetiühendus"
+ "Seadmed ei saa ühendust luua"
+ "Lülita jagamine välja"
+ "Kuumkoht või jagamine on sisse lülitatud"
+ "Rändluse kasutamisega võivad kaasneda lisatasud"
+
diff --git a/Tethering/res/values-mcc311-mnc480-eu/strings.xml b/Tethering/res/values-mcc311-mnc480-eu/strings.xml
new file mode 100644
index 0000000000..33bccab3e8
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-eu/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Konexioa partekatzeko aukerak ez du Interneteko konexiorik"
+ "Ezin dira konektatu gailuak"
+ "Desaktibatu konexioa partekatzeko aukera"
+ "Wifi-gunea edo konexioa partekatzeko aukera aktibatuta dago"
+ "Baliteke kostu gehigarriak ordaindu behar izatea ibiltaritzan"
+
diff --git a/Tethering/res/values-mcc311-mnc480-fa/strings.xml b/Tethering/res/values-mcc311-mnc480-fa/strings.xml
new file mode 100644
index 0000000000..cf8a0cc277
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-fa/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "«اشتراکگذاری اینترنت» به اینترنت دسترسی ندارد"
+ "دستگاهها متصل نمیشوند"
+ "خاموش کردن «اشتراکگذاری اینترنت»"
+ "«نقطه اتصال» یا «اشتراکگذاری اینترنت» روشن است"
+ "ممکن است درحین فراگردی تغییرات دیگر اعمال شود"
+
diff --git a/Tethering/res/values-mcc311-mnc480-fi/strings.xml b/Tethering/res/values-mcc311-mnc480-fi/strings.xml
new file mode 100644
index 0000000000..6a3ab806db
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-fi/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Ei jaettavaa internetyhteyttä"
+ "Laitteet eivät voi muodostaa yhteyttä"
+ "Laita yhteyden jakaminen pois päältä"
+ "Hotspot tai yhteyden jakaminen on päällä"
+ "Roaming voi aiheuttaa lisämaksuja"
+
diff --git a/Tethering/res/values-mcc311-mnc480-fr-rCA/strings.xml b/Tethering/res/values-mcc311-mnc480-fr-rCA/strings.xml
new file mode 100644
index 0000000000..ffb9bf6047
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-fr-rCA/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Le partage de connexion n\'est pas connecté à Internet"
+ "Impossible de connecter les appareils"
+ "Désactiver le partage de connexion"
+ "Le point d\'accès ou le partage de connexion est activé"
+ "En itinérance, des frais supplémentaires peuvent s\'appliquer"
+
diff --git a/Tethering/res/values-mcc311-mnc480-fr/strings.xml b/Tethering/res/values-mcc311-mnc480-fr/strings.xml
new file mode 100644
index 0000000000..768bce3f0a
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-fr/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Aucune connexion à Internet n\'est disponible pour le partage de connexion"
+ "Impossible de connecter les appareils"
+ "Désactiver le partage de connexion"
+ "Le point d\'accès ou le partage de connexion est activé"
+ "En itinérance, des frais supplémentaires peuvent s\'appliquer"
+
diff --git a/Tethering/res/values-mcc311-mnc480-gl/strings.xml b/Tethering/res/values-mcc311-mnc480-gl/strings.xml
new file mode 100644
index 0000000000..0c4195a7ca
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-gl/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "A conexión compartida non ten Internet"
+ "Non se puideron conectar os dispositivos"
+ "Desactivar conexión compartida"
+ "Está activada a zona wifi ou a conexión compartida"
+ "Pódense aplicar cargos adicionais en itinerancia"
+
diff --git a/Tethering/res/values-mcc311-mnc480-gu/strings.xml b/Tethering/res/values-mcc311-mnc480-gu/strings.xml
new file mode 100644
index 0000000000..e9d33a7db2
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-gu/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ઇન્ટરનેટ શેર કરવાની સુવિધામાં ઇન્ટરનેટ નથી"
+ "ડિવાઇસ કનેક્ટ કરી શકાતા નથી"
+ "ઇન્ટરનેટ શેર કરવાની સુવિધા બંધ કરો"
+ "હૉટસ્પૉટ અથવા ઇન્ટરનેટ શેર કરવાની સુવિધા ચાલુ છે"
+ "રોમિંગમાં વધારાના શુલ્ક લાગી શકે છે"
+
diff --git a/Tethering/res/values-mcc311-mnc480-hi/strings.xml b/Tethering/res/values-mcc311-mnc480-hi/strings.xml
new file mode 100644
index 0000000000..aa418ac5d3
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-hi/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "टेदरिंग से इंटरनेट नहीं चल रहा"
+ "डिवाइस कनेक्ट नहीं हो पा रहे"
+ "टेदरिंग बंद करें"
+ "हॉटस्पॉट या टेदरिंग चालू है"
+ "रोमिंग के दौरान अतिरिक्त शुल्क लग सकता है"
+
diff --git a/Tethering/res/values-mcc311-mnc480-hr/strings.xml b/Tethering/res/values-mcc311-mnc480-hr/strings.xml
new file mode 100644
index 0000000000..51c524afbc
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-hr/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Modemsko povezivanje nema internet"
+ "Uređaji se ne mogu povezati"
+ "Isključivanje modemskog povezivanja"
+ "Uključena je žarišna točka ili modemsko povezivanje"
+ "U roamingu su mogući dodatni troškovi"
+
diff --git a/Tethering/res/values-mcc311-mnc480-hu/strings.xml b/Tethering/res/values-mcc311-mnc480-hu/strings.xml
new file mode 100644
index 0000000000..164e45edd1
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-hu/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Nincs internetkapcsolat az internet megosztásához"
+ "Az eszközök nem tudnak csatlakozni"
+ "Internetmegosztás kikapcsolása"
+ "A hotspot vagy az internetmegosztás be van kapcsolva"
+ "Roaming során további díjak léphetnek fel"
+
diff --git a/Tethering/res/values-mcc311-mnc480-hy/strings.xml b/Tethering/res/values-mcc311-mnc480-hy/strings.xml
new file mode 100644
index 0000000000..e76c0a4c80
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-hy/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Մոդեմի ռեժիմի կապը բացակայում է"
+ "Չհաջողվեց միացնել սարքը"
+ "Անջատել մոդեմի ռեժիմը"
+ "Թեժ կետը կամ մոդեմի ռեժիմը միացված է"
+ "Ռոումինգում կարող են լրացուցիչ վճարներ գանձվել"
+
diff --git a/Tethering/res/values-mcc311-mnc480-in/strings.xml b/Tethering/res/values-mcc311-mnc480-in/strings.xml
new file mode 100644
index 0000000000..2b817f8abd
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-in/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tidak ada koneksi internet di tethering"
+ "Perangkat tidak dapat terhubung"
+ "Nonaktifkan tethering"
+ "Hotspot atau tethering aktif"
+ "Biaya tambahan mungkin berlaku saat roaming"
+
diff --git a/Tethering/res/values-mcc311-mnc480-is/strings.xml b/Tethering/res/values-mcc311-mnc480-is/strings.xml
new file mode 100644
index 0000000000..a338d9c7ca
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-is/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tjóðrun er ekki með internettengingu"
+ "Tæki geta ekki tengst"
+ "Slökkva á tjóðrun"
+ "Kveikt er á heitum reit eða tjóðrun"
+ "Viðbótargjöld kunna að eiga við í reiki"
+
diff --git a/Tethering/res/values-mcc311-mnc480-it/strings.xml b/Tethering/res/values-mcc311-mnc480-it/strings.xml
new file mode 100644
index 0000000000..77769c2ac5
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-it/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Nessuna connessione a Internet per il tethering"
+ "Impossibile connettere i dispositivi"
+ "Disattiva il tethering"
+ "Hotspot o tethering attivi"
+ "Potrebbero essere applicati costi aggiuntivi durante il roaming"
+
diff --git a/Tethering/res/values-mcc311-mnc480-iw/strings.xml b/Tethering/res/values-mcc311-mnc480-iw/strings.xml
new file mode 100644
index 0000000000..5267b51264
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-iw/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "אי אפשר להפעיל את תכונת שיתוף האינטרנט בין מכשירים כי אין חיבור לאינטרנט"
+ "למכשירים אין אפשרות להתחבר"
+ "השבתה של שיתוף האינטרנט בין מכשירים"
+ "תכונת הנקודה לשיתוף אינטרנט או תכונת שיתוף האינטרנט בין מכשירים פועלת"
+ "ייתכנו חיובים נוספים בעת נדידה"
+
diff --git a/Tethering/res/values-mcc311-mnc480-ja/strings.xml b/Tethering/res/values-mcc311-mnc480-ja/strings.xml
new file mode 100644
index 0000000000..66a9a6dd35
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-ja/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "テザリングがインターネットに接続されていません"
+ "デバイスを接続できません"
+ "テザリングを OFF にする"
+ "アクセス ポイントまたはテザリングが ON です"
+ "ローミング時に追加料金が発生することがあります"
+
diff --git a/Tethering/res/values-mcc311-mnc480-ka/strings.xml b/Tethering/res/values-mcc311-mnc480-ka/strings.xml
new file mode 100644
index 0000000000..d8ad880849
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-ka/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ტეტერინგს არ აქვს ინტერნეტზე წვდომა"
+ "მოწყობილობები ვერ ახერხებენ დაკავშირებას"
+ "ტეტერინგის გამორთვა"
+ "ჩართულია უსადენო ქსელი ან ტეტერინგი"
+ "როუმინგის გამოყენებისას შეიძლება ჩამოგეჭრათ დამატებითი საფასური"
+
diff --git a/Tethering/res/values-mcc311-mnc480-kk/strings.xml b/Tethering/res/values-mcc311-mnc480-kk/strings.xml
new file mode 100644
index 0000000000..1ddd6b419b
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-kk/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Тетеринг режимі интернет байланысынсыз пайдаланылуда"
+ "Құрылғыларды байланыстыру мүмкін емес"
+ "Тетерингіні өшіру"
+ "Хотспот немесе тетеринг қосулы"
+ "Роуминг кезінде қосымша ақы алынуы мүмкін."
+
diff --git a/Tethering/res/values-mcc311-mnc480-km/strings.xml b/Tethering/res/values-mcc311-mnc480-km/strings.xml
new file mode 100644
index 0000000000..cf5a1379cc
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-km/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ការភ្ជាប់មិនមានអ៊ីនធឺណិតទេ"
+ "មិនអាចភ្ជាប់ឧបករណ៍បានទេ"
+ "បិទការភ្ជាប់"
+ "ហតស្ប៉ត ឬការភ្ជាប់ត្រូវបានបើក"
+ "អាចមានការគិតថ្លៃបន្ថែម នៅពេលរ៉ូមីង"
+
diff --git a/Tethering/res/values-mcc311-mnc480-kn/strings.xml b/Tethering/res/values-mcc311-mnc480-kn/strings.xml
new file mode 100644
index 0000000000..68ae68bc19
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-kn/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ಟೆಥರಿಂಗ್ ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಕನೆಕ್ಷನ್ ಹೊಂದಿಲ್ಲ"
+ "ಸಾಧನಗಳನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"
+ "ಟೆಥರಿಂಗ್ ಆಫ್ ಮಾಡಿ"
+ "ಹಾಟ್ಸ್ಪಾಟ್ ಅಥವಾ ಟೆಥರಿಂಗ್ ಆನ್ ಆಗಿದೆ"
+ "ರೋಮಿಂಗ್ನಲ್ಲಿರುವಾಗ ಹೆಚ್ಚುವರಿ ಶುಲ್ಕಗಳು ಅನ್ವಯವಾಗಬಹುದು"
+
diff --git a/Tethering/res/values-mcc311-mnc480-ko/strings.xml b/Tethering/res/values-mcc311-mnc480-ko/strings.xml
new file mode 100644
index 0000000000..17185ba2d0
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-ko/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "테더링으로 인터넷을 사용할 수 없음"
+ "기기에서 연결할 수 없음"
+ "테더링 사용 중지"
+ "핫스팟 또는 테더링 켜짐"
+ "로밍 중에는 추가 요금이 발생할 수 있습니다."
+
diff --git a/Tethering/res/values-mcc311-mnc480-ky/strings.xml b/Tethering/res/values-mcc311-mnc480-ky/strings.xml
new file mode 100644
index 0000000000..6a9fb9810c
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-ky/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Модем режими Интернети жок колдонулууда"
+ "Түзмөктөр туташпай жатат"
+ "Модем режимин өчүрүү"
+ "Байланыш түйүнү же модем режими күйүк"
+ "Роумингде кошумча акы алынышы мүмкүн"
+
diff --git a/Tethering/res/values-mcc311-mnc480-lo/strings.xml b/Tethering/res/values-mcc311-mnc480-lo/strings.xml
new file mode 100644
index 0000000000..bcc4b57626
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-lo/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ການປ່ອຍສັນຍານບໍ່ມີອິນເຕີເນັດ"
+ "ອຸປະກອນບໍ່ສາມາດເຊື່ອມຕໍ່ໄດ້"
+ "ປິດການປ່ອຍສັນຍານ"
+ "ເປີດໃຊ້ຮັອດສະປອດ ຫຼື ການປ່ອຍສັນຍານຢູ່"
+ "ອາດມີຄ່າໃຊ້ຈ່າຍເພີ່ມເຕີມໃນລະຫວ່າງການໂຣມມິງ"
+
diff --git a/Tethering/res/values-mcc311-mnc480-lt/strings.xml b/Tethering/res/values-mcc311-mnc480-lt/strings.xml
new file mode 100644
index 0000000000..011c2c11fb
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-lt/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Nėra įrenginio kaip modemo naudojimo interneto ryšio"
+ "Nepavyko susieti įrenginių"
+ "Išjungti įrenginio kaip modemo naudojimą"
+ "Įjungtas viešosios interneto prieigos taškas arba įrenginio kaip modemo naudojimas"
+ "Veikiant tarptinkliniam ryšiui gali būti taikomi papildomi mokesčiai"
+
diff --git a/Tethering/res/values-mcc311-mnc480-lv/strings.xml b/Tethering/res/values-mcc311-mnc480-lv/strings.xml
new file mode 100644
index 0000000000..5cb2f3b7aa
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-lv/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Piesaistei nav interneta savienojuma"
+ "Nevar savienot ierīces"
+ "Izslēgt piesaisti"
+ "Ir ieslēgts tīklājs vai piesaiste"
+ "Viesabonēšanas laikā var tikt piemērota papildu samaksa"
+
diff --git a/Tethering/res/values-mcc311-mnc480-mk/strings.xml b/Tethering/res/values-mcc311-mnc480-mk/strings.xml
new file mode 100644
index 0000000000..4cbfd887c5
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-mk/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Нема интернет преку мобилен"
+ "Уредите не може да се поврзат"
+ "Исклучи интернет преку мобилен"
+ "Точката на пристап или интернетот преку мобилен е вклучен"
+ "При роаминг може да се наплатат дополнителни трошоци"
+
diff --git a/Tethering/res/values-mcc311-mnc480-ml/strings.xml b/Tethering/res/values-mcc311-mnc480-ml/strings.xml
new file mode 100644
index 0000000000..9cf4eaf34a
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-ml/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ടെതറിംഗിന് ഇന്റർനെറ്റ് ഇല്ല"
+ "ഉപകരണങ്ങൾ കണക്റ്റ് ചെയ്യാനാവില്ല"
+ "ടെതറിംഗ് ഓഫാക്കുക"
+ "ഹോട്ട്സ്പോട്ട് അല്ലെങ്കിൽ ടെതറിംഗ് ഓണാണ്"
+ "റോമിംഗ് ചെയ്യുമ്പോൾ അധിക നിരക്കുകൾ ബാധകമായേക്കാം"
+
diff --git a/Tethering/res/values-mcc311-mnc480-mn/strings.xml b/Tethering/res/values-mcc311-mnc480-mn/strings.xml
new file mode 100644
index 0000000000..47c82c14d9
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-mn/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Модемд интернэт алга байна"
+ "Төхөөрөмжүүд холбогдох боломжгүй байна"
+ "Модем болгохыг унтраах"
+ "Сүлжээний цэг эсвэл модем болгох асаалттай байна"
+ "Роумингийн үеэр нэмэлт төлбөр нэхэмжилж болзошгүй"
+
diff --git a/Tethering/res/values-mcc311-mnc480-mr/strings.xml b/Tethering/res/values-mcc311-mnc480-mr/strings.xml
new file mode 100644
index 0000000000..ad9e809ab2
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-mr/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "टेदरिंगला इंटरनेट नाही"
+ "डिव्हाइस कनेक्ट होऊ शकत नाहीत"
+ "टेदरिंग बंद करा"
+ "हॉटस्पॉट किंवा टेदरिंग सुरू आहे"
+ "रोमिंगदरम्यान अतिरिक्त शुल्क लागू होऊ शकतात"
+
diff --git a/Tethering/res/values-mcc311-mnc480-ms/strings.xml b/Tethering/res/values-mcc311-mnc480-ms/strings.xml
new file mode 100644
index 0000000000..e708cb8717
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-ms/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Penambatan tiada Internet"
+ "Peranti tidak dapat disambungkan"
+ "Matikan penambatan"
+ "Tempat liputan atau penambatan dihidupkan"
+ "Caj tambahan mungkin digunakan semasa perayauan"
+
diff --git a/Tethering/res/values-mcc311-mnc480-my/strings.xml b/Tethering/res/values-mcc311-mnc480-my/strings.xml
new file mode 100644
index 0000000000..ba5462250b
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-my/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်းတွင် အင်တာနက် မရှိပါ"
+ "စက်များ ချိတ်ဆက်၍ မရပါ"
+ "မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်း ပိတ်ရန်"
+ "ဟော့စပေါ့ (သို့) မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်း ဖွင့်ထားသည်"
+ "ပြင်ပကွန်ရက်နှင့် ချိတ်ဆက်သည့်အခါ နောက်ထပ်ကျသင့်မှုများ ရှိနိုင်သည်"
+
diff --git a/Tethering/res/values-mcc311-mnc480-nb/strings.xml b/Tethering/res/values-mcc311-mnc480-nb/strings.xml
new file mode 100644
index 0000000000..57db484a25
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-nb/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Internettdeling har ikke internettilgang"
+ "Enhetene kan ikke koble til"
+ "Slå av internettdeling"
+ "Wi-Fi-sone eller internettdeling er på"
+ "Ytterligere kostnader kan påløpe under roaming"
+
diff --git a/Tethering/res/values-mcc311-mnc480-ne/strings.xml b/Tethering/res/values-mcc311-mnc480-ne/strings.xml
new file mode 100644
index 0000000000..1503244f50
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-ne/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "टेदरिङमार्फत इन्टरनेट कनेक्सन प्राप्त हुन सकेन"
+ "यन्त्रहरू कनेक्ट गर्न सकिएन"
+ "टेदरिङ निष्क्रिय पार्नुहोस्"
+ "हटस्पट वा टेदरिङ सक्रिय छ"
+ "रोमिङ सेवा प्रयोग गर्दा अतिरिक्त शुल्क लाग्न सक्छ"
+
diff --git a/Tethering/res/values-mcc311-mnc480-nl/strings.xml b/Tethering/res/values-mcc311-mnc480-nl/strings.xml
new file mode 100644
index 0000000000..b08133f4e5
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-nl/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering heeft geen internet"
+ "Apparaten kunnen niet worden verbonden"
+ "Tethering uitschakelen"
+ "Hotspot of tethering is ingeschakeld"
+ "Er kunnen extra kosten voor roaming in rekening worden gebracht."
+
diff --git a/Tethering/res/values-mcc311-mnc480-or/strings.xml b/Tethering/res/values-mcc311-mnc480-or/strings.xml
new file mode 100644
index 0000000000..1ad4ca354a
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-or/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ଟିଥରିଂ ପାଇଁ କୌଣସି ଇଣ୍ଟର୍ନେଟ୍ ସଂଯୋଗ ନାହିଁ"
+ "ଡିଭାଇସଗୁଡ଼ିକ ସଂଯୋଗ କରାଯାଇପାରିବ ନାହିଁ"
+ "ଟିଥରିଂ ବନ୍ଦ କରନ୍ତୁ"
+ "ହଟସ୍ପଟ୍ କିମ୍ବା ଟିଥରିଂ ଚାଲୁ ଅଛି"
+ "ରୋମିଂରେ ଥିବା ସମୟରେ ଅତିରିକ୍ତ ଶୁଳ୍କ ଲାଗୁ ହୋଇପାରେ"
+
diff --git a/Tethering/res/values-mcc311-mnc480-pa/strings.xml b/Tethering/res/values-mcc311-mnc480-pa/strings.xml
new file mode 100644
index 0000000000..88def563d8
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-pa/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ਟੈਦਰਿੰਗ ਕੋਲ ਇੰਟਰਨੈੱਟ ਪਹੁੰਚ ਨਹੀਂ ਹੈ"
+ "ਡੀਵਾਈਸ ਕਨੈਕਟ ਨਹੀਂ ਕੀਤੇ ਜਾ ਸਕਦੇ"
+ "ਟੈਦਰਿੰਗ ਬੰਦ ਕਰੋ"
+ "ਹੌਟਸਪੌਟ ਜਾਂ ਟੈਦਰਿੰਗ ਚਾਲੂ ਹੈ"
+ "ਰੋਮਿੰਗ ਦੌਰਾਨ ਵਧੀਕ ਖਰਚੇ ਲਾਗੂ ਹੋ ਸਕਦੇ ਹਨ"
+
diff --git a/Tethering/res/values-mcc311-mnc480-pl/strings.xml b/Tethering/res/values-mcc311-mnc480-pl/strings.xml
new file mode 100644
index 0000000000..f9890abdc2
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-pl/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering nie ma internetu"
+ "Urządzenia nie mogą się połączyć"
+ "Wyłącz tethering"
+ "Hotspot lub tethering jest włączony"
+ "Podczas korzystania z roamingu mogą zostać naliczone dodatkowe opłaty"
+
diff --git a/Tethering/res/values-mcc311-mnc480-pt-rBR/strings.xml b/Tethering/res/values-mcc311-mnc480-pt-rBR/strings.xml
new file mode 100644
index 0000000000..ce3b88479f
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-pt-rBR/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "O tethering não tem Internet"
+ "Não é possível conectar os dispositivos"
+ "Desativar o tethering"
+ "Ponto de acesso ou tethering ativado"
+ "Pode haver cobranças extras durante o roaming"
+
diff --git a/Tethering/res/values-mcc311-mnc480-pt-rPT/strings.xml b/Tethering/res/values-mcc311-mnc480-pt-rPT/strings.xml
new file mode 100644
index 0000000000..7e883ea576
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-pt-rPT/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "A ligação (à Internet) via telemóvel não tem Internet"
+ "Não é possível ligar os dispositivos"
+ "Desativar ligação (à Internet) via telemóvel"
+ "A zona Wi-Fi ou a ligação (à Internet) via telemóvel está ativada"
+ "Podem aplicar-se custos adicionais em roaming."
+
diff --git a/Tethering/res/values-mcc311-mnc480-pt/strings.xml b/Tethering/res/values-mcc311-mnc480-pt/strings.xml
new file mode 100644
index 0000000000..ce3b88479f
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-pt/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "O tethering não tem Internet"
+ "Não é possível conectar os dispositivos"
+ "Desativar o tethering"
+ "Ponto de acesso ou tethering ativado"
+ "Pode haver cobranças extras durante o roaming"
+
diff --git a/Tethering/res/values-mcc311-mnc480-ro/strings.xml b/Tethering/res/values-mcc311-mnc480-ro/strings.xml
new file mode 100644
index 0000000000..1009417316
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-ro/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Procesul de tethering nu are internet"
+ "Dispozitivele nu se pot conecta"
+ "Dezactivați procesul de tethering"
+ "S-a activat hotspotul sau tethering"
+ "Se pot aplica taxe suplimentare pentru roaming"
+
diff --git a/Tethering/res/values-mcc311-mnc480-ru/strings.xml b/Tethering/res/values-mcc311-mnc480-ru/strings.xml
new file mode 100644
index 0000000000..88683bed95
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-ru/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Режим модема используется без доступа к Интернету"
+ "Невозможно подключить устройства."
+ "Отключить режим модема"
+ "Включены точка доступа или режим модема"
+ "За использование услуг связи в роуминге может взиматься дополнительная плата."
+
diff --git a/Tethering/res/values-mcc311-mnc480-si/strings.xml b/Tethering/res/values-mcc311-mnc480-si/strings.xml
new file mode 100644
index 0000000000..176bcdb797
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-si/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ටෙදරින් හට අන්තර්ජාලය නැත"
+ "උපාංගවලට සම්බන්ධ විය නොහැකිය"
+ "ටෙදරින් ක්රියාවිරහිත කරන්න"
+ "හොට්ස්පොට් හෝ ටෙදරින් ක්රියාත්මකයි"
+ "රෝමිං අතරතුර අමතර ගාස්තු අදාළ විය හැකිය"
+
diff --git a/Tethering/res/values-mcc311-mnc480-sk/strings.xml b/Tethering/res/values-mcc311-mnc480-sk/strings.xml
new file mode 100644
index 0000000000..b9e2127fa8
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-sk/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering nemá internetové pripojenie"
+ "Zariadenia sa nemôžu pripojiť"
+ "Vypnúť tethering"
+ "Je zapnutý hotspot alebo tethering"
+ "Počas roamingu vám môžu byť účtované ďalšie poplatky"
+
diff --git a/Tethering/res/values-mcc311-mnc480-sl/strings.xml b/Tethering/res/values-mcc311-mnc480-sl/strings.xml
new file mode 100644
index 0000000000..e8140e686a
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-sl/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Internetna povezava prek mobilnega telefona ni vzpostavljena"
+ "Napravi se ne moreta povezati"
+ "Izklopi internetno povezavo prek mobilnega telefona"
+ "Dostopna točka ali internetna povezava prek mobilnega telefona je vklopljena"
+ "Med gostovanjem lahko nastanejo dodatni stroški"
+
diff --git a/Tethering/res/values-mcc311-mnc480-sq/strings.xml b/Tethering/res/values-mcc311-mnc480-sq/strings.xml
new file mode 100644
index 0000000000..61e698d6e8
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-sq/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Ndarja e internetit nuk ka internet"
+ "Pajisjet nuk mund të lidhen"
+ "Çaktivizo ndarjen e internetit"
+ "Zona e qasjes për internet ose ndarja e internetit është aktive"
+ "Mund të zbatohen tarifime shtesë kur je në roaming"
+
diff --git a/Tethering/res/values-mcc311-mnc480-sr/strings.xml b/Tethering/res/values-mcc311-mnc480-sr/strings.xml
new file mode 100644
index 0000000000..b4c411c354
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-sr/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Привезивање нема приступ интернету"
+ "Повезивање уређаја није успело"
+ "Искључи привезивање"
+ "Укључен је хотспот или привезивање"
+ "Можда важе додатни трошкови у ромингу"
+
diff --git a/Tethering/res/values-mcc311-mnc480-sv/strings.xml b/Tethering/res/values-mcc311-mnc480-sv/strings.xml
new file mode 100644
index 0000000000..4f543e47b9
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-sv/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Det finns ingen internetanslutning för internetdelningen"
+ "Enheterna kan inte anslutas"
+ "Inaktivera internetdelning"
+ "Surfzon eller internetdelning har aktiverats"
+ "Ytterligare avgifter kan tillkomma vid roaming"
+
diff --git a/Tethering/res/values-mcc311-mnc480-sw/strings.xml b/Tethering/res/values-mcc311-mnc480-sw/strings.xml
new file mode 100644
index 0000000000..ac347ab485
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-sw/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Kipengele cha kusambaza mtandao hakina intaneti"
+ "Imeshindwa kuunganisha vifaa"
+ "Zima kipengele cha kusambaza mtandao"
+ "Umewasha kipengele cha kusambaza mtandao au mtandao pepe"
+ "Huenda ukatozwa gharama za ziada ukitumia mitandao ya ng\'ambo"
+
diff --git a/Tethering/res/values-mcc311-mnc480-ta/strings.xml b/Tethering/res/values-mcc311-mnc480-ta/strings.xml
new file mode 100644
index 0000000000..2ea2467e58
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-ta/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "இணைப்பு முறைக்கு இணைய இணைப்பு இல்லை"
+ "சாதனங்களால் இணைய முடியவில்லை"
+ "இணைப்பு முறையை ஆஃப் செய்"
+ "ஹாட்ஸ்பாட் அல்லது இணைப்பு முறை ஆன் செய்யப்பட்டுள்ளது"
+ "ரோமிங்கின்போது கூடுதல் கட்டணங்கள் விதிக்கப்படக்கூடும்"
+
diff --git a/Tethering/res/values-mcc311-mnc480-te/strings.xml b/Tethering/res/values-mcc311-mnc480-te/strings.xml
new file mode 100644
index 0000000000..9360297dd8
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-te/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "టెథరింగ్ చేయడానికి ఇంటర్నెట్ కనెక్షన్ లేదు"
+ "పరికరాలు కనెక్ట్ అవ్వడం లేదు"
+ "టెథరింగ్ను ఆఫ్ చేయండి"
+ "హాట్స్పాట్ లేదా టెథరింగ్ ఆన్లో ఉంది"
+ "రోమింగ్లో ఉన్నప్పుడు అదనపు ఛార్జీలు వర్తించవచ్చు"
+
diff --git a/Tethering/res/values-mcc311-mnc480-th/strings.xml b/Tethering/res/values-mcc311-mnc480-th/strings.xml
new file mode 100644
index 0000000000..9c4d7e08f2
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-th/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "การเชื่อมต่ออินเทอร์เน็ตผ่านมือถือไม่มีอินเทอร์เน็ต"
+ "อุปกรณ์เชื่อมต่อไม่ได้"
+ "ปิดการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือ"
+ "ฮอตสปอตหรือการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือเปิดอยู่"
+ "อาจมีค่าใช้จ่ายเพิ่มเติมขณะโรมมิ่ง"
+
diff --git a/Tethering/res/values-mcc311-mnc480-tl/strings.xml b/Tethering/res/values-mcc311-mnc480-tl/strings.xml
new file mode 100644
index 0000000000..a7c78a5932
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-tl/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Walang internet ang pag-tether"
+ "Hindi makakonekta ang mga device"
+ "I-off ang pag-tether"
+ "Naka-on ang Hotspot o pag-tether"
+ "Posibleng magkaroon ng mga karagdagang singil habang nagro-roam"
+
diff --git a/Tethering/res/values-mcc311-mnc480-tr/strings.xml b/Tethering/res/values-mcc311-mnc480-tr/strings.xml
new file mode 100644
index 0000000000..93da2c3f79
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-tr/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Tethering\'in internet bağlantısı yok"
+ "Cihazlar bağlanamıyor"
+ "Tethering\'i kapat"
+ "Hotspot veya tethering açık"
+ "Dolaşım sırasında ek ücretler uygulanabilir"
+
diff --git a/Tethering/res/values-mcc311-mnc480-uk/strings.xml b/Tethering/res/values-mcc311-mnc480-uk/strings.xml
new file mode 100644
index 0000000000..ee0dcd2c4b
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-uk/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Телефон, який використовується як модем, не підключений до Інтернету"
+ "Не вдається підключити пристрої"
+ "Вимкнути використання телефона як модема"
+ "Увімкнено точку доступу або використання телефона як модема"
+ "У роумінгу може стягуватися додаткова плата"
+
diff --git a/Tethering/res/values-mcc311-mnc480-ur/strings.xml b/Tethering/res/values-mcc311-mnc480-ur/strings.xml
new file mode 100644
index 0000000000..41cd28eef9
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-ur/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "ٹیدرنگ میں انٹرنیٹ نہیں ہے"
+ "آلات منسلک نہیں ہو سکتے"
+ "ٹیدرنگ آف کریں"
+ "ہاٹ اسپاٹ یا ٹیدرنگ آن ہے"
+ "رومنگ کے دوران اضافی چارجز لاگو ہو سکتے ہیں"
+
diff --git a/Tethering/res/values-mcc311-mnc480-uz/strings.xml b/Tethering/res/values-mcc311-mnc480-uz/strings.xml
new file mode 100644
index 0000000000..c847bc943b
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-uz/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Modem internetga ulanmagan"
+ "Qurilmalar ulanmadi"
+ "Modem rejimini faolsizlantirish"
+ "Hotspot yoki modem rejimi yoniq"
+ "Rouming vaqtida qoʻshimcha haq olinishi mumkin"
+
diff --git a/Tethering/res/values-mcc311-mnc480-vi/strings.xml b/Tethering/res/values-mcc311-mnc480-vi/strings.xml
new file mode 100644
index 0000000000..a74326f09e
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-vi/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Không có Internet để chia sẻ kết Internet"
+ "Các thiết bị không thể kết nối"
+ "Tắt tính năng chia sẻ Internet"
+ "Điểm phát sóng hoặc tính năng chia sẻ Internet đang bật"
+ "Bạn có thể mất thêm phí dữ liệu khi chuyển vùng"
+
diff --git a/Tethering/res/values-mcc311-mnc480-zh-rCN/strings.xml b/Tethering/res/values-mcc311-mnc480-zh-rCN/strings.xml
new file mode 100644
index 0000000000..d7370036e3
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-zh-rCN/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "共享网络未连接到互联网"
+ "设备无法连接"
+ "关闭网络共享"
+ "热点或网络共享已开启"
+ "漫游时可能会产生额外的费用"
+
diff --git a/Tethering/res/values-mcc311-mnc480-zh-rHK/strings.xml b/Tethering/res/values-mcc311-mnc480-zh-rHK/strings.xml
new file mode 100644
index 0000000000..f378a9dc2c
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-zh-rHK/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "無法透過網絡共享連線至互聯網"
+ "裝置無法連接"
+ "關閉網絡共享"
+ "熱點或網絡共享已開啟"
+ "漫遊時可能需要支付額外費用"
+
diff --git a/Tethering/res/values-mcc311-mnc480-zh-rTW/strings.xml b/Tethering/res/values-mcc311-mnc480-zh-rTW/strings.xml
new file mode 100644
index 0000000000..cd653df1da
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-zh-rTW/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "無法透過網路共用連上網際網路"
+ "裝置無法連線"
+ "關閉網路共用"
+ "無線基地台或網路共用已開啟"
+ "使用漫遊服務可能須支付額外費用"
+
diff --git a/Tethering/res/values-mcc311-mnc480-zu/strings.xml b/Tethering/res/values-mcc311-mnc480-zu/strings.xml
new file mode 100644
index 0000000000..32f6df56f1
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480-zu/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ "Ukusebenzisa ifoni njengemodemu akunayo i-inthanethi"
+ "Amadivayisi awakwazi ukuxhumeka"
+ "Vala ukusebenzisa ifoni njengemodemu"
+ "I-hotspot noma ukusebenzisa ifoni njengemodemu kuvuliwe"
+ "Kungaba nezinkokhelo ezengeziwe uma uzula"
+
diff --git a/Tethering/res/values-mcc311-mnc480/config.xml b/Tethering/res/values-mcc311-mnc480/config.xml
new file mode 100644
index 0000000000..5c5be0466a
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480/config.xml
@@ -0,0 +1,23 @@
+
+
+
+
+ 5000
+
+
+ true
+
\ No newline at end of file
diff --git a/Tethering/res/values-mcc311-mnc480/strings.xml b/Tethering/res/values-mcc311-mnc480/strings.xml
new file mode 100644
index 0000000000..ce9ff60807
--- /dev/null
+++ b/Tethering/res/values-mcc311-mnc480/strings.xml
@@ -0,0 +1,28 @@
+
+
+
+
+ Tethering has no internet
+
+ Devices can\u2019t connect
+
+ Turn off tethering
+
+
+ Hotspot or tethering is on
+
+ Additional charges may apply while roaming
+
diff --git a/Tethering/res/values-mk/strings.xml b/Tethering/res/values-mk/strings.xml
new file mode 100644
index 0000000000..9ad9b9a589
--- /dev/null
+++ b/Tethering/res/values-mk/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Активно е врзување или точка на пристап"
+ "Допрете за поставување."
+ "Врзувањето е оневозможено"
+ "Контактирајте со администраторот за детали"
+ "Статус на точката на пристап и врзувањето"
+
+
+
+
+
+
diff --git a/Tethering/res/values-ml/strings.xml b/Tethering/res/values-ml/strings.xml
new file mode 100644
index 0000000000..9db79ce220
--- /dev/null
+++ b/Tethering/res/values-ml/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "ടെതറിംഗ് അല്ലെങ്കിൽ ഹോട്ട്സ്പോട്ട് സജീവമാണ്"
+ "സജ്ജീകരിക്കാൻ ടാപ്പ് ചെയ്യുക."
+ "ടെതറിംഗ് പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു"
+ "വിശദാംശങ്ങൾക്ക് നിങ്ങളുടെ അഡ്മിനെ ബന്ധപ്പെടുക"
+ "ഹോട്ട്സ്പോട്ടിന്റെയും ടെതറിംഗിന്റെയും നില"
+
+
+
+
+
+
diff --git a/Tethering/res/values-mn/strings.xml b/Tethering/res/values-mn/strings.xml
new file mode 100644
index 0000000000..42d1edbace
--- /dev/null
+++ b/Tethering/res/values-mn/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Модем болгох эсвэл сүлжээний цэг идэвхтэй байна"
+ "Тохируулахын тулд товшино уу."
+ "Модем болгохыг идэвхгүй болгосон"
+ "Дэлгэрэнгүй мэдээлэл авахын тулд админтайгаа холбогдоно уу"
+ "Сүлжээний цэг болон модем болгох төлөв"
+
+
+
+
+
+
diff --git a/Tethering/res/values-mr/strings.xml b/Tethering/res/values-mr/strings.xml
new file mode 100644
index 0000000000..13995b6b8a
--- /dev/null
+++ b/Tethering/res/values-mr/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "टेदरिंग किंवा हॉटस्पॉट अॅक्टिव्ह आहे"
+ "सेट करण्यासाठी टॅप करा."
+ "टेदरिंग बंद केले आहे"
+ "तपशीलांसाठी तुमच्या ॲडमिनशी संपर्क साधा"
+ "हॉटस्पॉट आणि टेदरिंगची स्थिती"
+
+
+
+
+
+
diff --git a/Tethering/res/values-ms/strings.xml b/Tethering/res/values-ms/strings.xml
new file mode 100644
index 0000000000..d6a67f37b1
--- /dev/null
+++ b/Tethering/res/values-ms/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Penambatan atau tempat liputan aktif"
+ "Ketik untuk membuat persediaan."
+ "Penambatan dilumpuhkan"
+ "Hubungi pentadbir anda untuk mendapatkan maklumat lanjut"
+ "Status tempat liputan & penambatan"
+
+
+
+
+
+
diff --git a/Tethering/res/values-my/strings.xml b/Tethering/res/values-my/strings.xml
new file mode 100644
index 0000000000..49f6b88a75
--- /dev/null
+++ b/Tethering/res/values-my/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်း သို့မဟုတ် ဟော့စပေါ့ ဖွင့်ထားသည်"
+ "စနစ်ထည့်သွင်းရန် တို့ပါ။"
+ "မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်းကို ပိတ်ထားသည်"
+ "အသေးစိတ်အတွက် သင့်စီမံခန့်ခွဲသူကို ဆက်သွယ်ပါ"
+ "ဟော့စပေါ့နှင့် မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်း အခြေအနေ"
+
+
+
+
+
+
diff --git a/Tethering/res/values-nb/strings.xml b/Tethering/res/values-nb/strings.xml
new file mode 100644
index 0000000000..9594e0a70a
--- /dev/null
+++ b/Tethering/res/values-nb/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Internettdeling eller Wi-Fi-sone er aktiv"
+ "Trykk for å konfigurere."
+ "Internettdeling er slått av"
+ "Ta kontakt med administratoren din for å få mer informasjon"
+ "Status for Wi-Fi-sone og internettdeling"
+
+
+
+
+
+
diff --git a/Tethering/res/values-ne/strings.xml b/Tethering/res/values-ne/strings.xml
new file mode 100644
index 0000000000..72ae3a80a9
--- /dev/null
+++ b/Tethering/res/values-ne/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "टेदरिङ वा हटस्पट सक्रिय छ"
+ "सेटअप गर्न ट्याप गर्नुहोस्।"
+ "टेदरिङ सुविधा असक्षम पारिएको छ"
+ "विवरणहरूका लागि आफ्ना प्रशासकलाई सम्पर्क गर्नुहोस्"
+ "हटस्पट तथा टेदरिङको स्थिति"
+
+
+
+
+
+
diff --git a/Tethering/res/values-nl/strings.xml b/Tethering/res/values-nl/strings.xml
new file mode 100644
index 0000000000..18b2bbfc76
--- /dev/null
+++ b/Tethering/res/values-nl/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Tethering of hotspot actief"
+ "Tik om in te stellen."
+ "Tethering is uitgeschakeld"
+ "Neem contact op met je beheerder voor meer informatie"
+ "Status van hotspot en tethering"
+
+
+
+
+
+
diff --git a/Tethering/res/values-or/strings.xml b/Tethering/res/values-or/strings.xml
new file mode 100644
index 0000000000..a15a6db42a
--- /dev/null
+++ b/Tethering/res/values-or/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "ଟିଥେରିଂ କିମ୍ୱା ହଟସ୍ପଟ୍ ସକ୍ରିୟ ଅଛି"
+ "ସେଟ୍ ଅପ୍ କରିବାକୁ ଟାପ୍ କରନ୍ତୁ।"
+ "ଟିଥେରିଂ ଅକ୍ଷମ କରାଯାଇଛି"
+ "ବିବରଣୀଗୁଡ଼ିକ ପାଇଁ ଆପଣଙ୍କ ଆଡମିନଙ୍କ ସହ ଯୋଗାଯୋଗ କରନ୍ତୁ"
+ "ହଟସ୍ପଟ୍ ଓ ଟିଥେରିଂ ସ୍ଥିତି"
+
+
+
+
+
+
diff --git a/Tethering/res/values-pa/strings.xml b/Tethering/res/values-pa/strings.xml
new file mode 100644
index 0000000000..a8235e423e
--- /dev/null
+++ b/Tethering/res/values-pa/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "ਟੈਦਰਿੰਗ ਜਾਂ ਹੌਟਸਪੌਟ ਕਿਰਿਆਸ਼ੀਲ"
+ "ਸੈੱਟਅੱਪ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"
+ "ਟੈਦਰਿੰਗ ਨੂੰ ਬੰਦ ਕੀਤਾ ਗਿਆ ਹੈ"
+ "ਵੇਰਵਿਆਂ ਲਈ ਆਪਣੇ ਪ੍ਰਸ਼ਾਸਕ ਨਾਲ ਸੰਪਰਕ ਕਰੋ"
+ "ਹੌਟਸਪੌਟ ਅਤੇ ਟੈਦਰਿੰਗ ਦੀ ਸਥਿਤੀ"
+
+
+
+
+
+
diff --git a/Tethering/res/values-pl/strings.xml b/Tethering/res/values-pl/strings.xml
new file mode 100644
index 0000000000..ccb017d43f
--- /dev/null
+++ b/Tethering/res/values-pl/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Aktywny tethering lub punkt dostępu"
+ "Kliknij, by skonfigurować"
+ "Tethering został wyłączony"
+ "Aby uzyskać szczegółowe informacje, skontaktuj się z administratorem"
+ "Hotspot i tethering – stan"
+
+
+
+
+
+
diff --git a/Tethering/res/values-pt-rBR/strings.xml b/Tethering/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000000..a0a4745f93
--- /dev/null
+++ b/Tethering/res/values-pt-rBR/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Ponto de acesso ou tethering ativo"
+ "Toque para configurar."
+ "Tethering desativado"
+ "Fale com seu administrador para saber detalhes"
+ "Status de ponto de acesso e tethering"
+
+
+
+
+
+
diff --git a/Tethering/res/values-pt-rPT/strings.xml b/Tethering/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000000..e3f03fcc69
--- /dev/null
+++ b/Tethering/res/values-pt-rPT/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Ligação (à Internet) via telemóvel ou zona Wi-Fi ativas"
+ "Toque para configurar."
+ "A ligação (à Internet) via telemóvel está desativada."
+ "Contacte o administrador para obter detalhes."
+ "Estado da zona Wi-Fi e da ligação (à Internet) via telemóvel"
+
+
+
+
+
+
diff --git a/Tethering/res/values-pt/strings.xml b/Tethering/res/values-pt/strings.xml
new file mode 100644
index 0000000000..a0a4745f93
--- /dev/null
+++ b/Tethering/res/values-pt/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Ponto de acesso ou tethering ativo"
+ "Toque para configurar."
+ "Tethering desativado"
+ "Fale com seu administrador para saber detalhes"
+ "Status de ponto de acesso e tethering"
+
+
+
+
+
+
diff --git a/Tethering/res/values-ro/strings.xml b/Tethering/res/values-ro/strings.xml
new file mode 100644
index 0000000000..5706a4a69c
--- /dev/null
+++ b/Tethering/res/values-ro/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Tethering sau hotspot activ"
+ "Atingeți ca să configurați."
+ "Tetheringul este dezactivat"
+ "Contactați administratorul pentru detalii"
+ "Starea hotspotului și a tetheringului"
+
+
+
+
+
+
diff --git a/Tethering/res/values-ru/strings.xml b/Tethering/res/values-ru/strings.xml
new file mode 100644
index 0000000000..7cb6f7db3f
--- /dev/null
+++ b/Tethering/res/values-ru/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Включен режим модема или точка доступа"
+ "Нажмите, чтобы настроить."
+ "Использование телефона в качестве модема запрещено"
+ "Чтобы узнать подробности, обратитесь к администратору."
+ "Статус хот-спота и режима модема"
+
+
+
+
+
+
diff --git a/Tethering/res/values-si/strings.xml b/Tethering/res/values-si/strings.xml
new file mode 100644
index 0000000000..ec34c22de7
--- /dev/null
+++ b/Tethering/res/values-si/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "ටෙදරින් හෝ හොට්ස්පොට් සක්රීයයි"
+ "පිහිටුවීමට තට්ටු කරන්න."
+ "ටෙදරින් අබල කර ඇත"
+ "විස්තර සඳහා ඔබගේ පරිපාලක අමතන්න"
+ "හොට්ස්පොට් & ටෙදරින් තත්ත්වය"
+
+
+
+
+
+
diff --git a/Tethering/res/values-sk/strings.xml b/Tethering/res/values-sk/strings.xml
new file mode 100644
index 0000000000..43e787c84f
--- /dev/null
+++ b/Tethering/res/values-sk/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Tethering alebo prístupový bod je aktívny"
+ "Klepnutím prejdete na nastavenie."
+ "Tethering je deaktivovaný"
+ "O podrobnosti požiadajte svojho správcu"
+ "Stav hotspotu a tetheringu"
+
+
+
+
+
+
diff --git a/Tethering/res/values-sl/strings.xml b/Tethering/res/values-sl/strings.xml
new file mode 100644
index 0000000000..59433626a1
--- /dev/null
+++ b/Tethering/res/values-sl/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Povezava z internetom prek mobilnega telefona ali dostopna točka je aktivna"
+ "Dotaknite se, če želite nastaviti."
+ "Povezava z internetom prek mobilnega telefona je onemogočena"
+ "Za podrobnosti se obrnite na skrbnika"
+ "Stanje dostopne točke in povezave z internetom prek mobilnega telefona"
+
+
+
+
+
+
diff --git a/Tethering/res/values-sq/strings.xml b/Tethering/res/values-sq/strings.xml
new file mode 100644
index 0000000000..21e11558bb
--- /dev/null
+++ b/Tethering/res/values-sq/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Ndarja e internetit ose zona e qasjes së internetit është aktive"
+ "Trokit për ta konfiguruar."
+ "Ndarja e internetit është çaktivizuar"
+ "Kontakto me administratorin për detaje"
+ "Statusi i zonës së qasjes dhe ndarjes së internetit"
+
+
+
+
+
+
diff --git a/Tethering/res/values-sr/strings.xml b/Tethering/res/values-sr/strings.xml
new file mode 100644
index 0000000000..e2e4dc6361
--- /dev/null
+++ b/Tethering/res/values-sr/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Привезивање или хотспот је активан"
+ "Додирните да бисте подесили."
+ "Привезивање је онемогућено"
+ "Потражите детаље од администратора"
+ "Статус хотспота и привезивања"
+
+
+
+
+
+
diff --git a/Tethering/res/values-sv/strings.xml b/Tethering/res/values-sv/strings.xml
new file mode 100644
index 0000000000..72702c2858
--- /dev/null
+++ b/Tethering/res/values-sv/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Internetdelning eller surfzon har aktiverats"
+ "Tryck om du vill konfigurera."
+ "Internetdelning har inaktiverats"
+ "Kontakta administratören om du vill veta mer"
+ "Trådlös surfzon och internetdelning har inaktiverats"
+
+
+
+
+
+
diff --git a/Tethering/res/values-sw/strings.xml b/Tethering/res/values-sw/strings.xml
new file mode 100644
index 0000000000..65e4aa8ceb
--- /dev/null
+++ b/Tethering/res/values-sw/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Kusambaza mtandao au mtandaopepe umewashwa"
+ "Gusa ili uweke mipangilio."
+ "Umezima kipengele cha kusambaza mtandao"
+ "Wasiliana na msimamizi wako ili upate maelezo zaidi"
+ "Mtandaopepe na hali ya kusambaza mtandao"
+
+
+
+
+
+
diff --git a/Tethering/res/values-ta/strings.xml b/Tethering/res/values-ta/strings.xml
new file mode 100644
index 0000000000..4aba62d4ab
--- /dev/null
+++ b/Tethering/res/values-ta/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "டெதெரிங் அல்லது ஹாட்ஸ்பாட் இயங்குகிறது"
+ "அமைக்க, தட்டவும்."
+ "டெதெரிங் முடக்கப்பட்டுள்ளது"
+ "விவரங்களுக்கு உங்கள் நிர்வாகியைத் தொடர்புகொள்ளவும்"
+ "ஹாட்ஸ்பாட் & டெதெரிங் நிலை"
+
+
+
+
+
+
diff --git a/Tethering/res/values-te/strings.xml b/Tethering/res/values-te/strings.xml
new file mode 100644
index 0000000000..1f91791341
--- /dev/null
+++ b/Tethering/res/values-te/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "టెథరింగ్ లేదా హాట్స్పాట్ యాక్టివ్గా ఉంది"
+ "సెటప్ చేయడానికి ట్యాప్ చేయండి."
+ "టెథరింగ్ డిజేబుల్ చేయబడింది"
+ "వివరాల కోసం మీ అడ్మిన్ని సంప్రదించండి"
+ "హాట్స్పాట్ & టెథరింగ్ స్థితి"
+
+
+
+
+
+
diff --git a/Tethering/res/values-th/strings.xml b/Tethering/res/values-th/strings.xml
new file mode 100644
index 0000000000..44171c0db8
--- /dev/null
+++ b/Tethering/res/values-th/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "การเชื่อมต่ออินเทอร์เน็ตผ่านมือถือหรือฮอตสปอตทำงานอยู่"
+ "แตะเพื่อตั้งค่า"
+ "ปิดใช้การเชื่อมต่ออินเทอร์เน็ตผ่านมือถือแล้ว"
+ "ติดต่อผู้ดูแลระบบเพื่อขอรายละเอียด"
+ "สถานะฮอตสปอตและการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือ"
+
+
+
+
+
+
diff --git a/Tethering/res/values-tl/strings.xml b/Tethering/res/values-tl/strings.xml
new file mode 100644
index 0000000000..7347dd3e62
--- /dev/null
+++ b/Tethering/res/values-tl/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Aktibo ang pag-tether o hotspot"
+ "I-tap para i-set up."
+ "Naka-disable ang pag-tether"
+ "Makipag-ugnayan sa iyong admin para sa mga detalye"
+ "Status ng hotspot at pag-tether"
+
+
+
+
+
+
diff --git a/Tethering/res/values-tr/strings.xml b/Tethering/res/values-tr/strings.xml
new file mode 100644
index 0000000000..32030f1765
--- /dev/null
+++ b/Tethering/res/values-tr/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Tethering veya hotspot etkin"
+ "Ayarlamak için dokunun."
+ "Tethering devre dışı bırakıldı"
+ "Ayrıntılı bilgi için yöneticinize başvurun"
+ "Hotspot ve tethering durumu"
+
+
+
+
+
+
diff --git a/Tethering/res/values-uk/strings.xml b/Tethering/res/values-uk/strings.xml
new file mode 100644
index 0000000000..1ca89b3f78
--- /dev/null
+++ b/Tethering/res/values-uk/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Модем чи точка доступу активні"
+ "Натисніть, щоб налаштувати."
+ "Використання телефона як модема вимкнено"
+ "Щоб дізнатися більше, зв\'яжіться з адміністратором"
+ "Статус точки доступу та модема"
+
+
+
+
+
+
diff --git a/Tethering/res/values-ur/strings.xml b/Tethering/res/values-ur/strings.xml
new file mode 100644
index 0000000000..d72c7d4195
--- /dev/null
+++ b/Tethering/res/values-ur/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "ٹیدرنگ یا ہاٹ اسپاٹ فعال"
+ "سیٹ اپ کرنے کیلئے تھپتھپائیں۔"
+ "ٹیدرنگ غیر فعال ہے"
+ "تفصیلات کے لئے اپنے منتظم سے رابطہ کریں"
+ "ہاٹ اسپاٹ اور ٹیتھرنگ کا اسٹیٹس"
+
+
+
+
+
+
diff --git a/Tethering/res/values-uz/strings.xml b/Tethering/res/values-uz/strings.xml
new file mode 100644
index 0000000000..af3b2ebb35
--- /dev/null
+++ b/Tethering/res/values-uz/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Modem rejimi yoki hotspot yoniq"
+ "Sozlash uchun bosing."
+ "Modem rejimi faolsizlantirildi"
+ "Tafsilotlari uchun administratoringizga murojaat qiling"
+ "Hotspot va modem rejimi holati"
+
+
+
+
+
+
diff --git a/Tethering/res/values-vi/strings.xml b/Tethering/res/values-vi/strings.xml
new file mode 100644
index 0000000000..21a0735922
--- /dev/null
+++ b/Tethering/res/values-vi/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Tính năng chia sẻ Internet hoặc điểm phát sóng đang hoạt động"
+ "Hãy nhấn để thiết lập."
+ "Đã tắt tính năng chia sẻ Internet"
+ "Hãy liên hệ với quản trị viên của bạn để biết chi tiết"
+ "Trạng thái điểm phát sóng và chia sẻ Internet"
+
+
+
+
+
+
diff --git a/Tethering/res/values-zh-rCN/strings.xml b/Tethering/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000000..98e3b4b46f
--- /dev/null
+++ b/Tethering/res/values-zh-rCN/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "网络共享或热点已启用"
+ "点按即可设置。"
+ "网络共享已停用"
+ "如需了解详情,请与您的管理员联系"
+ "热点和网络共享状态"
+
+
+
+
+
+
diff --git a/Tethering/res/values-zh-rHK/strings.xml b/Tethering/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000000..9cafd42dd4
--- /dev/null
+++ b/Tethering/res/values-zh-rHK/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "網絡共享或熱點已啟用"
+ "輕按即可設定。"
+ "網絡共享已停用"
+ "請聯絡您的管理員以瞭解詳情"
+ "熱點和網絡共享狀態"
+
+
+
+
+
+
diff --git a/Tethering/res/values-zh-rTW/strings.xml b/Tethering/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000000..50a50bf7a9
--- /dev/null
+++ b/Tethering/res/values-zh-rTW/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "網路共用或無線基地台已啟用"
+ "輕觸即可進行設定。"
+ "網路共用已停用"
+ "詳情請洽你的管理員"
+ "無線基地台與網路共用狀態"
+
+
+
+
+
+
diff --git a/Tethering/res/values-zu/strings.xml b/Tethering/res/values-zu/strings.xml
new file mode 100644
index 0000000000..f210f8726e
--- /dev/null
+++ b/Tethering/res/values-zu/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ "Ukusebenzisa njengemodemu noma i-hotspot ephathekayo kuvuliwe"
+ "Thepha ukuze usethe."
+ "Ukusebenzisa ifoni njengemodemu kukhutshaziwe"
+ "Xhumana nomphathi wakho ukuze uthole imininingwane"
+ "I-Hotspot nesimo sokusebenzisa ifoni njengemodemu"
+
+
+
+
+
+
diff --git a/Tethering/res/values/config.xml b/Tethering/res/values/config.xml
new file mode 100644
index 0000000000..4391006741
--- /dev/null
+++ b/Tethering/res/values/config.xml
@@ -0,0 +1,196 @@
+
+
+
+
+
+
+ - "usb\\d"
+ - "rndis\\d"
+
+
+
+
+
+
+
+
+ - "wlan\\d"
+ - "softap\\d"
+ - "ap_br_wlan\\d"
+ - "ap_br_softap\\d"
+
+
+
+
+ - "wigig\\d"
+
+
+
+
+ - "p2p-p2p\\d-.*"
+ - "p2p\\d"
+
+
+
+
+ - "bt-pan"
+
+
+
+ true
+
+
+ false
+
+
+ false
+
+
+
+
+
+
+ 5000
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 24
+
+
+ com.android.settings/.wifi.tether.TetherService
+
+
+
+ -1
+
+
+
+ false
+
diff --git a/Tethering/res/values/overlayable.xml b/Tethering/res/values/overlayable.xml
new file mode 100644
index 0000000000..0ee7a992ee
--- /dev/null
+++ b/Tethering/res/values/overlayable.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tethering/res/values/strings.xml b/Tethering/res/values/strings.xml
new file mode 100644
index 0000000000..d63c7c5063
--- /dev/null
+++ b/Tethering/res/values/strings.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+ Tethering or hotspot active
+
+ Tap to set up.
+
+
+
+ Tethering is disabled
+
+ Contact your admin for details
+
+
+
+ Hotspot & tethering status
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tethering/src/android/net/dhcp/DhcpServerCallbacks.java b/Tethering/src/android/net/dhcp/DhcpServerCallbacks.java
new file mode 100644
index 0000000000..9fda1257b4
--- /dev/null
+++ b/Tethering/src/android/net/dhcp/DhcpServerCallbacks.java
@@ -0,0 +1,36 @@
+/*
+ * 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/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java b/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
new file mode 100644
index 0000000000..aaaec17bf9
--- /dev/null
+++ b/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
@@ -0,0 +1,203 @@
+/*
+ * 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.
+ *
+ * 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.
+ *
+ *
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.
+ *
+ *
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 defaultRouters) {
+ this.defaultRouters = toIntArray(defaultRouters);
+ return this;
+ }
+
+ /**
+ * Set the default routers to be advertised to DHCP clients.
+ *
+ * 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.
+ *
+ *
Equivalent to calling {@link #setDefaultRouters(Inet4Address...)} with no address.
+ */
+ public DhcpServingParamsParcelExt setNoDefaultRouter() {
+ return setDefaultRouters();
+ }
+
+ /**
+ * Set the DNS servers to be advertised to DHCP clients.
+ *
+ *
This may be an empty set, but it must always be set explicitly.
+ */
+ public DhcpServingParamsParcelExt setDnsServers(@NonNull Set dnsServers) {
+ this.dnsServers = toIntArray(dnsServers);
+ return this;
+ }
+
+ /**
+ * Set the DNS servers to be advertised to DHCP clients.
+ *
+ * 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.
+ *
+ *
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.
+ *
+ *
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 excludedAddrs) {
+ this.excludedAddrs = toIntArray(excludedAddrs);
+ return this;
+ }
+
+ /**
+ * Set excluded addresses that the DHCP server is not allowed to assign to clients.
+ *
+ * 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.
+ *
+ *
This parameter is required.
+ */
+ public DhcpServingParamsParcelExt setDhcpLeaseTimeSecs(long dhcpLeaseTimeSecs) {
+ this.dhcpLeaseTimeSecs = dhcpLeaseTimeSecs;
+ return this;
+ }
+
+ /**
+ * Set the link MTU to be advertised to DHCP clients.
+ *
+ *
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.
+ *
+ *
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.
+ *
+ *
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.
+ *
+ *
If not set, the default value is false.
+ */
+ public DhcpServingParamsParcelExt setChangePrefixOnDecline(boolean changePrefixOnDecline) {
+ this.changePrefixOnDecline = changePrefixOnDecline;
+ return this;
+ }
+
+ private static int[] toIntArray(@NonNull Collection addrs) {
+ int[] res = new int[addrs.size()];
+ int i = 0;
+ for (Inet4Address addr : addrs) {
+ res[i] = inet4AddressToIntHTH(addr);
+ i++;
+ }
+ return res;
+ }
+
+ private static ArraySet newArraySet(Inet4Address... addrs) {
+ ArraySet addrSet = new ArraySet<>(addrs.length);
+ Collections.addAll(addrSet, addrs);
+ return addrSet;
+ }
+}
diff --git a/Tethering/src/android/net/ip/DadProxy.java b/Tethering/src/android/net/ip/DadProxy.java
new file mode 100644
index 0000000000..e2976b7890
--- /dev/null
+++ b/Tethering/src/android/net/ip/DadProxy.java
@@ -0,0 +1,54 @@
+/*
+ * 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/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
new file mode 100644
index 0000000000..c45ce830e2
--- /dev/null
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -0,0 +1,1469 @@
+/*
+ * 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.ClientInfo;
+import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
+import com.android.networkstack.tethering.PrivateAddressCoordinator;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+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 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);
+ }
+
+ /** 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 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 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.
+ *
+ * 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 leaseParcelables) {
+ final ArrayList 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. See the comment of the interface mapping update in
+ // CMD_TETHER_CONNECTION_CHANGED. Adding the mapping update here to the avoid potential
+ // timing issue. It prevents that the IPv6 capability is updated later than
+ // CMD_TETHER_CONNECTION_CHANGED.
+ 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 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 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 deprecatedPrefixes, HashSet 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 addedPrefixes = (HashSet) newPrefixes.clone();
+ if (mLastRaParams != null) {
+ addedPrefixes.removeAll(mLastRaParams.prefixes);
+ }
+
+ if (!addedPrefixes.isEmpty()) {
+ addRoutesToLocalNetwork(getLocalRoutesFor(mIfaceName, addedPrefixes));
+ }
+ }
+ }
+
+ private void configureLocalIPv6Dns(
+ HashSet deprecatedDnses, HashSet 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 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);
+ }
+ }
+
+ // TODO: consider moving into BpfCoordinator.
+ private void updateClientInfoIpv4(NeighborEvent e) {
+ // TODO: Perhaps remove this protection check.
+ // See the related comment in #addIpv6ForwardingRule.
+ if (!mUsingBpfOffload) return;
+
+ if (e == null) return;
+ if (!(e.ip instanceof Inet4Address) || e.ip.isMulticastAddress()
+ || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) {
+ return;
+ }
+
+ // When deleting clients, IpServer still need to pass a non-null MAC, even though it's
+ // ignored. Do this here instead of in the ClientInfo constructor to ensure that
+ // IpServer never add clients with a null MAC, only delete them.
+ final MacAddress clientMac = e.isValid() ? e.macAddr : NULL_MAC_ADDRESS;
+ final ClientInfo clientInfo = new ClientInfo(mInterfaceParams.index,
+ mInterfaceParams.macAddr, (Inet4Address) e.ip, clientMac);
+ if (e.isValid()) {
+ mBpfCoordinator.tetherOffloadClientAdd(this, clientInfo);
+ } else {
+ // TODO: Delete all related offload rules which are using this client.
+ mBpfCoordinator.tetherOffloadClientRemove(this, clientInfo);
+ }
+ }
+
+ private void handleNeighborEvent(NeighborEvent e) {
+ if (mInterfaceParams != null
+ && mInterfaceParams.index == e.ifindex
+ && mInterfaceParams.hasMacAddress) {
+ updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, mLastIPv6UpstreamIfindex, e);
+ updateClientInfoIpv4(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;
+ }
+ }
+
+ private void startConntrackMonitoring() {
+ mBpfCoordinator.startMonitoring(this);
+ }
+
+ private void stopConntrackMonitoring() {
+ mBpfCoordinator.stopMonitoring(this);
+ }
+
+ class BaseServingState extends State {
+ @Override
+ public void enter() {
+ startConntrackMonitoring();
+
+ 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();
+ stopConntrackMonitoring();
+
+ 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.
+ mBpfCoordinator.maybeDetachProgram(mIfaceName, upstreamIface);
+ 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 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) {
+ // 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 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.
+ // Note that both IPv4 and IPv6 interface may be added because
+ // Tethering::setUpstreamNetwork calls getTetheringInterfaces which merges
+ // IPv4 and IPv6 interface name (if any) into an InterfaceSet. The IPv6
+ // capability may be updated later. In that case, IPv6 interface mapping is
+ // updated in updateUpstreamIPv6LinkProperties.
+ if (!ifname.startsWith("v4-")) { // ignore clat interfaces
+ final InterfaceParams upstreamIfaceParams =
+ mDeps.getInterfaceParams(ifname);
+ if (upstreamIfaceParams != null) {
+ mBpfCoordinator.addUpstreamNameToLookupTable(
+ upstreamIfaceParams.index, ifname);
+ }
+ }
+
+ mBpfCoordinator.maybeAttachProgram(mIfaceName, ifname);
+ 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 upstreamInterfacesRemoved(InterfaceSet newIfaces) {
+ if (mUpstreamIfaceSet == null) return new HashSet<>();
+
+ final HashSet removed = new HashSet<>(mUpstreamIfaceSet.ifnames);
+ removed.removeAll(newIfaces.ifnames);
+ return removed;
+ }
+
+ private Set upstreamInterfacesAdd(InterfaceSet newIfaces) {
+ final HashSet 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 getLocalRoutesFor(
+ String ifname, HashSet prefixes) {
+ final ArrayList localRoutes = new ArrayList();
+ 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/Tethering/src/android/net/ip/NeighborPacketForwarder.java b/Tethering/src/android/net/ip/NeighborPacketForwarder.java
new file mode 100644
index 0000000000..084743db03
--- /dev/null
+++ b/Tethering/src/android/net/ip/NeighborPacketForwarder.java
@@ -0,0 +1,181 @@
+/*
+ * 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.SocketUtils;
+import android.net.util.TetheringUtils;
+import android.os.Handler;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import com.android.net.module.util.PacketReader;
+
+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/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
new file mode 100644
index 0000000000..543a5c722f
--- /dev/null
+++ b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
@@ -0,0 +1,659 @@
+/*
+ * 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.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 static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_RA_HEADER_LEN;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
+import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS;
+import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_ON_LINK;
+import static com.android.net.module.util.NetworkStackConstants.TAG_SYSTEM_NEIGHBOR;
+
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.MacAddress;
+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.net.module.util.structs.Icmpv6Header;
+import com.android.net.module.util.structs.LlaOption;
+import com.android.net.module.util.structs.MtuOption;
+import com.android.net.module.util.structs.PrefixInformationOption;
+import com.android.net.module.util.structs.RaHeader;
+import com.android.net.module.util.structs.RdnssOption;
+
+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();
+
+ // 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 prefixes;
+ public HashSet dnses;
+
+ public RaParams() {
+ hasDefaultRoute = false;
+ hopLimit = DEFAULT_HOPLIMIT;
+ mtu = IPV6_MIN_MTU;
+ prefixes = new HashSet();
+ dnses = new HashSet();
+ }
+
+ 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 mPrefixes = new HashMap<>();
+ private final HashMap mDnses = new HashMap<>();
+
+ Set getPrefixes() {
+ return mPrefixes.keySet();
+ }
+
+ void putPrefixes(Set prefixes) {
+ for (IpPrefix ipp : prefixes) {
+ mPrefixes.put(ipp, MAX_URGENT_RTR_ADVERTISEMENTS);
+ }
+ }
+
+ void removePrefixes(Set prefixes) {
+ for (IpPrefix ipp : prefixes) {
+ mPrefixes.remove(ipp);
+ }
+ }
+
+ Set getDnses() {
+ return mDnses.keySet();
+ }
+
+ void putDnses(Set dnses) {
+ for (Inet6Address dns : dnses) {
+ mDnses.put(dns, MAX_URGENT_RTR_ADVERTISEMENTS);
+ }
+ }
+
+ void removeDnses(Set 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 boolean decrementCounter(HashMap map) {
+ boolean removed = false;
+
+ for (Iterator> it = map.entrySet().iterator();
+ it.hasNext();) {
+ Map.Entry 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 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) {
+ // RFC 4191 "high" preference, iff. advertising a default route.
+ final byte flags = hasDefaultRoute ? asByte(0x08) : asByte(0);
+ final short lifetime = hasDefaultRoute ? asShort(DEFAULT_LIFETIME) : asShort(0);
+ final Icmpv6Header icmpv6Header =
+ new Icmpv6Header(asByte(ICMPV6_ROUTER_ADVERTISEMENT) /* type */,
+ asByte(0) /* code */, asShort(0) /* checksum */);
+ final RaHeader raHeader = new RaHeader(hopLimit, flags, lifetime, 0 /* reachableTime */,
+ 0 /* retransTimer */);
+ icmpv6Header.writeToByteBuffer(ra);
+ raHeader.writeToByteBuffer(ra);
+ }
+
+ private static void putSlla(ByteBuffer ra, byte[] slla) {
+ if (slla == null || slla.length != 6) {
+ // Only IEEE 802.3 6-byte addresses are supported.
+ return;
+ }
+
+ final ByteBuffer sllaOption = LlaOption.build(asByte(ICMPV6_ND_OPTION_SLLA),
+ MacAddress.fromBytes(slla));
+ ra.put(sllaOption);
+ }
+
+ 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) {
+ final ByteBuffer mtuOption = MtuOption.build((mtu < IPV6_MIN_MTU) ? IPV6_MIN_MTU : mtu);
+ ra.put(mtuOption);
+ }
+
+ private static void putPio(ByteBuffer ra, IpPrefix ipp,
+ int validTime, int preferredTime) {
+ final int prefixLength = ipp.getPrefixLength();
+ if (prefixLength != 64) {
+ return;
+ }
+
+ if (validTime < 0) validTime = 0;
+ if (preferredTime < 0) preferredTime = 0;
+ if (preferredTime > validTime) preferredTime = validTime;
+
+ final ByteBuffer pioOption = PrefixInformationOption.build(ipp,
+ asByte(PIO_FLAG_ON_LINK | PIO_FLAG_AUTONOMOUS), validTime, preferredTime);
+ ra.put(pioOption);
+ }
+
+ 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 dnses, int lifetime) {
+ final HashSet filteredDnses = new HashSet<>();
+ for (Inet6Address dns : dnses) {
+ if ((new LinkAddress(dns, RFC7421_PREFIX_LENGTH)).isGlobalPreferred()) {
+ filteredDnses.add(dns);
+ }
+ }
+ if (filteredDnses.isEmpty()) return;
+
+ final Inet6Address[] dnsesArray =
+ filteredDnses.toArray(new Inet6Address[filteredDnses.size()]);
+ final ByteBuffer rdnssOption = RdnssOption.build(lifetime, dnsesArray);
+ // 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(rdnssOption);
+ }
+
+ private boolean createSocket() {
+ final int send_timout_ms = 300;
+
+ final int oldTag = TrafficStats.getAndSetThreadStatsTag(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 < ICMPV6_RA_HEADER_LEN) {
+ // 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] != asByte(ICMPV6_ROUTER_SOLICITATION)) {
+ 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 < ICMPV6_RA_HEADER_LEN) {
+ // 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/Tethering/src/android/net/util/InterfaceSet.java b/Tethering/src/android/net/util/InterfaceSet.java
new file mode 100644
index 0000000000..758978711b
--- /dev/null
+++ b/Tethering/src/android/net/util/InterfaceSet.java
@@ -0,0 +1,52 @@
+/*
+ * 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 ifnames;
+
+ public InterfaceSet(String... names) {
+ final Set 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/Tethering/src/android/net/util/PrefixUtils.java b/Tethering/src/android/net/util/PrefixUtils.java
new file mode 100644
index 0000000000..f203e9995f
--- /dev/null
+++ b/Tethering/src/android/net/util/PrefixUtils.java
@@ -0,0 +1,88 @@
+/*
+ * 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 getNonForwardablePrefixes() {
+ final HashSet prefixes = new HashSet<>();
+ addNonForwardablePrefixes(prefixes);
+ return prefixes;
+ }
+
+ /** Add non forwardable prefixes. */
+ public static void addNonForwardablePrefixes(Set prefixes) {
+ Collections.addAll(prefixes, MIN_NON_FORWARDABLE_PREFIXES);
+ }
+
+ /** Get local prefixes from |lp|. */
+ public static Set localPrefixesFrom(LinkProperties lp) {
+ final HashSet 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/Tethering/src/android/net/util/TetheringMessageBase.java b/Tethering/src/android/net/util/TetheringMessageBase.java
new file mode 100644
index 0000000000..29c0a817b6
--- /dev/null
+++ b/Tethering/src/android/net/util/TetheringMessageBase.java
@@ -0,0 +1,25 @@
+/*
+ * 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/Tethering/src/android/net/util/TetheringUtils.java b/Tethering/src/android/net/util/TetheringUtils.java
new file mode 100644
index 0000000000..29900d9beb
--- /dev/null
+++ b/Tethering/src/android/net/util/TetheringUtils.java
@@ -0,0 +1,179 @@
+/*
+ * 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 com.android.networkstack.tethering.TetherStatsValue;
+
+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 {
+ static {
+ System.loadLibrary("tetherutilsjni");
+ }
+
+ 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 TetherStatsValue 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
+ && request.connectivityScope == otherRequest.connectivityScope;
+ }
+
+ /** 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/Tethering/src/android/net/util/VersionedBroadcastListener.java b/Tethering/src/android/net/util/VersionedBroadcastListener.java
new file mode 100644
index 0000000000..e2804abd75
--- /dev/null
+++ b/Tethering/src/android/net/util/VersionedBroadcastListener.java
@@ -0,0 +1,106 @@
+/*
+ * 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 mCallback;
+ private final AtomicInteger mGenerationNumber;
+ private BroadcastReceiver mReceiver;
+
+ public VersionedBroadcastListener(String tag, Context ctx, Handler handler,
+ IntentFilter filter, Consumer 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 callback;
+ // Used to verify this receiver is still current.
+ public final int generationNumber;
+
+ Receiver(String tag, AtomicInteger atomicGenerationNumber, Consumer 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/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
new file mode 100644
index 0000000000..8adcbd9ff9
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -0,0 +1,1577 @@
+/*
+ * 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.ip.ConntrackMonitor.ConntrackEvent;
+import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
+import static android.system.OsConstants.ETH_P_IP;
+import static android.system.OsConstants.ETH_P_IPV6;
+
+import static com.android.networkstack.tethering.BpfUtils.DOWNSTREAM;
+import static com.android.networkstack.tethering.BpfUtils.UPSTREAM;
+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.LinkProperties;
+import android.net.MacAddress;
+import android.net.NetworkStats;
+import android.net.NetworkStats.Entry;
+import android.net.TetherOffloadRuleParcel;
+import android.net.ip.ConntrackMonitor;
+import android.net.ip.ConntrackMonitor.ConntrackEventConsumer;
+import android.net.ip.IpServer;
+import android.net.netlink.NetlinkConstants;
+import android.net.netstats.provider.NetworkStatsProvider;
+import android.net.util.InterfaceParams;
+import android.net.util.SharedLog;
+import android.net.util.TetheringUtils.ForwardedStats;
+import android.os.Handler;
+import android.system.ErrnoException;
+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 com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.NetworkStackConstants;
+import com.android.net.module.util.Struct;
+import com.android.networkstack.tethering.apishim.common.BpfCoordinatorShim;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * 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 {
+ // Ensure the JNI code is loaded. In production this will already have been loaded by
+ // TetherService, but for tests it needs to be either loaded here or loaded by every test.
+ // TODO: is there a better way?
+ static {
+ System.loadLibrary("tetherutilsjni");
+ }
+
+ private static final String TAG = BpfCoordinator.class.getSimpleName();
+ private static final int DUMP_TIMEOUT_MS = 10_000;
+ private static final MacAddress NULL_MAC_ADDRESS = MacAddress.fromString(
+ "00:00:00:00:00:00");
+ private static final String TETHER_DOWNSTREAM4_MAP_PATH = makeMapPath(DOWNSTREAM, 4);
+ private static final String TETHER_UPSTREAM4_MAP_PATH = makeMapPath(UPSTREAM, 4);
+ private static final String TETHER_DOWNSTREAM6_FS_PATH = makeMapPath(DOWNSTREAM, 6);
+ private static final String TETHER_UPSTREAM6_FS_PATH = makeMapPath(UPSTREAM, 6);
+ private static final String TETHER_STATS_MAP_PATH = makeMapPath("stats");
+ private static final String TETHER_LIMIT_MAP_PATH = makeMapPath("limit");
+ private static final String TETHER_ERROR_MAP_PATH = makeMapPath("error");
+ private static final String TETHER_DEV_MAP_PATH = makeMapPath("dev");
+
+ /** The names of all the BPF counters defined in bpf_tethering.h. */
+ public static final String[] sBpfCounterNames = getBpfCounterNames();
+
+ private static String makeMapPath(String which) {
+ return "/sys/fs/bpf/tethering/map_offload_tether_" + which + "_map";
+ }
+
+ private static String makeMapPath(boolean downstream, int ipVersion) {
+ return makeMapPath((downstream ? "downstream" : "upstream") + ipVersion);
+ }
+
+ @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;
+ @NonNull
+ private final ConntrackMonitor mConntrackMonitor;
+ @Nullable
+ private final BpfTetherStatsProvider mStatsProvider;
+ @NonNull
+ private final BpfCoordinatorShim mBpfCoordinatorShim;
+ @NonNull
+ private final BpfConntrackEventConsumer mBpfConntrackEventConsumer;
+
+ // 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 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 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 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>
+ mIpv6ForwardingRules = new LinkedHashMap<>();
+
+ // Map of downstream client maps. Each of these maps represents the IPv4 clients for a given
+ // downstream. Needed to build IPv4 forwarding rules when conntrack events are received.
+ // 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 client, and deleted when the IpServer deletes
+ // its last client.
+ // Note that relying on the client address for finding downstream is okay for now because the
+ // client address is unique. See PrivateAddressCoordinator#requestDownstreamAddress.
+ // TODO: Refactor if any possible that the client address is not unique.
+ private final HashMap>
+ mTetherClients = new HashMap<>();
+
+ // Set for which downstream is monitoring the conntrack netlink message.
+ private final Set mMonitoringIpServers = new HashSet<>();
+
+ // Map of upstream interface IPv4 address to interface index.
+ // TODO: consider making the key to be unique because the upstream address is not unique. It
+ // is okay for now because there have only one upstream generally.
+ private final HashMap mIpv4UpstreamIndices = new HashMap<>();
+
+ // Map for upstream and downstream pair.
+ private final HashMap> mForwardingPairs = new HashMap<>();
+
+ // Set for upstream and downstream device map. Used for caching BPF dev map status and
+ // reduce duplicate adding or removing map operations. Use LinkedHashSet because the test
+ // BpfCoordinatorTest needs predictable iteration order.
+ private final Set mDeviceMapSet = new LinkedHashSet<>();
+
+ // Runnable that used by scheduling next polling of stats.
+ private final Runnable mScheduledPollingTask = () -> {
+ updateForwardedStats();
+ maybeSchedulePollingStats();
+ };
+
+ // TODO: add BpfMap retrieving function.
+ @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();
+
+ /** Get conntrack monitor. */
+ @NonNull public ConntrackMonitor getConntrackMonitor(ConntrackEventConsumer consumer) {
+ return new ConntrackMonitor(getHandler(), getSharedLog(), consumer);
+ }
+
+ /** Get interface information for a given interface. */
+ @NonNull public InterfaceParams getInterfaceParams(String ifName) {
+ return InterfaceParams.getByName(ifName);
+ }
+
+ /**
+ * Check OS Build at least S.
+ *
+ * TODO: move to BpfCoordinatorShim once the test doesn't need the mocked OS build for
+ * testing different code flows concurrently.
+ */
+ public boolean isAtLeastS() {
+ // TODO: consider using ShimUtils.isAtLeastS.
+ return SdkLevel.isAtLeastS();
+ }
+
+ /** Get downstream4 BPF map. */
+ @Nullable public BpfMap getBpfDownstream4Map() {
+ if (!isAtLeastS()) return null;
+ try {
+ return new BpfMap<>(TETHER_DOWNSTREAM4_MAP_PATH,
+ BpfMap.BPF_F_RDWR, Tether4Key.class, Tether4Value.class);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot create downstream4 map: " + e);
+ return null;
+ }
+ }
+
+ /** Get upstream4 BPF map. */
+ @Nullable public BpfMap getBpfUpstream4Map() {
+ if (!isAtLeastS()) return null;
+ try {
+ return new BpfMap<>(TETHER_UPSTREAM4_MAP_PATH,
+ BpfMap.BPF_F_RDWR, Tether4Key.class, Tether4Value.class);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot create upstream4 map: " + e);
+ return null;
+ }
+ }
+
+ /** Get downstream6 BPF map. */
+ @Nullable public BpfMap getBpfDownstream6Map() {
+ if (!isAtLeastS()) return null;
+ try {
+ return new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH,
+ BpfMap.BPF_F_RDWR, TetherDownstream6Key.class, Tether6Value.class);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot create downstream6 map: " + e);
+ return null;
+ }
+ }
+
+ /** Get upstream6 BPF map. */
+ @Nullable public BpfMap getBpfUpstream6Map() {
+ if (!isAtLeastS()) return null;
+ try {
+ return new BpfMap<>(TETHER_UPSTREAM6_FS_PATH, BpfMap.BPF_F_RDWR,
+ TetherUpstream6Key.class, Tether6Value.class);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot create upstream6 map: " + e);
+ return null;
+ }
+ }
+
+ /** Get stats BPF map. */
+ @Nullable public BpfMap getBpfStatsMap() {
+ if (!isAtLeastS()) return null;
+ try {
+ return new BpfMap<>(TETHER_STATS_MAP_PATH,
+ BpfMap.BPF_F_RDWR, TetherStatsKey.class, TetherStatsValue.class);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot create stats map: " + e);
+ return null;
+ }
+ }
+
+ /** Get limit BPF map. */
+ @Nullable public BpfMap getBpfLimitMap() {
+ if (!isAtLeastS()) return null;
+ try {
+ return new BpfMap<>(TETHER_LIMIT_MAP_PATH,
+ BpfMap.BPF_F_RDWR, TetherLimitKey.class, TetherLimitValue.class);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot create limit map: " + e);
+ return null;
+ }
+ }
+
+ /** Get dev BPF map. */
+ @Nullable public BpfMap getBpfDevMap() {
+ if (!isAtLeastS()) return null;
+ try {
+ return new BpfMap<>(TETHER_DEV_MAP_PATH,
+ BpfMap.BPF_F_RDWR, TetherDevKey.class, TetherDevValue.class);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot create dev map: " + e);
+ return null;
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public BpfCoordinator(@NonNull Dependencies deps) {
+ mDeps = deps;
+ mHandler = mDeps.getHandler();
+ mNetd = mDeps.getNetd();
+ mLog = mDeps.getSharedLog().forSubComponent(TAG);
+ mIsBpfEnabled = isBpfEnabled();
+
+ // The conntrack consummer needs to be initialized in BpfCoordinator constructor because it
+ // have to access the data members of BpfCoordinator which is not a static class. The
+ // consumer object is also needed for initializing the conntrack monitor which may be
+ // mocked for testing.
+ mBpfConntrackEventConsumer = new BpfConntrackEventConsumer();
+ mConntrackMonitor = mDeps.getConntrackMonitor(mBpfConntrackEventConsumer);
+
+ 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;
+
+ mBpfCoordinatorShim = BpfCoordinatorShim.getBpfCoordinatorShim(deps);
+ if (!mBpfCoordinatorShim.isInitialized()) {
+ mLog.e("Bpf shim not initialized");
+ }
+ }
+
+ /**
+ * 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 (!isUsingBpf()) {
+ mLog.i("BPF is not using");
+ 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);
+ }
+ updateForwardedStats();
+ mPollingStarted = false;
+
+ mLog.i("Polling stopped");
+ }
+
+ private boolean isUsingBpf() {
+ return mIsBpfEnabled && mBpfCoordinatorShim.isInitialized();
+ }
+
+ /**
+ * Start conntrack message monitoring.
+ * Note that this can be only called on handler thread.
+ *
+ * TODO: figure out a better logging for non-interesting conntrack message.
+ * For example, the following logging is an IPCTNL_MSG_CT_GET message but looks scary.
+ * +---------------------------------------------------------------------------+
+ * | ERROR unparsable netlink msg: 1400000001010103000000000000000002000000 |
+ * +------------------+--------------------------------------------------------+
+ * | | struct nlmsghdr |
+ * | 14000000 | length = 20 |
+ * | 0101 | type = NFNL_SUBSYS_CTNETLINK << 8 | IPCTNL_MSG_CT_GET |
+ * | 0103 | flags |
+ * | 00000000 | seqno = 0 |
+ * | 00000000 | pid = 0 |
+ * | | struct nfgenmsg |
+ * | 02 | nfgen_family = AF_INET |
+ * | 00 | version = NFNETLINK_V0 |
+ * | 0000 | res_id |
+ * +------------------+--------------------------------------------------------+
+ * See NetlinkMonitor#handlePacket, NetlinkMessage#parseNfMessage.
+ */
+ public void startMonitoring(@NonNull final IpServer ipServer) {
+ // TODO: Wrap conntrackMonitor starting function into mBpfCoordinatorShim.
+ if (!isUsingBpf() || !mDeps.isAtLeastS()) return;
+
+ if (mMonitoringIpServers.contains(ipServer)) {
+ Log.wtf(TAG, "The same downstream " + ipServer.interfaceName()
+ + " should not start monitoring twice.");
+ return;
+ }
+
+ if (mMonitoringIpServers.isEmpty()) {
+ mConntrackMonitor.start();
+ mLog.i("Monitoring started");
+ }
+
+ mMonitoringIpServers.add(ipServer);
+ }
+
+ /**
+ * Stop conntrack event monitoring.
+ * Note that this can be only called on handler thread.
+ */
+ public void stopMonitoring(@NonNull final IpServer ipServer) {
+ // TODO: Wrap conntrackMonitor stopping function into mBpfCoordinatorShim.
+ if (!isUsingBpf() || !mDeps.isAtLeastS()) return;
+
+ mMonitoringIpServers.remove(ipServer);
+
+ if (!mMonitoringIpServers.isEmpty()) return;
+
+ mConntrackMonitor.stop();
+ mLog.i("Monitoring 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 (!isUsingBpf()) return;
+
+ // TODO: Perhaps avoid to add a duplicate rule.
+ if (!mBpfCoordinatorShim.tetherOffloadRuleAdd(rule)) return;
+
+ if (!mIpv6ForwardingRules.containsKey(ipServer)) {
+ mIpv6ForwardingRules.put(ipServer, new LinkedHashMap());
+ }
+ LinkedHashMap rules = mIpv6ForwardingRules.get(ipServer);
+
+ // Add upstream and downstream interface index to dev map.
+ maybeAddDevMap(rule.upstreamIfindex, rule.downstreamIfindex);
+
+ // When the first rule is added to an upstream, setup upstream forwarding and data limit.
+ maybeSetLimit(rule.upstreamIfindex);
+
+ if (!isAnyRuleFromDownstreamToUpstream(rule.downstreamIfindex, rule.upstreamIfindex)) {
+ final int downstream = rule.downstreamIfindex;
+ final int upstream = rule.upstreamIfindex;
+ // TODO: support upstream forwarding on non-point-to-point interfaces.
+ // TODO: get the MTU from LinkProperties and update the rules when it changes.
+ if (!mBpfCoordinatorShim.startUpstreamIpv6Forwarding(downstream, upstream, rule.srcMac,
+ NULL_MAC_ADDRESS, NULL_MAC_ADDRESS, NetworkStackConstants.ETHER_MTU)) {
+ mLog.e("Failed to enable upstream IPv6 forwarding from "
+ + mInterfaceNames.get(downstream) + " to " + mInterfaceNames.get(upstream));
+ }
+ }
+
+ // 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 (!isUsingBpf()) return;
+
+ if (!mBpfCoordinatorShim.tetherOffloadRuleRemove(rule)) return;
+
+ LinkedHashMap 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);
+ }
+
+ // If no more rules between this upstream and downstream, stop upstream forwarding.
+ if (!isAnyRuleFromDownstreamToUpstream(rule.downstreamIfindex, rule.upstreamIfindex)) {
+ final int downstream = rule.downstreamIfindex;
+ final int upstream = rule.upstreamIfindex;
+ if (!mBpfCoordinatorShim.stopUpstreamIpv6Forwarding(downstream, upstream,
+ rule.srcMac)) {
+ mLog.e("Failed to disable upstream IPv6 forwarding from "
+ + mInterfaceNames.get(downstream) + " to " + mInterfaceNames.get(upstream));
+ }
+ }
+
+ // Do cleanup functionality if there is no more rule on the given upstream.
+ maybeClearLimit(rule.upstreamIfindex);
+ }
+
+ /**
+ * 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 (!isUsingBpf()) return;
+
+ final LinkedHashMap 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(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 (!isUsingBpf()) return;
+
+ final LinkedHashMap rules = mIpv6ForwardingRules.get(
+ ipServer);
+ if (rules == null) return;
+
+ // Need to build a rule list because the rule map may be changed in the iteration.
+ // First remove all the old rules, then add all the new rules. This is because the upstream
+ // forwarding code in tetherOffloadRuleAdd cannot support rules on two upstreams at the
+ // same time. Deleting the rules first ensures that upstream forwarding is disabled on the
+ // old upstream when the last rule is removed from it, and re-enabled on the new upstream
+ // when the first rule is added to it.
+ // TODO: Once the IPv6 client processing code has moved from IpServer to BpfCoordinator, do
+ // something smarter.
+ final ArrayList rulesCopy = new ArrayList<>(rules.values());
+ for (final Ipv6ForwardingRule rule : rulesCopy) {
+ // 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);
+ }
+ for (final Ipv6ForwardingRule rule : rulesCopy) {
+ 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 (!isUsingBpf()) 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);
+ }
+ }
+
+ /**
+ * Add downstream client.
+ */
+ public void tetherOffloadClientAdd(@NonNull final IpServer ipServer,
+ @NonNull final ClientInfo client) {
+ if (!isUsingBpf()) return;
+
+ if (!mTetherClients.containsKey(ipServer)) {
+ mTetherClients.put(ipServer, new HashMap());
+ }
+
+ HashMap clients = mTetherClients.get(ipServer);
+ clients.put(client.clientAddress, client);
+ }
+
+ /**
+ * Remove downstream client.
+ */
+ public void tetherOffloadClientRemove(@NonNull final IpServer ipServer,
+ @NonNull final ClientInfo client) {
+ if (!isUsingBpf()) return;
+
+ HashMap clients = mTetherClients.get(ipServer);
+ if (clients == null) return;
+
+ // 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 (clients.remove(client.clientAddress) == null) return;
+
+ // Remove the downstream entry if it has no more rule.
+ if (clients.isEmpty()) {
+ mTetherClients.remove(ipServer);
+ }
+ }
+
+ /**
+ * Call when UpstreamNetworkState may be changed.
+ * If upstream has ipv4 for tethering, update this new UpstreamNetworkState to map. The
+ * upstream interface index and its address mapping is prepared for building IPv4
+ * offload rule.
+ *
+ * TODO: Delete the unused upstream interface mapping.
+ * TODO: Support ether ip upstream interface.
+ */
+ public void addUpstreamIfindexToMap(LinkProperties lp) {
+ if (!mPollingStarted) return;
+
+ // This will not work on a network that is using 464xlat because hasIpv4Address will not be
+ // true.
+ // TODO: need to consider 464xlat.
+ if (lp == null || !lp.hasIpv4Address()) return;
+
+ // Support raw ip upstream interface only.
+ final InterfaceParams params = mDeps.getInterfaceParams(lp.getInterfaceName());
+ if (params == null || params.hasMacAddress) return;
+
+ Collection addresses = lp.getAddresses();
+ for (InetAddress addr: addresses) {
+ if (addr instanceof Inet4Address) {
+ Inet4Address i4addr = (Inet4Address) addr;
+ if (!i4addr.isAnyLocalAddress() && !i4addr.isLinkLocalAddress()
+ && !i4addr.isLoopbackAddress() && !i4addr.isMulticastAddress()) {
+ mIpv4UpstreamIndices.put(i4addr, params.index);
+ }
+ }
+ }
+ }
+
+ /**
+ * Attach BPF program
+ *
+ * TODO: consider error handling if the attach program failed.
+ */
+ public void maybeAttachProgram(@NonNull String intIface, @NonNull String extIface) {
+ if (forwardingPairExists(intIface, extIface)) return;
+
+ boolean firstDownstreamForThisUpstream = !isAnyForwardingPairOnUpstream(extIface);
+ forwardingPairAdd(intIface, extIface);
+
+ mBpfCoordinatorShim.attachProgram(intIface, UPSTREAM);
+ // Attach if the upstream is the first time to be used in a forwarding pair.
+ if (firstDownstreamForThisUpstream) {
+ mBpfCoordinatorShim.attachProgram(extIface, DOWNSTREAM);
+ }
+ }
+
+ /**
+ * Detach BPF program
+ */
+ public void maybeDetachProgram(@NonNull String intIface, @NonNull String extIface) {
+ forwardingPairRemove(intIface, extIface);
+
+ // Detaching program may fail because the interface has been removed already.
+ mBpfCoordinatorShim.detachProgram(intIface);
+ // Detach if no more forwarding pair is using the upstream.
+ if (!isAnyForwardingPairOnUpstream(extIface)) {
+ mBpfCoordinatorShim.detachProgram(extIface);
+ }
+ }
+
+ // TODO: make mInterfaceNames accessible to the shim and move this code to there.
+ private String getIfName(long ifindex) {
+ return mInterfaceNames.get((int) ifindex, Long.toString(ifindex));
+ }
+
+ /**
+ * 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) {
+ 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("Bpf shim: " + mBpfCoordinatorShim.toString());
+
+ pw.println("Forwarding stats:");
+ pw.increaseIndent();
+ if (mStats.size() == 0) {
+ pw.println("");
+ } else {
+ dumpStats(pw);
+ }
+ pw.decreaseIndent();
+
+ pw.println("Forwarding rules:");
+ pw.increaseIndent();
+ dumpIpv6UpstreamRules(pw);
+ dumpIpv6ForwardingRules(pw);
+ dumpIpv4ForwardingRules(pw);
+ pw.decreaseIndent();
+
+ pw.println("Device map:");
+ pw.increaseIndent();
+ dumpDevmap(pw);
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("Forwarding counters:");
+ pw.increaseIndent();
+ dumpCounters(pw);
+ pw.decreaseIndent();
+ }
+
+ 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) {
+ if (mIpv6ForwardingRules.size() == 0) {
+ pw.println("No IPv6 rules");
+ return;
+ }
+
+ for (Map.Entry> 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 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.getHostAddress(), rule.srcMac, rule.dstMac));
+ }
+ pw.decreaseIndent();
+ }
+ }
+
+ private String ipv6UpstreamRuletoString(TetherUpstream6Key key, Tether6Value value) {
+ return String.format("%d(%s) %s -> %d(%s) %04x %s %s",
+ key.iif, getIfName(key.iif), key.dstMac, value.oif, getIfName(value.oif),
+ value.ethProto, value.ethSrcMac, value.ethDstMac);
+ }
+
+ private void dumpIpv6UpstreamRules(IndentingPrintWriter pw) {
+ try (BpfMap map = mDeps.getBpfUpstream6Map()) {
+ if (map == null) {
+ pw.println("No IPv6 upstream");
+ return;
+ }
+ if (map.isEmpty()) {
+ pw.println("No IPv6 upstream rules");
+ return;
+ }
+ map.forEach((k, v) -> pw.println(ipv6UpstreamRuletoString(k, v)));
+ } catch (ErrnoException e) {
+ pw.println("Error dumping IPv6 upstream map: " + e);
+ }
+ }
+
+ private String ipv4RuleToString(Tether4Key key, Tether4Value value) {
+ final String private4, public4, dst4;
+ try {
+ private4 = InetAddress.getByAddress(key.src4).getHostAddress();
+ dst4 = InetAddress.getByAddress(key.dst4).getHostAddress();
+ public4 = InetAddress.getByAddress(value.src46).getHostAddress();
+ } catch (UnknownHostException impossible) {
+ throw new AssertionError("4-byte array not valid IPv4 address!");
+ }
+ return String.format("[%s] %d(%s) %s:%d -> %d(%s) %s:%d -> %s:%d",
+ key.dstMac, key.iif, getIfName(key.iif), private4, key.srcPort,
+ value.oif, getIfName(value.oif),
+ public4, value.srcPort, dst4, key.dstPort);
+ }
+
+ private void dumpIpv4ForwardingRules(IndentingPrintWriter pw) {
+ try (BpfMap map = mDeps.getBpfUpstream4Map()) {
+ if (map == null) {
+ pw.println("No IPv4 support");
+ return;
+ }
+ if (map.isEmpty()) {
+ pw.println("No IPv4 rules");
+ return;
+ }
+ pw.println("IPv4: [inDstMac] iif(iface) src -> nat -> dst");
+ pw.increaseIndent();
+ map.forEach((k, v) -> pw.println(ipv4RuleToString(k, v)));
+ } catch (ErrnoException e) {
+ pw.println("Error dumping IPv4 map: " + e);
+ }
+ pw.decreaseIndent();
+ }
+
+ /**
+ * Simple struct that only contains a u32. Must be public because Struct needs access to it.
+ * TODO: make this a public inner class of Struct so anyone can use it as, e.g., Struct.U32?
+ */
+ public static class U32Struct extends Struct {
+ @Struct.Field(order = 0, type = Struct.Type.U32)
+ public long val;
+ }
+
+ private void dumpCounters(@NonNull IndentingPrintWriter pw) {
+ if (!mDeps.isAtLeastS()) {
+ pw.println("No counter support");
+ return;
+ }
+ try (BpfMap map = new BpfMap<>(TETHER_ERROR_MAP_PATH,
+ BpfMap.BPF_F_RDONLY, U32Struct.class, U32Struct.class)) {
+
+ map.forEach((k, v) -> {
+ String counterName;
+ try {
+ counterName = sBpfCounterNames[(int) k.val];
+ } catch (IndexOutOfBoundsException e) {
+ // Should never happen because this code gets the counter name from the same
+ // include file as the BPF program that increments the counter.
+ Log.wtf(TAG, "Unknown tethering counter type " + k.val);
+ counterName = Long.toString(k.val);
+ }
+ if (v.val > 0) pw.println(String.format("%s: %d", counterName, v.val));
+ });
+ } catch (ErrnoException e) {
+ pw.println("Error dumping counter map: " + e);
+ }
+ }
+
+ private void dumpDevmap(@NonNull IndentingPrintWriter pw) {
+ try (BpfMap map = mDeps.getBpfDevMap()) {
+ if (map == null) {
+ pw.println("No devmap support");
+ return;
+ }
+ if (map.isEmpty()) {
+ pw.println("No interface index");
+ return;
+ }
+ pw.println("ifindex (iface) -> ifindex (iface)");
+ pw.increaseIndent();
+ map.forEach((k, v) -> {
+ // Only get upstream interface name. Just do the best to make the index readable.
+ // TODO: get downstream interface name because the index is either upstrema or
+ // downstream interface in dev map.
+ pw.println(String.format("%d (%s) -> %d (%s)", k.ifIndex, getIfName(k.ifIndex),
+ v.ifIndex, getIfName(v.ifIndex)));
+ });
+ } catch (ErrnoException e) {
+ pw.println("Error dumping dev map: " + e);
+ }
+ pw.decreaseIndent();
+ }
+
+ /** IPv6 forwarding rule class. */
+ public static class Ipv6ForwardingRule {
+ // The upstream6 and downstream6 rules are built as the following tables. Only raw ip
+ // upstream interface is supported.
+ // TODO: support ether ip upstream interface.
+ //
+ // NAT network topology:
+ //
+ // public network (rawip) private network
+ // | UE |
+ // +------------+ V +------------+------------+ V +------------+
+ // | Sever +---------+ Upstream | Downstream +---------+ Client |
+ // +------------+ +------------+------------+ +------------+
+ //
+ // upstream6 key and value:
+ //
+ // +------+-------------+
+ // | TetherUpstream6Key |
+ // +------+------+------+
+ // |field |iif |dstMac|
+ // | | | |
+ // +------+------+------+
+ // |value |downst|downst|
+ // | |ream |ream |
+ // +------+------+------+
+ //
+ // +------+----------------------------------+
+ // | |Tether6Value |
+ // +------+------+------+------+------+------+
+ // |field |oif |ethDst|ethSrc|ethPro|pmtu |
+ // | | |mac |mac |to | |
+ // +------+------+------+------+------+------+
+ // |value |upstre|-- |-- |ETH_P_|1500 |
+ // | |am | | |IP | |
+ // +------+------+------+------+------+------+
+ //
+ // downstream6 key and value:
+ //
+ // +------+--------------------+
+ // | |TetherDownstream6Key|
+ // +------+------+------+------+
+ // |field |iif |dstMac|neigh6|
+ // | | | | |
+ // +------+------+------+------+
+ // |value |upstre|-- |client|
+ // | |am | | |
+ // +------+------+------+------+
+ //
+ // +------+----------------------------------+
+ // | |Tether6Value |
+ // +------+------+------+------+------+------+
+ // |field |oif |ethDst|ethSrc|ethPro|pmtu |
+ // | | |mac |mac |to | |
+ // +------+------+------+------+------+------+
+ // |value |downst|client|downst|ETH_P_|1500 |
+ // | |ream | |ream |IP | |
+ // +------+------+------+------+------+------+
+ //
+ public final int upstreamIfindex;
+ public final int downstreamIfindex;
+
+ // TODO: store a ClientInfo object instead of storing address, srcMac, and dstMac directly.
+ @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;
+ }
+
+ /**
+ * Return a TetherDownstream6Key object built from the rule.
+ */
+ @NonNull
+ public TetherDownstream6Key makeTetherDownstream6Key() {
+ return new TetherDownstream6Key(upstreamIfindex, NULL_MAC_ADDRESS,
+ address.getAddress());
+ }
+
+ /**
+ * Return a Tether6Value object built from the rule.
+ */
+ @NonNull
+ public Tether6Value makeTether6Value() {
+ return new Tether6Value(downstreamIfindex, dstMac, srcMac, ETH_P_IPV6,
+ NetworkStackConstants.ETHER_MTU);
+ }
+
+ @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);
+ }
+ }
+
+ /** Tethering client information class. */
+ public static class ClientInfo {
+ public final int downstreamIfindex;
+
+ @NonNull
+ public final MacAddress downstreamMac;
+ @NonNull
+ public final Inet4Address clientAddress;
+ @NonNull
+ public final MacAddress clientMac;
+
+ public ClientInfo(int downstreamIfindex,
+ @NonNull MacAddress downstreamMac, @NonNull Inet4Address clientAddress,
+ @NonNull MacAddress clientMac) {
+ this.downstreamIfindex = downstreamIfindex;
+ this.downstreamMac = downstreamMac;
+ this.clientAddress = clientAddress;
+ this.clientMac = clientMac;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ClientInfo)) return false;
+ ClientInfo that = (ClientInfo) o;
+ return this.downstreamIfindex == that.downstreamIfindex
+ && Objects.equals(this.downstreamMac, that.downstreamMac)
+ && Objects.equals(this.clientAddress, that.clientAddress)
+ && Objects.equals(this.clientMac, that.clientMac);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(downstreamIfindex, downstreamMac, clientAddress, clientMac);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("downstream: %d (%s), client: %s (%s)",
+ downstreamIfindex, downstreamMac, clientAddress, clientMac);
+ }
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ @Nullable
+ private ClientInfo getClientInfo(@NonNull Inet4Address clientAddress) {
+ for (HashMap clients : mTetherClients.values()) {
+ for (ClientInfo client : clients.values()) {
+ if (clientAddress.equals(client.clientAddress)) {
+ return client;
+ }
+ }
+ }
+ return null;
+ }
+
+ // Support raw ip only.
+ // TODO: add ether ip support.
+ // TODO: parse CTA_PROTOINFO of conntrack event in ConntrackMonitor. For TCP, only add rules
+ // while TCP status is established.
+ @VisibleForTesting
+ class BpfConntrackEventConsumer implements ConntrackEventConsumer {
+ @NonNull
+ private Tether4Key makeTetherUpstream4Key(
+ @NonNull ConntrackEvent e, @NonNull ClientInfo c) {
+ return new Tether4Key(c.downstreamIfindex, c.downstreamMac,
+ e.tupleOrig.protoNum, e.tupleOrig.srcIp.getAddress(),
+ e.tupleOrig.dstIp.getAddress(), e.tupleOrig.srcPort, e.tupleOrig.dstPort);
+ }
+
+ @NonNull
+ private Tether4Key makeTetherDownstream4Key(
+ @NonNull ConntrackEvent e, @NonNull ClientInfo c, int upstreamIndex) {
+ return new Tether4Key(upstreamIndex, NULL_MAC_ADDRESS /* dstMac (rawip) */,
+ e.tupleReply.protoNum, e.tupleReply.srcIp.getAddress(),
+ e.tupleReply.dstIp.getAddress(), e.tupleReply.srcPort, e.tupleReply.dstPort);
+ }
+
+ @NonNull
+ private Tether4Value makeTetherUpstream4Value(@NonNull ConntrackEvent e,
+ int upstreamIndex) {
+ return new Tether4Value(upstreamIndex,
+ NULL_MAC_ADDRESS /* ethDstMac (rawip) */,
+ NULL_MAC_ADDRESS /* ethSrcMac (rawip) */, ETH_P_IP,
+ NetworkStackConstants.ETHER_MTU, toIpv4MappedAddressBytes(e.tupleReply.dstIp),
+ toIpv4MappedAddressBytes(e.tupleReply.srcIp), e.tupleReply.dstPort,
+ e.tupleReply.srcPort, 0 /* lastUsed, filled by bpf prog only */);
+ }
+
+ @NonNull
+ private Tether4Value makeTetherDownstream4Value(@NonNull ConntrackEvent e,
+ @NonNull ClientInfo c, int upstreamIndex) {
+ return new Tether4Value(c.downstreamIfindex,
+ c.clientMac, c.downstreamMac, ETH_P_IP, NetworkStackConstants.ETHER_MTU,
+ toIpv4MappedAddressBytes(e.tupleOrig.dstIp),
+ toIpv4MappedAddressBytes(e.tupleOrig.srcIp),
+ e.tupleOrig.dstPort, e.tupleOrig.srcPort,
+ 0 /* lastUsed, filled by bpf prog only */);
+ }
+
+ @NonNull
+ private byte[] toIpv4MappedAddressBytes(Inet4Address ia4) {
+ final byte[] addr4 = ia4.getAddress();
+ final byte[] addr6 = new byte[16];
+ addr6[10] = (byte) 0xff;
+ addr6[11] = (byte) 0xff;
+ addr6[12] = addr4[0];
+ addr6[13] = addr4[1];
+ addr6[14] = addr4[2];
+ addr6[15] = addr4[3];
+ return addr6;
+ }
+
+ public void accept(ConntrackEvent e) {
+ final ClientInfo tetherClient = getClientInfo(e.tupleOrig.srcIp);
+ if (tetherClient == null) return;
+
+ final Integer upstreamIndex = mIpv4UpstreamIndices.get(e.tupleReply.dstIp);
+ if (upstreamIndex == null) return;
+
+ final Tether4Key upstream4Key = makeTetherUpstream4Key(e, tetherClient);
+ final Tether4Key downstream4Key = makeTetherDownstream4Key(e, tetherClient,
+ upstreamIndex);
+
+ if (e.msgType == (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8
+ | NetlinkConstants.IPCTNL_MSG_CT_DELETE)) {
+ mBpfCoordinatorShim.tetherOffloadRuleRemove(UPSTREAM, upstream4Key);
+ mBpfCoordinatorShim.tetherOffloadRuleRemove(DOWNSTREAM, downstream4Key);
+ maybeClearLimit(upstreamIndex);
+ return;
+ }
+
+ final Tether4Value upstream4Value = makeTetherUpstream4Value(e, upstreamIndex);
+ final Tether4Value downstream4Value = makeTetherDownstream4Value(e, tetherClient,
+ upstreamIndex);
+
+ maybeAddDevMap(upstreamIndex, tetherClient.downstreamIfindex);
+ maybeSetLimit(upstreamIndex);
+ mBpfCoordinatorShim.tetherOffloadRuleAdd(UPSTREAM, upstream4Key, upstream4Value);
+ mBpfCoordinatorShim.tetherOffloadRuleAdd(DOWNSTREAM, downstream4Key, downstream4Value);
+ }
+ }
+
+ private boolean isBpfEnabled() {
+ final TetheringConfiguration config = mDeps.getTetherConfig();
+ return (config != null) ? config.isBpfOffloadEnabled() : true /* default value */;
+ }
+
+ private int getInterfaceIndexFromRules(@NonNull String ifName) {
+ for (LinkedHashMap 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 sendDataLimitToBpfMap(int ifIndex, long quotaBytes) {
+ if (ifIndex == 0) {
+ Log.wtf(TAG, "Invalid interface index.");
+ return false;
+ }
+
+ return mBpfCoordinatorShim.tetherOffloadSetInterfaceQuota(ifIndex, quotaBytes);
+ }
+
+ // 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);
+ sendDataLimitToBpfMap(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 sendDataLimitToBpfMap(ifIndex, quotaBytes);
+ }
+
+ private void maybeSetLimit(int upstreamIfindex) {
+ if (isAnyRuleOnUpstream(upstreamIfindex)
+ || mBpfCoordinatorShim.isAnyIpv4RuleOnUpstream(upstreamIfindex)) {
+ return;
+ }
+
+ // 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.");
+ }
+ }
+
+ // TODO: This should be also called while IpServer wants to clear all IPv4 rules. Relying on
+ // conntrack event can't cover this case.
+ private void maybeClearLimit(int upstreamIfindex) {
+ if (isAnyRuleOnUpstream(upstreamIfindex)
+ || mBpfCoordinatorShim.isAnyIpv4RuleOnUpstream(upstreamIfindex)) {
+ return;
+ }
+
+ final TetherStatsValue statsValue =
+ mBpfCoordinatorShim.tetherOffloadGetAndClearStats(upstreamIfindex);
+ if (statsValue == null) {
+ Log.wtf(TAG, "Fail to cleanup tether stats for upstream index " + upstreamIfindex);
+ return;
+ }
+
+ SparseArray tetherStatsList = new SparseArray();
+ tetherStatsList.put(upstreamIfindex, statsValue);
+
+ // Update the last stats delta and delete the local cache for a given upstream.
+ updateQuotaAndStatsFromSnapshot(tetherStatsList);
+ mStats.remove(upstreamIfindex);
+ }
+
+ // TODO: Rename to isAnyIpv6RuleOnUpstream and define an isAnyRuleOnUpstream method that called
+ // both isAnyIpv6RuleOnUpstream and mBpfCoordinatorShim.isAnyIpv4RuleOnUpstream.
+ private boolean isAnyRuleOnUpstream(int upstreamIfindex) {
+ for (LinkedHashMap rules : mIpv6ForwardingRules
+ .values()) {
+ for (Ipv6ForwardingRule rule : rules.values()) {
+ if (upstreamIfindex == rule.upstreamIfindex) return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isAnyRuleFromDownstreamToUpstream(int downstreamIfindex, int upstreamIfindex) {
+ for (LinkedHashMap rules : mIpv6ForwardingRules
+ .values()) {
+ for (Ipv6ForwardingRule rule : rules.values()) {
+ if (downstreamIfindex == rule.downstreamIfindex
+ && upstreamIfindex == rule.upstreamIfindex) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ // TODO: remove the index from map while the interface has been removed because the map size
+ // is 64 entries. See packages\modules\Connectivity\Tethering\bpf_progs\offload.c.
+ private void maybeAddDevMap(int upstreamIfindex, int downstreamIfindex) {
+ for (Integer index : new Integer[] {upstreamIfindex, downstreamIfindex}) {
+ if (mDeviceMapSet.contains(index)) continue;
+ if (mBpfCoordinatorShim.addDevMap(index)) mDeviceMapSet.add(index);
+ }
+ }
+
+ private void forwardingPairAdd(@NonNull String intIface, @NonNull String extIface) {
+ if (!mForwardingPairs.containsKey(extIface)) {
+ mForwardingPairs.put(extIface, new HashSet());
+ }
+ mForwardingPairs.get(extIface).add(intIface);
+ }
+
+ private void forwardingPairRemove(@NonNull String intIface, @NonNull String extIface) {
+ HashSet downstreams = mForwardingPairs.get(extIface);
+ if (downstreams == null) return;
+ if (!downstreams.remove(intIface)) return;
+
+ if (downstreams.isEmpty()) {
+ mForwardingPairs.remove(extIface);
+ }
+ }
+
+ private boolean forwardingPairExists(@NonNull String intIface, @NonNull String extIface) {
+ if (!mForwardingPairs.containsKey(extIface)) return false;
+
+ return mForwardingPairs.get(extIface).contains(intIface);
+ }
+
+ private boolean isAnyForwardingPairOnUpstream(@NonNull String extIface) {
+ return mForwardingPairs.containsKey(extIface);
+ }
+
+ @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 SparseArray tetherStatsList) {
+ long usedAlertQuota = 0;
+ for (int i = 0; i < tetherStatsList.size(); i++) {
+ final Integer ifIndex = tetherStatsList.keyAt(i);
+ final TetherStatsValue tetherStats = tetherStatsList.valueAt(i);
+ 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 updateForwardedStats() {
+ final SparseArray tetherStatsList =
+ mBpfCoordinatorShim.tetherOffloadGetStats();
+
+ if (tetherStatsList == null) {
+ mLog.e("Problem fetching tethering stats");
+ 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>
+ 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 getInterfaceNamesForTesting() {
+ return mInterfaceNames;
+ }
+
+ // Return BPF conntrack event consumer. This is used for testing only.
+ // Note that this can be only called on handler thread.
+ @NonNull
+ @VisibleForTesting
+ final BpfConntrackEventConsumer getBpfConntrackEventConsumerForTesting() {
+ return mBpfConntrackEventConsumer;
+ }
+
+ private static native String[] getBpfCounterNames();
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfMap.java b/Tethering/src/com/android/networkstack/tethering/BpfMap.java
new file mode 100644
index 0000000000..1363dc5150
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/BpfMap.java
@@ -0,0 +1,288 @@
+/*
+ * 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.system.OsConstants.EEXIST;
+import static android.system.OsConstants.ENOENT;
+
+import android.system.ErrnoException;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.Struct;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.function.BiConsumer;
+
+/**
+ * BpfMap is a key -> value mapping structure that is designed to maintained the bpf map entries.
+ * This is a wrapper class of in-kernel data structure. The in-kernel data can be read/written by
+ * passing syscalls with map file descriptor.
+ *
+ * @param the key of the map.
+ * @param