(untested) add salt to UDP connection ID

This commit is contained in:
Lawrence, Rendall
2022-11-30 19:10:23 +03:00
parent a69a476024
commit f80e9af156
5 changed files with 118 additions and 67 deletions
+67 -31
View File
@@ -3,17 +3,17 @@ package udp
import (
"crypto/hmac"
"encoding/binary"
"github.com/cespare/xxhash/v2"
"hash"
"math/rand"
"net/netip"
"time"
"github.com/zeebo/xxh3"
"github.com/sot-tech/mochi/pkg/log"
)
// ttl is the duration a connection ID should be valid according to BEP 15.
const ttl = 2 * time.Minute
var ttl = int64(2 * time.Minute)
// A ConnectionIDGenerator is a reusable generator and validator for connection
// IDs as described in BEP 15.
@@ -33,66 +33,102 @@ type ConnectionIDGenerator struct {
// It will be overwritten by subsequent calls to Generate.
connID []byte
buff []byte
// scratch is a 32-byte slice that is used as a scratchpad for the generated
// HMACs.
scratch []byte
// the leeway for a timestamp on a connection ID.
maxClockSkew time.Duration
maxClockSkew int64
cnt uint64
}
// NewConnectionIDGenerator creates a new connection ID generator.
func NewConnectionIDGenerator(key string, maxClockSkew time.Duration) *ConnectionIDGenerator {
return &ConnectionIDGenerator{
mac: hmac.New(func() hash.Hash {
return xxh3.New()
return xxhash.New()
}, []byte(key)),
connID: make([]byte, 8),
maxClockSkew: maxClockSkew,
buff: make([]byte, 9),
scratch: make([]byte, 16),
maxClockSkew: int64(maxClockSkew),
cnt: rand.Uint64(),
}
}
// xor-shift-star generator
func (g *ConnectionIDGenerator) nextCnt() uint64 {
g.cnt ^= g.cnt >> 12
g.cnt ^= g.cnt << 25
g.cnt ^= g.cnt >> 27
g.cnt *= 0x2545F4914F6CDD1D
return g.cnt
}
// 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.buff = g.buff[:9]
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.
// The first byte is random salt, next 2 bytes - truncated unix timestamp
// when ID was generated, last 5 bytes are a truncated HMAC token created
// from salt (1 byte), full unix timestamp (8 bytes) and source IP (4/16 bytes).
// Salt used to mitigate generation same MAC if there are several clients
// from same IP sent requests within one second.
//
// 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
// of the truncated HMAC token. In this use case we have 40 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
// The generated ID is written to g.buffer, which is also returned. g.buffer
// 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 netip.Addr, now time.Time) []byte {
g.mac.Reset()
binary.BigEndian.PutUint32(g.connID, uint32(now.Unix()))
g.mac.Write(g.connID[:4])
func (g *ConnectionIDGenerator) Generate(ip netip.Addr, now time.Time) (out []byte) {
g.reset()
g.buff[0] = byte(g.nextCnt())
binary.BigEndian.PutUint64(g.buff[1:], uint64(now.Unix()))
g.mac.Write(g.buff)
g.mac.Write(ip.AsSlice())
copy(g.connID[4:8], g.mac.Sum(nil)[:4])
g.scratch = g.mac.Sum(g.scratch)
g.connID[0], g.connID[1], g.connID[2] = g.buff[0], g.buff[7], g.buff[8]
copy(g.connID[3:], g.scratch[:5])
log.Debug().
Stringer("ip", ip).
Time("now", now).
Hex("connID", g.connID).
Msg("generated connection ID")
return g.connID
return g.connID[:8]
}
// Validate validates the given connection ID for an IP and the current time.
func (g *ConnectionIDGenerator) Validate(connectionID []byte, ip netip.Addr, now time.Time) bool {
g.mac.Reset()
tsBytes := connectionID[:4]
ts := time.Unix(int64(binary.BigEndian.Uint32(tsBytes)), 0)
g.reset()
nowTS := now.Unix()
g.buff[0] = connectionID[0]
ts := nowTS&((^int64(0)>>16)<<16) | int64(connectionID[1])<<8 | int64(connectionID[2])
binary.BigEndian.PutUint64(g.buff[1:], uint64(ts))
g.mac.Write(g.buff)
g.mac.Write(ip.AsSlice())
g.scratch = g.mac.Sum(g.scratch)
res := hmac.Equal(g.scratch[:5], connectionID[3:8])
res = ts-g.maxClockSkew <= nowTS && res
res = nowTS < ts+ttl+g.maxClockSkew && res
log.Debug().
Stringer("ip", ip).
Time("ts", ts).
Time("now", now).
Hex("connID", g.connID).
Hex("connID", connectionID).
Bool("result", res).
Msg("validating connection ID")
g.mac.Write(tsBytes)
g.mac.Write(ip.AsSlice())
return hmac.Equal(g.mac.Sum(nil)[:4], connectionID[4:8]) &&
now.Before(ts.Add(ttl)) &&
ts.Before(now.Add(g.maxClockSkew))
return res
}
+16 -14
View File
@@ -4,16 +4,17 @@ import (
"crypto/hmac"
"encoding/binary"
"fmt"
"github.com/cespare/xxhash/v2"
"hash"
"math/rand"
"net/netip"
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/zeebo/xxh3"
"github.com/sot-tech/mochi/pkg/log"
_ "github.com/sot-tech/mochi/pkg/randseed"
"github.com/stretchr/testify/require"
)
var golden = []struct {
@@ -46,25 +47,26 @@ func ValidConnectionID(connectionID []byte, ip netip.Addr, now time.Time, maxClo
// simpleNewConnectionID generates a new connection ID the explicit way.
// This is used to verify correct behaviour of the generator.
func simpleNewConnectionID(ip netip.Addr, now time.Time, key string) []byte {
buf := make([]byte, 8)
binary.BigEndian.PutUint32(buf, uint32(now.Unix()))
buffer := make([]byte, 9)
mac := hmac.New(func() hash.Hash {
return xxh3.New()
return xxhash.New()
}, []byte(key))
mac.Write(buf[:4])
buffer[0] = byte(rand.Int())
binary.BigEndian.PutUint64(buffer[1:], uint64(now.Unix()))
mac.Write(buffer)
mac.Write(ip.AsSlice())
macBytes := mac.Sum(nil)[:4]
copy(buf[4:], macBytes)
buffer[0], buffer[1], buffer[2] = buffer[0], buffer[7], buffer[8]
copy(buffer[3:8], mac.Sum(nil))
buffer = buffer[:8]
// this is just in here because logging impacts performance and we benchmark
// this version too.
log.Debug().
Stringer("ip", ip).
Time("now", now).
Hex("connID", buf).
Msg("manually generated connection ID")
return buf
Time("ts", now).
Hex("connID", buffer).
Msg("generated connection ID")
return buffer
}
func TestVerification(t *testing.T) {
+19 -3
View File
@@ -24,8 +24,12 @@ import (
"github.com/sot-tech/mochi/pkg/timecache"
)
// Name - registered name of the frontend
const Name = "udp"
const (
// Name - registered name of the frontend
Name = "udp"
maxAllowedClockSkew = 30 * time.Second
defaultMaxClockSkew = 10 * time.Second
)
var (
logger = log.NewLogger("frontend/udp")
@@ -62,7 +66,19 @@ func (cfg Config) Validate() (validCfg Config) {
logger.Warn().
Str("name", "PrivateKey").
Str("provided", "").
Str("key", validCfg.PrivateKey).
Str("default", validCfg.PrivateKey).
Msg("falling back to default configuration")
}
sb := cfg.MaxClockSkew >> 63
validCfg.MaxClockSkew = (cfg.MaxClockSkew ^ sb) + (sb & 1)
if validCfg.MaxClockSkew == 0 || validCfg.MaxClockSkew > maxAllowedClockSkew {
validCfg.MaxClockSkew = defaultMaxClockSkew
logger.Warn().
Str("name", "MaxClockSkew").
Dur("provided", cfg.MaxClockSkew).
Dur("default", validCfg.MaxClockSkew).
Msg("falling back to default configuration")
}
+6 -7
View File
@@ -3,11 +3,12 @@ module github.com/sot-tech/mochi
go 1.19
require (
code.cloudfoundry.org/go-diodes v0.0.0-20221122215814-f99b6a62c703
github.com/MicahParks/keyfunc v1.6.0
github.com/anacrolix/torrent v1.47.0
code.cloudfoundry.org/go-diodes v0.0.0-20221128154616-f0ef2e038721
github.com/MicahParks/keyfunc v1.7.0
github.com/anacrolix/torrent v1.47.1-0.20221102120345-c63f7e1bd720
github.com/cespare/xxhash/v2 v2.1.2
github.com/go-redis/redis/v8 v8.11.5
github.com/golang-jwt/jwt/v4 v4.4.2
github.com/golang-jwt/jwt/v4 v4.4.3
github.com/jackc/pgx/v5 v5.1.1
github.com/julienschmidt/httprouter v1.3.0
github.com/libp2p/go-reuseport v0.2.0
@@ -16,18 +17,16 @@ require (
github.com/prometheus/client_golang v1.14.0
github.com/rs/zerolog v1.28.0
github.com/stretchr/testify v1.8.1
github.com/zeebo/xxh3 v1.0.2
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/anacrolix/dht/v2 v2.19.1 // indirect
github.com/anacrolix/dht/v2 v2.19.2 // indirect
github.com/anacrolix/log v0.13.2-0.20220711050817-613cb738ef30 // indirect
github.com/anacrolix/missinggo v1.3.0 // indirect
github.com/anacrolix/missinggo/v2 v2.7.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
+10 -12
View File
@@ -30,15 +30,15 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
code.cloudfoundry.org/go-diodes v0.0.0-20221122215814-f99b6a62c703 h1:AYI2TSzZnOwbxvCy1a2+AINk2d43J8+z4wVxt6YC364=
code.cloudfoundry.org/go-diodes v0.0.0-20221122215814-f99b6a62c703/go.mod h1:kDqUxF0g8gENeHnd/Np+/ES9u2l00Maamk2ECzPd+lA=
code.cloudfoundry.org/go-diodes v0.0.0-20221128154616-f0ef2e038721 h1:Dr3lCN4tcyMoX04sUwP9C0uCO5Uc+J11GWGVBNIJaYk=
code.cloudfoundry.org/go-diodes v0.0.0-20221128154616-f0ef2e038721/go.mod h1:kDqUxF0g8gENeHnd/Np+/ES9u2l00Maamk2ECzPd+lA=
crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk=
crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/MicahParks/keyfunc v1.6.0 h1:VsoMTaQDCxaEYhlYVmuqkOopsDx12/XqIgJIz7cl/0g=
github.com/MicahParks/keyfunc v1.6.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw=
github.com/MicahParks/keyfunc v1.7.0 h1:LBd4tBj6FwGs2S4GXniQbgrG0PXzIldyGDKWch8slhg=
github.com/MicahParks/keyfunc v1.7.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw=
github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI=
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
@@ -49,8 +49,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/anacrolix/dht/v2 v2.19.1 h1:V/UUGBASGYqYkSnmHJwX8uQmzkyhbgwE6jqcHKnNTD8=
github.com/anacrolix/dht/v2 v2.19.1/go.mod h1:3TU93c1s/oA8I/VH4m3CNP/BeKsiOGmo6HwfZBMTKUs=
github.com/anacrolix/dht/v2 v2.19.2 h1:U+lbEFYwa9yajIW2oE9jBxt0K8DlT10pSTYbXyfCxC8=
github.com/anacrolix/dht/v2 v2.19.2/go.mod h1:MctKM1HS5YYDb3F30NGJxLE+QPuqWoT5ReW/4jt8xew=
github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4=
@@ -73,8 +73,8 @@ github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQ
github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
github.com/anacrolix/torrent v1.47.0 h1:aDUnhQZ8+kfStLICHiXOGGYVFgDENK+kz4q96linyRg=
github.com/anacrolix/torrent v1.47.0/go.mod h1:SYPxEUjMwqhDr3kWGzyQLkFMuAb1bgJ57JRMpuD3ZzE=
github.com/anacrolix/torrent v1.47.1-0.20221102120345-c63f7e1bd720 h1:3cvS7QtC2hcrIOfT44fKDfPZUcXvkDf9SkI1mN6TB+s=
github.com/anacrolix/torrent v1.47.1-0.20221102120345-c63f7e1bd720/go.mod h1:SYPxEUjMwqhDr3kWGzyQLkFMuAb1bgJ57JRMpuD3ZzE=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -141,8 +141,9 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg78
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -367,9 +368,6 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=