]> Cypherpunks.ru repositories - govpn.git/blob - src/govpn/peer.go
Move cprCycleCalculate to peer.go, as it is the only user of it
[govpn.git] / src / govpn / peer.go
1 /*
2 GoVPN -- simple secure free software virtual private network daemon
3 Copyright (C) 2014-2016 Sergey Matveev <stargrave@stargrave.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 package govpn
20
21 import (
22         "bytes"
23         "encoding/binary"
24         "io"
25         "log"
26         "sync"
27         "sync/atomic"
28         "time"
29
30         "golang.org/x/crypto/poly1305"
31         "golang.org/x/crypto/salsa20"
32         "golang.org/x/crypto/xtea"
33 )
34
35 const (
36         NonceSize       = 8
37         NonceBucketSize = 128
38         TagSize         = poly1305.TagSize
39         // S20BS is Salsa20's internal blocksize in bytes
40         S20BS = 64
41         // Maximal amount of bytes transfered with single key (4 GiB)
42         MaxBytesPerKey int64 = 1 << 32
43         // Heartbeat rate, relative to Timeout
44         TimeoutHeartbeat = 4
45         // Minimal valid packet length
46         MinPktLength = 1 + 16 + 8
47         // Padding byte
48         PadByte = byte(0x80)
49 )
50
51 func newNonceCipher(key *[32]byte) *xtea.Cipher {
52         nonceKey := make([]byte, 16)
53         salsa20.XORKeyStream(
54                 nonceKey,
55                 make([]byte, 32),
56                 make([]byte, xtea.BlockSize),
57                 key,
58         )
59         ciph, err := xtea.NewCipher(nonceKey)
60         if err != nil {
61                 panic(err)
62         }
63         return ciph
64 }
65
66 type Peer struct {
67         Addr string
68         Id   *PeerId
69         Conn io.Writer
70
71         // Traffic behaviour
72         NoiseEnable bool
73         CPR         int
74         CPRCycle    time.Duration `json:"-"`
75         EncLess     bool
76         MTU         int
77
78         // Cryptography related
79         Key          *[SSize]byte `json:"-"`
80         NonceCipher  *xtea.Cipher `json:"-"`
81         nonceRecv    uint64
82         nonceLatest  uint64
83         nonceOur     uint64
84         NonceExpect  uint64 `json:"-"`
85         nonceBucket0 map[uint64]struct{}
86         nonceBucket1 map[uint64]struct{}
87         nonceFound0  bool
88         nonceFound1  bool
89         nonceBucketN int32
90
91         // Timers
92         Timeout       time.Duration `json:"-"`
93         Established   time.Time
94         LastPing      time.Time
95         LastSent      time.Time
96         willSentCycle time.Time
97
98         // Statistics
99         BytesIn         int64
100         BytesOut        int64
101         BytesPayloadIn  int64
102         BytesPayloadOut int64
103         FramesIn        int
104         FramesOut       int
105         FramesUnauth    int
106         FramesDup       int
107         HeartbeatRecv   int
108         HeartbeatSent   int
109
110         // Receiver
111         BusyR    sync.Mutex `json:"-"`
112         bufR     []byte
113         tagR     *[TagSize]byte
114         keyAuthR *[SSize]byte
115         pktSizeR int
116
117         // Transmitter
118         BusyT    sync.Mutex `json:"-"`
119         bufT     []byte
120         tagT     *[TagSize]byte
121         keyAuthT *[SSize]byte
122         frameT   []byte
123         now      time.Time
124 }
125
126 func (p *Peer) String() string {
127         return p.Id.String() + ":" + p.Addr
128 }
129
130 // Zero peer's memory state.
131 func (p *Peer) Zero() {
132         p.BusyT.Lock()
133         p.BusyR.Lock()
134         SliceZero(p.Key[:])
135         SliceZero(p.bufR)
136         SliceZero(p.bufT)
137         SliceZero(p.keyAuthR[:])
138         SliceZero(p.keyAuthT[:])
139         p.BusyT.Unlock()
140         p.BusyR.Unlock()
141 }
142
143 func (p *Peer) NonceExpectation(buf []byte) {
144         binary.BigEndian.PutUint64(buf, p.NonceExpect)
145         p.NonceCipher.Encrypt(buf, buf)
146 }
147
148 func cprCycleCalculate(rate int) time.Duration {
149         if rate == 0 {
150                 return time.Duration(0)
151         }
152         return time.Second / time.Duration(rate*(1<<10)/MTUMax)
153 }
154
155 func newPeer(isClient bool, addr string, conn io.Writer, conf *PeerConf, key *[SSize]byte) *Peer {
156         now := time.Now()
157         timeout := conf.Timeout
158
159         cprCycle := cprCycleCalculate(conf.CPR)
160         noiseEnable := conf.Noise
161         if conf.CPR > 0 {
162                 noiseEnable = true
163                 timeout = cprCycle
164         } else {
165                 timeout = timeout / TimeoutHeartbeat
166         }
167
168         bufSize := S20BS + 2*conf.MTU
169         if conf.EncLess {
170                 bufSize += EncLessEnlargeSize
171                 noiseEnable = true
172         }
173         peer := Peer{
174                 Addr: addr,
175                 Id:   conf.Id,
176                 Conn: conn,
177
178                 NoiseEnable: noiseEnable,
179                 CPR:         conf.CPR,
180                 CPRCycle:    cprCycle,
181                 EncLess:     conf.EncLess,
182                 MTU:         conf.MTU,
183
184                 Key:          key,
185                 NonceCipher:  newNonceCipher(key),
186                 nonceBucket0: make(map[uint64]struct{}, NonceBucketSize),
187                 nonceBucket1: make(map[uint64]struct{}, NonceBucketSize),
188
189                 Timeout:     timeout,
190                 Established: now,
191                 LastPing:    now,
192
193                 bufR:     make([]byte, bufSize),
194                 bufT:     make([]byte, bufSize),
195                 tagR:     new([TagSize]byte),
196                 tagT:     new([TagSize]byte),
197                 keyAuthR: new([SSize]byte),
198                 keyAuthT: new([SSize]byte),
199         }
200         if isClient {
201                 peer.nonceOur = 1
202                 peer.NonceExpect = 0 + 2
203         } else {
204                 peer.nonceOur = 0
205                 peer.NonceExpect = 1 + 2
206         }
207         return &peer
208
209 }
210
211 // Process incoming Ethernet packet.
212 // ready channel is TAPListen's synchronization channel used to tell him
213 // that he is free to receive new packets. Encrypted and authenticated
214 // packets will be sent to remote Peer side immediately.
215 func (p *Peer) EthProcess(data []byte) {
216         if len(data) > p.MTU-1 { // 1 is for padding byte
217                 log.Println("Padded data packet size", len(data)+1, "is bigger than MTU", p.MTU, p)
218                 return
219         }
220         p.now = time.Now()
221         p.BusyT.Lock()
222
223         // Zero size is a heartbeat packet
224         SliceZero(p.bufT)
225         if len(data) == 0 {
226                 // If this heartbeat is necessary
227                 if !p.LastSent.Add(p.Timeout).Before(p.now) {
228                         p.BusyT.Unlock()
229                         return
230                 }
231                 p.bufT[S20BS+0] = PadByte
232                 p.HeartbeatSent++
233         } else {
234                 // Copy payload to our internal buffer and we are ready to
235                 // accept the next one
236                 copy(p.bufT[S20BS:], data)
237                 p.bufT[S20BS+len(data)] = PadByte
238                 p.BytesPayloadOut += int64(len(data))
239         }
240
241         if p.NoiseEnable && !p.EncLess {
242                 p.frameT = p.bufT[S20BS : S20BS+p.MTU-TagSize]
243         } else if p.EncLess {
244                 p.frameT = p.bufT[S20BS : S20BS+p.MTU]
245         } else {
246                 p.frameT = p.bufT[S20BS : S20BS+len(data)+1+NonceSize]
247         }
248         p.nonceOur += 2
249         binary.BigEndian.PutUint64(p.frameT[len(p.frameT)-NonceSize:], p.nonceOur)
250         p.NonceCipher.Encrypt(
251                 p.frameT[len(p.frameT)-NonceSize:],
252                 p.frameT[len(p.frameT)-NonceSize:],
253         )
254         var out []byte
255         if p.EncLess {
256                 var err error
257                 out, err = EncLessEncode(
258                         p.Key,
259                         p.frameT[len(p.frameT)-NonceSize:],
260                         p.frameT[:len(p.frameT)-NonceSize],
261                 )
262                 if err != nil {
263                         panic(err)
264                 }
265                 out = append(out, p.frameT[len(p.frameT)-NonceSize:]...)
266         } else {
267                 salsa20.XORKeyStream(
268                         p.bufT[:S20BS+len(p.frameT)-NonceSize],
269                         p.bufT[:S20BS+len(p.frameT)-NonceSize],
270                         p.frameT[len(p.frameT)-NonceSize:],
271                         p.Key,
272                 )
273                 copy(p.keyAuthT[:], p.bufT[:SSize])
274                 poly1305.Sum(p.tagT, p.frameT, p.keyAuthT)
275                 atomic.AddInt64(&p.BytesOut, int64(len(p.frameT)+TagSize))
276                 out = append(p.tagT[:], p.frameT...)
277         }
278         p.FramesOut++
279
280         if p.CPRCycle != time.Duration(0) {
281                 p.willSentCycle = p.LastSent.Add(p.CPRCycle)
282                 if p.willSentCycle.After(p.now) {
283                         time.Sleep(p.willSentCycle.Sub(p.now))
284                         p.now = p.willSentCycle
285                 }
286         }
287
288         p.LastSent = p.now
289         p.Conn.Write(out)
290         p.BusyT.Unlock()
291 }
292
293 func (p *Peer) PktProcess(data []byte, tap io.Writer, reorderable bool) bool {
294         if len(data) < MinPktLength {
295                 return false
296         }
297         var out []byte
298         p.BusyR.Lock()
299         if p.EncLess {
300                 var err error
301                 out, err = EncLessDecode(
302                         p.Key,
303                         data[len(data)-NonceSize:],
304                         data[:len(data)-NonceSize],
305                 )
306                 if err != nil {
307                         p.FramesUnauth++
308                         p.BusyR.Unlock()
309                         return false
310                 }
311         } else {
312                 for i := 0; i < SSize; i++ {
313                         p.bufR[i] = 0
314                 }
315                 copy(p.bufR[S20BS:], data[TagSize:])
316                 salsa20.XORKeyStream(
317                         p.bufR[:S20BS+len(data)-TagSize-NonceSize],
318                         p.bufR[:S20BS+len(data)-TagSize-NonceSize],
319                         data[len(data)-NonceSize:],
320                         p.Key,
321                 )
322                 copy(p.keyAuthR[:], p.bufR[:SSize])
323                 copy(p.tagR[:], data[:TagSize])
324                 if !poly1305.Verify(p.tagR, data[TagSize:], p.keyAuthR) {
325                         p.FramesUnauth++
326                         p.BusyR.Unlock()
327                         return false
328                 }
329                 out = p.bufR[S20BS : S20BS+len(data)-TagSize-NonceSize]
330         }
331
332         // Check if received nonce is known to us in either of two buckets.
333         // If yes, then this is ignored duplicate.
334         // Check from the oldest bucket, as in most cases this will result
335         // in constant time check.
336         // If Bucket0 is filled, then it becomes Bucket1.
337         p.NonceCipher.Decrypt(
338                 data[len(data)-NonceSize:],
339                 data[len(data)-NonceSize:],
340         )
341         p.nonceRecv = binary.BigEndian.Uint64(data[len(data)-NonceSize:])
342         if reorderable {
343                 _, p.nonceFound0 = p.nonceBucket0[p.nonceRecv]
344                 _, p.nonceFound1 = p.nonceBucket1[p.nonceRecv]
345                 if p.nonceFound0 || p.nonceFound1 || p.nonceRecv+2*NonceBucketSize < p.nonceLatest {
346                         p.FramesDup++
347                         p.BusyR.Unlock()
348                         return false
349                 }
350                 p.nonceBucket0[p.nonceRecv] = struct{}{}
351                 p.nonceBucketN++
352                 if p.nonceBucketN == NonceBucketSize {
353                         p.nonceBucket1 = p.nonceBucket0
354                         p.nonceBucket0 = make(map[uint64]struct{}, NonceBucketSize)
355                         p.nonceBucketN = 0
356                 }
357         } else {
358                 if p.nonceRecv != p.NonceExpect {
359                         p.FramesDup++
360                         p.BusyR.Unlock()
361                         return false
362                 }
363                 p.NonceExpect += 2
364         }
365         if p.nonceRecv > p.nonceLatest {
366                 p.nonceLatest = p.nonceRecv
367         }
368
369         p.FramesIn++
370         atomic.AddInt64(&p.BytesIn, int64(len(data)))
371         p.LastPing = time.Now()
372         p.pktSizeR = bytes.LastIndexByte(out, PadByte)
373         if p.pktSizeR == -1 {
374                 p.BusyR.Unlock()
375                 return false
376         }
377         // Validate the pad
378         for i := p.pktSizeR + 1; i < len(out); i++ {
379                 if out[i] != 0 {
380                         p.BusyR.Unlock()
381                         return false
382                 }
383         }
384
385         if p.pktSizeR == 0 {
386                 p.HeartbeatRecv++
387                 p.BusyR.Unlock()
388                 return true
389         }
390         p.BytesPayloadIn += int64(p.pktSizeR)
391         tap.Write(out[:p.pktSizeR])
392         p.BusyR.Unlock()
393         return true
394 }