Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gnrc_ipv6_nib/SLAAC: rfc8981 temporary address (privacy extensions) #20369

Open
wants to merge 65 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
3d58132
includes
xnumad Feb 7, 2024
1fddb3b
config: Define feature flag
xnumad Feb 7, 2024
670176a
config: RFC params
xnumad Feb 7, 2024
55c3dab
Increase addr limit
xnumad Feb 7, 2024
6cbbcad
SLAAC only for /64 prefixes
xnumad Feb 7, 2024
bdf3988
config: RFC regen advance constant
xnumad Feb 7, 2024
b4fd28f
creation: Check IANA IID
xnumad Feb 7, 2024
2e05fac
IDGEN
xnumad Feb 7, 2024
f069a49
Check prefix existence
xnumad Feb 7, 2024
7e8a80a
Temporary address creation
xnumad Feb 7, 2024
81dcba8
coupling: Delete ta_pfx along with ta
xnumad Feb 7, 2024
88acfa1
Temporary address helper functions
xnumad Feb 7, 2024
1403842
Handle DAD failure for temporary address
xnumad Feb 7, 2024
6d146af
Temporary address helper function
xnumad Feb 7, 2024
dec0b26
coupling: Remove ta_pfxs if deleting SLAAC prefix
xnumad Feb 7, 2024
4d4f74c
regen: Define event type
xnumad Feb 7, 2024
5c7c1b3
regen: Delete event timer
xnumad Feb 7, 2024
84e3c14
Change return value
xnumad Feb 7, 2024
ae245f1
regen: Add timer on temp addr creation
xnumad Feb 7, 2024
d8b2a53
regen: Handle event
xnumad Feb 7, 2024
b4d588c
regen: reschedule on DAD failure
xnumad Feb 7, 2024
2800f76
retries: New flag
xnumad Feb 7, 2024
3a2a4cf
retries: Read flag
xnumad Feb 7, 2024
75e3dbe
retries: Set flag
xnumad Feb 7, 2024
81ee71e
creation: Add index param
xnumad Feb 7, 2024
c574857
regen: recover from reschedule failure
xnumad Feb 7, 2024
e3c2020
Implement rfc6724 Rule 7
xnumad Feb 7, 2024
ced8a8a
finalize: Wrap in IS_ACTIVE
xnumad Feb 7, 2024
72b8278
finalize: Doxygen and const params
xnumad Feb 7, 2024
fd96825
finalize: Link to HTML RFC
xnumad Feb 8, 2024
43af168
regen: Support deletion
xnumad Feb 8, 2024
2a92751
regen: Delete timer if retry limit reached
xnumad Feb 8, 2024
8303bf6
Rename
xnumad Feb 8, 2024
d07f103
Rename
xnumad Feb 8, 2024
eb6889d
Rename
xnumad Feb 8, 2024
fe1e208
Documentation for constant
xnumad Feb 8, 2024
14f85c2
Documentation
xnumad Feb 8, 2024
a4adfc0
finalize: whitespacecheck
xnumad Feb 8, 2024
c7902d8
finalize: doccheck
xnumad Feb 8, 2024
f8811d9
finalize: vera++ style check
xnumad Feb 8, 2024
addcd5d
Do not back up variable
xnumad Feb 8, 2024
d52ba25
Include
xnumad Feb 8, 2024
fc79a9a
Documentation
xnumad Feb 8, 2024
c009040
6lowpan: Register temporary address
xnumad Feb 9, 2024
2a0a53d
Documentation: backlink
xnumad Feb 9, 2024
d2039c4
Document
xnumad Feb 9, 2024
f4a35f6
fix compile on board
xnumad Feb 9, 2024
159dc1a
gnrc_netif_ipv6_addr_pfx_idx
xnumad Feb 9, 2024
e22592b
Supress SLAAC trigger also for 6lo
xnumad Feb 9, 2024
52a3e8b
Readability
xnumad Feb 9, 2024
4d34bab
ifconfig: Print TMP property
xnumad Feb 9, 2024
590521d
Documentation
xnumad Feb 10, 2024
e452a84
Adjust log levels
xnumad Feb 10, 2024
337feb1
fixup! gnrc_netif_ipv6_addr_pfx_idx
xnumad Feb 10, 2024
7e35d0a
fixup! gnrc_netif_ipv6_addr_pfx_idx
xnumad Feb 10, 2024
ac00481
fixup! creation: Add index param
xnumad Feb 10, 2024
c80961f
fixup! config: RFC params
xnumad Feb 11, 2024
964a81c
fixup! Handle DAD failure for temporary address
xnumad Feb 11, 2024
1bbf44d
fixup! Temporary address creation
xnumad Feb 11, 2024
c153aa7
docs: Wording
xnumad Feb 12, 2024
4481d1a
fix: Delete regen timer on deprecation
xnumad Feb 12, 2024
381f188
refactor: Remove prefix before its addresses
xnumad Mar 8, 2024
00dc1ae
refactor: Move regen event from SLAAC prefix to ta_pfx
xnumad May 19, 2024
0a4aff2
refactor: Extract duplicate code to method
xnumad May 19, 2024
b4439c7
Adjust for PR #20371
xnumad Sep 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions sys/include/net/gnrc/ipv6/nib.h
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,18 @@ extern "C" {
* @brief Interface down event
*/
#define GNRC_IPV6_NIB_IFACE_DOWN (0x4fd5U)

#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_SLAAC_TEMPORARY_ADDRESSES) || defined(DOXYGEN)
/**
* @brief Temporary address: regenerate
*
* This message type is for the event of a regeneration of a temporary address.
* The expected message context is a valid off-link entry representing the temporary address prefix.
*
* @see "REGEN_ADVANCE time units before" - https://www.rfc-editor.org/rfc/rfc8981.html#section-3.6
*/
#define GNRC_IPV6_NIB_REGEN_TEMP_ADDR (0x4fd6U)
#endif
/** @} */

/**
Expand Down
10 changes: 9 additions & 1 deletion sys/include/net/gnrc/ipv6/nib/conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
# endif
# ifndef CONFIG_GNRC_IPV6_NIB_NUMOF
/* only needs to store default router */
# define CONFIG_GNRC_IPV6_NIB_NUMOF (1)
# define CONFIG_GNRC_IPV6_NIB_NUMOF (1 + IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_SLAAC_TEMPORARY_ADDRESSES))

Check warning on line 68 in sys/include/net/gnrc/ipv6/nib/conf.h

View workflow job for this annotation

GitHub Actions / static-tests

line is longer than 100 characters
# endif
#endif
#endif
Expand Down Expand Up @@ -186,6 +186,14 @@
#define CONFIG_GNRC_IPV6_NIB_SLAAC 1
#endif

/**
* @brief Use temporary addresses (rfc8981)
* @see [RFC 8981](https://www.rfc-editor.org/rfc/rfc8981.html)
*/
#ifndef CONFIG_GNRC_IPV6_NIB_SLAAC_TEMPORARY_ADDRESSES
#define CONFIG_GNRC_IPV6_NIB_SLAAC_TEMPORARY_ADDRESSES 0
#endif

/**
* @brief handle Redirect Messages
*/
Expand Down
13 changes: 13 additions & 0 deletions sys/include/net/gnrc/ipv6/nib/pl.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,19 @@ int gnrc_ipv6_nib_pl_set(unsigned iface,
void gnrc_ipv6_nib_pl_del(unsigned iface,
const ipv6_addr_t *pfx, unsigned pfx_len);

#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_SLAAC_TEMPORARY_ADDRESSES) || defined(DOXYGEN)
/**
* @brief Check whether the prefix list has a prefix as specified
* @param[in] iface The interface @p pfx is expected to be on (0 for any).
* @param[in] pfx The prefix to check for.
* @param[in] pfx_len Length of @p pfx in bits.
* @return true if such prefix is present, false otherwise
*/
bool gnrc_ipv6_nib_pl_has_prefix(const unsigned int iface, const ipv6_addr_t *pfx,
const uint8_t pfx_len);

#endif

/**
* @brief Iterates over all prefix list entries in the NIB.
*
Expand Down
32 changes: 30 additions & 2 deletions sys/include/net/gnrc/netif/conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,33 @@ extern "C" {
#define GNRC_NETIF_IPV6_RTR_ADDR (0)
#endif

/**
* @brief Maximum assumed number of valid (preferred or deprecated) temporary addresses.
* (see _nib-slaac.h)
*
* This value is not enforced but only used to increase
* the amount of configurable addresses, to make space for temporary addresses.
*
* Assuming continued use of the same prefix,
* the number of simultaneous temporary addresses
* can be expressed by the following (not considering REGEN_ADVANCE):
* @ref TEMP_VALID_LIFETIME / min_pref_lft=(@ref TEMP_PREFERRED_LIFETIME - @ref MAX_DESYNC_FACTOR)
* (Calculates the addresses that are generated within an address's lifetime.)
* -> floor(...) to only consider already generated addresses
* -> +1 to also account for the first address itself
*
* @see CONFIG_GNRC_IPV6_NIB_OFFL_NUMOF
* May need to be increased; each temporary address has a /128 prefix
* to manage its maximum total lifetimes.
*/
#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_SLAAC_TEMPORARY_ADDRESSES) || defined(DOXYGEN)
#ifndef MAX_TEMP_ADDRESSES
#define MAX_TEMP_ADDRESSES (4)
#endif
#else
#define MAX_TEMP_ADDRESSES (0)
#endif

/**
* @brief Maximum number of unicast and anycast addresses per interface
*
Expand All @@ -110,11 +137,12 @@ extern "C" {
* addresses' solicited nodes multicast addresses.
*
* Default: 2 (1 link-local + 1 global address) + any additional address via
* configuration protocol (e.g. DHCPv6 leases).
* configuration protocol (e.g. DHCPv6 leases) + temporary addresses.
*/
#ifndef CONFIG_GNRC_NETIF_IPV6_ADDRS_NUMOF
#define CONFIG_GNRC_NETIF_IPV6_ADDRS_NUMOF (2 + \
DHCPV6_CLIENT_ADDRS_NUMOF)
DHCPV6_CLIENT_ADDRS_NUMOF + \
MAX_TEMP_ADDRESSES)
#endif

