]> Cypherpunks.ru repositories - govpn.git/blob - src/govpn/peer.go
f5f917ac594182b1ddfc5fa593546de083024ed8
[govpn.git] / src / govpn / peer.go
1 package govpn
2
3 import (
4         "encoding/binary"
5         "io"
6         "sync"
7         "sync/atomic"
8         "time"
9
10         "golang.org/x/crypto/poly1305"
11         "golang.org/x/crypto/salsa20"
12         "golang.org/x/crypto/xtea"
13 )
14
15 const (
16         NonceSize       = 8
17         NonceBucketSize = 128
18         TagSize         = poly1305.TagSize
19         // S20BS is Salsa20's internal blocksize in bytes
20         S20BS = 64
21         // Maximal amount of bytes transfered with single key (4 GiB)
22         MaxBytesPerKey int64 = 1 << 32
23         // Size of packet's size mark in bytes
24         PktSizeSize = 2
25         // Heartbeat rate, relative to Timeout
26         TimeoutHeartbeat = 4
27 )
28
29 func newNonceCipher(key *[32]byte) *xtea.Cipher {
30         nonceKey := make([]byte, 16)
31         salsa20.XORKeyStream(
32                 nonceKey,
33                 make([]byte, 32),
34                 make([]byte, xtea.BlockSize),
35                 key,
36         )
37         ciph, err := xtea.NewCipher(nonceKey)
38         if err != nil {
39                 panic(err)
40         }
41         return ciph
42 }
43
44 type Peer struct {
45         Addr string
46         Id   *PeerId
47         Conn io.Writer
48
49         // Traffic behaviour
50         NoiseEnable bool
51         CPR         int
52         CPRCycle    time.Duration `json:"-"`
53
54         // Cryptography related
55         Key          *[SSize]byte `json:"-"`
56         NonceCipher  *xtea.Cipher `json:"-"`
57         nonceRecv    uint64
58         nonceLatest  uint64
59         nonceOur     uint64
60         NonceExpect  uint64 `json:"-"`
61         nonceBucket0 map[uint64]struct{}
62         nonceBucket1 map[uint64]struct{}
63         nonceFound0  bool
64         nonceFound1  bool
65         nonceBucketN int32
66
67         // Timers
68         Timeout       time.Duration `json:"-"`
69         Established   time.Time
70         LastPing      time.Time
71         LastSent      time.Time
72         willSentCycle time.Time
73
74         // Statistics
75         BytesIn         int64
76         BytesOut        int64
77         BytesPayloadIn  int64
78         BytesPayloadOut int64
79         FramesIn        int
80         FramesOut       int
81         FramesUnauth    int
82         FramesDup       int
83         HeartbeatRecv   int
84         HeartbeatSent   int
85
86         // Receiver
87         BusyR    sync.Mutex `json:"-"`
88         bufR     []byte
89         tagR     *[TagSize]byte
90         keyAuthR *[SSize]byte
91         pktSizeR uint16
92
93         // Transmitter
94         BusyT    sync.Mutex `json:"-"`
95         bufT     []byte
96         tagT     *[TagSize]byte
97         keyAuthT *[SSize]byte
98         frameT   []byte
99         now      time.Time
100 }
101
102 func (p *Peer) String() string {
103         return p.Id.String() + ":" + p.Addr
104 }
105
106 // Zero peer's memory state.
107 func (p *Peer) Zero() {
108         p.BusyT.Lock()
109         p.BusyR.Lock()
110         sliceZero(p.Key[:])
111         sliceZero(p.bufR)
112         sliceZero(p.bufT)
113         sliceZero(p.keyAuthR[:])
114         sliceZero(p.keyAuthT[:])
115         p.BusyT.Unlock()
116         p.BusyR.Unlock()
117 }
118
119 func newPeer(isClient bool, addr string, conn io.Writer, conf *PeerConf, key *[SSize]byte) *Peer {
120         now := time.Now()
121         timeout := conf.Timeout
122
123         cprCycle := cprCycleCalculate(conf.CPR)
124         noiseEnable := conf.NoiseEnable
125         if conf.CPR > 0 {
126                 noiseEnable = true
127                 timeout = cprCycle
128         } else {
129                 timeout = timeout / TimeoutHeartbeat
130         }
131
132         peer := Peer{
133                 Addr: addr,
134                 Id:   conf.Id,
135                 Conn: conn,
136
137                 NoiseEnable: noiseEnable,
138                 CPR:         conf.CPR,
139                 CPRCycle:    cprCycle,
140
141                 Key:          key,
142                 NonceCipher:  newNonceCipher(key),
143                 nonceBucket0: make(map[uint64]struct{}, NonceBucketSize),
144                 nonceBucket1: make(map[uint64]struct{}, NonceBucketSize),
145
146                 Timeout:     timeout,
147                 Established: now,
148                 LastPing:    now,
149
150                 bufR:     make([]byte, S20BS+MTU+NonceSize),
151                 bufT:     make([]byte, S20BS+MTU+NonceSize),
152                 tagR:     new([TagSize]byte),
153                 tagT:     new([TagSize]byte),
154                 keyAuthR: new([SSize]byte),
155                 keyAuthT: new([SSize]byte),
156         }
157         if isClient {
158                 peer.nonceOur = 1
159                 peer.NonceExpect = 0 + 2
160         } else {
161                 peer.nonceOur = 0
162                 peer.NonceExpect = 1 + 2
163         }
164         return &peer
165
166 }
167
168 // Process incoming Ethernet packet.
169 // ready channel is TAPListen's synchronization channel used to tell him
170 // that he is free to receive new packets. Encrypted and authenticated
171 // packets will be sent to remote Peer side immediately.
172 func (p *Peer) EthProcess(data []byte) {
173         p.now = time.Now()
174         p.BusyT.Lock()
175
176         // Zero size is a heartbeat packet
177         if len(data) == 0 {
178                 // If this heartbeat is necessary
179                 if !p.LastSent.Add(p.Timeout).Before(p.now) {
180                         p.BusyT.Unlock()
181                         return
182                 }
183                 p.bufT[S20BS+0] = byte(0)
184                 p.bufT[S20BS+1] = byte(0)
185                 p.HeartbeatSent++
186         } else {
187                 // Copy payload to our internal buffer and we are ready to
188                 // accept the next one
189                 binary.BigEndian.PutUint16(
190                         p.bufT[S20BS:S20BS+PktSizeSize],
191                         uint16(len(data)),
192                 )
193                 copy(p.bufT[S20BS+PktSizeSize:], data)
194                 p.BytesPayloadOut += int64(len(data))
195         }
196
197         if p.NoiseEnable {
198                 p.frameT = p.bufT[S20BS : S20BS+MTU-TagSize]
199         } else {
200                 p.frameT = p.bufT[S20BS : S20BS+PktSizeSize+len(data)+NonceSize]
201         }
202         p.nonceOur += 2
203         binary.BigEndian.PutUint64(p.frameT[len(p.frameT)-NonceSize:], p.nonceOur)
204         p.NonceCipher.Encrypt(
205                 p.frameT[len(p.frameT)-NonceSize:],
206                 p.frameT[len(p.frameT)-NonceSize:],
207         )
208         for i := 0; i < SSize; i++ {
209                 p.bufT[i] = byte(0)
210         }
211         salsa20.XORKeyStream(
212                 p.bufT[:S20BS+len(p.frameT)-NonceSize],
213                 p.bufT[:S20BS+len(p.frameT)-NonceSize],
214                 p.frameT[len(p.frameT)-NonceSize:],
215                 p.Key,
216         )
217
218         copy(p.keyAuthT[:], p.bufT[:SSize])
219         poly1305.Sum(p.tagT, p.frameT, p.keyAuthT)
220
221         atomic.AddInt64(&p.BytesOut, int64(len(p.frameT)+TagSize))
222         p.FramesOut++
223
224         if p.CPRCycle != time.Duration(0) {
225                 p.willSentCycle = p.LastSent.Add(p.CPRCycle)
226                 if p.willSentCycle.After(p.now) {
227                         time.Sleep(p.willSentCycle.Sub(p.now))
228                         p.now = p.willSentCycle
229                 }
230         }
231
232         p.LastSent = p.now
233         p.Conn.Write(append(p.tagT[:], p.frameT...))
234         p.BusyT.Unlock()
235 }
236
237 func (p *Peer) PktProcess(data []byte, tap io.Writer, reorderable bool) bool {
238         p.BusyR.Lock()
239         for i := 0; i < SSize; i++ {
240                 p.bufR[i] = byte(0)
241         }
242         copy(p.bufR[S20BS:], data[TagSize:])
243         salsa20.XORKeyStream(
244                 p.bufR[:S20BS+len(data)-TagSize-NonceSize],
245                 p.bufR[:S20BS+len(data)-TagSize-NonceSize],
246                 data[len(data)-NonceSize:],
247                 p.Key,
248         )
249
250         copy(p.keyAuthR[:], p.bufR[:SSize])
251         copy(p.tagR[:], data[:TagSize])
252         if !poly1305.Verify(p.tagR, data[TagSize:], p.keyAuthR) {
253                 p.FramesUnauth++
254                 p.BusyR.Unlock()
255                 return false
256         }
257
258         // Check if received nonce is known to us in either of two buckets.
259         // If yes, then this is ignored duplicate.
260         // Check from the oldest bucket, as in most cases this will result
261         // in constant time check.
262         // If Bucket0 is filled, then it becomes Bucket1.
263         p.NonceCipher.Decrypt(
264                 data[len(data)-NonceSize:],
265                 data[len(data)-NonceSize:],
266         )
267         p.nonceRecv = binary.BigEndian.Uint64(data[len(data)-NonceSize:])
268         if reorderable {
269                 _, p.nonceFound0 = p.nonceBucket0[p.nonceRecv]
270                 _, p.nonceFound1 = p.nonceBucket1[p.nonceRecv]
271                 if p.nonceFound0 || p.nonceFound1 || p.nonceRecv+2*NonceBucketSize < p.nonceLatest {
272                         p.FramesDup++
273                         p.BusyR.Unlock()
274                         return false
275                 }
276                 p.nonceBucket0[p.nonceRecv] = struct{}{}
277                 p.nonceBucketN++
278                 if p.nonceBucketN == NonceBucketSize {
279                         p.nonceBucket1 = p.nonceBucket0
280                         p.nonceBucket0 = make(map[uint64]struct{}, NonceBucketSize)
281                         p.nonceBucketN = 0
282                 }
283         } else {
284                 if p.nonceRecv != p.NonceExpect {
285                         p.FramesDup++
286                         p.BusyR.Unlock()
287                         return false
288                 }
289                 p.NonceExpect += 2
290         }
291         if p.nonceRecv > p.nonceLatest {
292                 p.nonceLatest = p.nonceRecv
293         }
294
295         p.FramesIn++
296         atomic.AddInt64(&p.BytesIn, int64(len(data)))
297         p.LastPing = time.Now()
298         p.pktSizeR = binary.BigEndian.Uint16(p.bufR[S20BS : S20BS+PktSizeSize])
299
300         if p.pktSizeR == 0 {
301                 p.HeartbeatRecv++
302                 p.BusyR.Unlock()
303                 return true
304         }
305         p.BytesPayloadIn += int64(p.pktSizeR)
306         tap.Write(p.bufR[S20BS+PktSizeSize : S20BS+PktSizeSize+p.pktSizeR])
307         p.BusyR.Unlock()
308         return true
309 }