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