/**
Expand Down
54 changes: 51 additions & 3 deletions sys/include/net/gnrc/netif/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,28 @@ void gnrc_netif_ipv6_addr_remove_internal(gnrc_netif_t *netif,
int gnrc_netif_ipv6_addr_idx(gnrc_netif_t *netif,
const ipv6_addr_t *addr);

/**
* @brief Returns the index of the first addr
* in gnrc_netif_t::ipv6_addrs of @p netif
* where the first @p pfx_len bits match with @p pfx
*
* @pre `(netif != NULL) && (pfx != NULL)`
*
* Can be used to check if an address is assigned to an interface.
*
* @param[in] netif the network interface
* @param[in] pfx the prefix to match
* @param[in] pfx_len the amount of bits to compare
*
* @note Only available with @ref net_gnrc_ipv6 "gnrc_ipv6".
*
* @return index of the first matching address
* in gnrc_netif_t::ipv6_addrs of @p netif
* @return -1, if no matching address found for @p netif
*/
int gnrc_netif_ipv6_addr_pfx_idx(gnrc_netif_t *netif,
const ipv6_addr_t *pfx, uint8_t pfx_len);

/**
* @brief Gets state from address flags
*
Expand Down Expand Up @@ -160,6 +182,24 @@ static inline uint8_t gnrc_netif_ipv6_addr_dad_trans(const gnrc_netif_t *netif,
return netif->ipv6.addrs_flags[idx] & GNRC_NETIF_IPV6_ADDRS_FLAGS_STATE_TENTATIVE;
}

#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_SLAAC_TEMPORARY_ADDRESSES) || defined(DOXYGEN)
/**
* @brief Gets number of address generation retries already performed for an address
*
* @param[in] netif the network interface
* @param[in] idx index of the address (and its flags)
*
* @return the number of address generation retries already
* performed
*/
static inline uint8_t gnrc_netif_ipv6_addr_gen_retries(const gnrc_netif_t *netif,
int idx)
{
return (netif->ipv6.addrs_flags[idx] & GNRC_NETIF_IPV6_ADDRS_FLAGS_IDGEN_RETRIES)
>> GNRC_NETIF_IPV6_ADDRS_FLAGS_IDGEN_RETRIES_POS;
}
#endif

