win32: fix ethernet and bip6 (#774)
Switch from WinPcap ton npcap. Include npcap sdk in cmake add libs for ipv6 in cmake fix ethernet uninitialized var
This commit is contained in:
+31
-3
@@ -673,8 +673,8 @@ elseif(WIN32)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
winmm
|
||||
$<$<BOOL:${BACDL_BIP}>:ws2_32>
|
||||
$<$<BOOL:${BACDL_BIP}>:iphlpapi>)
|
||||
$<$<BOOL:${BACDL_BIP} OR BOOL:${BACDL_BIP6}>:ws2_32>
|
||||
$<$<BOOL:${BACDL_BIP} OR BOOL:${BACDL_BIP6}>:iphlpapi>)
|
||||
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
ports/win32/bacport.h
|
||||
@@ -683,10 +683,38 @@ elseif(WIN32)
|
||||
ports/win32/datetime-init.c
|
||||
$<$<BOOL:${BACDL_MSTP}>:ports/win32/dlmstp.c>
|
||||
# ports/win32/dlmstp-mm.c
|
||||
$<$<BOOL:${BACDL_ETHERNET}>:ports/win32/ethernet.c>
|
||||
ports/win32/mstimer-init.c
|
||||
$<$<BOOL:${BACDL_MSTP}>:ports/win32/rs485.c>
|
||||
$<$<BOOL:${BACDL_MSTP}>:ports/win32/rs485.h>)
|
||||
|
||||
if(BACDL_ETHERNET)
|
||||
include(ExternalProject)
|
||||
set(PCAP_LIB_DIR ${CMAKE_CURRENT_BINARY_DIR}/npcap/Lib/x64)
|
||||
set(PCAP_LIB_WPCAP ${PCAP_LIB_DIR}/wpcap.lib)
|
||||
set(PCAP_LIB_PACKET ${PCAP_LIB_DIR}/Packet.lib)
|
||||
set(PCAP_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/npcap/Include)
|
||||
message(STATUS "BACNET: npcap Include:..................\"${PCAP_INCLUDE}\"")
|
||||
message(STATUS "BACNET: npcap Lib wpcap:................\"${PCAP_LIB_WPCAP}\"")
|
||||
message(STATUS "BACNET: npcap Lib Packet:...............\"${PCAP_LIB_PACKET}\"")
|
||||
ExternalProject_Add(npcap
|
||||
URL https://npcap.com/dist/npcap-sdk-1.13.zip
|
||||
URL_HASH SHA1=8d5bb6f3adb813374402344a8d2a12b9cb725197
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP true
|
||||
SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/npcap
|
||||
BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/npcap/Lib/x64
|
||||
UPDATE_COMMAND ""
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
TEST_COMMAND ""
|
||||
BUILD_BYPRODUCTS ${PCAP_LIB_WPCAP}
|
||||
BUILD_BYPRODUCTS ${PCAP_LIB_PACKET})
|
||||
include_directories(${PCAP_INCLUDE})
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE ${PCAP_LIB_WPCAP})
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE ${PCAP_LIB_PACKET})
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
ports/win32/ethernet.c)
|
||||
endif()
|
||||
elseif(APPLE)
|
||||
message(STATUS "BACNET: building for APPLE")
|
||||
set(BACNET_PORT_DIRECTORY_PATH ${CMAKE_CURRENT_LIST_DIR}/ports/bsd)
|
||||
|
||||
+3
-3
@@ -393,9 +393,9 @@ int bip6_send_mpdu(
|
||||
* Otherwise, -1 shall be returned and errno set to indicate the error.
|
||||
*/
|
||||
int bip6_send_pdu(
|
||||
const BACNET_ADDRESS *dest,
|
||||
const BACNET_NPDU_DATA *npdu_data,
|
||||
const uint8_t *pdu,
|
||||
BACNET_ADDRESS *dest,
|
||||
BACNET_NPDU_DATA *npdu_data,
|
||||
uint8_t *pdu,
|
||||
unsigned pdu_len)
|
||||
{
|
||||
return bvlc6_send_pdu(dest, npdu_data, pdu, pdu_len);
|
||||
|
||||
+168
-47
@@ -15,21 +15,20 @@
|
||||
#include "bacnet/datalink/ethernet.h"
|
||||
#include "bacnet/bacdcode.h"
|
||||
|
||||
/* Uses WinPCap to access raw ethernet */
|
||||
/* Uses Npcap to access raw ethernet */
|
||||
/* Notes: */
|
||||
/* To make ethernet.c work under win32, you have to: */
|
||||
/* 1. install winpcap 3.1 development pack; */
|
||||
/* 2. install Microsoft Platform SDK Feb 2003. */
|
||||
/* 1. install Npcap 1.80 installer for Windows; */
|
||||
/* 2. install msys2 x86_64 */
|
||||
/* 3. remove or modify functions used for log such as */
|
||||
/* "LogError()", "LogInfo()", which were implemented */
|
||||
/* as a wrapper of Log4cpp. */
|
||||
/* -- Kevin Liao */
|
||||
/* -- Patrick Grimm */
|
||||
|
||||
/* includes for accessing ethernet by using winpcap */
|
||||
#include "pcap.h"
|
||||
#include "packet32.h"
|
||||
#include "ntddndis.h"
|
||||
#include "remote-ext.h"
|
||||
|
||||
/* commonly used comparison address for ethernet */
|
||||
uint8_t Ethernet_Broadcast[MAX_MAC_LEN] = {
|
||||
@@ -46,14 +45,105 @@ static char pcap_errbuf[PCAP_ERRBUF_SIZE + 1];
|
||||
static pcap_t *pcap_eth802_fp = NULL; /* 802.2 file handle, from winpcap */
|
||||
static unsigned eth_timeout = 100;
|
||||
|
||||
/* #######Begin Packet32.h copy########*/
|
||||
/*!
|
||||
\brief Structure containing an OID request.
|
||||
|
||||
It is used by the PacketRequest() function to send an OID to the interface
|
||||
card driver. It can be used, for example, to retrieve the status of the error
|
||||
counters on the adapter, its MAC address, the list of the multicast groups
|
||||
defined on it, and so on.
|
||||
*/
|
||||
struct _PACKET_OID_DATA {
|
||||
ULONG Oid; /* ///< OID code. See the Microsoft DDK
|
||||
documentation or the file ntddndis.h
|
||||
///< for a complete list of valid codes. */
|
||||
ULONG Length; /* ///< Length of the data field
|
||||
_Field_size_full_(Length)*/
|
||||
UCHAR Data[1]; /* ///< variable-lenght field that contains the
|
||||
information passed to or received
|
||||
///< from the adapter.*/
|
||||
};
|
||||
typedef struct _PACKET_OID_DATA PACKET_OID_DATA, *PPACKET_OID_DATA;
|
||||
|
||||
#define MAX_LINK_NAME_LENGTH \
|
||||
64 /* //< Maximum length of the devices symbolic links*/
|
||||
#define ADAPTER_NAME_LENGTH \
|
||||
256 + 12 /*///< Maximum length for the name of an adapter. The value is \
|
||||
the same used by the IP Helper API.*/
|
||||
typedef struct WAN_ADAPTER_INT
|
||||
WAN_ADAPTER; /*///< Describes an opened wan (dialup, VPN...) network adapter
|
||||
using the NetMon API*/
|
||||
typedef WAN_ADAPTER
|
||||
*PWAN_ADAPTER; /*///< Describes an opened wan (dialup, VPN...) network
|
||||
adapter using the NetMon API*/
|
||||
|
||||
/*!
|
||||
\brief Describes an opened network adapter.
|
||||
|
||||
This structure is the most important for the functioning of packet.dll, but
|
||||
the great part of its fields should be ignored by the user, since the library
|
||||
offers functions that avoid to cope with low-level parameters
|
||||
*/
|
||||
typedef struct _ADAPTER {
|
||||
HANDLE hFile; /* ///< \internal Handle to an open instance of the
|
||||
NPF driver.*/
|
||||
CHAR
|
||||
SymbolicLink[MAX_LINK_NAME_LENGTH]; /*///< \internal A string containing
|
||||
the name of the network adapter
|
||||
currently opened.*/
|
||||
int NumWrites; /* ///< \internal Number of times a packets written
|
||||
on this adapter will be repeated
|
||||
///< on the wire.*/
|
||||
HANDLE
|
||||
ReadEvent; /* ///< A notification event associated with the read
|
||||
calls on the adapter.
|
||||
///< It can be passed to standard Win32 functions (like WaitForSingleObject
|
||||
///< or WaitForMultipleObjects) to wait until the driver's buffer contains some
|
||||
///< data. It is particularly useful in GUI applications that need to wait
|
||||
///< concurrently on several events. The PacketSetMinToCopy()
|
||||
///< function can be used to define the minimum amount of data in the kernel
|
||||
buffer
|
||||
///< that will cause the event to be signalled. */
|
||||
|
||||
UINT ReadTimeOut; /*///< \internal The amount of time PacketReceivePacket
|
||||
will wait for the ReadEvent to be signalled before
|
||||
issuing a ReadFile.*/
|
||||
CHAR Name[ADAPTER_NAME_LENGTH];
|
||||
PWAN_ADAPTER pWanAdapter;
|
||||
UINT Flags; /* ///< Adapter's flags. Tell if this adapter must be
|
||||
treated in a different way.*/
|
||||
|
||||
#ifdef HAVE_AIRPCAP_API
|
||||
PAirpcapHandle AirpcapAd;
|
||||
#endif /*// HAVE_AIRPCAP_API*/
|
||||
} ADAPTER, *LPADAPTER;
|
||||
|
||||
_Ret_maybenull_ LPADAPTER PacketOpenAdapter(_In_ PCCH AdapterName);
|
||||
_Success_(return) BOOLEAN PacketRequest(
|
||||
_In_ LPADAPTER AdapterObject,
|
||||
_In_ BOOLEAN Set,
|
||||
_Inout_ PPACKET_OID_DATA OidData);
|
||||
VOID PacketCloseAdapter(_In_ _Post_invalid_ LPADAPTER lpAdapter);
|
||||
|
||||
/* #######End Packet32.h copy##########*/
|
||||
|
||||
/* couple of external func for runtime error logging, you can simply */
|
||||
/* replace them with standard "printf(...)" */
|
||||
/* Logging extern functions: Info level */
|
||||
extern void LogInfo(const char *msg);
|
||||
/* Logging extern functions: Error level*/
|
||||
extern void LogError(const char *msg);
|
||||
/* Logging extern functions: Debug level*/
|
||||
extern void LogDebug(const char *msg);
|
||||
/* Logging functions: Info level */
|
||||
void LogInfo(const char *msg)
|
||||
{
|
||||
fprintf(stdout, "info ethernet: %s", msg);
|
||||
}
|
||||
/* Logging functions: Error level*/
|
||||
void LogError(const char *msg)
|
||||
{
|
||||
fprintf(stderr, "error ethernet: %s", msg);
|
||||
}
|
||||
/* Logging functions: Debug level*/
|
||||
void LogDebug(const char *msg)
|
||||
{
|
||||
fprintf(stdout, "debug ethernet: %s", msg);
|
||||
}
|
||||
|
||||
bool ethernet_valid(void)
|
||||
{
|
||||
@@ -66,7 +156,7 @@ void ethernet_cleanup(void)
|
||||
pcap_close(pcap_eth802_fp);
|
||||
pcap_eth802_fp = NULL;
|
||||
}
|
||||
LogInfo("ethernet.c: ethernet_cleanup() ok.\n");
|
||||
LogInfo("ethernet_cleanup() ok.\n");
|
||||
}
|
||||
|
||||
void ethernet_set_timeout(unsigned timeout)
|
||||
@@ -108,7 +198,9 @@ bool ethernet_init(char *if_name)
|
||||
BOOLEAN result;
|
||||
CHAR str[sizeof(PACKET_OID_DATA) + 128];
|
||||
int i;
|
||||
char msg[200];
|
||||
char msg[400];
|
||||
int devnum;
|
||||
char *device = NULL;
|
||||
|
||||
if (ethernet_valid()) {
|
||||
ethernet_cleanup();
|
||||
@@ -118,24 +210,42 @@ bool ethernet_init(char *if_name)
|
||||
* Find the interface user specified
|
||||
*/
|
||||
/* Retrieve the device list */
|
||||
if (pcap_findalldevs(&pcap_all_if, pcap_errbuf) == -1) {
|
||||
snprintf(
|
||||
msg, sizeof(msg), "ethernet.c: error in pcap_findalldevs: %s\n",
|
||||
pcap_errbuf);
|
||||
if (pcap_findalldevs(&pcap_all_if, pcap_errbuf) == PCAP_ERROR) {
|
||||
snprintf(msg, sizeof(msg), "pcap_findalldevs: %s\n", pcap_errbuf);
|
||||
LogError(msg);
|
||||
return false;
|
||||
}
|
||||
/* Scan the list printing every entry */
|
||||
devnum = atoi(if_name);
|
||||
i = 0;
|
||||
for (dev = pcap_all_if; dev; dev = dev->next) {
|
||||
if (strcmp(if_name, dev->name) == 0) {
|
||||
break;
|
||||
/* struct pcap_addr *dev_addr;*/
|
||||
i++;
|
||||
if (devnum == i) {
|
||||
device = dev->name;
|
||||
snprintf(msg, sizeof(msg), "interface select index: %i\n", i);
|
||||
LogInfo(msg);
|
||||
}
|
||||
if ((dev->flags & PCAP_IF_UP) && !(dev->flags & PCAP_IF_LOOPBACK) &&
|
||||
(dev->flags & PCAP_IF_RUNNING) &&
|
||||
(dev->flags & PCAP_IF_CONNECTION_STATUS_CONNECTED)) {
|
||||
snprintf(msg, sizeof(msg), "interface index: %i\n", i);
|
||||
LogInfo(msg);
|
||||
snprintf(msg, sizeof(msg), " name: %s\n", dev->name);
|
||||
LogInfo(msg);
|
||||
snprintf(msg, sizeof(msg), " description: %s\n", dev->description);
|
||||
LogInfo(msg);
|
||||
}
|
||||
}
|
||||
pcap_freealldevs(pcap_all_if); /* we don't need it anymore */
|
||||
if (dev == NULL) {
|
||||
if (if_name == NULL) {
|
||||
snprintf(msg, sizeof(msg), "interface index not set\n");
|
||||
LogError(msg);
|
||||
return false;
|
||||
}
|
||||
if (device == NULL) {
|
||||
snprintf(
|
||||
msg, sizeof(msg), "ethernet.c: specified interface not found: %s\n",
|
||||
if_name);
|
||||
msg, sizeof(msg), "specified interface not found: %s\n", if_name);
|
||||
LogError(msg);
|
||||
return false;
|
||||
}
|
||||
@@ -144,12 +254,11 @@ bool ethernet_init(char *if_name)
|
||||
* Get local MAC address
|
||||
*/
|
||||
ZeroMemory(str, sizeof(PACKET_OID_DATA) + 128);
|
||||
lpAdapter = PacketOpenAdapter(if_name);
|
||||
lpAdapter = PacketOpenAdapter(device);
|
||||
if (lpAdapter == NULL) {
|
||||
ethernet_cleanup();
|
||||
snprintf(
|
||||
msg, sizeof(msg),
|
||||
"ethernet.c: error in PacketOpenAdapter(\"%s\")\n", if_name);
|
||||
msg, sizeof(msg), "local mac PacketOpenAdapter(\"%s\")\n", device);
|
||||
LogError(msg);
|
||||
return false;
|
||||
}
|
||||
@@ -160,7 +269,7 @@ bool ethernet_init(char *if_name)
|
||||
if (!result) {
|
||||
PacketCloseAdapter(lpAdapter);
|
||||
ethernet_cleanup();
|
||||
LogError("ethernet.c: error in PacketRequest()\n");
|
||||
LogError("local mac PacketRequest()\n");
|
||||
return false;
|
||||
}
|
||||
for (i = 0; i < 6; ++i) {
|
||||
@@ -168,16 +277,23 @@ bool ethernet_init(char *if_name)
|
||||
}
|
||||
PacketCloseAdapter(lpAdapter);
|
||||
|
||||
snprintf(
|
||||
msg, sizeof(msg), "local mac %02x:%02x:%02x:%02x:%02x:%02x \n",
|
||||
Ethernet_MAC_Address[0], Ethernet_MAC_Address[1],
|
||||
Ethernet_MAC_Address[2], Ethernet_MAC_Address[3],
|
||||
Ethernet_MAC_Address[4], Ethernet_MAC_Address[5]);
|
||||
LogInfo(msg);
|
||||
|
||||
/**
|
||||
* Open interface for subsequent sending and receiving
|
||||
*/
|
||||
/* Open the output device */
|
||||
pcap_eth802_fp = pcap_open(
|
||||
if_name, /* name of the device */
|
||||
pcap_eth802_fp = pcap_open_live(
|
||||
device, /* name of the device */
|
||||
ETHERNET_MPDU_MAX, /* portion of the packet to capture */
|
||||
PCAP_OPENFLAG_PROMISCUOUS, /* promiscuous mode */
|
||||
eth_timeout, /* read timeout */
|
||||
NULL, /* authentication on the remote machine */
|
||||
/* NULL, */ /* authentication on the remote machine */
|
||||
pcap_errbuf /* error buffer */
|
||||
);
|
||||
if (pcap_eth802_fp == NULL) {
|
||||
@@ -185,14 +301,14 @@ bool ethernet_init(char *if_name)
|
||||
ethernet_cleanup();
|
||||
snprintf(
|
||||
msg, sizeof(msg),
|
||||
"ethernet.c: unable to open the adapter. %s is not supported by "
|
||||
"WinPcap\n",
|
||||
if_name);
|
||||
"unable to open the adapter. %s is not supported by "
|
||||
"Npcap\n",
|
||||
device);
|
||||
LogError(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
LogInfo("ethernet.c: ethernet_init() ok.\n");
|
||||
LogInfo("ethernet_init() ok.\n");
|
||||
|
||||
atexit(ethernet_cleanup);
|
||||
|
||||
@@ -201,21 +317,21 @@ bool ethernet_init(char *if_name)
|
||||
|
||||
/* function to send a packet out the 802.2 socket */
|
||||
/* returns bytes sent success, negative on failure */
|
||||
int ethernet_send(
|
||||
int ethernet_send_dst(
|
||||
BACNET_ADDRESS *dest, /* destination address */
|
||||
BACNET_ADDRESS *src, /* source address */
|
||||
uint8_t *pdu, /* any data to be sent - may be null */
|
||||
unsigned pdu_len /* number of bytes of data */
|
||||
)
|
||||
{
|
||||
int bytes = 0;
|
||||
/* int bytes = 0; */
|
||||
uint8_t mtu[ETHERNET_MPDU_MAX] = { 0 };
|
||||
int mtu_len = 0;
|
||||
int i = 0;
|
||||
|
||||
/* don't waste time if the socket is not valid */
|
||||
if (!ethernet_valid()) {
|
||||
LogError("ethernet.c: invalid 802.2 ethernet interface descriptor!\n");
|
||||
LogError("invalid 802.2 ethernet interface descriptor!\n");
|
||||
return -1;
|
||||
}
|
||||
/* load destination ethernet MAC address */
|
||||
@@ -225,7 +341,7 @@ int ethernet_send(
|
||||
mtu_len++;
|
||||
}
|
||||
} else {
|
||||
LogError("ethernet.c: invalid destination MAC address!\n");
|
||||
LogError("invalid destination MAC address!\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
@@ -236,11 +352,11 @@ int ethernet_send(
|
||||
mtu_len++;
|
||||
}
|
||||
} else {
|
||||
LogError("ethernet.c: invalid source MAC address!\n");
|
||||
LogError("invalid source MAC address!\n");
|
||||
return -3;
|
||||
}
|
||||
if ((14 + 3 + pdu_len) > ETHERNET_MPDU_MAX) {
|
||||
LogError("ethernet.c: PDU is too big to send!\n");
|
||||
LogError("PDU is too big to send!\n");
|
||||
return -4;
|
||||
}
|
||||
/* packet length */
|
||||
@@ -257,7 +373,7 @@ int ethernet_send(
|
||||
/* did it get sent? */
|
||||
char msg[200];
|
||||
snprintf(
|
||||
msg, sizeof(msg), "ethernet.c: error sending packet: %s\n",
|
||||
msg, sizeof(msg), "error sending packet: %s\n",
|
||||
pcap_geterr(pcap_eth802_fp));
|
||||
LogError(msg);
|
||||
return -5;
|
||||
@@ -270,12 +386,14 @@ int ethernet_send(
|
||||
/* returns number of bytes sent on success, negative on failure */
|
||||
int ethernet_send_pdu(
|
||||
BACNET_ADDRESS *dest, /* destination address */
|
||||
BACNET_NPDU_DATA *npdu_data, /* network information */
|
||||
uint8_t *pdu, /* any data to be sent - may be null */
|
||||
unsigned pdu_len /* number of bytes of data */
|
||||
)
|
||||
{
|
||||
int i = 0; /* counter */
|
||||
BACNET_ADDRESS src = { 0 }; /* source address */
|
||||
(void)npdu_data;
|
||||
|
||||
for (i = 0; i < 6; i++) {
|
||||
src.mac[i] = Ethernet_MAC_Address[i];
|
||||
@@ -283,7 +401,7 @@ int ethernet_send_pdu(
|
||||
}
|
||||
/* function to send a packet out the 802.2 socket */
|
||||
/* returns 1 on success, 0 on failure */
|
||||
return ethernet_send(
|
||||
return ethernet_send_dst(
|
||||
dest, /* destination address */
|
||||
&src, /* source address */
|
||||
pdu, /* any data to be sent - may be null */
|
||||
@@ -303,12 +421,14 @@ uint16_t ethernet_receive(
|
||||
{
|
||||
struct pcap_pkthdr *header;
|
||||
int res;
|
||||
u_char *pkt_data;
|
||||
const u_char *pkt_data;
|
||||
uint16_t pdu_len = 0; /* return value */
|
||||
|
||||
/* unused ? */
|
||||
(void)timeout;
|
||||
/* Make sure the socket is open */
|
||||
if (!ethernet_valid()) {
|
||||
LogError("ethernet.c: invalid 802.2 ethernet interface descriptor!\n");
|
||||
LogError("invalid 802.2 ethernet interface descriptor!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -317,8 +437,9 @@ uint16_t ethernet_receive(
|
||||
if (res < 0) {
|
||||
char msg[200];
|
||||
snprintf(
|
||||
msg, sizeof(), "ethernet.c: error in receiving packet: %s\n",
|
||||
msg, sizeof(msg), "error in receiving packet: %s\n",
|
||||
pcap_geterr(pcap_eth802_fp));
|
||||
LogError(msg);
|
||||
return 0;
|
||||
} else if (res == 0) {
|
||||
return 0;
|
||||
@@ -330,7 +451,7 @@ uint16_t ethernet_receive(
|
||||
|
||||
/* the signature of an 802.2 BACnet packet */
|
||||
if ((pkt_data[14] != 0x82) && (pkt_data[15] != 0x82)) {
|
||||
/*eth_log_error("ethernet.c: Non-BACnet packet\n"); */
|
||||
/*LogError("ethernet.c: Non-BACnet packet\n"); */
|
||||
return 0;
|
||||
}
|
||||
/* copy the source address */
|
||||
@@ -341,7 +462,7 @@ uint16_t ethernet_receive(
|
||||
/* the Ethernet card is in promiscious mode */
|
||||
if ((memcmp(&pkt_data[0], Ethernet_MAC_Address, 6) != 0) &&
|
||||
(memcmp(&pkt_data[0], Ethernet_Broadcast, 6) != 0)) {
|
||||
/*eth_log_error( "ethernet.c: This packet isn't for us\n"); */
|
||||
/*LogError( "ethernet.c: This packet isn't for us\n"); */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user