From 3642b8cd1c0c95befab4c5d3ddfd4a2da26ed43c Mon Sep 17 00:00:00 2001 From: gammazero <11790789+gammazero@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:45:15 -1000 Subject: [PATCH 1/5] feat: purge peer connections and information Connections to a specific peer, or to all peers, can be closed and the peer information removed from the peer store. This can be useful to help determine if the presence/absence of a connection to a peer is affecting behavior. Be aware that purging a connection is inherently racey as it is possible for the peer to reestablish a connection at any time following a purge. - `http://$RAINBOW_CTL_LISTEN_ADDRESS/mgr/purge?peer=` purges connection and info for peer identifid by peer_id - `http://$RAINBOW_CTL_LISTEN_ADDRESS/mgr/purge?peer=all` purges connections and info for all peers - `http://$RAINBOW_CTL_LISTEN_ADDRESS/mgr/peers` returns a list of currently connected peers Closes #193 --- README.md | 13 +++++++ handlers.go | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 2 + 3 files changed, 122 insertions(+) diff --git a/README.md b/README.md index 36e9398..2aa6eb9 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,19 @@ possible to dynamically modify the logging at runtime. - `http://$RAINBOW_CTL_LISTEN_ADDRESS/mgr/log/level?subsystem=&level=` will set the logging level for a subsystem - `http://$RAINBOW_CTL_LISTEN_ADDRESS/mgr/log/ls` will return a comma separated list of available logging subsystems +## Purging Peer Connections + +Connections to a specific peer, or to all peers, can be closed and the peer information removed from the peer store. This can be useful to help determine if the presence/absence of a connection to a peer is affecting behavior. Be aware that purging a connection is inherently racey as it is possible for the peer to reestablish a connection at any time following a purge. + +- `http://$RAINBOW_CTL_LISTEN_ADDRESS/mgr/purge?peer=` purges connection and info for peer identifid by peer_id +- `http://$RAINBOW_CTL_LISTEN_ADDRESS/mgr/purge?peer=all` purges connections and info for all peers +- `http://$RAINBOW_CTL_LISTEN_ADDRESS/mgr/peers` returns a list of currently connected peers + +Example cURL commmand to show connected peers and purge peer connection: + + curl http://127.0.0.1:8091/mgr/peers + curl http://127.0.0.1:8091/mgr/purge?peer=QmQzqxhK82kAmKvARFZSkUVS6fo9sySaiogAnx5EnZ6ZmC + ## Tracing See [docs/tracing.md](docs/tracing.md). diff --git a/handlers.go b/handlers.go index 1afcd6e..b4b5856 100644 --- a/handlers.go +++ b/handlers.go @@ -13,6 +13,8 @@ import ( "github.com/ipfs/boxo/blockstore" leveldb "github.com/ipfs/go-ds-leveldb" "github.com/ipfs/go-log/v2" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" _ "embed" _ "net/http/pprof" @@ -92,6 +94,111 @@ func GCHandler(gnd *Node) func(w http.ResponseWriter, r *http.Request) { } } +func PurgePeerHandler(p2pHost host.Host) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + + q := r.URL.Query() + peerIDStr := q.Get("peer") + if peerIDStr == "" { + http.Error(w, "missing peer id", http.StatusBadRequest) + return + } + + if peerIDStr == "all" { + purgeCount, err := purgeAllConnections(p2pHost) + if err != nil { + goLog.Errorw("Error closing all libp2p connections", "err", err) + http.Error(w, "error closing connections", http.StatusInternalServerError) + return + } + goLog.Infow("Purged connections", "count", purgeCount) + + h := w.Header() + h.Set("Content-Type", "text/plain; charset=utf-8") + fmt.Fprintln(w, "Peer connections purged:", purgeCount) + return + } + + peerID, err := peer.Decode(peerIDStr) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + err = purgeConnection(p2pHost, peerID) + if err != nil { + goLog.Errorw("Error closing libp2p connection", "err", err, "peer", peerID) + http.Error(w, "error closing connection", http.StatusInternalServerError) + return + } + goLog.Infow("Purged connection", "peer", peerID) + + h := w.Header() + h.Set("Content-Type", "text/plain; charset=utf-8") + fmt.Fprintln(w, "Purged connection to peer", peerID) + } +} + +func purgeConnection(p2pHost host.Host, peerID peer.ID) error { + peerStore := p2pHost.Peerstore() + if peerStore != nil { + peerStore.RemovePeer(peerID) + peerStore.ClearAddrs(peerID) + } + return p2pHost.Network().ClosePeer(peerID) +} + +func purgeAllConnections(p2pHost host.Host) (int, error) { + net := p2pHost.Network() + peers := net.Peers() + + peerStore := p2pHost.Peerstore() + if peerStore != nil { + for _, peerID := range peers { + peerStore.RemovePeer(peerID) + peerStore.ClearAddrs(peerID) + } + } + + var errCount, purgeCount int + for _, peerID := range peers { + err := net.ClosePeer(peerID) + if err != nil { + goLog.Errorw("Closing libp2p connection", "err", err, "peer", peerID) + errCount++ + } else { + purgeCount++ + } + } + + if errCount != 0 { + return 0, fmt.Errorf("error closing connections to %d peers", errCount) + } + + return purgeCount, nil +} + +func showPeersHandler(p2pHost host.Host) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + + h := w.Header() + h.Set("Content-Type", "text/plain; charset=utf-8") + + peers := p2pHost.Network().Peers() + if len(peers) == 0 { + fmt.Fprintln(w, "no connected peers") + return + } + + fmt.Fprintln(w, "Connected peers:", len(peers)) + for _, peerID := range peers { + fmt.Fprintln(w, peerID.String()) + } + } +} + func withConnect(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // ServeMux does not support requests with CONNECT method, diff --git a/main.go b/main.go index be37c14..e2a6a39 100644 --- a/main.go +++ b/main.go @@ -590,6 +590,8 @@ share the same seed as long as the indexes are different. apiMux := makeMetricsAndDebuggingHandler() apiMux.HandleFunc("/mgr/gc", GCHandler(gnd)) + apiMux.HandleFunc("/mgr/purge", PurgePeerHandler(gnd.host)) + apiMux.HandleFunc("/mgr/peers", showPeersHandler(gnd.host)) addLogHandlers(apiMux) apiSrv := &http.Server{ From bb39fc03a2fb69edda3925193bbf8cb7a28a898d Mon Sep 17 00:00:00 2001 From: gammazero <11790789+gammazero@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:52:27 -1000 Subject: [PATCH 2/5] Do not export mgr handler functions --- handlers.go | 4 ++-- main.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/handlers.go b/handlers.go index b4b5856..52ca635 100644 --- a/handlers.go +++ b/handlers.go @@ -74,7 +74,7 @@ func addLogHandlers(mux *http.ServeMux) { }) } -func GCHandler(gnd *Node) func(w http.ResponseWriter, r *http.Request) { +func gcHandler(gnd *Node) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() @@ -94,7 +94,7 @@ func GCHandler(gnd *Node) func(w http.ResponseWriter, r *http.Request) { } } -func PurgePeerHandler(p2pHost host.Host) func(w http.ResponseWriter, r *http.Request) { +func purgePeerHandler(p2pHost host.Host) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() diff --git a/main.go b/main.go index e2a6a39..d373dfc 100644 --- a/main.go +++ b/main.go @@ -589,8 +589,8 @@ share the same seed as long as the indexes are different. otel.SetTextMapPropagator(autoprop.NewTextMapPropagator()) apiMux := makeMetricsAndDebuggingHandler() - apiMux.HandleFunc("/mgr/gc", GCHandler(gnd)) - apiMux.HandleFunc("/mgr/purge", PurgePeerHandler(gnd.host)) + apiMux.HandleFunc("/mgr/gc", gcHandler(gnd)) + apiMux.HandleFunc("/mgr/purge", purgePeerHandler(gnd.host)) apiMux.HandleFunc("/mgr/peers", showPeersHandler(gnd.host)) addLogHandlers(apiMux) From cbb7701ee7ab805ef9cca63b6fc17cdd9145b422 Mon Sep 17 00:00:00 2001 From: gammazero <11790789+gammazero@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:54:59 -1000 Subject: [PATCH 3/5] update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f655860..736a222 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ The following emojis are used to highlight certain changes: ### Added +- Added endpoints to show and purge connected peers [#194](https://github.com/ipfs/rainbow/pull/194) + ### Changed ### Removed From d8c1757f0b5eccde13fdebb27f4638a3106e8557 Mon Sep 17 00:00:00 2001 From: Andrew Gillis <11790789+gammazero@users.noreply.github.com> Date: Mon, 4 Nov 2024 12:39:08 -0800 Subject: [PATCH 4/5] Update README.md Co-authored-by: Marcin Rataj --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2aa6eb9..dc2d274 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,8 @@ possible to dynamically modify the logging at runtime. Connections to a specific peer, or to all peers, can be closed and the peer information removed from the peer store. This can be useful to help determine if the presence/absence of a connection to a peer is affecting behavior. Be aware that purging a connection is inherently racey as it is possible for the peer to reestablish a connection at any time following a purge. +If `RAINBOW_DHT_SHARED_HOST=false` this endpoint will not show peers connected to DHT host, and only list ones used for Bitswap. + - `http://$RAINBOW_CTL_LISTEN_ADDRESS/mgr/purge?peer=` purges connection and info for peer identifid by peer_id - `http://$RAINBOW_CTL_LISTEN_ADDRESS/mgr/purge?peer=all` purges connections and info for all peers - `http://$RAINBOW_CTL_LISTEN_ADDRESS/mgr/peers` returns a list of currently connected peers From 9975a213884ce18ad13e85ea3479cbaa44c2c86a Mon Sep 17 00:00:00 2001 From: gammazero <11790789+gammazero@users.noreply.github.com> Date: Mon, 4 Nov 2024 10:48:00 -1000 Subject: [PATCH 5/5] Output peers list in json --- handlers.go | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/handlers.go b/handlers.go index 52ca635..57a989b 100644 --- a/handlers.go +++ b/handlers.go @@ -114,8 +114,7 @@ func purgePeerHandler(p2pHost host.Host) func(w http.ResponseWriter, r *http.Req } goLog.Infow("Purged connections", "count", purgeCount) - h := w.Header() - h.Set("Content-Type", "text/plain; charset=utf-8") + w.Header().Set("Content-Type", "text/plain; charset=utf-8") fmt.Fprintln(w, "Peer connections purged:", purgeCount) return } @@ -134,8 +133,7 @@ func purgePeerHandler(p2pHost host.Host) func(w http.ResponseWriter, r *http.Req } goLog.Infow("Purged connection", "peer", peerID) - h := w.Header() - h.Set("Content-Type", "text/plain; charset=utf-8") + w.Header().Set("Content-Type", "text/plain; charset=utf-8") fmt.Fprintln(w, "Purged connection to peer", peerID) } } @@ -183,18 +181,28 @@ func showPeersHandler(p2pHost host.Host) func(w http.ResponseWriter, r *http.Req return func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - h := w.Header() - h.Set("Content-Type", "text/plain; charset=utf-8") + w.Header().Set("Content-Type", "application/json; charset=utf-8") peers := p2pHost.Network().Peers() - if len(peers) == 0 { - fmt.Fprintln(w, "no connected peers") - return + body := struct { + Count int64 + Peers []string + }{ + Count: int64(len(peers)), } - fmt.Fprintln(w, "Connected peers:", len(peers)) - for _, peerID := range peers { - fmt.Fprintln(w, peerID.String()) + if len(peers) != 0 { + peerStrs := make([]string, len(peers)) + for i, peerID := range peers { + peerStrs[i] = peerID.String() + } + body.Peers = peerStrs + } + + enc := json.NewEncoder(w) + if err := enc.Encode(body); err != nil { + goLog.Errorw("cannot write response", "err", err) + http.Error(w, "", http.StatusInternalServerError) } } }