/**
* @brief Returns the index of an address in gnrc_netif_t::ipv6_addrs of @p
* netif that matches @p addr best
Expand Down Expand Up @@ -201,9 +241,6 @@ int gnrc_netif_ipv6_addr_match(gnrc_netif_t *netif,
* @todo Rule 6 from RFC 6724 is currently not implemented. Has to updated as
* soon as gnrc supports flow labels.
*
* @todo Rule 7 from RFC 6724 is currently not implemented. Has to updated as
* soon as gnrc supports temporary addresses.
*
* @return The best source address for a packet addressed to @p dst
* @return NULL, if no matching address can be found on the interface.
*/
Expand Down Expand Up @@ -563,6 +600,17 @@ void gnrc_netif_init_6ln(gnrc_netif_t *netif);
*/
void gnrc_netif_ipv6_init_mtu(gnrc_netif_t *netif);

#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_SLAAC_TEMPORARY_ADDRESSES) || defined(DOXYGEN)
/**
* @brief Get DupAddrDetectTransmits
* @see https://datatracker.ietf.org/doc/html/rfc4862#section-5.1
* @return DupAddrDetectTransmits
* @return `-ENOTSUP`, when unimplemented for
* gnrc_netif_t::device_type of @p netif
*/
int gnrc_netif_ipv6_dad_transmits(const gnrc_netif_t *netif);
#endif

