import (
"encoding/binary"
"io"
- "net"
"time"
"golang.org/x/crypto/poly1305"
TimeoutHeartbeat = 4
)
+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
NonceOur uint64 `json:"-"`
NonceRecv uint64 `json:"-"`
NonceCipher *xtea.Cipher `json:"-"`
+ nonceExpect uint64 `json:"-"`
nonceBucket0 map[uint64]struct{}
nonceBucket1 map[uint64]struct{}
nonceFound bool
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 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)
timeout = timeout / TimeoutHeartbeat
}
peer := Peer{
- Addr: addr,
- Timeout: timeout,
- Established: now,
- LastPing: now,
- Id: conf.Id,
- NoiseEnable: noiseEnable,
- CPR: conf.CPR,
- CPRCycle: cprCycle,
- NonceOur: uint64(nonce),
- NonceRecv: uint64(0),
- nonceBucket0: make(map[uint64]struct{}, NonceBucketSize),
- nonceBucket1: make(map[uint64]struct{}, NonceBucketSize),
- Key: key,
- NonceCipher: newNonceCipher(key),
- buf: make([]byte, MTU+S20BS),
- tag: new([poly1305.TagSize]byte),
- keyAuth: new([SSize]byte),
- nonce: make([]byte, NonceSize),
+ Addr: addr,
+ Conn: conn,
+ Timeout: timeout,
+ Established: now,
+ LastPing: now,
+ Id: conf.Id,
+ NoiseEnable: noiseEnable,
+ CPR: conf.CPR,
+ CPRCycle: cprCycle,
+ NonceRecv: 0,
+ Key: key,
+ NonceCipher: newNonceCipher(key),
+ 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
}
// 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.buf, udpPkt[:NonceSize])
- ready <- struct{}{}
- p.nonceRecv, _ = binary.Uvarint(p.buf[:NonceSize])
- 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
+ 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
}
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[:]...))
}