import (
"encoding/binary"
"io"
- "net"
"time"
"golang.org/x/crypto/poly1305"
)
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)
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
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:"-"`
nonceRecv uint64
frame []byte
nonce []byte
- pktSize uint64
+ pktSize uint16
size int
now time.Time
}
func (p *Peer) String() string {
- return p.Id.String() + ":" + p.Addr.String()
+ return p.Id.String() + ":" + p.Addr
}
// Zero peer's memory state.
}
var (
- Emptiness = make([]byte, 1<<14)
- taps = make(map[string]*TAP)
+ taps = make(map[string]*TAP)
)
// Create TAP listening goroutine.
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(
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)
}
peer := Peer{
Addr: addr,
+ Conn: conn,
Timeout: timeout,
Established: now,
LastPing: now,
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)
}
}
p.LastSent = p.now
- conn.WriteToUDP(append(p.frame, p.tag[:]...), p.Addr)
+ p.Conn.Write(append(p.frame, p.tag[:]...))
}