diff options
author | Xiao Ma <xiaom@google.com> | 2019-08-26 17:24:26 +0900 |
---|---|---|
committer | Xiao Ma <xiaom@google.com> | 2019-12-27 17:03:09 +0900 |
commit | 8bcd6735907353caa493d232f01a3fa191bc2f4c (patch) | |
tree | 8fefdb16aa50f0a31662a37c6f29d44bfa8ff6c1 /src/android/net | |
parent | 7b60bfa137ad7ec9213ec4c2169cea4f303e2ce0 (diff) |
Provide RRO configuration to send DHCP client hostname option.
Add a configurable option in the RRO which controls whether or not to
send the hostname set in the Settings->About phone->Device name. The
option in RRO is false by default, that means DHCP Request still not
include any hostname by default. Once the option is overlaid and enabled,
the device name after transliteration will be wrote into hostname option.
Bug: 131783527
Test: atest NetworkStackTests NetworkStackIntegrationTests
Test: manual test, create empty APK to overlay the RRO configuration.
Change-Id: I9af0b0d9e7bb526d3a3c1003bb99d0a3d69b1e9e
Diffstat (limited to 'src/android/net')
-rw-r--r-- | src/android/net/dhcp/DhcpClient.java | 33 | ||||
-rw-r--r-- | src/android/net/dhcp/DhcpPacket.java | 35 | ||||
-rw-r--r-- | src/android/net/util/HostnameTransliterator.java | 103 |
3 files changed, 162 insertions, 9 deletions
diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java index 982d8ce..b1df996 100644 --- a/src/android/net/dhcp/DhcpClient.java +++ b/src/android/net/dhcp/DhcpClient.java @@ -68,6 +68,7 @@ import android.net.ipmemorystore.OnStatusListener; import android.net.metrics.DhcpClientEvent; import android.net.metrics.DhcpErrorEvent; import android.net.metrics.IpConnectivityLog; +import android.net.util.HostnameTransliterator; import android.net.util.InterfaceParams; import android.net.util.NetworkStackUtils; import android.net.util.PacketReader; @@ -76,6 +77,7 @@ import android.os.Handler; import android.os.Message; import android.os.PowerManager; import android.os.SystemClock; +import android.provider.Settings; import android.system.ErrnoException; import android.system.Os; import android.util.EventLog; @@ -306,6 +308,8 @@ public class DhcpClient extends StateMachine { private final NetworkStackIpMemoryStore mIpMemoryStore; @Nullable private DhcpPacketHandler mDhcpPacketHandler; + @Nullable + private final String mHostname; // Milliseconds SystemClock timestamps used to record transition times to DhcpBoundState. private long mLastInitEnterTime; @@ -350,6 +354,22 @@ public class DhcpClient extends StateMachine { } /** + * Get the configuration from RRO to check whether or not to send hostname option in + * DHCPDISCOVER/DHCPREQUEST message. + */ + public boolean getSendHostnameOption(final Context context) { + return context.getResources().getBoolean(R.bool.config_dhcp_client_hostname); + } + + /** + * Get the device name from system settings. + */ + public String getDeviceName(final Context context) { + return Settings.Global.getString(context.getContentResolver(), + Settings.Global.DEVICE_NAME); + } + + /** * Get a IpMemoryStore instance. */ public NetworkStackIpMemoryStore getIpMemoryStore() { @@ -450,6 +470,11 @@ public class DhcpClient extends StateMachine { mRenewAlarm = makeWakeupMessage("RENEW", CMD_RENEW_DHCP); mRebindAlarm = makeWakeupMessage("REBIND", CMD_REBIND_DHCP); mExpiryAlarm = makeWakeupMessage("EXPIRY", CMD_EXPIRE_DHCP); + + // Transliterate hostname read from system settings if RRO option is enabled. + final boolean sendHostname = deps.getSendHostnameOption(context); + mHostname = sendHostname ? new HostnameTransliterator().transliterate( + deps.getDeviceName(mContext)) : null; } public void registerForPreDhcpNotification() { @@ -641,7 +666,7 @@ public class DhcpClient extends StateMachine { private boolean sendDiscoverPacket() { final ByteBuffer packet = DhcpPacket.buildDiscoverPacket( DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr, - DO_UNICAST, REQUESTED_PARAMS, isDhcpRapidCommitEnabled()); + DO_UNICAST, REQUESTED_PARAMS, isDhcpRapidCommitEnabled(), mHostname); return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST); } @@ -655,7 +680,7 @@ public class DhcpClient extends StateMachine { final ByteBuffer packet = DhcpPacket.buildRequestPacket( encap, mTransactionId, getSecs(), clientAddress, DO_UNICAST, mHwAddr, requestedAddress, - serverAddress, REQUESTED_PARAMS, null); + serverAddress, REQUESTED_PARAMS, mHostname); String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null; String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() + " request=" + requestedAddress.getHostAddress() + @@ -744,7 +769,7 @@ public class DhcpClient extends StateMachine { mDhcpLease = results; if (mDhcpLease.dnsServers.isEmpty()) { // supplement customized dns servers - String[] dnsServersList = + final String[] dnsServersList = mContext.getResources().getStringArray(R.array.config_default_dns_servers); for (final String dnsServer : dnsServersList) { try { @@ -1259,7 +1284,7 @@ public class DhcpClient extends StateMachine { final Layer2PacketParcelable l2Packet = new Layer2PacketParcelable(); final ByteBuffer packet = DhcpPacket.buildDiscoverPacket( DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr, - DO_UNICAST, REQUESTED_PARAMS, true /* rapid commit */); + DO_UNICAST, REQUESTED_PARAMS, true /* rapid commit */, mHostname); l2Packet.dstMacAddress = MacAddress.fromBytes(DhcpPacket.ETHER_BROADCAST); l2Packet.payload = packet.array(); diff --git a/src/android/net/dhcp/DhcpPacket.java b/src/android/net/dhcp/DhcpPacket.java index c5700b3..9eca0b5 100644 --- a/src/android/net/dhcp/DhcpPacket.java +++ b/src/android/net/dhcp/DhcpPacket.java @@ -1,3 +1,19 @@ +/* + * 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.dhcp; import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ALL; @@ -15,6 +31,8 @@ import android.text.TextUtils; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.android.networkstack.apishim.ShimUtils; + import java.io.UnsupportedEncodingException; import java.net.Inet4Address; import java.net.UnknownHostException; @@ -350,7 +368,6 @@ public abstract class DhcpPacket { // Set in unit tests, to ensure that the test does not break when run on different devices and // on different releases. static String testOverrideVendorId = null; - static String testOverrideHostname = null; protected DhcpPacket(int transId, short secs, Inet4Address clientIp, Inet4Address yourIp, Inet4Address nextIp, Inet4Address relayIp, @@ -724,9 +741,16 @@ public abstract class DhcpPacket { return "android-dhcp-" + Build.VERSION.RELEASE; } - private String getHostname() { - if (testOverrideHostname != null) return testOverrideHostname; - return SystemProperties.get("net.hostname"); + /** + * Get the DHCP client hostname after transliteration. + */ + @VisibleForTesting + public String getHostname() { + if (mHostName == null + && !ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)) { + return SystemProperties.get("net.hostname"); + } + return mHostName; } /** @@ -1328,10 +1352,11 @@ public abstract class DhcpPacket { */ public static ByteBuffer buildDiscoverPacket(int encap, int transactionId, short secs, byte[] clientMac, boolean broadcast, byte[] expectedParams, - boolean rapidCommit) { + boolean rapidCommit, String hostname) { DhcpPacket pkt = new DhcpDiscoverPacket(transactionId, secs, INADDR_ANY /* relayIp */, clientMac, broadcast, INADDR_ANY /* srcIp */, rapidCommit); pkt.mRequestedParams = expectedParams; + pkt.mHostName = hostname; return pkt.buildPacket(encap, DHCP_SERVER, DHCP_CLIENT); } diff --git a/src/android/net/util/HostnameTransliterator.java b/src/android/net/util/HostnameTransliterator.java new file mode 100644 index 0000000..cf126d1 --- /dev/null +++ b/src/android/net/util/HostnameTransliterator.java @@ -0,0 +1,103 @@ +/* + * 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.icu.text.Transliterator; +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; + +/** + * Transliterator to display a human-readable DHCP client hostname. + */ +public class HostnameTransliterator { + private static final String TAG = "HostnameTransliterator"; + + // Maximum length of hostname to be encoded in the DHCP message. Following RFC1035#2.3.4 + // and this transliterator converts the device name to a single label, so the label length + // limit applies to the whole hostname. + private static final int MAX_DNS_LABEL_LENGTH = 63; + + @Nullable + private final Transliterator mTransliterator; + + public HostnameTransliterator() { + final Enumeration<String> availableIDs = Transliterator.getAvailableIDs(); + final Set<String> actualIds = new HashSet<>(Collections.list(availableIDs)); + final StringBuilder rules = new StringBuilder(); + if (actualIds.contains("Any-ASCII")) { + rules.append(":: Any-ASCII; "); + } else if (actualIds.contains("Any-Latin") && actualIds.contains("Latin-ASCII")) { + rules.append(":: Any-Latin; :: Latin-ASCII; "); + } else { + Log.e(TAG, "ICU Transliterator doesn't include supported ID"); + mTransliterator = null; + return; + } + mTransliterator = Transliterator.createFromRules("", rules.toString(), + Transliterator.FORWARD); + } + + @VisibleForTesting + public HostnameTransliterator(Transliterator transliterator) { + mTransliterator = transliterator; + } + + // RFC952 and RFC1123 stipulates an valid hostname should be: + // 1. Only contain the alphabet (A-Z, a-z), digits (0-9), minus sign (-). + // 2. No blank or space characters are permitted as part of a name. + // 3. The first character must be an alpha character or digit. + // 4. The last character must not be a minus sign (-). + private String maybeRemoveRedundantSymbols(@NonNull String string) { + String result = string.replaceAll("[^a-zA-Z0-9-]", "-"); + result = result.replaceAll("-+", "-"); + if (result.startsWith("-")) { + result = result.replaceFirst("-", ""); + } + if (result.endsWith("-")) { + result = result.substring(0, result.length() - 1); + } + return result; + } + + /** + * Transliterate the device name to valid hostname that could be human-readable string. + */ + public String transliterate(@NonNull String deviceName) { + if (deviceName == null) return null; + if (mTransliterator == null) { + if (!deviceName.matches("\\p{ASCII}*")) return null; + deviceName = maybeRemoveRedundantSymbols(deviceName); + if (TextUtils.isEmpty(deviceName)) return null; + return deviceName.length() > MAX_DNS_LABEL_LENGTH + ? deviceName.substring(0, MAX_DNS_LABEL_LENGTH) : deviceName; + } + + String hostname = maybeRemoveRedundantSymbols(mTransliterator.transliterate(deviceName)); + if (TextUtils.isEmpty(hostname)) return null; + return hostname.length() > MAX_DNS_LABEL_LENGTH + ? hostname.substring(0, MAX_DNS_LABEL_LENGTH) : hostname; + } +} |