From ac08f17d1b59cbdea4d5230b12f5974091873bdb Mon Sep 17 00:00:00 2001 From: ucwong Date: Thu, 11 May 2023 23:40:29 +0800 Subject: [PATCH] deps --- go.mod | 12 +- go.sum | 23 +- .../torrentfs/backend/handler.go | 2 +- .../torrentfs/monitor/monitor.go | 2 +- .../github.com/anacrolix/generics/errors.go | 8 + .../github.com/anacrolix/generics/generics.go | 9 + vendor/github.com/anacrolix/generics/map.go | 33 ++- .../github.com/anacrolix/generics/option.go | 2 +- .../github.com/anacrolix/generics/result.go | 7 + vendor/github.com/anacrolix/generics/slice.go | 16 ++ vendor/github.com/anacrolix/torrent/NOTES.md | 19 ++ vendor/github.com/anacrolix/torrent/client.go | 145 ++++++++++--- vendor/github.com/anacrolix/torrent/global.go | 1 + .../anacrolix/torrent/netip-addrport.go | 44 ++++ vendor/github.com/anacrolix/torrent/peer.go | 6 +- .../torrent/peer_protocol/handshake.go | 28 ++- .../anacrolix/torrent/peer_protocol/pex.go | 1 + .../peer_protocol/ut-holepunch/err-code.go | 27 +++ .../ut-holepunch/ut-holepunch.go | 84 ++++++++ .../github.com/anacrolix/torrent/peerconn.go | 86 +++++++- vendor/github.com/anacrolix/torrent/pex.go | 24 ++- .../github.com/anacrolix/torrent/pexconn.go | 62 ++++-- vendor/github.com/anacrolix/torrent/socket.go | 47 +++- .../github.com/anacrolix/torrent/sockopts.go | 10 + .../anacrolix/torrent/sockopts_unix.go | 29 +++ .../anacrolix/torrent/sockopts_wasm.go | 12 ++ .../anacrolix/torrent/sockopts_windows.go | 15 ++ .../github.com/anacrolix/torrent/torrent.go | 202 ++++++++++++++++-- .../anacrolix/torrent/ut-holepunching.go | 11 + .../cockroachdb/pebble/mem_table.go | 1 + .../pebble/objstorage/objstorage.go | 2 +- .../objstorage/objstorageprovider/shared.go | 45 ---- .../objstorageprovider/shared_obj_name.go | 80 +++++++ .../cockroachdb/pebble/sstable/reader.go | 153 ++++++++----- .../cockroachdb/pebble/table_cache.go | 1 + ...efault_linux_noarm.go => default_linux.go} | 4 +- .../pebble/vfs/default_linux_arm.go | 73 ------- .../github.com/dgraph-io/badger/v4/levels.go | 11 +- vendor/golang.org/x/exp/maps/maps.go | 94 ++++++++ vendor/modules.txt | 14 +- 40 files changed, 1147 insertions(+), 298 deletions(-) create mode 100644 vendor/github.com/anacrolix/generics/errors.go create mode 100644 vendor/github.com/anacrolix/torrent/netip-addrport.go create mode 100644 vendor/github.com/anacrolix/torrent/peer_protocol/ut-holepunch/err-code.go create mode 100644 vendor/github.com/anacrolix/torrent/peer_protocol/ut-holepunch/ut-holepunch.go create mode 100644 vendor/github.com/anacrolix/torrent/sockopts.go create mode 100644 vendor/github.com/anacrolix/torrent/sockopts_unix.go create mode 100644 vendor/github.com/anacrolix/torrent/sockopts_wasm.go create mode 100644 vendor/github.com/anacrolix/torrent/sockopts_windows.go create mode 100644 vendor/github.com/anacrolix/torrent/ut-holepunching.go create mode 100644 vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/shared_obj_name.go rename vendor/github.com/cockroachdb/pebble/vfs/{default_linux_noarm.go => default_linux.go} (98%) delete mode 100644 vendor/github.com/cockroachdb/pebble/vfs/default_linux_arm.go create mode 100644 vendor/golang.org/x/exp/maps/maps.go diff --git a/go.mod b/go.mod index e62d1f37de..239921ac86 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.1 github.com/CortexFoundation/inference v1.0.2-0.20230307032835-9197d586a4e8 github.com/CortexFoundation/statik v0.0.0-20210315012922-8bb8a7b5dc66 - github.com/CortexFoundation/torrentfs v1.0.43-0.20230505163706-a480ac5364fc + github.com/CortexFoundation/torrentfs v1.0.43-0.20230511153028-45240ab0a27a github.com/VictoriaMetrics/fastcache v1.12.1 github.com/arsham/figurine v1.3.0 github.com/aws/aws-sdk-go-v2 v1.17.7 @@ -17,7 +17,7 @@ require ( github.com/cespare/cp v1.1.1 github.com/charmbracelet/bubbletea v0.23.2 github.com/cloudflare/cloudflare-go v0.57.1 - github.com/cockroachdb/pebble v0.0.0-20230503231107-9e575c4c10ae + github.com/cockroachdb/pebble v0.0.0-20230510135629-fe7ae7a62e0f github.com/consensys/gnark-crypto v0.10.0 github.com/crate-crypto/go-kzg-4844 v0.2.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc @@ -65,7 +65,7 @@ require ( golang.org/x/crypto v0.8.0 golang.org/x/exp v0.0.0-20230420155640-133eef4313cb golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f - golang.org/x/sync v0.1.0 + golang.org/x/sync v0.2.0 golang.org/x/sys v0.8.0 golang.org/x/text v0.9.0 golang.org/x/time v0.3.0 @@ -89,7 +89,7 @@ require ( github.com/anacrolix/chansync v0.3.0 // indirect github.com/anacrolix/dht/v2 v2.20.0 // indirect github.com/anacrolix/envpprof v1.3.0 // indirect - github.com/anacrolix/generics v0.0.0-20230303103524-f0e0c7158a76 // indirect + github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68 // indirect github.com/anacrolix/go-libutp v1.2.0 // indirect github.com/anacrolix/log v0.13.2-0.20221123232138-02e2764801c3 // indirect github.com/anacrolix/missinggo v1.3.0 // indirect @@ -99,7 +99,7 @@ require ( github.com/anacrolix/multiless v0.3.1-0.20221221005021-2d12701f83f7 // indirect github.com/anacrolix/stm v0.5.0 // indirect github.com/anacrolix/sync v0.4.0 // indirect - github.com/anacrolix/torrent v1.50.1-0.20230429045449-7e65e55c3501 // indirect + github.com/anacrolix/torrent v1.50.1-0.20230509054651-5703f9b5eb9a // indirect github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96 // indirect github.com/anacrolix/utp v0.1.0 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect @@ -130,7 +130,7 @@ require ( github.com/crate-crypto/go-ipa v0.0.0-20230315201338-1643fdc2ead8 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/deepmap/oapi-codegen v1.12.4 // indirect - github.com/dgraph-io/badger/v4 v4.0.2-0.20230504174847-9afd0a093523 // indirect + github.com/dgraph-io/badger/v4 v4.0.2-0.20230509100715-ef0e55289cf7 // indirect github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dlclark/regexp2 v1.8.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect diff --git a/go.sum b/go.sum index 4cc9d14eee..0363d77505 100644 --- a/go.sum +++ b/go.sum @@ -67,8 +67,8 @@ github.com/CortexFoundation/statik v0.0.0-20210315012922-8bb8a7b5dc66/go.mod h1: github.com/CortexFoundation/torrentfs v1.0.13-0.20200623060705-ce027f43f2f8/go.mod h1:Ma+tGhPPvz4CEZHaqEJQMOEGOfHeQBiAoNd1zyc/w3Q= github.com/CortexFoundation/torrentfs v1.0.14-0.20200703071639-3fcabcabf274/go.mod h1:qnb3YlIJmuetVBtC6Lsejr0Xru+1DNmDCdTqnwy7lhk= github.com/CortexFoundation/torrentfs v1.0.20-0.20200810031954-d36d26f82fcc/go.mod h1:N5BsicP5ynjXIi/Npl/SRzlJ630n1PJV2sRj0Z0t2HA= -github.com/CortexFoundation/torrentfs v1.0.43-0.20230505163706-a480ac5364fc h1:KJnUQPga49bV2ZOyGPoxmlIR5gc94SW3JeFdtrYf1Qo= -github.com/CortexFoundation/torrentfs v1.0.43-0.20230505163706-a480ac5364fc/go.mod h1:N3arBC803tRBHVIQVeBDkzE7/k+6XL2Eve/tRZXLeWM= +github.com/CortexFoundation/torrentfs v1.0.43-0.20230511153028-45240ab0a27a h1:64d/Bi1ompkq/F0guyTZCCfljpDYJdlveC8txrmGUi8= +github.com/CortexFoundation/torrentfs v1.0.43-0.20230511153028-45240ab0a27a/go.mod h1:HGCo/mLMGZc7dKIJREdBh+m3jFEjIW3Ga1LRm0QJzVw= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= @@ -134,8 +134,8 @@ github.com/anacrolix/envpprof v1.0.1/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAK github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4= github.com/anacrolix/envpprof v1.3.0 h1:WJt9bpuT7A/CDCxPOv/eeZqHWlle/Y0keJUvc6tcJDk= github.com/anacrolix/envpprof v1.3.0/go.mod h1:7QIG4CaX1uexQ3tqd5+BRa/9e2D02Wcertl6Yh0jCB0= -github.com/anacrolix/generics v0.0.0-20230303103524-f0e0c7158a76 h1:RfplUXFsgyNupeETFiB3qNw//dFA817uEMTnpboE6Mg= -github.com/anacrolix/generics v0.0.0-20230303103524-f0e0c7158a76/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8= +github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68 h1:fyXlBfnlFzZSFckJ8QLb2lfmWfY++4RiUnae7ZMuv0A= +github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8= github.com/anacrolix/go-libutp v0.0.0-20180522111405-6baeb806518d/go.mod h1:beQSaSxwH2d9Eeu5ijrEnHei5Qhk+J6cDm1QkWFru4E= github.com/anacrolix/go-libutp v1.0.2/go.mod h1:uIH0A72V++j0D1nnmTjjZUiH/ujPkFxYWkxQ02+7S0U= github.com/anacrolix/go-libutp v1.0.3/go.mod h1:8vSGX5g0b4eebsDBNVQHUXSCwYaN18Lnkse0hUW8/5w= @@ -212,8 +212,8 @@ github.com/anacrolix/torrent v1.15.0/go.mod h1:MFc6KcbpAyfwGqOyRkdarUK9QnKA/FkVg github.com/anacrolix/torrent v1.15.1-0.20200504230043-cc5d2abe18e5/go.mod h1:QlOfgrCz5kbvhOz8M58dUwHY5SfZ9VbIvReZ0z0MdIk= github.com/anacrolix/torrent v1.15.1-0.20200619022403-dd51e99b88cc/go.mod h1:wuopQPC5+/M+zHYvhcA2vp5UCTm9rUc+VqjyBa882Q8= github.com/anacrolix/torrent v1.15.1-0.20200715061614-dd906f8fa72e/go.mod h1:XWo/fJN1oKgcjgxM+pUZpvalHfqHDs27BY5mBZjIQWo= -github.com/anacrolix/torrent v1.50.1-0.20230429045449-7e65e55c3501 h1:Sm4QVsfFeyzK4hT8D1UBgbOjiBYzWH8ZUGmNh31oPHE= -github.com/anacrolix/torrent v1.50.1-0.20230429045449-7e65e55c3501/go.mod h1:qT3yS5oQwDUHnBXy+zf3nozLPudG7SFNDL3Jl/zQwFw= +github.com/anacrolix/torrent v1.50.1-0.20230509054651-5703f9b5eb9a h1:zZLeMNLL+YaYI3rzf4VbOvCBhP9A6IMOmU7i/Pft/qw= +github.com/anacrolix/torrent v1.50.1-0.20230509054651-5703f9b5eb9a/go.mod h1:3Si/HOOtbLwTcJ+LiyDowY8cJdRCXJ1QPikmb7FSsC8= github.com/anacrolix/upnp v0.1.1/go.mod h1:LXsbsp5h+WGN7YR+0A7iVXm5BL1LYryDev1zuJMWYQo= github.com/anacrolix/upnp v0.1.2-0.20200416075019-5e9378ed1425/go.mod h1:Pz94W3kl8rf+wxH3IbCa9Sq+DTJr8OSbV2Q3/y51vYs= github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96 h1:QAVZ3pN/J4/UziniAhJR2OZ9Ox5kOY2053tBbbqUPYA= @@ -349,8 +349,8 @@ github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZO github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/pebble v0.0.0-20230503231107-9e575c4c10ae h1:su/c0k4GX0GfC9eE1ulVSg1l2SFZAtRDUeREyCHkeL0= -github.com/cockroachdb/pebble v0.0.0-20230503231107-9e575c4c10ae/go.mod h1:TkdVsGYRqtULUppt2RbC+YaKtTHnHoWa2apfFrSKABw= +github.com/cockroachdb/pebble v0.0.0-20230510135629-fe7ae7a62e0f h1:NQ2CYGSQoozmZlh8Md436mTqO7B0DNFgeitYw7Gn0LU= +github.com/cockroachdb/pebble v0.0.0-20230510135629-fe7ae7a62e0f/go.mod h1:TkdVsGYRqtULUppt2RbC+YaKtTHnHoWa2apfFrSKABw= github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= @@ -397,8 +397,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3 github.com/deepmap/oapi-codegen v1.12.4 h1:pPmn6qI9MuOtCz82WY2Xaw46EQjgvxednXXrP7g5Q2s= github.com/deepmap/oapi-codegen v1.12.4/go.mod h1:3lgHGMu6myQ2vqbbTXH2H1o4eXFTGnFiDaOaKKl5yas= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= -github.com/dgraph-io/badger/v4 v4.0.2-0.20230504174847-9afd0a093523 h1:kYXByd6NdxTSU6Q5FvR+Yw/TarA7KmlZE4XmVb67N1s= -github.com/dgraph-io/badger/v4 v4.0.2-0.20230504174847-9afd0a093523/go.mod h1:P50u28d39ibBRmIJuQC/NSdBOg46HnHw7al2SW5QRHg= +github.com/dgraph-io/badger/v4 v4.0.2-0.20230509100715-ef0e55289cf7 h1:b7OofX+02tsxy3Midj88nwRsktdcsglG0yEnvHnLhes= +github.com/dgraph-io/badger/v4 v4.0.2-0.20230509100715-ef0e55289cf7/go.mod h1:P50u28d39ibBRmIJuQC/NSdBOg46HnHw7al2SW5QRHg= github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -1506,8 +1506,9 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/vendor/github.com/CortexFoundation/torrentfs/backend/handler.go b/vendor/github.com/CortexFoundation/torrentfs/backend/handler.go index 425cbfbc2f..f8bb02de68 100644 --- a/vendor/github.com/CortexFoundation/torrentfs/backend/handler.go +++ b/vendor/github.com/CortexFoundation/torrentfs/backend/handler.go @@ -258,7 +258,7 @@ func (tm *TorrentManager) ListAllTorrents() map[string]map[string]int { defer tm.lock.RUnlock() defer tm.localSeedLock.RUnlock() - tts := make(map[string]map[string]int) + tts := make(map[string]map[string]int, tm.torrents.Len()) /*for ih, tt := range tm.torrents { tType := torrentTypeOnChain if _, ok := tm.localSeedFiles[ih]; ok { diff --git a/vendor/github.com/CortexFoundation/torrentfs/monitor/monitor.go b/vendor/github.com/CortexFoundation/torrentfs/monitor/monitor.go index 50321f44f7..f689e3af73 100644 --- a/vendor/github.com/CortexFoundation/torrentfs/monitor/monitor.go +++ b/vendor/github.com/CortexFoundation/torrentfs/monitor/monitor.go @@ -213,7 +213,7 @@ func (m *Monitor) indexCheck() error { } func (m *Monitor) indexInit() error { - fileMap := make(map[string]*types.FileInfo) + fileMap := make(map[string]*types.FileInfo, len(m.fs.Files())) for _, file := range m.fs.Files() { if f, ok := fileMap[file.Meta.InfoHash]; ok { if f.LeftSize > file.LeftSize { diff --git a/vendor/github.com/anacrolix/generics/errors.go b/vendor/github.com/anacrolix/generics/errors.go new file mode 100644 index 0000000000..99247f0338 --- /dev/null +++ b/vendor/github.com/anacrolix/generics/errors.go @@ -0,0 +1,8 @@ +package generics + +func UnwrapErrorTuple[T any](t T, err error) T { + if err != nil { + panic(err) + } + return t +} diff --git a/vendor/github.com/anacrolix/generics/generics.go b/vendor/github.com/anacrolix/generics/generics.go index 48ad368bb8..554b33dc2d 100644 --- a/vendor/github.com/anacrolix/generics/generics.go +++ b/vendor/github.com/anacrolix/generics/generics.go @@ -1,5 +1,7 @@ package generics +import "golang.org/x/exp/constraints" + func InitNew[T any](p **T) { *p = new(T) } @@ -11,3 +13,10 @@ func SetZero[T any](p *T) { func PtrTo[T any](t T) *T { return &t } + +// Returns a zero-size, zero-allocation slice of the given length that can be used with range to +// loop n times. Also has the advantage of not requiring a loop variable. Similar to bradfitz's +// iter.N, and my clone in anacrolix/missinggo. +func Range[T constraints.Integer](n T) []struct{} { + return make([]struct{}, n) +} diff --git a/vendor/github.com/anacrolix/generics/map.go b/vendor/github.com/anacrolix/generics/map.go index e7b244345c..7ab14deb8e 100644 --- a/vendor/github.com/anacrolix/generics/map.go +++ b/vendor/github.com/anacrolix/generics/map.go @@ -2,13 +2,14 @@ package generics import "golang.org/x/exp/constraints" -func MakeMapIfNilAndSet[K comparable, V any](pm *map[K]V, k K, v V) { +// Deprecated: Use MakeMapIfNil and MapInsert separately. +func MakeMapIfNilAndSet[K comparable, V any](pm *map[K]V, k K, v V) (added bool) { + MakeMapIfNil(pm) m := *pm - if m == nil { - m = make(map[K]V) - *pm = m - } + _, exists := m[k] + added = !exists m[k] = v + return } // Does this exist in the maps package? @@ -25,3 +26,25 @@ func MakeMapIfNil[K comparable, V any, M ~map[K]V](pm *M) { MakeMap(pm) } } + +func MapContains[K comparable, V any, M ~map[K]V](m M, k K) bool { + _, ok := m[k] + return ok +} + +func MapMustGet[K comparable, V any, M ~map[K]V](m M, k K) V { + v, ok := m[k] + if !ok { + panic(k) + } + return v +} + +func MapInsert[K comparable, V any, M ~map[K]V](m M, k K, v V) Option[V] { + old, ok := m[k] + m[k] = v + return Option[V]{ + Value: old, + Ok: ok, + } +} diff --git a/vendor/github.com/anacrolix/generics/option.go b/vendor/github.com/anacrolix/generics/option.go index 71dfb641be..209f4032b8 100644 --- a/vendor/github.com/anacrolix/generics/option.go +++ b/vendor/github.com/anacrolix/generics/option.go @@ -6,7 +6,7 @@ type Option[V any] struct { Value V } -func (me *Option[V]) UnwrapOrZeroValue() (_ V) { +func (me Option[V]) UnwrapOrZeroValue() (_ V) { if me.Ok { return me.Value } diff --git a/vendor/github.com/anacrolix/generics/result.go b/vendor/github.com/anacrolix/generics/result.go index c25447e461..aa71165a60 100644 --- a/vendor/github.com/anacrolix/generics/result.go +++ b/vendor/github.com/anacrolix/generics/result.go @@ -22,3 +22,10 @@ func (r Result[T]) Unwrap() T { } return r.Ok } + +func (r Result[T]) ToOption() Option[T] { + return Option[T]{ + Ok: r.Err == nil, + Value: r.Ok, + } +} diff --git a/vendor/github.com/anacrolix/generics/slice.go b/vendor/github.com/anacrolix/generics/slice.go index 61864dbad8..ceba43868f 100644 --- a/vendor/github.com/anacrolix/generics/slice.go +++ b/vendor/github.com/anacrolix/generics/slice.go @@ -55,3 +55,19 @@ func SliceTake[T any](n int, slice []T) []T { func SliceDrop[T any](n int, slice []T) []T { return slice[Min(n, len(slice)):] } + +func SliceGet[T any, I constraints.Integer](slice []T, index I) (ret Option[T]) { + if int(index) < len(slice) { + ret = Some(slice[index]) + } + return +} + +// Surely you should just pass iterator functions around instead. Go sux. +func SliceMap[From, To any](froms []From, convert func(From) To) []To { + tos := make([]To, 0, len(froms)) + for _, from := range froms { + tos = append(tos, convert(from)) + } + return tos +} diff --git a/vendor/github.com/anacrolix/torrent/NOTES.md b/vendor/github.com/anacrolix/torrent/NOTES.md index 37afb8c5c8..80da84b437 100644 --- a/vendor/github.com/anacrolix/torrent/NOTES.md +++ b/vendor/github.com/anacrolix/torrent/NOTES.md @@ -11,3 +11,22 @@ * [A South American paper on peer-selection strategies for uploading](https://arxiv.org/pdf/1402.2187.pdf) Has some useful overviews of piece-selection. + +### Hole-punching + +Holepunching is tracked in Torrent, rather than in Client because if we send a rendezvous message, and subsequently receive a connect message, we do not know if a peer sent a rendezvous message to our relay and we're receiving the connect message for their rendezvous or ours. Relays are not required to respond to rendezvous, so we can't enforce a timeout. If we don't know if who sent the rendezvous that triggered a connect, then we don't know what infohash to use in the handshake. Once we send a rendezvous, and never receive a reply, we would have to always perform handshakes with our original infohash, or always copy the infohash the remote sends. Handling connects by always being the passive side in the handshake won't work since the other side might use the same behaviour and neither will initiate. + +If we only perform rendezvous through relays for the same torrent as the relay, then all the handshake can be done actively for all connect messages. All connect messages received from a peer can only be for the same torrent for which we are connected to the peer. + +In 2006, approximately 70% of clients were behind NAT (https://web.archive.org/web/20100724011252/http://illuminati.coralcdn.org/stats/). According to https://fosdem.org/2023/schedule/event/network_hole_punching_in_the_wild/, hole punching (in libp2p) 70% of NAT can be defeated by relay mechanisms. + +If either or both peers in a potential peer do not have NAT, or are full cone NAT, then NAT doesn't matter at least for BitTorrent, as both parties are trying to connect to each other and connections will always work in one direction. + +The chance that 2 peers can connect to each other would be 1-(badnat)^2, and 1-unrelayable*(badnat)^2 where unrelayable is the chance they can't work even with a relay, and badnat is the chance a peer has a bad NAT (not full cone). For example if unrelayable is 0.3 per the libp2p study, and badnat is 0.5 (i made this up), 92.5% of peers can connect with each other if they use "relay mechanisms", and 75% if they don't. as long as any peers in the swarm are not badnat, they can relay those that are, and and act as super nodes for peers that can't or don't implement hole punching. + +The DHT is a bit different: you can't be an active node if you are a badnat, but you can still query the network to get what you need, you just don't contribute to it. It also doesn't matter what the swarm looks like for a given torrent on the DHT, because you don't have to be in the swarm to host its data. all that matters is that there are some peers that aren't badnat that are in the DHT, of which there are millions (for BitTorrent). + +- https://blog.ipfs.tech/2022-01-20-libp2p-hole-punching/ +- https://www.bittorrent.org/beps/bep_0055.html +- https://github.com/anacrolix/torrent/issues/685 +- https://stackoverflow.com/questions/38786438/libutp-%C2%B5tp-and-nat-traversal-udp-hole-punching diff --git a/vendor/github.com/anacrolix/torrent/client.go b/vendor/github.com/anacrolix/torrent/client.go index b6fed302f3..342025ac55 100644 --- a/vendor/github.com/anacrolix/torrent/client.go +++ b/vendor/github.com/anacrolix/torrent/client.go @@ -16,15 +16,14 @@ import ( "net/netip" "sort" "strconv" - "strings" "time" "github.com/anacrolix/chansync" "github.com/anacrolix/chansync/events" "github.com/anacrolix/dht/v2" "github.com/anacrolix/dht/v2/krpc" - g "github.com/anacrolix/generics" . "github.com/anacrolix/generics" + g "github.com/anacrolix/generics" "github.com/anacrolix/log" "github.com/anacrolix/missinggo/perf" "github.com/anacrolix/missinggo/v2" @@ -43,6 +42,7 @@ import ( "github.com/anacrolix/torrent/metainfo" "github.com/anacrolix/torrent/mse" pp "github.com/anacrolix/torrent/peer_protocol" + utHolepunch "github.com/anacrolix/torrent/peer_protocol/ut-holepunch" request_strategy "github.com/anacrolix/torrent/request-strategy" "github.com/anacrolix/torrent/storage" "github.com/anacrolix/torrent/tracker" @@ -645,7 +645,7 @@ func DialFirst(ctx context.Context, addr string, dialers []Dialer) (res DialResu res = <-resCh } }() - // There are still incompleted dials. + // There are still uncompleted dials. go func() { for ; left > 0; left-- { conn := (<-resCh).Conn @@ -662,8 +662,12 @@ func DialFirst(ctx context.Context, addr string, dialers []Dialer) (res DialResu func dialFromSocket(ctx context.Context, s Dialer, addr string) net.Conn { c, err := s.Dial(ctx, addr) + if err != nil { + log.Levelf(log.Debug, "error dialing %q: %v", addr, err) + } // This is a bit optimistic, but it looks non-trivial to thread this through the proxy code. Set - // it now in case we close the connection forthwith. + // it now in case we close the connection forthwith. Note this is also done in the TCP dialer + // code to increase the chance it's done. if tc, ok := c.(*net.TCPConn); ok { tc.SetLinger(0) } @@ -671,10 +675,6 @@ func dialFromSocket(ctx context.Context, s Dialer, addr string) net.Conn { return c } -func forgettableDialError(err error) bool { - return strings.Contains(err.Error(), "no suitable address found") -} - func (cl *Client) noLongerHalfOpen(t *Torrent, addr string) { if _, ok := t.halfOpen[addr]; !ok { panic("invariant broken") @@ -686,7 +686,7 @@ func (cl *Client) noLongerHalfOpen(t *Torrent, addr string) { } } -// Performs initiator handshakes and returns a connection. Returns nil *connection if no connection +// Performs initiator handshakes and returns a connection. Returns nil *PeerConn if no connection // for valid reasons. func (cl *Client) initiateProtocolHandshakes( ctx context.Context, @@ -713,8 +713,77 @@ func (cl *Client) initiateProtocolHandshakes( return } +func (cl *Client) waitForRendezvousConnect(ctx context.Context, rz *utHolepunchRendezvous) error { + for { + switch { + case rz.gotConnect.IsSet(): + return nil + case len(rz.relays) == 0: + return errors.New("all relays failed") + case ctx.Err() != nil: + return context.Cause(ctx) + } + relayCond := rz.relayCond.Signaled() + cl.unlock() + select { + case <-rz.gotConnect.Done(): + case <-relayCond: + case <-ctx.Done(): + } + cl.lock() + } +} + // Returns nil connection and nil error if no connection could be established for valid reasons. -func (cl *Client) establishOutgoingConnEx(t *Torrent, addr PeerRemoteAddr, obfuscatedHeader bool) (*PeerConn, error) { +func (cl *Client) initiateRendezvousConnect( + t *Torrent, addr PeerRemoteAddr, +) (ok bool, err error) { + holepunchAddr, err := addrPortFromPeerRemoteAddr(addr) + if err != nil { + return + } + cl.lock() + defer cl.unlock() + rz, err := t.startHolepunchRendezvous(holepunchAddr) + if err != nil { + return + } + if rz == nil { + return + } + ok = true + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + err = cl.waitForRendezvousConnect(ctx, rz) + delete(t.utHolepunchRendezvous, holepunchAddr) + if err != nil { + err = fmt.Errorf("waiting for rendezvous connect signal: %w", err) + } + return +} + +// Returns nil connection and nil error if no connection could be established for valid reasons. +func (cl *Client) establishOutgoingConnEx( + opts outgoingConnOpts, + obfuscatedHeader bool, +) ( + _ *PeerConn, err error, +) { + t := opts.t + addr := opts.addr + var rzOk bool + if !opts.skipHolepunchRendezvous { + rzOk, err = cl.initiateRendezvousConnect(t, addr) + if err != nil { + err = fmt.Errorf("initiating rendezvous connect: %w", err) + } + } + if opts.requireRendezvous && !rzOk { + return nil, err + } + if err != nil { + t.logger.Print(err) + } dialCtx, cancel := context.WithTimeout(context.Background(), func() time.Duration { cl.rLock() defer cl.rUnlock() @@ -730,14 +799,16 @@ func (cl *Client) establishOutgoingConnEx(t *Torrent, addr PeerRemoteAddr, obfus return nil, errors.New("dial failed") } addrIpPort, _ := tryIpPortFromNetAddr(addr) - c, err := cl.initiateProtocolHandshakes(context.Background(), nc, t, obfuscatedHeader, newConnectionOpts{ - outgoing: true, - remoteAddr: addr, - // It would be possible to retrieve a public IP from the dialer used here? - localPublicAddr: cl.publicAddr(addrIpPort.IP), - network: dr.Dialer.DialerNetwork(), - connString: regularNetConnPeerConnConnString(nc), - }) + c, err := cl.initiateProtocolHandshakes( + context.Background(), nc, t, obfuscatedHeader, + newConnectionOpts{ + outgoing: true, + remoteAddr: addr, + // It would be possible to retrieve a public IP from the dialer used here? + localPublicAddr: cl.publicAddr(addrIpPort.IP), + network: dr.Dialer.DialerNetwork(), + connString: regularNetConnPeerConnConnString(nc), + }) if err != nil { nc.Close() } @@ -746,10 +817,10 @@ func (cl *Client) establishOutgoingConnEx(t *Torrent, addr PeerRemoteAddr, obfus // Returns nil connection and nil error if no connection could be established // for valid reasons. -func (cl *Client) establishOutgoingConn(t *Torrent, addr PeerRemoteAddr) (c *PeerConn, err error) { +func (cl *Client) establishOutgoingConn(opts outgoingConnOpts) (c *PeerConn, err error) { torrent.Add("establish outgoing connection", 1) obfuscatedHeaderFirst := cl.config.HeaderObfuscationPolicy.Preferred - c, err = cl.establishOutgoingConnEx(t, addr, obfuscatedHeaderFirst) + c, err = cl.establishOutgoingConnEx(opts, obfuscatedHeaderFirst) if err == nil { torrent.Add("initiated conn with preferred header obfuscation", 1) return @@ -761,7 +832,7 @@ func (cl *Client) establishOutgoingConn(t *Torrent, addr PeerRemoteAddr) (c *Pee return } // Try again with encryption if we didn't earlier, or without if we did. - c, err = cl.establishOutgoingConnEx(t, addr, !obfuscatedHeaderFirst) + c, err = cl.establishOutgoingConnEx(opts, !obfuscatedHeaderFirst) if err == nil { torrent.Add("initiated conn with fallback header obfuscation", 1) } @@ -769,11 +840,20 @@ func (cl *Client) establishOutgoingConn(t *Torrent, addr PeerRemoteAddr) (c *Pee return } +type outgoingConnOpts struct { + t *Torrent + addr PeerRemoteAddr + // Don't attempt to connect unless a connect message is received after initiating a rendezvous. + requireRendezvous bool + // Don't send rendezvous requests to eligible relays. + skipHolepunchRendezvous bool +} + // Called to dial out and run a connection. The addr we're given is already // considered half-open. -func (cl *Client) outgoingConnection(t *Torrent, addr PeerRemoteAddr, ps PeerSource, trusted bool) { +func (cl *Client) outgoingConnection(opts outgoingConnOpts, ps PeerSource, trusted bool) { cl.dialRateLimiter.Wait(context.Background()) - c, err := cl.establishOutgoingConn(t, addr) + c, err := cl.establishOutgoingConn(opts) if err == nil { c.conn.SetWriteDeadline(time.Time{}) } @@ -781,17 +861,17 @@ func (cl *Client) outgoingConnection(t *Torrent, addr PeerRemoteAddr, ps PeerSou defer cl.unlock() // Don't release lock between here and addPeerConn, unless it's for // failure. - cl.noLongerHalfOpen(t, addr.String()) + cl.noLongerHalfOpen(opts.t, opts.addr.String()) if err != nil { if cl.config.Debug { - cl.logger.Levelf(log.Debug, "error establishing outgoing connection to %v: %v", addr, err) + cl.logger.Levelf(log.Debug, "error establishing outgoing connection to %v: %v", opts.addr, err) } return } defer c.close() c.Discovery = ps c.trusted = trusted - t.runHandshookConnLoggingErr(c) + opts.t.runHandshookConnLoggingErr(c) } // The port number for incoming peer connections. 0 if the client isn't listening. @@ -1043,7 +1123,8 @@ func (cl *Client) sendInitialMessages(conn *PeerConn, torrent *Torrent) { ExtendedPayload: func() []byte { msg := pp.ExtendedHandshakeMessage{ M: map[pp.ExtensionName]pp.ExtensionNumber{ - pp.ExtensionNameMetadata: metadataExtendedId, + pp.ExtensionNameMetadata: metadataExtendedId, + utHolepunch.ExtensionName: utHolepunchExtendedId, }, V: cl.config.ExtendedHandshakeClientVersion, Reqq: localClientReqq, @@ -1212,7 +1293,7 @@ func (cl *Client) newTorrentOpt(opts AddTorrentOpts) (t *Torrent) { t.smartBanCache.Hash = sha1.Sum t.smartBanCache.Init() t.networkingEnabled.Set() - t.logger = cl.logger.WithContextValue(t).WithNames("torrent", t.infoHash.HexString()) + t.logger = cl.logger.WithContextValue(t).WithNames("torrent", t.infoHash.HexString()).WithDefaultLevel(log.Debug) t.sourcesLogger = t.logger.WithNames("sources") if opts.ChunkSize == 0 { opts.ChunkSize = defaultChunkSize @@ -1510,13 +1591,17 @@ func (cl *Client) newConnection(nc net.Conn, opts newConnectionOpts) (c *PeerCon } } c.peerImpl = c - c.logger = cl.logger.WithDefaultLevel(log.Warning).WithContextValue(c) + c.logger = cl.logger.WithDefaultLevel(log.Warning) c.setRW(connStatsReadWriter{nc, c}) c.r = &rateLimitedReader{ l: cl.config.DownloadRateLimiter, r: c.r, } - c.logger.WithDefaultLevel(log.Debug).Printf("initialized with remote %v over network %v (outgoing=%t)", opts.remoteAddr, opts.network, opts.outgoing) + c.logger.Levelf( + log.Debug, + "new PeerConn %p [Client %p remoteAddr %v network %v outgoing %t]", + c, cl, opts.remoteAddr, opts.network, opts.outgoing, + ) for _, f := range cl.config.Callbacks.NewPeer { f(&c.Peer) } diff --git a/vendor/github.com/anacrolix/torrent/global.go b/vendor/github.com/anacrolix/torrent/global.go index 988d434a28..dfb6bd4ced 100644 --- a/vendor/github.com/anacrolix/torrent/global.go +++ b/vendor/github.com/anacrolix/torrent/global.go @@ -24,6 +24,7 @@ const ( const ( metadataExtendedId = iota + 1 // 0 is reserved for deleting keys pexExtendedId + utHolepunchExtendedId ) func defaultPeerExtensionBytes() PeerExtensionBits { diff --git a/vendor/github.com/anacrolix/torrent/netip-addrport.go b/vendor/github.com/anacrolix/torrent/netip-addrport.go new file mode 100644 index 0000000000..cf9edfd5ed --- /dev/null +++ b/vendor/github.com/anacrolix/torrent/netip-addrport.go @@ -0,0 +1,44 @@ +package torrent + +import ( + "fmt" + "net" + "net/netip" + + "github.com/anacrolix/dht/v2/krpc" +) + +func ipv4AddrPortFromKrpcNodeAddr(na krpc.NodeAddr) (_ netip.AddrPort, err error) { + ip4 := na.IP.To4() + if ip4 == nil { + err = fmt.Errorf("not an ipv4 address: %v", na.IP) + return + } + addr := netip.AddrFrom4([4]byte(ip4)) + addrPort := netip.AddrPortFrom(addr, uint16(na.Port)) + return addrPort, nil +} + +func ipv6AddrPortFromKrpcNodeAddr(na krpc.NodeAddr) (_ netip.AddrPort, err error) { + ip6 := na.IP.To16() + if ip6 == nil { + err = fmt.Errorf("not an ipv4 address: %v", na.IP) + return + } + addr := netip.AddrFrom16([16]byte(ip6)) + addrPort := netip.AddrPortFrom(addr, uint16(na.Port)) + return addrPort, nil +} + +func addrPortFromPeerRemoteAddr(pra PeerRemoteAddr) (netip.AddrPort, error) { + switch v := pra.(type) { + case *net.TCPAddr: + return v.AddrPort(), nil + case *net.UDPAddr: + return v.AddrPort(), nil + case netip.AddrPort: + return v, nil + default: + return netip.ParseAddrPort(pra.String()) + } +} diff --git a/vendor/github.com/anacrolix/torrent/peer.go b/vendor/github.com/anacrolix/torrent/peer.go index d5ed19e53a..1d8ead1002 100644 --- a/vendor/github.com/anacrolix/torrent/peer.go +++ b/vendor/github.com/anacrolix/torrent/peer.go @@ -21,7 +21,7 @@ import ( "github.com/anacrolix/torrent/mse" pp "github.com/anacrolix/torrent/peer_protocol" request_strategy "github.com/anacrolix/torrent/request-strategy" - "github.com/anacrolix/torrent/typed-roaring" + typedRoaring "github.com/anacrolix/torrent/typed-roaring" ) type ( @@ -86,7 +86,6 @@ type ( peerChoking bool peerRequests map[Request]*peerRequestState PeerPrefersEncryption bool // as indicated by 'e' field in extension handshake - PeerListenPort int // The highest possible number of pieces the torrent could have based on // communication with the peer. Generally only useful until we have the // torrent info. @@ -117,6 +116,7 @@ type ( ) const ( + PeerSourceUtHolepunch = "C" PeerSourceTracker = "Tr" PeerSourceIncoming = "I" PeerSourceDhtGetPeers = "Hg" // Peers we found by searching a DHT. @@ -275,7 +275,7 @@ func (cn *Peer) iterContiguousPieceRequests(f func(piece pieceIndex, count int)) next(None[pieceIndex]()) } -func (cn *Peer) writeStatus(w io.Writer, t *Torrent) { +func (cn *Peer) writeStatus(w io.Writer) { // \t isn't preserved in
 blocks?
 	if cn.closed.IsSet() {
 		fmt.Fprint(w, "CLOSED: ")
diff --git a/vendor/github.com/anacrolix/torrent/peer_protocol/handshake.go b/vendor/github.com/anacrolix/torrent/peer_protocol/handshake.go
index acdc3da58f..76dc2b05d2 100644
--- a/vendor/github.com/anacrolix/torrent/peer_protocol/handshake.go
+++ b/vendor/github.com/anacrolix/torrent/peer_protocol/handshake.go
@@ -5,7 +5,10 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"math/bits"
 	"strconv"
+	"strings"
+	"unsafe"
 
 	"github.com/anacrolix/torrent/metainfo"
 )
@@ -33,8 +36,31 @@ type (
 	PeerExtensionBits [8]byte
 )
 
+var bitTags = []struct {
+	bit ExtensionBit
+	tag string
+}{
+	// Ordered by their base protocol type values (PORT, fast.., EXTENDED)
+	{ExtensionBitDHT, "dht"},
+	{ExtensionBitFast, "fast"},
+	{ExtensionBitExtended, "ext"},
+}
+
 func (pex PeerExtensionBits) String() string {
-	return hex.EncodeToString(pex[:])
+	pexHex := hex.EncodeToString(pex[:])
+	tags := make([]string, 0, len(bitTags)+1)
+	for _, bitTag := range bitTags {
+		if pex.GetBit(bitTag.bit) {
+			tags = append(tags, bitTag.tag)
+			pex.SetBit(bitTag.bit, false)
+		}
+	}
+	unknownCount := bits.OnesCount64(*(*uint64)((unsafe.Pointer(unsafe.SliceData(pex[:])))))
+	if unknownCount != 0 {
+		tags = append(tags, fmt.Sprintf("%v unknown", unknownCount))
+	}
+	return fmt.Sprintf("%v (%s)", pexHex, strings.Join(tags, ", "))
+
 }
 
 func NewPeerExtensionBytes(bits ...ExtensionBit) (ret PeerExtensionBits) {
diff --git a/vendor/github.com/anacrolix/torrent/peer_protocol/pex.go b/vendor/github.com/anacrolix/torrent/peer_protocol/pex.go
index 784aaa5925..466548a30a 100644
--- a/vendor/github.com/anacrolix/torrent/peer_protocol/pex.go
+++ b/vendor/github.com/anacrolix/torrent/peer_protocol/pex.go
@@ -28,6 +28,7 @@ func (m *PexMsg) Message(pexExtendedId ExtensionNumber) Message {
 	}
 }
 
+// Unmarshals and returns a PEX message.
 func LoadPexMsg(b []byte) (ret PexMsg, err error) {
 	err = bencode.Unmarshal(b, &ret)
 	return
diff --git a/vendor/github.com/anacrolix/torrent/peer_protocol/ut-holepunch/err-code.go b/vendor/github.com/anacrolix/torrent/peer_protocol/ut-holepunch/err-code.go
new file mode 100644
index 0000000000..42b1db1adf
--- /dev/null
+++ b/vendor/github.com/anacrolix/torrent/peer_protocol/ut-holepunch/err-code.go
@@ -0,0 +1,27 @@
+package utHolepunch
+
+type ErrCode uint32
+
+var _ error = ErrCode(0)
+
+const (
+	NoSuchPeer ErrCode = iota + 1
+	NotConnected
+	NoSupport
+	NoSelf
+)
+
+func (ec ErrCode) Error() string {
+	switch ec {
+	case NoSuchPeer:
+		return "target endpoint is invalid"
+	case NotConnected:
+		return "the relaying peer is not connected to the target peer"
+	case NoSupport:
+		return "the target peer does not support the holepunch extension"
+	case NoSelf:
+		return "the target endpoint belongs to the relaying peer"
+	default:
+		panic(ec)
+	}
+}
diff --git a/vendor/github.com/anacrolix/torrent/peer_protocol/ut-holepunch/ut-holepunch.go b/vendor/github.com/anacrolix/torrent/peer_protocol/ut-holepunch/ut-holepunch.go
new file mode 100644
index 0000000000..1436c43f22
--- /dev/null
+++ b/vendor/github.com/anacrolix/torrent/peer_protocol/ut-holepunch/ut-holepunch.go
@@ -0,0 +1,84 @@
+package utHolepunch
+
+import (
+	"bytes"
+	"encoding/binary"
+	"fmt"
+	"net/netip"
+)
+
+const ExtensionName = "ut_holepunch"
+
+type (
+	Msg struct {
+		MsgType  MsgType
+		AddrPort netip.AddrPort
+		ErrCode  ErrCode
+	}
+	MsgType  byte
+	AddrType byte
+)
+
+const (
+	Rendezvous MsgType = iota
+	Connect
+	Error
+)
+
+const (
+	Ipv4 AddrType = iota
+	Ipv6 AddrType = iota
+)
+
+func (m *Msg) UnmarshalBinary(b []byte) error {
+	if len(b) < 12 {
+		return fmt.Errorf("buffer too small to be valid")
+	}
+	m.MsgType = MsgType(b[0])
+	b = b[1:]
+	addrType := AddrType(b[0])
+	b = b[1:]
+	var addr netip.Addr
+	switch addrType {
+	case Ipv4:
+		addr = netip.AddrFrom4([4]byte(b[:4]))
+		b = b[4:]
+	case Ipv6:
+		if len(b) < 22 {
+			return fmt.Errorf("not enough bytes")
+		}
+		addr = netip.AddrFrom16([16]byte(b[:16]))
+		b = b[16:]
+	default:
+		return fmt.Errorf("unhandled addr type value %v", addrType)
+	}
+	port := binary.BigEndian.Uint16(b[:])
+	b = b[2:]
+	m.AddrPort = netip.AddrPortFrom(addr, port)
+	m.ErrCode = ErrCode(binary.BigEndian.Uint32(b[:]))
+	b = b[4:]
+	if len(b) != 0 {
+		return fmt.Errorf("%v trailing unused bytes", len(b))
+	}
+	return nil
+}
+
+func (m *Msg) MarshalBinary() (_ []byte, err error) {
+	var buf bytes.Buffer
+	buf.Grow(24)
+	buf.WriteByte(byte(m.MsgType))
+	addr := m.AddrPort.Addr()
+	switch {
+	case addr.Is4():
+		buf.WriteByte(byte(Ipv4))
+	case addr.Is6():
+		buf.WriteByte(byte(Ipv6))
+	default:
+		err = fmt.Errorf("unhandled addr type: %v", addr)
+		return
+	}
+	buf.Write(addr.AsSlice())
+	binary.Write(&buf, binary.BigEndian, m.AddrPort.Port())
+	binary.Write(&buf, binary.BigEndian, m.ErrCode)
+	return buf.Bytes(), nil
+}
diff --git a/vendor/github.com/anacrolix/torrent/peerconn.go b/vendor/github.com/anacrolix/torrent/peerconn.go
index 0318cb6e33..d85d0d511c 100644
--- a/vendor/github.com/anacrolix/torrent/peerconn.go
+++ b/vendor/github.com/anacrolix/torrent/peerconn.go
@@ -9,15 +9,18 @@ import (
 	"io"
 	"math/rand"
 	"net"
+	"net/netip"
 	"strconv"
 	"strings"
 	"time"
 
 	"github.com/RoaringBitmap/roaring"
+	"github.com/anacrolix/generics"
 	. "github.com/anacrolix/generics"
 	"github.com/anacrolix/log"
 	"github.com/anacrolix/missinggo/v2/bitmap"
 	"github.com/anacrolix/multiless"
+	"golang.org/x/exp/maps"
 	"golang.org/x/time/rate"
 
 	"github.com/anacrolix/torrent/bencode"
@@ -25,6 +28,7 @@ import (
 	"github.com/anacrolix/torrent/metainfo"
 	"github.com/anacrolix/torrent/mse"
 	pp "github.com/anacrolix/torrent/peer_protocol"
+	utHolepunch "github.com/anacrolix/torrent/peer_protocol/ut-holepunch"
 )
 
 // Maintains the state of a BitTorrent-protocol based connection with a peer.
@@ -38,6 +42,7 @@ type PeerConn struct {
 	// See BEP 3 etc.
 	PeerID             PeerID
 	PeerExtensionBytes pp.PeerExtensionBits
+	PeerListenPort     int
 
 	// The actual Conn, used for closing, and setting socket options. Do not use methods on this
 	// while holding any mutexes.
@@ -59,10 +64,48 @@ type PeerConn struct {
 	peerSentHaveAll bool
 
 	peerRequestDataAllocLimiter alloclim.Limiter
+
+	outstandingHolepunchingRendezvous map[netip.AddrPort]struct{}
+}
+
+func (cn *PeerConn) pexStatus() string {
+	if !cn.bitExtensionEnabled(pp.ExtensionBitExtended) {
+		return "extended protocol disabled"
+	}
+	if cn.PeerExtensionIDs == nil {
+		return "pending extended handshake"
+	}
+	if !cn.supportsExtension(pp.ExtensionNamePex) {
+		return "unsupported"
+	}
+	if true {
+		return fmt.Sprintf(
+			"%v conns, %v unsent events",
+			len(cn.pex.remoteLiveConns),
+			cn.pex.numPending(),
+		)
+	} else {
+		// This alternative branch prints out the remote live conn addresses.
+		return fmt.Sprintf(
+			"%v conns, %v unsent events",
+			strings.Join(generics.SliceMap(
+				maps.Keys(cn.pex.remoteLiveConns),
+				func(from netip.AddrPort) string {
+					return from.String()
+				}), ","),
+			cn.pex.numPending(),
+		)
+
+	}
 }
 
 func (cn *PeerConn) peerImplStatusLines() []string {
-	return []string{fmt.Sprintf("%+-55q %s %s", cn.PeerID, cn.PeerExtensionBytes, cn.connString)}
+	return []string{
+		cn.connString,
+		fmt.Sprintf("peer id: %+q", cn.PeerID),
+		fmt.Sprintf("extensions: %v", cn.PeerExtensionBytes),
+		fmt.Sprintf("pex: %s", cn.pexStatus()),
+	}
 }
 
 // Returns true if the connection is over IPv6.
@@ -74,10 +117,12 @@ func (cn *PeerConn) ipv6() bool {
 	return len(ip) == net.IPv6len
 }
 
-// Returns true the if the dialer/initiator has the lower client peer ID. TODO: Find the
-// specification for this.
+// Returns true the if the dialer/initiator has the higher client peer ID. See
+// https://github.com/arvidn/libtorrent/blame/272828e1cc37b042dfbbafa539222d8533e99755/src/bt_peer_connection.cpp#L3536-L3557.
+// As far as I can tell, Transmission just keeps the oldest connection.
 func (cn *PeerConn) isPreferredDirection() bool {
-	return bytes.Compare(cn.t.cl.peerID[:], cn.PeerID[:]) < 0 == cn.outgoing
+	// True if our client peer ID is higher than the remote's peer ID.
+	return bytes.Compare(cn.PeerID[:], cn.t.cl.peerID[:]) < 0 == cn.outgoing
 }
 
 // Returns whether the left connection should be preferred over the right one,
@@ -848,6 +893,7 @@ func (c *PeerConn) onReadExtendedMsg(id pp.ExtensionNumber, payload []byte) (err
 		c.requestPendingMetadata()
 		if !t.cl.config.DisablePEX {
 			t.pex.Add(c) // we learnt enough now
+			// This checks the extension is supported internally.
 			c.pex.Init(c)
 		}
 		return nil
@@ -861,7 +907,20 @@ func (c *PeerConn) onReadExtendedMsg(id pp.ExtensionNumber, payload []byte) (err
 		if !c.pex.IsEnabled() {
 			return nil // or hang-up maybe?
 		}
-		return c.pex.Recv(payload)
+		err = c.pex.Recv(payload)
+		if err != nil {
+			err = fmt.Errorf("receiving pex message: %w", err)
+		}
+		return
+	case utHolepunchExtendedId:
+		var msg utHolepunch.Msg
+		err = msg.UnmarshalBinary(payload)
+		if err != nil {
+			err = fmt.Errorf("unmarshalling ut_holepunch message: %w", err)
+			return
+		}
+		err = c.t.handleReceivedUtHolepunchMsg(msg, c)
+		return
 	default:
 		return fmt.Errorf("unexpected extended message ID: %v", id)
 	}
@@ -1007,6 +1066,8 @@ func (c *PeerConn) dialAddr() PeerRemoteAddr {
 			dialAddr := *addr
 			dialAddr.Port = c.PeerListenPort
 			return &dialAddr
+		default:
+			panic(addr)
 		}
 	}
 	return c.RemoteAddr
@@ -1033,3 +1094,18 @@ func (cn *PeerConn) PeerPieces() *roaring.Bitmap {
 func (pc *PeerConn) remoteIsTransmission() bool {
 	return bytes.HasPrefix(pc.PeerID[:], []byte("-TR")) && pc.PeerID[7] == '-'
 }
+
+func (pc *PeerConn) remoteAddrPort() Option[netip.AddrPort] {
+	return Some(pc.conn.RemoteAddr().(interface {
+		AddrPort() netip.AddrPort
+	}).AddrPort())
+}
+
+func (pc *PeerConn) remoteDialAddrPort() (netip.AddrPort, error) {
+	dialAddr := pc.dialAddr()
+	return addrPortFromPeerRemoteAddr(dialAddr)
+}
+
+func (pc *PeerConn) bitExtensionEnabled(bit pp.ExtensionBit) bool {
+	return pc.t.cl.config.Extensions.GetBit(bit) && pc.PeerExtensionBytes.GetBit(bit)
+}
diff --git a/vendor/github.com/anacrolix/torrent/pex.go b/vendor/github.com/anacrolix/torrent/pex.go
index 7fa0be887d..c5ed6099d3 100644
--- a/vendor/github.com/anacrolix/torrent/pex.go
+++ b/vendor/github.com/anacrolix/torrent/pex.go
@@ -3,7 +3,6 @@ package torrent
 import (
 	"net"
 	"sync"
-	"time"
 
 	"github.com/anacrolix/dht/v2/krpc"
 
@@ -145,8 +144,8 @@ func (me *pexMsgFactory) append(event pexEvent) {
 	}
 }
 
-func (me *pexMsgFactory) PexMsg() pp.PexMsg {
-	return me.msg
+func (me *pexMsgFactory) PexMsg() *pp.PexMsg {
+	return &me.msg
 }
 
 // Convert an arbitrary torrent peer Addr into one that can be represented by the compact addr
@@ -162,7 +161,6 @@ type pexState struct {
 	sync.RWMutex
 	tail *pexEvent     // event feed list
 	hold []pexEvent    // delayed drops
-	rest time.Time     // cooldown deadline on inbound
 	nc   int           // net number of alive conns
 	msg0 pexMsgFactory // initial message
 }
@@ -174,7 +172,6 @@ func (s *pexState) Reset() {
 	s.tail = nil
 	s.hold = nil
 	s.nc = 0
-	s.rest = time.Time{}
 	s.msg0 = pexMsgFactory{}
 }
 
@@ -225,7 +222,7 @@ func (s *pexState) Genmsg(start *pexEvent) (pp.PexMsg, *pexEvent) {
 	s.RLock()
 	defer s.RUnlock()
 	if start == nil {
-		return s.msg0.PexMsg(), s.tail
+		return *s.msg0.PexMsg(), s.tail
 	}
 	var msg pexMsgFactory
 	last := start
@@ -236,5 +233,18 @@ func (s *pexState) Genmsg(start *pexEvent) (pp.PexMsg, *pexEvent) {
 		msg.append(*e)
 		last = e
 	}
-	return msg.PexMsg(), last
+	return *msg.PexMsg(), last
+}
+
+// The same as Genmsg but just counts up the distinct events that haven't been sent.
+func (s *pexState) numPending(start *pexEvent) (num int) {
+	s.RLock()
+	defer s.RUnlock()
+	if start == nil {
+		return s.msg0.PexMsg().Len()
+	}
+	for e := start.next; e != nil; e = e.next {
+		num++
+	}
+	return
 }
diff --git a/vendor/github.com/anacrolix/torrent/pexconn.go b/vendor/github.com/anacrolix/torrent/pexconn.go
index d0308f756d..cc6c3fc679 100644
--- a/vendor/github.com/anacrolix/torrent/pexconn.go
+++ b/vendor/github.com/anacrolix/torrent/pexconn.go
@@ -2,8 +2,10 @@ package torrent
 
 import (
 	"fmt"
+	"net/netip"
 	"time"
 
+	g "github.com/anacrolix/generics"
 	"github.com/anacrolix/log"
 
 	pp "github.com/anacrolix/torrent/peer_protocol"
@@ -26,6 +28,9 @@ type pexConnState struct {
 	Listed  bool
 	info    log.Logger
 	dbg     log.Logger
+	// Running record of live connections the remote end of the connection purports to have.
+	remoteLiveConns map[netip.AddrPort]g.Option[pp.PexPeerFlags]
+	lastRecv        time.Time
 }
 
 func (s *pexConnState) IsEnabled() bool {
@@ -67,6 +72,13 @@ func (s *pexConnState) genmsg() *pp.PexMsg {
 	return &tx
 }
 
+func (s *pexConnState) numPending() int {
+	if s.torrent == nil {
+		return 0
+	}
+	return s.torrent.pex.numPending(s.last)
+}
+
 // Share is called from the writer goroutine if when it is woken up with the write buffers empty
 // Returns whether there's more room on the send buffer to write to.
 func (s *pexConnState) Share(postfn messageWriter) bool {
@@ -86,33 +98,53 @@ func (s *pexConnState) Share(postfn messageWriter) bool {
 	return true
 }
 
-// Recv is called from the reader goroutine
-func (s *pexConnState) Recv(payload []byte) error {
-	if !s.torrent.wantPeers() {
-		s.dbg.Printf("peer reserve ok, incoming PEX discarded")
-		return nil
+func (s *pexConnState) updateRemoteLiveConns(rx pp.PexMsg) (errs []error) {
+	for _, dropped := range rx.Dropped {
+		addrPort, _ := ipv4AddrPortFromKrpcNodeAddr(dropped)
+		delete(s.remoteLiveConns, addrPort)
 	}
-	if time.Now().Before(s.torrent.pex.rest) {
-		s.dbg.Printf("in cooldown period, incoming PEX discarded")
-		return nil
+	for _, dropped := range rx.Dropped6 {
+		addrPort, _ := ipv6AddrPortFromKrpcNodeAddr(dropped)
+		delete(s.remoteLiveConns, addrPort)
+	}
+	for i, added := range rx.Added {
+		addr := netip.AddrFrom4([4]byte(added.IP.To4()))
+		addrPort := netip.AddrPortFrom(addr, uint16(added.Port))
+		flags := g.SliceGet(rx.AddedFlags, i)
+		g.MakeMapIfNilAndSet(&s.remoteLiveConns, addrPort, flags)
 	}
+	for i, added := range rx.Added6 {
+		addr := netip.AddrFrom16([16]byte(added.IP.To16()))
+		addrPort := netip.AddrPortFrom(addr, uint16(added.Port))
+		flags := g.SliceGet(rx.Added6Flags, i)
+		g.MakeMapIfNilAndSet(&s.remoteLiveConns, addrPort, flags)
+	}
+	return
+}
 
+// Recv is called from the reader goroutine
+func (s *pexConnState) Recv(payload []byte) error {
 	rx, err := pp.LoadPexMsg(payload)
 	if err != nil {
-		return fmt.Errorf("error unmarshalling PEX message: %s", err)
+		return fmt.Errorf("unmarshalling pex message: %w", err)
 	}
-	s.dbg.Print("incoming PEX message: ", rx)
+	s.dbg.Printf("received pex message: %v", rx)
 	torrent.Add("pex added peers received", int64(len(rx.Added)))
 	torrent.Add("pex added6 peers received", int64(len(rx.Added6)))
 
+	// "Clients must batch updates to send no more than 1 PEX message per minute."
+	timeSinceLastRecv := time.Since(s.lastRecv)
+	if timeSinceLastRecv < 45*time.Second {
+		return fmt.Errorf("last received only %v ago", timeSinceLastRecv)
+	}
+	s.lastRecv = time.Now()
+	s.updateRemoteLiveConns(rx)
+
 	var peers peerInfos
 	peers.AppendFromPex(rx.Added6, rx.Added6Flags)
 	peers.AppendFromPex(rx.Added, rx.AddedFlags)
-	s.dbg.Printf("adding %d peers from PEX", len(peers))
-	if len(peers) > 0 {
-		s.torrent.pex.rest = time.Now().Add(pexInterval)
-		s.torrent.addPeers(peers)
-	}
+	added := s.torrent.addPeers(peers)
+	s.dbg.Printf("got %v peers over pex, added %v", len(peers), added)
 
 	// one day we may also want to:
 	// - check if the peer is not flooding us with PEX updates
diff --git a/vendor/github.com/anacrolix/torrent/socket.go b/vendor/github.com/anacrolix/torrent/socket.go
index 127cd29afe..c1fb97fdbc 100644
--- a/vendor/github.com/anacrolix/torrent/socket.go
+++ b/vendor/github.com/anacrolix/torrent/socket.go
@@ -4,13 +4,12 @@ import (
 	"context"
 	"net"
 	"strconv"
+	"syscall"
 
 	"github.com/anacrolix/log"
 	"github.com/anacrolix/missinggo/perf"
 	"github.com/anacrolix/missinggo/v2"
 	"github.com/pkg/errors"
-
-	"github.com/anacrolix/torrent/dialer"
 )
 
 type Listener interface {
@@ -38,13 +37,53 @@ func listen(n network, addr string, f firewallCallback, logger log.Logger) (sock
 	}
 }
 
+var tcpListenConfig = net.ListenConfig{
+	Control: func(network, address string, c syscall.RawConn) (err error) {
+		controlErr := c.Control(func(fd uintptr) {
+			err = setReusePortSockOpts(fd)
+		})
+		if err != nil {
+			return
+		}
+		err = controlErr
+		return
+	},
+	// BitTorrent connections manage their own keep-alives.
+	KeepAlive: -1,
+}
+
 func listenTcp(network, address string) (s socket, err error) {
-	l, err := net.Listen(network, address)
+	l, err := tcpListenConfig.Listen(context.Background(), network, address)
 	return tcpSocket{
 		Listener: l,
 		NetworkDialer: NetworkDialer{
 			Network: network,
-			Dialer:  dialer.Default,
+			Dialer: &net.Dialer{
+				// Dialling TCP from a local port limits us to a single outgoing TCP connection to
+				// each remote client. Instead this should be a last resort if we need to use holepunching, and only then to connect to other clients that actually try to holepunch TCP.
+				//LocalAddr: l.Addr(),
+
+				// We don't want fallback, as we explicitly manage the IPv4/IPv6 distinction
+				// ourselves, although it's probably not triggered as I think the network is already
+				// constrained to tcp4 or tcp6 at this point.
+				FallbackDelay: -1,
+				// BitTorrent connections manage their own keep-alives.
+				KeepAlive: tcpListenConfig.KeepAlive,
+				Control: func(network, address string, c syscall.RawConn) (err error) {
+					controlErr := c.Control(func(fd uintptr) {
+						err = setSockNoLinger(fd)
+						if err != nil {
+							// Failing to disable linger is undesirable, but not fatal.
+							log.Printf("error setting linger socket option on tcp socket: %v", err)
+						}
+						err = setReusePortSockOpts(fd)
+					})
+					if err == nil {
+						err = controlErr
+					}
+					return
+				},
+			},
 		},
 	}, err
 }
diff --git a/vendor/github.com/anacrolix/torrent/sockopts.go b/vendor/github.com/anacrolix/torrent/sockopts.go
new file mode 100644
index 0000000000..54f307d1d1
--- /dev/null
+++ b/vendor/github.com/anacrolix/torrent/sockopts.go
@@ -0,0 +1,10 @@
+//go:build !wasm
+
+package torrent
+
+import "syscall"
+
+var lingerOffVal = syscall.Linger{
+	Onoff:  0,
+	Linger: 0,
+}
diff --git a/vendor/github.com/anacrolix/torrent/sockopts_unix.go b/vendor/github.com/anacrolix/torrent/sockopts_unix.go
new file mode 100644
index 0000000000..52ec9e8d37
--- /dev/null
+++ b/vendor/github.com/anacrolix/torrent/sockopts_unix.go
@@ -0,0 +1,29 @@
+//go:build !windows && !wasm
+
+package torrent
+
+import (
+	"syscall"
+
+	"golang.org/x/sys/unix"
+)
+
+func setReusePortSockOpts(fd uintptr) (err error) {
+	// I would use libp2p/go-reuseport to do this here, but no surprise it's
+	// implemented incorrectly.
+
+	// Looks like we can get away with just REUSEPORT at least on Darwin, and probably by
+	// extension BSDs and Linux.
+	if false {
+		err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
+		if err != nil {
+			return
+		}
+	}
+	err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1)
+	return
+}
+
+func setSockNoLinger(fd uintptr) (err error) {
+	return syscall.SetsockoptLinger(int(fd), syscall.SOL_SOCKET, syscall.SO_LINGER, &lingerOffVal)
+}
diff --git a/vendor/github.com/anacrolix/torrent/sockopts_wasm.go b/vendor/github.com/anacrolix/torrent/sockopts_wasm.go
new file mode 100644
index 0000000000..9705b91420
--- /dev/null
+++ b/vendor/github.com/anacrolix/torrent/sockopts_wasm.go
@@ -0,0 +1,12 @@
+package torrent
+
+// It's possible that we either need to use JS-specific way to allow port reuse, or to fall back to
+// dialling TCP without forcing the local address to match the listener. If the fallback is
+// implemented, then this should probably return an error to trigger it.
+func setReusePortSockOpts(fd uintptr) error {
+	return nil
+}
+
+func setSockNoLinger(fd uintptr) error {
+	return nil
+}
diff --git a/vendor/github.com/anacrolix/torrent/sockopts_windows.go b/vendor/github.com/anacrolix/torrent/sockopts_windows.go
new file mode 100644
index 0000000000..c3c0ab0494
--- /dev/null
+++ b/vendor/github.com/anacrolix/torrent/sockopts_windows.go
@@ -0,0 +1,15 @@
+package torrent
+
+import (
+	"syscall"
+
+	"golang.org/x/sys/windows"
+)
+
+func setReusePortSockOpts(fd uintptr) (err error) {
+	return windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1)
+}
+
+func setSockNoLinger(fd uintptr) (err error) {
+	return syscall.SetsockoptLinger(syscall.Handle(fd), syscall.SOL_SOCKET, syscall.SO_LINGER, &lingerOffVal)
+}
diff --git a/vendor/github.com/anacrolix/torrent/torrent.go b/vendor/github.com/anacrolix/torrent/torrent.go
index f8d31b647d..c7ca254923 100644
--- a/vendor/github.com/anacrolix/torrent/torrent.go
+++ b/vendor/github.com/anacrolix/torrent/torrent.go
@@ -22,6 +22,7 @@ import (
 	"github.com/anacrolix/chansync/events"
 	"github.com/anacrolix/dht/v2"
 	. "github.com/anacrolix/generics"
+	g "github.com/anacrolix/generics"
 	"github.com/anacrolix/log"
 	"github.com/anacrolix/missinggo/perf"
 	"github.com/anacrolix/missinggo/slices"
@@ -32,11 +33,13 @@ import (
 	"github.com/anacrolix/sync"
 	"github.com/davecgh/go-spew/spew"
 	"github.com/pion/datachannel"
+	"golang.org/x/exp/maps"
 
 	"github.com/anacrolix/torrent/bencode"
 	"github.com/anacrolix/torrent/common"
 	"github.com/anacrolix/torrent/metainfo"
 	pp "github.com/anacrolix/torrent/peer_protocol"
+	utHolepunch "github.com/anacrolix/torrent/peer_protocol/ut-holepunch"
 	request_strategy "github.com/anacrolix/torrent/request-strategy"
 	"github.com/anacrolix/torrent/segments"
 	"github.com/anacrolix/torrent/storage"
@@ -102,13 +105,15 @@ type Torrent struct {
 	// Set of addrs to which we're attempting to connect. Connections are
 	// half-open until all handshakes are completed.
 	halfOpen map[string]PeerInfo
+	// The final ess is not silent here as it's in the plural.
+	utHolepunchRendezvous map[netip.AddrPort]*utHolepunchRendezvous
 
 	// Reserve of peers to connect to. A peer can be both here and in the
 	// active connections if were told about the peer after connecting with
 	// them. That encourages us to reconnect to peers that are well known in
 	// the swarm.
 	peers prioritizedPeers
-	// Whether we want to know to know more peers.
+	// Whether we want to know more peers.
 	wantPeersEvent missinggo.Event
 	// An announcer for each tracker URL.
 	trackerAnnouncers map[string]torrentTrackerAnnouncer
@@ -759,24 +764,37 @@ func (t *Torrent) writeStatus(w io.Writer) {
 	spew.NewDefaultConfig()
 	spew.Fdump(w, t.statsLocked())
 
-	peers := t.peersAsSlice()
-	sort.Slice(peers, func(_i, _j int) bool {
-		i := peers[_i]
-		j := peers[_j]
-		if less, ok := multiless.New().EagerSameLess(
-			i.downloadRate() == j.downloadRate(), i.downloadRate() < j.downloadRate(),
-		).LessOk(); ok {
-			return less
-		}
-		return worseConn(i, j)
+	fmt.Fprintf(w, "webseeds:\n")
+	t.writePeerStatuses(w, maps.Values(t.webSeeds))
+
+	peerConns := maps.Keys(t.conns)
+	// Peers without priorities first, then those with. I'm undecided about how to order peers
+	// without priorities.
+	sort.Slice(peerConns, func(li, ri int) bool {
+		l := peerConns[li]
+		r := peerConns[ri]
+		ml := multiless.New()
+		lpp := g.ResultFromTuple(l.peerPriority()).ToOption()
+		rpp := g.ResultFromTuple(r.peerPriority()).ToOption()
+		ml = ml.Bool(lpp.Ok, rpp.Ok)
+		ml = ml.Uint32(rpp.Value, lpp.Value)
+		return ml.Less()
 	})
+
+	fmt.Fprintf(w, "peer conns:\n")
+	t.writePeerStatuses(w, g.SliceMap(peerConns, func(pc *PeerConn) *Peer {
+		return &pc.Peer
+	}))
+}
+
+func (t *Torrent) writePeerStatuses(w io.Writer, peers []*Peer) {
 	var buf bytes.Buffer
-	for i, c := range peers {
-		fmt.Fprintf(w, "%2d. ", i+1)
+	for _, c := range peers {
+		fmt.Fprintf(w, "- ")
 		buf.Reset()
-		c.writeStatus(&buf, t)
+		c.writeStatus(&buf)
 		w.Write(bytes.TrimRight(
-			bytes.ReplaceAll(buf.Bytes(), []byte("\n"), []byte("\n    ")),
+			bytes.ReplaceAll(buf.Bytes(), []byte("\n"), []byte("\n  ")),
 			" "))
 	}
 }
@@ -1361,7 +1379,7 @@ func (t *Torrent) openNewConns() (initiated int) {
 			return
 		}
 		p := t.peers.PopMax()
-		t.initiateConn(p)
+		t.initiateConn(p, false, false)
 		initiated++
 	}
 	return
@@ -1983,8 +2001,10 @@ func (t *Torrent) addPeerConn(c *PeerConn) (err error) {
 		panic(len(t.conns))
 	}
 	t.conns[c] = struct{}{}
+	t.cl.event.Broadcast()
+	// We'll never receive the "p" extended handshake parameter.
 	if !t.cl.config.DisablePEX && !c.PeerExtensionBytes.SupportsExtended() {
-		t.pex.Add(c) // as no further extended handshake expected
+		t.pex.Add(c)
 	}
 	return nil
 }
@@ -2341,8 +2361,13 @@ func (t *Torrent) VerifyData() {
 	}
 }
 
-// Start the process of connecting to the given peer for the given torrent if appropriate.
-func (t *Torrent) initiateConn(peer PeerInfo) {
+// Start the process of connecting to the given peer for the given torrent if appropriate. I'm not
+// sure all the PeerInfo fields are being used.
+func (t *Torrent) initiateConn(
+	peer PeerInfo,
+	requireRendezvous bool,
+	skipHolepunchRendezvous bool,
+) {
 	if peer.Id == t.cl.peerID {
 		return
 	}
@@ -2355,7 +2380,12 @@ func (t *Torrent) initiateConn(peer PeerInfo) {
 	}
 	t.cl.numHalfOpen++
 	t.halfOpen[addr.String()] = peer
-	go t.cl.outgoingConnection(t, addr, peer.Source, peer.Trusted)
+	go t.cl.outgoingConnection(outgoingConnOpts{
+		t:                       t,
+		addr:                    peer.Addr,
+		requireRendezvous:       requireRendezvous,
+		skipHolepunchRendezvous: skipHolepunchRendezvous,
+	}, peer.Source, peer.Trusted)
 }
 
 // Adds a trusted, pending peer for each of the given Client's addresses. Typically used in tests to
@@ -2648,3 +2678,135 @@ func (t *Torrent) checkValidReceiveChunk(r Request) error {
 	// catch most of the overflow manipulation stuff by checking index and begin above.
 	return nil
 }
+
+func (t *Torrent) peerConnsWithDialAddrPort(target netip.AddrPort) (ret []*PeerConn) {
+	for pc := range t.conns {
+		dialAddr, err := pc.remoteDialAddrPort()
+		if err != nil {
+			continue
+		}
+		if dialAddr != target {
+			continue
+		}
+		ret = append(ret, pc)
+	}
+	return
+}
+
+func makeUtHolepunchMsgForPeerConn(
+	recipient *PeerConn,
+	msgType utHolepunch.MsgType,
+	addrPort netip.AddrPort,
+	errCode utHolepunch.ErrCode,
+) pp.Message {
+	utHolepunchMsg := utHolepunch.Msg{
+		MsgType:  msgType,
+		AddrPort: addrPort,
+		ErrCode:  errCode,
+	}
+	extendedPayload, err := utHolepunchMsg.MarshalBinary()
+	if err != nil {
+		panic(err)
+	}
+	return pp.Message{
+		Type:            pp.Extended,
+		ExtendedID:      MapMustGet(recipient.PeerExtensionIDs, utHolepunch.ExtensionName),
+		ExtendedPayload: extendedPayload,
+	}
+}
+
+func sendUtHolepunchMsg(
+	pc *PeerConn,
+	msgType utHolepunch.MsgType,
+	addrPort netip.AddrPort,
+	errCode utHolepunch.ErrCode,
+) {
+	pc.write(makeUtHolepunchMsgForPeerConn(pc, msgType, addrPort, errCode))
+}
+
+func (t *Torrent) handleReceivedUtHolepunchMsg(msg utHolepunch.Msg, sender *PeerConn) error {
+	switch msg.MsgType {
+	case utHolepunch.Rendezvous:
+		t.logger.Printf("got holepunch rendezvous request for %v from %p", msg.AddrPort, sender)
+		sendMsg := sendUtHolepunchMsg
+		senderAddrPort, err := sender.remoteDialAddrPort()
+		if err != nil {
+			sender.logger.Levelf(
+				log.Warning,
+				"error getting ut_holepunch rendezvous sender's dial address: %v",
+				err,
+			)
+			// There's no better error code. The sender's address itself is invalid. I don't see
+			// this error message being appropriate anywhere else anyway.
+			sendMsg(sender, utHolepunch.Error, msg.AddrPort, utHolepunch.NoSuchPeer)
+		}
+		targets := t.peerConnsWithDialAddrPort(msg.AddrPort)
+		if len(targets) == 0 {
+			sendMsg(sender, utHolepunch.Error, msg.AddrPort, utHolepunch.NotConnected)
+			return nil
+		}
+		for _, pc := range targets {
+			if !pc.supportsExtension(utHolepunch.ExtensionName) {
+				sendMsg(sender, utHolepunch.Error, msg.AddrPort, utHolepunch.NoSupport)
+				continue
+			}
+			sendMsg(sender, utHolepunch.Connect, msg.AddrPort, 0)
+			sendMsg(pc, utHolepunch.Connect, senderAddrPort, 0)
+		}
+		return nil
+	case utHolepunch.Connect:
+		t.logger.Printf("got holepunch connect from %v for %v", sender, msg.AddrPort)
+		rz, ok := t.utHolepunchRendezvous[msg.AddrPort]
+		if ok {
+			delete(rz.relays, sender)
+			rz.gotConnect.Set()
+			rz.relayCond.Broadcast()
+		} else {
+			// If the rendezvous was removed because we timed out or already got a connect signal,
+			// it doesn't hurt to try again.
+			t.initiateConn(PeerInfo{
+				Addr:   msg.AddrPort,
+				Source: PeerSourceUtHolepunch,
+			}, false, true)
+		}
+		return nil
+	case utHolepunch.Error:
+		rz, ok := t.utHolepunchRendezvous[msg.AddrPort]
+		if ok {
+			delete(rz.relays, sender)
+			rz.relayCond.Broadcast()
+		}
+		t.logger.Printf("received ut_holepunch error message from %v: %v", sender, msg.ErrCode)
+		return nil
+	default:
+		return fmt.Errorf("unhandled msg type %v", msg.MsgType)
+	}
+}
+
+func (t *Torrent) startHolepunchRendezvous(addrPort netip.AddrPort) (rz *utHolepunchRendezvous, err error) {
+	if MapContains(t.utHolepunchRendezvous, addrPort) {
+		err = errors.New("rendezvous already exists")
+		return
+	}
+	g.InitNew(&rz)
+	for pc := range t.conns {
+		if !pc.supportsExtension(utHolepunch.ExtensionName) {
+			continue
+		}
+		if pc.supportsExtension(pp.ExtensionNamePex) {
+			if !g.MapContains(pc.pex.remoteLiveConns, addrPort) {
+				continue
+			}
+		}
+		sendUtHolepunchMsg(pc, utHolepunch.Rendezvous, addrPort, 0)
+		MakeMapIfNilAndSet(&rz.relays, pc, struct{}{})
+	}
+	if len(rz.relays) == 0 {
+		err = fmt.Errorf("no eligible relays")
+		return
+	}
+	if !MakeMapIfNilAndSet(&t.utHolepunchRendezvous, addrPort, rz) {
+		panic("expected to fail earlier if rendezvous already exists")
+	}
+	return
+}
diff --git a/vendor/github.com/anacrolix/torrent/ut-holepunching.go b/vendor/github.com/anacrolix/torrent/ut-holepunching.go
new file mode 100644
index 0000000000..08f0ac79e4
--- /dev/null
+++ b/vendor/github.com/anacrolix/torrent/ut-holepunching.go
@@ -0,0 +1,11 @@
+package torrent
+
+import (
+	"github.com/anacrolix/chansync"
+)
+
+type utHolepunchRendezvous struct {
+	relays     map[*PeerConn]struct{}
+	gotConnect chansync.SetOnce
+	relayCond  chansync.BroadcastCond
+}
diff --git a/vendor/github.com/cockroachdb/pebble/mem_table.go b/vendor/github.com/cockroachdb/pebble/mem_table.go
index 6110470c48..86ef7a3540 100644
--- a/vendor/github.com/cockroachdb/pebble/mem_table.go
+++ b/vendor/github.com/cockroachdb/pebble/mem_table.go
@@ -143,6 +143,7 @@ func newMemTable(opts memTableOptions) *memTable {
 	m.skl.Reset(arena, m.cmp)
 	m.rangeDelSkl.Reset(arena, m.cmp)
 	m.rangeKeySkl.Reset(arena, m.cmp)
+	m.reserved = arena.Size()
 	return m
 }
 
diff --git a/vendor/github.com/cockroachdb/pebble/objstorage/objstorage.go b/vendor/github.com/cockroachdb/pebble/objstorage/objstorage.go
index e51c2ce76d..5cc22a1347 100644
--- a/vendor/github.com/cockroachdb/pebble/objstorage/objstorage.go
+++ b/vendor/github.com/cockroachdb/pebble/objstorage/objstorage.go
@@ -105,7 +105,7 @@ type CreatorID uint64
 // IsSet returns true if the CreatorID is not zero.
 func (c CreatorID) IsSet() bool { return c != 0 }
 
-func (c CreatorID) String() string { return fmt.Sprintf("%020d", c) }
+func (c CreatorID) String() string { return fmt.Sprintf("%d", c) }
 
 // SharedCleanupMethod indicates the method for cleaning up unused shared objects.
 type SharedCleanupMethod uint8
diff --git a/vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/shared.go b/vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/shared.go
index 8660c160b8..66ea1ba69f 100644
--- a/vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/shared.go
+++ b/vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/shared.go
@@ -6,7 +6,6 @@ package objstorageprovider
 
 import (
 	"context"
-	"fmt"
 	"sync"
 	"sync/atomic"
 
@@ -136,50 +135,6 @@ func (p *provider) sharedPath(meta objstorage.ObjectMetadata) string {
 	return "shared://" + sharedObjectName(meta)
 }
 
-// sharedObjectName returns the name of an object on shared storage.
-//
-// For sstables, the format is: -.sst
-// For example: 00000000000000000002-000001.sst
-func sharedObjectName(meta objstorage.ObjectMetadata) string {
-	// TODO(radu): prepend a "shard" value for better distribution within the bucket?
-	return fmt.Sprintf(
-		"%s-%s",
-		meta.Shared.CreatorID, base.MakeFilename(meta.FileType, meta.Shared.CreatorFileNum),
-	)
-}
-
-// sharedObjectRefName returns the name of the object's ref marker associated
-// with this provider. This name is the object's name concatenated with
-// ".ref..".
-//
-// For example: 00000000000000000002-000001.sst.ref.00000000000000000005.000008
-func (p *provider) sharedObjectRefName(meta objstorage.ObjectMetadata) string {
-	if meta.Shared.CleanupMethod != objstorage.SharedRefTracking {
-		panic("ref object used when ref tracking disabled")
-	}
-	return sharedObjectRefName(meta, p.shared.creatorID, meta.DiskFileNum)
-}
-
-func sharedObjectRefName(
-	meta objstorage.ObjectMetadata, refCreatorID objstorage.CreatorID, refFileNum base.DiskFileNum,
-) string {
-	if meta.Shared.CleanupMethod != objstorage.SharedRefTracking {
-		panic("ref object used when ref tracking disabled")
-	}
-	return fmt.Sprintf(
-		"%s-%s.ref.%s.%s",
-		meta.Shared.CreatorID, base.MakeFilename(meta.FileType, meta.Shared.CreatorFileNum), refCreatorID, refFileNum,
-	)
-
-}
-
-func sharedObjectRefPrefix(meta objstorage.ObjectMetadata) string {
-	return fmt.Sprintf(
-		"%s-%s.ref.",
-		meta.Shared.CreatorID, base.MakeFilename(meta.FileType, meta.Shared.CreatorFileNum),
-	)
-}
-
 // sharedCreateRef creates a reference marker object.
 func (p *provider) sharedCreateRef(meta objstorage.ObjectMetadata) error {
 	if meta.Shared.CleanupMethod != objstorage.SharedRefTracking {
diff --git a/vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/shared_obj_name.go b/vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/shared_obj_name.go
new file mode 100644
index 0000000000..49695f8331
--- /dev/null
+++ b/vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/shared_obj_name.go
@@ -0,0 +1,80 @@
+// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
+// of this source code is governed by a BSD-style license that can be found in
+// the LICENSE file.
+
+package objstorageprovider
+
+import (
+	"fmt"
+
+	"github.com/cockroachdb/pebble/internal/base"
+	"github.com/cockroachdb/pebble/objstorage"
+)
+
+// sharedObjectName returns the name of an object on shared storage.
+//
+// For sstables, the format is: --.sst
+// For example: 1a3f-2-000001.sst
+func sharedObjectName(meta objstorage.ObjectMetadata) string {
+	switch meta.FileType {
+	case base.FileTypeTable:
+		return fmt.Sprintf(
+			"%04x-%d-%06d.sst",
+			objHash(meta), meta.Shared.CreatorID, meta.Shared.CreatorFileNum.FileNum(),
+		)
+	}
+	panic("unknown FileType")
+}
+
+// sharedObjectRefName returns the name of the object's ref marker associated
+// with a given referencing provider. This name is the object's name concatenated with
+// ".ref..".
+//
+// For example: 1a3f-2-000001.sst.ref.5.000008
+func sharedObjectRefName(
+	meta objstorage.ObjectMetadata, refCreatorID objstorage.CreatorID, refFileNum base.DiskFileNum,
+) string {
+	if meta.Shared.CleanupMethod != objstorage.SharedRefTracking {
+		panic("ref object used when ref tracking disabled")
+	}
+	switch meta.FileType {
+	case base.FileTypeTable:
+		return fmt.Sprintf(
+			"%04x-%d-%06d.sst.ref.%d.%06d",
+			objHash(meta), meta.Shared.CreatorID, meta.Shared.CreatorFileNum.FileNum(), refCreatorID, refFileNum.FileNum(),
+		)
+	}
+	panic("unknown FileType")
+}
+
+func sharedObjectRefPrefix(meta objstorage.ObjectMetadata) string {
+	switch meta.FileType {
+	case base.FileTypeTable:
+		return fmt.Sprintf(
+			"%04x-%d-%06d.sst.ref.",
+			objHash(meta), meta.Shared.CreatorID, meta.Shared.CreatorFileNum.FileNum(),
+		)
+	}
+	panic("unknown FileType")
+}
+
+// sharedObjectRefName returns the name of the object's ref marker associated
+// with this provider. This name is the object's name concatenated with
+// ".ref..".
+//
+// For example: 1a3f-2-000001.sst.ref.5.000008
+func (p *provider) sharedObjectRefName(meta objstorage.ObjectMetadata) string {
+	if meta.Shared.CleanupMethod != objstorage.SharedRefTracking {
+		panic("ref object used when ref tracking disabled")
+	}
+	return sharedObjectRefName(meta, p.shared.creatorID, meta.DiskFileNum)
+}
+
+// objHash returns a 16-bit hash value derived from the creator ID and creator
+// file num. We prepend this value to object names to ensure balanced
+// partitioning with AWS (and likely other blob storage providers).
+func objHash(meta objstorage.ObjectMetadata) uint16 {
+	const prime1 = 7459
+	const prime2 = 17539
+	return uint16(uint64(meta.Shared.CreatorID)*prime1 + uint64(meta.Shared.CreatorFileNum.FileNum())*prime2)
+}
diff --git a/vendor/github.com/cockroachdb/pebble/sstable/reader.go b/vendor/github.com/cockroachdb/pebble/sstable/reader.go
index 0d86f171da..97f027e817 100644
--- a/vendor/github.com/cockroachdb/pebble/sstable/reader.go
+++ b/vendor/github.com/cockroachdb/pebble/sstable/reader.go
@@ -504,14 +504,6 @@ func (i *singleLevelIterator) initBounds() {
 	}
 }
 
-func (i *singleLevelIterator) keyOutOfBounds(key []byte, upper []byte, upperInclusive bool) bool {
-	if upper == nil {
-		return false
-	}
-	cmp := i.cmp(key, upper)
-	return (!upperInclusive && cmp >= 0) || cmp > 0
-}
-
 type loadBlockResult int8
 
 const (
@@ -694,9 +686,12 @@ func (i *singleLevelIterator) trySeekGEUsingNextWithinBlock(
 	for j := 0; j < numStepsBeforeSeek; j++ {
 		curKeyCmp := i.cmp(k.UserKey, key)
 		if curKeyCmp >= 0 {
-			if i.keyOutOfBounds(k.UserKey, i.blockUpper, i.endKeyInclusive) {
-				i.exhaustedBounds = +1
-				return nil, base.LazyValue{}, true
+			if i.blockUpper != nil {
+				cmp := i.cmp(k.UserKey, i.blockUpper)
+				if (!i.endKeyInclusive && cmp >= 0) || cmp > 0 {
+					i.exhaustedBounds = +1
+					return nil, base.LazyValue{}, true
+				}
 			}
 			return k, v, true
 		}
@@ -854,9 +849,12 @@ func (i *singleLevelIterator) seekGEHelper(
 				less = i.cmp(currKey.UserKey, key) < 0
 			}
 			if !less {
-				if i.keyOutOfBounds(currKey.UserKey, i.blockUpper, i.endKeyInclusive) {
-					i.exhaustedBounds = +1
-					return nil, base.LazyValue{}
+				if i.blockUpper != nil {
+					cmp := i.cmp(currKey.UserKey, i.blockUpper)
+					if (!i.endKeyInclusive && cmp >= 0) || cmp > 0 {
+						i.exhaustedBounds = +1
+						return nil, base.LazyValue{}
+					}
 				}
 				return currKey, value
 			}
@@ -887,9 +885,12 @@ func (i *singleLevelIterator) seekGEHelper(
 			// though this is the block separator, the same user key can span
 			// multiple blocks. If upper is exclusive we use >= below, else
 			// we use >.
-			if i.keyOutOfBounds(ikey.UserKey, i.upper, i.endKeyInclusive) {
-				i.exhaustedBounds = +1
-				return nil, base.LazyValue{}
+			if i.upper != nil {
+				cmp := i.cmp(ikey.UserKey, i.upper)
+				if (!i.endKeyInclusive && cmp >= 0) || cmp > 0 {
+					i.exhaustedBounds = +1
+					return nil, base.LazyValue{}
+				}
 			}
 			// Want to skip to the next block.
 			dontSeekWithinBlock = true
@@ -897,9 +898,12 @@ func (i *singleLevelIterator) seekGEHelper(
 	}
 	if !dontSeekWithinBlock {
 		if ikey, val := i.data.SeekGE(key, flags.DisableTrySeekUsingNext()); ikey != nil {
-			if i.keyOutOfBounds(ikey.UserKey, i.blockUpper, i.endKeyInclusive) {
-				i.exhaustedBounds = +1
-				return nil, base.LazyValue{}
+			if i.blockUpper != nil {
+				cmp := i.cmp(ikey.UserKey, i.blockUpper)
+				if (!i.endKeyInclusive && cmp >= 0) || cmp > 0 {
+					i.exhaustedBounds = +1
+					return nil, base.LazyValue{}
+				}
 			}
 			return ikey, val
 		}
@@ -1179,9 +1183,12 @@ func (i *singleLevelIterator) firstInternal() (*InternalKey, base.LazyValue) {
 	}
 	if result == loadBlockOK {
 		if ikey, val := i.data.First(); ikey != nil {
-			if i.keyOutOfBounds(ikey.UserKey, i.blockUpper, i.endKeyInclusive) {
-				i.exhaustedBounds = +1
-				return nil, base.LazyValue{}
+			if i.blockUpper != nil {
+				cmp := i.cmp(ikey.UserKey, i.blockUpper)
+				if (!i.endKeyInclusive && cmp >= 0) || cmp > 0 {
+					i.exhaustedBounds = +1
+					return nil, base.LazyValue{}
+				}
 			}
 			return ikey, val
 		}
@@ -1193,9 +1200,12 @@ func (i *singleLevelIterator) firstInternal() (*InternalKey, base.LazyValue) {
 		// ikey.UserKey since even though this is the block separator, the
 		// same user key can span multiple blocks. If upper is exclusive we
 		// use >= below, else we use >.
-		if i.keyOutOfBounds(ikey.UserKey, i.upper, i.endKeyInclusive) {
-			i.exhaustedBounds = +1
-			return nil, base.LazyValue{}
+		if i.upper != nil {
+			cmp := i.cmp(ikey.UserKey, i.upper)
+			if (!i.endKeyInclusive && cmp >= 0) || cmp > 0 {
+				i.exhaustedBounds = +1
+				return nil, base.LazyValue{}
+			}
 		}
 		// Else fall through to skipForward.
 	}
@@ -1280,9 +1290,12 @@ func (i *singleLevelIterator) Next() (*InternalKey, base.LazyValue) {
 		return nil, base.LazyValue{}
 	}
 	if key, val := i.data.Next(); key != nil {
-		if i.keyOutOfBounds(key.UserKey, i.blockUpper, i.endKeyInclusive) {
-			i.exhaustedBounds = +1
-			return nil, base.LazyValue{}
+		if i.blockUpper != nil {
+			cmp := i.cmp(key.UserKey, i.blockUpper)
+			if (!i.endKeyInclusive && cmp >= 0) || cmp > 0 {
+				i.exhaustedBounds = +1
+				return nil, base.LazyValue{}
+			}
 		}
 		return key, val
 	}
@@ -1302,9 +1315,12 @@ func (i *singleLevelIterator) NextPrefix(succKey []byte) (*InternalKey, base.Laz
 		return nil, base.LazyValue{}
 	}
 	if key, val := i.data.nextPrefix(succKey); key != nil {
-		if i.keyOutOfBounds(key.UserKey, i.blockUpper, i.endKeyInclusive) {
-			i.exhaustedBounds = +1
-			return nil, base.LazyValue{}
+		if i.blockUpper != nil {
+			cmp := i.cmp(key.UserKey, i.blockUpper)
+			if (!i.endKeyInclusive && cmp >= 0) || cmp > 0 {
+				i.exhaustedBounds = +1
+				return nil, base.LazyValue{}
+			}
 		}
 		return key, val
 	}
@@ -1340,14 +1356,20 @@ func (i *singleLevelIterator) NextPrefix(succKey []byte) (*InternalKey, base.Laz
 		// though this is the block separator, the same user key can span
 		// multiple blocks. If upper is exclusive we use >= below, else we use
 		// >.
-		if i.keyOutOfBounds(ikey.UserKey, i.upper, i.endKeyInclusive) {
-			i.exhaustedBounds = +1
-			return nil, base.LazyValue{}
+		if i.upper != nil {
+			cmp := i.cmp(ikey.UserKey, i.upper)
+			if (!i.endKeyInclusive && cmp >= 0) || cmp > 0 {
+				i.exhaustedBounds = +1
+				return nil, base.LazyValue{}
+			}
 		}
 	} else if key, val := i.data.SeekGE(succKey, base.SeekGEFlagsNone); key != nil {
-		if i.keyOutOfBounds(key.UserKey, i.blockUpper, i.endKeyInclusive) {
-			i.exhaustedBounds = +1
-			return nil, base.LazyValue{}
+		if i.blockUpper != nil {
+			cmp := i.cmp(key.UserKey, i.blockUpper)
+			if (!i.endKeyInclusive && cmp >= 0) || cmp > 0 {
+				i.exhaustedBounds = +1
+				return nil, base.LazyValue{}
+			}
 		}
 		return key, val
 	}
@@ -1402,16 +1424,22 @@ func (i *singleLevelIterator) skipForward() (*InternalKey, base.LazyValue) {
 			// keys >= key.UserKey since even though this is the block
 			// separator, the same user key can span multiple blocks. If upper
 			// is exclusive we use >= below, else we use >.
-			if i.keyOutOfBounds(key.UserKey, i.upper, i.endKeyInclusive) {
-				i.exhaustedBounds = +1
-				return nil, base.LazyValue{}
+			if i.upper != nil {
+				cmp := i.cmp(key.UserKey, i.upper)
+				if (!i.endKeyInclusive && cmp >= 0) || cmp > 0 {
+					i.exhaustedBounds = +1
+					return nil, base.LazyValue{}
+				}
 			}
 			continue
 		}
 		if key, val := i.data.First(); key != nil {
-			if i.keyOutOfBounds(key.UserKey, i.blockUpper, i.endKeyInclusive) {
-				i.exhaustedBounds = +1
-				return nil, base.LazyValue{}
+			if i.blockUpper != nil {
+				cmp := i.cmp(key.UserKey, i.blockUpper)
+				if (!i.endKeyInclusive && cmp >= 0) || cmp > 0 {
+					i.exhaustedBounds = +1
+					return nil, base.LazyValue{}
+				}
 			}
 			return key, val
 		}
@@ -1957,8 +1985,11 @@ func (i *twoLevelIterator) SeekGE(
 			// ikey.UserKey since even though this is the block separator, the
 			// same user key can span multiple index blocks. If upper is
 			// exclusive we use >= below, else we use >.
-			if i.keyOutOfBounds(ikey.UserKey, i.upper, i.endKeyInclusive) {
-				i.exhaustedBounds = +1
+			if i.upper != nil {
+				cmp := i.cmp(ikey.UserKey, i.upper)
+				if (!i.endKeyInclusive && cmp >= 0) || cmp > 0 {
+					i.exhaustedBounds = +1
+				}
 			}
 			// Fall through to skipForward.
 			dontSeekWithinSingleLevelIter = true
@@ -2142,8 +2173,11 @@ func (i *twoLevelIterator) SeekPrefixGE(
 			// ikey.UserKey since even though this is the block separator, the
 			// same user key can span multiple index blocks. If upper is
 			// exclusive we use >= below, else we use >.
-			if i.keyOutOfBounds(ikey.UserKey, i.upper, i.endKeyInclusive) {
-				i.exhaustedBounds = +1
+			if i.upper != nil {
+				cmp := i.cmp(ikey.UserKey, i.upper)
+				if (!i.endKeyInclusive && cmp >= 0) || cmp > 0 {
+					i.exhaustedBounds = +1
+				}
 			}
 			// Fall through to skipForward.
 			dontSeekWithinSingleLevelIter = true
@@ -2364,8 +2398,11 @@ func (i *twoLevelIterator) First() (*InternalKey, base.LazyValue) {
 		// starts with keys >= ikey.UserKey since even though this is the
 		// block separator, the same user key can span multiple index blocks.
 		// If upper is exclusive we use >= below, else we use >.
-		if i.keyOutOfBounds(ikey.UserKey, i.upper, i.endKeyInclusive) {
-			i.exhaustedBounds = +1
+		if i.upper != nil {
+			cmp := i.cmp(ikey.UserKey, i.upper)
+			if (!i.endKeyInclusive && cmp >= 0) || cmp > 0 {
+				i.exhaustedBounds = +1
+			}
 		}
 	}
 	// NB: skipForward checks whether exhaustedBounds is already +1.
@@ -2471,8 +2508,11 @@ func (i *twoLevelIterator) NextPrefix(succKey []byte) (*InternalKey, base.LazyVa
 		// Note that the next entry starts with keys >= ikey.UserKey since even
 		// though this is the block separator, the same user key can span multiple
 		// index blocks. If upper is exclusive we use >= below, else we use >.
-		if i.keyOutOfBounds(ikey.UserKey, i.upper, i.endKeyInclusive) {
-			i.exhaustedBounds = +1
+		if i.upper != nil {
+			cmp := i.cmp(ikey.UserKey, i.upper)
+			if (!i.endKeyInclusive && cmp >= 0) || cmp > 0 {
+				i.exhaustedBounds = +1
+			}
 		}
 	} else if key, val := i.singleLevelIterator.SeekGE(succKey, base.SeekGEFlagsNone); key != nil {
 		return key, val
@@ -2525,9 +2565,12 @@ func (i *twoLevelIterator) skipForward() (*InternalKey, base.LazyValue) {
 			// this is the block separator, the same user key can span
 			// multiple index blocks. If upper is exclusive we use >=
 			// below, else we use >.
-			if i.keyOutOfBounds(ikey.UserKey, i.upper, i.endKeyInclusive) {
-				i.exhaustedBounds = +1
-				// Next iteration will return.
+			if i.upper != nil {
+				cmp := i.cmp(ikey.UserKey, i.upper)
+				if (!i.endKeyInclusive && cmp >= 0) || cmp > 0 {
+					i.exhaustedBounds = +1
+					// Next iteration will return.
+				}
 			}
 		}
 	}
diff --git a/vendor/github.com/cockroachdb/pebble/table_cache.go b/vendor/github.com/cockroachdb/pebble/table_cache.go
index 846901d457..0b97ce6903 100644
--- a/vendor/github.com/cockroachdb/pebble/table_cache.go
+++ b/vendor/github.com/cockroachdb/pebble/table_cache.go
@@ -401,6 +401,7 @@ func (c *tableCacheShard) newIters(
 		) (sstable.Iterator, error)
 	}
 
+	// TODO(bananabrick): We suffer an allocation if file is a virtual sstable.
 	var ic iterCreator = v.reader
 	if file.Virtual {
 		virtualReader := sstable.MakeVirtualReader(
diff --git a/vendor/github.com/cockroachdb/pebble/vfs/default_linux_noarm.go b/vendor/github.com/cockroachdb/pebble/vfs/default_linux.go
similarity index 98%
rename from vendor/github.com/cockroachdb/pebble/vfs/default_linux_noarm.go
rename to vendor/github.com/cockroachdb/pebble/vfs/default_linux.go
index 2e6c5e6911..ec29074606 100644
--- a/vendor/github.com/cockroachdb/pebble/vfs/default_linux_noarm.go
+++ b/vendor/github.com/cockroachdb/pebble/vfs/default_linux.go
@@ -2,8 +2,8 @@
 // of this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 
-//go:build linux && !arm
-// +build linux,!arm
+//go:build linux
+// +build linux
 
 package vfs
 
diff --git a/vendor/github.com/cockroachdb/pebble/vfs/default_linux_arm.go b/vendor/github.com/cockroachdb/pebble/vfs/default_linux_arm.go
deleted file mode 100644
index 338b3c0b53..0000000000
--- a/vendor/github.com/cockroachdb/pebble/vfs/default_linux_arm.go
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
-// of this source code is governed by a BSD-style license that can be found in
-// the LICENSE file.
-
-//go:build linux && arm
-// +build linux,arm
-
-package vfs
-
-import (
-	"os"
-	"syscall"
-
-	"github.com/cockroachdb/errors"
-	"golang.org/x/sys/unix"
-)
-
-func wrapOSFileImpl(f *os.File) File {
-	lf := &linuxOnArmFile{File: f, fd: f.Fd()}
-	if lf.fd != InvalidFd {
-		lf.useSyncRange = isSyncRangeSupported(lf.fd)
-	}
-	return lf
-}
-
-func (defaultFS) OpenDir(name string) (File, error) {
-	f, err := os.OpenFile(name, syscall.O_CLOEXEC, 0)
-	if err != nil {
-		return nil, errors.WithStack(err)
-	}
-	return &linuxOnArmDir{f}, nil
-}
-
-// Assert that linuxOnArmFile and linuxOnArmDir implement vfs.File.
-var (
-	_ File = (*linuxOnArmDir)(nil)
-	_ File = (*linuxOnArmFile)(nil)
-)
-
-type linuxOnArmDir struct {
-	*os.File
-}
-
-func (d *linuxOnArmDir) Prefetch(offset int64, length int64) error      { return nil }
-func (d *linuxOnArmDir) Preallocate(offset, length int64) error         { return nil }
-func (d *linuxOnArmDir) SyncData() error                                { return d.Sync() }
-func (d *linuxOnArmDir) SyncTo(offset int64) (fullSync bool, err error) { return false, nil }
-
-type linuxOnArmFile struct {
-	*os.File
-	fd           uintptr
-	useSyncRange bool
-}
-
-func (f *linuxOnArmFile) Prefetch(offset int64, length int64) error {
-	_, _, err := unix.Syscall(unix.SYS_READAHEAD, uintptr(f.fd), uintptr(offset), uintptr(length))
-	return err
-}
-
-func (f *linuxOnArmFile) Preallocate(offset, length int64) error { return nil }
-
-func (f *linuxOnArmFile) SyncData() error {
-	// TODO(radu): does arm support unix.Fdatasync?
-	return f.Sync()
-}
-
-func (f *linuxOnArmFile) SyncTo(int64) (fullSync bool, err error) {
-	// syscall.SyncFileRange is not defined form arm (see https://github.com/cockroachdb/pebble/issues/171).
-	if err = f.Sync(); err != nil {
-		return false, err
-	}
-	return true, nil
-}
diff --git a/vendor/github.com/dgraph-io/badger/v4/levels.go b/vendor/github.com/dgraph-io/badger/v4/levels.go
index 387ee7e3ee..04aa04dc63 100644
--- a/vendor/github.com/dgraph-io/badger/v4/levels.go
+++ b/vendor/github.com/dgraph-io/badger/v4/levels.go
@@ -1073,14 +1073,13 @@ func (s *levelsController) addSplits(cd *compactDef) {
 			return
 		}
 		if i%width == width-1 {
-			// Right should always have ts=maxUint64 otherwise we'll lose keys
-			// in subcompaction. Consider the following.
+			// Right is assigned ts=0. The encoding ts bytes take MaxUint64-ts,
+			// so, those with smaller TS will be considered larger for the same key.
+			// Consider the following.
 			// Top table is [A1...C3(deleted)]
 			// bot table is [B1....C2]
-			// This will generate splits like [A1 ... C2] . Notice that we
-			// dropped the C3 which is the last key of the top table.
-			// See TestCompaction/with_split test.
-			right := y.KeyWithTs(y.ParseKey(t.Biggest()), math.MaxUint64)
+			// It will generate a split [A1 ... C0], including any records of Key C.
+			right := y.KeyWithTs(y.ParseKey(t.Biggest()), 0)
 			addRange(right)
 		}
 	}
diff --git a/vendor/golang.org/x/exp/maps/maps.go b/vendor/golang.org/x/exp/maps/maps.go
new file mode 100644
index 0000000000..ecc0dabb74
--- /dev/null
+++ b/vendor/golang.org/x/exp/maps/maps.go
@@ -0,0 +1,94 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package maps defines various functions useful with maps of any type.
+package maps
+
+// Keys returns the keys of the map m.
+// The keys will be in an indeterminate order.
+func Keys[M ~map[K]V, K comparable, V any](m M) []K {
+	r := make([]K, 0, len(m))
+	for k := range m {
+		r = append(r, k)
+	}
+	return r
+}
+
+// Values returns the values of the map m.
+// The values will be in an indeterminate order.
+func Values[M ~map[K]V, K comparable, V any](m M) []V {
+	r := make([]V, 0, len(m))
+	for _, v := range m {
+		r = append(r, v)
+	}
+	return r
+}
+
+// Equal reports whether two maps contain the same key/value pairs.
+// Values are compared using ==.
+func Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool {
+	if len(m1) != len(m2) {
+		return false
+	}
+	for k, v1 := range m1 {
+		if v2, ok := m2[k]; !ok || v1 != v2 {
+			return false
+		}
+	}
+	return true
+}
+
+// EqualFunc is like Equal, but compares values using eq.
+// Keys are still compared with ==.
+func EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M2, eq func(V1, V2) bool) bool {
+	if len(m1) != len(m2) {
+		return false
+	}
+	for k, v1 := range m1 {
+		if v2, ok := m2[k]; !ok || !eq(v1, v2) {
+			return false
+		}
+	}
+	return true
+}
+
+// Clear removes all entries from m, leaving it empty.
+func Clear[M ~map[K]V, K comparable, V any](m M) {
+	for k := range m {
+		delete(m, k)
+	}
+}
+
+// Clone returns a copy of m.  This is a shallow clone:
+// the new keys and values are set using ordinary assignment.
+func Clone[M ~map[K]V, K comparable, V any](m M) M {
+	// Preserve nil in case it matters.
+	if m == nil {
+		return nil
+	}
+	r := make(M, len(m))
+	for k, v := range m {
+		r[k] = v
+	}
+	return r
+}
+
+// Copy copies all key/value pairs in src adding them to dst.
+// When a key in src is already present in dst,
+// the value in dst will be overwritten by the value associated
+// with the key in src.
+func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) {
+	for k, v := range src {
+		dst[k] = v
+	}
+}
+
+// DeleteFunc deletes any key/value pairs from m for which del returns true.
+func DeleteFunc[M ~map[K]V, K comparable, V any](m M, del func(K, V) bool) {
+	for k, v := range m {
+		if del(k, v) {
+			delete(m, k)
+		}
+	}
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 508a692987..a77f09b07d 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -45,7 +45,7 @@ github.com/CortexFoundation/merkletree
 # github.com/CortexFoundation/statik v0.0.0-20210315012922-8bb8a7b5dc66
 ## explicit; go 1.16
 github.com/CortexFoundation/statik
-# github.com/CortexFoundation/torrentfs v1.0.43-0.20230505163706-a480ac5364fc
+# github.com/CortexFoundation/torrentfs v1.0.43-0.20230511153028-45240ab0a27a
 ## explicit; go 1.20
 github.com/CortexFoundation/torrentfs
 github.com/CortexFoundation/torrentfs/backend
@@ -92,7 +92,7 @@ github.com/anacrolix/dht/v2/types
 # github.com/anacrolix/envpprof v1.3.0
 ## explicit; go 1.12
 github.com/anacrolix/envpprof
-# github.com/anacrolix/generics v0.0.0-20230303103524-f0e0c7158a76
+# github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68
 ## explicit; go 1.18
 github.com/anacrolix/generics
 # github.com/anacrolix/go-libutp v1.2.0
@@ -137,7 +137,7 @@ github.com/anacrolix/stm/stmutil
 # github.com/anacrolix/sync v0.4.0
 ## explicit; go 1.13
 github.com/anacrolix/sync
-# github.com/anacrolix/torrent v1.50.1-0.20230429045449-7e65e55c3501
+# github.com/anacrolix/torrent v1.50.1-0.20230509054651-5703f9b5eb9a
 ## explicit; go 1.20
 github.com/anacrolix/torrent
 github.com/anacrolix/torrent/analysis
@@ -153,6 +153,7 @@ github.com/anacrolix/torrent/metainfo
 github.com/anacrolix/torrent/mmap_span
 github.com/anacrolix/torrent/mse
 github.com/anacrolix/torrent/peer_protocol
+github.com/anacrolix/torrent/peer_protocol/ut-holepunch
 github.com/anacrolix/torrent/request-strategy
 github.com/anacrolix/torrent/segments
 github.com/anacrolix/torrent/smartban
@@ -329,7 +330,7 @@ github.com/cockroachdb/errors/withstack
 # github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b
 ## explicit; go 1.16
 github.com/cockroachdb/logtags
-# github.com/cockroachdb/pebble v0.0.0-20230503231107-9e575c4c10ae
+# github.com/cockroachdb/pebble v0.0.0-20230510135629-fe7ae7a62e0f
 ## explicit; go 1.19
 github.com/cockroachdb/pebble
 github.com/cockroachdb/pebble/bloom
@@ -428,7 +429,7 @@ github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa
 ## explicit; go 1.18
 github.com/deepmap/oapi-codegen/pkg/runtime
 github.com/deepmap/oapi-codegen/pkg/types
-# github.com/dgraph-io/badger/v4 v4.0.2-0.20230504174847-9afd0a093523
+# github.com/dgraph-io/badger/v4 v4.0.2-0.20230509100715-ef0e55289cf7
 ## explicit; go 1.19
 github.com/dgraph-io/badger/v4
 github.com/dgraph-io/badger/v4/fb
@@ -1000,6 +1001,7 @@ golang.org/x/crypto/ssh/terminal
 # golang.org/x/exp v0.0.0-20230420155640-133eef4313cb
 ## explicit; go 1.20
 golang.org/x/exp/constraints
+golang.org/x/exp/maps
 golang.org/x/exp/rand
 golang.org/x/exp/slices
 # golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f
@@ -1031,7 +1033,7 @@ golang.org/x/net/ipv4
 golang.org/x/net/proxy
 golang.org/x/net/publicsuffix
 golang.org/x/net/trace
-# golang.org/x/sync v0.1.0
+# golang.org/x/sync v0.2.0
 ## explicit
 golang.org/x/sync/errgroup
 golang.org/x/sync/singleflight