116 lines
4.2 KiB
Go
116 lines
4.2 KiB
Go
package udp
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"encoding/binary"
|
|
"hash"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/minio/sha256-simd"
|
|
|
|
"github.com/chihaya/chihaya/pkg/log"
|
|
)
|
|
|
|
// ttl is the duration a connection ID should be valid according to BEP 15.
|
|
const ttl = 2 * time.Minute
|
|
|
|
// NewConnectionID creates an 8-byte connection identifier for UDP packets as
|
|
// described by BEP 15.
|
|
// This is a wrapper around creating a new ConnectionIDGenerator and generating
|
|
// an ID. It is recommended to use the generator for performance.
|
|
func NewConnectionID(ip net.IP, now time.Time, key string) []byte {
|
|
return NewConnectionIDGenerator(key).Generate(ip, now)
|
|
}
|
|
|
|
// ValidConnectionID determines whether a connection identifier is legitimate.
|
|
// This is a wrapper around creating a new ConnectionIDGenerator and validating
|
|
// the ID. It is recommended to use the generator for performance.
|
|
func ValidConnectionID(connectionID []byte, ip net.IP, now time.Time, maxClockSkew time.Duration, key string) bool {
|
|
return NewConnectionIDGenerator(key).Validate(connectionID, ip, now, maxClockSkew)
|
|
}
|
|
|
|
// A ConnectionIDGenerator is a reusable generator and validator for connection
|
|
// IDs as described in BEP 15.
|
|
// It is not thread safe, but is safe to be pooled and reused by other
|
|
// goroutines. It manages its state itself, so it can be taken from and returned
|
|
// to a pool without any cleanup.
|
|
// After initial creation, it can generate connection IDs without allocating.
|
|
// See Generate and Validate for usage notes and guarantees.
|
|
type ConnectionIDGenerator struct {
|
|
// mac is a keyed HMAC that can be reused for subsequent connection ID
|
|
// generations.
|
|
mac hash.Hash
|
|
|
|
// connID is an 8-byte slice that holds the generated connection ID after a
|
|
// call to Generate.
|
|
// It must not be referenced after the generator is returned to a pool.
|
|
// It will be overwritten by subsequent calls to Generate.
|
|
connID []byte
|
|
|
|
// scratch is a 32-byte slice that is used as a scratchpad for the generated
|
|
// HMACs.
|
|
scratch []byte
|
|
}
|
|
|
|
// NewConnectionIDGenerator creates a new connection ID generator.
|
|
func NewConnectionIDGenerator(key string) *ConnectionIDGenerator {
|
|
return &ConnectionIDGenerator{
|
|
mac: hmac.New(sha256.New, []byte(key)),
|
|
connID: make([]byte, 8),
|
|
scratch: make([]byte, 32),
|
|
}
|
|
}
|
|
|
|
// reset resets the generator.
|
|
// This is called by other methods of the generator, it's not necessary to call
|
|
// it after getting a generator from a pool.
|
|
func (g *ConnectionIDGenerator) reset() {
|
|
g.mac.Reset()
|
|
g.connID = g.connID[:8]
|
|
g.scratch = g.scratch[:0]
|
|
}
|
|
|
|
// Generate generates an 8-byte connection ID as described in BEP 15 for the
|
|
// given IP and the current time.
|
|
//
|
|
// The first 4 bytes of the connection identifier is a unix timestamp and the
|
|
// last 4 bytes are a truncated HMAC token created from the aforementioned
|
|
// unix timestamp and the source IP address of the UDP packet.
|
|
//
|
|
// Truncated HMAC is known to be safe for 2^(-n) where n is the size in bits
|
|
// of the truncated HMAC token. In this use case we have 32 bits, thus a
|
|
// forgery probability of approximately 1 in 4 billion.
|
|
//
|
|
// The generated ID is written to g.connID, which is also returned. g.connID
|
|
// will be reused, so it must not be referenced after returning the generator
|
|
// to a pool and will be overwritten be subsequent calls to Generate!
|
|
func (g *ConnectionIDGenerator) Generate(ip net.IP, now time.Time) []byte {
|
|
g.reset()
|
|
|
|
binary.BigEndian.PutUint32(g.connID, uint32(now.Unix()))
|
|
|
|
g.mac.Write(g.connID[:4])
|
|
g.mac.Write(ip)
|
|
g.scratch = g.mac.Sum(g.scratch)
|
|
copy(g.connID[4:8], g.scratch[:4])
|
|
|
|
log.Debug("generated connection ID", log.Fields{"ip": ip, "now": now, "connID": g.connID})
|
|
return g.connID
|
|
}
|
|
|
|
// Validate validates the given connection ID for an IP and the current time.
|
|
func (g *ConnectionIDGenerator) Validate(connectionID []byte, ip net.IP, now time.Time, maxClockSkew time.Duration) bool {
|
|
ts := time.Unix(int64(binary.BigEndian.Uint32(connectionID[:4])), 0)
|
|
log.Debug("validating connection ID", log.Fields{"connID": connectionID, "ip": ip, "ts": ts, "now": now})
|
|
if now.After(ts.Add(ttl)) || ts.After(now.Add(maxClockSkew)) {
|
|
return false
|
|
}
|
|
|
|
g.reset()
|
|
|
|
g.mac.Write(connectionID[:4])
|
|
g.mac.Write(ip)
|
|
g.scratch = g.mac.Sum(g.scratch)
|
|
return hmac.Equal(g.scratch[:4], connectionID[4:])
|
|
}
|