]> Sergey Matveev's repositories - btrtrc.git/commitdiff
Prevent bad metadata_size in extended handshakes from stalling completion of metadata
authorMatt Joiner <anacrolix@gmail.com>
Fri, 27 Mar 2015 04:36:59 +0000 (15:36 +1100)
committerMatt Joiner <anacrolix@gmail.com>
Fri, 27 Mar 2015 04:36:59 +0000 (15:36 +1100)
Occasionally bad peers send ridiculous or incorrect metadata_size in their handshakes. If the first acceptable size is wrong, and too small, we'll keep failing metadata completion. If it's too large, honest peers will never send us the pieces we're asking for and we'll never complete the metadata. Now we just adjust the expected metadata size, and keep retrying until we finish. Additionally, we can now request metadata after sending initial messages.

client.go
connection.go
torrent.go

index 0436dc3c1368dc963a48a989a36e46d69ea3c4d1..cc83037d9e021532f9bbbf824834715b59193895 100644 (file)
--- a/client.go
+++ b/client.go
@@ -1329,27 +1329,19 @@ func (cl *Client) requestPendingMetadata(t *torrent, c *connection) {
        if t.haveInfo() {
                return
        }
+       if c.PeerExtensionIDs["ut_metadata"] == 0 {
+               // Peer doesn't support this.
+               return
+       }
+       // Request metadata pieces that we don't have in a random order.
        var pending []int
        for index := 0; index < t.metadataPieceCount(); index++ {
-               if !t.haveMetadataPiece(index) {
+               if !t.haveMetadataPiece(index) && !c.requestedMetadataPiece(index) {
                        pending = append(pending, index)
                }
        }
        for _, i := range mathRand.Perm(len(pending)) {
-               c.Post(pp.Message{
-                       Type:       pp.Extended,
-                       ExtendedID: byte(c.PeerExtensionIDs["ut_metadata"]),
-                       ExtendedPayload: func() []byte {
-                               b, err := bencode.Marshal(map[string]int{
-                                       "msg_type": 0,
-                                       "piece":    pending[i],
-                               })
-                               if err != nil {
-                                       panic(err)
-                               }
-                               return b
-                       }(),
-               })
+               c.requestMetadataPiece(pending[i])
        }
 }
 
@@ -1405,6 +1397,11 @@ func (cl *Client) gotMetadataExtensionMsg(payload []byte, t *torrent, c *connect
                        log.Printf("got bad metadata piece")
                        break
                }
+               if !c.requestedMetadataPiece(piece) {
+                       log.Printf("got unexpected metadata piece %d", piece)
+                       break
+               }
+               c.metadataRequests[piece] = false
                t.saveMetadataPiece(piece, payload[begin:])
                c.UsefulChunksReceived++
                c.lastUsefulChunkReceived = time.Now()
@@ -1600,7 +1597,7 @@ func (me *Client) connectionLoop(t *torrent, c *connection) error {
                                        break
                                }
                                if c.PeerExtensionIDs == nil {
-                                       c.PeerExtensionIDs = make(map[string]int64, len(mTyped))
+                                       c.PeerExtensionIDs = make(map[string]byte, len(mTyped))
                                }
                                for name, v := range mTyped {
                                        id, ok := v.(int64)
@@ -1614,7 +1611,7 @@ func (me *Client) connectionLoop(t *torrent, c *connection) error {
                                                if c.PeerExtensionIDs[name] == 0 {
                                                        supportedExtensionMessages.Add(name, 1)
                                                }
-                                               c.PeerExtensionIDs[name] = id
+                                               c.PeerExtensionIDs[name] = byte(id)
                                        }
                                }
                                metadata_sizeUntyped, ok := d["metadata_size"]
@@ -1623,7 +1620,7 @@ func (me *Client) connectionLoop(t *torrent, c *connection) error {
                                        if !ok {
                                                log.Printf("bad metadata_size type: %T", metadata_sizeUntyped)
                                        } else {
-                                               t.setMetadataSize(metadata_size)
+                                               t.setMetadataSize(metadata_size, me)
                                        }
                                }
                                if _, ok := c.PeerExtensionIDs["ut_metadata"]; ok {
index 2469894d9e90de5da5716c21941e31f7a62dea1a..b365f743d0e544f3834fc8dc6f727e37c6e7ec9f 100644 (file)
@@ -57,6 +57,9 @@ type connection struct {
        Choked           bool
        Requests         map[request]struct{}
        requestsLowWater int
+       // Indexed by metadata piece, set to true if posted and pending a
+       // response.
+       metadataRequests []bool
 
        // Stuff controlled by the remote peer.
        PeerID             [20]byte
@@ -70,7 +73,7 @@ type connection struct {
        peerHasAll bool
 
        PeerMaxRequests  int // Maximum pending requests the peer allows.
-       PeerExtensionIDs map[string]int64
+       PeerExtensionIDs map[string]byte
        PeerClientName   string
 }
 
@@ -274,6 +277,38 @@ func (c *connection) RequestPending(r request) bool {
        return ok
 }
 
+func (c *connection) requestMetadataPiece(index int) {
+       eID := c.PeerExtensionIDs["ut_metadata"]
+       if eID == 0 {
+               return
+       }
+       if index < len(c.metadataRequests) && c.metadataRequests[index] {
+               return
+       }
+       c.Post(pp.Message{
+               Type:       pp.Extended,
+               ExtendedID: eID,
+               ExtendedPayload: func() []byte {
+                       b, err := bencode.Marshal(map[string]int{
+                               "msg_type": pp.RequestMetadataExtensionMsgType,
+                               "piece":    index,
+                       })
+                       if err != nil {
+                               panic(err)
+                       }
+                       return b
+               }(),
+       })
+       for index >= len(c.metadataRequests) {
+               c.metadataRequests = append(c.metadataRequests, false)
+       }
+       c.metadataRequests[index] = true
+}
+
+func (c *connection) requestedMetadataPiece(index int) bool {
+       return index < len(c.metadataRequests) && c.metadataRequests[index]
+}
+
 // Returns true if more requests can be sent.
 func (c *connection) Request(chunk request) bool {
        if len(c.Requests) >= c.PeerMaxRequests {
index 7b9fff057d5d55974d1f533d31a19c0b6db59970..32fa54d7d27d95c9b6b5e900bd7914287cdf9596 100644 (file)
@@ -342,15 +342,24 @@ func (t *torrent) haveAllMetadataPieces() bool {
        return true
 }
 
-func (t *torrent) setMetadataSize(bytes int64) {
-       if t.MetaData != nil {
+func (t *torrent) setMetadataSize(bytes int64, cl *Client) {
+       if t.haveInfo() {
+               // We already know the correct metadata size.
                return
        }
        if bytes <= 0 || bytes > 10000000 { // 10MB, pulled from my ass.
+               log.Printf("received bad metadata size: %d", bytes)
+               return
+       }
+       if t.MetaData != nil && len(t.MetaData) == int(bytes) {
                return
        }
        t.MetaData = make([]byte, bytes)
        t.metadataHave = make([]bool, (bytes+(1<<14)-1)/(1<<14))
+       for _, c := range t.Conns {
+               cl.requestPendingMetadata(t, c)
+       }
+
 }
 
 // The current working name for the torrent. Either the name in the info dict,