mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
net_util, virtio-devices, vhost_user_net: Relocate code for opening TAP
By moving the code for opening the TAP device into a shared location we are starting to remove the requirement for the vhost-user-net backend to depend on the virtio-devices crate which in of itself depends on many other crates that are not necessary for the backend to function. Signed-off-by: Rob Bradford <robert.bradford@intel.com>
This commit is contained in:
Generated
+1
@@ -638,6 +638,7 @@ dependencies = [
|
|||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"virtio-bindings",
|
||||||
"vmm-sys-util",
|
"vmm-sys-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -5,12 +5,12 @@ authors = ["The Chromium OS Authors"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libc = "0.2.72"
|
libc = "0.2.72"
|
||||||
|
net_gen = { path = "../net_gen" }
|
||||||
rand = "0.7.3"
|
rand = "0.7.3"
|
||||||
serde = "1.0.114"
|
serde = "1.0.114"
|
||||||
|
virtio-bindings = "0.1.0"
|
||||||
vmm-sys-util = ">=0.3.1"
|
vmm-sys-util = ">=0.3.1"
|
||||||
|
|
||||||
net_gen = { path = "../net_gen" }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
lazy_static = "1.3.0"
|
lazy_static = "1.3.0"
|
||||||
pnet = "0.26.0"
|
pnet = "0.26.0"
|
||||||
|
|||||||
+4
-2
@@ -11,13 +11,14 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
|
extern crate net_gen;
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
|
extern crate virtio_bindings;
|
||||||
extern crate net_gen;
|
|
||||||
extern crate vmm_sys_util;
|
extern crate vmm_sys_util;
|
||||||
|
|
||||||
mod mac;
|
mod mac;
|
||||||
|
mod open_tap;
|
||||||
mod tap;
|
mod tap;
|
||||||
|
|
||||||
use std::io::Error as IoError;
|
use std::io::Error as IoError;
|
||||||
@@ -26,6 +27,7 @@ use std::net;
|
|||||||
use std::os::unix::io::FromRawFd;
|
use std::os::unix::io::FromRawFd;
|
||||||
|
|
||||||
pub use mac::{MacAddr, MAC_ADDR_LEN};
|
pub use mac::{MacAddr, MAC_ADDR_LEN};
|
||||||
|
pub use open_tap::{open_tap, Error as OpenTapError};
|
||||||
pub use tap::{Error as TapError, Tap};
|
pub use tap::{Error as TapError, Tap};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
// Copyright (c) 2020 Intel Corporation. All rights reserved.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
|
use super::TapError;
|
||||||
|
use super::{MacAddr, Tap};
|
||||||
|
use std::net::Ipv4Addr;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::{fs, io};
|
||||||
|
use virtio_bindings::bindings::virtio_net::virtio_net_hdr_v1;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
/// Failed to convert an hexadecimal string into an integer.
|
||||||
|
ConvertHexStringToInt(std::num::ParseIntError),
|
||||||
|
/// Error related to the multiqueue support.
|
||||||
|
MultiQueueSupport(String),
|
||||||
|
/// Failed to read the TAP flags from sysfs.
|
||||||
|
ReadSysfsTunFlags(io::Error),
|
||||||
|
/// Open tap device failed.
|
||||||
|
TapOpen(TapError),
|
||||||
|
/// Setting tap IP failed.
|
||||||
|
TapSetIp(TapError),
|
||||||
|
/// Setting tap netmask failed.
|
||||||
|
TapSetNetmask(TapError),
|
||||||
|
/// Setting MAC address failed
|
||||||
|
TapSetMac(TapError),
|
||||||
|
/// Getting MAC address failed
|
||||||
|
TapGetMac(TapError),
|
||||||
|
/// Setting tap interface offload flags failed.
|
||||||
|
TapSetOffload(TapError),
|
||||||
|
/// Setting vnet header size failed.
|
||||||
|
TapSetVnetHdrSize(TapError),
|
||||||
|
/// Enabling tap interface failed.
|
||||||
|
TapEnable(TapError),
|
||||||
|
}
|
||||||
|
|
||||||
|
type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
fn vnet_hdr_len() -> usize {
|
||||||
|
std::mem::size_of::<virtio_net_hdr_v1>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_mq_support(if_name: &Option<&str>, queue_pairs: usize) -> Result<()> {
|
||||||
|
if let Some(tap_name) = if_name {
|
||||||
|
let mq = queue_pairs > 1;
|
||||||
|
let path = format!("/sys/class/net/{}/tun_flags", tap_name);
|
||||||
|
// interface does not exist, check is not required
|
||||||
|
if !Path::new(&path).exists() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let tun_flags_str = fs::read_to_string(path).map_err(Error::ReadSysfsTunFlags)?;
|
||||||
|
let tun_flags = u32::from_str_radix(tun_flags_str.trim().trim_start_matches("0x"), 16)
|
||||||
|
.map_err(Error::ConvertHexStringToInt)?;
|
||||||
|
if (tun_flags & net_gen::IFF_MULTI_QUEUE != 0) && !mq {
|
||||||
|
return Err(Error::MultiQueueSupport(String::from(
|
||||||
|
"TAP interface supports MQ while device does not",
|
||||||
|
)));
|
||||||
|
} else if (tun_flags & net_gen::IFF_MULTI_QUEUE == 0) && mq {
|
||||||
|
return Err(Error::MultiQueueSupport(String::from(
|
||||||
|
"Device supports MQ while TAP interface does not",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new virtio network device with the given IP address and
|
||||||
|
/// netmask.
|
||||||
|
pub fn open_tap(
|
||||||
|
if_name: Option<&str>,
|
||||||
|
ip_addr: Option<Ipv4Addr>,
|
||||||
|
netmask: Option<Ipv4Addr>,
|
||||||
|
host_mac: &mut Option<MacAddr>,
|
||||||
|
num_rx_q: usize,
|
||||||
|
) -> Result<Vec<Tap>> {
|
||||||
|
let mut taps: Vec<Tap> = Vec::new();
|
||||||
|
let mut ifname: String = String::new();
|
||||||
|
let vnet_hdr_size = vnet_hdr_len() as i32;
|
||||||
|
let flag = net_gen::TUN_F_CSUM | net_gen::TUN_F_UFO | net_gen::TUN_F_TSO4 | net_gen::TUN_F_TSO6;
|
||||||
|
|
||||||
|
// In case the tap interface already exists, check if the number of
|
||||||
|
// queues is appropriate. The tap might not support multiqueue while
|
||||||
|
// the number of queues indicates the user expects multiple queues, or
|
||||||
|
// on the contrary, the tap might support multiqueue while the number
|
||||||
|
// of queues indicates the user doesn't expect multiple queues.
|
||||||
|
check_mq_support(&if_name, num_rx_q)?;
|
||||||
|
|
||||||
|
for i in 0..num_rx_q {
|
||||||
|
let tap: Tap;
|
||||||
|
if i == 0 {
|
||||||
|
tap = match if_name {
|
||||||
|
Some(name) => Tap::open_named(name, num_rx_q).map_err(Error::TapOpen)?,
|
||||||
|
None => Tap::new(num_rx_q).map_err(Error::TapOpen)?,
|
||||||
|
};
|
||||||
|
if let Some(ip) = ip_addr {
|
||||||
|
tap.set_ip_addr(ip).map_err(Error::TapSetIp)?;
|
||||||
|
}
|
||||||
|
if let Some(mask) = netmask {
|
||||||
|
tap.set_netmask(mask).map_err(Error::TapSetNetmask)?;
|
||||||
|
}
|
||||||
|
if let Some(mac) = host_mac {
|
||||||
|
tap.set_mac_addr(*mac).map_err(Error::TapSetMac)?
|
||||||
|
} else {
|
||||||
|
*host_mac = Some(tap.get_mac_addr().map_err(Error::TapGetMac)?)
|
||||||
|
}
|
||||||
|
tap.enable().map_err(Error::TapEnable)?;
|
||||||
|
tap.set_offload(flag).map_err(Error::TapSetOffload)?;
|
||||||
|
|
||||||
|
tap.set_vnet_hdr_size(vnet_hdr_size)
|
||||||
|
.map_err(Error::TapSetVnetHdrSize)?;
|
||||||
|
|
||||||
|
ifname = String::from_utf8(tap.get_if_name()).unwrap();
|
||||||
|
} else {
|
||||||
|
tap = Tap::open_named(ifname.as_str(), num_rx_q).map_err(Error::TapOpen)?;
|
||||||
|
tap.set_offload(flag).map_err(Error::TapSetOffload)?;
|
||||||
|
|
||||||
|
tap.set_vnet_hdr_size(vnet_hdr_size)
|
||||||
|
.map_err(Error::TapSetVnetHdrSize)?;
|
||||||
|
}
|
||||||
|
taps.push(tap);
|
||||||
|
}
|
||||||
|
Ok(taps)
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ extern crate virtio_devices;
|
|||||||
|
|
||||||
use libc::{self, EFD_NONBLOCK};
|
use libc::{self, EFD_NONBLOCK};
|
||||||
use log::*;
|
use log::*;
|
||||||
use net_util::{MacAddr, Tap};
|
use net_util::{open_tap, MacAddr, OpenTapError, Tap};
|
||||||
use option_parser::{OptionParser, OptionParserError};
|
use option_parser::{OptionParser, OptionParserError};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::{self};
|
use std::io::{self};
|
||||||
@@ -28,7 +28,7 @@ use vhost_rs::vhost_user::{Error as VhostUserError, Listener};
|
|||||||
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring, VringWorker};
|
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring, VringWorker};
|
||||||
use virtio_bindings::bindings::virtio_net::*;
|
use virtio_bindings::bindings::virtio_net::*;
|
||||||
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
|
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
|
||||||
use virtio_devices::net_util::{open_tap, RxVirtio, TxVirtio};
|
use virtio_devices::net_util::{RxVirtio, TxVirtio};
|
||||||
use virtio_devices::{NetCounters, NetQueuePair};
|
use virtio_devices::{NetCounters, NetQueuePair};
|
||||||
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap};
|
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
@@ -66,7 +66,7 @@ pub enum Error {
|
|||||||
/// No memory configured.
|
/// No memory configured.
|
||||||
NoMemoryConfigured,
|
NoMemoryConfigured,
|
||||||
/// Open tap device failed.
|
/// Open tap device failed.
|
||||||
OpenTap(virtio_devices::net_util::Error),
|
OpenTap(OpenTapError),
|
||||||
/// No socket provided
|
/// No socket provided
|
||||||
SocketParameterMissing,
|
SocketParameterMissing,
|
||||||
/// Underlying QueuePair error
|
/// Underlying QueuePair error
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
// found in the THIRD-PARTY file.
|
// found in the THIRD-PARTY file.
|
||||||
|
|
||||||
use super::net_util::{
|
use super::net_util::{
|
||||||
build_net_config_space, build_net_config_space_with_mq, open_tap, register_listener,
|
build_net_config_space, build_net_config_space_with_mq, register_listener, unregister_listener,
|
||||||
unregister_listener, CtrlVirtio, NetCtrlEpollHandler, RxVirtio, TxVirtio, VirtioNetConfig,
|
CtrlVirtio, NetCtrlEpollHandler, RxVirtio, TxVirtio, VirtioNetConfig, KILL_EVENT,
|
||||||
KILL_EVENT, NET_EVENTS_COUNT, PAUSE_EVENT, RX_QUEUE_EVENT, RX_TAP_EVENT, TX_QUEUE_EVENT,
|
NET_EVENTS_COUNT, PAUSE_EVENT, RX_QUEUE_EVENT, RX_TAP_EVENT, TX_QUEUE_EVENT,
|
||||||
};
|
};
|
||||||
use super::Error as DeviceError;
|
use super::Error as DeviceError;
|
||||||
use super::{
|
use super::{
|
||||||
@@ -18,6 +18,7 @@ use crate::VirtioInterrupt;
|
|||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use libc::EAGAIN;
|
use libc::EAGAIN;
|
||||||
use libc::EFD_NONBLOCK;
|
use libc::EFD_NONBLOCK;
|
||||||
|
use net_util::{open_tap, OpenTapError};
|
||||||
use net_util::{MacAddr, Tap};
|
use net_util::{MacAddr, Tap};
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -44,7 +45,7 @@ use vmm_sys_util::eventfd::EventFd;
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Failed to open taps.
|
/// Failed to open taps.
|
||||||
OpenTap(super::net_util::Error),
|
OpenTap(OpenTapError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|||||||
@@ -4,17 +4,14 @@
|
|||||||
|
|
||||||
use super::Error as DeviceError;
|
use super::Error as DeviceError;
|
||||||
use super::{DescriptorChain, DeviceEventT, Queue};
|
use super::{DescriptorChain, DeviceEventT, Queue};
|
||||||
use net_util::{MacAddr, Tap, TapError};
|
use net_util::{MacAddr, Tap};
|
||||||
use serde::ser::{Serialize, SerializeStruct, Serializer};
|
use serde::ser::{Serialize, SerializeStruct, Serializer};
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::fs;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::net::Ipv4Addr;
|
|
||||||
use std::num::Wrapping;
|
use std::num::Wrapping;
|
||||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
@@ -94,8 +91,6 @@ unsafe impl ByteValued for VirtioNetConfig {}
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Failed to convert an hexadecimal string into an integer.
|
|
||||||
ConvertHexStringToInt(std::num::ParseIntError),
|
|
||||||
/// Read process MQ.
|
/// Read process MQ.
|
||||||
FailedProcessMQ,
|
FailedProcessMQ,
|
||||||
/// Read queue failed.
|
/// Read queue failed.
|
||||||
@@ -108,30 +103,10 @@ pub enum Error {
|
|||||||
InvalidDesc,
|
InvalidDesc,
|
||||||
/// Invalid queue pairs number
|
/// Invalid queue pairs number
|
||||||
InvalidQueuePairsNum,
|
InvalidQueuePairsNum,
|
||||||
/// Error related to the multiqueue support.
|
|
||||||
MultiQueueSupport(String),
|
|
||||||
/// No memory passed in.
|
/// No memory passed in.
|
||||||
NoMemory,
|
NoMemory,
|
||||||
/// No ueue pairs nummber.
|
/// No ueue pairs nummber.
|
||||||
NoQueuePairsNum,
|
NoQueuePairsNum,
|
||||||
/// Failed to read the TAP flags from sysfs.
|
|
||||||
ReadSysfsTunFlags(io::Error),
|
|
||||||
/// Open tap device failed.
|
|
||||||
TapOpen(TapError),
|
|
||||||
/// Setting tap IP failed.
|
|
||||||
TapSetIp(TapError),
|
|
||||||
/// Setting tap netmask failed.
|
|
||||||
TapSetNetmask(TapError),
|
|
||||||
/// Setting MAC address failed
|
|
||||||
TapSetMac(TapError),
|
|
||||||
/// Getting MAC address failed
|
|
||||||
TapGetMac(TapError),
|
|
||||||
/// Setting tap interface offload flags failed.
|
|
||||||
TapSetOffload(TapError),
|
|
||||||
/// Setting vnet header size failed.
|
|
||||||
TapSetVnetHdrSize(TapError),
|
|
||||||
/// Enabling tap interface failed.
|
|
||||||
TapEnable(TapError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CtrlVirtio {
|
pub struct CtrlVirtio {
|
||||||
@@ -538,85 +513,3 @@ pub fn build_net_config_space_with_mq(
|
|||||||
fn vnet_hdr_len() -> usize {
|
fn vnet_hdr_len() -> usize {
|
||||||
mem::size_of::<virtio_net_hdr_v1>()
|
mem::size_of::<virtio_net_hdr_v1>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_mq_support(if_name: &Option<&str>, queue_pairs: usize) -> Result<()> {
|
|
||||||
if let Some(tap_name) = if_name {
|
|
||||||
let mq = queue_pairs > 1;
|
|
||||||
let path = format!("/sys/class/net/{}/tun_flags", tap_name);
|
|
||||||
// interface does not exist, check is not required
|
|
||||||
if !Path::new(&path).exists() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let tun_flags_str = fs::read_to_string(path).map_err(Error::ReadSysfsTunFlags)?;
|
|
||||||
let tun_flags = u32::from_str_radix(tun_flags_str.trim().trim_start_matches("0x"), 16)
|
|
||||||
.map_err(Error::ConvertHexStringToInt)?;
|
|
||||||
if (tun_flags & net_gen::IFF_MULTI_QUEUE != 0) && !mq {
|
|
||||||
return Err(Error::MultiQueueSupport(String::from(
|
|
||||||
"TAP interface supports MQ while device does not",
|
|
||||||
)));
|
|
||||||
} else if (tun_flags & net_gen::IFF_MULTI_QUEUE == 0) && mq {
|
|
||||||
return Err(Error::MultiQueueSupport(String::from(
|
|
||||||
"Device supports MQ while TAP interface does not",
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new virtio network device with the given IP address and
|
|
||||||
/// netmask.
|
|
||||||
pub fn open_tap(
|
|
||||||
if_name: Option<&str>,
|
|
||||||
ip_addr: Option<Ipv4Addr>,
|
|
||||||
netmask: Option<Ipv4Addr>,
|
|
||||||
host_mac: &mut Option<MacAddr>,
|
|
||||||
num_rx_q: usize,
|
|
||||||
) -> Result<Vec<Tap>> {
|
|
||||||
let mut taps: Vec<Tap> = Vec::new();
|
|
||||||
let mut ifname: String = String::new();
|
|
||||||
let vnet_hdr_size = vnet_hdr_len() as i32;
|
|
||||||
let flag = net_gen::TUN_F_CSUM | net_gen::TUN_F_UFO | net_gen::TUN_F_TSO4 | net_gen::TUN_F_TSO6;
|
|
||||||
|
|
||||||
// In case the tap interface already exists, check if the number of
|
|
||||||
// queues is appropriate. The tap might not support multiqueue while
|
|
||||||
// the number of queues indicates the user expects multiple queues, or
|
|
||||||
// on the contrary, the tap might support multiqueue while the number
|
|
||||||
// of queues indicates the user doesn't expect multiple queues.
|
|
||||||
check_mq_support(&if_name, num_rx_q)?;
|
|
||||||
|
|
||||||
for i in 0..num_rx_q {
|
|
||||||
let tap: Tap;
|
|
||||||
if i == 0 {
|
|
||||||
tap = match if_name {
|
|
||||||
Some(name) => Tap::open_named(name, num_rx_q).map_err(Error::TapOpen)?,
|
|
||||||
None => Tap::new(num_rx_q).map_err(Error::TapOpen)?,
|
|
||||||
};
|
|
||||||
if let Some(ip) = ip_addr {
|
|
||||||
tap.set_ip_addr(ip).map_err(Error::TapSetIp)?;
|
|
||||||
}
|
|
||||||
if let Some(mask) = netmask {
|
|
||||||
tap.set_netmask(mask).map_err(Error::TapSetNetmask)?;
|
|
||||||
}
|
|
||||||
if let Some(mac) = host_mac {
|
|
||||||
tap.set_mac_addr(*mac).map_err(Error::TapSetMac)?
|
|
||||||
} else {
|
|
||||||
*host_mac = Some(tap.get_mac_addr().map_err(Error::TapGetMac)?)
|
|
||||||
}
|
|
||||||
tap.enable().map_err(Error::TapEnable)?;
|
|
||||||
tap.set_offload(flag).map_err(Error::TapSetOffload)?;
|
|
||||||
|
|
||||||
tap.set_vnet_hdr_size(vnet_hdr_size)
|
|
||||||
.map_err(Error::TapSetVnetHdrSize)?;
|
|
||||||
|
|
||||||
ifname = String::from_utf8(tap.get_if_name()).unwrap();
|
|
||||||
} else {
|
|
||||||
tap = Tap::open_named(ifname.as_str(), num_rx_q).map_err(Error::TapOpen)?;
|
|
||||||
tap.set_offload(flag).map_err(Error::TapSetOffload)?;
|
|
||||||
|
|
||||||
tap.set_vnet_hdr_size(vnet_hdr_size)
|
|
||||||
.map_err(Error::TapSetVnetHdrSize)?;
|
|
||||||
}
|
|
||||||
taps.push(tap);
|
|
||||||
}
|
|
||||||
Ok(taps)
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user