fixed i forgot about but seem to work
This commit is contained in:
@@ -109,6 +109,8 @@ build/microtcp.c: 3p/include/tinycthread.h 3p/src/tinycthread.c $(wildcard src/*
|
|||||||
printf "\n#endif /* MICROTCP_BACKGROUND_THREAD */" >> $@
|
printf "\n#endif /* MICROTCP_BACKGROUND_THREAD */" >> $@
|
||||||
printf "\n#line 1 \"src/defs.h\"\n" >> $@
|
printf "\n#line 1 \"src/defs.h\"\n" >> $@
|
||||||
cat src/defs.h >> $@
|
cat src/defs.h >> $@
|
||||||
|
printf "\n#line 1 \"src/utils.h\"\n" >> $@
|
||||||
|
cat src/utils.h >> $@
|
||||||
printf "\n#line 1 \"src/endian.h\"\n" >> $@
|
printf "\n#line 1 \"src/endian.h\"\n" >> $@
|
||||||
cat src/endian.h >> $@
|
cat src/endian.h >> $@
|
||||||
printf "\n#line 1 \"src/arp.h\"\n" >> $@
|
printf "\n#line 1 \"src/arp.h\"\n" >> $@
|
||||||
@@ -121,6 +123,8 @@ build/microtcp.c: 3p/include/tinycthread.h 3p/src/tinycthread.c $(wildcard src/*
|
|||||||
cat src/tcp_timer.h >> $@
|
cat src/tcp_timer.h >> $@
|
||||||
printf "\n#line 1 \"src/tcp.h\"\n" >> $@
|
printf "\n#line 1 \"src/tcp.h\"\n" >> $@
|
||||||
cat src/tcp.h >> $@
|
cat src/tcp.h >> $@
|
||||||
|
printf "\n#line 1 \"src/utils.c\"\n" >> $@
|
||||||
|
cat src/utils.c >> $@
|
||||||
printf "\n#line 1 \"src/endian.c\"\n" >> $@
|
printf "\n#line 1 \"src/endian.c\"\n" >> $@
|
||||||
cat src/endian.c >> $@
|
cat src/endian.c >> $@
|
||||||
printf "\n#line 1 \"src/arp.c\"\n" >> $@
|
printf "\n#line 1 \"src/arp.c\"\n" >> $@
|
||||||
@@ -142,6 +146,9 @@ build/echo_http: examples/microhttp/main.c $(LIBDIR)/libtuntap.a 3p/include/tunt
|
|||||||
build/test: tests/test.c tests/test_tcp_timer.c src/tcp_timer.c src/tcp_timer.h
|
build/test: tests/test.c tests/test_tcp_timer.c src/tcp_timer.c src/tcp_timer.h
|
||||||
gcc -fprofile-arcs -ftest-coverage tests/test.c tests/test_tcp_timer.c src/tcp_timer.c -o $@ -Wall -Wextra -DTCP_MAX_TIMERS=4
|
gcc -fprofile-arcs -ftest-coverage tests/test.c tests/test_tcp_timer.c src/tcp_timer.c -o $@ -Wall -Wextra -DTCP_MAX_TIMERS=4
|
||||||
|
|
||||||
|
build/test_arp: tests/test_arp.c
|
||||||
|
gcc tests/test_arp.c -o build/test_arp -Wall -Wextra
|
||||||
|
|
||||||
report: build/test
|
report: build/test
|
||||||
./build/test
|
./build/test
|
||||||
gcov -b build/test-tcp_timer.c
|
gcov -b build/test-tcp_timer.c
|
||||||
|
|||||||
@@ -128,19 +128,6 @@ struct ethhdr {
|
|||||||
#define ARP_DEBUG_LOG(...)
|
#define ARP_DEBUG_LOG(...)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
ARP_HARDWARE_ETHERNET = 1,
|
|
||||||
} arp_hardware_type;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
ARP_PROTOCOL_IP = 0x800,
|
|
||||||
} arp_protocol_type;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
ARP_OPERATION_REQUEST = 1,
|
|
||||||
ARP_OPERATION_REPLY = 2,
|
|
||||||
} arp_operation_t;
|
|
||||||
|
|
||||||
void arp_change_output_buffer(arp_state_t *state, void *ptr, size_t max)
|
void arp_change_output_buffer(arp_state_t *state, void *ptr, size_t max)
|
||||||
{
|
{
|
||||||
if (max < sizeof(arp_packet_t))
|
if (max < sizeof(arp_packet_t))
|
||||||
@@ -619,6 +606,7 @@ void arp_resolve_mac(arp_state_t *state, ip_address_t ip, void *userp, void (*ca
|
|||||||
append_pending_request_to_used_list(state, pending_request);
|
append_pending_request_to_used_list(state, pending_request);
|
||||||
|
|
||||||
arp_packet_t *packet = state->output;
|
arp_packet_t *packet = state->output;
|
||||||
|
if (packet != NULL) {
|
||||||
packet->hardware_type = cpu_to_net_u16(ARP_HARDWARE_ETHERNET);
|
packet->hardware_type = cpu_to_net_u16(ARP_HARDWARE_ETHERNET);
|
||||||
packet->protocol_type = cpu_to_net_u16(ARP_PROTOCOL_IP);
|
packet->protocol_type = cpu_to_net_u16(ARP_PROTOCOL_IP);
|
||||||
packet->hardware_len = 6;
|
packet->hardware_len = 6;
|
||||||
@@ -632,6 +620,9 @@ void arp_resolve_mac(arp_state_t *state, ip_address_t ip, void *userp, void (*ca
|
|||||||
ARP_DEBUG_LOG("Sending out ARP request to resolve MAC");
|
ARP_DEBUG_LOG("Sending out ARP request to resolve MAC");
|
||||||
|
|
||||||
state->send(state->send_data, MAC_BROADCAST);
|
state->send(state->send_data, MAC_BROADCAST);
|
||||||
|
} else {
|
||||||
|
ARP_DEBUG_LOG("Couldn't send ARP request because no output buffer was provided");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,19 @@ struct arp_pending_request_t {
|
|||||||
void (*callback)(void*, arp_resolution_status_t status, mac_address_t);
|
void (*callback)(void*, arp_resolution_status_t status, mac_address_t);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ARP_HARDWARE_ETHERNET = 1,
|
||||||
|
} arp_hardware_type;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ARP_PROTOCOL_IP = 0x800,
|
||||||
|
} arp_protocol_type;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ARP_OPERATION_REQUEST = 1,
|
||||||
|
ARP_OPERATION_REPLY = 2,
|
||||||
|
} arp_operation_t;
|
||||||
|
|
||||||
typedef struct __attribute__((__packed__)) {
|
typedef struct __attribute__((__packed__)) {
|
||||||
uint16_t hardware_type;
|
uint16_t hardware_type;
|
||||||
uint16_t protocol_type;
|
uint16_t protocol_type;
|
||||||
@@ -93,9 +106,23 @@ typedef enum {
|
|||||||
ARP_PROCESS_RESULT_OK,
|
ARP_PROCESS_RESULT_OK,
|
||||||
} arp_process_result_t;
|
} arp_process_result_t;
|
||||||
|
|
||||||
void arp_init(arp_state_t *state, ip_address_t ip, mac_address_t mac, void *send_data, void (*send)(void*, mac_address_t));
|
void arp_init(arp_state_t *state,
|
||||||
|
ip_address_t ip,
|
||||||
|
mac_address_t mac,
|
||||||
|
void *send_data,
|
||||||
|
void (*send)(void*, mac_address_t));
|
||||||
|
|
||||||
void arp_free(arp_state_t *state);
|
void arp_free(arp_state_t *state);
|
||||||
arp_process_result_t arp_process_packet(arp_state_t *state, const void *packet, size_t len);
|
|
||||||
void arp_resolve_mac(arp_state_t *state, ip_address_t ip, void *userp, void (*callback)(void*, arp_resolution_status_t, mac_address_t));
|
arp_process_result_t
|
||||||
|
arp_process_packet(arp_state_t *state,
|
||||||
|
const void *packet,
|
||||||
|
size_t len);
|
||||||
|
|
||||||
|
void arp_resolve_mac(arp_state_t *state,
|
||||||
|
ip_address_t ip,
|
||||||
|
void *userp,
|
||||||
|
void (*callback)(void*, arp_resolution_status_t, mac_address_t));
|
||||||
|
|
||||||
void arp_seconds_passed(arp_state_t *state, size_t seconds);
|
void arp_seconds_passed(arp_state_t *state, size_t seconds);
|
||||||
void arp_change_output_buffer(arp_state_t *state, void *ptr, size_t max);
|
void arp_change_output_buffer(arp_state_t *state, void *ptr, size_t max);
|
||||||
+1
-119
@@ -1,5 +1,4 @@
|
|||||||
#include <time.h> // time()
|
#include <time.h> // time()
|
||||||
#include <ctype.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <string.h> // strerror()
|
#include <string.h> // strerror()
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@@ -9,6 +8,7 @@
|
|||||||
# include "ip.h"
|
# include "ip.h"
|
||||||
# include "arp.h"
|
# include "arp.h"
|
||||||
# include "tcp.h"
|
# include "tcp.h"
|
||||||
|
# include "utils.h"
|
||||||
# include "endian.h"
|
# include "endian.h"
|
||||||
# include "microtcp.h"
|
# include "microtcp.h"
|
||||||
# ifdef MICROTCP_BACKGROUND_THREAD
|
# ifdef MICROTCP_BACKGROUND_THREAD
|
||||||
@@ -435,124 +435,6 @@ static int loop(void *data)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static bool is_hex_digit(char c)
|
|
||||||
{
|
|
||||||
return (c >= '0' && c <= '9')
|
|
||||||
|| (c >= 'a' && c <= 'f')
|
|
||||||
|| (c >= 'A' && c <= 'F');
|
|
||||||
}
|
|
||||||
|
|
||||||
static int int_from_hex_digit(char c)
|
|
||||||
{
|
|
||||||
assert(is_hex_digit(c));
|
|
||||||
if (c >= 'A' || c <= 'F')
|
|
||||||
return c - 'A' + 10;
|
|
||||||
if (c >= 'a' || c <= 'f')
|
|
||||||
return c - 'a' + 10;
|
|
||||||
return c - '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool parse_mac(const char *src, size_t len,
|
|
||||||
mac_address_t *mac)
|
|
||||||
{
|
|
||||||
if (src == NULL || len != 17
|
|
||||||
|| !is_hex_digit(src[0])
|
|
||||||
|| !is_hex_digit(src[1])
|
|
||||||
|| src[2] != ':'
|
|
||||||
|| !is_hex_digit(src[3])
|
|
||||||
|| !is_hex_digit(src[4])
|
|
||||||
|| src[5] != ':'
|
|
||||||
|| !is_hex_digit(src[6])
|
|
||||||
|| !is_hex_digit(src[7])
|
|
||||||
|| src[8] != ':'
|
|
||||||
|| !is_hex_digit(src[9])
|
|
||||||
|| !is_hex_digit(src[10])
|
|
||||||
|| src[11] != ':'
|
|
||||||
|| !is_hex_digit(src[12])
|
|
||||||
|| !is_hex_digit(src[13])
|
|
||||||
|| src[14] != ':'
|
|
||||||
|| !is_hex_digit(src[15])
|
|
||||||
|| !is_hex_digit(src[16]))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
static const char max_char_map[] = "0123456789ABCDEF";
|
|
||||||
|
|
||||||
if (mac) {
|
|
||||||
mac->data[0] = max_char_map[int_from_hex_digit(src[ 0])] << 4
|
|
||||||
| max_char_map[int_from_hex_digit(src[ 1])];
|
|
||||||
mac->data[1] = max_char_map[int_from_hex_digit(src[ 3])] << 4
|
|
||||||
| max_char_map[int_from_hex_digit(src[ 4])];
|
|
||||||
mac->data[2] = max_char_map[int_from_hex_digit(src[ 6])] << 4
|
|
||||||
| max_char_map[int_from_hex_digit(src[ 7])];
|
|
||||||
mac->data[3] = max_char_map[int_from_hex_digit(src[ 9])] << 4
|
|
||||||
| max_char_map[int_from_hex_digit(src[10])];
|
|
||||||
mac->data[4] = max_char_map[int_from_hex_digit(src[12])] << 4
|
|
||||||
| max_char_map[int_from_hex_digit(src[13])];
|
|
||||||
mac->data[5] = max_char_map[int_from_hex_digit(src[15])] << 4
|
|
||||||
| max_char_map[int_from_hex_digit(src[16])];
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static mac_address_t generate_random_mac()
|
|
||||||
{
|
|
||||||
mac_address_t mac = {
|
|
||||||
.data = {
|
|
||||||
rand() & 0xff,
|
|
||||||
rand() & 0xff,
|
|
||||||
rand() & 0xff,
|
|
||||||
rand() & 0xff,
|
|
||||||
rand() & 0xff,
|
|
||||||
rand() & 0xff,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return mac;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool parse_ip(const char *ip, ip_address_t *parsed_ip)
|
|
||||||
{
|
|
||||||
size_t len = strlen(ip);
|
|
||||||
size_t i = 0;
|
|
||||||
|
|
||||||
uint32_t value = 0;
|
|
||||||
|
|
||||||
for (size_t k = 0; k < 4; k++) {
|
|
||||||
if (i == len || !isdigit(ip[i]))
|
|
||||||
return false;
|
|
||||||
int n = 0; // Used to represent a byte, but it's larger
|
|
||||||
// to detect overflows.
|
|
||||||
do {
|
|
||||||
// Convert character to number
|
|
||||||
int digit = ip[i] - '0';
|
|
||||||
if (n > (UINT8_MAX - digit)/10)
|
|
||||||
// Adding this digit would make the
|
|
||||||
// byte overflow, so it can't be part
|
|
||||||
// of the octet.
|
|
||||||
break;
|
|
||||||
n = n * 10 + digit;
|
|
||||||
i++;
|
|
||||||
} while (i < len && isdigit(ip[i]));
|
|
||||||
|
|
||||||
assert(n >= 0 && n <= UINT8_MAX);
|
|
||||||
value = (value << 8) | (uint8_t) n;
|
|
||||||
|
|
||||||
// If this isn't the last octet and there is no
|
|
||||||
// dot following it, the address is invalid.
|
|
||||||
if (k < 3) {
|
|
||||||
if (i == len || ip[i] != '.')
|
|
||||||
return false;
|
|
||||||
i++; // Consume the dot.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (i < len)
|
|
||||||
// source string contains something
|
|
||||||
// other than the address in it.
|
|
||||||
return false;
|
|
||||||
|
|
||||||
*parsed_ip = cpu_to_net_u32(value);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
microtcp_t *microtcp_create_using_callbacks(const char *ip, const char *mac,
|
microtcp_t *microtcp_create_using_callbacks(const char *ip, const char *mac,
|
||||||
microtcp_callbacks_t callbacks)
|
microtcp_callbacks_t callbacks)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -409,6 +409,8 @@ static void timeout_callback_time_wait(void *data)
|
|||||||
tcp_connection_t *connection = data;
|
tcp_connection_t *connection = data;
|
||||||
assert(connection->state == TCP_STATE_TIME_WAIT);
|
assert(connection->state == TCP_STATE_TIME_WAIT);
|
||||||
|
|
||||||
|
TCP_DEBUG_LOG("TIME-WAIT -> CLOSED");
|
||||||
|
|
||||||
// We can finally free up this connection structure!
|
// We can finally free up this connection structure!
|
||||||
really_close_connection(connection);
|
really_close_connection(connection);
|
||||||
}
|
}
|
||||||
|
|||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
#include <ctype.h>
|
||||||
|
#include <stdlib.h> // rand
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifndef MICROTCP_AMALGAMATION
|
||||||
|
#include "utils.h"
|
||||||
|
#include "endian.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static bool is_hex_digit(char c)
|
||||||
|
{
|
||||||
|
return (c >= '0' && c <= '9')
|
||||||
|
|| (c >= 'a' && c <= 'f')
|
||||||
|
|| (c >= 'A' && c <= 'F');
|
||||||
|
}
|
||||||
|
|
||||||
|
static int int_from_hex_digit(char c)
|
||||||
|
{
|
||||||
|
assert(is_hex_digit(c));
|
||||||
|
if (c >= 'A' || c <= 'F')
|
||||||
|
return c - 'A' + 10;
|
||||||
|
if (c >= 'a' || c <= 'f')
|
||||||
|
return c - 'a' + 10;
|
||||||
|
return c - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse_mac(const char *src, size_t len,
|
||||||
|
mac_address_t *mac)
|
||||||
|
{
|
||||||
|
if (src == NULL || len != 17
|
||||||
|
|| !is_hex_digit(src[0])
|
||||||
|
|| !is_hex_digit(src[1])
|
||||||
|
|| src[2] != ':'
|
||||||
|
|| !is_hex_digit(src[3])
|
||||||
|
|| !is_hex_digit(src[4])
|
||||||
|
|| src[5] != ':'
|
||||||
|
|| !is_hex_digit(src[6])
|
||||||
|
|| !is_hex_digit(src[7])
|
||||||
|
|| src[8] != ':'
|
||||||
|
|| !is_hex_digit(src[9])
|
||||||
|
|| !is_hex_digit(src[10])
|
||||||
|
|| src[11] != ':'
|
||||||
|
|| !is_hex_digit(src[12])
|
||||||
|
|| !is_hex_digit(src[13])
|
||||||
|
|| src[14] != ':'
|
||||||
|
|| !is_hex_digit(src[15])
|
||||||
|
|| !is_hex_digit(src[16]))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
static const char max_char_map[] = "0123456789ABCDEF";
|
||||||
|
|
||||||
|
if (mac) {
|
||||||
|
mac->data[0] = max_char_map[int_from_hex_digit(src[ 0])] << 4
|
||||||
|
| max_char_map[int_from_hex_digit(src[ 1])];
|
||||||
|
mac->data[1] = max_char_map[int_from_hex_digit(src[ 3])] << 4
|
||||||
|
| max_char_map[int_from_hex_digit(src[ 4])];
|
||||||
|
mac->data[2] = max_char_map[int_from_hex_digit(src[ 6])] << 4
|
||||||
|
| max_char_map[int_from_hex_digit(src[ 7])];
|
||||||
|
mac->data[3] = max_char_map[int_from_hex_digit(src[ 9])] << 4
|
||||||
|
| max_char_map[int_from_hex_digit(src[10])];
|
||||||
|
mac->data[4] = max_char_map[int_from_hex_digit(src[12])] << 4
|
||||||
|
| max_char_map[int_from_hex_digit(src[13])];
|
||||||
|
mac->data[5] = max_char_map[int_from_hex_digit(src[15])] << 4
|
||||||
|
| max_char_map[int_from_hex_digit(src[16])];
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
mac_address_t generate_random_mac()
|
||||||
|
{
|
||||||
|
mac_address_t mac = {
|
||||||
|
.data = {
|
||||||
|
rand() & 0xff,
|
||||||
|
rand() & 0xff,
|
||||||
|
rand() & 0xff,
|
||||||
|
rand() & 0xff,
|
||||||
|
rand() & 0xff,
|
||||||
|
rand() & 0xff,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return mac;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool parse_ip(const char *ip, ip_address_t *parsed_ip)
|
||||||
|
{
|
||||||
|
size_t len = strlen(ip);
|
||||||
|
size_t i = 0;
|
||||||
|
|
||||||
|
uint32_t value = 0;
|
||||||
|
|
||||||
|
for (size_t k = 0; k < 4; k++) {
|
||||||
|
if (i == len || !isdigit(ip[i]))
|
||||||
|
return false;
|
||||||
|
int n = 0; // Used to represent a byte, but it's larger
|
||||||
|
// to detect overflows.
|
||||||
|
do {
|
||||||
|
// Convert character to number
|
||||||
|
int digit = ip[i] - '0';
|
||||||
|
if (n > (UINT8_MAX - digit)/10)
|
||||||
|
// Adding this digit would make the
|
||||||
|
// byte overflow, so it can't be part
|
||||||
|
// of the octet.
|
||||||
|
break;
|
||||||
|
n = n * 10 + digit;
|
||||||
|
i++;
|
||||||
|
} while (i < len && isdigit(ip[i]));
|
||||||
|
|
||||||
|
assert(n >= 0 && n <= UINT8_MAX);
|
||||||
|
value = (value << 8) | (uint8_t) n;
|
||||||
|
|
||||||
|
// If this isn't the last octet and there is no
|
||||||
|
// dot following it, the address is invalid.
|
||||||
|
if (k < 3) {
|
||||||
|
if (i == len || ip[i] != '.')
|
||||||
|
return false;
|
||||||
|
i++; // Consume the dot.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i < len)
|
||||||
|
// source string contains something
|
||||||
|
// other than the address in it.
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*parsed_ip = cpu_to_net_u32(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifndef MICROTCP_AMALGAMATION
|
||||||
|
#include "defs.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool parse_mac(const char *src, size_t len, mac_address_t *mac);
|
||||||
|
bool parse_ip(const char *ip, ip_address_t *parsed_ip);
|
||||||
|
mac_address_t generate_random_mac();
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
@type [ARP]
|
||||||
|
|
||||||
|
@desc
|
||||||
|
Peer requests host's MAC given its IP and host replies.
|
||||||
|
|
||||||
|
@IP [10.0.0.4]
|
||||||
|
@MAC [56:34:f5:4d:4f:44]
|
||||||
|
|
||||||
|
@timeline
|
||||||
|
<= [ ETHERNET | IP | 6 | 4 | REQUEST | 00:34:56:34:f5:4f | 10.0.0.5 | 00:00:00:00:00:00 | 10.0.0.4 ]
|
||||||
|
=> [ ETHERNET | IP | 6 | 4 | REPLY | 56:34:f5:4d:4f:44 | 10.0.0.4 | 00:34:56:34:f5:4f | 10.0.0.5 ]
|
||||||
@@ -0,0 +1,463 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "../src/utils.c"
|
||||||
|
#include "../src/endian.c"
|
||||||
|
#include "../src/arp.c"
|
||||||
|
|
||||||
|
/*
|
||||||
|
BLACK BOX TEST CASES
|
||||||
|
|
||||||
|
A) Peer requests host's MAC given its IP and host
|
||||||
|
replies.
|
||||||
|
|
||||||
|
B) Peer requests host's non MAC level 2 address
|
||||||
|
given its IP and host doesn't reply because it
|
||||||
|
only supports MAC.
|
||||||
|
|
||||||
|
C) Peer requests host's MAC address given its non
|
||||||
|
IP level 3 address and host doesn't reply
|
||||||
|
because it only supports IP.
|
||||||
|
|
||||||
|
D) Peer requests host's MAC given its IP, but the
|
||||||
|
MAC address length field isn't 6, therefore
|
||||||
|
host doesn't reply.
|
||||||
|
|
||||||
|
E) Peer requests host's MAC given its IP, but the
|
||||||
|
IP address length field isn't 4, therefore host
|
||||||
|
doesn't reply.
|
||||||
|
|
||||||
|
F) Peer sends a request/reply that doesn't refer
|
||||||
|
to host and is from a sender with IP never seen
|
||||||
|
by the host (no entry in the translation table).
|
||||||
|
It's expected that no entry is added to the
|
||||||
|
translation table.
|
||||||
|
|
||||||
|
G) Peer sends a request/reply that doesn't refer
|
||||||
|
to host but is from a sender with IP already
|
||||||
|
in the ARP translation table, therefore is
|
||||||
|
expected that host updates the entry.
|
||||||
|
|
||||||
|
H) Program queries the ARP module for a MAC that
|
||||||
|
isn't cached, therefore an ARP request is
|
||||||
|
expected to be generated and, when replied to,
|
||||||
|
the ARP module is expected to resolve the
|
||||||
|
program's query.
|
||||||
|
|
||||||
|
I) Program queries the ARP module for a MAC that's
|
||||||
|
cached, therefore the ARP module is expected to
|
||||||
|
resolve it without sending packets.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
TEST_PASSED,
|
||||||
|
TEST_FAILED,
|
||||||
|
TEST_ABORTED,
|
||||||
|
} test_result_t;
|
||||||
|
|
||||||
|
#define OUTPUT_QUEUE_SIZE 8
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
arp_state_t *state;
|
||||||
|
arp_packet_t queue[OUTPUT_QUEUE_SIZE];
|
||||||
|
int count;
|
||||||
|
int oflow;
|
||||||
|
} output_queue_t;
|
||||||
|
|
||||||
|
static void send_arp_packet_callback(void *data, mac_address_t mac)
|
||||||
|
{
|
||||||
|
output_queue_t *oq = (output_queue_t*) data;
|
||||||
|
if (oq->count == OUTPUT_QUEUE_SIZE)
|
||||||
|
oq->oflow++;
|
||||||
|
else {
|
||||||
|
oq->count++;
|
||||||
|
if (oq->count == OUTPUT_QUEUE_SIZE)
|
||||||
|
arp_change_output_buffer(oq->state, NULL, 0);
|
||||||
|
else
|
||||||
|
arp_change_output_buffer(oq->state, oq->queue+oq->count, sizeof(arp_packet_t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_result_t test_arp_bb_A(char *msg, size_t msgmax)
|
||||||
|
{
|
||||||
|
// Addresses of the packets that will be sent towards
|
||||||
|
// the ARP module
|
||||||
|
const char net_ip_str[] = "10.0.0.5";
|
||||||
|
const char net_mac_str[] = "00:34:56:34:f5:4f";
|
||||||
|
|
||||||
|
// Addresses of the ARP module
|
||||||
|
const char ip_str[] = "10.0.0.4";
|
||||||
|
const char mac_str[] = "56:34:f5:4d:4f:44";
|
||||||
|
|
||||||
|
// Parse the addresses to binary form
|
||||||
|
ip_address_t ip, net_ip;
|
||||||
|
mac_address_t mac, net_mac;
|
||||||
|
if (!parse_mac(mac_str, sizeof(mac_str)-1, &mac) ||
|
||||||
|
!parse_ip(ip_str, &ip) ||
|
||||||
|
!parse_mac(net_mac_str, sizeof(net_mac_str)-1, &net_mac) ||
|
||||||
|
!parse_ip(net_ip_str, &net_ip)) {
|
||||||
|
snprintf(msg, msgmax, "Couldn't parse IP and MAC strings");
|
||||||
|
return TEST_ABORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the module with the output queue
|
||||||
|
arp_state_t state;
|
||||||
|
output_queue_t oq = {.state=&state, .count=0, .oflow=0}; // Buffer where replies and requests will be stored
|
||||||
|
// by the ARP module
|
||||||
|
arp_init(&state, ip, mac, &oq, send_arp_packet_callback);
|
||||||
|
arp_change_output_buffer(&state, oq.queue, sizeof(arp_packet_t));
|
||||||
|
|
||||||
|
// Build the request
|
||||||
|
arp_packet_t request = {
|
||||||
|
.hardware_type = cpu_to_net_u16(ARP_HARDWARE_ETHERNET),
|
||||||
|
.protocol_type = cpu_to_net_u16(ARP_PROTOCOL_IP),
|
||||||
|
.hardware_len = 6,
|
||||||
|
.protocol_len = 4,
|
||||||
|
.operation_type = cpu_to_net_u16(ARP_OPERATION_REQUEST),
|
||||||
|
.sender_hardware_address = net_mac,
|
||||||
|
.sender_protocol_address = net_ip,
|
||||||
|
.target_hardware_address = MAC_ZERO,
|
||||||
|
.target_protocol_address = ip,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send the request
|
||||||
|
arp_process_result_t res;
|
||||||
|
res = arp_process_packet(&state, &request, sizeof(arp_packet_t));
|
||||||
|
switch (res) {
|
||||||
|
case ARP_PROCESS_RESULT_HWARENOTSUPP:
|
||||||
|
case ARP_PROCESS_RESULT_PROTONOTSUPP:
|
||||||
|
case ARP_PROCESS_RESULT_INVALID:
|
||||||
|
snprintf(msg, msgmax, "ARP module couldn't process request");
|
||||||
|
return TEST_FAILED;
|
||||||
|
|
||||||
|
case ARP_PROCESS_RESULT_OK:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that the module replies one time and one time only
|
||||||
|
if (oq.count == 0) {
|
||||||
|
// The ARP module sent no reply given
|
||||||
|
// our request.
|
||||||
|
snprintf(msg, msgmax, "ARP module didn't reply");
|
||||||
|
return TEST_FAILED;
|
||||||
|
}
|
||||||
|
if (oq.count > 1) {
|
||||||
|
// Sent too many replies
|
||||||
|
snprintf(msg, msgmax, "ARP module replied too many times");
|
||||||
|
return TEST_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the reply has the right content
|
||||||
|
arp_packet_t *reply = oq.queue;
|
||||||
|
if (net_to_cpu_u16(reply->hardware_type) != ARP_HARDWARE_ETHERNET ||
|
||||||
|
net_to_cpu_u16(reply->protocol_type) != ARP_PROTOCOL_IP ||
|
||||||
|
net_to_cpu_u16(reply->operation_type) != ARP_OPERATION_REPLY ||
|
||||||
|
memcmp(&reply->sender_hardware_address, &mac, sizeof(mac_address_t)) ||
|
||||||
|
memcmp(&reply->sender_protocol_address, &ip, sizeof(ip_address_t)) ||
|
||||||
|
memcmp(&reply->target_hardware_address, &net_mac, sizeof(mac_address_t)) ||
|
||||||
|
memcmp(&reply->target_protocol_address, &net_ip, sizeof(ip_address_t))) {
|
||||||
|
// Unexpected reply
|
||||||
|
snprintf(msg, msgmax, "ARP module sent an unexpected reply");
|
||||||
|
return TEST_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TEST_PASSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
test_result_t test_arp_bb_B(char *msg, size_t msgmax)
|
||||||
|
{
|
||||||
|
// Addresses of the packets that will be sent towards
|
||||||
|
// the ARP module
|
||||||
|
const char net_ip_str[] = "10.0.0.5";
|
||||||
|
const char net_mac_str[] = "00:34:56:34:f5:4f";
|
||||||
|
|
||||||
|
// Addresses of the ARP module
|
||||||
|
const char ip_str[] = "10.0.0.4";
|
||||||
|
const char mac_str[] = "56:34:f5:4d:4f:44";
|
||||||
|
|
||||||
|
// Parse the addresses to binary form
|
||||||
|
ip_address_t ip, net_ip;
|
||||||
|
mac_address_t mac, net_mac;
|
||||||
|
if (!parse_mac(mac_str, sizeof(mac_str)-1, &mac) ||
|
||||||
|
!parse_ip(ip_str, &ip) ||
|
||||||
|
!parse_mac(net_mac_str, sizeof(net_mac_str)-1, &net_mac) ||
|
||||||
|
!parse_ip(net_ip_str, &net_ip)) {
|
||||||
|
snprintf(msg, msgmax, "Couldn't parse IP and MAC strings");
|
||||||
|
return TEST_ABORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the module with the output queue
|
||||||
|
arp_state_t state;
|
||||||
|
output_queue_t oq = {.state=&state, .count=0, .oflow=0}; // Buffer where replies and requests will be stored
|
||||||
|
// by the ARP module
|
||||||
|
arp_init(&state, ip, mac, &oq, send_arp_packet_callback);
|
||||||
|
arp_change_output_buffer(&state, oq.queue, sizeof(arp_packet_t));
|
||||||
|
|
||||||
|
// Build the request
|
||||||
|
arp_packet_t request = {
|
||||||
|
.hardware_type = cpu_to_net_u16(ARP_HARDWARE_ETHERNET)+1, // Some value other than ETHERNET
|
||||||
|
.protocol_type = cpu_to_net_u16(ARP_PROTOCOL_IP),
|
||||||
|
.hardware_len = 6,
|
||||||
|
.protocol_len = 4,
|
||||||
|
.operation_type = cpu_to_net_u16(ARP_OPERATION_REQUEST),
|
||||||
|
.sender_hardware_address = net_mac,
|
||||||
|
.sender_protocol_address = net_ip,
|
||||||
|
.target_hardware_address = MAC_ZERO,
|
||||||
|
.target_protocol_address = ip,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send the request
|
||||||
|
arp_process_result_t res;
|
||||||
|
res = arp_process_packet(&state, &request, sizeof(arp_packet_t));
|
||||||
|
switch (res) {
|
||||||
|
case ARP_PROCESS_RESULT_HWARENOTSUPP:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARP_PROCESS_RESULT_PROTONOTSUPP:
|
||||||
|
case ARP_PROCESS_RESULT_INVALID:
|
||||||
|
snprintf(msg, msgmax, "ARP module couldn't process request");
|
||||||
|
return TEST_FAILED;
|
||||||
|
|
||||||
|
case ARP_PROCESS_RESULT_OK:
|
||||||
|
snprintf(msg, msgmax, "ARP module processed a request for an hardware type it didn't support");
|
||||||
|
return TEST_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oq.count > 0) {
|
||||||
|
// Sent replies
|
||||||
|
snprintf(msg, msgmax, "ARP module replied even though it failed to process the request");
|
||||||
|
return TEST_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TEST_PASSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
test_result_t test_arp_bb_C(char *msg, size_t msgmax)
|
||||||
|
{
|
||||||
|
// Addresses of the packets that will be sent towards
|
||||||
|
// the ARP module
|
||||||
|
const char net_ip_str[] = "10.0.0.5";
|
||||||
|
const char net_mac_str[] = "00:34:56:34:f5:4f";
|
||||||
|
|
||||||
|
// Addresses of the ARP module
|
||||||
|
const char ip_str[] = "10.0.0.4";
|
||||||
|
const char mac_str[] = "56:34:f5:4d:4f:44";
|
||||||
|
|
||||||
|
// Parse the addresses to binary form
|
||||||
|
ip_address_t ip, net_ip;
|
||||||
|
mac_address_t mac, net_mac;
|
||||||
|
if (!parse_mac(mac_str, sizeof(mac_str)-1, &mac) ||
|
||||||
|
!parse_ip(ip_str, &ip) ||
|
||||||
|
!parse_mac(net_mac_str, sizeof(net_mac_str)-1, &net_mac) ||
|
||||||
|
!parse_ip(net_ip_str, &net_ip)) {
|
||||||
|
snprintf(msg, msgmax, "Couldn't parse IP and MAC strings");
|
||||||
|
return TEST_ABORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the module with the output queue
|
||||||
|
arp_state_t state;
|
||||||
|
output_queue_t oq = {.state=&state, .count=0, .oflow=0}; // Buffer where replies and requests will be stored
|
||||||
|
// by the ARP module
|
||||||
|
arp_init(&state, ip, mac, &oq, send_arp_packet_callback);
|
||||||
|
arp_change_output_buffer(&state, oq.queue, sizeof(arp_packet_t));
|
||||||
|
|
||||||
|
// Build the request
|
||||||
|
arp_packet_t request = {
|
||||||
|
.hardware_type = cpu_to_net_u16(ARP_HARDWARE_ETHERNET),
|
||||||
|
.protocol_type = cpu_to_net_u16(ARP_PROTOCOL_IP)+1, // Some value other than IP
|
||||||
|
.hardware_len = 6,
|
||||||
|
.protocol_len = 4,
|
||||||
|
.operation_type = cpu_to_net_u16(ARP_OPERATION_REQUEST),
|
||||||
|
.sender_hardware_address = net_mac,
|
||||||
|
.sender_protocol_address = net_ip,
|
||||||
|
.target_hardware_address = MAC_ZERO,
|
||||||
|
.target_protocol_address = ip,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send the request
|
||||||
|
arp_process_result_t res;
|
||||||
|
res = arp_process_packet(&state, &request, sizeof(arp_packet_t));
|
||||||
|
switch (res) {
|
||||||
|
|
||||||
|
case ARP_PROCESS_RESULT_PROTONOTSUPP:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARP_PROCESS_RESULT_HWARENOTSUPP:
|
||||||
|
case ARP_PROCESS_RESULT_INVALID:
|
||||||
|
snprintf(msg, msgmax, "ARP module couldn't process request");
|
||||||
|
return TEST_FAILED;
|
||||||
|
|
||||||
|
case ARP_PROCESS_RESULT_OK:
|
||||||
|
snprintf(msg, msgmax, "ARP module processed a request for a protocol type it didn't support");
|
||||||
|
return TEST_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oq.count > 0) {
|
||||||
|
// Sent replies
|
||||||
|
snprintf(msg, msgmax, "ARP module replied even though it failed to process the request");
|
||||||
|
return TEST_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TEST_PASSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
test_result_t test_arp_bb_D(char *msg, size_t msgmax)
|
||||||
|
{
|
||||||
|
// Addresses of the packets that will be sent towards
|
||||||
|
// the ARP module
|
||||||
|
const char net_ip_str[] = "10.0.0.5";
|
||||||
|
const char net_mac_str[] = "00:34:56:34:f5:4f";
|
||||||
|
|
||||||
|
// Addresses of the ARP module
|
||||||
|
const char ip_str[] = "10.0.0.4";
|
||||||
|
const char mac_str[] = "56:34:f5:4d:4f:44";
|
||||||
|
|
||||||
|
// Parse the addresses to binary form
|
||||||
|
ip_address_t ip, net_ip;
|
||||||
|
mac_address_t mac, net_mac;
|
||||||
|
if (!parse_mac(mac_str, sizeof(mac_str)-1, &mac) ||
|
||||||
|
!parse_ip(ip_str, &ip) ||
|
||||||
|
!parse_mac(net_mac_str, sizeof(net_mac_str)-1, &net_mac) ||
|
||||||
|
!parse_ip(net_ip_str, &net_ip)) {
|
||||||
|
snprintf(msg, msgmax, "Couldn't parse IP and MAC strings");
|
||||||
|
return TEST_ABORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the module with the output queue
|
||||||
|
arp_state_t state;
|
||||||
|
output_queue_t oq = {.state=&state, .count=0, .oflow=0}; // Buffer where replies and requests will be stored
|
||||||
|
// by the ARP module
|
||||||
|
arp_init(&state, ip, mac, &oq, send_arp_packet_callback);
|
||||||
|
arp_change_output_buffer(&state, oq.queue, sizeof(arp_packet_t));
|
||||||
|
|
||||||
|
// Build the request
|
||||||
|
arp_packet_t request = {
|
||||||
|
.hardware_type = cpu_to_net_u16(ARP_HARDWARE_ETHERNET),
|
||||||
|
.protocol_type = cpu_to_net_u16(ARP_PROTOCOL_IP),
|
||||||
|
.hardware_len = 6+1, // Something other than the correct length
|
||||||
|
.protocol_len = 4,
|
||||||
|
.operation_type = cpu_to_net_u16(ARP_OPERATION_REQUEST),
|
||||||
|
.sender_hardware_address = net_mac,
|
||||||
|
.sender_protocol_address = net_ip,
|
||||||
|
.target_hardware_address = MAC_ZERO,
|
||||||
|
.target_protocol_address = ip,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send the request
|
||||||
|
arp_process_result_t res;
|
||||||
|
res = arp_process_packet(&state, &request, sizeof(arp_packet_t));
|
||||||
|
switch (res) {
|
||||||
|
|
||||||
|
case ARP_PROCESS_RESULT_INVALID:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARP_PROCESS_RESULT_HWARENOTSUPP:
|
||||||
|
case ARP_PROCESS_RESULT_PROTONOTSUPP:
|
||||||
|
snprintf(msg, msgmax, "ARP module couldn't process request");
|
||||||
|
return TEST_FAILED;
|
||||||
|
|
||||||
|
case ARP_PROCESS_RESULT_OK:
|
||||||
|
snprintf(msg, msgmax, "ARP module processed a request for an invalid hardware address length");
|
||||||
|
return TEST_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oq.count > 0) {
|
||||||
|
// Sent replies
|
||||||
|
snprintf(msg, msgmax, "ARP module replied even though it failed to process the request");
|
||||||
|
return TEST_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TEST_PASSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
test_result_t test_arp_bb_E(char *msg, size_t msgmax)
|
||||||
|
{
|
||||||
|
// Addresses of the packets that will be sent towards
|
||||||
|
// the ARP module
|
||||||
|
const char net_ip_str[] = "10.0.0.5";
|
||||||
|
const char net_mac_str[] = "00:34:56:34:f5:4f";
|
||||||
|
|
||||||
|
// Addresses of the ARP module
|
||||||
|
const char ip_str[] = "10.0.0.4";
|
||||||
|
const char mac_str[] = "56:34:f5:4d:4f:44";
|
||||||
|
|
||||||
|
// Parse the addresses to binary form
|
||||||
|
ip_address_t ip, net_ip;
|
||||||
|
mac_address_t mac, net_mac;
|
||||||
|
if (!parse_mac(mac_str, sizeof(mac_str)-1, &mac) ||
|
||||||
|
!parse_ip(ip_str, &ip) ||
|
||||||
|
!parse_mac(net_mac_str, sizeof(net_mac_str)-1, &net_mac) ||
|
||||||
|
!parse_ip(net_ip_str, &net_ip)) {
|
||||||
|
snprintf(msg, msgmax, "Couldn't parse IP and MAC strings");
|
||||||
|
return TEST_ABORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the module with the output queue
|
||||||
|
arp_state_t state;
|
||||||
|
output_queue_t oq = {.state=&state, .count=0, .oflow=0}; // Buffer where replies and requests will be stored
|
||||||
|
// by the ARP module
|
||||||
|
arp_init(&state, ip, mac, &oq, send_arp_packet_callback);
|
||||||
|
arp_change_output_buffer(&state, oq.queue, sizeof(arp_packet_t));
|
||||||
|
|
||||||
|
// Build the request
|
||||||
|
arp_packet_t request = {
|
||||||
|
.hardware_type = cpu_to_net_u16(ARP_HARDWARE_ETHERNET),
|
||||||
|
.protocol_type = cpu_to_net_u16(ARP_PROTOCOL_IP),
|
||||||
|
.hardware_len = 6,
|
||||||
|
.protocol_len = 4+1, // Something other than the correct length
|
||||||
|
.operation_type = cpu_to_net_u16(ARP_OPERATION_REQUEST),
|
||||||
|
.sender_hardware_address = net_mac,
|
||||||
|
.sender_protocol_address = net_ip,
|
||||||
|
.target_hardware_address = MAC_ZERO,
|
||||||
|
.target_protocol_address = ip,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send the request
|
||||||
|
arp_process_result_t res;
|
||||||
|
res = arp_process_packet(&state, &request, sizeof(arp_packet_t));
|
||||||
|
switch (res) {
|
||||||
|
|
||||||
|
case ARP_PROCESS_RESULT_INVALID:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARP_PROCESS_RESULT_HWARENOTSUPP:
|
||||||
|
case ARP_PROCESS_RESULT_PROTONOTSUPP:
|
||||||
|
snprintf(msg, msgmax, "ARP module couldn't process request");
|
||||||
|
return TEST_FAILED;
|
||||||
|
|
||||||
|
case ARP_PROCESS_RESULT_OK:
|
||||||
|
snprintf(msg, msgmax, "ARP module processed a request for an invalid protocol address length");
|
||||||
|
return TEST_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oq.count > 0) {
|
||||||
|
// Sent replies
|
||||||
|
snprintf(msg, msgmax, "ARP module replied even though it failed to process the request");
|
||||||
|
return TEST_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TEST_PASSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
void *tests[] = {
|
||||||
|
test_arp_bb_A,
|
||||||
|
test_arp_bb_B,
|
||||||
|
test_arp_bb_C,
|
||||||
|
test_arp_bb_D,
|
||||||
|
test_arp_bb_E,
|
||||||
|
};
|
||||||
|
|
||||||
|
char msg[256];
|
||||||
|
for (size_t i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) {
|
||||||
|
test_result_t (*test_fn)(char*, size_t) = tests[i];
|
||||||
|
switch (test_fn(msg, sizeof(msg))) {
|
||||||
|
case TEST_PASSED: fprintf(stdout, "PASSED\n"); break;
|
||||||
|
case TEST_FAILED: fprintf(stdout, "FAILED: %s\n", msg); break;
|
||||||
|
case TEST_ABORTED: fprintf(stdout, "ABORTED: %s\n", msg); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user