Commit ce046c56 authored by David S. Miller's avatar David S. Miller

Merge branch 'ipv6_stable_privacy_address'

Hannes Frederic Sowa says:

====================
ipv6: RFC7217 stable privacy addresses implementation

this is an implementation of basic support for RFC7217 stable privacy
addresses. Please review and consider for net-next.

v2:
* Correct references to RFC 7212 -> RFC 7217 in documentation patch (thanks, Eric!)
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 7f163d07 9f0761c1
......@@ -1220,6 +1220,17 @@ anycast_src_echo_reply - BOOLEAN
FALSE: disabled
Default: FALSE
idgen_delay - INTEGER
Controls the delay in seconds after which time to retry
privacy stable address generation if a DAD conflict is
detected.
Default: 1 (as specified in RFC7217)
idgen_retries - INTEGER
Controls the number of retries to generate a stable privacy
address if a DAD conflict is detected.
Default: 3 (as specified in RFC7217)
mld_qrv - INTEGER
Controls the MLD query robustness variable (see RFC3810 9.1).
Default: 2 (as specified by RFC3810 9.1)
......@@ -1540,6 +1551,20 @@ use_optimistic - BOOLEAN
0: disabled (default)
1: enabled
stable_secret - IPv6 address
This IPv6 address will be used as a secret to generate IPv6
addresses for link-local addresses and autoconfigured
ones. All addresses generated after setting this secret will
be stable privacy ones by default. This can be changed via the
addrgenmode ip-link. conf/default/stable_secret is used as the
secret for the namespace, the interface specific ones can
overwrite that. Writes to conf/all/stable_secret are refused.
It is recommended to generate this secret during installation
of a system and keep it stable after that.
By default the stable secret is unset.
icmp/*:
ratelimit - INTEGER
Limit the maximal rates for sending ICMPv6 packets.
......
......@@ -53,6 +53,10 @@ struct ipv6_devconf {
__s32 ndisc_notify;
__s32 suppress_frag_ndisc;
__s32 accept_ra_mtu;
struct ipv6_stable_secret {
bool initialized;
struct in6_addr secret;
} stable_secret;
void *sysctl;
};
......
......@@ -47,12 +47,12 @@ struct inet6_ifaddr {
__u32 prefered_lft;
atomic_t refcnt;
spinlock_t lock;
spinlock_t state_lock;
int state;
__u32 flags;
__u8 dad_probes;
__u8 stable_privacy_retry;
__u16 scope;
......
......@@ -32,6 +32,8 @@ struct netns_sysctl_ipv6 {
int icmpv6_time;
int anycast_src_echo_reply;
int fwmark_reflect;
int idgen_retries;
int idgen_delay;
};
struct netns_ipv6 {
......
......@@ -51,6 +51,7 @@ enum {
#define IFA_F_MANAGETEMPADDR 0x100
#define IFA_F_NOPREFIXROUTE 0x200
#define IFA_F_MCAUTOJOIN 0x400
#define IFA_F_STABLE_PRIVACY 0x800
struct ifa_cacheinfo {
__u32 ifa_prefered;
......
......@@ -216,6 +216,7 @@ enum {
enum in6_addr_gen_mode {
IN6_ADDR_GEN_MODE_EUI64,
IN6_ADDR_GEN_MODE_NONE,
IN6_ADDR_GEN_MODE_STABLE_PRIVACY,
};
/* Bridge section */
......
......@@ -170,6 +170,7 @@ enum {
DEVCONF_ACCEPT_RA_FROM_LOCAL,
DEVCONF_USE_OPTIMISTIC,
DEVCONF_ACCEPT_RA_MTU,
DEVCONF_STABLE_SECRET,
DEVCONF_MAX
};
......
......@@ -198,3 +198,4 @@ void sha_init(__u32 *buf)
buf[3] = 0x10325476;
buf[4] = 0xc3d2e1f0;
}
EXPORT_SYMBOL(sha_init);
......@@ -46,6 +46,7 @@
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/net.h>
#include <linux/inet.h>
#include <linux/in6.h>
#include <linux/netdevice.h>
#include <linux/if_addr.h>
......@@ -102,6 +103,9 @@
#define INFINITY_LIFE_TIME 0xFFFFFFFF
#define IPV6_MAX_STRLEN \
sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")
static inline u32 cstamp_delta(unsigned long cstamp)
{
return (cstamp - INITIAL_JIFFIES) * 100UL / HZ;
......@@ -127,6 +131,9 @@ static void ipv6_regen_rndid(unsigned long data);
static int ipv6_generate_eui64(u8 *eui, struct net_device *dev);
static int ipv6_count_addresses(struct inet6_dev *idev);
static int ipv6_generate_stable_address(struct in6_addr *addr,
u8 dad_count,
const struct inet6_dev *idev);
/*
* Configured unicast address hash table
......@@ -202,6 +209,9 @@ static struct ipv6_devconf ipv6_devconf __read_mostly = {
.accept_dad = 1,
.suppress_frag_ndisc = 1,
.accept_ra_mtu = 1,
.stable_secret = {
.initialized = false,
}
};
static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
......@@ -240,6 +250,9 @@ static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
.accept_dad = 1,
.suppress_frag_ndisc = 1,
.accept_ra_mtu = 1,
.stable_secret = {
.initialized = false,
},
};
/* Check if a valid qdisc is available */
......@@ -860,7 +873,6 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr,
ifa->peer_addr = *peer_addr;
spin_lock_init(&ifa->lock);
spin_lock_init(&ifa->state_lock);
INIT_DELAYED_WORK(&ifa->dad_work, addrconf_dad_work);
INIT_HLIST_NODE(&ifa->addr_lst);
ifa->scope = scope;
......@@ -1003,10 +1015,10 @@ static void ipv6_del_addr(struct inet6_ifaddr *ifp)
ASSERT_RTNL();
spin_lock_bh(&ifp->state_lock);
spin_lock_bh(&ifp->lock);
state = ifp->state;
ifp->state = INET6_IFADDR_STATE_DEAD;
spin_unlock_bh(&ifp->state_lock);
spin_unlock_bh(&ifp->lock);
if (state == INET6_IFADDR_STATE_DEAD)
goto out;
......@@ -1686,19 +1698,21 @@ static int addrconf_dad_end(struct inet6_ifaddr *ifp)
{
int err = -ENOENT;
spin_lock_bh(&ifp->state_lock);
spin_lock_bh(&ifp->lock);
if (ifp->state == INET6_IFADDR_STATE_DAD) {
ifp->state = INET6_IFADDR_STATE_POSTDAD;
err = 0;
}
spin_unlock_bh(&ifp->state_lock);
spin_unlock_bh(&ifp->lock);
return err;
}
void addrconf_dad_failure(struct inet6_ifaddr *ifp)
{
struct in6_addr addr;
struct inet6_dev *idev = ifp->idev;
struct net *net = dev_net(ifp->idev->dev);
if (addrconf_dad_end(ifp)) {
in6_ifa_put(ifp);
......@@ -1708,9 +1722,57 @@ void addrconf_dad_failure(struct inet6_ifaddr *ifp)
net_info_ratelimited("%s: IPv6 duplicate address %pI6c detected!\n",
ifp->idev->dev->name, &ifp->addr);
if (idev->cnf.accept_dad > 1 && !idev->cnf.disable_ipv6) {
struct in6_addr addr;
spin_lock_bh(&ifp->lock);
if (ifp->flags & IFA_F_STABLE_PRIVACY) {
int scope = ifp->scope;
u32 flags = ifp->flags;
struct in6_addr new_addr;
struct inet6_ifaddr *ifp2;
u32 valid_lft, preferred_lft;
int pfxlen = ifp->prefix_len;
int retries = ifp->stable_privacy_retry + 1;
if (retries > net->ipv6.sysctl.idgen_retries) {
net_info_ratelimited("%s: privacy stable address generation failed because of DAD conflicts!\n",
ifp->idev->dev->name);
goto errdad;
}
new_addr = ifp->addr;
if (ipv6_generate_stable_address(&new_addr, retries,
idev))
goto errdad;
valid_lft = ifp->valid_lft;
preferred_lft = ifp->prefered_lft;
spin_unlock_bh(&ifp->lock);
if (idev->cnf.max_addresses &&
ipv6_count_addresses(idev) >=
idev->cnf.max_addresses)
goto lock_errdad;
net_info_ratelimited("%s: generating new stable privacy address because of DAD conflict\n",
ifp->idev->dev->name);
ifp2 = ipv6_add_addr(idev, &new_addr, NULL, pfxlen,
scope, flags, valid_lft,
preferred_lft);
if (IS_ERR(ifp2))
goto lock_errdad;
spin_lock_bh(&ifp2->lock);
ifp2->stable_privacy_retry = retries;
ifp2->state = INET6_IFADDR_STATE_PREDAD;
spin_unlock_bh(&ifp2->lock);
addrconf_mod_dad_work(ifp2, net->ipv6.sysctl.idgen_delay);
in6_ifa_put(ifp2);
lock_errdad:
spin_lock_bh(&ifp->lock);
} else if (idev->cnf.accept_dad > 1 && !idev->cnf.disable_ipv6) {
addr.s6_addr32[0] = htonl(0xfe800000);
addr.s6_addr32[1] = 0;
......@@ -1724,10 +1786,10 @@ void addrconf_dad_failure(struct inet6_ifaddr *ifp)
}
}
spin_lock_bh(&ifp->state_lock);
errdad:
/* transition from _POSTDAD to _ERRDAD */
ifp->state = INET6_IFADDR_STATE_ERRDAD;
spin_unlock_bh(&ifp->state_lock);
spin_unlock_bh(&ifp->lock);
addrconf_mod_dad_work(ifp, 0);
}
......@@ -2186,6 +2248,7 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
__u32 valid_lft;
__u32 prefered_lft;
int addr_type;
u32 addr_flags = 0;
struct inet6_dev *in6_dev;
struct net *net = dev_net(dev);
......@@ -2292,6 +2355,12 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
in6_dev->token.s6_addr + 8, 8);
read_unlock_bh(&in6_dev->lock);
tokenized = true;
} else if (in6_dev->addr_gen_mode ==
IN6_ADDR_GEN_MODE_STABLE_PRIVACY &&
!ipv6_generate_stable_address(&addr, 0,
in6_dev)) {
addr_flags |= IFA_F_STABLE_PRIVACY;
goto ok;
} else if (ipv6_generate_eui64(addr.s6_addr + 8, dev) &&
ipv6_inherit_eui64(addr.s6_addr + 8, in6_dev)) {
in6_dev_put(in6_dev);
......@@ -2310,7 +2379,6 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
if (ifp == NULL && valid_lft) {
int max_addresses = in6_dev->cnf.max_addresses;
u32 addr_flags = 0;
#ifdef CONFIG_IPV6_OPTIMISTIC_DAD
if (in6_dev->cnf.optimistic_dad &&
......@@ -2350,7 +2418,7 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
u32 stored_lft;
/* update lifetime (RFC2462 5.5.3 e) */
spin_lock(&ifp->lock);
spin_lock_bh(&ifp->lock);
now = jiffies;
if (ifp->valid_lft > (now - ifp->tstamp) / HZ)
stored_lft = ifp->valid_lft - (now - ifp->tstamp) / HZ;
......@@ -2380,12 +2448,12 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
ifp->tstamp = now;
flags = ifp->flags;
ifp->flags &= ~IFA_F_DEPRECATED;
spin_unlock(&ifp->lock);
spin_unlock_bh(&ifp->lock);
if (!(flags&IFA_F_TENTATIVE))
ipv6_ifa_notify(0, ifp);
} else
spin_unlock(&ifp->lock);
spin_unlock_bh(&ifp->lock);
manage_tempaddrs(in6_dev, ifp, valid_lft, prefered_lft,
create, now);
......@@ -2789,10 +2857,11 @@ static void init_loopback(struct net_device *dev)
}
}
static void addrconf_add_linklocal(struct inet6_dev *idev, const struct in6_addr *addr)
static void addrconf_add_linklocal(struct inet6_dev *idev,
const struct in6_addr *addr, u32 flags)
{
struct inet6_ifaddr *ifp;
u32 addr_flags = IFA_F_PERMANENT;
u32 addr_flags = flags | IFA_F_PERMANENT;
#ifdef CONFIG_IPV6_OPTIMISTIC_DAD
if (idev->cnf.optimistic_dad &&
......@@ -2800,7 +2869,6 @@ static void addrconf_add_linklocal(struct inet6_dev *idev, const struct in6_addr
addr_flags |= IFA_F_OPTIMISTIC;
#endif
ifp = ipv6_add_addr(idev, addr, NULL, 64, IFA_LINK, addr_flags,
INFINITY_LIFE_TIME, INFINITY_LIFE_TIME);
if (!IS_ERR(ifp)) {
......@@ -2810,18 +2878,103 @@ static void addrconf_add_linklocal(struct inet6_dev *idev, const struct in6_addr
}
}
static bool ipv6_reserved_interfaceid(struct in6_addr address)
{
if ((address.s6_addr32[2] | address.s6_addr32[3]) == 0)
return true;
if (address.s6_addr32[2] == htonl(0x02005eff) &&
((address.s6_addr32[3] & htonl(0xfe000000)) == htonl(0xfe000000)))
return true;
if (address.s6_addr32[2] == htonl(0xfdffffff) &&
((address.s6_addr32[3] & htonl(0xffffff80)) == htonl(0xffffff80)))
return true;
return false;
}
static int ipv6_generate_stable_address(struct in6_addr *address,
u8 dad_count,
const struct inet6_dev *idev)
{
static DEFINE_SPINLOCK(lock);
static __u32 digest[SHA_DIGEST_WORDS];
static __u32 workspace[SHA_WORKSPACE_WORDS];
static union {
char __data[SHA_MESSAGE_BYTES];
struct {
struct in6_addr secret;
__be64 prefix;
unsigned char hwaddr[MAX_ADDR_LEN];
u8 dad_count;
} __packed;
} data;
struct in6_addr secret;
struct in6_addr temp;
struct net *net = dev_net(idev->dev);
BUILD_BUG_ON(sizeof(data.__data) != sizeof(data));
if (idev->cnf.stable_secret.initialized)
secret = idev->cnf.stable_secret.secret;
else if (net->ipv6.devconf_dflt->stable_secret.initialized)
secret = net->ipv6.devconf_dflt->stable_secret.secret;
else
return -1;
retry:
spin_lock_bh(&lock);
sha_init(digest);
memset(&data, 0, sizeof(data));
memset(workspace, 0, sizeof(workspace));
memcpy(data.hwaddr, idev->dev->perm_addr, idev->dev->addr_len);
data.prefix = ((__be64)address->s6_addr32[0] << 32) |
(__be64)address->s6_addr32[1];
data.secret = secret;
data.dad_count = dad_count;
sha_transform(digest, data.__data, workspace);
temp = *address;
temp.s6_addr32[2] = digest[0];
temp.s6_addr32[3] = digest[1];
spin_unlock_bh(&lock);
if (ipv6_reserved_interfaceid(temp)) {
dad_count++;
if (dad_count > dev_net(idev->dev)->ipv6.sysctl.idgen_retries)
return -1;
goto retry;
}
*address = temp;
return 0;
}
static void addrconf_addr_gen(struct inet6_dev *idev, bool prefix_route)
{
if (idev->addr_gen_mode == IN6_ADDR_GEN_MODE_EUI64) {
struct in6_addr addr;
ipv6_addr_set(&addr, htonl(0xFE800000), 0, 0, 0);
if (idev->addr_gen_mode == IN6_ADDR_GEN_MODE_STABLE_PRIVACY) {
if (!ipv6_generate_stable_address(&addr, 0, idev))
addrconf_add_linklocal(idev, &addr,
IFA_F_STABLE_PRIVACY);
else if (prefix_route)
addrconf_prefix_route(&addr, 64, idev->dev, 0, 0);
} else if (idev->addr_gen_mode == IN6_ADDR_GEN_MODE_EUI64) {
/* addrconf_add_linklocal also adds a prefix_route and we
* only need to care about prefix routes if ipv6_generate_eui64
* couldn't generate one.
*/
if (ipv6_generate_eui64(addr.s6_addr + 8, idev->dev) == 0)
addrconf_add_linklocal(idev, &addr);
addrconf_add_linklocal(idev, &addr, 0);
else if (prefix_route)
addrconf_prefix_route(&addr, 64, idev->dev, 0, 0);
}
......@@ -3159,10 +3312,10 @@ static int addrconf_ifdown(struct net_device *dev, int how)
write_unlock_bh(&idev->lock);
spin_lock_bh(&ifa->state_lock);
spin_lock_bh(&ifa->lock);
state = ifa->state;
ifa->state = INET6_IFADDR_STATE_DEAD;
spin_unlock_bh(&ifa->state_lock);
spin_unlock_bh(&ifa->lock);
if (state != INET6_IFADDR_STATE_DEAD) {
__ipv6_ifa_notify(RTM_DELADDR, ifa);
......@@ -3320,12 +3473,12 @@ static void addrconf_dad_start(struct inet6_ifaddr *ifp)
{
bool begin_dad = false;
spin_lock_bh(&ifp->state_lock);
spin_lock_bh(&ifp->lock);
if (ifp->state != INET6_IFADDR_STATE_DEAD) {
ifp->state = INET6_IFADDR_STATE_PREDAD;
begin_dad = true;
}
spin_unlock_bh(&ifp->state_lock);
spin_unlock_bh(&ifp->lock);
if (begin_dad)
addrconf_mod_dad_work(ifp, 0);
......@@ -3347,7 +3500,7 @@ static void addrconf_dad_work(struct work_struct *w)
rtnl_lock();
spin_lock_bh(&ifp->state_lock);
spin_lock_bh(&ifp->lock);
if (ifp->state == INET6_IFADDR_STATE_PREDAD) {
action = DAD_BEGIN;
ifp->state = INET6_IFADDR_STATE_DAD;
......@@ -3355,7 +3508,7 @@ static void addrconf_dad_work(struct work_struct *w)
action = DAD_ABORT;
ifp->state = INET6_IFADDR_STATE_POSTDAD;
}
spin_unlock_bh(&ifp->state_lock);
spin_unlock_bh(&ifp->lock);
if (action == DAD_BEGIN) {
addrconf_dad_begin(ifp);
......@@ -4430,6 +4583,7 @@ static inline void ipv6_store_devconf(struct ipv6_devconf *cnf,
array[DEVCONF_SUPPRESS_FRAG_NDISC] = cnf->suppress_frag_ndisc;
array[DEVCONF_ACCEPT_RA_FROM_LOCAL] = cnf->accept_ra_from_local;
array[DEVCONF_ACCEPT_RA_MTU] = cnf->accept_ra_mtu;
/* we omit DEVCONF_STABLE_SECRET for now */
}
static inline size_t inet6_ifla6_size(void)
......@@ -4664,8 +4818,15 @@ static int inet6_set_link_af(struct net_device *dev, const struct nlattr *nla)
u8 mode = nla_get_u8(tb[IFLA_INET6_ADDR_GEN_MODE]);
if (mode != IN6_ADDR_GEN_MODE_EUI64 &&
mode != IN6_ADDR_GEN_MODE_NONE)
mode != IN6_ADDR_GEN_MODE_NONE &&
mode != IN6_ADDR_GEN_MODE_STABLE_PRIVACY)
return -EINVAL;
if (mode == IN6_ADDR_GEN_MODE_STABLE_PRIVACY &&
!idev->cnf.stable_secret.initialized &&
!dev_net(dev)->ipv6.devconf_dflt->stable_secret.initialized)
return -EINVAL;
idev->addr_gen_mode = mode;
err = 0;
}
......@@ -5074,6 +5235,74 @@ int addrconf_sysctl_proxy_ndp(struct ctl_table *ctl, int write,
return ret;
}
static int addrconf_sysctl_stable_secret(struct ctl_table *ctl, int write,
void __user *buffer, size_t *lenp,
loff_t *ppos)
{
int err;
struct in6_addr addr;
char str[IPV6_MAX_STRLEN];
struct ctl_table lctl = *ctl;
struct net *net = ctl->extra2;
struct ipv6_stable_secret *secret = ctl->data;
if (&net->ipv6.devconf_all->stable_secret == ctl->data)
return -EIO;
lctl.maxlen = IPV6_MAX_STRLEN;
lctl.data = str;
if (!rtnl_trylock())
return restart_syscall();
if (!write && !secret->initialized) {
err = -EIO;
goto out;
}
if (!write) {
err = snprintf(str, sizeof(str), "%pI6",
&secret->secret);
if (err >= sizeof(str)) {
err = -EIO;
goto out;
}
}
err = proc_dostring(&lctl, write, buffer, lenp, ppos);
if (err || !write)
goto out;
if (in6_pton(str, -1, addr.in6_u.u6_addr8, -1, NULL) != 1) {
err = -EIO;
goto out;
}
secret->initialized = true;
secret->secret = addr;
if (&net->ipv6.devconf_dflt->stable_secret == ctl->data) {
struct net_device *dev;
for_each_netdev(net, dev) {
struct inet6_dev *idev = __in6_dev_get(dev);
if (idev) {
idev->addr_gen_mode =
IN6_ADDR_GEN_MODE_STABLE_PRIVACY;
}
}
} else {
struct inet6_dev *idev = ctl->extra1;
idev->addr_gen_mode = IN6_ADDR_GEN_MODE_STABLE_PRIVACY;
}
out:
rtnl_unlock();
return err;
}
static struct addrconf_sysctl_table
{
......@@ -5346,6 +5575,13 @@ static struct addrconf_sysctl_table
.mode = 0644,
.proc_handler = proc_dointvec,
},
{
.procname = "stable_secret",
.data = &ipv6_devconf.stable_secret,
.maxlen = IPV6_MAX_STRLEN,
.mode = 0600,
.proc_handler = addrconf_sysctl_stable_secret,
},
{
/* sentinel */
}
......@@ -5442,6 +5678,9 @@ static int __net_init addrconf_init_net(struct net *net)
dflt->autoconf = ipv6_defaults.autoconf;
dflt->disable_ipv6 = ipv6_defaults.disable_ipv6;
dflt->stable_secret.initialized = false;
all->stable_secret.initialized = false;
net->ipv6.devconf_all = all;
net->ipv6.devconf_dflt = dflt;
......
......@@ -766,6 +766,8 @@ static int __net_init inet6_net_init(struct net *net)
net->ipv6.sysctl.icmpv6_time = 1*HZ;
net->ipv6.sysctl.flowlabel_consistency = 1;
net->ipv6.sysctl.auto_flowlabels = 0;
net->ipv6.sysctl.idgen_retries = 3;
net->ipv6.sysctl.idgen_delay = 1 * HZ;
atomic_set(&net->ipv6.fib6_sernum, 1);
err = ipv6_init_mibs(net);
......
......@@ -54,6 +54,20 @@ static struct ctl_table ipv6_table_template[] = {
.mode = 0644,
.proc_handler = proc_dointvec
},
{
.procname = "idgen_retries",
.data = &init_net.ipv6.sysctl.idgen_retries,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
},
{
.procname = "idgen_delay",
.data = &init_net.ipv6.sysctl.idgen_delay,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_jiffies,
},
{ }
};
......@@ -93,6 +107,8 @@ static int __net_init ipv6_sysctl_net_init(struct net *net)
ipv6_table[2].data = &net->ipv6.sysctl.flowlabel_consistency;
ipv6_table[3].data = &net->ipv6.sysctl.auto_flowlabels;
ipv6_table[4].data = &net->ipv6.sysctl.fwmark_reflect;
ipv6_table[5].data = &net->ipv6.sysctl.idgen_retries;
ipv6_table[6].data = &net->ipv6.sysctl.idgen_delay;
ipv6_route_table = ipv6_route_sysctl_init(net);
if (!ipv6_route_table)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment