]> Cypherpunks.ru repositories - govpn.git/blobdiff - src/cypherpunks.ru/govpn/peer.go
Replace (X)Salsa20 with ChaCha20
[govpn.git] / src / cypherpunks.ru / govpn / peer.go
index 5ea245bbd44a02aa196c66801b3bd22b4c3f19f3..22c893c80fb51d3bb0bb1affbfa395e32a137257 100644 (file)
@@ -20,6 +20,7 @@ package govpn
 
 import (
        "bytes"
+       "crypto/subtle"
        "encoding/binary"
        "io"
        "log"
@@ -27,19 +28,19 @@ import (
        "sync/atomic"
        "time"
 
+       "chacha20"
+       "golang.org/x/crypto/blake2b"
        "golang.org/x/crypto/poly1305"
-       "golang.org/x/crypto/salsa20"
-       "golang.org/x/crypto/xtea"
 )
 
 const (
        NonceSize       = 8
-       NonceBucketSize = 128
+       NonceBucketSize = 256
        TagSize         = poly1305.TagSize
-       // S20BS is Salsa20's internal blocksize in bytes
+       // S20BS is ChaCha20's internal blocksize in bytes
        S20BS = 64
        // Maximal amount of bytes transfered with single key (4 GiB)
-       MaxBytesPerKey int64 = 1 << 32
+       MaxBytesPerKey uint64 = 1 << 32
        // Heartbeat rate, relative to Timeout
        TimeoutHeartbeat = 4
        // Minimal valid packet length
@@ -48,25 +49,45 @@ const (
        PadByte = byte(0x80)
 )
 
-func newNonceCipher(key *[32]byte) *xtea.Cipher {
-       nonceKey := make([]byte, 16)
-       salsa20.XORKeyStream(
-               nonceKey,
-               make([]byte, 32),
-               make([]byte, xtea.BlockSize),
-               key,
-       )
-       ciph, err := xtea.NewCipher(nonceKey)
+func newNonces(key *[32]byte, i uint64) chan *[NonceSize]byte {
+       macKey := make([]byte, 32)
+       chacha20.XORKeyStream(macKey, make([]byte, 32), new([16]byte), key)
+       mac, err := blake2b.New256(macKey)
        if err != nil {
                panic(err)
        }
-       return ciph
+       nonces := make(chan *[NonceSize]byte, NonceBucketSize*3)
+       go func() {
+               for {
+                       buf := new([NonceSize]byte)
+                       binary.BigEndian.PutUint64(buf[:], i)
+                       mac.Write(buf[:])
+                       mac.Sum(buf[:0])
+                       nonces <- buf
+                       mac.Reset()
+                       i += 2
+               }
+       }()
+       return nonces
 }
 
 type Peer struct {
+       // Statistics (they are at the beginning for correct int64 alignment)
+       BytesIn         uint64
+       BytesOut        uint64
+       BytesPayloadIn  uint64
+       BytesPayloadOut uint64
+       FramesIn        uint64
+       FramesOut       uint64
+       FramesUnauth    uint64
+       FramesDup       uint64
+       HeartbeatRecv   uint64
+       HeartbeatSent   uint64
+
+       // Basic
        Addr string
        Id   *PeerId
-       Conn io.Writer
+       Conn io.Writer `json:"-"`
 
        // Traffic behaviour
        NoiseEnable bool
@@ -75,52 +96,40 @@ type Peer struct {
        Encless     bool
        MTU         int
 
-       // Cryptography related
-       Key          *[SSize]byte `json:"-"`
-       NonceCipher  *xtea.Cipher `json:"-"`
-       nonceRecv    uint64
-       nonceLatest  uint64
-       nonceOur     uint64
-       NonceExpect  uint64 `json:"-"`
-       nonceBucket0 map[uint64]struct{}
-       nonceBucket1 map[uint64]struct{}
-       nonceFound0  bool
-       nonceFound1  bool
-       nonceBucketN int32
+       key *[SSize]byte `json:"-"`
 
        // Timers
-       Timeout       time.Duration `json:"-"`
-       Established   time.Time
-       LastPing      time.Time
-       LastSent      time.Time
-       willSentCycle time.Time
-
-       // Statistics
-       BytesIn         int64
-       BytesOut        int64
-       BytesPayloadIn  int64
-       BytesPayloadOut int64
-       FramesIn        int
-       FramesOut       int
-       FramesUnauth    int
-       FramesDup       int
-       HeartbeatRecv   int
-       HeartbeatSent   int
+       Timeout     time.Duration `json:"-"`
+       Established time.Time
+       LastPing    time.Time
 
        // Receiver
        BusyR    sync.Mutex `json:"-"`
        bufR     []byte
        tagR     *[TagSize]byte
        keyAuthR *[SSize]byte
+       nonceR   *[16]byte
        pktSizeR int
 
+       // UDP-related
+       noncesR      chan *[NonceSize]byte
+       nonceRecv    [NonceSize]byte
+       nonceBucketL map[[NonceSize]byte]struct{}
+       nonceBucketM map[[NonceSize]byte]struct{}
+       nonceBucketH map[[NonceSize]byte]struct{}
+
+       // TCP-related
+       NonceExpect  []byte `json:"-"`
+       noncesExpect chan *[NonceSize]byte
+
        // Transmitter
        BusyT    sync.Mutex `json:"-"`
        bufT     []byte
        tagT     *[TagSize]byte
        keyAuthT *[SSize]byte
+       nonceT   *[16]byte
        frameT   []byte
-       now      time.Time
+       noncesT  chan *[NonceSize]byte
 }
 
 func (p *Peer) String() string {
@@ -131,7 +140,7 @@ func (p *Peer) String() string {
 func (p *Peer) Zero() {
        p.BusyT.Lock()
        p.BusyR.Lock()
-       SliceZero(p.Key[:])
+       SliceZero(p.key[:])
        SliceZero(p.bufR)
        SliceZero(p.bufT)
        SliceZero(p.keyAuthR[:])
@@ -140,11 +149,6 @@ func (p *Peer) Zero() {
        p.BusyR.Unlock()
 }
 
-func (p *Peer) NonceExpectation(buf []byte) {
-       binary.BigEndian.PutUint64(buf, p.NonceExpect)
-       p.NonceCipher.Encrypt(buf, buf)
-}
-
 func cprCycleCalculate(conf *PeerConf) time.Duration {
        if conf.CPR == 0 {
                return time.Duration(0)
@@ -176,6 +180,7 @@ func newPeer(isClient bool, addr string, conn io.Writer, conf *PeerConf, key *[S
                bufSize += EnclessEnlargeSize
                noiseEnable = true
        }
+
        peer := Peer{
                Addr: addr,
                Id:   conf.Id,
@@ -187,10 +192,7 @@ func newPeer(isClient bool, addr string, conn io.Writer, conf *PeerConf, key *[S
                Encless:     conf.Encless,
                MTU:         conf.MTU,
 
-               Key:          key,
-               NonceCipher:  newNonceCipher(key),
-               nonceBucket0: make(map[uint64]struct{}, NonceBucketSize),
-               nonceBucket1: make(map[uint64]struct{}, NonceBucketSize),
+               key: key,
 
                Timeout:     timeout,
                Established: now,
@@ -201,17 +203,43 @@ func newPeer(isClient bool, addr string, conn io.Writer, conf *PeerConf, key *[S
                tagR:     new([TagSize]byte),
                tagT:     new([TagSize]byte),
                keyAuthR: new([SSize]byte),
+               nonceR:   new([16]byte),
                keyAuthT: new([SSize]byte),
+               nonceT:   new([16]byte),
        }
+
        if isClient {
-               peer.nonceOur = 1
-               peer.NonceExpect = 0 + 2
+               peer.noncesT = newNonces(peer.key, 1+2)
+               peer.noncesR = newNonces(peer.key, 0+2)
+               peer.noncesExpect = newNonces(peer.key, 0+2)
        } else {
-               peer.nonceOur = 0
-               peer.NonceExpect = 1 + 2
+               peer.noncesT = newNonces(peer.key, 0+2)
+               peer.noncesR = newNonces(peer.key, 1+2)
+               peer.noncesExpect = newNonces(peer.key, 1+2)
        }
-       return &peer
 
+       peer.NonceExpect = make([]byte, NonceSize)
+       nonce := <-peer.noncesExpect
+       copy(peer.NonceExpect, nonce[:])
+
+       var i int
+       peer.nonceBucketL = make(map[[NonceSize]byte]struct{}, NonceBucketSize)
+       for i = 0; i < NonceBucketSize; i++ {
+               nonce = <-peer.noncesR
+               peer.nonceBucketL[*nonce] = struct{}{}
+       }
+       peer.nonceBucketM = make(map[[NonceSize]byte]struct{}, NonceBucketSize)
+       for i = 0; i < NonceBucketSize; i++ {
+               nonce = <-peer.noncesR
+               peer.nonceBucketM[*nonce] = struct{}{}
+       }
+       peer.nonceBucketH = make(map[[NonceSize]byte]struct{}, NonceBucketSize)
+       for i = 0; i < NonceBucketSize; i++ {
+               nonce = <-peer.noncesR
+               peer.nonceBucketH[*nonce] = struct{}{}
+       }
+
+       return &peer
 }
 
 // Process incoming Ethernet packet.
@@ -223,17 +251,11 @@ func (p *Peer) EthProcess(data []byte) {
                log.Println("Padded data packet size", len(data)+1, "is bigger than MTU", p.MTU, p)
                return
        }
-       p.now = time.Now()
        p.BusyT.Lock()
 
        // Zero size is a heartbeat packet
        SliceZero(p.bufT)
        if len(data) == 0 {
-               // If this heartbeat is necessary
-               if !p.LastSent.Add(p.Timeout).Before(p.now) {
-                       p.BusyT.Unlock()
-                       return
-               }
                p.bufT[S20BS+0] = PadByte
                p.HeartbeatSent++
        } else {
@@ -241,7 +263,7 @@ func (p *Peer) EthProcess(data []byte) {
                // accept the next one
                copy(p.bufT[S20BS:], data)
                p.bufT[S20BS+len(data)] = PadByte
-               p.BytesPayloadOut += int64(len(data))
+               p.BytesPayloadOut += uint64(len(data))
        }
 
        if p.NoiseEnable && !p.Encless {
@@ -251,47 +273,29 @@ func (p *Peer) EthProcess(data []byte) {
        } else {
                p.frameT = p.bufT[S20BS : S20BS+len(data)+1+NonceSize]
        }
-       p.nonceOur += 2
-       binary.BigEndian.PutUint64(p.frameT[len(p.frameT)-NonceSize:], p.nonceOur)
-       p.NonceCipher.Encrypt(
-               p.frameT[len(p.frameT)-NonceSize:],
-               p.frameT[len(p.frameT)-NonceSize:],
-       )
+       copy(p.frameT[len(p.frameT)-NonceSize:], (<-p.noncesT)[:])
        var out []byte
+       copy(p.nonceT[8:], p.frameT[len(p.frameT)-NonceSize:])
        if p.Encless {
                var err error
-               out, err = EnclessEncode(
-                       p.Key,
-                       p.frameT[len(p.frameT)-NonceSize:],
-                       p.frameT[:len(p.frameT)-NonceSize],
-               )
+               out, err = EnclessEncode(p.key, p.nonceT, p.frameT[:len(p.frameT)-NonceSize])
                if err != nil {
                        panic(err)
                }
                out = append(out, p.frameT[len(p.frameT)-NonceSize:]...)
        } else {
-               salsa20.XORKeyStream(
+               chacha20.XORKeyStream(
                        p.bufT[:S20BS+len(p.frameT)-NonceSize],
                        p.bufT[:S20BS+len(p.frameT)-NonceSize],
-                       p.frameT[len(p.frameT)-NonceSize:],
-                       p.Key,
+                       p.nonceT,
+                       p.key,
                )
                copy(p.keyAuthT[:], p.bufT[:SSize])
                poly1305.Sum(p.tagT, p.frameT, p.keyAuthT)
-               atomic.AddInt64(&p.BytesOut, int64(len(p.frameT)+TagSize))
+               atomic.AddUint64(&p.BytesOut, uint64(len(p.frameT)+TagSize))
                out = append(p.tagT[:], p.frameT...)
        }
        p.FramesOut++
-
-       if p.CPRCycle != time.Duration(0) {
-               p.willSentCycle = p.LastSent.Add(p.CPRCycle)
-               if p.willSentCycle.After(p.now) {
-                       time.Sleep(p.willSentCycle.Sub(p.now))
-                       p.now = p.willSentCycle
-               }
-       }
-
-       p.LastSent = p.now
        p.Conn.Write(out)
        p.BusyT.Unlock()
 }
@@ -305,13 +309,10 @@ func (p *Peer) PktProcess(data []byte, tap io.Writer, reorderable bool) bool {
        }
        var out []byte
        p.BusyR.Lock()
+       copy(p.nonceR[8:], data[len(data)-NonceSize:])
        if p.Encless {
                var err error
-               out, err = EnclessDecode(
-                       p.Key,
-                       data[len(data)-NonceSize:],
-                       data[:len(data)-NonceSize],
-               )
+               out, err = EnclessDecode(p.key, p.nonceR, data[:len(data)-NonceSize])
                if err != nil {
                        p.FramesUnauth++
                        p.BusyR.Unlock()
@@ -322,11 +323,11 @@ func (p *Peer) PktProcess(data []byte, tap io.Writer, reorderable bool) bool {
                        p.bufR[i] = 0
                }
                copy(p.bufR[S20BS:], data[TagSize:])
-               salsa20.XORKeyStream(
+               chacha20.XORKeyStream(
                        p.bufR[:S20BS+len(data)-TagSize-NonceSize],
                        p.bufR[:S20BS+len(data)-TagSize-NonceSize],
-                       data[len(data)-NonceSize:],
-                       p.Key,
+                       p.nonceR,
+                       p.key,
                )
                copy(p.keyAuthR[:], p.bufR[:SSize])
                copy(p.tagR[:], data[:TagSize])
@@ -338,45 +339,49 @@ func (p *Peer) PktProcess(data []byte, tap io.Writer, reorderable bool) bool {
                out = p.bufR[S20BS : S20BS+len(data)-TagSize-NonceSize]
        }
 
-       // Check if received nonce is known to us in either of two buckets.
-       // If yes, then this is ignored duplicate.
-       // Check from the oldest bucket, as in most cases this will result
-       // in constant time check.
-       // If Bucket0 is filled, then it becomes Bucket1.
-       p.NonceCipher.Decrypt(
-               data[len(data)-NonceSize:],
-               data[len(data)-NonceSize:],
-       )
-       p.nonceRecv = binary.BigEndian.Uint64(data[len(data)-NonceSize:])
        if reorderable {
-               _, p.nonceFound0 = p.nonceBucket0[p.nonceRecv]
-               _, p.nonceFound1 = p.nonceBucket1[p.nonceRecv]
-               if p.nonceFound0 || p.nonceFound1 || p.nonceRecv+2*NonceBucketSize < p.nonceLatest {
+               copy(p.nonceRecv[:], data[len(data)-NonceSize:])
+               _, foundL := p.nonceBucketL[p.nonceRecv]
+               _, foundM := p.nonceBucketM[p.nonceRecv]
+               _, foundH := p.nonceBucketH[p.nonceRecv]
+               // If found is none of buckets: either it is too old,
+               // or too new (many packets were lost)
+               if !(foundL || foundM || foundH) {
                        p.FramesDup++
                        p.BusyR.Unlock()
                        return false
                }
-               p.nonceBucket0[p.nonceRecv] = struct{}{}
-               p.nonceBucketN++
-               if p.nonceBucketN == NonceBucketSize {
-                       p.nonceBucket1 = p.nonceBucket0
-                       p.nonceBucket0 = make(map[uint64]struct{}, NonceBucketSize)
-                       p.nonceBucketN = 0
+               // Delete seen nonce
+               if foundL {
+                       delete(p.nonceBucketL, p.nonceRecv)
+               }
+               if foundM {
+                       delete(p.nonceBucketM, p.nonceRecv)
+               }
+               if foundH {
+                       delete(p.nonceBucketH, p.nonceRecv)
+               }
+               // If we are dealing with the latest bucket, create the new one
+               if foundH {
+                       p.nonceBucketL, p.nonceBucketM = p.nonceBucketM, p.nonceBucketH
+                       p.nonceBucketH = make(map[[NonceSize]byte]struct{})
+                       var nonce *[NonceSize]byte
+                       for i := 0; i < NonceBucketSize; i++ {
+                               nonce = <-p.noncesR
+                               p.nonceBucketH[*nonce] = struct{}{}
+                       }
                }
        } else {
-               if p.nonceRecv != p.NonceExpect {
+               if subtle.ConstantTimeCompare(data[len(data)-NonceSize:], p.NonceExpect) != 1 {
                        p.FramesDup++
                        p.BusyR.Unlock()
                        return false
                }
-               p.NonceExpect += 2
-       }
-       if p.nonceRecv > p.nonceLatest {
-               p.nonceLatest = p.nonceRecv
+               copy(p.NonceExpect, (<-p.noncesExpect)[:])
        }
 
        p.FramesIn++
-       atomic.AddInt64(&p.BytesIn, int64(len(data)))
+       atomic.AddUint64(&p.BytesIn, uint64(len(data)))
        p.LastPing = time.Now()
        p.pktSizeR = bytes.LastIndexByte(out, PadByte)
        if p.pktSizeR == -1 {
@@ -396,8 +401,52 @@ func (p *Peer) PktProcess(data []byte, tap io.Writer, reorderable bool) bool {
                p.BusyR.Unlock()
                return true
        }
-       p.BytesPayloadIn += int64(p.pktSizeR)
+       p.BytesPayloadIn += uint64(p.pktSizeR)
        tap.Write(out[:p.pktSizeR])
        p.BusyR.Unlock()
        return true
 }
+
+func PeerTapProcessor(peer *Peer, tap *TAP, terminator chan struct{}) {
+       var data []byte
+       var now time.Time
+       lastSent := time.Now()
+       heartbeat := time.NewTicker(peer.Timeout)
+       if peer.CPRCycle == time.Duration(0) {
+       RawProcessor:
+               for {
+                       select {
+                       case <-terminator:
+                               break RawProcessor
+                       case <-heartbeat.C:
+                               now = time.Now()
+                               if lastSent.Add(peer.Timeout).Before(now) {
+                                       peer.EthProcess(nil)
+                                       lastSent = now
+                               }
+                       case data = <-tap.Sink:
+                               peer.EthProcess(data)
+                               lastSent = time.Now()
+                       }
+               }
+       } else {
+       CPRProcessor:
+               for {
+                       data = nil
+                       select {
+                       case <-terminator:
+                               break CPRProcessor
+                       case data = <-tap.Sink:
+                               peer.EthProcess(data)
+                       default:
+                       }
+                       if data == nil {
+                               peer.EthProcess(nil)
+                       }
+                       time.Sleep(peer.CPRCycle)
+               }
+       }
+       close(terminator)
+       peer.Zero()
+       heartbeat.Stop()
+}