]> Sergey Matveev's repositories - btrtrc.git/blob - handshake.go
Generate default peerExtensionBytes using helpers
[btrtrc.git] / handshake.go
1 package torrent
2
3 import (
4         "bytes"
5         "encoding/hex"
6         "fmt"
7         "io"
8         "net"
9         "time"
10
11         "github.com/anacrolix/missinggo"
12
13         "github.com/anacrolix/torrent/metainfo"
14         "github.com/anacrolix/torrent/mse"
15         pp "github.com/anacrolix/torrent/peer_protocol"
16 )
17
18 type ExtensionBit uint
19
20 const (
21         ExtensionBitDHT      = 0  // http://www.bittorrent.org/beps/bep_0005.html
22         ExtensionBitExtended = 20 // http://www.bittorrent.org/beps/bep_0010.html
23         ExtensionBitFast     = 2  // http://www.bittorrent.org/beps/bep_0006.html
24 )
25
26 func handshakeWriter(w io.Writer, bb <-chan []byte, done chan<- error) {
27         var err error
28         for b := range bb {
29                 _, err = w.Write(b)
30                 if err != nil {
31                         break
32                 }
33         }
34         done <- err
35 }
36
37 type (
38         peerExtensionBytes [8]byte
39 )
40
41 func newPeerExtensionBytes(bits ...ExtensionBit) (ret peerExtensionBytes) {
42         for _, b := range bits {
43                 ret.SetBit(b)
44         }
45         return
46 }
47
48 func (pex peerExtensionBytes) SupportsExtended() bool {
49         return pex.GetBit(ExtensionBitExtended)
50 }
51
52 func (pex peerExtensionBytes) SupportsDHT() bool {
53         return pex.GetBit(ExtensionBitDHT)
54 }
55
56 func (pex peerExtensionBytes) SupportsFast() bool {
57         return pex.GetBit(ExtensionBitFast)
58 }
59
60 func (pex *peerExtensionBytes) SetBit(bit ExtensionBit) {
61         pex[7-bit/8] |= 1 << (bit % 8)
62 }
63
64 func (pex peerExtensionBytes) GetBit(bit ExtensionBit) bool {
65         return pex[7-bit/8]&(1<<(bit%8)) != 0
66 }
67
68 type handshakeResult struct {
69         peerExtensionBytes
70         PeerID
71         metainfo.Hash
72 }
73
74 // ih is nil if we expect the peer to declare the InfoHash, such as when the
75 // peer initiated the connection. Returns ok if the handshake was successful,
76 // and err if there was an unexpected condition other than the peer simply
77 // abandoning the handshake.
78 func handshake(sock io.ReadWriter, ih *metainfo.Hash, peerID [20]byte, extensions peerExtensionBytes) (res handshakeResult, ok bool, err error) {
79         // Bytes to be sent to the peer. Should never block the sender.
80         postCh := make(chan []byte, 4)
81         // A single error value sent when the writer completes.
82         writeDone := make(chan error, 1)
83         // Performs writes to the socket and ensures posts don't block.
84         go handshakeWriter(sock, postCh, writeDone)
85
86         defer func() {
87                 close(postCh) // Done writing.
88                 if !ok {
89                         return
90                 }
91                 if err != nil {
92                         panic(err)
93                 }
94                 // Wait until writes complete before returning from handshake.
95                 err = <-writeDone
96                 if err != nil {
97                         err = fmt.Errorf("error writing: %s", err)
98                 }
99         }()
100
101         post := func(bb []byte) {
102                 select {
103                 case postCh <- bb:
104                 default:
105                         panic("mustn't block while posting")
106                 }
107         }
108
109         post([]byte(pp.Protocol))
110         post(extensions[:])
111         if ih != nil { // We already know what we want.
112                 post(ih[:])
113                 post(peerID[:])
114         }
115         var b [68]byte
116         _, err = io.ReadFull(sock, b[:68])
117         if err != nil {
118                 err = nil
119                 return
120         }
121         if string(b[:20]) != pp.Protocol {
122                 return
123         }
124         missinggo.CopyExact(&res.peerExtensionBytes, b[20:28])
125         missinggo.CopyExact(&res.Hash, b[28:48])
126         missinggo.CopyExact(&res.PeerID, b[48:68])
127         peerExtensions.Add(hex.EncodeToString(res.peerExtensionBytes[:]), 1)
128
129         // TODO: Maybe we can just drop peers here if we're not interested. This
130         // could prevent them trying to reconnect, falsely believing there was
131         // just a problem.
132         if ih == nil { // We were waiting for the peer to tell us what they wanted.
133                 post(res.Hash[:])
134                 post(peerID[:])
135         }
136
137         ok = true
138         return
139 }
140
141 // Wraps a raw connection and provides the interface we want for using the
142 // connection in the message loop.
143 type deadlineReader struct {
144         nc net.Conn
145         r  io.Reader
146 }
147
148 func (r deadlineReader) Read(b []byte) (int, error) {
149         // Keep-alives should be received every 2 mins. Give a bit of gracetime.
150         err := r.nc.SetReadDeadline(time.Now().Add(150 * time.Second))
151         if err != nil {
152                 return 0, fmt.Errorf("error setting read deadline: %s", err)
153         }
154         return r.r.Read(b)
155 }
156
157 func handleEncryption(
158         rw io.ReadWriter,
159         skeys mse.SecretKeyIter,
160         policy EncryptionPolicy,
161 ) (
162         ret io.ReadWriter,
163         headerEncrypted bool,
164         cryptoMethod uint32,
165         err error,
166 ) {
167         if !policy.ForceEncryption {
168                 var protocol [len(pp.Protocol)]byte
169                 _, err = io.ReadFull(rw, protocol[:])
170                 if err != nil {
171                         return
172                 }
173                 rw = struct {
174                         io.Reader
175                         io.Writer
176                 }{
177                         io.MultiReader(bytes.NewReader(protocol[:]), rw),
178                         rw,
179                 }
180                 if string(protocol[:]) == pp.Protocol {
181                         ret = rw
182                         return
183                 }
184         }
185         headerEncrypted = true
186         ret, err = mse.ReceiveHandshake(rw, skeys, func(provides uint32) uint32 {
187                 cryptoMethod = func() uint32 {
188                         switch {
189                         case policy.ForceEncryption:
190                                 return mse.CryptoMethodRC4
191                         case policy.DisableEncryption:
192                                 return mse.CryptoMethodPlaintext
193                         case policy.PreferNoEncryption && provides&mse.CryptoMethodPlaintext != 0:
194                                 return mse.CryptoMethodPlaintext
195                         default:
196                                 return mse.DefaultCryptoSelector(provides)
197                         }
198                 }()
199                 return cryptoMethod
200         })
201         return
202 }