/**
* @brief Converts a given hardware address to an IPv6 IID.
*
Expand Down
12 changes: 12 additions & 0 deletions sys/include/net/gnrc/netif/ipv6.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,18 @@ extern "C" {
* @brief Address is an anycast address
*/
#define GNRC_NETIF_IPV6_ADDRS_FLAGS_ANYCAST (0x20U)

#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_SLAAC_TEMPORARY_ADDRESSES) || defined(DOXYGEN)
/**
* @brief Number of address generation retries
* Used for temporary addresses, where the upper limit is defined by @ref TEMP_IDGEN_RETRIES
*/
#define GNRC_NETIF_IPV6_ADDRS_FLAGS_IDGEN_RETRIES (0xC0U)
/**
* @brief Shift position of address generation retries
*/
#define GNRC_NETIF_IPV6_ADDRS_FLAGS_IDGEN_RETRIES_POS (6)
#endif
/** @} */

/**
Expand Down
72 changes: 68 additions & 4 deletions sys/net/gnrc/netif/gnrc_netif.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
#include "net/gnrc/netif.h"
#include "net/gnrc/netif/internal.h"
#include "net/gnrc/tx_sync.h"
#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_SLAAC_TEMPORARY_ADDRESSES)
#include "../network_layer/ipv6/nib/_nib-slaac.h"
#endif

#define ENABLE_DEBUG 0
#include "debug.h"
Expand Down Expand Up @@ -502,6 +505,9 @@

#if IS_USED(MODULE_GNRC_NETIF_IPV6)
static int _addr_idx(const gnrc_netif_t *netif, const ipv6_addr_t *addr);
static int _addr_pfx_idx(const gnrc_netif_t *netif,
const ipv6_addr_t *pfx,
uint8_t pfx_len);
static int _group_idx(const gnrc_netif_t *netif, const ipv6_addr_t *addr);

static char addr_str[IPV6_ADDR_MAX_STR_LEN];
Expand Down Expand Up @@ -689,6 +695,9 @@
{
bool remove_sol_nodes = true;
ipv6_addr_t sol_nodes;
#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_SLAAC_TEMPORARY_ADDRESSES)
ipv6_addr_t addr_backup = *addr;
#endif

assert((netif != NULL) && (addr != NULL));
ipv6_addr_set_solicited_nodes(&sol_nodes, addr);
Expand All @@ -713,6 +722,15 @@
gnrc_netif_ipv6_group_leave_internal(netif, &sol_nodes);
}
gnrc_netif_release(netif);

#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_SLAAC_TEMPORARY_ADDRESSES)
// for temporary addresses (deleted on DAD failure or manually),
// also their prefix needs to be removed
gnrc_ipv6_nib_pl_del(netif->pid, &addr_backup, IPV6_ADDR_BIT_LEN);
// only expected to find a prefix if it's a temporary address.
// on prefix deletion, it won't find an address to delete
// this is fine, to avoid infinite loop
#endif
}

int gnrc_netif_ipv6_addr_idx(gnrc_netif_t *netif,
Expand All @@ -730,6 +748,23 @@
return idx;
}

int gnrc_netif_ipv6_addr_pfx_idx(gnrc_netif_t *netif,
const ipv6_addr_t *pfx, uint8_t pfx_len)
{
int idx;

assert((netif != NULL) && (pfx != NULL));
DEBUG("gnrc_netif: get index of first address matching %s/%u"
" from interface %" PRIkernel_pid "\n",
ipv6_addr_to_str(addr_str, pfx, sizeof(addr_str)),
pfx_len,
netif->pid);
gnrc_netif_acquire(netif);
idx = _addr_pfx_idx(netif, pfx, pfx_len);
gnrc_netif_release(netif);
return idx;
}

int gnrc_netif_ipv6_addr_match(gnrc_netif_t *netif,
const ipv6_addr_t *addr)
{
Expand Down Expand Up @@ -947,11 +982,35 @@
return -1;
}

static int _pfx_idx(const gnrc_netif_t *netif, const ipv6_addr_t *pfx, uint8_t pfx_len, bool mcast)
{
//same as function @ref _idx above, but with generalized condition
if (!ipv6_addr_is_unspecified(pfx)) {
const ipv6_addr_t *iplist = (mcast) ? netif->ipv6.groups :
netif->ipv6.addrs;
unsigned ipmax = (mcast) ? GNRC_NETIF_IPV6_GROUPS_NUMOF :
CONFIG_GNRC_NETIF_IPV6_ADDRS_NUMOF;
for (unsigned i = 0; i < ipmax; i++) {
if (ipv6_addr_match_prefix(&iplist[i], pfx) >= pfx_len) {
return i;
}
}
}
return -1;
}

static inline int _addr_idx(const gnrc_netif_t *netif, const ipv6_addr_t *addr)
{
return _idx(netif, addr, false);
}

static inline int _addr_pfx_idx(const gnrc_netif_t *netif,
const ipv6_addr_t *pfx,
uint8_t pfx_len)
{
return _pfx_idx(netif, pfx, pfx_len, false);
}

static inline int _group_idx(const gnrc_netif_t *netif, const ipv6_addr_t *addr)
{
return _idx(netif, addr, true);
Expand Down Expand Up @@ -1110,6 +1169,8 @@
/* number of "points" assigned to an source address candidate in preferred
* state */
#define RULE_3_PTS (1)
/* number of "points" assigned to a temporary source address candidate */
#define RULE_7_PTS (1)

