Implementation of Eth Service updateConfiguration
EthernetServiceImpl#updateConfiguration API implementation to allow for dynamically updating the network capability or ip configuration of an ethernet based interface. Bug: 210485380 Test: atest EthernetServiceTests Change-Id: Idd3b7875a24643d245d0f4bb6f2f4c459898116e
This commit is contained in:
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.EthernetNetworkSpecifier;
|
||||
import android.net.IInternalNetworkManagementListener;
|
||||
import android.net.IpConfiguration;
|
||||
import android.net.IpConfiguration.IpAssignment;
|
||||
import android.net.IpConfiguration.ProxySettings;
|
||||
@@ -98,14 +99,13 @@ public class EthernetNetworkFactory extends NetworkFactory {
|
||||
}
|
||||
}
|
||||
|
||||
public EthernetNetworkFactory(Handler handler, Context context, NetworkCapabilities filter) {
|
||||
this(handler, context, filter, new Dependencies());
|
||||
public EthernetNetworkFactory(Handler handler, Context context) {
|
||||
this(handler, context, new Dependencies());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
EthernetNetworkFactory(Handler handler, Context context, NetworkCapabilities filter,
|
||||
Dependencies deps) {
|
||||
super(handler.getLooper(), context, NETWORK_TYPE, filter);
|
||||
EthernetNetworkFactory(Handler handler, Context context, Dependencies deps) {
|
||||
super(handler.getLooper(), context, NETWORK_TYPE, createDefaultNetworkCapabilities());
|
||||
|
||||
mHandler = handler;
|
||||
mContext = context;
|
||||
@@ -166,8 +166,9 @@ public class EthernetNetworkFactory extends NetworkFactory {
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
void addInterface(String ifaceName, String hwAddress, NetworkCapabilities capabilities,
|
||||
IpConfiguration ipConfiguration) {
|
||||
void addInterface(@NonNull final String ifaceName, @NonNull final String hwAddress,
|
||||
@NonNull final IpConfiguration ipConfig,
|
||||
@NonNull final NetworkCapabilities capabilities) {
|
||||
if (mTrackingInterfaces.containsKey(ifaceName)) {
|
||||
Log.e(TAG, "Interface with name " + ifaceName + " already exists.");
|
||||
return;
|
||||
@@ -177,14 +178,48 @@ public class EthernetNetworkFactory extends NetworkFactory {
|
||||
Log.d(TAG, "addInterface, iface: " + ifaceName + ", capabilities: " + capabilities);
|
||||
}
|
||||
|
||||
NetworkInterfaceState iface = new NetworkInterfaceState(
|
||||
ifaceName, hwAddress, mHandler, mContext, capabilities, this, mDeps);
|
||||
iface.setIpConfig(ipConfiguration);
|
||||
final NetworkInterfaceState iface = new NetworkInterfaceState(
|
||||
ifaceName, hwAddress, mHandler, mContext, ipConfig, capabilities, this, mDeps);
|
||||
mTrackingInterfaces.put(ifaceName, iface);
|
||||
|
||||
updateCapabilityFilter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a network's configuration and restart it if necessary.
|
||||
*
|
||||
* @param ifaceName the interface name of the network to be updated.
|
||||
* @param ipConfig the desired {@link IpConfiguration} for the given network.
|
||||
* @param capabilities the desired {@link NetworkCapabilities} for the given network. If
|
||||
* {@code null} is passed, then the network's current
|
||||
* {@link NetworkCapabilities} will be used in support of existing APIs as
|
||||
* the public API does not allow this.
|
||||
* @param listener an optional {@link IInternalNetworkManagementListener} to notify callers of
|
||||
* completion.
|
||||
*/
|
||||
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
|
||||
protected void updateInterface(@NonNull final String ifaceName,
|
||||
@NonNull final IpConfiguration ipConfig,
|
||||
@Nullable final NetworkCapabilities capabilities,
|
||||
@Nullable final IInternalNetworkManagementListener listener) {
|
||||
enforceInterfaceIsTracked(ifaceName);
|
||||
final NetworkInterfaceState iface = mTrackingInterfaces.get(ifaceName);
|
||||
// TODO: The listener will have issues if called in quick succession for the same interface
|
||||
// before the IP layer restarts. Update the listener logic to address multiple successive
|
||||
// calls for a particular interface.
|
||||
iface.mNetworkManagementListener = listener;
|
||||
if (iface.updateInterface(ipConfig, capabilities)) {
|
||||
mTrackingInterfaces.put(ifaceName, iface);
|
||||
updateCapabilityFilter();
|
||||
}
|
||||
}
|
||||
|
||||
private void enforceInterfaceIsTracked(@NonNull final String ifaceName) {
|
||||
if (!hasInterface(ifaceName)) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Interface with name " + ifaceName + " is not being tracked.");
|
||||
}
|
||||
}
|
||||
|
||||
private static NetworkCapabilities mixInCapabilities(NetworkCapabilities nc,
|
||||
NetworkCapabilities addedNc) {
|
||||
final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(nc);
|
||||
@@ -194,11 +229,7 @@ public class EthernetNetworkFactory extends NetworkFactory {
|
||||
}
|
||||
|
||||
private void updateCapabilityFilter() {
|
||||
NetworkCapabilities capabilitiesFilter =
|
||||
NetworkCapabilities.Builder.withoutDefaultCapabilities()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||
.build();
|
||||
|
||||
NetworkCapabilities capabilitiesFilter = createDefaultNetworkCapabilities();
|
||||
for (NetworkInterfaceState iface: mTrackingInterfaces.values()) {
|
||||
capabilitiesFilter = mixInCapabilities(capabilitiesFilter, iface.mCapabilities);
|
||||
}
|
||||
@@ -207,6 +238,12 @@ public class EthernetNetworkFactory extends NetworkFactory {
|
||||
setCapabilityFilter(capabilitiesFilter);
|
||||
}
|
||||
|
||||
private static NetworkCapabilities createDefaultNetworkCapabilities() {
|
||||
return NetworkCapabilities.Builder
|
||||
.withoutDefaultCapabilities()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET).build();
|
||||
}
|
||||
|
||||
void removeInterface(String interfaceName) {
|
||||
NetworkInterfaceState iface = mTrackingInterfaces.remove(interfaceName);
|
||||
if (iface != null) {
|
||||
@@ -230,15 +267,8 @@ public class EthernetNetworkFactory extends NetworkFactory {
|
||||
return iface.updateLinkState(up);
|
||||
}
|
||||
|
||||
boolean hasInterface(String interfacName) {
|
||||
return mTrackingInterfaces.containsKey(interfacName);
|
||||
}
|
||||
|
||||
void updateIpConfiguration(String iface, IpConfiguration ipConfiguration) {
|
||||
NetworkInterfaceState network = mTrackingInterfaces.get(iface);
|
||||
if (network != null) {
|
||||
network.setIpConfig(ipConfiguration);
|
||||
}
|
||||
boolean hasInterface(String ifaceName) {
|
||||
return mTrackingInterfaces.containsKey(ifaceName);
|
||||
}
|
||||
|
||||
private NetworkInterfaceState networkForRequest(NetworkRequest request) {
|
||||
@@ -272,24 +302,26 @@ public class EthernetNetworkFactory extends NetworkFactory {
|
||||
return network;
|
||||
}
|
||||
|
||||
private static class NetworkInterfaceState {
|
||||
@VisibleForTesting
|
||||
static class NetworkInterfaceState {
|
||||
final String name;
|
||||
|
||||
private final String mHwAddress;
|
||||
private final NetworkCapabilities mCapabilities;
|
||||
private final Handler mHandler;
|
||||
private final Context mContext;
|
||||
private final NetworkFactory mNetworkFactory;
|
||||
private final Dependencies mDeps;
|
||||
private final int mLegacyType;
|
||||
|
||||
private static String sTcpBufferSizes = null; // Lazy initialized.
|
||||
|
||||
private boolean mLinkUp;
|
||||
private int mLegacyType;
|
||||
private LinkProperties mLinkProperties = new LinkProperties();
|
||||
|
||||
private volatile @Nullable IpClientManager mIpClient;
|
||||
private @NonNull NetworkCapabilities mCapabilities;
|
||||
private @Nullable IpClientCallbacksImpl mIpClientCallback;
|
||||
private @Nullable IInternalNetworkManagementListener mNetworkManagementListener;
|
||||
private @Nullable EthernetNetworkAgent mNetworkAgent;
|
||||
private @Nullable IpConfiguration mIpConfig;
|
||||
|
||||
@@ -362,42 +394,17 @@ public class EthernetNetworkFactory extends NetworkFactory {
|
||||
}
|
||||
|
||||
NetworkInterfaceState(String ifaceName, String hwAddress, Handler handler, Context context,
|
||||
@NonNull NetworkCapabilities capabilities, NetworkFactory networkFactory,
|
||||
Dependencies deps) {
|
||||
@NonNull IpConfiguration ipConfig, @NonNull NetworkCapabilities capabilities,
|
||||
NetworkFactory networkFactory, Dependencies deps) {
|
||||
name = ifaceName;
|
||||
mIpConfig = Objects.requireNonNull(ipConfig);
|
||||
mCapabilities = Objects.requireNonNull(capabilities);
|
||||
mLegacyType = getLegacyType(mCapabilities);
|
||||
mHandler = handler;
|
||||
mContext = context;
|
||||
mNetworkFactory = networkFactory;
|
||||
mDeps = deps;
|
||||
final int legacyType;
|
||||
int[] transportTypes = mCapabilities.getTransportTypes();
|
||||
|
||||
if (transportTypes.length > 0) {
|
||||
legacyType = getLegacyType(transportTypes[0]);
|
||||
} else {
|
||||
// Should never happen as transport is always one of ETHERNET or a valid override
|
||||
throw new ConfigurationException("Network Capabilities do not have an associated "
|
||||
+ "transport type.");
|
||||
}
|
||||
|
||||
mHwAddress = hwAddress;
|
||||
mLegacyType = legacyType;
|
||||
}
|
||||
|
||||
void setIpConfig(IpConfiguration ipConfig) {
|
||||
if (Objects.equals(this.mIpConfig, ipConfig)) {
|
||||
if (DBG) Log.d(TAG, "ipConfig have not changed,so ignore setIpConfig");
|
||||
return;
|
||||
}
|
||||
this.mIpConfig = ipConfig;
|
||||
if (mNetworkAgent != null) {
|
||||
restart();
|
||||
}
|
||||
}
|
||||
|
||||
boolean isRestricted() {
|
||||
return !mCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -408,6 +415,52 @@ public class EthernetNetworkFactory extends NetworkFactory {
|
||||
return sTransports.get(transport, ConnectivityManager.TYPE_NONE);
|
||||
}
|
||||
|
||||
private static int getLegacyType(@NonNull final NetworkCapabilities capabilities) {
|
||||
final int[] transportTypes = capabilities.getTransportTypes();
|
||||
if (transportTypes.length > 0) {
|
||||
return getLegacyType(transportTypes[0]);
|
||||
}
|
||||
|
||||
// Should never happen as transport is always one of ETHERNET or a valid override
|
||||
throw new ConfigurationException("Network Capabilities do not have an associated "
|
||||
+ "transport type.");
|
||||
}
|
||||
|
||||
private void setCapabilities(@NonNull final NetworkCapabilities capabilities) {
|
||||
mCapabilities = new NetworkCapabilities(capabilities);
|
||||
mLegacyType = getLegacyType(mCapabilities);
|
||||
}
|
||||
|
||||
boolean updateInterface(@NonNull final IpConfiguration ipConfig,
|
||||
@Nullable final NetworkCapabilities capabilities) {
|
||||
final boolean shouldUpdateIpConfig = !Objects.equals(mIpConfig, ipConfig);
|
||||
final boolean shouldUpdateCapabilities = null != capabilities
|
||||
&& !Objects.equals(mCapabilities, capabilities);
|
||||
if (DBG) {
|
||||
Log.d(TAG, "updateInterface, iface: " + name
|
||||
+ ", shouldUpdateIpConfig: " + shouldUpdateIpConfig
|
||||
+ ", shouldUpdateCapabilities: " + shouldUpdateCapabilities
|
||||
+ ", ipConfig: " + ipConfig + ", old ipConfig: " + mIpConfig
|
||||
+ ", capabilities: " + capabilities + ", old capabilities: " + mCapabilities
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldUpdateIpConfig) { mIpConfig = ipConfig; };
|
||||
if (shouldUpdateCapabilities) { setCapabilities(capabilities); };
|
||||
if (shouldUpdateIpConfig || shouldUpdateCapabilities) {
|
||||
// TODO: Update this logic to only do a restart if required. Although a restart may
|
||||
// be required due to the capabilities or ipConfiguration values, not all
|
||||
// capabilities changes require a restart.
|
||||
restart();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isRestricted() {
|
||||
return !mCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
|
||||
}
|
||||
|
||||
private void start() {
|
||||
if (mIpClient != null) {
|
||||
if (DBG) Log.d(TAG, "IpClient already started");
|
||||
@@ -459,6 +512,19 @@ public class EthernetNetworkFactory extends NetworkFactory {
|
||||
});
|
||||
mNetworkAgent.register();
|
||||
mNetworkAgent.markConnected();
|
||||
sendNetworkManagementCallback();
|
||||
}
|
||||
|
||||
private void sendNetworkManagementCallback() {
|
||||
if (null != mNetworkManagementListener) {
|
||||
try {
|
||||
mNetworkManagementListener.onComplete(mNetworkAgent.getNetwork(), null);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Can't send onComplete for network management callback", e);
|
||||
} finally {
|
||||
mNetworkManagementListener = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onIpLayerStopped(LinkProperties linkProperties) {
|
||||
@@ -546,7 +612,7 @@ public class EthernetNetworkFactory extends NetworkFactory {
|
||||
}
|
||||
|
||||
void restart(){
|
||||
if (DBG) Log.d(TAG, "reconnecting Etherent");
|
||||
if (DBG) Log.d(TAG, "reconnecting Ethernet");
|
||||
stop();
|
||||
start();
|
||||
}
|
||||
|
||||
@@ -17,11 +17,16 @@
|
||||
package com.android.server.ethernet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.INetd;
|
||||
import android.net.util.NetdService;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
import com.android.server.SystemService;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class EthernetService extends SystemService {
|
||||
|
||||
private static final String TAG = "EthernetService";
|
||||
@@ -32,9 +37,17 @@ public final class EthernetService extends SystemService {
|
||||
super(context);
|
||||
final HandlerThread handlerThread = new HandlerThread(THREAD_NAME);
|
||||
handlerThread.start();
|
||||
final Handler handler = handlerThread.getThreadHandler();
|
||||
final EthernetNetworkFactory factory = new EthernetNetworkFactory(handler, context);
|
||||
mImpl = new EthernetServiceImpl(
|
||||
context, handlerThread.getThreadHandler(),
|
||||
new EthernetTracker(context, handlerThread.getThreadHandler()));
|
||||
context, handler,
|
||||
new EthernetTracker(context, handler, factory, getNetd()));
|
||||
}
|
||||
|
||||
private INetd getNetd() {
|
||||
final INetd netd = NetdService.getInstance();
|
||||
Objects.requireNonNull(netd, "could not get netd instance");
|
||||
return netd;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -233,6 +233,9 @@ public class EthernetServiceImpl extends IEthernetManager.Stub {
|
||||
+ ", request=" + request + ", listener=" + listener);
|
||||
validateNetworkManagementState(iface, "updateConfiguration()");
|
||||
// TODO: validate that iface is listed in overlay config_ethernet_interfaces
|
||||
|
||||
mTracker.updateConfiguration(
|
||||
iface, request.getIpConfig(), request.getNetworkCapabilities(), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.net.IEthernetServiceListener;
|
||||
import android.net.IInternalNetworkManagementListener;
|
||||
import android.net.INetd;
|
||||
import android.net.ITetheredInterfaceCallback;
|
||||
import android.net.InterfaceConfigurationParcel;
|
||||
@@ -34,7 +35,6 @@ import android.net.LinkAddress;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.StaticIpConfiguration;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceSpecificException;
|
||||
@@ -118,14 +118,12 @@ public class EthernetTracker {
|
||||
}
|
||||
}
|
||||
|
||||
EthernetTracker(Context context, Handler handler) {
|
||||
EthernetTracker(@NonNull final Context context, @NonNull final Handler handler,
|
||||
@NonNull final EthernetNetworkFactory factory, @NonNull final INetd netd) {
|
||||
mContext = context;
|
||||
mHandler = handler;
|
||||
|
||||
// The services we use.
|
||||
mNetd = INetd.Stub.asInterface(
|
||||
(IBinder) context.getSystemService(Context.NETD_SERVICE));
|
||||
Objects.requireNonNull(mNetd, "could not get netd instance");
|
||||
mFactory = factory;
|
||||
mNetd = netd;
|
||||
|
||||
// Interface match regex.
|
||||
updateIfaceMatchRegexp();
|
||||
@@ -138,9 +136,6 @@ public class EthernetTracker {
|
||||
}
|
||||
|
||||
mConfigStore = new EthernetConfigStore();
|
||||
|
||||
NetworkCapabilities nc = createNetworkCapabilities(true /* clear default capabilities */);
|
||||
mFactory = new EthernetNetworkFactory(handler, context, nc);
|
||||
}
|
||||
|
||||
void start() {
|
||||
@@ -168,11 +163,30 @@ public class EthernetTracker {
|
||||
if (DBG) {
|
||||
Log.i(TAG, "updateIpConfiguration, iface: " + iface + ", cfg: " + ipConfiguration);
|
||||
}
|
||||
writeIpConfiguration(iface, ipConfiguration);
|
||||
mHandler.post(() -> mFactory.updateInterface(iface, ipConfiguration, null, null));
|
||||
}
|
||||
|
||||
mConfigStore.write(iface, ipConfiguration);
|
||||
mIpConfigurations.put(iface, ipConfiguration);
|
||||
private void writeIpConfiguration(@NonNull final String iface,
|
||||
@NonNull final IpConfiguration ipConfig) {
|
||||
mConfigStore.write(iface, ipConfig);
|
||||
mIpConfigurations.put(iface, ipConfig);
|
||||
}
|
||||
|
||||
mHandler.post(() -> mFactory.updateIpConfiguration(iface, ipConfiguration));
|
||||
@VisibleForTesting(visibility = PACKAGE)
|
||||
protected void updateConfiguration(@NonNull final String iface,
|
||||
@NonNull final StaticIpConfiguration staticIpConfig,
|
||||
@NonNull final NetworkCapabilities capabilities,
|
||||
@Nullable final IInternalNetworkManagementListener listener) {
|
||||
if (DBG) {
|
||||
Log.i(TAG, "updateConfiguration, iface: " + iface + ", capabilities: " + capabilities
|
||||
+ ", staticIpConfig: " + staticIpConfig);
|
||||
}
|
||||
final IpConfiguration ipConfig = createIpConfiguration(staticIpConfig);
|
||||
writeIpConfiguration(iface, ipConfig);
|
||||
mNetworkCapabilities.put(iface, capabilities);
|
||||
mHandler.post(() ->
|
||||
mFactory.updateInterface(iface, ipConfig, capabilities, listener));
|
||||
}
|
||||
|
||||
IpConfiguration getIpConfiguration(String iface) {
|
||||
@@ -325,7 +339,7 @@ public class EthernetTracker {
|
||||
}
|
||||
|
||||
Log.d(TAG, "Tracking interface in client mode: " + iface);
|
||||
mFactory.addInterface(iface, hwAddress, nc, ipConfiguration);
|
||||
mFactory.addInterface(iface, hwAddress, ipConfiguration, nc);
|
||||
} else {
|
||||
maybeUpdateServerModeInterfaceState(iface, true);
|
||||
}
|
||||
@@ -460,11 +474,11 @@ public class EthernetTracker {
|
||||
NetworkCapabilities nc = createNetworkCapabilities(
|
||||
!TextUtils.isEmpty(config.mCapabilities) /* clear default capabilities */,
|
||||
config.mCapabilities, config.mTransport).build();
|
||||
mNetworkCapabilities.put(config.mName, nc);
|
||||
mNetworkCapabilities.put(config.mIface, nc);
|
||||
|
||||
if (null != config.mIpConfig) {
|
||||
IpConfiguration ipConfig = parseStaticIpConfiguration(config.mIpConfig);
|
||||
mIpConfigurations.put(config.mName, ipConfig);
|
||||
mIpConfigurations.put(config.mIface, ipConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -493,10 +507,6 @@ public class EthernetTracker {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static NetworkCapabilities createNetworkCapabilities(boolean clearDefaultCapabilities) {
|
||||
return createNetworkCapabilities(clearDefaultCapabilities, null, null).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a static list of network capabilities
|
||||
*
|
||||
@@ -623,10 +633,15 @@ public class EthernetTracker {
|
||||
}
|
||||
}
|
||||
}
|
||||
return createIpConfiguration(staticIpConfigBuilder.build());
|
||||
}
|
||||
|
||||
static IpConfiguration createIpConfiguration(
|
||||
@NonNull final StaticIpConfiguration staticIpConfig) {
|
||||
final IpConfiguration ret = new IpConfiguration();
|
||||
ret.setIpAssignment(IpAssignment.STATIC);
|
||||
ret.setProxySettings(ProxySettings.NONE);
|
||||
ret.setStaticIpConfiguration(staticIpConfigBuilder.build());
|
||||
ret.setStaticIpConfiguration(staticIpConfig);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -681,14 +696,14 @@ public class EthernetTracker {
|
||||
|
||||
@VisibleForTesting
|
||||
static class EthernetTrackerConfig {
|
||||
final String mName;
|
||||
final String mIface;
|
||||
final String mCapabilities;
|
||||
final String mIpConfig;
|
||||
final String mTransport;
|
||||
|
||||
EthernetTrackerConfig(@NonNull final String[] tokens) {
|
||||
Objects.requireNonNull(tokens, "EthernetTrackerConfig requires non-null tokens");
|
||||
mName = tokens[0];
|
||||
mIface = tokens[0];
|
||||
mCapabilities = tokens.length > 1 ? tokens[1] : null;
|
||||
mIpConfig = tokens.length > 2 && !TextUtils.isEmpty(tokens[2]) ? tokens[2] : null;
|
||||
mTransport = tokens.length > 3 ? tokens[3] : null;
|
||||
|
||||
Reference in New Issue
Block a user