]> Cypherpunks.ru repositories - govpn.git/blobdiff - src/govpn/transport.go
[DOC] Move 'in the media' section lower
[govpn.git] / src / govpn / transport.go
index f46eeefb62b3aae5c9af6fbebe46705d9688e1b7..9c7a73328edb2fef7d8e81ec155e58a22bdf51d8 100644 (file)
@@ -21,7 +21,6 @@ package govpn
 import (
        "encoding/binary"
        "io"
-       "net"
        "time"
 
        "golang.org/x/crypto/poly1305"
@@ -30,7 +29,8 @@ import (
 )
 
 const (
-       NonceSize = 8
+       NonceSize       = 8
+       NonceBucketSize = 128
        // S20BS is Salsa20's internal blocksize in bytes
        S20BS = 64
        // Maximal amount of bytes transfered with single key (4 GiB)
@@ -41,14 +41,16 @@ const (
        TimeoutHeartbeat = 4
 )
 
-type UDPPkt struct {
-       Addr *net.UDPAddr
-       Size int
+type RemoteConn interface {
+       io.Writer
+       // Can incoming packets be reordered
+       Reorderable() bool
 }
 
 type Peer struct {
-       Addr *net.UDPAddr
+       Addr string
        Id   *PeerId
+       Conn RemoteConn
 
        // Traffic behaviour
        NoiseEnable bool
@@ -56,11 +58,15 @@ type Peer struct {
        CPRCycle    time.Duration `json:"-"`
 
        // Cryptography related
-       Key         *[SSize]byte `json:"-"`
-       Noncediff   int
-       NonceOur    uint64       `json:"-"`
-       NonceRecv   uint64       `json:"-"`
-       NonceCipher *xtea.Cipher `json:"-"`
+       Key          *[SSize]byte `json:"-"`
+       NonceOur     uint64       `json:"-"`
+       NonceRecv    uint64       `json:"-"`
+       NonceCipher  *xtea.Cipher `json:"-"`
+       nonceExpect  uint64       `json:"-"`
+       nonceBucket0 map[uint64]struct{}
+       nonceBucket1 map[uint64]struct{}
+       nonceFound   bool
+       nonceBucketN int32
 
        // Timers
        Timeout       time.Duration `json:"-"`
@@ -76,7 +82,7 @@ type Peer struct {
        nonceRecv uint64
        frame     []byte
        nonce     []byte
-       pktSize   uint64
+       pktSize   uint16
        size      int
        now       time.Time
 
@@ -94,7 +100,7 @@ type Peer struct {
 }
 
 func (p *Peer) String() string {
-       return p.Id.String() + ":" + p.Addr.String()
+       return p.Id.String() + ":" + p.Addr
 }
 
 // Zero peer's memory state.
@@ -108,8 +114,7 @@ func (p *Peer) Zero() {
 }
 
 var (
-       Emptiness = make([]byte, 1<<14)
-       taps      = make(map[string]*TAP)
+       taps = make(map[string]*TAP)
 )
 
 // Create TAP listening goroutine.
@@ -180,35 +185,6 @@ func TAPListen(ifaceName string, timeout time.Duration, cpr int) (*TAP, chan []b
        return tap, sink, sinkReady, sinkTerminate, nil
 }
 
-// Create UDP listening goroutine.
-// This function takes already listening UDP socket and a buffer where
-// all UDP packet data will be saved, channel where information about
-// remote address and number of written bytes are stored, and a channel
-// used to tell that buffer is ready to be overwritten.
-func ConnListen(conn *net.UDPConn) (chan UDPPkt, []byte, chan struct{}) {
-       buf := make([]byte, MTU)
-       sink := make(chan UDPPkt)
-       sinkReady := make(chan struct{})
-       go func(conn *net.UDPConn) {
-               var n int
-               var addr *net.UDPAddr
-               var err error
-               for {
-                       <-sinkReady
-                       conn.SetReadDeadline(time.Now().Add(time.Second))
-                       n, addr, err = conn.ReadFromUDP(buf)
-                       if err != nil {
-                               // This is needed for ticking the timeouts counter outside
-                               sink <- UDPPkt{nil, 0}
-                               continue
-                       }
-                       sink <- UDPPkt{addr, n}
-               }
-       }(conn)
-       sinkReady <- struct{}{}
-       return sink, buf, sinkReady
-}
-
 func newNonceCipher(key *[32]byte) *xtea.Cipher {
        nonceKey := make([]byte, 16)
        salsa20.XORKeyStream(
@@ -231,7 +207,7 @@ func cprCycleCalculate(rate int) time.Duration {
        return time.Second / time.Duration(rate*(1<<10)/MTU)
 }
 
-func newPeer(addr *net.UDPAddr, conf *PeerConf, nonce int, key *[SSize]byte) *Peer {
+func newPeer(isClient bool, addr string, conn RemoteConn, conf *PeerConf, key *[SSize]byte) *Peer {
        now := time.Now()
        timeout := conf.Timeout
        cprCycle := cprCycleCalculate(conf.CPR)
@@ -244,6 +220,7 @@ func newPeer(addr *net.UDPAddr, conf *PeerConf, nonce int, key *[SSize]byte) *Pe
        }
        peer := Peer{
                Addr:        addr,
+               Conn:        conn,
                Timeout:     timeout,
                Established: now,
                LastPing:    now,
@@ -251,103 +228,140 @@ func newPeer(addr *net.UDPAddr, conf *PeerConf, nonce int, key *[SSize]byte) *Pe
                NoiseEnable: noiseEnable,
                CPR:         conf.CPR,
                CPRCycle:    cprCycle,
-               Noncediff:   conf.Noncediff,
-               NonceOur:    uint64(conf.Noncediff + nonce),
-               NonceRecv:   uint64(conf.Noncediff + 0),
+               NonceRecv:   0,
                Key:         key,
                NonceCipher: newNonceCipher(key),
-               buf:         make([]byte, MTU+S20BS),
+               buf:         make([]byte, MTU+S20BS+NonceSize+poly1305.TagSize),
                tag:         new([poly1305.TagSize]byte),
                keyAuth:     new([SSize]byte),
                nonce:       make([]byte, NonceSize),
        }
+       if isClient {
+               peer.NonceOur = 1
+               peer.nonceExpect = 0 + 2
+       } else {
+               peer.NonceOur = 0
+               peer.nonceExpect = 1 + 2
+       }
+       if conn.Reorderable() {
+               peer.nonceBucket0 = make(map[uint64]struct{}, NonceBucketSize)
+               peer.nonceBucket1 = make(map[uint64]struct{}, NonceBucketSize)
+       }
        return &peer
 }
 
 // Process incoming UDP packet.
-// udpPkt is received data, related to the peer tap interface and
 // ConnListen'es synchronization channel used to tell him that he is
 // free to receive new packets. Authenticated and decrypted packets
 // will be written to the interface immediately (except heartbeat ones).
-func (p *Peer) UDPProcess(udpPkt []byte, tap io.Writer, ready chan struct{}) bool {
-       p.size = len(udpPkt)
-       copy(p.buf, Emptiness)
-       copy(p.tag[:], udpPkt[p.size-poly1305.TagSize:])
-       copy(p.buf[S20BS:], udpPkt[NonceSize:p.size-poly1305.TagSize])
+func (p *Peer) PktProcess(data []byte, tap io.Writer, ready chan struct{}) bool {
+       p.size = len(data)
+       p.frame = make([]byte, p.size)
+       copy(p.frame, data)
+       ready <- struct{}{}
+
+       copy(p.buf[S20BS:], p.frame[:p.size-NonceSize-poly1305.TagSize])
+       for i := 0; i < S20BS; i++ {
+               p.buf[i] = byte(0)
+       }
        salsa20.XORKeyStream(
-               p.buf[:S20BS+p.size-poly1305.TagSize],
-               p.buf[:S20BS+p.size-poly1305.TagSize],
-               udpPkt[:NonceSize],
+               p.buf[:S20BS+p.size-NonceSize-poly1305.TagSize],
+               p.buf[:S20BS+p.size-NonceSize-poly1305.TagSize],
+               p.frame[p.size-NonceSize-poly1305.TagSize:p.size-poly1305.TagSize],
                p.Key,
        )
+       copy(p.tag[:], p.frame[p.size-poly1305.TagSize:])
        copy(p.keyAuth[:], p.buf[:SSize])
-       if !poly1305.Verify(p.tag, udpPkt[:p.size-poly1305.TagSize], p.keyAuth) {
-               ready <- struct{}{}
+       if !poly1305.Verify(p.tag, p.frame[:p.size-poly1305.TagSize], p.keyAuth) {
                p.FramesUnauth++
                return false
        }
-       p.NonceCipher.Decrypt(p.buf, udpPkt[:NonceSize])
-       p.nonceRecv, _ = binary.Uvarint(p.buf[:NonceSize])
-       if int(p.NonceRecv)-p.Noncediff >= 0 && int(p.nonceRecv) < int(p.NonceRecv)-p.Noncediff {
-               ready <- struct{}{}
-               p.FramesDup++
-               return false
+
+       // 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(
+               p.nonce,
+               p.frame[p.size-NonceSize-poly1305.TagSize:p.size-poly1305.TagSize],
+       )
+
+       p.nonceRecv = binary.BigEndian.Uint64(p.nonce)
+       if p.Conn.Reorderable() {
+               if _, p.nonceFound = p.nonceBucket1[p.NonceRecv]; p.nonceFound {
+                       p.FramesDup++
+                       return false
+               }
+               if _, p.nonceFound = p.nonceBucket0[p.NonceRecv]; p.nonceFound {
+                       p.FramesDup++
+                       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
+               }
+       } else {
+               if p.nonceRecv != p.nonceExpect {
+                       p.FramesDup++
+                       return false
+               }
+               p.nonceExpect += 2
        }
-       ready <- struct{}{}
+
        p.FramesIn++
        p.BytesIn += int64(p.size)
        p.LastPing = time.Now()
        p.NonceRecv = p.nonceRecv
-       p.pktSize, _ = binary.Uvarint(p.buf[S20BS : S20BS+PktSizeSize])
+       p.pktSize = binary.BigEndian.Uint16(p.buf[S20BS : S20BS+PktSizeSize])
        if p.pktSize == 0 {
                p.HeartbeatRecv++
                return true
        }
-       p.frame = p.buf[S20BS+PktSizeSize : S20BS+PktSizeSize+p.pktSize]
        p.BytesPayloadIn += int64(p.pktSize)
-       tap.Write(p.frame)
+       tap.Write(p.buf[S20BS+PktSizeSize : S20BS+PktSizeSize+p.pktSize])
        return true
 }
 
-type WriteToUDPer interface {
-       WriteToUDP([]byte, *net.UDPAddr) (int, error)
-}
-
 // Process incoming Ethernet packet.
-// ethPkt is received data, conn is our outgoing connection.
 // ready channel is TAPListen's synchronization channel used to tell him
 // that he is free to receive new packets. Encrypted and authenticated
 // packets will be sent to remote Peer side immediately.
-func (p *Peer) EthProcess(ethPkt []byte, conn WriteToUDPer, ready chan struct{}) {
+func (p *Peer) EthProcess(data []byte, ready chan struct{}) {
        p.now = time.Now()
-       p.size = len(ethPkt)
+       p.size = len(data)
        // If this heartbeat is necessary
        if p.size == 0 && !p.LastSent.Add(p.Timeout).Before(p.now) {
                return
        }
-       copy(p.buf, Emptiness)
        if p.size > 0 {
-               copy(p.buf[S20BS+PktSizeSize:], ethPkt)
+               copy(p.buf[S20BS+PktSizeSize:], data)
                ready <- struct{}{}
-               binary.PutUvarint(p.buf[S20BS:S20BS+PktSizeSize], uint64(p.size))
+               binary.BigEndian.PutUint16(p.buf[S20BS:S20BS+PktSizeSize], uint16(p.size))
                p.BytesPayloadOut += int64(p.size)
        } else {
+               p.buf[S20BS+0] = byte(0)
+               p.buf[S20BS+1] = byte(0)
                p.HeartbeatSent++
        }
 
        p.NonceOur += 2
-       copy(p.nonce, Emptiness)
-       binary.PutUvarint(p.nonce, p.NonceOur)
+       binary.BigEndian.PutUint64(p.nonce, p.NonceOur)
        p.NonceCipher.Encrypt(p.nonce, p.nonce)
 
+       for i := 0; i < S20BS; i++ {
+               p.buf[i] = byte(0)
+       }
        salsa20.XORKeyStream(p.buf, p.buf, p.nonce, p.Key)
-       copy(p.buf[S20BS-NonceSize:S20BS], p.nonce)
-       copy(p.keyAuth[:], p.buf[:SSize])
        if p.NoiseEnable {
-               p.frame = p.buf[S20BS-NonceSize : S20BS+MTU-NonceSize-poly1305.TagSize]
+               p.frame = append(p.buf[S20BS:S20BS+MTU-NonceSize-poly1305.TagSize], p.nonce...)
        } else {
-               p.frame = p.buf[S20BS-NonceSize : S20BS+PktSizeSize+p.size]
+               p.frame = append(p.buf[S20BS:S20BS+PktSizeSize+p.size], p.nonce...)
        }
+       copy(p.keyAuth[:], p.buf[:SSize])
        poly1305.Sum(p.tag, p.frame, p.keyAuth)
 
        p.BytesOut += int64(len(p.frame) + poly1305.TagSize)
@@ -361,5 +375,5 @@ func (p *Peer) EthProcess(ethPkt []byte, conn WriteToUDPer, ready chan struct{})
                }
        }
        p.LastSent = p.now
-       conn.WriteToUDP(append(p.frame, p.tag[:]...), p.Addr)
+       p.Conn.Write(append(p.frame, p.tag[:]...))
 }