/**
* @brief Caps the match at a source addresses prefix length
Expand Down Expand Up @@ -1234,10 +1295,13 @@
* TODO: update as soon as gnrc supports flow labels
*/

/* Rule 7: Prefer temporary addresses.
* Temporary addresses are currently not supported by gnrc.
* TODO: update as soon as gnrc supports temporary addresses
*/
/* Rule 7: Prefer temporary addresses. */
#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_SLAAC_TEMPORARY_ADDRESSES)
if (is_temporary_addr(netif, ptr)) {
DEBUG("winner for rule 7 found\n");
winner_set[i] += RULE_7_PTS;
}
#endif

if (winner_set[i] > max_pts) {
idx = i;
Expand Down Expand Up @@ -2149,4 +2213,4 @@
}
}
}
/** @} */

Check warning on line 2216 in sys/net/gnrc/netif/gnrc_netif.c

View workflow job for this annotation

GitHub Actions / static-tests

source file is too long
19 changes: 19 additions & 0 deletions sys/net/gnrc/netif/gnrc_netif_device_type.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "net/eui48.h"
#include "net/gnrc/netif.h"
#include "net/ieee802154.h"
#include "net/sixlowpan/nd.h"
#include "net/l2util.h"

#if IS_USED(MODULE_GNRC_NETIF_IPV6)
Expand Down Expand Up @@ -219,6 +220,24 @@ void gnrc_netif_ipv6_init_mtu(gnrc_netif_t *netif)
}
}

