From: Matt Joiner Date: Fri, 27 Mar 2015 04:36:59 +0000 (+1100) Subject: Prevent bad metadata_size in extended handshakes from stalling completion of metadata X-Git-Tag: v1.0.0~1238 X-Git-Url: http://www.git.stargrave.org/?a=commitdiff_plain;h=60d8ea75a2e4cd1f7a713ddc907faefcca466405;p=btrtrc.git Prevent bad metadata_size in extended handshakes from stalling completion of metadata 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. --- diff --git a/client.go b/client.go index 0436dc3c..cc83037d 100644 --- 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 { diff --git a/connection.go b/connection.go index 2469894d..b365f743 100644 --- a/connection.go +++ b/connection.go @@ -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 { diff --git a/torrent.go b/torrent.go index 7b9fff05..32fa54d7 100644 --- a/torrent.go +++ b/torrent.go @@ -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,