mirror of
https://github.com/espressif/openthread.git
synced 2026-08-13 14:17:47 +00:00
[net] add Ip4::Address class (#6334)
This commit adds IPv4 address definitions in OpenThread: - Adds `Ip4::Address` class. - Adds methods to convert IPv4 address to/from string. - Updates the `Ip6::Address::FromString()` to use the IPv4 `FromString()` for the case where the IPv6 address contains an embedded IPv4 address. - Renames unit test to `test_ip_address` and updates it to cover the behavior of newly added IPv4 definitions in addition to IPv6 related tests.
This commit is contained in:
@@ -269,6 +269,7 @@ LOCAL_SRC_FILES := \
|
||||
src/core/net/dns_types.cpp \
|
||||
src/core/net/dnssd_server.cpp \
|
||||
src/core/net/icmp6.cpp \
|
||||
src/core/net/ip4_address.cpp \
|
||||
src/core/net/ip6.cpp \
|
||||
src/core/net/ip6_address.cpp \
|
||||
src/core/net/ip6_filter.cpp \
|
||||
|
||||
@@ -492,6 +492,8 @@ openthread_core_files = [
|
||||
"net/dnssd_server.hpp",
|
||||
"net/icmp6.cpp",
|
||||
"net/icmp6.hpp",
|
||||
"net/ip4_address.cpp",
|
||||
"net/ip4_address.hpp",
|
||||
"net/ip6.cpp",
|
||||
"net/ip6.hpp",
|
||||
"net/ip6_address.cpp",
|
||||
|
||||
@@ -145,6 +145,7 @@ set(COMMON_SOURCES
|
||||
net/dns_types.cpp
|
||||
net/dnssd_server.cpp
|
||||
net/icmp6.cpp
|
||||
net/ip4_address.cpp
|
||||
net/ip6.cpp
|
||||
net/ip6_address.cpp
|
||||
net/ip6_filter.cpp
|
||||
|
||||
@@ -222,6 +222,7 @@ SOURCES_COMMON = \
|
||||
net/dns_types.cpp \
|
||||
net/dnssd_server.cpp \
|
||||
net/icmp6.cpp \
|
||||
net/ip4_address.cpp \
|
||||
net/ip6.cpp \
|
||||
net/ip6_address.cpp \
|
||||
net/ip6_filter.cpp \
|
||||
@@ -476,6 +477,7 @@ HEADERS_COMMON = \
|
||||
net/dns_types.hpp \
|
||||
net/dnssd_server.hpp \
|
||||
net/icmp6.hpp \
|
||||
net/ip4_address.hpp \
|
||||
net/ip6.hpp \
|
||||
net/ip6_address.hpp \
|
||||
net/ip6_filter.hpp \
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (c) 2021, The OpenThread Authors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holder nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This file implements IPv4 address related functionality.
|
||||
*/
|
||||
|
||||
#include "ip4_address.hpp"
|
||||
|
||||
#include "common/code_utils.hpp"
|
||||
#include "common/numeric_limits.hpp"
|
||||
|
||||
namespace ot {
|
||||
namespace Ip4 {
|
||||
|
||||
Error Address::FromString(const char *aString)
|
||||
{
|
||||
enum : char
|
||||
{
|
||||
kSeperatorChar = '.',
|
||||
kNullChar = '\0',
|
||||
};
|
||||
|
||||
Error error = kErrorParse;
|
||||
|
||||
for (uint8_t index = 0;; index++)
|
||||
{
|
||||
uint16_t value = 0;
|
||||
uint8_t hasFirstDigit = false;
|
||||
|
||||
for (char digitChar = *aString;; ++aString, digitChar = *aString)
|
||||
{
|
||||
if ((digitChar < '0') || (digitChar > '9'))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
value = static_cast<uint16_t>((value * 10) + static_cast<uint8_t>(digitChar - '0'));
|
||||
VerifyOrExit(value <= NumericLimits<uint8_t>::Max());
|
||||
hasFirstDigit = true;
|
||||
}
|
||||
|
||||
VerifyOrExit(hasFirstDigit);
|
||||
|
||||
mBytes[index] = static_cast<uint8_t>(value);
|
||||
|
||||
if (index == sizeof(Address) - 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
VerifyOrExit(*aString == kSeperatorChar);
|
||||
aString++;
|
||||
}
|
||||
|
||||
VerifyOrExit(*aString == kNullChar);
|
||||
error = kErrorNone;
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
Address::InfoString Address::ToString(void) const
|
||||
{
|
||||
return InfoString("%d.%d.%d.%d", mBytes[0], mBytes[1], mBytes[2], mBytes[3]);
|
||||
}
|
||||
|
||||
} // namespace Ip4
|
||||
} // namespace ot
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (c) 2021, The OpenThread Authors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holder nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This file includes definitions for IPv4 addresses.
|
||||
*/
|
||||
|
||||
#ifndef IP4_ADDRESS_HPP_
|
||||
#define IP4_ADDRESS_HPP_
|
||||
|
||||
#include "openthread-core-config.h"
|
||||
|
||||
#include "common/clearable.hpp"
|
||||
#include "common/equatable.hpp"
|
||||
#include "common/error.hpp"
|
||||
#include "common/string.hpp"
|
||||
|
||||
namespace ot {
|
||||
namespace Ip4 {
|
||||
|
||||
/**
|
||||
* This class represents an IPv4 address.
|
||||
*
|
||||
*/
|
||||
OT_TOOL_PACKED_BEGIN
|
||||
class Address : public Equatable<Address>, public Clearable<Address>
|
||||
{
|
||||
public:
|
||||
enum : uint8_t
|
||||
{
|
||||
kSize = 4, ///< Size of an IPv4 Address (in bytes).
|
||||
kAddressStringSize = 17, ///< String size used by `ToString()`.
|
||||
};
|
||||
|
||||
/**
|
||||
* This type defines the fixed-length `String` object returned from `ToString()`.
|
||||
*
|
||||
*/
|
||||
typedef String<kAddressStringSize> InfoString;
|
||||
|
||||
/**
|
||||
* This method gets the IPv4 address as a pointer to a byte array.
|
||||
*
|
||||
* @returns A pointer to a byte array containing the IPv4 address.
|
||||
*
|
||||
*/
|
||||
const uint8_t *GetBytes(void) const { return mBytes; }
|
||||
|
||||
/**
|
||||
* This method sets the IPv4 address from a given byte array.
|
||||
*
|
||||
* @param[in] aBuffer Pointer to an array containing the IPv4 address. `kSize` bytes from the buffer
|
||||
* are copied to form the IPv4 address.
|
||||
*
|
||||
*/
|
||||
void SetBytes(const uint8_t *aBuffer) { memcpy(mBytes, aBuffer, kSize); }
|
||||
|
||||
/**
|
||||
* This method parses an IPv4 address string.
|
||||
*
|
||||
* The string MUST follow the quad-dotted notation of four decimal values (ranging from 0 to 255 each). For
|
||||
* example, "127.0.0.1"
|
||||
*
|
||||
* @param[in] aString A pointer to the null-terminated string.
|
||||
*
|
||||
* @retval kErrorNone Successfully parsed the IPv4 address string.
|
||||
* @retval kErrorParse Failed to parse the IPv4 address string.
|
||||
*
|
||||
*/
|
||||
Error FromString(const char *aString);
|
||||
|
||||
/**
|
||||
* This method converts the IPv4 address to a string.
|
||||
*
|
||||
* The string format uses quad-dotted notation of four bytes in the address (e.g., "127.0.0.1").
|
||||
*
|
||||
* @returns An `InfoString` representing the IPv4 address.
|
||||
*
|
||||
*/
|
||||
InfoString ToString(void) const;
|
||||
|
||||
private:
|
||||
uint8_t mBytes[kSize];
|
||||
} OT_TOOL_PACKED_END;
|
||||
|
||||
} // namespace Ip4
|
||||
} // namespace ot
|
||||
|
||||
#endif // IP4_ADDRESS_HPP_
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "common/encoding.hpp"
|
||||
#include "common/instance.hpp"
|
||||
#include "common/random.hpp"
|
||||
#include "net/ip4_address.hpp"
|
||||
#include "net/netif.hpp"
|
||||
|
||||
using ot::Encoding::BigEndian::HostSwap32;
|
||||
@@ -509,7 +510,7 @@ Error Address::FromString(const char *aBuf)
|
||||
hasIp4 = true;
|
||||
|
||||
// Do not count bytes of the embedded IPv4 address.
|
||||
endp -= kIp4AddressSize;
|
||||
endp -= Ip4::Address::kSize;
|
||||
|
||||
VerifyOrExit(dst <= endp, error = kErrorParse);
|
||||
|
||||
@@ -539,40 +540,10 @@ Error Address::FromString(const char *aBuf)
|
||||
|
||||
if (hasIp4)
|
||||
{
|
||||
val = 0;
|
||||
Ip4::Address ip4Addr;
|
||||
|
||||
// Reset the start and end pointers.
|
||||
dst = reinterpret_cast<uint8_t *>(mFields.m8 + 12);
|
||||
endp = reinterpret_cast<uint8_t *>(mFields.m8 + 15);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
ch = *colonc++;
|
||||
|
||||
if (ch == '.' || ch == '\0' || ch == ' ')
|
||||
{
|
||||
VerifyOrExit(dst <= endp, error = kErrorParse);
|
||||
|
||||
*dst++ = static_cast<uint8_t>(val);
|
||||
val = 0;
|
||||
|
||||
if (ch == '\0' || ch == ' ')
|
||||
{
|
||||
// Check if embedded IPv4 address had exactly four parts.
|
||||
VerifyOrExit(dst == endp + 1, error = kErrorParse);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
VerifyOrExit('0' <= ch && ch <= '9', error = kErrorParse);
|
||||
|
||||
val = (10 * val) + (ch & 0xf);
|
||||
|
||||
// Single part of IPv4 address has to fit in one byte.
|
||||
VerifyOrExit(val <= 0xff, error = kErrorParse);
|
||||
}
|
||||
}
|
||||
SuccessOrExit(error = ip4Addr.FromString(colonc));
|
||||
memcpy(mFields.m8 + 12, ip4Addr.GetBytes(), Ip4::Address::kSize);
|
||||
}
|
||||
|
||||
exit:
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <openthread/ip6.h>
|
||||
|
||||
#include "common/clearable.hpp"
|
||||
#include "common/encoding.hpp"
|
||||
#include "common/equatable.hpp"
|
||||
@@ -906,10 +908,7 @@ public:
|
||||
* @retval false The IPv6 address is larger than or equal to @p aOther.
|
||||
*
|
||||
*/
|
||||
bool operator<(const Ip6::Address &aOther) const
|
||||
{
|
||||
return memcmp(mFields.m8, aOther.mFields.m8, sizeof(Ip6::Address)) < 0;
|
||||
}
|
||||
bool operator<(const Address &aOther) const { return memcmp(mFields.m8, aOther.mFields.m8, sizeof(Address)) < 0; }
|
||||
|
||||
private:
|
||||
void SetPrefix(uint8_t aOffset, const uint8_t *aPrefix, uint8_t aPrefixLength);
|
||||
@@ -923,7 +922,6 @@ private:
|
||||
|
||||
enum
|
||||
{
|
||||
kIp4AddressSize = 4, ///< Size of the IPv4 address.
|
||||
kMulticastNetworkPrefixLengthOffset = 3, ///< Prefix-Based Multicast Address (RFC3306).
|
||||
kMulticastNetworkPrefixOffset = 4, ///< Prefix-Based Multicast Address (RFC3306).
|
||||
};
|
||||
|
||||
@@ -306,26 +306,26 @@ target_link_libraries(test-hmac-sha256
|
||||
|
||||
add_test(NAME test-hmac-sha256 COMMAND test-hmac-sha256)
|
||||
|
||||
add_executable(test-ip6-address
|
||||
test_ip6_address.cpp
|
||||
add_executable(test-ip-address
|
||||
test_ip_address.cpp
|
||||
)
|
||||
|
||||
target_include_directories(test-ip6-address
|
||||
target_include_directories(test-ip-address
|
||||
PRIVATE
|
||||
${COMMON_INCLUDES}
|
||||
)
|
||||
|
||||
target_compile_options(test-ip6-address
|
||||
target_compile_options(test-ip-address
|
||||
PRIVATE
|
||||
${COMMON_COMPILE_OPTIONS}
|
||||
)
|
||||
|
||||
target_link_libraries(test-ip6-address
|
||||
target_link_libraries(test-ip-address
|
||||
PRIVATE
|
||||
${COMMON_LIBS}
|
||||
)
|
||||
|
||||
add_test(NAME test-ip6-address COMMAND test-ip6-address)
|
||||
add_test(NAME test-ip-address COMMAND test-ip-address)
|
||||
|
||||
add_executable(test-link-quality
|
||||
test_link_quality.cpp
|
||||
@@ -720,7 +720,7 @@ set_target_properties(
|
||||
test-heap
|
||||
test-hkdf-sha256
|
||||
test-hmac-sha256
|
||||
test-ip6-address
|
||||
test-ip-address
|
||||
test-link-quality
|
||||
test-linked-list
|
||||
test-lookup-table
|
||||
|
||||
@@ -117,7 +117,7 @@ check_PROGRAMS += \
|
||||
test-heap \
|
||||
test-hkdf-sha256 \
|
||||
test-hmac-sha256 \
|
||||
test-ip6-address \
|
||||
test-ip-address \
|
||||
test-link-quality \
|
||||
test-linked-list \
|
||||
test-lookup-table \
|
||||
@@ -212,8 +212,8 @@ test_hkdf_sha256_SOURCES = $(COMMON_SOURCES) test_hkdf_sha256.cpp
|
||||
test_hmac_sha256_LDADD = $(COMMON_LDADD)
|
||||
test_hmac_sha256_SOURCES = $(COMMON_SOURCES) test_hmac_sha256.cpp
|
||||
|
||||
test_ip6_address_LDADD = $(COMMON_LDADD)
|
||||
test_ip6_address_SOURCES = $(COMMON_SOURCES) test_ip6_address.cpp
|
||||
test_ip_address_LDADD = $(COMMON_LDADD)
|
||||
test_ip_address_SOURCES = $(COMMON_SOURCES) test_ip_address.cpp
|
||||
|
||||
test_link_quality_LDADD = $(COMMON_LDADD)
|
||||
test_link_quality_SOURCES = $(COMMON_SOURCES) test_link_quality.cpp
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <limits.h>
|
||||
|
||||
#include "common/encoding.hpp"
|
||||
#include "net/ip4_address.hpp"
|
||||
#include "net/ip6_address.hpp"
|
||||
#include "net/ip6_headers.hpp"
|
||||
|
||||
@@ -36,32 +37,39 @@
|
||||
|
||||
using ot::Encoding::BigEndian::ReadUint16;
|
||||
|
||||
struct Ip6AddressStringTestVector
|
||||
template <typename AddressType> struct TestVector
|
||||
{
|
||||
const char * mString;
|
||||
const uint8_t mAddr[OT_IP6_ADDRESS_SIZE];
|
||||
const uint8_t mAddr[sizeof(AddressType)];
|
||||
ot::Error mError;
|
||||
};
|
||||
|
||||
static void checkAddressFromString(Ip6AddressStringTestVector *aTestVector)
|
||||
template <typename AddressType> static void checkAddressFromString(TestVector<AddressType> *aTestVector)
|
||||
{
|
||||
ot::Error error;
|
||||
ot::Ip6::Address address;
|
||||
ot::Error error;
|
||||
AddressType address;
|
||||
|
||||
address.Clear();
|
||||
|
||||
error = address.FromString(aTestVector->mString);
|
||||
|
||||
VerifyOrQuit(error == aTestVector->mError, "Ip6::Address::FromString returned unexpected error code");
|
||||
printf("%-42s -> %-42s\n", aTestVector->mString,
|
||||
(error == ot::kErrorNone) ? address.ToString().AsCString() : "(parse error)");
|
||||
|
||||
VerifyOrQuit(error == aTestVector->mError, "Address::FromString returned unexpected error code");
|
||||
|
||||
if (error == ot::kErrorNone)
|
||||
{
|
||||
VerifyOrQuit(0 == memcmp(address.mFields.m8, aTestVector->mAddr, OT_IP6_ADDRESS_SIZE),
|
||||
"Ip6::Address::FromString parsing failed");
|
||||
VerifyOrQuit(0 == memcmp(address.GetBytes(), aTestVector->mAddr, sizeof(AddressType)),
|
||||
"Address::FromString parsing failed");
|
||||
}
|
||||
}
|
||||
|
||||
void TestIp6AddressFromString(void)
|
||||
{
|
||||
Ip6AddressStringTestVector testVectors[] = {
|
||||
typedef TestVector<ot::Ip6::Address> Ip6AddressTestVector;
|
||||
|
||||
Ip6AddressTestVector testVectors[] = {
|
||||
// Valid full IPv6 address.
|
||||
{"0102:0304:0506:0708:090a:0b0c:0d0e:0f00",
|
||||
{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00},
|
||||
@@ -134,7 +142,33 @@ void TestIp6AddressFromString(void)
|
||||
{":f:0:0:c:0:f:f:.", {0}, ot::kErrorParse},
|
||||
};
|
||||
|
||||
for (Ip6AddressStringTestVector &testVector : testVectors)
|
||||
for (Ip6AddressTestVector &testVector : testVectors)
|
||||
{
|
||||
checkAddressFromString(&testVector);
|
||||
}
|
||||
}
|
||||
|
||||
void TestIp4AddressFromString(void)
|
||||
{
|
||||
typedef TestVector<ot::Ip4::Address> Ip4AddressTestVector;
|
||||
|
||||
Ip4AddressTestVector testVectors[] = {
|
||||
{"0.0.0.0", {0, 0, 0, 0}, ot::kErrorNone},
|
||||
{"255.255.255.255", {255, 255, 255, 255}, ot::kErrorNone},
|
||||
{"127.0.0.1", {127, 0, 0, 1}, ot::kErrorNone},
|
||||
{"1.2.3.4", {1, 2, 3, 4}, ot::kErrorNone},
|
||||
{"001.002.003.004", {1, 2, 3, 4}, ot::kErrorNone},
|
||||
{"00000127.000.000.000001", {127, 0, 0, 1}, ot::kErrorNone},
|
||||
{"123.231.0.256", {0}, ot::kErrorParse}, // Invalid byte value.
|
||||
{"100123.231.0.256", {0}, ot::kErrorParse}, // Invalid byte value.
|
||||
{"1.22.33", {0}, ot::kErrorParse}, // Too few bytes.
|
||||
{"1.22.33.44.5", {0}, ot::kErrorParse}, // Too many bytes.
|
||||
{"a.b.c.d", {0}, ot::kErrorParse}, // Wrong digit char.
|
||||
{"123.23.45 .12", {0}, ot::kErrorParse}, // Extra space.
|
||||
{".", {0}, ot::kErrorParse}, // Invalid.
|
||||
};
|
||||
|
||||
for (Ip4AddressTestVector &testVector : testVectors)
|
||||
{
|
||||
checkAddressFromString(&testVector);
|
||||
}
|
||||
@@ -360,6 +394,7 @@ void TestIp6Header(void)
|
||||
int main(void)
|
||||
{
|
||||
TestIp6AddressSetPrefix();
|
||||
TestIp4AddressFromString();
|
||||
TestIp6AddressFromString();
|
||||
TestIp6Prefix();
|
||||
TestIp6Header();
|
||||
Reference in New Issue
Block a user