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.
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])
}
}
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()
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)
if c.PeerExtensionIDs[name] == 0 {
supportedExtensionMessages.Add(name, 1)
}
- c.PeerExtensionIDs[name] = id
+ c.PeerExtensionIDs[name] = byte(id)
}
}
metadata_sizeUntyped, ok := d["metadata_size"]
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 {
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
peerHasAll bool
PeerMaxRequests int // Maximum pending requests the peer allows.
- PeerExtensionIDs map[string]int64
+ PeerExtensionIDs map[string]byte
PeerClientName string
}
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 {
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,