* [PATCH 1/4] tunnel: implement 64 bits statistics
2012-04-12 16:31 [PATCH 0/4] Tunneling related patches Stephen Hemminger
@ 2012-04-12 16:31 ` Stephen Hemminger
2012-04-14 18:51 ` David Miller
2012-04-12 16:31 ` [PATCH 2/4] ipgre: follow state of lower device Stephen Hemminger
` (2 subsequent siblings)
3 siblings, 1 reply; 12+ messages in thread
From: Stephen Hemminger @ 2012-04-12 16:31 UTC (permalink / raw)
To: David Miller; +Cc: netdev
[-- Attachment #1: tunnel-64bit-stats.patch --]
[-- Type: text/plain, Size: 9277 bytes --]
Convert the per-cpu statistics kept for GRE, IPIP, and SIT tunnels
to use 64 bit statistics.
Signed-off-by: Stephen Hemminger <shemminger@vyatta.com>
---
include/net/ipip.h | 2 +
net/ipv4/ip_gre.c | 59 +++++++++++++++++++++++++++++++++++------------------
net/ipv4/ipip.c | 53 +++++++++++++++++++++++++++++++----------------
net/ipv6/sit.c | 52 ++++++++++++++++++++++++++++++----------------
4 files changed, 111 insertions(+), 55 deletions(-)
--- a/include/net/ipip.h 2012-03-14 09:51:26.511060845 -0700
+++ b/include/net/ipip.h 2012-04-11 11:46:20.338815164 -0700
@@ -54,8 +54,10 @@ struct ip_tunnel_prl_entry {
\
err = ip_local_out(skb); \
if (likely(net_xmit_eval(err) == 0)) { \
+ u64_stats_update_begin(&(stats1)->syncp); \
(stats1)->tx_bytes += pkt_len; \
(stats1)->tx_packets++; \
+ u64_stats_update_end(&(stats1)->syncp); \
} else { \
(stats2)->tx_errors++; \
(stats2)->tx_aborted_errors++; \
--- a/net/ipv4/ip_gre.c 2012-04-09 11:18:09.000000000 -0700
+++ b/net/ipv4/ip_gre.c 2012-04-11 11:46:20.338815164 -0700
@@ -169,30 +169,49 @@ struct ipgre_net {
/* often modified stats are per cpu, other are shared (netdev->stats) */
struct pcpu_tstats {
- unsigned long rx_packets;
- unsigned long rx_bytes;
- unsigned long tx_packets;
- unsigned long tx_bytes;
-} __attribute__((aligned(4*sizeof(unsigned long))));
+ u64 rx_packets;
+ u64 rx_bytes;
+ u64 tx_packets;
+ u64 tx_bytes;
+ struct u64_stats_sync syncp;
+};
-static struct net_device_stats *ipgre_get_stats(struct net_device *dev)
+static struct rtnl_link_stats64 *ipgre_get_stats64(struct net_device *dev,
+ struct rtnl_link_stats64 *tot)
{
- struct pcpu_tstats sum = { 0 };
int i;
for_each_possible_cpu(i) {
const struct pcpu_tstats *tstats = per_cpu_ptr(dev->tstats, i);
+ u64 rx_packets, rx_bytes, tx_packets, tx_bytes;
+ unsigned int start;
+
+ do {
+ start = u64_stats_fetch_begin_bh(&tstats->syncp);
+ rx_packets = tstats->rx_packets;
+ tx_packets = tstats->tx_packets;
+ rx_bytes = tstats->rx_bytes;
+ tx_bytes = tstats->tx_bytes;
+ } while (u64_stats_fetch_retry_bh(&tstats->syncp, start));
+
+ tot->rx_packets += rx_packets;
+ tot->tx_packets += tx_packets;
+ tot->rx_bytes += rx_bytes;
+ tot->tx_bytes += tx_bytes;
+ }
+
+ tot->multicast = dev->stats.multicast;
+ tot->rx_crc_errors = dev->stats.rx_crc_errors;
+ tot->rx_fifo_errors = dev->stats.rx_fifo_errors;
+ tot->rx_length_errors = dev->stats.rx_length_errors;
+ tot->rx_errors = dev->stats.rx_errors;
+ tot->tx_fifo_errors = dev->stats.tx_fifo_errors;
+ tot->tx_carrier_errors = dev->stats.tx_carrier_errors;
+ tot->tx_dropped = dev->stats.tx_dropped;
+ tot->tx_aborted_errors = dev->stats.tx_aborted_errors;
+ tot->tx_errors = dev->stats.tx_errors;
- sum.rx_packets += tstats->rx_packets;
- sum.rx_bytes += tstats->rx_bytes;
- sum.tx_packets += tstats->tx_packets;
- sum.tx_bytes += tstats->tx_bytes;
- }
- dev->stats.rx_packets = sum.rx_packets;
- dev->stats.rx_bytes = sum.rx_bytes;
- dev->stats.tx_packets = sum.tx_packets;
- dev->stats.tx_bytes = sum.tx_bytes;
- return &dev->stats;
+ return tot;
}
/* Given src, dst and key, find appropriate for input tunnel. */
@@ -672,8 +691,10 @@ static int ipgre_rcv(struct sk_buff *skb
}
tstats = this_cpu_ptr(tunnel->dev->tstats);
+ u64_stats_update_begin(&tstats->syncp);
tstats->rx_packets++;
tstats->rx_bytes += skb->len;
+ u64_stats_update_end(&tstats->syncp);
__skb_tunnel_rx(skb, tunnel->dev);
@@ -1253,7 +1274,7 @@ static const struct net_device_ops ipgre
.ndo_start_xmit = ipgre_tunnel_xmit,
.ndo_do_ioctl = ipgre_tunnel_ioctl,
.ndo_change_mtu = ipgre_tunnel_change_mtu,
- .ndo_get_stats = ipgre_get_stats,
+ .ndo_get_stats64 = ipgre_get_stats64,
};
static void ipgre_dev_free(struct net_device *dev)
@@ -1507,7 +1528,7 @@ static const struct net_device_ops ipgre
.ndo_set_mac_address = eth_mac_addr,
.ndo_validate_addr = eth_validate_addr,
.ndo_change_mtu = ipgre_tunnel_change_mtu,
- .ndo_get_stats = ipgre_get_stats,
+ .ndo_get_stats64 = ipgre_get_stats64,
};
static void ipgre_tap_setup(struct net_device *dev)
--- a/net/ipv4/ipip.c 2012-03-14 08:47:00.034845540 -0700
+++ b/net/ipv4/ipip.c 2012-04-11 11:46:20.338815164 -0700
@@ -144,30 +144,45 @@ static void ipip_dev_free(struct net_dev
/* often modified stats are per cpu, other are shared (netdev->stats) */
struct pcpu_tstats {
- unsigned long rx_packets;
- unsigned long rx_bytes;
- unsigned long tx_packets;
- unsigned long tx_bytes;
-} __attribute__((aligned(4*sizeof(unsigned long))));
+ u64 rx_packets;
+ u64 rx_bytes;
+ u64 tx_packets;
+ u64 tx_bytes;
+ struct u64_stats_sync syncp;
+};
-static struct net_device_stats *ipip_get_stats(struct net_device *dev)
+static struct rtnl_link_stats64 *ipip_get_stats64(struct net_device *dev,
+ struct rtnl_link_stats64 *tot)
{
- struct pcpu_tstats sum = { 0 };
int i;
for_each_possible_cpu(i) {
const struct pcpu_tstats *tstats = per_cpu_ptr(dev->tstats, i);
+ u64 rx_packets, rx_bytes, tx_packets, tx_bytes;
+ unsigned int start;
+
+ do {
+ start = u64_stats_fetch_begin_bh(&tstats->syncp);
+ rx_packets = tstats->rx_packets;
+ tx_packets = tstats->tx_packets;
+ rx_bytes = tstats->rx_bytes;
+ tx_bytes = tstats->tx_bytes;
+ } while (u64_stats_fetch_retry_bh(&tstats->syncp, start));
+
+ tot->rx_packets += rx_packets;
+ tot->tx_packets += tx_packets;
+ tot->rx_bytes += rx_bytes;
+ tot->tx_bytes += tx_bytes;
+ }
+
+ tot->tx_fifo_errors = dev->stats.tx_fifo_errors;
+ tot->tx_carrier_errors = dev->stats.tx_carrier_errors;
+ tot->tx_dropped = dev->stats.tx_dropped;
+ tot->tx_aborted_errors = dev->stats.tx_aborted_errors;
+ tot->tx_errors = dev->stats.tx_errors;
+ tot->collisions = dev->stats.collisions;
- sum.rx_packets += tstats->rx_packets;
- sum.rx_bytes += tstats->rx_bytes;
- sum.tx_packets += tstats->tx_packets;
- sum.tx_bytes += tstats->tx_bytes;
- }
- dev->stats.rx_packets = sum.rx_packets;
- dev->stats.rx_bytes = sum.rx_bytes;
- dev->stats.tx_packets = sum.tx_packets;
- dev->stats.tx_bytes = sum.tx_bytes;
- return &dev->stats;
+ return tot;
}
static struct ip_tunnel * ipip_tunnel_lookup(struct net *net,
@@ -404,8 +419,10 @@ static int ipip_rcv(struct sk_buff *skb)
skb->pkt_type = PACKET_HOST;
tstats = this_cpu_ptr(tunnel->dev->tstats);
+ u64_stats_update_begin(&tstats->syncp);
tstats->rx_packets++;
tstats->rx_bytes += skb->len;
+ u64_stats_update_end(&tstats->syncp);
__skb_tunnel_rx(skb, tunnel->dev);
@@ -730,7 +747,7 @@ static const struct net_device_ops ipip_
.ndo_start_xmit = ipip_tunnel_xmit,
.ndo_do_ioctl = ipip_tunnel_ioctl,
.ndo_change_mtu = ipip_tunnel_change_mtu,
- .ndo_get_stats = ipip_get_stats,
+ .ndo_get_stats64 = ipip_get_stats64,
};
static void ipip_dev_free(struct net_device *dev)
--- a/net/ipv6/sit.c 2012-04-09 11:18:09.685154426 -0700
+++ b/net/ipv6/sit.c 2012-04-11 11:46:20.338815164 -0700
@@ -87,31 +87,47 @@ struct sit_net {
/* often modified stats are per cpu, other are shared (netdev->stats) */
struct pcpu_tstats {
- unsigned long rx_packets;
- unsigned long rx_bytes;
- unsigned long tx_packets;
- unsigned long tx_bytes;
-} __attribute__((aligned(4*sizeof(unsigned long))));
+ u64 rx_packets;
+ u64 rx_bytes;
+ u64 tx_packets;
+ u64 tx_bytes;
+ struct u64_stats_sync syncp;
+};
-static struct net_device_stats *ipip6_get_stats(struct net_device *dev)
+static struct rtnl_link_stats64 *ipip6_get_stats64(struct net_device *dev,
+ struct rtnl_link_stats64 *tot)
{
- struct pcpu_tstats sum = { 0 };
int i;
for_each_possible_cpu(i) {
const struct pcpu_tstats *tstats = per_cpu_ptr(dev->tstats, i);
+ u64 rx_packets, rx_bytes, tx_packets, tx_bytes;
+ unsigned int start;
+
+ do {
+ start = u64_stats_fetch_begin_bh(&tstats->syncp);
+ rx_packets = tstats->rx_packets;
+ tx_packets = tstats->tx_packets;
+ rx_bytes = tstats->rx_bytes;
+ tx_bytes = tstats->tx_bytes;
+ } while (u64_stats_fetch_retry_bh(&tstats->syncp, start));
+
+ tot->rx_packets += rx_packets;
+ tot->tx_packets += tx_packets;
+ tot->rx_bytes += rx_bytes;
+ tot->tx_bytes += tx_bytes;
+ }
+
+ tot->rx_errors = dev->stats.rx_errors;
+ tot->tx_fifo_errors = dev->stats.tx_fifo_errors;
+ tot->tx_carrier_errors = dev->stats.tx_carrier_errors;
+ tot->tx_dropped = dev->stats.tx_dropped;
+ tot->tx_aborted_errors = dev->stats.tx_aborted_errors;
+ tot->tx_errors = dev->stats.tx_errors;
- sum.rx_packets += tstats->rx_packets;
- sum.rx_bytes += tstats->rx_bytes;
- sum.tx_packets += tstats->tx_packets;
- sum.tx_bytes += tstats->tx_bytes;
- }
- dev->stats.rx_packets = sum.rx_packets;
- dev->stats.rx_bytes = sum.rx_bytes;
- dev->stats.tx_packets = sum.tx_packets;
- dev->stats.tx_bytes = sum.tx_bytes;
- return &dev->stats;
+ return tot;
}
+
/*
* Must be invoked with rcu_read_lock
*/
@@ -1126,7 +1142,7 @@ static const struct net_device_ops ipip6
.ndo_start_xmit = ipip6_tunnel_xmit,
.ndo_do_ioctl = ipip6_tunnel_ioctl,
.ndo_change_mtu = ipip6_tunnel_change_mtu,
- .ndo_get_stats = ipip6_get_stats,
+ .ndo_get_stats64= ipip6_get_stats64,
};
static void ipip6_dev_free(struct net_device *dev)
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH 2/4] ipgre: follow state of lower device
2012-04-12 16:31 [PATCH 0/4] Tunneling related patches Stephen Hemminger
2012-04-12 16:31 ` [PATCH 1/4] tunnel: implement 64 bits statistics Stephen Hemminger
@ 2012-04-12 16:31 ` Stephen Hemminger
2012-04-12 17:32 ` Ben Hutchings
2012-04-14 18:53 ` David Miller
2012-04-12 16:31 ` [PATCH 3/4] sit: " Stephen Hemminger
2012-04-12 16:31 ` [PATCH 4/4] ipip: " Stephen Hemminger
3 siblings, 2 replies; 12+ messages in thread
From: Stephen Hemminger @ 2012-04-12 16:31 UTC (permalink / raw)
To: David Miller; +Cc: netdev
[-- Attachment #1: ipgre-lowerup.patch --]
[-- Type: text/plain, Size: 2275 bytes --]
GRE tunnels like other layered devices should propogate
carrier and RFC2863 state from lower device to tunnel.
Signed-off-by: Stephen Hemminger <shemminger@vyatta.com>
--- a/net/ipv4/ip_gre.c 2012-04-12 08:07:39.508107847 -0700
+++ b/net/ipv4/ip_gre.c 2012-04-12 08:10:14.177499183 -0700
@@ -991,6 +991,7 @@ static int ipgre_tunnel_bind_dev(struct
if (tdev) {
hlen = tdev->hard_header_len + tdev->needed_headroom;
mtu = tdev->mtu;
+ netif_stacked_transfer_operstate(tdev, dev);
}
dev->iflink = tunnel->parms.link;
@@ -1575,6 +1576,7 @@ static int ipgre_newlink(struct net *src
dev_hold(dev);
ipgre_tunnel_link(ign, nt);
+ linkwatch_fire_event(dev);
out:
return err;
@@ -1732,6 +1734,36 @@ static struct rtnl_link_ops ipgre_tap_op
.fill_info = ipgre_fill_info,
};
+/* If lower device changes state, reflect that to the tunnel. */
+static int ipgre_notify(struct notifier_block *unused,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = ptr;
+ struct net *net = dev_net(dev);
+ struct ipgre_net *ign = net_generic(net, ipgre_net_id);
+ unsigned int prio, h;
+ struct ip_tunnel *t;
+
+ if (event == NETDEV_CHANGE)
+ return NOTIFY_DONE;
+
+ for (prio = 0; prio < 4; prio++)
+ for (h = 0; h < HASH_SIZE; h++) {
+ for (t = rtnl_dereference(ign->tunnels[prio][h]);
+ t; t = rtnl_dereference(t->next)) {
+ if (dev->ifindex != t->dev->iflink)
+ continue;
+ netif_stacked_transfer_operstate(dev, t->dev);
+ }
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block ipgre_notifier = {
+ .notifier_call = ipgre_notify,
+};
+
/*
* And now the modules code and kernel interface.
*/
@@ -1760,9 +1792,15 @@ static int __init ipgre_init(void)
if (err < 0)
goto tap_ops_failed;
+ err = register_netdevice_notifier(&ipgre_notifier);
+ if (err < 0)
+ goto notify_failed;
+
out:
return err;
+notify_failed:
+ rtnl_link_unregister(&ipgre_tap_ops);
tap_ops_failed:
rtnl_link_unregister(&ipgre_link_ops);
rtnl_link_failed:
@@ -1774,6 +1812,7 @@ add_proto_failed:
static void __exit ipgre_fini(void)
{
+ unregister_netdevice_notifier(&ipgre_notifier);
rtnl_link_unregister(&ipgre_tap_ops);
rtnl_link_unregister(&ipgre_link_ops);
if (gre_del_protocol(&ipgre_protocol, GREPROTO_CISCO) < 0)
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH 3/4] sit: follow state of lower device
2012-04-12 16:31 [PATCH 0/4] Tunneling related patches Stephen Hemminger
2012-04-12 16:31 ` [PATCH 1/4] tunnel: implement 64 bits statistics Stephen Hemminger
2012-04-12 16:31 ` [PATCH 2/4] ipgre: follow state of lower device Stephen Hemminger
@ 2012-04-12 16:31 ` Stephen Hemminger
2012-04-12 16:31 ` [PATCH 4/4] ipip: " Stephen Hemminger
3 siblings, 0 replies; 12+ messages in thread
From: Stephen Hemminger @ 2012-04-12 16:31 UTC (permalink / raw)
To: David Miller; +Cc: netdev
[-- Attachment #1: sit-lowerup.patch --]
[-- Type: text/plain, Size: 2515 bytes --]
SIT tunnels like other layered devices should propogate
carrier and RFC2863 state from lower device to tunnel.
Signed-off-by: Stephen Hemminger <shemminger@vyatta.com>
--- a/net/ipv6/sit.c 2012-04-12 08:31:05.249059443 -0700
+++ b/net/ipv6/sit.c 2012-04-12 08:34:44.093009537 -0700
@@ -284,6 +284,7 @@ static struct ip_tunnel *ipip6_tunnel_lo
dev_hold(dev);
ipip6_tunnel_link(sitn, nt);
+ linkwatch_fire_event(dev);
return nt;
failed_free:
@@ -901,6 +902,7 @@ static void ipip6_tunnel_bind_dev(struct
dev->mtu = tdev->mtu - sizeof(struct iphdr);
if (dev->mtu < IPV6_MIN_MTU)
dev->mtu = IPV6_MIN_MTU;
+ netif_stacked_transfer_operstate(tdev, dev);
}
dev->iflink = tunnel->parms.link;
}
@@ -1231,6 +1233,36 @@ static void __net_exit sit_destroy_tunne
}
}
+/* If lower device changes state, reflect that to the tunnel. */
+static int sit_notify(struct notifier_block *unused,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = ptr;
+ struct net *net = dev_net(dev);
+ struct sit_net *sitn = net_generic(net, sit_net_id);
+ unsigned int prio, h;
+ struct ip_tunnel *t;
+
+ if (event == NETDEV_CHANGE)
+ return NOTIFY_DONE;
+
+ for (prio = 0; prio < 4; prio++)
+ for (h = 0; h < HASH_SIZE; h++) {
+ for (t = rtnl_dereference(sitn->tunnels[prio][h]);
+ t; t = rtnl_dereference(t->next)) {
+ if (dev->ifindex != t->dev->iflink)
+ continue;
+ netif_stacked_transfer_operstate(dev, t->dev);
+ }
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block sit_notifier = {
+ .notifier_call = sit_notify,
+};
+
static int __net_init sit_init_net(struct net *net)
{
struct sit_net *sitn = net_generic(net, sit_net_id);
@@ -1293,6 +1325,7 @@ static struct pernet_operations sit_net_
static void __exit sit_cleanup(void)
{
+ unregister_netdevice_notifier(&sit_notifier);
xfrm4_tunnel_deregister(&sit_handler, AF_INET6);
unregister_pernet_device(&sit_net_ops);
@@ -1309,11 +1342,21 @@ static int __init sit_init(void)
if (err < 0)
return err;
err = xfrm4_tunnel_register(&sit_handler, AF_INET6);
- if (err < 0) {
- unregister_pernet_device(&sit_net_ops);
- printk(KERN_INFO "sit init: Can't add protocol\n");
- }
+ if (err < 0)
+ goto xfrm4_failed;
+
+ err = register_netdevice_notifier(&sit_notifier);
+ if (err < 0)
+ goto notify_failed;
+
+out:
return err;
+
+notify_failed:
+ xfrm4_tunnel_deregister(&sit_handler, AF_INET6);
+xfrm4_failed:
+ unregister_pernet_device(&sit_net_ops);
+ goto out;
}
module_init(sit_init);
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH 4/4] ipip: follow state of lower device
2012-04-12 16:31 [PATCH 0/4] Tunneling related patches Stephen Hemminger
` (2 preceding siblings ...)
2012-04-12 16:31 ` [PATCH 3/4] sit: " Stephen Hemminger
@ 2012-04-12 16:31 ` Stephen Hemminger
3 siblings, 0 replies; 12+ messages in thread
From: Stephen Hemminger @ 2012-04-12 16:31 UTC (permalink / raw)
To: David Miller; +Cc: netdev
[-- Attachment #1: ipip-lowerup.patch --]
[-- Type: text/plain, Size: 2365 bytes --]
Same as other tunnels propogate
carrier and RFC2863 state from lower device to tunnel.
Signed-off-by: Stephen Hemminger <shemminger@vyatta.com>
--- a/net/ipv4/ipip.c 2012-04-12 08:07:39.000000000 -0700
+++ b/net/ipv4/ipip.c 2012-04-12 08:14:37.776589029 -0700
@@ -304,6 +304,8 @@ static struct ip_tunnel * ipip_tunnel_lo
dev_hold(dev);
ipip_tunnel_link(ipn, nt);
+ linkwatch_fire_event(dev);
+
return nt;
failed_free:
@@ -614,6 +616,7 @@ static void ipip_tunnel_bind_dev(struct
if (tdev) {
dev->hard_header_len = tdev->hard_header_len + sizeof(struct iphdr);
dev->mtu = tdev->mtu - sizeof(struct iphdr);
+ netif_stacked_transfer_operstate(tdev, dev);
}
dev->iflink = tunnel->parms.link;
}
@@ -897,6 +900,36 @@ static struct pernet_operations ipip_net
.size = sizeof(struct ipip_net),
};
+/* If lower device changes state, reflect that to the tunnel. */
+static int ipip_notify(struct notifier_block *unused,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = ptr;
+ struct net *net = dev_net(dev);
+ struct ipip_net *ipn = net_generic(net, ipip_net_id);
+ struct ip_tunnel *t;
+ unsigned int prio, h;
+
+ if (event == NETDEV_CHANGE)
+ return NOTIFY_DONE;
+
+ for (prio = 0; prio < 4; prio++)
+ for (h = 0; h < HASH_SIZE; h++) {
+ for (t = rtnl_dereference(ipn->tunnels[prio][h]); t;
+ t = rtnl_dereference(t->next)) {
+ if (dev->ifindex != t->dev->iflink)
+ continue;
+ netif_stacked_transfer_operstate(dev, t->dev);
+ }
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block ipip_notifier = {
+ .notifier_call = ipip_notify,
+};
+
static int __init ipip_init(void)
{
int err;
@@ -906,12 +939,24 @@ static int __init ipip_init(void)
err = register_pernet_device(&ipip_net_ops);
if (err < 0)
return err;
+
+ err = register_netdevice_notifier(&ipip_notifier);
+ if (err < 0)
+ goto notify_failed;
+
err = xfrm4_tunnel_register(&ipip_handler, AF_INET);
- if (err < 0) {
- unregister_pernet_device(&ipip_net_ops);
- pr_info("%s: can't register tunnel\n", __func__);
- }
+ if (err < 0)
+ goto xfrm4_tunnel_failed;
+out:
return err;
+
+xfrm4_tunnel_failed:
+ pr_info("%s: can't register tunnel\n", __func__);
+ unregister_netdevice_notifier(&ipip_notifier);
+
+notify_failed:
+ unregister_pernet_device(&ipip_net_ops);
+ goto out;
}
static void __exit ipip_fini(void)
^ permalink raw reply [flat|nested] 12+ messages in thread