Merge changes from topic "nsd_serviceinfo_network" am: 585b14d79c am: 2d8dd0e89c
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1944534 Change-Id: I4ec5220c269ebe830c4c8682aeabedbbbc276759
This commit is contained in:
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package android.net.nsd;
|
package android.net.nsd;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
import android.annotation.SdkConstant;
|
import android.annotation.SdkConstant;
|
||||||
import android.annotation.SdkConstant.SdkConstantType;
|
import android.annotation.SdkConstant.SdkConstantType;
|
||||||
import android.annotation.SystemService;
|
import android.annotation.SystemService;
|
||||||
@@ -23,6 +25,7 @@ import android.app.compat.CompatChanges;
|
|||||||
import android.compat.annotation.ChangeId;
|
import android.compat.annotation.ChangeId;
|
||||||
import android.compat.annotation.EnabledSince;
|
import android.compat.annotation.EnabledSince;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.net.Network;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
@@ -633,6 +636,14 @@ public final class NsdManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as {@link #discoverServices(String, int, Network, DiscoveryListener)} with a null
|
||||||
|
* {@link Network}.
|
||||||
|
*/
|
||||||
|
public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
|
||||||
|
discoverServices(serviceType, protocolType, null, listener);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiate service discovery to browse for instances of a service type. Service discovery
|
* Initiate service discovery to browse for instances of a service type. Service discovery
|
||||||
* consumes network bandwidth and will continue until the application calls
|
* consumes network bandwidth and will continue until the application calls
|
||||||
@@ -657,11 +668,13 @@ public final class NsdManager {
|
|||||||
* @param serviceType The service type being discovered. Examples include "_http._tcp" for
|
* @param serviceType The service type being discovered. Examples include "_http._tcp" for
|
||||||
* http services or "_ipp._tcp" for printers
|
* http services or "_ipp._tcp" for printers
|
||||||
* @param protocolType The service discovery protocol
|
* @param protocolType The service discovery protocol
|
||||||
|
* @param network Network to discover services on, or null to discover on all available networks
|
||||||
* @param listener The listener notifies of a successful discovery and is used
|
* @param listener The listener notifies of a successful discovery and is used
|
||||||
* to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
|
* to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
|
||||||
* Cannot be null. Cannot be in use for an active service discovery.
|
* Cannot be null. Cannot be in use for an active service discovery.
|
||||||
*/
|
*/
|
||||||
public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
|
public void discoverServices(@NonNull String serviceType, int protocolType,
|
||||||
|
@Nullable Network network, @NonNull DiscoveryListener listener) {
|
||||||
if (TextUtils.isEmpty(serviceType)) {
|
if (TextUtils.isEmpty(serviceType)) {
|
||||||
throw new IllegalArgumentException("Service type cannot be empty");
|
throw new IllegalArgumentException("Service type cannot be empty");
|
||||||
}
|
}
|
||||||
@@ -669,6 +682,7 @@ public final class NsdManager {
|
|||||||
|
|
||||||
NsdServiceInfo s = new NsdServiceInfo();
|
NsdServiceInfo s = new NsdServiceInfo();
|
||||||
s.setServiceType(serviceType);
|
s.setServiceType(serviceType);
|
||||||
|
s.setNetwork(network);
|
||||||
|
|
||||||
int key = putListener(listener, s);
|
int key = putListener(listener, s);
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -17,7 +17,9 @@
|
|||||||
package android.net.nsd;
|
package android.net.nsd;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
import android.compat.annotation.UnsupportedAppUsage;
|
import android.compat.annotation.UnsupportedAppUsage;
|
||||||
|
import android.net.Network;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -49,6 +51,9 @@ public final class NsdServiceInfo implements Parcelable {
|
|||||||
|
|
||||||
private int mPort;
|
private int mPort;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Network mNetwork;
|
||||||
|
|
||||||
public NsdServiceInfo() {
|
public NsdServiceInfo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,18 +312,37 @@ public final class NsdServiceInfo implements Parcelable {
|
|||||||
return txtRecord;
|
return txtRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
/**
|
||||||
StringBuffer sb = new StringBuffer();
|
* Get the network where the service can be found.
|
||||||
|
*
|
||||||
|
* This is never null if this {@link NsdServiceInfo} was obtained from
|
||||||
|
* {@link NsdManager#discoverServices} or {@link NsdManager#resolveService}.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public Network getNetwork() {
|
||||||
|
return mNetwork;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the network where the service can be found.
|
||||||
|
* @param network The network, or null to search for, or to announce, the service on all
|
||||||
|
* connected networks.
|
||||||
|
*/
|
||||||
|
public void setNetwork(@Nullable Network network) {
|
||||||
|
mNetwork = network;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("name: ").append(mServiceName)
|
sb.append("name: ").append(mServiceName)
|
||||||
.append(", type: ").append(mServiceType)
|
.append(", type: ").append(mServiceType)
|
||||||
.append(", host: ").append(mHost)
|
.append(", host: ").append(mHost)
|
||||||
.append(", port: ").append(mPort);
|
.append(", port: ").append(mPort)
|
||||||
|
.append(", network: ").append(mNetwork);
|
||||||
|
|
||||||
byte[] txtRecord = getTxtRecord();
|
byte[] txtRecord = getTxtRecord();
|
||||||
if (txtRecord != null) {
|
|
||||||
sb.append(", txtRecord: ").append(new String(txtRecord, StandardCharsets.UTF_8));
|
sb.append(", txtRecord: ").append(new String(txtRecord, StandardCharsets.UTF_8));
|
||||||
}
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,6 +376,8 @@ public final class NsdServiceInfo implements Parcelable {
|
|||||||
}
|
}
|
||||||
dest.writeString(key);
|
dest.writeString(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dest.writeParcelable(mNetwork, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Implement the Parcelable interface */
|
/** Implement the Parcelable interface */
|
||||||
@@ -381,6 +407,7 @@ public final class NsdServiceInfo implements Parcelable {
|
|||||||
}
|
}
|
||||||
info.mTxtRecord.put(in.readString(), valueArray);
|
info.mTxtRecord.put(in.readString(), valueArray);
|
||||||
}
|
}
|
||||||
|
info.mNetwork = in.readParcelable(null, Network.class);
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ package com.android.server;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.LinkProperties;
|
||||||
|
import android.net.Network;
|
||||||
import android.net.nsd.INsdManager;
|
import android.net.nsd.INsdManager;
|
||||||
import android.net.nsd.INsdManagerCallback;
|
import android.net.nsd.INsdManagerCallback;
|
||||||
import android.net.nsd.INsdServiceConnector;
|
import android.net.nsd.INsdServiceConnector;
|
||||||
@@ -44,6 +47,9 @@ import com.android.net.module.util.DnsSdTxtRecord;
|
|||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
@@ -60,6 +66,7 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
|
|
||||||
private static final boolean DBG = true;
|
private static final boolean DBG = true;
|
||||||
private static final long CLEANUP_DELAY_MS = 10000;
|
private static final long CLEANUP_DELAY_MS = 10000;
|
||||||
|
private static final int IFACE_IDX_ANY = 0;
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final NsdStateMachine mNsdStateMachine;
|
private final NsdStateMachine mNsdStateMachine;
|
||||||
@@ -297,7 +304,7 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
|
|
||||||
maybeStartDaemon();
|
maybeStartDaemon();
|
||||||
id = getUniqueId();
|
id = getUniqueId();
|
||||||
if (discoverServices(id, args.serviceInfo.getServiceType())) {
|
if (discoverServices(id, args.serviceInfo)) {
|
||||||
if (DBG) {
|
if (DBG) {
|
||||||
Log.d(TAG, "Discover " + msg.arg2 + " " + id
|
Log.d(TAG, "Discover " + msg.arg2 + " " + id
|
||||||
+ args.serviceInfo.getServiceType());
|
+ args.serviceInfo.getServiceType());
|
||||||
@@ -430,13 +437,38 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
}
|
}
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case NativeResponseCode.SERVICE_FOUND:
|
case NativeResponseCode.SERVICE_FOUND:
|
||||||
/* NNN uniqueId serviceName regType domain */
|
/* NNN uniqueId serviceName regType domain interfaceIdx netId */
|
||||||
servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
|
servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
|
||||||
|
final int foundNetId;
|
||||||
|
try {
|
||||||
|
foundNetId = Integer.parseInt(cooked[6]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Log.wtf(TAG, "Invalid network received from mdnsd: " + cooked[6]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (foundNetId == 0L) {
|
||||||
|
// Ignore services that do not have a Network: they are not usable
|
||||||
|
// by apps, as they would need privileged permissions to use
|
||||||
|
// interfaces that do not have an associated Network.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
servInfo.setNetwork(new Network(foundNetId));
|
||||||
clientInfo.onServiceFound(clientId, servInfo);
|
clientInfo.onServiceFound(clientId, servInfo);
|
||||||
break;
|
break;
|
||||||
case NativeResponseCode.SERVICE_LOST:
|
case NativeResponseCode.SERVICE_LOST:
|
||||||
/* NNN uniqueId serviceName regType domain */
|
/* NNN uniqueId serviceName regType domain interfaceIdx netId */
|
||||||
|
final int lostNetId;
|
||||||
|
try {
|
||||||
|
lostNetId = Integer.parseInt(cooked[6]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Log.wtf(TAG, "Invalid network received from mdnsd: " + cooked[6]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
|
servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
|
||||||
|
// The network could be null if it was torn down when the service is lost
|
||||||
|
// TODO: avoid returning null in that case, possibly by remembering found
|
||||||
|
// services on the same interface index and their network at the time
|
||||||
|
servInfo.setNetwork(lostNetId == 0 ? null : new Network(lostNetId));
|
||||||
clientInfo.onServiceLost(clientId, servInfo);
|
clientInfo.onServiceLost(clientId, servInfo);
|
||||||
break;
|
break;
|
||||||
case NativeResponseCode.SERVICE_DISCOVERY_FAILED:
|
case NativeResponseCode.SERVICE_DISCOVERY_FAILED:
|
||||||
@@ -461,7 +493,7 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
/* NNN regId errorCode */
|
/* NNN regId errorCode */
|
||||||
break;
|
break;
|
||||||
case NativeResponseCode.SERVICE_RESOLVED:
|
case NativeResponseCode.SERVICE_RESOLVED:
|
||||||
/* NNN resolveId fullName hostName port txtlen txtdata */
|
/* NNN resolveId fullName hostName port txtlen txtdata interfaceIdx */
|
||||||
int index = 0;
|
int index = 0;
|
||||||
while (index < cooked[2].length() && cooked[2].charAt(index) != '.') {
|
while (index < cooked[2].length() && cooked[2].charAt(index) != '.') {
|
||||||
if (cooked[2].charAt(index) == '\\') {
|
if (cooked[2].charAt(index) == '\\') {
|
||||||
@@ -473,6 +505,7 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
Log.e(TAG, "Invalid service found " + raw);
|
Log.e(TAG, "Invalid service found " + raw);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
String name = cooked[2].substring(0, index);
|
String name = cooked[2].substring(0, index);
|
||||||
String rest = cooked[2].substring(index);
|
String rest = cooked[2].substring(index);
|
||||||
String type = rest.replace(".local.", "");
|
String type = rest.replace(".local.", "");
|
||||||
@@ -483,12 +516,13 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
clientInfo.mResolvedService.setServiceType(type);
|
clientInfo.mResolvedService.setServiceType(type);
|
||||||
clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4]));
|
clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4]));
|
||||||
clientInfo.mResolvedService.setTxtRecords(cooked[6]);
|
clientInfo.mResolvedService.setTxtRecords(cooked[6]);
|
||||||
|
// Network will be added after SERVICE_GET_ADDR_SUCCESS
|
||||||
|
|
||||||
stopResolveService(id);
|
stopResolveService(id);
|
||||||
removeRequestMap(clientId, id, clientInfo);
|
removeRequestMap(clientId, id, clientInfo);
|
||||||
|
|
||||||
int id2 = getUniqueId();
|
int id2 = getUniqueId();
|
||||||
if (getAddrInfo(id2, cooked[3], cooked[7])) {
|
if (getAddrInfo(id2, cooked[3], cooked[7] /* interfaceIdx */)) {
|
||||||
storeRequestMap(clientId, id2, clientInfo, NsdManager.RESOLVE_SERVICE);
|
storeRequestMap(clientId, id2, clientInfo, NsdManager.RESOLVE_SERVICE);
|
||||||
} else {
|
} else {
|
||||||
clientInfo.onResolveServiceFailed(
|
clientInfo.onResolveServiceFailed(
|
||||||
@@ -513,12 +547,31 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
clientId, NsdManager.FAILURE_INTERNAL_ERROR);
|
clientId, NsdManager.FAILURE_INTERNAL_ERROR);
|
||||||
break;
|
break;
|
||||||
case NativeResponseCode.SERVICE_GET_ADDR_SUCCESS:
|
case NativeResponseCode.SERVICE_GET_ADDR_SUCCESS:
|
||||||
/* NNN resolveId hostname ttl addr */
|
/* NNN resolveId hostname ttl addr interfaceIdx netId */
|
||||||
|
Network network = null;
|
||||||
try {
|
try {
|
||||||
clientInfo.mResolvedService.setHost(InetAddress.getByName(cooked[4]));
|
final int netId = Integer.parseInt(cooked[6]);
|
||||||
|
network = netId == 0L ? null : new Network(netId);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Log.wtf(TAG, "Invalid network in GET_ADDR_SUCCESS: " + cooked[6], e);
|
||||||
|
}
|
||||||
|
|
||||||
|
InetAddress serviceHost = null;
|
||||||
|
try {
|
||||||
|
serviceHost = InetAddress.getByName(cooked[4]);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
Log.wtf(TAG, "Invalid host in GET_ADDR_SUCCESS", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the resolved service is on an interface without a network, consider it
|
||||||
|
// as a failure: it would not be usable by apps as they would need
|
||||||
|
// privileged permissions.
|
||||||
|
if (network != null && serviceHost != null) {
|
||||||
|
clientInfo.mResolvedService.setHost(serviceHost);
|
||||||
|
clientInfo.mResolvedService.setNetwork(network);
|
||||||
clientInfo.onResolveServiceSucceeded(
|
clientInfo.onResolveServiceSucceeded(
|
||||||
clientId, clientInfo.mResolvedService);
|
clientId, clientInfo.mResolvedService);
|
||||||
} catch (java.net.UnknownHostException e) {
|
} else {
|
||||||
clientInfo.onResolveServiceFailed(
|
clientInfo.onResolveServiceFailed(
|
||||||
clientId, NsdManager.FAILURE_INTERNAL_ERROR);
|
clientId, NsdManager.FAILURE_INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
@@ -815,8 +868,15 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
return mDaemon.execute("update", regId, t.size(), t.getRawData());
|
return mDaemon.execute("update", regId, t.size(), t.getRawData());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean discoverServices(int discoveryId, String serviceType) {
|
private boolean discoverServices(int discoveryId, NsdServiceInfo serviceInfo) {
|
||||||
return mDaemon.execute("discover", discoveryId, serviceType);
|
final Network network = serviceInfo.getNetwork();
|
||||||
|
final int discoverInterface = getNetworkInterfaceIndex(network);
|
||||||
|
if (network != null && discoverInterface == IFACE_IDX_ANY) {
|
||||||
|
Log.e(TAG, "Interface to discover service on not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return mDaemon.execute("discover", discoveryId, serviceInfo.getServiceType(),
|
||||||
|
discoverInterface);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean stopServiceDiscovery(int discoveryId) {
|
private boolean stopServiceDiscovery(int discoveryId) {
|
||||||
@@ -824,17 +884,61 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean resolveService(int resolveId, NsdServiceInfo service) {
|
private boolean resolveService(int resolveId, NsdServiceInfo service) {
|
||||||
String name = service.getServiceName();
|
final String name = service.getServiceName();
|
||||||
String type = service.getServiceType();
|
final String type = service.getServiceType();
|
||||||
return mDaemon.execute("resolve", resolveId, name, type, "local.");
|
final Network network = service.getNetwork();
|
||||||
|
final int resolveInterface = getNetworkInterfaceIndex(network);
|
||||||
|
if (network != null && resolveInterface == IFACE_IDX_ANY) {
|
||||||
|
Log.e(TAG, "Interface to resolve service on not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return mDaemon.execute("resolve", resolveId, name, type, "local.", resolveInterface);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guess the interface to use to resolve or discover a service on a specific network.
|
||||||
|
*
|
||||||
|
* This is an imperfect guess, as for example the network may be gone or not yet fully
|
||||||
|
* registered. This is fine as failing is correct if the network is gone, and a client
|
||||||
|
* attempting to resolve/discover on a network not yet setup would have a bad time anyway; also
|
||||||
|
* this is to support the legacy mdnsresponder implementation, which historically resolved
|
||||||
|
* services on an unspecified network.
|
||||||
|
*/
|
||||||
|
private int getNetworkInterfaceIndex(Network network) {
|
||||||
|
if (network == null) return IFACE_IDX_ANY;
|
||||||
|
|
||||||
|
final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
|
||||||
|
if (cm == null) {
|
||||||
|
Log.wtf(TAG, "No ConnectivityManager for resolveService");
|
||||||
|
return IFACE_IDX_ANY;
|
||||||
|
}
|
||||||
|
final LinkProperties lp = cm.getLinkProperties(network);
|
||||||
|
if (lp == null) return IFACE_IDX_ANY;
|
||||||
|
|
||||||
|
// Only resolve on non-stacked interfaces
|
||||||
|
final NetworkInterface iface;
|
||||||
|
try {
|
||||||
|
iface = NetworkInterface.getByName(lp.getInterfaceName());
|
||||||
|
} catch (SocketException e) {
|
||||||
|
Log.e(TAG, "Error querying interface", e);
|
||||||
|
return IFACE_IDX_ANY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iface == null) {
|
||||||
|
Log.e(TAG, "Interface not found: " + lp.getInterfaceName());
|
||||||
|
return IFACE_IDX_ANY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return iface.getIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean stopResolveService(int resolveId) {
|
private boolean stopResolveService(int resolveId) {
|
||||||
return mDaemon.execute("stop-resolve", resolveId);
|
return mDaemon.execute("stop-resolve", resolveId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean getAddrInfo(int resolveId, String hostname, String interfaceName) {
|
private boolean getAddrInfo(int resolveId, String hostname, String interfaceIdx) {
|
||||||
return mDaemon.execute("getaddrinfo", resolveId, hostname, interfaceName);
|
// interfaceIdx is always obtained (as string) from the service resolved callback
|
||||||
|
return mDaemon.execute("getaddrinfo", resolveId, hostname, interfaceIdx);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean stopGetAddrInfo(int resolveId) {
|
private boolean stopGetAddrInfo(int resolveId) {
|
||||||
|
|||||||
Reference in New Issue
Block a user