mirror of
https://github.com/sot-tech/mochi.git
synced 2026-06-14 00:23:35 -07:00
(untested) add salt to UDP connection ID
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
Reference in New Issue
Block a user