]> Sergey Matveev's repositories - btrtrc.git/commitdiff
Don't close webseed peers for bad data
authorMatt Joiner <anacrolix@gmail.com>
Thu, 14 Aug 2025 02:30:34 +0000 (12:30 +1000)
committerMatt Joiner <anacrolix@gmail.com>
Thu, 14 Aug 2025 02:36:41 +0000 (12:36 +1000)
peer-impl.go
peerconn.go
smartban.go
torrent.go
webseed-peer.go
webseed-requesting.go

index 27be7b0648657a0aee285b0c9406b7deb9f30259..25fe5a72b9591e5ca09938146ef01c9b352ac277 100644 (file)
@@ -31,7 +31,7 @@ type legacyPeerImpl interface {
        // Drop connection. This may be a no-op if there is no connection.
        drop()
        // Rebuke the peer
-       ban()
+       providedBadData()
        String() string
        // Per peer-impl lines for WriteStatus.
        peerImplStatusLines() []string
index 9758cbd23d26c838e83dfe9c15bdfbff236c6cd8..573ae30ed47564698a278e29c4b605882b29dc05 100644 (file)
@@ -1165,7 +1165,7 @@ func (cn *PeerConn) drop() {
        cn.t.dropConnection(cn)
 }
 
-func (cn *PeerConn) ban() {
+func (cn *PeerConn) providedBadData() {
        cn.t.cl.banPeerIP(cn.remoteIp())
 }
 
index 857ca2914d3cbd97ddcca9995bc7b491a44461d7..dd7d6662f7e0d07ab261094a79ef64e000aa1d63 100644 (file)
@@ -11,6 +11,7 @@ import (
 
 type bannableAddr = netip.Addr
 
+// TODO: Should be keyed on weak[Peer].
 type smartBanCache = smartban.Cache[bannableAddr, RequestIndex, uint64]
 
 type blockCheckingWriter struct {
index 2f6b7b98f838f2f89f7ebffe0b0989a177b32b58..d66d51da578883dc761364da088a3cd66eb0e5c3 100644 (file)
@@ -1232,7 +1232,8 @@ func (t *Torrent) countBytesHashed(n int64) {
 
 func (t *Torrent) hashPiece(piece pieceIndex) (
        correct bool,
-       // These are peers that sent us blocks that differ from what we hash here.
+       // These are peers that sent us blocks that differ from what we hash here. TODO: Track Peer not
+       // bannable addr for peer types that are rebuked differently.
        differingPeers map[bannableAddr]struct{},
        err error,
 ) {
@@ -2625,7 +2626,7 @@ func (t *Torrent) pieceHashed(piece pieceIndex, passed bool, hashIoErr error) {
                                                "piece failed hash. banning peer",
                                                "piece", piece,
                                                "peer", c)
-                                       c.ban()
+                                       c.providedBadData()
                                        // TODO: Check if we now have no available peers for pieces we want.
                                }
                        }
index 19ace3142422f78c1c6b48c365ca21aa6045c047..2ebfd66c7e81cf41cbf1a17c8f167cf92249f72b 100644 (file)
@@ -23,15 +23,30 @@ import (
 
 type webseedPeer struct {
        // First field for stats alignment.
-       peer             Peer
-       logger           *slog.Logger
-       client           webseed.Client
-       activeRequests   map[*webseedRequest]struct{}
-       locker           sync.Locker
-       lastUnhandledErr time.Time
-       hostKey          webseedHostKeyHandle
+       peer           Peer
+       logger         *slog.Logger
+       client         webseed.Client
+       activeRequests map[*webseedRequest]struct{}
+       locker         sync.Locker
+       hostKey        webseedHostKeyHandle
        // We need this to look ourselves up in the Client.activeWebseedRequests map.
        url webseedUrlKey
+
+       // When requests are allowed to resume. If Zero, then anytime.
+       penanceComplete time.Time
+       lastCrime       error
+}
+
+func (me *webseedPeer) suspended() bool {
+       return me.lastCrime != nil && time.Now().Before(me.penanceComplete)
+}
+
+func (me *webseedPeer) convict(err error, term time.Duration) {
+       if me.suspended() {
+               return
+       }
+       me.lastCrime = err
+       me.penanceComplete = time.Now().Add(term)
 }
 
 func (*webseedPeer) allConnStatsImplField(stats *AllConnStats) *ConnStats {
@@ -78,7 +93,12 @@ var _ legacyPeerImpl = (*webseedPeer)(nil)
 func (me *webseedPeer) peerImplStatusLines() []string {
        lines := []string{
                me.client.Url,
-               fmt.Sprintf("last unhandled error: %v", eventAgeString(me.lastUnhandledErr)),
+       }
+       if me.lastCrime != nil {
+               lines = append(lines, fmt.Sprintf("last crime: %v", me.lastCrime))
+       }
+       if me.suspended() {
+               lines = append(lines, fmt.Sprintf("suspended for %v more", time.Until(me.penanceComplete)))
        }
        if len(me.activeRequests) > 0 {
                elems := make([]string, 0, len(me.activeRequests))
@@ -238,8 +258,8 @@ func (ws *webseedPeer) connectionFlags() string {
 // Maybe this should drop all existing connections, or something like that.
 func (ws *webseedPeer) drop() {}
 
-func (cn *webseedPeer) ban() {
-       cn.peer.close()
+func (cn *webseedPeer) providedBadData() {
+       cn.convict(errors.New("provided bad data"), time.Minute)
 }
 
 func (ws *webseedPeer) onClose() {
index 3bd95f500e80f3b17588f86d4c4f18b85ba116f4..4cc9ccb462802fb30d7bb02df5eba607c859b7a8 100644 (file)
@@ -384,6 +384,9 @@ func (cl *Client) iterPossibleWebseedRequests() iter.Seq2[webseedUniqueRequestKe
                                        panicif.GreaterThanOrEqual(firstRequest, t.maxEndRequest())
                                        webseedSliceIndex := t.requestIndexToWebseedSliceIndex(firstRequest)
                                        for url, ws := range t.webSeeds {
+                                               if ws.suspended() {
+                                                       continue
+                                               }
                                                // Return value from this function (RequestPieceFunc) doesn't terminate
                                                // iteration, so propagate that to not handling the yield return value.
                                                if !yield(