]> Cypherpunks.ru repositories - govpn.git/blob - src/govpn/peer.go
7366ffd642da84cec01a7ec76dc361667286855a
[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 newPeer(isClient bool, addr string, conn io.Writer, conf *PeerConf, key *[SSize]byte) *Peer {
122         now := time.Now()
123         timeout := conf.Timeout
124
125         cprCycle := cprCycleCalculate(conf.CPR)
126         noiseEnable := conf.Noise
127         if conf.CPR > 0 {
128                 noiseEnable = true
129                 timeout = cprCycle
130         } else {
131                 timeout = timeout / TimeoutHeartbeat
132         }
133
134         peer := Peer{
135                 Addr: addr,
136                 Id:   conf.Id,
137                 Conn: conn,
138
139                 NoiseEnable: noiseEnable,
140                 CPR:         conf.CPR,
141                 CPRCycle:    cprCycle,
142
143                 Key:          key,
144                 NonceCipher:  newNonceCipher(key),
145                 nonceBucket0: make(map[uint64]struct{}, NonceBucketSize),
146                 nonceBucket1: make(map[uint64]struct{}, NonceBucketSize),
147
148                 Timeout:     timeout,
149                 Established: now,
150                 LastPing:    now,
151
152                 bufR:     make([]byte, S20BS+MTU+NonceSize),
153                 bufT:     make([]byte, S20BS+MTU+NonceSize),
154                 tagR:     new([TagSize]byte),
155                 tagT:     new([TagSize]byte),
156                 keyAuthR: new([SSize]byte),
157                 keyAuthT: new([SSize]byte),
158         }
159         if isClient {
160                 peer.nonceOur = 1
161                 peer.NonceExpect = 0 + 2
162         } else {
163                 peer.nonceOur = 0
164                 peer.NonceExpect = 1 + 2
165         }
166         return &peer
167
168 }
169
170 // Process incoming Ethernet packet.
171 // ready channel is TAPListen's synchronization channel used to tell him
172 // that he is free to receive new packets. Encrypted and authenticated
173 // packets will be sent to remote Peer side immediately.
174 func (p *Peer) EthProcess(data []byte) {
175         p.now = time.Now()
176         p.BusyT.Lock()
177
178         // Zero size is a heartbeat packet
179         if len(data) == 0 {
180                 // If this heartbeat is necessary
181                 if !p.LastSent.Add(p.Timeout).Before(p.now) {
182                         p.BusyT.Unlock()
183                         return
184                 }
185                 p.bufT[S20BS+0] = byte(0)
186                 p.bufT[S20BS+1] = byte(0)
187                 p.HeartbeatSent++
188         } else {
189                 // Copy payload to our internal buffer and we are ready to
190                 // accept the next one
191                 binary.BigEndian.PutUint16(
192                         p.bufT[S20BS:S20BS+PktSizeSize],
193                         uint16(len(data)),
194                 )
195                 copy(p.bufT[S20BS+PktSizeSize:], data)
196                 p.BytesPayloadOut += int64(len(data))
197         }
198
199         if p.NoiseEnable {
200                 p.frameT = p.bufT[S20BS : S20BS+MTU-TagSize]
201         } else {
202                 p.frameT = p.bufT[S20BS : S20BS+PktSizeSize+len(data)+NonceSize]
203         }
204         p.nonceOur += 2
205         binary.BigEndian.PutUint64(p.frameT[len(p.frameT)-NonceSize:], p.nonceOur)
206         p.NonceCipher.Encrypt(
207                 p.frameT[len(p.frameT)-NonceSize:],
208                 p.frameT[len(p.frameT)-NonceSize:],
209         )
210         for i := 0; i < SSize; i++ {
211                 p.bufT[i] = byte(0)
212         }
213         salsa20.XORKeyStream(
214                 p.bufT[:S20BS+len(p.frameT)-NonceSize],
215                 p.bufT[:S20BS+len(p.frameT)-NonceSize],
216                 p.frameT[len(p.frameT)-NonceSize:],
217                 p.Key,
218         )
219
220         copy(p.keyAuthT[:], p.bufT[:SSize])
221         poly1305.Sum(p.tagT, p.frameT, p.keyAuthT)
222
223         atomic.AddInt64(&p.BytesOut, int64(len(p.frameT)+TagSize))
224         p.FramesOut++
225
226         if p.CPRCycle != time.Duration(0) {
227                 p.willSentCycle = p.LastSent.Add(p.CPRCycle)
228                 if p.willSentCycle.After(p.now) {
229                         time.Sleep(p.willSentCycle.Sub(p.now))
230                         p.now = p.willSentCycle
231                 }
232         }
233
234         p.LastSent = p.now
235         p.Conn.Write(append(p.tagT[:], p.frameT...))
236         p.BusyT.Unlock()
237 }
238
239 func (p *Peer) PktProcess(data []byte, tap io.Writer, reorderable bool) bool {
240         p.BusyR.Lock()
241         for i := 0; i < SSize; i++ {
242                 p.bufR[i] = byte(0)
243         }
244         copy(p.bufR[S20BS:], data[TagSize:])
245         salsa20.XORKeyStream(
246                 p.bufR[:S20BS+len(data)-TagSize-NonceSize],
247                 p.bufR[:S20BS+len(data)-TagSize-NonceSize],
248                 data[len(data)-NonceSize:],
249                 p.Key,
250         )
251
252         copy(p.keyAuthR[:], p.bufR[:SSize])
253         copy(p.tagR[:], data[:TagSize])
254         if !poly1305.Verify(p.tagR, data[TagSize:], p.keyAuthR) {
255                 p.FramesUnauth++
256                 p.BusyR.Unlock()
257                 return false
258         }
259
260         // Check if received nonce is known to us in either of two buckets.
261         // If yes, then this is ignored duplicate.
262         // Check from the oldest bucket, as in most cases this will result
263         // in constant time check.
264         // If Bucket0 is filled, then it becomes Bucket1.
265         p.NonceCipher.Decrypt(
266                 data[len(data)-NonceSize:],
267                 data[len(data)-NonceSize:],
268         )
269         p.nonceRecv = binary.BigEndian.Uint64(data[len(data)-NonceSize:])
270         if reorderable {
271                 _, p.nonceFound0 = p.nonceBucket0[p.nonceRecv]
272                 _, p.nonceFound1 = p.nonceBucket1[p.nonceRecv]
273                 if p.nonceFound0 || p.nonceFound1 || p.nonceRecv+2*NonceBucketSize < p.nonceLatest {
274                         p.FramesDup++
275                         p.BusyR.Unlock()
276                         return false
277                 }
278                 p.nonceBucket0[p.nonceRecv] = struct{}{}
279                 p.nonceBucketN++
280                 if p.nonceBucketN == NonceBucketSize {
281                         p.nonceBucket1 = p.nonceBucket0
282                         p.nonceBucket0 = make(map[uint64]struct{}, NonceBucketSize)
283                         p.nonceBucketN = 0
284                 }
285         } else {
286                 if p.nonceRecv != p.NonceExpect {
287                         p.FramesDup++
288                         p.BusyR.Unlock()
289                         return false
290                 }
291                 p.NonceExpect += 2
292         }
293         if p.nonceRecv > p.nonceLatest {
294                 p.nonceLatest = p.nonceRecv
295         }
296
297         p.FramesIn++
298         atomic.AddInt64(&p.BytesIn, int64(len(data)))
299         p.LastPing = time.Now()
300         p.pktSizeR = binary.BigEndian.Uint16(p.bufR[S20BS : S20BS+PktSizeSize])
301
302         if p.pktSizeR == 0 {
303                 p.HeartbeatRecv++
304                 p.BusyR.Unlock()
305                 return true
306         }
307         p.BytesPayloadIn += int64(p.pktSizeR)
308         tap.Write(p.bufR[S20BS+PktSizeSize : S20BS+PktSizeSize+p.pktSizeR])
309         p.BusyR.Unlock()
310         return true
311 }