#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_SLAAC_TEMPORARY_ADDRESSES)
int gnrc_netif_ipv6_dad_transmits(const gnrc_netif_t *netif)
{
switch (netif->device_type) {
#if defined(MODULE_NETDEV_IEEE802154)
case NETDEV_TYPE_IEEE802154:
return SIXLOWPAN_ND_REG_TRANSMIT_NUMOF;
#endif
#if defined(MODULE_NETDEV_ETH)
case NETDEV_TYPE_ETHERNET:
return NDP_DAD_TRANSMIT_NUMOF;
#endif
default:
return -ENOTSUP;
}
}
#endif

int gnrc_netif_ipv6_iid_from_addr(const gnrc_netif_t *netif,
const uint8_t *addr, size_t addr_len,
eui64_t *iid)
Expand Down
3 changes: 3 additions & 0 deletions sys/net/gnrc/network_layer/ipv6/gnrc_ipv6.c
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,9 @@ static void *_event_loop(void *args)
case GNRC_IPV6_NIB_ADDR_REG_TIMEOUT:
case GNRC_IPV6_NIB_ABR_TIMEOUT:
case GNRC_IPV6_NIB_PFX_TIMEOUT:
#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_SLAAC_TEMPORARY_ADDRESSES)
case GNRC_IPV6_NIB_REGEN_TEMP_ADDR:
#endif
case GNRC_IPV6_NIB_RTR_TIMEOUT:
case GNRC_IPV6_NIB_RECALC_REACH_TIME:
case GNRC_IPV6_NIB_REREG_ADDRESS:
Expand Down
4 changes: 4 additions & 0 deletions sys/net/gnrc/network_layer/ipv6/nib/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ config GNRC_IPV6_NIB_SLAAC
default n if USEMODULE_GNRC_IPV6_NIB_6LR || USEMODULE_GNRC_IPV6_NIB_6LN
default y

config GNRC_IPV6_NIB_SLAAC_TEMPORARY_ADDRESSES
bool "Use temporary addresses (rfc8981)"
depends on GNRC_IPV6_NIB_SLAAC

config GNRC_IPV6_NIB_QUEUE_PKT
bool "Use packet queue with address resolution"
default n if USEMODULE_GNRC_IPV6_NIB_6LN || GNRC_IPV6_NIB_6LN
Expand Down
Loading
Loading