.stop   = rxrpc_connection_seq_stop,
        .show   = rxrpc_connection_seq_show,
 };
+
+/*
+ * generate a list of extant virtual peers in /proc/net/rxrpc/peers
+ */
+static int rxrpc_peer_seq_show(struct seq_file *seq, void *v)
+{
+       struct rxrpc_peer *peer;
+       time64_t now;
+       char lbuff[50], rbuff[50];
+
+       if (v == SEQ_START_TOKEN) {
+               seq_puts(seq,
+                        "Proto Local                                          "
+                        " Remote                                         "
+                        " Use CW  MTU   LastUse          RTT Rc\n"
+                        );
+               return 0;
+       }
+
+       peer = list_entry(v, struct rxrpc_peer, hash_link);
+
+       sprintf(lbuff, "%pISpc", &peer->local->srx.transport);
+
+       sprintf(rbuff, "%pISpc", &peer->srx.transport);
+
+       now = ktime_get_seconds();
+       seq_printf(seq,
+                  "UDP   %-47.47s %-47.47s %3u"
+                  " %3u %5u %6llus %12llu %2u\n",
+                  lbuff,
+                  rbuff,
+                  atomic_read(&peer->usage),
+                  peer->cong_cwnd,
+                  peer->mtu,
+                  now - peer->last_tx_at,
+                  peer->rtt,
+                  peer->rtt_cursor);
+
+       return 0;
+}
+
+static void *rxrpc_peer_seq_start(struct seq_file *seq, loff_t *_pos)
+       __acquires(rcu)
+{
+       struct rxrpc_net *rxnet = rxrpc_net(seq_file_net(seq));
+       unsigned int bucket, n;
+       unsigned int shift = 32 - HASH_BITS(rxnet->peer_hash);
+       void *p;
+
+       rcu_read_lock();
+
+       if (*_pos >= UINT_MAX)
+               return NULL;
+
+       n = *_pos & ((1U << shift) - 1);
+       bucket = *_pos >> shift;
+       for (;;) {
+               if (bucket >= HASH_SIZE(rxnet->peer_hash)) {
+                       *_pos = UINT_MAX;
+                       return NULL;
+               }
+               if (n == 0) {
+                       if (bucket == 0)
+                               return SEQ_START_TOKEN;
+                       *_pos += 1;
+                       n++;
+               }
+
+               p = seq_hlist_start_rcu(&rxnet->peer_hash[bucket], n - 1);
+               if (p)
+                       return p;
+               bucket++;
+               n = 1;
+               *_pos = (bucket << shift) | n;
+       }
+}
+
+static void *rxrpc_peer_seq_next(struct seq_file *seq, void *v, loff_t *_pos)
+{
+       struct rxrpc_net *rxnet = rxrpc_net(seq_file_net(seq));
+       unsigned int bucket, n;
+       unsigned int shift = 32 - HASH_BITS(rxnet->peer_hash);
+       void *p;
+
+       if (*_pos >= UINT_MAX)
+               return NULL;
+
+       bucket = *_pos >> shift;
+
+       p = seq_hlist_next_rcu(v, &rxnet->peer_hash[bucket], _pos);
+       if (p)
+               return p;
+
+       for (;;) {
+               bucket++;
+               n = 1;
+               *_pos = (bucket << shift) | n;
+
+               if (bucket >= HASH_SIZE(rxnet->peer_hash)) {
+                       *_pos = UINT_MAX;
+                       return NULL;
+               }
+               if (n == 0) {
+                       *_pos += 1;
+                       n++;
+               }
+
+               p = seq_hlist_start_rcu(&rxnet->peer_hash[bucket], n - 1);
+               if (p)
+                       return p;
+       }
+}
+
+static void rxrpc_peer_seq_stop(struct seq_file *seq, void *v)
+       __releases(rcu)
+{
+       rcu_read_unlock();
+}
+
+
+const struct seq_operations rxrpc_peer_seq_ops = {
+       .start  = rxrpc_peer_seq_start,
+       .next   = rxrpc_peer_seq_next,
+       .stop   = rxrpc_peer_seq_stop,
+       .show   = rxrpc_peer_seq_show,
+};