Commit 7c9c0ff6 authored by Andi Kleen's avatar Andi Kleen Committed by Stephen Hemminger

[NET]: Do lazy gettimeofday for network packets.

parent eccf6f14
...@@ -281,6 +281,8 @@ __neigh_lookup_errno(struct neigh_table *tbl, const void *pkey, ...@@ -281,6 +281,8 @@ __neigh_lookup_errno(struct neigh_table *tbl, const void *pkey,
return neigh_create(tbl, pkey, dev); return neigh_create(tbl, pkey, dev);
} }
#define LOCALLY_ENQUEUED -2
#endif #endif
#endif #endif
......
...@@ -382,6 +382,7 @@ enum sock_flags { ...@@ -382,6 +382,7 @@ enum sock_flags {
SOCK_LINGER, SOCK_LINGER,
SOCK_DESTROY, SOCK_DESTROY,
SOCK_BROADCAST, SOCK_BROADCAST,
SOCK_TIMESTAMP,
}; };
static inline void sock_set_flag(struct sock *sk, enum sock_flags flag) static inline void sock_set_flag(struct sock *sk, enum sock_flags flag)
...@@ -1023,12 +1024,34 @@ static inline int sock_intr_errno(long timeo) ...@@ -1023,12 +1024,34 @@ static inline int sock_intr_errno(long timeo)
static __inline__ void static __inline__ void
sock_recv_timestamp(struct msghdr *msg, struct sock *sk, struct sk_buff *skb) sock_recv_timestamp(struct msghdr *msg, struct sock *sk, struct sk_buff *skb)
{ {
if (sk->sk_rcvtstamp) struct timeval *stamp = &skb->stamp;
put_cmsg(msg, SOL_SOCKET, SO_TIMESTAMP, sizeof(skb->stamp), &skb->stamp); if (sk->sk_rcvtstamp) {
else /* Race occurred between timestamp enabling and packet
sk->sk_stamp = skb->stamp; receiving. Fill in the current time for now. */
if (stamp->tv_sec == 0)
do_gettimeofday(stamp);
put_cmsg(msg, SOL_SOCKET, SO_TIMESTAMP, sizeof(struct timeval),
stamp);
} else
sk->sk_stamp = *stamp;
} }
extern atomic_t netstamp_needed;
extern void sock_enable_timestamp(struct sock *sk);
extern void sock_disable_timestamp(struct sock *sk);
static inline void net_timestamp(struct timeval *stamp)
{
if (atomic_read(&netstamp_needed))
do_gettimeofday(stamp);
else {
stamp->tv_sec = 0;
stamp->tv_usec = 0;
}
}
extern int sock_get_timestamp(struct sock *, struct timeval *);
/* /*
* Enable debug/info messages * Enable debug/info messages
*/ */
......
...@@ -1795,13 +1795,7 @@ static int atalk_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) ...@@ -1795,13 +1795,7 @@ static int atalk_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
break; break;
} }
case SIOCGSTAMP: case SIOCGSTAMP:
if (!sk) rc = sock_get_timestamp(sk, (struct timeval *)arg);
break;
rc = -ENOENT;
if (!sk->sk_stamp.tv_sec)
break;
rc = copy_to_user((void *)arg, &sk->sk_stamp,
sizeof(struct timeval)) ? -EFAULT : 0;
break; break;
/* Routing */ /* Routing */
case SIOCADDRT: case SIOCADDRT:
......
...@@ -76,12 +76,8 @@ int vcc_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) ...@@ -76,12 +76,8 @@ int vcc_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
goto done; goto done;
} }
case SIOCGSTAMP: /* borrowed from IP */ case SIOCGSTAMP: /* borrowed from IP */
if (!vcc->sk->sk_stamp.tv_sec) { error = sock_get_timestamp(vcc->sk, (struct timeval *)
error = -ENOENT; arg);
goto done;
}
error = copy_to_user((void *)arg, &vcc->sk->sk_stamp,
sizeof(struct timeval)) ? -EFAULT : 0;
goto done; goto done;
case ATM_SETSC: case ATM_SETSC:
printk(KERN_WARNING "ATM_SETSC is obsolete\n"); printk(KERN_WARNING "ATM_SETSC is obsolete\n");
......
...@@ -1694,12 +1694,7 @@ static int ax25_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) ...@@ -1694,12 +1694,7 @@ static int ax25_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
case SIOCGSTAMP: case SIOCGSTAMP:
if (sk != NULL) { if (sk != NULL) {
if (!sk->sk_stamp.tv_sec) { res = sock_get_timestamp(sk, (struct timeval *)arg);
res = -ENOENT;
break;
}
res = copy_to_user((void *)arg, &sk->sk_stamp,
sizeof(struct timeval)) ? -EFAULT : 0;
break; break;
} }
res = -EINVAL; res = -EINVAL;
......
...@@ -1125,7 +1125,7 @@ int call_netdevice_notifiers(unsigned long val, void *v) ...@@ -1125,7 +1125,7 @@ int call_netdevice_notifiers(unsigned long val, void *v)
void dev_queue_xmit_nit(struct sk_buff *skb, struct net_device *dev) void dev_queue_xmit_nit(struct sk_buff *skb, struct net_device *dev)
{ {
struct packet_type *ptype; struct packet_type *ptype;
do_gettimeofday(&skb->stamp); net_timestamp(&skb->stamp);
rcu_read_lock(); rcu_read_lock();
list_for_each_entry_rcu(ptype, &ptype_all, list) { list_for_each_entry_rcu(ptype, &ptype_all, list) {
...@@ -1548,7 +1548,7 @@ int netif_rx(struct sk_buff *skb) ...@@ -1548,7 +1548,7 @@ int netif_rx(struct sk_buff *skb)
#endif #endif
if (!skb->stamp.tv_sec) if (!skb->stamp.tv_sec)
do_gettimeofday(&skb->stamp); net_timestamp(&skb->stamp);
/* /*
* The code is rearranged so that the path is the most * The code is rearranged so that the path is the most
...@@ -1710,7 +1710,7 @@ int netif_receive_skb(struct sk_buff *skb) ...@@ -1710,7 +1710,7 @@ int netif_receive_skb(struct sk_buff *skb)
#endif #endif
if (!skb->stamp.tv_sec) if (!skb->stamp.tv_sec)
do_gettimeofday(&skb->stamp); net_timestamp(&skb->stamp);
skb_bond(skb); skb_bond(skb);
......
...@@ -1094,7 +1094,7 @@ void pneigh_enqueue(struct neigh_table *tbl, struct neigh_parms *p, ...@@ -1094,7 +1094,7 @@ void pneigh_enqueue(struct neigh_table *tbl, struct neigh_parms *p,
kfree_skb(skb); kfree_skb(skb);
return; return;
} }
skb->stamp.tv_sec = 0; skb->stamp.tv_sec = LOCALLY_ENQUEUED;
skb->stamp.tv_usec = now + sched_next; skb->stamp.tv_usec = now + sched_next;
spin_lock(&tbl->proxy_queue.lock); spin_lock(&tbl->proxy_queue.lock);
......
...@@ -328,6 +328,8 @@ int sock_setsockopt(struct socket *sock, int level, int optname, ...@@ -328,6 +328,8 @@ int sock_setsockopt(struct socket *sock, int level, int optname,
case SO_TIMESTAMP: case SO_TIMESTAMP:
sk->sk_rcvtstamp = valbool; sk->sk_rcvtstamp = valbool;
if (valbool)
sock_enable_timestamp(sk);
break; break;
case SO_RCVLOWAT: case SO_RCVLOWAT:
...@@ -642,6 +644,8 @@ void sk_free(struct sock *sk) ...@@ -642,6 +644,8 @@ void sk_free(struct sock *sk)
sk->sk_filter = NULL; sk->sk_filter = NULL;
} }
sock_disable_timestamp(sk);
if (atomic_read(&sk->sk_omem_alloc)) if (atomic_read(&sk->sk_omem_alloc))
printk(KERN_DEBUG "%s: optmem leakage (%d bytes) detected.\n", printk(KERN_DEBUG "%s: optmem leakage (%d bytes) detected.\n",
__FUNCTION__, atomic_read(&sk->sk_omem_alloc)); __FUNCTION__, atomic_read(&sk->sk_omem_alloc));
...@@ -1135,6 +1139,9 @@ void sock_init_data(struct socket *sock, struct sock *sk) ...@@ -1135,6 +1139,9 @@ void sock_init_data(struct socket *sock, struct sock *sk)
sk->sk_sndtimeo = MAX_SCHEDULE_TIMEOUT; sk->sk_sndtimeo = MAX_SCHEDULE_TIMEOUT;
sk->sk_owner = NULL; sk->sk_owner = NULL;
sk->sk_stamp.tv_sec = -1L;
sk->sk_stamp.tv_usec = -1L;
atomic_set(&sk->sk_refcnt, 1); atomic_set(&sk->sk_refcnt, 1);
} }
...@@ -1160,9 +1167,42 @@ void fastcall release_sock(struct sock *sk) ...@@ -1160,9 +1167,42 @@ void fastcall release_sock(struct sock *sk)
wake_up(&(sk->sk_lock.wq)); wake_up(&(sk->sk_lock.wq));
spin_unlock_bh(&(sk->sk_lock.slock)); spin_unlock_bh(&(sk->sk_lock.slock));
} }
EXPORT_SYMBOL(release_sock); EXPORT_SYMBOL(release_sock);
/* When > 0 there are consumers of rx skb time stamps */
atomic_t netstamp_needed = ATOMIC_INIT(0);
int sock_get_timestamp(struct sock *sk, struct timeval *userstamp)
{
if (!sock_flag(sk, SOCK_TIMESTAMP))
sock_enable_timestamp(sk);
if (sk->sk_stamp.tv_sec == -1)
return -ENOENT;
if (sk->sk_stamp.tv_sec == 0)
do_gettimeofday(&sk->sk_stamp);
return copy_to_user(userstamp, &sk->sk_stamp, sizeof(struct timeval)) ?
-EFAULT : 0;
}
EXPORT_SYMBOL(sock_get_timestamp);
void sock_enable_timestamp(struct sock *sk)
{
if (!sock_flag(sk, SOCK_TIMESTAMP)) {
sock_set_flag(sk, SOCK_TIMESTAMP);
atomic_inc(&netstamp_needed);
}
}
EXPORT_SYMBOL(sock_enable_timestamp);
void sock_disable_timestamp(struct sock *sk)
{
if (sock_flag(sk, SOCK_TIMESTAMP)) {
sock_reset_flag(sk, SOCK_TIMESTAMP);
atomic_dec(&netstamp_needed);
}
}
EXPORT_SYMBOL(sock_disable_timestamp);
EXPORT_SYMBOL(__lock_sock); EXPORT_SYMBOL(__lock_sock);
EXPORT_SYMBOL(__release_sock); EXPORT_SYMBOL(__release_sock);
EXPORT_SYMBOL(sk_alloc); EXPORT_SYMBOL(sk_alloc);
......
...@@ -665,10 +665,8 @@ static int econet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg ...@@ -665,10 +665,8 @@ static int econet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg
switch(cmd) { switch(cmd) {
case SIOCGSTAMP: case SIOCGSTAMP:
if (!sk->sk_stamp.tv_sec) return sock_get_timestamp(sk,(struct timeval *)arg);
return -ENOENT;
return copy_to_user((void *)arg, &sk->sk_stamp,
sizeof(struct timeval)) ? -EFAULT : 0;
case SIOCSIFADDR: case SIOCSIFADDR:
case SIOCGIFADDR: case SIOCGIFADDR:
return ec_dev_ioctl(sock, cmd, (void *)arg); return ec_dev_ioctl(sock, cmd, (void *)arg);
......
...@@ -843,11 +843,7 @@ int inet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) ...@@ -843,11 +843,7 @@ int inet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
switch (cmd) { switch (cmd) {
case SIOCGSTAMP: case SIOCGSTAMP:
if (!sk->sk_stamp.tv_sec) err = sock_get_timestamp(sk, (struct timeval *)arg);
err = -ENOENT;
else if (copy_to_user((void *)arg, &sk->sk_stamp,
sizeof(struct timeval)))
err = -EFAULT;
break; break;
case SIOCADDRT: case SIOCADDRT:
case SIOCDELRT: case SIOCDELRT:
......
...@@ -860,7 +860,7 @@ int arp_process(struct sk_buff *skb) ...@@ -860,7 +860,7 @@ int arp_process(struct sk_buff *skb)
if (n) if (n)
neigh_release(n); neigh_release(n);
if (skb->stamp.tv_sec == 0 || if (skb->stamp.tv_sec == LOCALLY_ENQUEUED ||
skb->pkt_type == PACKET_HOST || skb->pkt_type == PACKET_HOST ||
in_dev->arp_parms->proxy_delay == 0) { in_dev->arp_parms->proxy_delay == 0) {
arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha); arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha);
......
...@@ -474,13 +474,7 @@ int inet6_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) ...@@ -474,13 +474,7 @@ int inet6_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
switch(cmd) switch(cmd)
{ {
case SIOCGSTAMP: case SIOCGSTAMP:
if (!sk->sk_stamp.tv_sec) return sock_get_timestamp(sk, (struct timeval *)arg);
return -ENOENT;
err = copy_to_user((void *)arg, &sk->sk_stamp,
sizeof(struct timeval));
if (err)
return -EFAULT;
return 0;
case SIOCADDRT: case SIOCADDRT:
case SIOCDELRT: case SIOCDELRT:
......
...@@ -764,7 +764,7 @@ static void ndisc_recv_ns(struct sk_buff *skb) ...@@ -764,7 +764,7 @@ static void ndisc_recv_ns(struct sk_buff *skb)
if (ipv6_chk_acast_addr(dev, &msg->target) || if (ipv6_chk_acast_addr(dev, &msg->target) ||
(idev->cnf.forwarding && (idev->cnf.forwarding &&
pneigh_lookup(&nd_tbl, &msg->target, dev, 0))) { pneigh_lookup(&nd_tbl, &msg->target, dev, 0))) {
if (skb->stamp.tv_sec != 0 && if (skb->stamp.tv_sec != LOCALLY_ENQUEUED &&
skb->pkt_type != PACKET_HOST && skb->pkt_type != PACKET_HOST &&
inc != 0 && inc != 0 &&
idev->nd_parms->proxy_delay != 0) { idev->nd_parms->proxy_delay != 0) {
......
...@@ -1797,6 +1797,7 @@ static int ipx_recvmsg(struct kiocb *iocb, struct socket *sock, ...@@ -1797,6 +1797,7 @@ static int ipx_recvmsg(struct kiocb *iocb, struct socket *sock,
copied); copied);
if (rc) if (rc)
goto out_free; goto out_free;
if (skb->stamp.tv_sec)
sk->sk_stamp = skb->stamp; sk->sk_stamp = skb->stamp;
msg->msg_namelen = sizeof(*sipx); msg->msg_namelen = sizeof(*sipx);
...@@ -1870,15 +1871,8 @@ static int ipx_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) ...@@ -1870,15 +1871,8 @@ static int ipx_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
break; break;
case SIOCGSTAMP: case SIOCGSTAMP:
rc = -EINVAL; rc = -EINVAL;
if (sk) { if (sk)
rc = -ENOENT; rc = sock_get_timestamp(sk, (struct timeval *)arg);
if (!sk->sk_stamp.tv_sec)
break;
rc = -EFAULT;
if (!copy_to_user((void *)arg, &sk->sk_stamp,
sizeof(struct timeval)))
rc = 0;
}
break; break;
case SIOCGIFDSTADDR: case SIOCGIFDSTADDR:
case SIOCSIFDSTADDR: case SIOCSIFDSTADDR:
......
...@@ -1796,14 +1796,8 @@ static int irda_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) ...@@ -1796,14 +1796,8 @@ static int irda_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
} }
case SIOCGSTAMP: case SIOCGSTAMP:
if (sk != NULL) { if (sk != NULL)
if (!sk->sk_stamp.tv_sec) return sock_get_timestamp(sk, (struct timeval *)arg);
return -ENOENT;
if (copy_to_user((void *)arg, &sk->sk_stamp,
sizeof(struct timeval)))
return -EFAULT;
return 0;
}
return -EINVAL; return -EINVAL;
case SIOCGIFADDR: case SIOCGIFADDR:
......
...@@ -1200,17 +1200,11 @@ static int nr_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) ...@@ -1200,17 +1200,11 @@ static int nr_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
} }
case SIOCGSTAMP: case SIOCGSTAMP:
if (sk != NULL) { ret = -EINVAL;
if (!sk->sk_stamp.tv_sec) { if (sk != NULL)
release_sock(sk); ret = sock_get_timestamp(sk, (struct timeval *)arg);
return -ENOENT;
}
ret = copy_to_user((void *)arg, &sk->sk_stamp, sizeof(struct timeval)) ? -EFAULT : 0;
release_sock(sk); release_sock(sk);
return ret; return ret;
}
release_sock(sk);
return -EINVAL;
case SIOCGIFADDR: case SIOCGIFADDR:
case SIOCSIFADDR: case SIOCSIFADDR:
......
...@@ -625,6 +625,10 @@ static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev, struct pack ...@@ -625,6 +625,10 @@ static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev, struct pack
h->tp_snaplen = snaplen; h->tp_snaplen = snaplen;
h->tp_mac = macoff; h->tp_mac = macoff;
h->tp_net = netoff; h->tp_net = netoff;
if (skb->stamp.tv_sec == 0) {
do_gettimeofday(&skb->stamp);
sock_enable_timestamp(sk);
}
h->tp_sec = skb->stamp.tv_sec; h->tp_sec = skb->stamp.tv_sec;
h->tp_usec = skb->stamp.tv_usec; h->tp_usec = skb->stamp.tv_usec;
...@@ -1461,12 +1465,7 @@ static int packet_ioctl(struct socket *sock, unsigned int cmd, ...@@ -1461,12 +1465,7 @@ static int packet_ioctl(struct socket *sock, unsigned int cmd,
return put_user(amount, (int *)arg); return put_user(amount, (int *)arg);
} }
case SIOCGSTAMP: case SIOCGSTAMP:
if (!sk->sk_stamp.tv_sec) return sock_get_timestamp(sk, (struct timeval *)arg);
return -ENOENT;
if (copy_to_user((void *)arg, &sk->sk_stamp,
sizeof(struct timeval)))
return -EFAULT;
break;
#ifdef CONFIG_INET #ifdef CONFIG_INET
case SIOCADDRT: case SIOCADDRT:
......
...@@ -1269,12 +1269,8 @@ static int rose_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) ...@@ -1269,12 +1269,8 @@ static int rose_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
} }
case SIOCGSTAMP: case SIOCGSTAMP:
if (sk != NULL) { if (sk != NULL)
if (!sk->sk_stamp.tv_sec) return sock_get_timestamp(sk, (struct timeval *)arg);
return -ENOENT;
return copy_to_user((void *)arg, &sk->sk_stamp,
sizeof(struct timeval)) ? -EFAULT : 0;
}
return -EINVAL; return -EINVAL;
case SIOCGIFADDR: case SIOCGIFADDR:
......
...@@ -341,6 +341,11 @@ static int rxrpc_incoming_msg(struct rxrpc_transport *trans, ...@@ -341,6 +341,11 @@ static int rxrpc_incoming_msg(struct rxrpc_transport *trans,
msg->trans = trans; msg->trans = trans;
msg->state = RXRPC_MSG_RECEIVED; msg->state = RXRPC_MSG_RECEIVED;
msg->stamp = pkt->stamp; msg->stamp = pkt->stamp;
if (msg->stamp.tv_sec == 0) {
do_gettimeofday(&msg->stamp);
if (pkt->sk)
sock_enable_timestamp(pkt->sk);
}
msg->seq = ntohl(msg->hdr.seq); msg->seq = ntohl(msg->hdr.seq);
/* attach the packet */ /* attach the packet */
......
...@@ -175,6 +175,12 @@ int sctp_rcv(struct sk_buff *skb) ...@@ -175,6 +175,12 @@ int sctp_rcv(struct sk_buff *skb)
rcvr = asoc ? &asoc->base : &ep->base; rcvr = asoc ? &asoc->base : &ep->base;
sk = rcvr->sk; sk = rcvr->sk;
/* SCTP seems to always need a timestamp right now (FIXME) */
if (skb->stamp.tv_sec == 0) {
do_gettimeofday(&skb->stamp);
sock_enable_timestamp(sk);
}
if (!xfrm_policy_check(sk, XFRM_POLICY_IN, skb, family)) if (!xfrm_policy_check(sk, XFRM_POLICY_IN, skb, family))
goto discard_release; goto discard_release;
......
...@@ -591,6 +591,12 @@ svc_udp_recvfrom(struct svc_rqst *rqstp) ...@@ -591,6 +591,12 @@ svc_udp_recvfrom(struct svc_rqst *rqstp)
/* possibly an icmp error */ /* possibly an icmp error */
dprintk("svc: recvfrom returned error %d\n", -err); dprintk("svc: recvfrom returned error %d\n", -err);
} }
if (skb->stamp.tv_sec == 0) {
skb->stamp.tv_sec = xtime.tv_sec;
skb->stamp.tv_usec = xtime.tv_nsec * 1000;
/* Don't enable netstamp, sunrpc doesn't
need that much accuracy */
}
svsk->sk_sk->sk_stamp = skb->stamp; svsk->sk_sk->sk_stamp = skb->stamp;
set_bit(SK_DATA, &svsk->sk_flags); /* there may be more data... */ set_bit(SK_DATA, &svsk->sk_flags); /* there may be more data... */
......
...@@ -1765,13 +1765,7 @@ static int wanpipe_ioctl(struct socket *sock, unsigned int cmd, unsigned long ar ...@@ -1765,13 +1765,7 @@ static int wanpipe_ioctl(struct socket *sock, unsigned int cmd, unsigned long ar
switch(cmd) switch(cmd)
{ {
case SIOCGSTAMP: case SIOCGSTAMP:
if (!sk->sk_stamp.tv_sec) return sock_get_timestamp(sk, (struct timeval *)arg);
return -ENOENT;
err = -EFAULT;
if (!copy_to_user((void *)arg, &sk->sk_stamp,
sizeof(struct timeval)))
err = 0;
return err;
case SIOC_WANPIPE_CHECK_TX: case SIOC_WANPIPE_CHECK_TX:
......
...@@ -1206,14 +1206,10 @@ static int x25_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) ...@@ -1206,14 +1206,10 @@ static int x25_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
} }
case SIOCGSTAMP: case SIOCGSTAMP:
if (sk) {
rc = -ENOENT;
if (!sk->sk_stamp.tv_sec)
break;
rc = copy_to_user((void *)arg, &sk->sk_stamp,
sizeof(struct timeval)) ? -EFAULT : 0;
}
rc = -EINVAL; rc = -EINVAL;
if (sk)
rc = sock_get_timestamp(sk,
(struct timeval *)arg);
break; break;
case SIOCGIFADDR: case SIOCGIFADDR:
case SIOCSIFADDR: case SIOCSIFADDR:
......
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